home *** CD-ROM | disk | FTP | other *** search
/ Programmer 7500 / MAX_PROGRAMMERS.iso / INFO / TURBOPAS / TUTORPAS.ARC / TUTOR10.DOC < prev    next >
Encoding:
Text File  |  1989-05-21  |  110.1 KB  |  3,986 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.  
  30.                             LET'S BUILD A COMPILER!
  31.  
  32.                                        By
  33.  
  34.                             Jack W. Crenshaw, Ph.D.
  35.  
  36.                                   21 May 1989
  37.  
  38.  
  39.                            Part X: INTRODUCING "TINY"
  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 last installment, I showed you the general  idea  for  the
  87.        top-down development of  a  compiler.    I gave you the first few
  88.        steps  of  the process for compilers for  Pascal  and  C,  but  I
  89.        stopped  far  short  of  pushing  it through to completion.   The
  90.        reason was simple: if we're going to produce  a  real, functional
  91.        compiler  for  any  language, I'd rather  do  it  for  KISS,  the
  92.        language that I've been defining in this tutorial series.
  93.  
  94.        In this installment, we're going to do just that, for a subset of
  95.        KISS which I've chosen to call TINY.
  96.  
  97.        The process  will be essentially that outlined in Installment IX,
  98.        except  for  one  notable  difference.   In that  installment,  I
  99.        suggested  that  you  begin  with  a full BNF description of  the
  100.        language.  That's fine for something like Pascal or C,  for which
  101.        the language definition is  firm.   In the case of TINY, however,
  102.        we don't yet have a full  description  ... we seem to be defining
  103.        the language as we go.  That's OK.    In  fact,  it's preferable,
  104.        since we can tailor the language  slightly  as we go, to keep the
  105.        parsing easy.
  106.  
  107.        So in the development  that  follows,  we'll  actually be doing a
  108.        top-down development of BOTH the  language and its compiler.  The
  109.        BNF description will grow along with the compiler.
  110.  
  111.        In this process, there will be a number of decisions to  be made,
  112.        each of which will influence the BNF and therefore the  nature of
  113.        the language.   At  each  decision  point I'll try to remember to
  114.        explain  the  decision  and the rationale behind my choice.  That
  115.        way, if you happen to hold a different opinion and would prefer a
  116.        different option, you can choose it instead.  You  now  have  the
  117.        background  to  do  that.  I guess the important thing to note is
  118.        that  nothing  we  do  here  is  cast  in  concrete.  When YOU'RE
  119.        designing YOUR language, you should feel free to do it YOUR way.
  120.  
  121.        Many of you may be asking at this point: Why bother starting over
  122.        from  scratch?  We had a working subset of KISS as the outcome of
  123.        Installment  VII  (lexical  scanning).  Why not just extend it as
  124.        needed?  The  answer  is  threefold.    First of all, I have been
  125.        making  a  number  of changes to further simplify the program ...
  126.        changes  like  encapsulating  the  code generation procedures, so
  127.        that  we  can  convert to a different target machine more easily.
  128.        Second, I want you to see how the development can indeed  be doneA*A*
  129.                                      - 2 -
  130.  
  131. PA A
  132.  
  133.  
  134.  
  135.  
  136.  
  137.        from the top down as outlined in the last installment.   Finally,
  138.        we both need the practice.  Each time I go through this exercise,
  139.        I get a little better at it, and you will, also.
  140.  
  141.  
  142.        GETTING STARTED
  143.  
  144.        Many  years  ago  there were languages called  Tiny  BASIC,  Tiny
  145.        Pascal, and Tiny C, each of which was a subset of its parent full
  146.        language.  Tiny BASIC,  for  example,  had  only single-character
  147.        variable names and global variables.   It supported only a single
  148.        data type.  Sound familiar?  At this point we have almost all the
  149.        tools we need to build a compiler like that.
  150.  
  151.        Yet a language called Tiny-anything  still  carries  some baggage
  152.        inherited from its parent language.   I've often wondered if this
  153.        is a  good  idea.    Granted,  a  language based upon some parent
  154.        language will have the  advantage  of  familiarity, but there may
  155.        also  be  some  peculiar syntax carried over from the parent that
  156.        may tend  to add unnecessary complexity to the compiler. (Nowhere
  157.        is this more true than in Small C.)
  158.  
  159.        I've wondered just how small and simple a compiler could  be made
  160.        and  still  be  useful, if it were designed from the outset to be
  161.        both easy to use and to  parse.    Let's find out.  This language
  162.        will just be called "TINY," period.  It's a subset of KISS, which
  163.        I  also  haven't  fully  defined,  so  that  at  least  makes  us
  164.        consistent (!).  I suppose you could call it TINY KISS.  But that
  165.        opens  up a whole can of worms involving  cuter  and  cuter  (and
  166.        perhaps more risque) names, so let's just stick with TINY.
  167.  
  168.        The main limitations  of  TINY  will  be because of the things we
  169.        haven't yet covered, such as data types.  Like its cousins Tiny C
  170.        and Tiny BASIC,  TINY  will  have  only one data type, the 16-bit
  171.        integer.    The  first  version  we  develop  will also  have  no
  172.        procedure  calls  and  will  use single-character variable names,
  173.        although as you will see we can remove these restrictions without
  174.        much effort.
  175.  
  176.        The language I have in mind will share some of the  good features
  177.        of  Pascal,  C,  and Ada.  Taking a lesson from the comparison of
  178.        the Pascal and  C  compilers in the previous installment, though,
  179.        TINY will have a decided Pascal flavor.  Wherever  feasible,    a
  180.        language structure will  be  bracketed by keywords or symbols, so
  181.        that  the parser will know where it's  going  without  having  to
  182.        guess.
  183.  
  184.        One other ground rule:  As we go, I'd like  to  keep the compiler
  185.        producing real, executable code.  Even though it may not  DO much
  186.        at the beginning, it will at least do it correctly.
  187.  
  188.        Finally,  I'll  use  a couple of Pascal  restrictions  that  make
  189.        sense:  All data and procedures must be declared before  they are
  190.        used.  That makes good sense,  even  though for now the only dataA*A*
  191.                                      - 3 -
  192.  
  193. PA A
  194.  
  195.  
  196.  
  197.  
  198.  
  199.        type we'll use  is a word.  This rule in turn means that the only
  200.        reasonable place to put the  executable code for the main program
  201.        is at the end of the listing.
  202.  
  203.        The top-level definition will be similar to Pascal:
  204.  
  205.  
  206.             <program> ::= PROGRAM <top-level decl> <main> '.'
  207.  
  208.  
  209.        Already, we've reached a decision point.  My first thought was to
  210.        make the main block optional.   It  doesn't seem to make sense to
  211.        write a "program" with no main program, but it does make sense if
  212.        we're  allowing  for  multiple modules, linked together.    As  a
  213.        matter of fact,  I intend to allow for this in KISS.  But then we
  214.        begin  to open up a can of worms that I'd rather leave closed for
  215.        now.  For example, the  term "PROGRAM" really becomes a misnomer.
  216.        The MODULE of Modula-2 or the Unit of Turbo Pascal would  be more
  217.        appropriate.  Second,  what  about  scope  rules?    We'd  need a
  218.        convention for  dealing  with  name  visibility  across  modules.
  219.        Better  for  now  to  just  keep  it  simple  and ignore the idea
  220.        altogether.
  221.  
  222.        There's also a decision in choosing to require  the  main program
  223.        to  be  last.    I  toyed  with  the idea of making its  position
  224.        optional,  as  in  C.  The nature of SK*DOS, the OS I'm compiling
  225.        for, make this very easy to do.   But  this  doesn't  really make
  226.        much sense in view of the Pascal-like requirement  that  all data
  227.        and procedures  be declared before they're referenced.  Since the
  228.        main  program can only call procedures  that  have  already  been
  229.        declared, the only position that makes sense is at the end,  a la
  230.        Pascal.
  231.  
  232.        Given  the  BNF  above, let's write a parser that just recognizes
  233.        the brackets:
  234.  
  235.  
  236.        {--------------------------------------------------------------}
  237.        {  Parse and Translate a Program }
  238.  
  239.        procedure Prog;
  240.        begin
  241.           Match('p');
  242.           Header;
  243.           Prolog;
  244.           Match('.');
  245.           Epilog;
  246.        end;
  247.        {--------------------------------------------------------------}
  248.  
  249.  
  250.        The procedure Header just emits  the startup code required by the
  251.        assembler:A6A6
  252.                                      - 4 -A*A*
  253.  
  254. PA A
  255.  
  256.  
  257.  
  258.  
  259.  
  260.        {--------------------------------------------------------------}
  261.        { Write Header Info }
  262.  
  263.        procedure Header;
  264.        begin
  265.           WriteLn('WARMST', TAB, 'EQU $A01E');
  266.        end;
  267.        {--------------------------------------------------------------}
  268.  
  269.  
  270.        The procedures Prolog and  Epilog  emit  the code for identifying
  271.        the main program, and for returning to the OS:
  272.  
  273.  
  274.        {--------------------------------------------------------------}
  275.        { Write the Prolog }
  276.  
  277.        procedure Prolog;
  278.        begin
  279.           PostLabel('MAIN');
  280.        end;
  281.  
  282.  
  283.        {--------------------------------------------------------------}
  284.        { Write the Epilog }
  285.  
  286.        procedure Epilog;
  287.        begin
  288.           EmitLn('DC WARMST');
  289.           EmitLn('END MAIN');
  290.        end;
  291.        {--------------------------------------------------------------}
  292.  
  293.  
  294.        The  main program just calls Prog, and then  looks  for  a  clean
  295.        ending:
  296.  
  297.  
  298.        {--------------------------------------------------------------}
  299.        { Main Program }
  300.  
  301.        begin
  302.           Init;
  303.           Prog;
  304.           if Look <> CR then Abort('Unexpected data after ''.''');
  305.        end.
  306.        {--------------------------------------------------------------}
  307.  
  308.  
  309.        At this point, TINY  will  accept  only  one input "program," the
  310.        null program:
  311.  
  312.  
  313.             PROGRAM .   (or 'p.' in our shorthand.)A*A*
  314.                                      - 5 -
  315.  
  316. PA A
  317.  
  318.  
  319.  
  320.  
  321.  
  322.        Note, though, that the  compiler  DOES  generate correct code for
  323.        this program.  It will run, and do  what  you'd  expect  the null
  324.        program to do, that is, nothing but return gracefully to the OS.
  325.  
  326.        As a matter of interest, one of my  favorite  compiler benchmarks
  327.        is to compile, link,  and  execute  the  null program in whatever
  328.        language   is   involved.     You  can  learn  a  lot  about  the
  329.        implementation by measuring  the  overhead  in  time  required to
  330.        compile what should be a trivial case.  It's also  interesting to
  331.        measure the amount of code produced.  In many compilers, the code
  332.        can be fairly large, because they always include  the  whole run-
  333.        time  library whether they need it or not.    Early  versions  of
  334.        Turbo Pascal produced a 12K object file for  this  case.    VAX C
  335.        generates 50K!
  336.  
  337.        The  smallest  null  programs  I've  seen are those  produced  by
  338.        Modula-2 compilers, and they run about 200-800 bytes.
  339.  
  340.        In the case of TINY, we HAVE no run-time library  as  yet, so the
  341.        object code is indeed tiny:  two  bytes.    That's  got  to  be a
  342.        record, and it's  likely  to  remain  one since it is the minimum
  343.        size required by the OS.
  344.  
  345.        The  next step is to process the code for the main program.  I'll
  346.        use the Pascal BEGIN-block:
  347.  
  348.  
  349.             <main> ::= BEGIN <block> END
  350.  
  351.  
  352.        Here,  again,  we  have made a decision.  We could have chosen to
  353.        require a "PROCEDURE MAIN" sort of declaration, similar to C.   I
  354.        must  admit  that  this  is  not  a bad idea at all ...  I  don't
  355.        particularly  like  the  Pascal  approach  since I tend  to  have
  356.        trouble locating the main  program  in a Pascal listing.  But the
  357.        alternative is a little awkward, too, since you have to deal with
  358.        the  error condition where the user omits  the  main  program  or
  359.        misspells its name.  Here I'm taking the easy way out.
  360.  
  361.        Another solution to the "where is the main program" problem might
  362.        be to require a name for  the  program, and then bracket the main
  363.        by
  364.  
  365.  
  366.             BEGIN <name>
  367.             END <name>
  368.  
  369.  
  370.        similar to the convention of  Modula  2.    This  adds  a  bit of
  371.        "syntactic sugar" to the language.  Things like this are  easy to
  372.        add or change to your liking, if the language is your own design.
  373.  
  374.        To parse this definition of a main block,  change  procedure Prog
  375.        to read:A*A*
  376.                                      - 6 -
  377.  
  378. PA A
  379.  
  380.  
  381.  
  382.  
  383.  
  384.        {--------------------------------------------------------------}
  385.        {  Parse and Translate a Program }
  386.  
  387.        procedure Prog;
  388.        begin
  389.           Match('p');
  390.           Header;
  391.           Main;
  392.           Match('.');
  393.        end;
  394.        {--------------------------------------------------------------}
  395.  
  396.  
  397.        and add the new procedure:
  398.  
  399.  
  400.        {--------------------------------------------------------------}
  401.        { Parse and Translate a Main Program }
  402.  
  403.        procedure Main;
  404.        begin
  405.           Match('b');
  406.           Prolog;
  407.           Match('e');
  408.           Epilog;
  409.        end;
  410.        {--------------------------------------------------------------}
  411.  
  412.  
  413.        Now, the only legal program is:
  414.  
  415.  
  416.             PROGRAM BEGIN END . (or 'pbe.')
  417.  
  418.  
  419.        Aren't we making progress???  Well, as usual it gets better.  You
  420.        might try some deliberate errors here, like omitting  the  'b' or
  421.        the 'e', and see what happens.  As always,  the  compiler  should
  422.        flag all illegal inputs.
  423.  
  424.  
  425.        DECLARATIONS
  426.  
  427.        The obvious next step is to decide what we mean by a declaration.
  428.        My  intent  here  is to have two kinds of declarations: variables
  429.        and  procedures/functions.    At  the  top  level,   only  global
  430.        declarations are allowed, just as in C.
  431.  
  432.        For now, there  can  only be variable declarations, identified by
  433.        the keyword VAR (abbreviated 'v'):
  434.  
  435.  
  436.             <top-level decls> ::= ( <data declaration> )*A6A6
  437.                                      - 7 -A*A*
  438.  
  439. PA A
  440.  
  441.  
  442.  
  443.  
  444.  
  445.             <data declaration> ::= VAR <var-list>
  446.  
  447.  
  448.        Note that since there is only one variable type, there is no need
  449.        to  declare the type.  Later on, for full KISS, we can easily add
  450.        a type description.
  451.  
  452.        The procedure Prog becomes:
  453.  
  454.  
  455.        {--------------------------------------------------------------}
  456.        {  Parse and Translate a Program }
  457.  
  458.        procedure Prog;
  459.        begin
  460.           Match('p');
  461.           Header;
  462.           TopDecls;
  463.           Main;
  464.           Match('.');
  465.        end;
  466.        {--------------------------------------------------------------}
  467.  
  468.  
  469.        Now, add the two new procedures:
  470.  
  471.  
  472.        {--------------------------------------------------------------}
  473.        { Process a Data Declaration }
  474.  
  475.        procedure Decl;
  476.        begin
  477.           Match('v');
  478.           GetChar;
  479.        end;
  480.  
  481.  
  482.        {--------------------------------------------------------------}
  483.        { Parse and Translate Global Declarations }
  484.  
  485.        procedure TopDecls;
  486.        begin
  487.           while Look <> 'b' do
  488.              case Look of
  489.                'v': Decl;
  490.              else Abort('Unrecognized Keyword ''' + Look + '''');
  491.              end;
  492.        end;
  493.        {--------------------------------------------------------------}
  494.  
  495.  
  496.        Note that at this point, Decl  is  just  a stub.  It generates no
  497.        code, and it doesn't process a list ... every variable must occur
  498.        in a separate VAR statement.A*A*
  499.                                      - 8 -
  500.  
  501. PA A
  502.  
  503.  
  504.  
  505.  
  506.  
  507.        OK,  now  we  can have any  number  of  data  declarations,  each
  508.        starting with a 'v' for VAR,  before  the BEGIN-block.  Try a few
  509.        cases and see what happens.
  510.  
  511.  
  512.        DECLARATIONS AND SYMBOLS
  513.  
  514.        That looks pretty good, but  we're still only generating the null
  515.        program  for  output.    A  real compiler would  issue  assembler
  516.        directives to allocate storage for  the  variables.    It's about
  517.        time we actually produced some code.
  518.  
  519.        With  a  little  extra  code,  that's  an  easy  thing to do from
  520.        procedure Decl.  Modify it as follows:
  521.  
  522.  
  523.        {--------------------------------------------------------------}
  524.        { Parse and Translate a Data Declaration }
  525.  
  526.        procedure Decl;
  527.        var Name: char;
  528.        begin
  529.           Match('v');
  530.           Alloc(GetName);
  531.        end;
  532.        {--------------------------------------------------------------}
  533.  
  534.  
  535.        The procedure Alloc just  issues  a  command  to the assembler to
  536.        allocate storage:
  537.  
  538.  
  539.        {--------------------------------------------------------------}
  540.        { Allocate Storage for a Variable }
  541.  
  542.        procedure Alloc(N: char);
  543.        begin
  544.           WriteLn(N, ':', TAB, 'DC 0');
  545.        end;
  546.        {--------------------------------------------------------------}
  547.  
  548.  
  549.        Give  this  one  a  whirl.    Try  an  input  that declares  some
  550.        variables, such as:
  551.  
  552.             pvxvyvzbe.
  553.  
  554.        See how the storage is allocated?    Simple, huh?  Note also that
  555.        the entry point, "MAIN," comes out in the right place.
  556.  
  557.        For the record, a "real" compiler would also have a  symbol table
  558.        to record the variables being used.  Normally,  the  symbol table
  559.        is necessary to record the type  of  each variable.  But since in
  560.        this case  all  variables  have  the  same  type, we don't need aA*A*
  561.                                      - 9 -
  562.  
  563. PA A
  564.  
  565.  
  566.  
  567.  
  568.  
  569.        symbol  table  for  that reason.  As it turns out, we're going to
  570.        find a symbol  necessary  even without different types, but let's
  571.        postpone that need until it arises.
  572.  
  573.        Of course, we haven't really parsed the correct syntax for a data
  574.        declaration, since it involves a variable list.  Our version only
  575.        permits a single variable.  That's easy to fix, too.
  576.  
  577.        The BNF for <var-list> is
  578.  
  579.  
  580.             <var-list> ::= <ident> (, <ident>)*
  581.  
  582.  
  583.        Adding this syntax to Decl gives this new version:
  584.  
  585.  
  586.        {--------------------------------------------------------------}
  587.        { Parse and Translate a Data Declaration }
  588.  
  589.        procedure Decl;
  590.        var Name: char;
  591.        begin
  592.           Match('v');
  593.           Alloc(GetName);
  594.           while Look = ',' do begin
  595.              GetChar;
  596.              Alloc(GetName);
  597.           end;
  598.        end;
  599.        {--------------------------------------------------------------}
  600.  
  601.  
  602.        OK, now compile this code and give it  a  try.    Try a number of
  603.        lines of VAR declarations, try a list of several variables on one
  604.        line, and try combinations of the two.  Does it work?
  605.  
  606.  
  607.        INITIALIZERS
  608.  
  609.        As long as we're dealing with data declarations, one thing that's
  610.        always  bothered  me  about  Pascal  is  that  it  doesn't  allow
  611.        initializing  data items in the declaration.    That  feature  is
  612.        admittedly sort of a frill, and it  may  be  out  of  place  in a
  613.        language that purports to  be  a minimal language.  But it's also
  614.        SO easy to add that it seems a shame not  to  do  so.    The  BNF
  615.        becomes:
  616.  
  617.  
  618.             <var-list> ::= <var> ( <var> )*
  619.  
  620.             <var> ::= <ident> [ = <integer> ]ABAB
  621.                                     - 10 -A*A*
  622.  
  623. PA A
  624.  
  625.  
  626.  
  627.  
  628.  
  629.        Change Alloc as follows:
  630.  
  631.  
  632.        {--------------------------------------------------------------}
  633.        { Allocate Storage for a Variable }
  634.  
  635.        procedure Alloc(N: char);
  636.        begin
  637.           Write(N, ':', TAB, 'DC ');
  638.           if Look = '=' then begin
  639.              Match('=');
  640.              WriteLn(GetNum);
  641.              end
  642.           else
  643.              WriteLn('0');
  644.        end;
  645.        {--------------------------------------------------------------}
  646.  
  647.  
  648.        There you are: an initializer with six added lines of Pascal.
  649.  
  650.        OK, try this  version  of  TINY  and verify that you can, indeed,
  651.        give the variables initial values.
  652.  
  653.        By golly, this thing is starting to look  real!    Of  course, it
  654.        still doesn't DO anything, but it looks good, doesn't it?
  655.  
  656.        Before leaving this section, I should point out  that  we've used
  657.        two versions of function GetNum.  One, the earlier one, returns a
  658.        character value, a single digit.  The other accepts a multi-digit
  659.        integer and returns an integer value.  Either one will work here,
  660.        since WriteLn will handle either type.  But there's no  reason to
  661.        limit ourselves  to  single-digit  values  here,  so  the correct
  662.        version to use is the one that returns an integer.  Here it is:
  663.  
  664.  
  665.        {--------------------------------------------------------------}
  666.        { Get a Number }
  667.  
  668.        function GetNum: integer;
  669.        var Val: integer;
  670.        begin
  671.           Val := 0;
  672.           if not IsDigit(Look) then Expected('Integer');
  673.           while IsDigit(Look) do begin
  674.              Val := 10 * Val + Ord(Look) - Ord('0');
  675.              GetChar;
  676.           end;
  677.           GetNum := Val;
  678.        end;
  679.        {--------------------------------------------------------------}ANAN
  680.                                     - 11 -A*A*
  681.  
  682. PA A
  683.  
  684.  
  685.  
  686.  
  687.  
  688.        As a matter  of  fact,  strictly  speaking  we  should  allow for
  689.        expressions in the data field of the initializer, or at  the very
  690.        least  for  negative  values.  For  now,  let's  just  allow  for
  691.        negative values by changing the code for Alloc as follows:
  692.  
  693.  
  694.        {--------------------------------------------------------------}
  695.        { Allocate Storage for a Variable }
  696.  
  697.        procedure Alloc(N: char);
  698.        begin
  699.           if InTable(N) then Abort('Duplicate Variable Name ' + N);
  700.           ST[N] := 'v';
  701.           Write(N, ':', TAB, 'DC ');
  702.           if Look = '=' then begin
  703.              Match('=');
  704.              If Look = '-' then begin
  705.                 Write(Look);
  706.                 Match('-');
  707.              end;
  708.              WriteLn(GetNum);
  709.              end
  710.           else
  711.              WriteLn('0');
  712.        end;
  713.        {--------------------------------------------------------------}
  714.  
  715.  
  716.        Now  you should be able to  initialize  variables  with  negative
  717.        and/or multi-digit values.
  718.  
  719.  
  720.        THE SYMBOL TABLE
  721.  
  722.        There's one problem  with  the  compiler  as it stands so far: it
  723.        doesn't do anything to record a variable when we declare it.   So
  724.        the compiler is perfectly content to allocate storage for several
  725.        variables with the same name.  You can easily verify this with an
  726.        input like
  727.  
  728.  
  729.             pvavavabe.
  730.  
  731.  
  732.        Here we've declared the variable A three times.  As you  can see,
  733.        the compiler will  cheerfully  accept  that,  and  generate three
  734.        identical labels.  Not good.
  735.  
  736.        Later on,  when we start referencing variables, the compiler will
  737.        also let us reference variables  that don't exist.  The assembler
  738.        will  catch  both  of these error conditions, but it doesn't seem
  739.        friendly at all to pass such errors along to the assembler.   The
  740.        compiler should catch such things at the source language level.A6A6
  741.                                     - 12 -A*A*
  742.  
  743. PA A
  744.  
  745.  
  746.  
  747.  
  748.  
  749.        So even though we don't need a symbol table to record data types,
  750.        we ought to install  one  just to check for these two conditions.
  751.        Since at this  point  we are still restricted to single-character
  752.        variable names, the symbol table can be trivial.  To  provide for
  753.        it, first add the following  declaration at the beginning of your
  754.        program:
  755.  
  756.  
  757.             var ST: array['A'..'Z'] of char;
  758.  
  759.  
  760.        and insert the following function:
  761.  
  762.  
  763.        {--------------------------------------------------------------}
  764.        { Look for Symbol in Table }
  765.  
  766.        function InTable(n: char): Boolean;
  767.        begin
  768.           InTable := ST[n] <> ' ';
  769.        end;
  770.        {--------------------------------------------------------------}
  771.  
  772.  
  773.        We  also  need  to initialize the  table  to  all  blanks.    The
  774.        following lines in Init will do the job:
  775.  
  776.  
  777.        var i: char;
  778.        begin
  779.           for i := 'A' to 'Z' do
  780.              ST[i] := ' ';
  781.           ...
  782.  
  783.  
  784.        Finally,  insert  the  following two lines at  the  beginning  of
  785.        Alloc:
  786.  
  787.  
  788.           if InTable(N) then Abort('Duplicate Variable Name ' + N);
  789.           ST[N] := 'v';
  790.  
  791.  
  792.        That  should  do  it.  The  compiler  will  now  catch  duplicate
  793.        declarations.  Later, we can  also  use  InTable  when generating
  794.        references to the variables.
  795.  
  796.  
  797.        EXECUTABLE STATEMENTS
  798.  
  799.        At this point, we can generate a null program that has  some data
  800.        variables  declared  and  possibly initialized.  But  so  far  we
  801.        haven't arranged to generate the first line of executable code.A6A6
  802.                                     - 13 -A*A*
  803.  
  804. PA A
  805.  
  806.  
  807.  
  808.  
  809.  
  810.        Believe  it or not, though, we almost  have  a  usable  language!
  811.        What's missing is the executable code that must go into  the main
  812.        program.  But that code is just assignment statements and control
  813.        statements ... all stuff we have done before.   So  it  shouldn't
  814.        take us long to provide for them, as well.
  815.  
  816.        The BNF definition given earlier  for the main program included a
  817.        statement block, which we have so far ignored:
  818.  
  819.  
  820.             <main> ::= BEGIN <block> END
  821.  
  822.  
  823.        For now,  we  can  just  consider  a  block  to  be  a  series of
  824.        assignment statements:
  825.  
  826.  
  827.             <block> ::= (Assignment)*
  828.  
  829.  
  830.        Let's start things off by adding  a  parser for the block.  We'll
  831.        begin with a stub for the assignment statement:
  832.  
  833.  
  834.        {--------------------------------------------------------------}
  835.        { Parse and Translate an Assignment Statement }
  836.  
  837.        procedure Assignment;
  838.        begin
  839.           GetChar;
  840.        end;
  841.  
  842.  
  843.        {--------------------------------------------------------------}
  844.        { Parse and Translate a Block of Statements }
  845.  
  846.        procedure Block;
  847.        begin
  848.           while Look <> 'e' do
  849.              Assignment;
  850.        end;
  851.        {--------------------------------------------------------------}
  852.  
  853.  
  854.        Modify procedure Main to call Block as shown below:
  855.  
  856.  
  857.        {--------------------------------------------------------------}
  858.        { Parse and Translate a Main Program }
  859.  
  860.        procedure Main;
  861.        begin
  862.           Match('b');
  863.           Prolog;A*A*
  864.                                     - 14 -
  865.  
  866. PA A
  867.  
  868.  
  869.  
  870.  
  871.  
  872.           Block;
  873.           Match('e');
  874.           Epilog;
  875.        end;
  876.        {--------------------------------------------------------------}
  877.  
  878.  
  879.        This version still won't generate any code for  the   "assignment
  880.        statements" ... all it does is to eat characters  until  it  sees
  881.        the 'e' for 'END.'  But it sets the stage for what is to follow.
  882.  
  883.        The  next  step,  of  course,  is  to  flesh out the code for  an
  884.        assignment statement.  This  is  something  we've done many times
  885.        before,  so  I  won't belabor it.  This time, though, I'd like to
  886.        deal with the code generation a little differently.  Up till now,
  887.        we've always just inserted the Emits that generate output code in
  888.        line with  the parsing routines.  A little unstructured, perhaps,
  889.        but it seemed the most straightforward approach, and made it easy
  890.        to see what kind of code would be emitted for each construct.
  891.  
  892.        However, I realize that most of you are using an  80x86 computer,
  893.        so  the 68000 code generated is of little use to you.  Several of
  894.        you have asked me if the CPU-dependent code couldn't be collected
  895.        into one spot  where  it  would  be easier to retarget to another
  896.        CPU.  The answer, of course, is yes.
  897.  
  898.        To  accomplish  this,  insert  the  following  "code  generation"
  899.        routines:
  900.  
  901.  
  902.        {---------------------------------------------------------------}
  903.        { Clear the Primary Register }
  904.  
  905.        procedure Clear;
  906.        begin
  907.           EmitLn('CLR D0');
  908.        end;
  909.  
  910.  
  911.        {---------------------------------------------------------------}
  912.        { Negate the Primary Register }
  913.  
  914.        procedure Negate;
  915.        begin
  916.           EmitLn('NEG D0');
  917.        end;
  918.  
  919.  
  920.        {---------------------------------------------------------------}
  921.        { Load a Constant Value to Primary Register }
  922.  
  923.        procedure LoadConst(n: integer);
  924.        begin
  925.           Emit('MOVE #');A*A*
  926.                                     - 15 -
  927.  
  928. PA A
  929.  
  930.  
  931.  
  932.  
  933.  
  934.           WriteLn(n, ',D0');
  935.        end;
  936.  
  937.  
  938.        {---------------------------------------------------------------}
  939.        { Load a Variable to Primary Register }
  940.  
  941.        procedure LoadVar(Name: char);
  942.        begin
  943.           if not InTable(Name) then Undefined(Name);
  944.           EmitLn('MOVE ' + Name + '(PC),D0');
  945.        end;
  946.  
  947.  
  948.        {---------------------------------------------------------------}
  949.        { Push Primary onto Stack }
  950.  
  951.        procedure Push;
  952.        begin
  953.           EmitLn('MOVE D0,-(SP)');
  954.        end;
  955.  
  956.  
  957.        {---------------------------------------------------------------}
  958.        { Add Top of Stack to Primary }
  959.  
  960.        procedure PopAdd;
  961.        begin
  962.           EmitLn('ADD (SP)+,D0');
  963.        end;
  964.  
  965.  
  966.        {---------------------------------------------------------------}
  967.        { Subtract Primary from Top of Stack }
  968.  
  969.        procedure PopSub;
  970.        begin
  971.           EmitLn('SUB (SP)+,D0');
  972.           EmitLn('NEG D0');
  973.        end;
  974.  
  975.  
  976.        {---------------------------------------------------------------}
  977.        { Multiply Top of Stack by Primary }
  978.  
  979.        procedure PopMul;
  980.        begin
  981.           EmitLn('MULS (SP)+,D0');
  982.        end;
  983.  
  984.  
  985.        {---------------------------------------------------------------}
  986.        { Divide Top of Stack by Primary }A6A6
  987.                                     - 16 -A*A*
  988.  
  989. PA A
  990.  
  991.  
  992.  
  993.  
  994.  
  995.        procedure PopDiv;
  996.        begin
  997.           EmitLn('MOVE (SP)+,D7');
  998.           EmitLn('EXT.L D7');
  999.           EmitLn('DIVS D0,D7');
  1000.           EmitLn('MOVE D7,D0');
  1001.        end;
  1002.  
  1003.  
  1004.        {---------------------------------------------------------------}
  1005.        { Store Primary to Variable }
  1006.  
  1007.        procedure Store(Name: char);
  1008.        begin
  1009.           if not InTable(Name) then Undefined(Name);
  1010.           EmitLn('LEA ' + Name + '(PC),A0');
  1011.           EmitLn('MOVE D0,(A0)')
  1012.        end;
  1013.        {---------------------------------------------------------------}
  1014.  
  1015.  
  1016.        The  nice  part  of  this  approach,  of  course,  is that we can
  1017.        retarget  the compiler to a new CPU  simply  by  rewriting  these
  1018.        "code generator" procedures.  In  addition,  we  will  find later
  1019.        that we can improve the code quality by tweaking these routines a
  1020.        bit, without having to modify the compiler proper.
  1021.  
  1022.        Note that both LoadVar  and  Store check the symbol table to make
  1023.        sure that the variable is defined.  The  error  handler Undefined
  1024.        simply calls Abort:
  1025.  
  1026.  
  1027.        {--------------------------------------------------------------}
  1028.        { Report an Undefined Identifier }
  1029.  
  1030.        procedure Undefined(n: string);
  1031.        begin
  1032.           Abort('Undefined Identifier ' + n);
  1033.        end;
  1034.        {--------------------------------------------------------------}
  1035.  
  1036.  
  1037.        OK, we are now finally ready to begin processing executable code.
  1038.        We'll  do  that  by  replacing  the  stub  version  of  procedure
  1039.        Assignment.
  1040.  
  1041.        We've been down this  road  many times before, so this should all
  1042.        be familiar to you.    In fact, except for the changes associated
  1043.        with the code generation, we  could just copy the procedures from
  1044.        Part  VII.    Since we are making some changes, I won't just copy
  1045.        them, but we will go a little faster than usual.
  1046.  
  1047.        The BNF for the assignment statement is:A6A6
  1048.                                     - 17 -A*A*
  1049.  
  1050. PA A
  1051.  
  1052.  
  1053.  
  1054.  
  1055.  
  1056.             <assignment> ::= <ident> = <expression>
  1057.  
  1058.             <expression> ::= <first term> ( <addop> <term> )*
  1059.  
  1060.             <first term> ::= <first factor> <rest>
  1061.  
  1062.             <term> ::= <factor> <rest>
  1063.  
  1064.             <rest> ::= ( <mulop> <factor> )*
  1065.  
  1066.             <first factor> ::= [ <addop> ] <factor>
  1067.  
  1068.             <factor> ::= <var> | <number> | ( <expression> )
  1069.  
  1070.  
  1071.        This version of the BNF is  also  a bit different than we've used
  1072.        before ... yet another "variation on the theme of an expression."
  1073.        This particular version  has  what  I  consider  to  be  the best
  1074.        treatment  of  the  unary minus.  As you'll see later, it lets us
  1075.        handle   negative  constant  values  efficiently.    It's   worth
  1076.        mentioning  here  that  we  have  often  seen  the advantages  of
  1077.        "tweaking"  the  BNF  as we go, to help make the language easy to
  1078.        parse.    What  you're looking at here is a bit different:  we've
  1079.        tweaked  the  BNF  to make the CODE  GENERATION  more  efficient!
  1080.        That's a first for this series.
  1081.  
  1082.        Anyhow, the following code implements the BNF:
  1083.  
  1084.  
  1085.        {---------------------------------------------------------------}
  1086.        { Parse and Translate a Math Factor }
  1087.  
  1088.        procedure Expression; Forward;
  1089.  
  1090.        procedure Factor;
  1091.        begin
  1092.           if Look = '(' then begin
  1093.              Match('(');
  1094.              Expression;
  1095.              Match(')');
  1096.              end
  1097.           else if IsAlpha(Look) then
  1098.              LoadVar(GetName)
  1099.           else
  1100.              LoadConst(GetNum);
  1101.        end;
  1102.  
  1103.  
  1104.        {--------------------------------------------------------------}
  1105.        { Parse and Translate a Negative Factor }
  1106.  
  1107.        procedure NegFactor;
  1108.        begin
  1109.           Match('-');A*A*
  1110.                                     - 18 -
  1111.  
  1112. PA A
  1113.  
  1114.  
  1115.  
  1116.  
  1117.  
  1118.           if IsDigit(Look) then
  1119.              LoadConst(-GetNum)
  1120.           else begin
  1121.              Factor;
  1122.              Negate;
  1123.           end;
  1124.        end;
  1125.  
  1126.  
  1127.        {--------------------------------------------------------------}
  1128.        { Parse and Translate a Leading Factor }
  1129.  
  1130.        procedure FirstFactor;
  1131.        begin
  1132.           case Look of
  1133.             '+': begin
  1134.                     Match('+');
  1135.                     Factor;
  1136.                  end;
  1137.             '-': NegFactor;
  1138.           else  Factor;
  1139.           end;
  1140.        end;
  1141.  
  1142.  
  1143.        {--------------------------------------------------------------}
  1144.        { Recognize and Translate a Multiply }
  1145.  
  1146.        procedure Multiply;
  1147.        begin
  1148.           Match('*');
  1149.           Factor;
  1150.           PopMul;
  1151.        end;
  1152.  
  1153.  
  1154.        {-------------------------------------------------------------}
  1155.        { Recognize and Translate a Divide }
  1156.  
  1157.        procedure Divide;
  1158.        begin
  1159.           Match('/');
  1160.           Factor;
  1161.           PopDiv;
  1162.        end;
  1163.  
  1164.  
  1165.        {---------------------------------------------------------------}
  1166.        { Common Code Used by Term and FirstTerm }
  1167.  
  1168.        procedure Term1;
  1169.        begin
  1170.           while IsMulop(Look) do begin
  1171.              Push;A*A*
  1172.                                     - 19 -
  1173.  
  1174. PA A
  1175.  
  1176.  
  1177.  
  1178.  
  1179.  
  1180.              case Look of
  1181.               '*': Multiply;
  1182.               '/': Divide;
  1183.              end;
  1184.           end;
  1185.        end;
  1186.  
  1187.  
  1188.        {---------------------------------------------------------------}
  1189.        { Parse and Translate a Math Term }
  1190.  
  1191.        procedure Term;
  1192.        begin
  1193.           Factor;
  1194.           Term1;
  1195.        end;
  1196.  
  1197.  
  1198.        {---------------------------------------------------------------}
  1199.        { Parse and Translate a Leading Term }
  1200.  
  1201.        procedure FirstTerm;
  1202.        begin
  1203.           FirstFactor;
  1204.           Term1;
  1205.        end;
  1206.  
  1207.  
  1208.        {--------------------------------------------------------------}
  1209.        { Recognize and Translate an Add }
  1210.  
  1211.        procedure Add;
  1212.        begin
  1213.           Match('+');
  1214.           Term;
  1215.           PopAdd;
  1216.        end;
  1217.  
  1218.  
  1219.        {-------------------------------------------------------------}
  1220.        { Recognize and Translate a Subtract }
  1221.  
  1222.        procedure Subtract;
  1223.        begin
  1224.           Match('-');
  1225.           Term;
  1226.           PopSub;
  1227.        end;
  1228.  
  1229.  
  1230.        {---------------------------------------------------------------}
  1231.        { Parse and Translate an Expression }
  1232.  
  1233.        procedure Expression;A*A*
  1234.                                     - 20 -
  1235.  
  1236. PA A
  1237.  
  1238.  
  1239.  
  1240.  
  1241.  
  1242.        begin
  1243.           FirstTerm;
  1244.           while IsAddop(Look) do begin
  1245.              Push;
  1246.              case Look of
  1247.               '+': Add;
  1248.               '-': Subtract;
  1249.              end;
  1250.           end;
  1251.        end;
  1252.  
  1253.  
  1254.        {--------------------------------------------------------------}
  1255.        { Parse and Translate an Assignment Statement }
  1256.  
  1257.        procedure Assignment;
  1258.        var Name: char;
  1259.        begin
  1260.           Name := GetName;
  1261.           Match('=');
  1262.           Expression;
  1263.           Store(Name);
  1264.        end;
  1265.        {--------------------------------------------------------------}
  1266.  
  1267.  
  1268.        OK, if you've  got  all  this  code inserted, then compile it and
  1269.        check  it out.  You should  be  seeing  reasonable-looking  code,
  1270.        representing a complete program that will  assemble  and execute.
  1271.        We have a compiler!
  1272.  
  1273.  
  1274.        BOOLEANS
  1275.  
  1276.        The next step should also  be  familiar  to  you.    We  must add
  1277.        Boolean  expressions  and relational operations.    Again,  since
  1278.        we've already dealt with them more than once,  I  won't elaborate
  1279.        much on them, except  where  they  are  different from what we've
  1280.        done before.  Again, we won't just copy from other  files because
  1281.        I've changed a few things just a bit.  Most  of  the changes just
  1282.        involve encapsulating the machine-dependent parts as  we  did for
  1283.        the   arithmetic  operations.    I've  also  modified   procedure
  1284.        NotFactor  somewhat,  to  parallel  the structure of FirstFactor.
  1285.        Finally,  I  corrected  an  error  in  the  object code  for  the
  1286.        relational operators:  The Scc instruction I used  only  sets the
  1287.        low 8 bits of D0.  We want all 16 bits set for a logical true, so
  1288.        I've added an instruction to sign-extend the low byte.
  1289.  
  1290.        To begin, we're going to need some more recognizers:
  1291.  
  1292.  
  1293.        {--------------------------------------------------------------}
  1294.        { Recognize a Boolean Orop }A6A6
  1295.                                     - 21 -A*A*
  1296.  
  1297. PA A
  1298.  
  1299.  
  1300.  
  1301.  
  1302.  
  1303.        function IsOrop(c: char): boolean;
  1304.        begin
  1305.           IsOrop := c in ['|', '~'];
  1306.        end;
  1307.  
  1308.  
  1309.        {--------------------------------------------------------------}
  1310.        { Recognize a Relop }
  1311.  
  1312.        function IsRelop(c: char): boolean;
  1313.        begin
  1314.           IsRelop := c in ['=', '#', '<', '>'];
  1315.        end;
  1316.        {--------------------------------------------------------------}
  1317.  
  1318.  
  1319.        Also, we're going to need some more code generation routines:
  1320.  
  1321.  
  1322.        {---------------------------------------------------------------}
  1323.        { Complement the Primary Register }
  1324.  
  1325.        procedure NotIt;
  1326.        begin
  1327.           EmitLn('NOT D0');
  1328.        end;
  1329.        {---------------------------------------------------------------}
  1330.        .
  1331.        .
  1332.        .
  1333.        {---------------------------------------------------------------}
  1334.        { AND Top of Stack with Primary }
  1335.  
  1336.        procedure PopAnd;
  1337.        begin
  1338.           EmitLn('AND (SP)+,D0');
  1339.        end;
  1340.  
  1341.  
  1342.        {---------------------------------------------------------------}
  1343.        { OR Top of Stack with Primary }
  1344.  
  1345.        procedure PopOr;
  1346.        begin
  1347.           EmitLn('OR (SP)+,D0');
  1348.        end;
  1349.  
  1350.  
  1351.        {---------------------------------------------------------------}
  1352.        { XOR Top of Stack with Primary }
  1353.  
  1354.        procedure PopXor;
  1355.        begin
  1356.           EmitLn('EOR (SP)+,D0');A*A*
  1357.                                     - 22 -
  1358.  
  1359. PA A
  1360.  
  1361.  
  1362.  
  1363.  
  1364.  
  1365.        end;
  1366.  
  1367.  
  1368.        {---------------------------------------------------------------}
  1369.        { Compare Top of Stack with Primary }
  1370.  
  1371.        procedure PopCompare;
  1372.        begin
  1373.           EmitLn('CMP (SP)+,D0');
  1374.        end;
  1375.  
  1376.  
  1377.        {---------------------------------------------------------------}
  1378.        { Set D0 If Compare was = }
  1379.  
  1380.        procedure SetEqual;
  1381.        begin
  1382.           EmitLn('SEQ D0');
  1383.           EmitLn('EXT D0');
  1384.        end;
  1385.  
  1386.  
  1387.        {---------------------------------------------------------------}
  1388.        { Set D0 If Compare was != }
  1389.  
  1390.        procedure SetNEqual;
  1391.        begin
  1392.           EmitLn('SNE D0');
  1393.           EmitLn('EXT D0');
  1394.        end;
  1395.  
  1396.  
  1397.        {---------------------------------------------------------------}
  1398.        { Set D0 If Compare was > }
  1399.  
  1400.        procedure SetGreater;
  1401.        begin
  1402.           EmitLn('SLT D0');
  1403.           EmitLn('EXT D0');
  1404.        end;
  1405.  
  1406.  
  1407.        {---------------------------------------------------------------}
  1408.        { Set D0 If Compare was < }
  1409.  
  1410.        procedure SetLess;
  1411.        begin
  1412.           EmitLn('SGT D0');
  1413.           EmitLn('EXT D0');
  1414.        end;
  1415.        {---------------------------------------------------------------}ANAN
  1416.                                     - 23 -A*A*
  1417.  
  1418. PA A
  1419.  
  1420.  
  1421.  
  1422.  
  1423.  
  1424.        All of this  gives us the tools we need.  The BNF for the Boolean
  1425.        expressions is:
  1426.  
  1427.  
  1428.             <bool-expr> ::= <bool-term> ( <orop> <bool-term> )*
  1429.  
  1430.             <bool-term> ::= <not-factor> ( <andop> <not-factor> )*
  1431.  
  1432.             <not-factor> ::= [ '!' ] <relation>
  1433.  
  1434.             <relation> ::= <expression> [ <relop> <expression> ]
  1435.  
  1436.  
  1437.        Sharp-eyed readers might  note  that this syntax does not include
  1438.        the non-terminal  "bool-factor" used in earlier versions.  It was
  1439.        needed then because I also allowed for the Boolean constants TRUE
  1440.        and FALSE.   But  remember  that  in TINY there is no distinction
  1441.        made between Boolean and arithmetic  types ... they can be freely
  1442.        intermixed.   So there is really no  need  for  these  predefined
  1443.        values ... we can just use -1 and 0, respectively.
  1444.  
  1445.        In C terminology, we could always use the defines:
  1446.  
  1447.  
  1448.             #define TRUE -1
  1449.             #define FALSE 0
  1450.  
  1451.  
  1452.        (That is, if TINY had a  preprocessor.)   Later on, when we allow
  1453.        for  declarations  of  constants,  these  two   values   will  be
  1454.        predefined by the language.
  1455.  
  1456.        The reason that I'm harping on this is that  I've  already  tried
  1457.        the alternative, which is to  include TRUE and FALSE as keywords.
  1458.        The problem with that approach is that it  then  requires lexical
  1459.        scanning for EVERY variable name  in every expression.  If you'll
  1460.        recall,  I pointed out in Installment VII  that  this  slows  the
  1461.        compiler  down considerably.  As long as  keywords  can't  be  in
  1462.        expressions, we need to do the scanning only at the  beginning of
  1463.        every  new  statement  ...  quite  an improvement.  So using  the
  1464.        syntax above not only simplifies the parsing, but  speeds  up the
  1465.        scanning as well.
  1466.  
  1467.        OK, given that we're  all  satisfied  with  the syntax above, the
  1468.        corresponding code is shown below:
  1469.  
  1470.  
  1471.        {---------------------------------------------------------------}
  1472.        { Recognize and Translate a Relational "Equals" }
  1473.  
  1474.        procedure Equals;
  1475.        begin
  1476.           Match('=');
  1477.           Expression;A*A*
  1478.                                     - 24 -
  1479.  
  1480. PA A
  1481.  
  1482.  
  1483.  
  1484.  
  1485.  
  1486.           PopCompare;
  1487.           SetEqual;
  1488.        end;
  1489.  
  1490.  
  1491.        {---------------------------------------------------------------}
  1492.        { Recognize and Translate a Relational "Not Equals" }
  1493.  
  1494.        procedure NotEquals;
  1495.        begin
  1496.           Match('#');
  1497.           Expression;
  1498.           PopCompare;
  1499.           SetNEqual;
  1500.        end;
  1501.  
  1502.  
  1503.        {---------------------------------------------------------------}
  1504.        { Recognize and Translate a Relational "Less Than" }
  1505.  
  1506.        procedure Less;
  1507.        begin
  1508.           Match('<');
  1509.           Expression;
  1510.           PopCompare;
  1511.           SetLess;
  1512.        end;
  1513.  
  1514.  
  1515.        {---------------------------------------------------------------}
  1516.        { Recognize and Translate a Relational "Greater Than" }
  1517.  
  1518.        procedure Greater;
  1519.        begin
  1520.           Match('>');
  1521.           Expression;
  1522.           PopCompare;
  1523.           SetGreater;
  1524.        end;
  1525.  
  1526.  
  1527.        {---------------------------------------------------------------}
  1528.        { Parse and Translate a Relation }
  1529.  
  1530.  
  1531.        procedure Relation;
  1532.        begin
  1533.           Expression;
  1534.           if IsRelop(Look) then begin
  1535.              Push;
  1536.              case Look of
  1537.               '=': Equals;
  1538.               '#': NotEquals;
  1539.               '<': Less;A*A*
  1540.                                     - 25 -
  1541.  
  1542. PA A
  1543.  
  1544.  
  1545.  
  1546.  
  1547.  
  1548.               '>': Greater;
  1549.              end;
  1550.           end;
  1551.        end;
  1552.  
  1553.  
  1554.        {---------------------------------------------------------------}
  1555.        { Parse and Translate a Boolean Factor with Leading NOT }
  1556.  
  1557.        procedure NotFactor;
  1558.        begin
  1559.           if Look = '!' then begin
  1560.              Match('!');
  1561.              Relation;
  1562.              NotIt;
  1563.              end
  1564.           else
  1565.              Relation;
  1566.        end;
  1567.  
  1568.  
  1569.        {---------------------------------------------------------------}
  1570.        { Parse and Translate a Boolean Term }
  1571.  
  1572.        procedure BoolTerm;
  1573.        begin
  1574.           NotFactor;
  1575.           while Look = '&' do begin
  1576.              Push;
  1577.              Match('&');
  1578.              NotFactor;
  1579.              PopAnd;
  1580.           end;
  1581.        end;
  1582.  
  1583.  
  1584.        {--------------------------------------------------------------}
  1585.        { Recognize and Translate a Boolean OR }
  1586.  
  1587.        procedure BoolOr;
  1588.        begin
  1589.           Match('|');
  1590.           BoolTerm;
  1591.           PopOr;
  1592.        end;
  1593.  
  1594.  
  1595.        {--------------------------------------------------------------}
  1596.        { Recognize and Translate an Exclusive Or }
  1597.  
  1598.        procedure BoolXor;
  1599.        begin
  1600.           Match('~');
  1601.           BoolTerm;A*A*
  1602.                                     - 26 -
  1603.  
  1604. PA A
  1605.  
  1606.  
  1607.  
  1608.  
  1609.  
  1610.           PopXor;
  1611.        end;
  1612.  
  1613.  
  1614.        {---------------------------------------------------------------}
  1615.        { Parse and Translate a Boolean Expression }
  1616.  
  1617.        procedure BoolExpression;
  1618.        begin
  1619.           BoolTerm;
  1620.           while IsOrOp(Look) do begin
  1621.              Push;
  1622.              case Look of
  1623.               '|': BoolOr;
  1624.               '~': BoolXor;
  1625.              end;
  1626.           end;
  1627.        end;
  1628.        {--------------------------------------------------------------}
  1629.  
  1630.  
  1631.        To tie it all together, don't forget to change the  references to
  1632.        Expression in  procedures Factor and Assignment so that they call
  1633.        BoolExpression instead.
  1634.  
  1635.        OK, if  you've  got  all  that typed in, compile it and give it a
  1636.        whirl.    First,  make  sure  you  can  still parse  an  ordinary
  1637.        arithmetic expression.  Then, try a Boolean one.    Finally, make
  1638.        sure  that you can assign the results of  relations.    Try,  for
  1639.        example:
  1640.  
  1641.             pvx,y,zbx=z>ye.
  1642.  
  1643.        which stands for:
  1644.  
  1645.             PROGRAM
  1646.             VAR X,Y,Z
  1647.             BEGIN
  1648.             X = Z > Y
  1649.             END.
  1650.  
  1651.  
  1652.        See how this assigns a Boolean value to X?
  1653.  
  1654.        CONTROL STRUCTURES
  1655.  
  1656.        We're almost home.   With  Boolean  expressions  in place, it's a
  1657.        simple  matter  to  add control structures.  For TINY, we'll only
  1658.        allow two kinds of them, the IF and the WHILE:
  1659.  
  1660.  
  1661.             <if> ::= IF <bool-expression> <block> [ ELSE <block>] ENDIF
  1662.  
  1663.             <while> ::= WHILE <bool-expression> <block> ENDWHILEA*A*
  1664.                                     - 27 -
  1665.  
  1666. PA A
  1667.  
  1668.  
  1669.  
  1670.  
  1671.  
  1672.        Once  again,  let  me  spell  out the decisions implicit in  this
  1673.        syntax, which departs strongly from that of C or Pascal.  In both
  1674.        of those languages, the "body" of an IF or WHILE is regarded as a
  1675.        single  statement.  If you intend to use a block of more than one
  1676.        statement, you have to build a compound statement using BEGIN-END
  1677.        (in Pascal) or  '{}' (in C).  In TINY (and KISS) there is no such
  1678.        thing as a compound statement  ... single or multiple they're all
  1679.        just blocks to these languages.
  1680.  
  1681.        In KISS, all the control structures will have explicit and unique
  1682.        keywords  bracketing  the  statement block, so there  can  be  no
  1683.        confusion as to where things begin  and  end.  This is the modern
  1684.        approach, used in such respected languages as Ada  and  Modula 2,
  1685.        and it completely eliminates the problem of the "dangling else."
  1686.  
  1687.        Note  that I could have chosen to use the same keyword END to end
  1688.        all  the constructs, as is done in Pascal.  (The closing '}' in C
  1689.        serves the same purpose.)  But this has always led  to confusion,
  1690.        which is why Pascal programmers tend to write things like
  1691.  
  1692.  
  1693.             end { loop }
  1694.  
  1695.        or   end { if }
  1696.  
  1697.  
  1698.        As I explained in  Part  V,  using  unique terminal keywords does
  1699.        increase  the  size  of the keyword list and therefore slows down
  1700.        the  scanning, but in this case it seems a small price to pay for
  1701.        the added insurance.   Better  to find the errors at compile time
  1702.        rather than run time.
  1703.  
  1704.        One last thought:  The two constructs above each  have  the  non-
  1705.        terminals
  1706.  
  1707.  
  1708.              <bool-expression> and <block>
  1709.  
  1710.  
  1711.        juxtaposed with no separating keyword.  In Pascal we would expect
  1712.        the keywords THEN and DO in these locations.
  1713.  
  1714.        I have no problem with leaving out these keywords, and the parser
  1715.        has no trouble either, ON CONDITION that we make no errors in the
  1716.        bool-expression part.  On  the  other hand, if we were to include
  1717.        these extra keywords we would get yet one more level of insurance
  1718.        at very little  cost,  and  I  have no problem with that, either.
  1719.        Use your best judgment as to which way to go.
  1720.  
  1721.        OK, with that bit of explanation let's proceed.  As  usual, we're
  1722.        going to need some new  code generation routines.  These generate
  1723.        the code for conditional and unconditional branches:ABAB
  1724.                                     - 28 -A*A*
  1725.  
  1726. PA A
  1727.  
  1728.  
  1729.  
  1730.  
  1731.  
  1732.        {---------------------------------------------------------------}
  1733.        { Branch Unconditional  }
  1734.  
  1735.        procedure Branch(L: string);
  1736.        begin
  1737.           EmitLn('BRA ' + L);
  1738.        end;
  1739.  
  1740.  
  1741.        {---------------------------------------------------------------}
  1742.        { Branch False }
  1743.  
  1744.        procedure BranchFalse(L: string);
  1745.        begin
  1746.           EmitLn('TST D0');
  1747.           EmitLn('BEQ ' + L);
  1748.        end;
  1749.        {--------------------------------------------------------------}
  1750.  
  1751.  
  1752.        Except for the encapsulation of  the code generation, the code to
  1753.        parse the control constructs is the same as you've seen before:
  1754.  
  1755.  
  1756.        {---------------------------------------------------------------}
  1757.        { Recognize and Translate an IF Construct }
  1758.  
  1759.        procedure Block; Forward;
  1760.  
  1761.  
  1762.        procedure DoIf;
  1763.        var L1, L2: string;
  1764.        begin
  1765.           Match('i');
  1766.           BoolExpression;
  1767.           L1 := NewLabel;
  1768.           L2 := L1;
  1769.           BranchFalse(L1);
  1770.           Block;
  1771.           if Look = 'l' then begin
  1772.              Match('l');
  1773.              L2 := NewLabel;
  1774.              Branch(L2);
  1775.              PostLabel(L1);
  1776.              Block;
  1777.           end;
  1778.           PostLabel(L2);
  1779.           Match('e');
  1780.        end;
  1781.  
  1782.  
  1783.        {--------------------------------------------------------------}
  1784.        { Parse and Translate a WHILE Statement }A6A6
  1785.                                     - 29 -A*A*
  1786.  
  1787. PA A
  1788.  
  1789.  
  1790.  
  1791.  
  1792.  
  1793.        procedure DoWhile;
  1794.        var L1, L2: string;
  1795.        begin
  1796.           Match('w');
  1797.           L1 := NewLabel;
  1798.           L2 := NewLabel;
  1799.           PostLabel(L1);
  1800.           BoolExpression;
  1801.           BranchFalse(L2);
  1802.           Block;
  1803.           Match('e');
  1804.           Branch(L1);
  1805.           PostLabel(L2);
  1806.        end;
  1807.        {--------------------------------------------------------------}
  1808.  
  1809.  
  1810.        To tie everything  together,  we need only modify procedure Block
  1811.        to recognize the "keywords" for the  IF  and WHILE.  As usual, we
  1812.        expand the definition of a block like so:
  1813.  
  1814.  
  1815.             <block> ::= ( <statement> )*
  1816.  
  1817.  
  1818.        where
  1819.  
  1820.  
  1821.             <statement> ::= <if> | <while> | <assignment>
  1822.  
  1823.  
  1824.        The corresponding code is:
  1825.  
  1826.  
  1827.        {--------------------------------------------------------------}
  1828.        { Parse and Translate a Block of Statements }
  1829.  
  1830.        procedure Block;
  1831.        begin
  1832.           while not(Look in ['e', 'l']) do begin
  1833.              case Look of
  1834.               'i': DoIf;
  1835.               'w': DoWhile;
  1836.              else Assignment;
  1837.              end;
  1838.           end;
  1839.        end;
  1840.        {--------------------------------------------------------------}
  1841.  
  1842.  
  1843.        OK,  add the routines I've given, compile and  test  them.    You
  1844.        should be able to parse the single-character versions  of  any of
  1845.        the control constructs.  It's looking pretty good!A6A6
  1846.                                     - 30 -A*A*
  1847.  
  1848. PA A
  1849.  
  1850.  
  1851.  
  1852.  
  1853.  
  1854.        As a matter  of  fact, except for the single-character limitation
  1855.        we've got a virtually complete version of TINY.  I call  it, with
  1856.        tongue planted firmly in cheek, TINY Version 0.1.
  1857.  
  1858.  
  1859.        LEXICAL SCANNING
  1860.  
  1861.        Of course, you know what's next:  We have to convert  the program
  1862.        so that  it can deal with multi-character keywords, newlines, and
  1863.        whitespace.   We have just gone through all  that  in  Part  VII.
  1864.        We'll use the distributed scanner  technique that I showed you in
  1865.        that  installment.    The  actual  implementation  is   a  little
  1866.        different because the way I'm handling newlines is different.
  1867.  
  1868.        To begin with, let's simply  allow for whitespace.  This involves
  1869.        only adding calls to SkipWhite at the end of the  three routines,
  1870.        GetName, GetNum, and Match.    A call to SkipWhite in Init primes
  1871.        the pump in case there are leading spaces.
  1872.  
  1873.        Next, we need to deal with  newlines.   This is really a two-step
  1874.        process,  since  the  treatment  of  the  newlines  with  single-
  1875.        character tokens is different from that for multi-character ones.
  1876.        We can eliminate some work by doing both  steps  at  once,  but I
  1877.        feel safer taking things one step at a time.
  1878.  
  1879.        Insert the new procedure:
  1880.  
  1881.  
  1882.        {--------------------------------------------------------------}
  1883.        { Skip Over an End-of-Line }
  1884.  
  1885.        procedure NewLine;
  1886.        begin
  1887.           while Look = CR do begin
  1888.              GetChar;
  1889.              if Look = LF then GetChar;
  1890.              SkipWhite;
  1891.           end;
  1892.        end;
  1893.        {--------------------------------------------------------------}
  1894.  
  1895.  
  1896.        Note that  we  have  seen  this  procedure  before in the form of
  1897.        Procedure Fin.  I've changed the name since this  new  one  seems
  1898.        more descriptive of the actual function.  I've  also  changed the
  1899.        code  to  allow  for multiple newlines and lines with nothing but
  1900.        white space.
  1901.  
  1902.        The next step is to insert calls to NewLine wherever we  decide a
  1903.        newline is permissible.  As I've pointed out before, this  can be
  1904.        very different in different languages.   In TINY, I've decided to
  1905.        allow them virtually anywhere.  This means that we need  calls to
  1906.        NewLine at the BEGINNING (not the end, as with SkipWhite)  of the
  1907.        procedures GetName, GetNum, and Match.A*A*
  1908.                                     - 31 -
  1909.  
  1910. PA A
  1911.  
  1912.  
  1913.  
  1914.  
  1915.  
  1916.        For procedures that have while loops, such as TopDecl, we  need a
  1917.        call  to NewLine at the beginning of the  procedure  AND  at  the
  1918.        bottom  of  each  loop.  That way, we can be assured that NewLine
  1919.        has just been called at the beginning of each  pass  through  the
  1920.        loop.
  1921.  
  1922.        If you've got all this done, try the program out and  verify that
  1923.        it will indeed handle white space and newlines.
  1924.  
  1925.        If it does, then we're  ready to deal with multi-character tokens
  1926.        and keywords.   To begin, add the additional declarations (copied
  1927.        almost verbatim from Part VII):
  1928.  
  1929.  
  1930.        {--------------------------------------------------------------}
  1931.        { Type Declarations }
  1932.  
  1933.        type Symbol = string[8];
  1934.  
  1935.             SymTab = array[1..1000] of Symbol;
  1936.  
  1937.             TabPtr = ^SymTab;
  1938.  
  1939.  
  1940.        {--------------------------------------------------------------}
  1941.        { Variable Declarations }
  1942.  
  1943.        var Look : char;             { Lookahead Character }
  1944.            Token: char;             { Encoded Token       }
  1945.            Value: string[16];       { Unencoded Token     }
  1946.  
  1947.            ST: Array['A'..'Z'] of char;
  1948.  
  1949.        {--------------------------------------------------------------}
  1950.        { Definition of Keywords and Token Types }
  1951.  
  1952.        const NKW =   9;
  1953.              NKW1 = 10;
  1954.  
  1955.        const KWlist: array[1..NKW] of Symbol =
  1956.                      ('IF', 'ELSE', 'ENDIF', 'WHILE', 'ENDWHILE',
  1957.                       'VAR', 'BEGIN', 'END', 'PROGRAM');
  1958.  
  1959.        const KWcode: string[NKW1] = 'xilewevbep';
  1960.        {--------------------------------------------------------------}
  1961.  
  1962.  
  1963.        Next, add the three procedures, also from Part VII:
  1964.  
  1965.  
  1966.        {--------------------------------------------------------------}
  1967.        { Table Lookup }
  1968.  
  1969.        function Lookup(T: TabPtr; s: string; n: integer): integer;A*A*
  1970.                                     - 32 -
  1971.  
  1972. PA A
  1973.  
  1974.  
  1975.  
  1976.  
  1977.  
  1978.        var i: integer;
  1979.            found: Boolean;
  1980.        begin
  1981.           found := false;
  1982.           i := n;
  1983.           while (i > 0) and not found do
  1984.              if s = T^[i] then
  1985.                 found := true
  1986.              else
  1987.                 dec(i);
  1988.           Lookup := i;
  1989.        end;
  1990.        {--------------------------------------------------------------}
  1991.        .
  1992.        .
  1993.        {--------------------------------------------------------------}
  1994.        { Get an Identifier and Scan it for Keywords }
  1995.  
  1996.        procedure Scan;
  1997.        begin
  1998.           GetName;
  1999.           Token := KWcode[Lookup(Addr(KWlist), Value, NKW) + 1];
  2000.        end;
  2001.        {--------------------------------------------------------------}
  2002.        .
  2003.        .
  2004.        {--------------------------------------------------------------}
  2005.        { Match a Specific Input String }
  2006.  
  2007.        procedure MatchString(x: string);
  2008.        begin
  2009.           if Value <> x then Expected('''' + x + '''');
  2010.        end;
  2011.        {--------------------------------------------------------------}
  2012.  
  2013.  
  2014.        Now, we have to make a  fairly  large number of subtle changes to
  2015.        the remaining procedures.  First,  we  must  change  the function
  2016.        GetName to a procedure, again as we did in Part VII:
  2017.  
  2018.  
  2019.        {--------------------------------------------------------------}
  2020.        { Get an Identifier }
  2021.  
  2022.        procedure GetName;
  2023.        begin
  2024.           NewLine;
  2025.           if not IsAlpha(Look) then Expected('Name');
  2026.           Value := '';
  2027.           while IsAlNum(Look) do begin
  2028.              Value := Value + UpCase(Look);
  2029.              GetChar;
  2030.           end;
  2031.           SkipWhite;A*A*
  2032.                                     - 33 -
  2033.  
  2034. PA A
  2035.  
  2036.  
  2037.  
  2038.  
  2039.  
  2040.        end;
  2041.        {--------------------------------------------------------------}
  2042.  
  2043.  
  2044.        Note that this procedure leaves its result in  the  global string
  2045.        Value.
  2046.  
  2047.        Next, we have to change every reference to GetName to reflect its
  2048.        new form. These occur in Factor, Assignment, and Decl:
  2049.  
  2050.  
  2051.        {---------------------------------------------------------------}
  2052.        { Parse and Translate a Math Factor }
  2053.  
  2054.        procedure BoolExpression; Forward;
  2055.  
  2056.        procedure Factor;
  2057.        begin
  2058.           if Look = '(' then begin
  2059.              Match('(');
  2060.              BoolExpression;
  2061.              Match(')');
  2062.              end
  2063.           else if IsAlpha(Look) then begin
  2064.              GetName;
  2065.              LoadVar(Value[1]);
  2066.              end
  2067.           else
  2068.              LoadConst(GetNum);
  2069.        end;
  2070.        {--------------------------------------------------------------}
  2071.        .
  2072.        .
  2073.        {--------------------------------------------------------------}
  2074.        { Parse and Translate an Assignment Statement }
  2075.  
  2076.        procedure Assignment;
  2077.        var Name: char;
  2078.        begin
  2079.           Name := Value[1];
  2080.           Match('=');
  2081.           BoolExpression;
  2082.           Store(Name);
  2083.        end;
  2084.        {---------------------------------------------------------------}
  2085.        .
  2086.        .
  2087.        {--------------------------------------------------------------}
  2088.        { Parse and Translate a Data Declaration }
  2089.  
  2090.        procedure Decl;
  2091.        begin
  2092.           GetName;
  2093.           Alloc(Value[1]);A*A*
  2094.                                     - 34 -
  2095.  
  2096. PA A
  2097.  
  2098.  
  2099.  
  2100.  
  2101.  
  2102.           while Look = ',' do begin
  2103.              Match(',');
  2104.              GetName;
  2105.              Alloc(Value[1]);
  2106.           end;
  2107.        end;
  2108.        {--------------------------------------------------------------}
  2109.  
  2110.  
  2111.        (Note that we're still  only  allowing  single-character variable
  2112.        names,  so we take the easy way out here and simply use the first
  2113.        character of the string.)
  2114.  
  2115.        Finally, we must make the changes to use Token instead of Look as
  2116.        the  test  character  and to call Scan at the appropriate places.
  2117.        Mostly, this  involves  deleting  calls  to  Match,  occasionally
  2118.        replacing calls to  Match  by calls to MatchString, and Replacing
  2119.        calls  to  NewLine  by  calls  to  Scan.    Here are the affected
  2120.        routines:
  2121.  
  2122.        {---------------------------------------------------------------}
  2123.        { Recognize and Translate an IF Construct }
  2124.  
  2125.        procedure Block; Forward;
  2126.  
  2127.  
  2128.        procedure DoIf;
  2129.        var L1, L2: string;
  2130.        begin
  2131.           BoolExpression;
  2132.           L1 := NewLabel;
  2133.           L2 := L1;
  2134.           BranchFalse(L1);
  2135.           Block;
  2136.           if Token = 'l' then begin
  2137.              L2 := NewLabel;
  2138.              Branch(L2);
  2139.              PostLabel(L1);
  2140.              Block;
  2141.           end;
  2142.           PostLabel(L2);
  2143.           MatchString('ENDIF');
  2144.        end;
  2145.  
  2146.  
  2147.        {--------------------------------------------------------------}
  2148.        { Parse and Translate a WHILE Statement }
  2149.  
  2150.        procedure DoWhile;
  2151.        var L1, L2: string;
  2152.        begin
  2153.           L1 := NewLabel;
  2154.           L2 := NewLabel;
  2155.           PostLabel(L1);A*A*
  2156.                                     - 35 -
  2157.  
  2158. PA A
  2159.  
  2160.  
  2161.  
  2162.  
  2163.  
  2164.           BoolExpression;
  2165.           BranchFalse(L2);
  2166.           Block;
  2167.           MatchString('ENDWHILE');
  2168.           Branch(L1);
  2169.           PostLabel(L2);
  2170.        end;
  2171.  
  2172.  
  2173.        {--------------------------------------------------------------}
  2174.        { Parse and Translate a Block of Statements }
  2175.  
  2176.        procedure Block;
  2177.        begin
  2178.           Scan;
  2179.           while not(Token in ['e', 'l']) do begin
  2180.              case Token of
  2181.               'i': DoIf;
  2182.               'w': DoWhile;
  2183.              else Assignment;
  2184.              end;
  2185.              Scan;
  2186.           end;
  2187.        end;
  2188.  
  2189.  
  2190.        {--------------------------------------------------------------}
  2191.        { Parse and Translate Global Declarations }
  2192.  
  2193.        procedure TopDecls;
  2194.        begin
  2195.           Scan;
  2196.           while Token <> 'b' do begin
  2197.              case Token of
  2198.                'v': Decl;
  2199.              else Abort('Unrecognized Keyword ' + Value);
  2200.              end;
  2201.              Scan;
  2202.           end;
  2203.        end;
  2204.  
  2205.  
  2206.        {--------------------------------------------------------------}
  2207.        { Parse and Translate a Main Program }
  2208.  
  2209.        procedure Main;
  2210.        begin
  2211.           MatchString('BEGIN');
  2212.           Prolog;
  2213.           Block;
  2214.           MatchString('END');
  2215.           Epilog;
  2216.        end;A6A6
  2217.                                     - 36 -A*A*
  2218.  
  2219. PA A
  2220.  
  2221.  
  2222.  
  2223.  
  2224.  
  2225.        {--------------------------------------------------------------}
  2226.        {  Parse and Translate a Program }
  2227.  
  2228.        procedure Prog;
  2229.        begin
  2230.           MatchString('PROGRAM');
  2231.           Header;
  2232.           TopDecls;
  2233.           Main;
  2234.           Match('.');
  2235.        end;
  2236.  
  2237.  
  2238.        {--------------------------------------------------------------}
  2239.        { Initialize }
  2240.  
  2241.        procedure Init;
  2242.        var i: char;
  2243.        begin
  2244.           for i := 'A' to 'Z' do
  2245.              ST[i] := ' ';
  2246.           GetChar;
  2247.           Scan;
  2248.        end;
  2249.        {--------------------------------------------------------------}
  2250.  
  2251.  
  2252.        That should do  it.    If  all  the changes got in correctly, you
  2253.        should now be parsing programs that look like programs.   (If you
  2254.        didn't  make  it  through all the  changes,  don't  despair.    A
  2255.        complete listing of the final form is given later.)
  2256.  
  2257.        Did it work?  If so, then we're just about home.  In fact, with a
  2258.        few minor  exceptions we've already got a compiler that's usable.
  2259.        There are still a few areas that need improvement.
  2260.  
  2261.  
  2262.        MULTI-CHARACTER VARIABLE NAMES
  2263.  
  2264.        One of those is  the  restriction  that  we still have, requiring
  2265.        single-character variable names.    Now that we can handle multi-
  2266.        character keywords, this one  begins  to  look  very much like an
  2267.        arbitrary  and  unnecessary  limitation.    And  indeed   it  is.
  2268.        Basically, its only virtue is  that it permits a trivially simple
  2269.        implementation  of  the   symbol   table.    But  that's  just  a
  2270.        convenience to the compiler writers, and needs to be eliminated.
  2271.  
  2272.        We've done this step before.  This time, as usual, I'm doing it a
  2273.        little differently.  I think  the approach used here keeps things
  2274.        just about as simple as possible.
  2275.  
  2276.        The natural  way  to  implement  a  symbol  table in Pascal is by
  2277.        declaring a record type, and making the symbol table an  array of
  2278.        such records.  Here, though, we don't really need  a  type  fieldA*A*
  2279.                                     - 37 -
  2280.  
  2281. PA A
  2282.  
  2283.  
  2284.  
  2285.  
  2286.  
  2287.        yet  (there is only one kind of entry allowed so far), so we only
  2288.        need an array of symbols.  This has the advantage that we can use
  2289.        the existing procedure Lookup to  search the symbol table as well
  2290.        as the  keyword  list.    As it turns out, even when we need more
  2291.        fields we can still use the same approach, simply by  storing the
  2292.        other fields in separate arrays.
  2293.  
  2294.        OK, here are the changes that  need  to  be made.  First, add the
  2295.        new typed constant:
  2296.  
  2297.  
  2298.              NEntry: integer = 0;
  2299.  
  2300.  
  2301.        Then change the definition of the symbol table as follows:
  2302.  
  2303.  
  2304.        const MaxEntry = 100;
  2305.  
  2306.        var ST   : array[1..MaxEntry] of Symbol;
  2307.  
  2308.  
  2309.        (Note that ST is _NOT_ declared as a SymTab.  That declaration is
  2310.        a phony one to get Lookup to work.  A SymTab  would  take  up too
  2311.        much RAM space, and so one is never actually allocated.)
  2312.  
  2313.        Next, we need to replace InTable:
  2314.  
  2315.  
  2316.        {--------------------------------------------------------------}
  2317.        { Look for Symbol in Table }
  2318.  
  2319.        function InTable(n: Symbol): Boolean;
  2320.        begin
  2321.           InTable := Lookup(@ST, n, MaxEntry) <> 0;
  2322.        end;
  2323.        {--------------------------------------------------------------}
  2324.  
  2325.  
  2326.        We also need a new procedure, AddEntry, that adds a new  entry to
  2327.        the table:
  2328.  
  2329.  
  2330.        {--------------------------------------------------------------}
  2331.        { Add a New Entry to Symbol Table }
  2332.  
  2333.        procedure AddEntry(N: Symbol; T: char);
  2334.        begin
  2335.           if InTable(N) then Abort('Duplicate Identifier ' + N);
  2336.           if NEntry = MaxEntry then Abort('Symbol Table Full');
  2337.           Inc(NEntry);
  2338.           ST[NEntry] := N;
  2339.           SType[NEntry] := T;
  2340.        end;A*A*
  2341.                                     - 38 -
  2342.  
  2343. PA A
  2344.  
  2345.  
  2346.  
  2347.  
  2348.  
  2349.        {--------------------------------------------------------------}
  2350.  
  2351.  
  2352.        This procedure is called by Alloc:
  2353.  
  2354.  
  2355.        {--------------------------------------------------------------}
  2356.        { Allocate Storage for a Variable }
  2357.  
  2358.        procedure Alloc(N: Symbol);
  2359.        begin
  2360.           if InTable(N) then Abort('Duplicate Variable Name ' + N);
  2361.           AddEntry(N, 'v');
  2362.        .
  2363.        .
  2364.        .
  2365.        {--------------------------------------------------------------}
  2366.  
  2367.  
  2368.        Finally, we must change all the routines that currently treat the
  2369.        variable name as a single character.  These include   LoadVar and
  2370.        Store (just change the  type  from  char  to string), and Factor,
  2371.        Assignment, and Decl (just change Value[1] to Value).
  2372.  
  2373.        One  last  thing:  change  procedure  Init to clear the array  as
  2374.        shown:
  2375.  
  2376.  
  2377.        {--------------------------------------------------------------}
  2378.        { Initialize }
  2379.  
  2380.        procedure Init;
  2381.        var i: integer;
  2382.        begin
  2383.           for i := 1 to MaxEntry do begin
  2384.              ST[i] := '';
  2385.              SType[i] := ' ';
  2386.           end;
  2387.           GetChar;
  2388.           Scan;
  2389.        end;
  2390.        {--------------------------------------------------------------}
  2391.  
  2392.  
  2393.        That should do it.  Try it out and verify  that  you can, indeed,
  2394.        use multi-character variable names.
  2395.  
  2396.  
  2397.        MORE RELOPS
  2398.  
  2399.        We still have one remaining single-character restriction: the one
  2400.        on relops.  Some of the relops are indeed single  characters, but
  2401.        others  require two.  These are '<=' and '>='.  I also prefer the
  2402.        Pascal '<>' for "not equals,"  instead of '#'.A*A*
  2403.                                     - 39 -
  2404.  
  2405. PA A
  2406.  
  2407.  
  2408.  
  2409.  
  2410.  
  2411.        If you'll recall, in Part VII I pointed out that the conventional
  2412.        way  to  deal  with  relops  is  to  include them in the list  of
  2413.        keywords, and let the  lexical  scanner  find  them.  But, again,
  2414.        this requires scanning throughout the expression parsing process,
  2415.        whereas so far we've been able to limit the use of the scanner to
  2416.        the beginning of a statement.
  2417.  
  2418.        I mentioned then that we can still get away with this,  since the
  2419.        multi-character relops are so few  and so limited in their usage.
  2420.        It's easy to just treat them as special cases and handle  them in
  2421.        an ad hoc manner.
  2422.  
  2423.        The changes required affect only the code generation routines and
  2424.        procedures Relation and friends.   First, we're going to need two
  2425.        more code generation routines:
  2426.  
  2427.  
  2428.        {---------------------------------------------------------------}
  2429.        { Set D0 If Compare was <= }
  2430.  
  2431.        procedure SetLessOrEqual;
  2432.        begin
  2433.           EmitLn('SGE D0');
  2434.           EmitLn('EXT D0');
  2435.        end;
  2436.  
  2437.  
  2438.        {---------------------------------------------------------------}
  2439.        { Set D0 If Compare was >= }
  2440.  
  2441.        procedure SetGreaterOrEqual;
  2442.        begin
  2443.           EmitLn('SLE D0');
  2444.           EmitLn('EXT D0');
  2445.        end;
  2446.        {---------------------------------------------------------------}
  2447.  
  2448.  
  2449.        Then, modify the relation parsing routines as shown below:
  2450.  
  2451.  
  2452.        {---------------------------------------------------------------}
  2453.        { Recognize and Translate a Relational "Less Than or Equal" }
  2454.  
  2455.        procedure LessOrEqual;
  2456.        begin
  2457.           Match('=');
  2458.           Expression;
  2459.           PopCompare;
  2460.           SetLessOrEqual;
  2461.        end;
  2462.  
  2463.  
  2464.        {---------------------------------------------------------------}A*A*
  2465.                                     - 40 -
  2466.  
  2467. PA A
  2468.  
  2469.  
  2470.  
  2471.  
  2472.  
  2473.        { Recognize and Translate a Relational "Not Equals" }
  2474.  
  2475.        procedure NotEqual;
  2476.        begin
  2477.           Match('>');
  2478.           Expression;
  2479.           PopCompare;
  2480.           SetNEqual;
  2481.        end;
  2482.  
  2483.  
  2484.        {---------------------------------------------------------------}
  2485.        { Recognize and Translate a Relational "Less Than" }
  2486.  
  2487.        procedure Less;
  2488.        begin
  2489.           Match('<');
  2490.           case Look of
  2491.             '=': LessOrEqual;
  2492.             '>': NotEqual;
  2493.           else begin
  2494.                   Expression;
  2495.                   PopCompare;
  2496.                   SetLess;
  2497.                end;
  2498.           end;
  2499.        end;
  2500.  
  2501.  
  2502.        {---------------------------------------------------------------}
  2503.        { Recognize and Translate a Relational "Greater Than" }
  2504.  
  2505.        procedure Greater;
  2506.        begin
  2507.           Match('>');
  2508.           if Look = '=' then begin
  2509.              Match('=');
  2510.              Expression;
  2511.              PopCompare;
  2512.              SetGreaterOrEqual;
  2513.              end
  2514.           else begin
  2515.              Expression;
  2516.              PopCompare;
  2517.              SetGreater;
  2518.           end;
  2519.        end;
  2520.        {---------------------------------------------------------------}
  2521.  
  2522.  
  2523.        That's all it takes.  Now  you  can  process all the relops.  Try
  2524.        it.ABAB
  2525.                                     - 41 -A*A*
  2526.  
  2527. PA A
  2528.  
  2529.  
  2530.  
  2531.  
  2532.  
  2533.        INPUT/OUTPUT
  2534.  
  2535.        We  now  have  a complete, working language, except for one minor
  2536.        embarassment: we have no way to get data in or out.  We need some
  2537.        I/O.
  2538.  
  2539.        Now, the convention these days, established in C and continued in
  2540.        Ada and Modula 2, is to leave I/O statements out of  the language
  2541.        itself,  and  just  include them in the subroutine library.  That
  2542.        would  be  fine, except that so far  we  have  no  provision  for
  2543.        subroutines.  Anyhow, with this approach you run into the problem
  2544.        of variable-length argument lists.  In Pascal, the I/O statements
  2545.        are built into the language because they are the  only  ones  for
  2546.        which  the  argument  list can have a variable number of entries.
  2547.        In C, we settle for kludges like scanf and printf, and  must pass
  2548.        the argument count to the called procedure.  In Ada and  Modula 2
  2549.        we must use the  awkward  (and SLOW!) approach of a separate call
  2550.        for each argument.
  2551.  
  2552.        So I think I prefer the  Pascal  approach of building the I/O in,
  2553.        even though we don't need to.
  2554.  
  2555.        As  usual,  for  this we need some more code generation routines.
  2556.        These turn out  to be the easiest of all, because all we do is to
  2557.        call library procedures to do the work:
  2558.  
  2559.  
  2560.        {---------------------------------------------------------------}
  2561.        { Read Variable to Primary Register }
  2562.  
  2563.        procedure ReadVar;
  2564.        begin
  2565.           EmitLn('BSR READ');
  2566.           Store(Value);
  2567.        end;
  2568.  
  2569.  
  2570.        {---------------------------------------------------------------}
  2571.        { Write Variable from Primary Register }
  2572.  
  2573.        procedure WriteVar;
  2574.        begin
  2575.           EmitLn('BSR WRITE');
  2576.        end;
  2577.        {--------------------------------------------------------------}
  2578.  
  2579.  
  2580.        The idea is that READ loads the value from input  to  the D0, and
  2581.        WRITE outputs it from there.
  2582.  
  2583.        These two procedures represent  our  first  encounter with a need
  2584.        for library procedures ... the components of a  Run  Time Library
  2585.        (RTL).    Of  course, someone (namely  us)  has  to  write  these
  2586.        routines, but they're not  part  of the compiler itself.  I won'tA*A*
  2587.                                     - 42 -
  2588.  
  2589. PA A
  2590.  
  2591.  
  2592.  
  2593.  
  2594.  
  2595.        even bother  showing the routines here, since these are obviously
  2596.        very much OS-dependent.   I  _WILL_  simply  say that for SK*DOS,
  2597.        they  are  particularly  simple ... almost trivial.  One reason I
  2598.        won't show them here is that  you  can add all kinds of fanciness
  2599.        to the things, for  example  by prompting in READ for the inputs,
  2600.        and by giving the user a chance to reenter a bad input.
  2601.  
  2602.        But that is really separate from compiler design, so for now I'll
  2603.        just assume that a library call TINYLIB.LIB exists.  Since we now
  2604.        need  it  loaded,  we need to add a statement to  include  it  in
  2605.        procedure Header:
  2606.  
  2607.  
  2608.        {--------------------------------------------------------------}
  2609.        { Write Header Info }
  2610.  
  2611.        procedure Header;
  2612.        begin
  2613.  
  2614.           WriteLn('WARMST', TAB, 'EQU $A01E');
  2615.           EmitLn('LIB TINYLIB');
  2616.        end;
  2617.        {--------------------------------------------------------------}
  2618.  
  2619.        That takes care of that part.  Now, we also need to recognize the
  2620.        read  and  write  commands.  We can do this by  adding  two  more
  2621.        keywords to our list:
  2622.  
  2623.  
  2624.        {--------------------------------------------------------------}
  2625.        { Definition of Keywords and Token Types }
  2626.  
  2627.        const NKW =   11;
  2628.              NKW1 = 12;
  2629.  
  2630.        const KWlist: array[1..NKW] of Symbol =
  2631.                      ('IF', 'ELSE', 'ENDIF', 'WHILE', 'ENDWHILE',
  2632.                       'READ',    'WRITE',    'VAR',    'BEGIN',   'END',
  2633.        'PROGRAM');
  2634.  
  2635.        const KWcode: string[NKW1] = 'xileweRWvbep';
  2636.        {--------------------------------------------------------------}
  2637.  
  2638.  
  2639.        (Note how I'm using upper case codes here to avoid  conflict with
  2640.        the 'w' of WHILE.)
  2641.  
  2642.        Next, we need procedures for processing the  read/write statement
  2643.        and its argument list:
  2644.  
  2645.  
  2646.        {--------------------------------------------------------------}
  2647.        { Process a Read Statement }A6A6
  2648.                                     - 43 -A*A*
  2649.  
  2650. PA A
  2651.  
  2652.  
  2653.  
  2654.  
  2655.  
  2656.        procedure DoRead;
  2657.        begin
  2658.           Match('(');
  2659.           GetName;
  2660.           ReadVar;
  2661.           while Look = ',' do begin
  2662.              Match(',');
  2663.              GetName;
  2664.              ReadVar;
  2665.           end;
  2666.           Match(')');
  2667.        end;
  2668.  
  2669.  
  2670.        {--------------------------------------------------------------}
  2671.        { Process a Write Statement }
  2672.  
  2673.        procedure DoWrite;
  2674.        begin
  2675.           Match('(');
  2676.           Expression;
  2677.           WriteVar;
  2678.           while Look = ',' do begin
  2679.              Match(',');
  2680.              Expression;
  2681.              WriteVar;
  2682.           end;
  2683.           Match(')');
  2684.        end;
  2685.        {--------------------------------------------------------------}
  2686.  
  2687.  
  2688.        Finally,  we  must  expand  procedure  Block  to  handle the  new
  2689.        statement types:
  2690.  
  2691.  
  2692.        {--------------------------------------------------------------}
  2693.        { Parse and Translate a Block of Statements }
  2694.  
  2695.        procedure Block;
  2696.        begin
  2697.           Scan;
  2698.           while not(Token in ['e', 'l']) do begin
  2699.              case Token of
  2700.               'i': DoIf;
  2701.               'w': DoWhile;
  2702.               'R': DoRead;
  2703.               'W': DoWrite;
  2704.              else Assignment;
  2705.              end;
  2706.              Scan;
  2707.           end;
  2708.        end;
  2709.        {--------------------------------------------------------------}A*A*
  2710.                                     - 44 -
  2711.  
  2712. PA A
  2713.  
  2714.  
  2715.  
  2716.  
  2717.  
  2718.        That's all there is to it.  _NOW_ we have a language!
  2719.  
  2720.  
  2721.        CONCLUSION
  2722.  
  2723.        At this point we have TINY completely defined.  It's not much ...
  2724.        actually a toy  compiler.    TINY  has  only one data type and no
  2725.        subroutines  ... but it's a complete,  usable  language.    While
  2726.        you're not likely to be able to write another compiler in  it, or
  2727.        do anything else very seriously, you could write programs to read
  2728.        some input, perform calculations,  and  output  the results.  Not
  2729.        too bad for a toy.
  2730.  
  2731.        Most importantly, we have a firm base upon which to build further
  2732.        extensions.  I know you'll be glad to hear this: this is the last
  2733.        time  I'll  start  over in building a parser ... from  now  on  I
  2734.        intend to just add features to  TINY  until it becomes KISS.  Oh,
  2735.        there'll be other times we will  need  to try things out with new
  2736.        copies  of  the  Cradle, but once we've found out how to do those
  2737.        things they'll be incorporated into TINY.
  2738.  
  2739.        What  will  those  features  be?    Well,  for starters  we  need
  2740.        subroutines and functions.    Then  we  need to be able to handle
  2741.        different types, including arrays, strings, and other structures.
  2742.        Then we need to deal with the idea of pointers.  All this will be
  2743.        upcoming in future installments.
  2744.  
  2745.        See you then.
  2746.  
  2747.        For references purposes, the complete listing of TINY Version 1.0
  2748.        is shown below:
  2749.  
  2750.  
  2751.        {--------------------------------------------------------------}
  2752.        program Tiny10;
  2753.  
  2754.        {--------------------------------------------------------------}
  2755.        { Constant Declarations }
  2756.  
  2757.        const TAB = ^I;
  2758.              CR  = ^M;
  2759.              LF  = ^J;
  2760.  
  2761.              LCount: integer = 0;
  2762.              NEntry: integer = 0;
  2763.  
  2764.  
  2765.        {--------------------------------------------------------------}
  2766.        { Type Declarations }
  2767.  
  2768.        type Symbol = string[8];
  2769.  
  2770.             SymTab = array[1..1000] of Symbol;A6A6
  2771.                                     - 45 -A*A*
  2772.  
  2773. PA A
  2774.  
  2775.  
  2776.  
  2777.  
  2778.  
  2779.             TabPtr = ^SymTab;
  2780.  
  2781.  
  2782.        {--------------------------------------------------------------}
  2783.        { Variable Declarations }
  2784.  
  2785.        var Look : char;             { Lookahead Character }
  2786.            Token: char;             { Encoded Token       }
  2787.            Value: string[16];       { Unencoded Token     }
  2788.  
  2789.  
  2790.        const MaxEntry = 100;
  2791.  
  2792.        var ST   : array[1..MaxEntry] of Symbol;
  2793.            SType: array[1..MaxEntry] of char;
  2794.  
  2795.  
  2796.        {--------------------------------------------------------------}
  2797.        { Definition of Keywords and Token Types }
  2798.  
  2799.        const NKW =   11;
  2800.              NKW1 = 12;
  2801.  
  2802.        const KWlist: array[1..NKW] of Symbol =
  2803.                      ('IF', 'ELSE', 'ENDIF', 'WHILE', 'ENDWHILE',
  2804.                       'READ',    'WRITE',    'VAR',    'BEGIN',   'END',
  2805.        'PROGRAM');
  2806.  
  2807.        const KWcode: string[NKW1] = 'xileweRWvbep';
  2808.  
  2809.  
  2810.        {--------------------------------------------------------------}
  2811.        { Read New Character From Input Stream }
  2812.  
  2813.        procedure GetChar;
  2814.        begin
  2815.           Read(Look);
  2816.        end;
  2817.  
  2818.        {--------------------------------------------------------------}
  2819.        { Report an Error }
  2820.  
  2821.        procedure Error(s: string);
  2822.        begin
  2823.           WriteLn;
  2824.           WriteLn(^G, 'Error: ', s, '.');
  2825.        end;
  2826.  
  2827.  
  2828.        {--------------------------------------------------------------}
  2829.        { Report Error and Halt }
  2830.  
  2831.        procedure Abort(s: string);
  2832.        beginA*A*
  2833.                                     - 46 -
  2834.  
  2835. PA A
  2836.  
  2837.  
  2838.  
  2839.  
  2840.  
  2841.           Error(s);
  2842.           Halt;
  2843.        end;
  2844.  
  2845.  
  2846.        {--------------------------------------------------------------}
  2847.        { Report What Was Expected }
  2848.  
  2849.        procedure Expected(s: string);
  2850.        begin
  2851.           Abort(s + ' Expected');
  2852.        end;
  2853.  
  2854.        {--------------------------------------------------------------}
  2855.        { Report an Undefined Identifier }
  2856.  
  2857.        procedure Undefined(n: string);
  2858.        begin
  2859.           Abort('Undefined Identifier ' + n);
  2860.        end;
  2861.  
  2862.  
  2863.        {--------------------------------------------------------------}
  2864.        { Recognize an Alpha Character }
  2865.  
  2866.        function IsAlpha(c: char): boolean;
  2867.        begin
  2868.           IsAlpha := UpCase(c) in ['A'..'Z'];
  2869.        end;
  2870.  
  2871.  
  2872.        {--------------------------------------------------------------}
  2873.        { Recognize a Decimal Digit }
  2874.  
  2875.        function IsDigit(c: char): boolean;
  2876.        begin
  2877.           IsDigit := c in ['0'..'9'];
  2878.        end;
  2879.  
  2880.  
  2881.        {--------------------------------------------------------------}
  2882.        { Recognize an AlphaNumeric Character }
  2883.  
  2884.        function IsAlNum(c: char): boolean;
  2885.        begin
  2886.           IsAlNum := IsAlpha(c) or IsDigit(c);
  2887.        end;
  2888.  
  2889.  
  2890.        {--------------------------------------------------------------}
  2891.        { Recognize an Addop }
  2892.  
  2893.        function IsAddop(c: char): boolean;
  2894.        beginA*A*
  2895.                                     - 47 -
  2896.  
  2897. PA A
  2898.  
  2899.  
  2900.  
  2901.  
  2902.  
  2903.           IsAddop := c in ['+', '-'];
  2904.        end;
  2905.  
  2906.  
  2907.        {--------------------------------------------------------------}
  2908.        { Recognize a Mulop }
  2909.  
  2910.        function IsMulop(c: char): boolean;
  2911.        begin
  2912.           IsMulop := c in ['*', '/'];
  2913.        end;
  2914.  
  2915.  
  2916.        {--------------------------------------------------------------}
  2917.        { Recognize a Boolean Orop }
  2918.  
  2919.        function IsOrop(c: char): boolean;
  2920.        begin
  2921.           IsOrop := c in ['|', '~'];
  2922.        end;
  2923.  
  2924.  
  2925.        {--------------------------------------------------------------}
  2926.        { Recognize a Relop }
  2927.  
  2928.        function IsRelop(c: char): boolean;
  2929.        begin
  2930.           IsRelop := c in ['=', '#', '<', '>'];
  2931.        end;
  2932.  
  2933.  
  2934.        {--------------------------------------------------------------}
  2935.        { Recognize White Space }
  2936.  
  2937.        function IsWhite(c: char): boolean;
  2938.        begin
  2939.           IsWhite := c in [' ', TAB];
  2940.        end;
  2941.  
  2942.  
  2943.        {--------------------------------------------------------------}
  2944.        { Skip Over Leading White Space }
  2945.  
  2946.        procedure SkipWhite;
  2947.        begin
  2948.           while IsWhite(Look) do
  2949.              GetChar;
  2950.        end;
  2951.  
  2952.  
  2953.        {--------------------------------------------------------------}
  2954.        { Skip Over an End-of-Line }
  2955.  
  2956.        procedure NewLine;A*A*
  2957.                                     - 48 -
  2958.  
  2959. PA A
  2960.  
  2961.  
  2962.  
  2963.  
  2964.  
  2965.        begin
  2966.           while Look = CR do begin
  2967.              GetChar;
  2968.              if Look = LF then GetChar;
  2969.              SkipWhite;
  2970.           end;
  2971.        end;
  2972.  
  2973.  
  2974.        {--------------------------------------------------------------}
  2975.        { Match a Specific Input Character }
  2976.  
  2977.        procedure Match(x: char);
  2978.        begin
  2979.           NewLine;
  2980.           if Look = x then GetChar
  2981.           else Expected('''' + x + '''');
  2982.           SkipWhite;
  2983.        end;
  2984.  
  2985.  
  2986.        {--------------------------------------------------------------}
  2987.        { Table Lookup }
  2988.  
  2989.        function Lookup(T: TabPtr; s: string; n: integer): integer;
  2990.        var i: integer;
  2991.            found: Boolean;
  2992.        begin
  2993.           found := false;
  2994.           i := n;
  2995.           while (i > 0) and not found do
  2996.              if s = T^[i] then
  2997.                 found := true
  2998.              else
  2999.                 dec(i);
  3000.           Lookup := i;
  3001.        end;
  3002.  
  3003.  
  3004.        {--------------------------------------------------------------}
  3005.        { Locate a Symbol in Table }
  3006.        { Returns the index of the entry.  Zero if not present. }
  3007.  
  3008.        function Locate(N: Symbol): integer;
  3009.        begin
  3010.           Locate := Lookup(@ST, n, MaxEntry);
  3011.        end;
  3012.  
  3013.  
  3014.        {--------------------------------------------------------------}
  3015.        { Look for Symbol in Table }
  3016.  
  3017.        function InTable(n: Symbol): Boolean;
  3018.        beginA*A*
  3019.                                     - 49 -
  3020.  
  3021. PA A
  3022.  
  3023.  
  3024.  
  3025.  
  3026.  
  3027.           InTable := Lookup(@ST, n, MaxEntry) <> 0;
  3028.        end;
  3029.  
  3030.  
  3031.        {--------------------------------------------------------------}
  3032.        { Add a New Entry to Symbol Table }
  3033.  
  3034.        procedure AddEntry(N: Symbol; T: char);
  3035.        begin
  3036.           if InTable(N) then Abort('Duplicate Identifier ' + N);
  3037.           if NEntry = MaxEntry then Abort('Symbol Table Full');
  3038.           Inc(NEntry);
  3039.           ST[NEntry] := N;
  3040.           SType[NEntry] := T;
  3041.        end;
  3042.  
  3043.  
  3044.        {--------------------------------------------------------------}
  3045.        { Get an Identifier }
  3046.  
  3047.        procedure GetName;
  3048.        begin
  3049.           NewLine;
  3050.           if not IsAlpha(Look) then Expected('Name');
  3051.           Value := '';
  3052.           while IsAlNum(Look) do begin
  3053.              Value := Value + UpCase(Look);
  3054.              GetChar;
  3055.           end;
  3056.           SkipWhite;
  3057.        end;
  3058.  
  3059.  
  3060.        {--------------------------------------------------------------}
  3061.        { Get a Number }
  3062.  
  3063.        function GetNum: integer;
  3064.        var Val: integer;
  3065.        begin
  3066.           NewLine;
  3067.           if not IsDigit(Look) then Expected('Integer');
  3068.           Val := 0;
  3069.           while IsDigit(Look) do begin
  3070.              Val := 10 * Val + Ord(Look) - Ord('0');
  3071.              GetChar;
  3072.           end;
  3073.           GetNum := Val;
  3074.           SkipWhite;
  3075.        end;
  3076.  
  3077.  
  3078.        {--------------------------------------------------------------}
  3079.        { Get an Identifier and Scan it for Keywords }A6A6
  3080.                                     - 50 -A*A*
  3081.  
  3082. PA A
  3083.  
  3084.  
  3085.  
  3086.  
  3087.  
  3088.        procedure Scan;
  3089.        begin
  3090.           GetName;
  3091.           Token := KWcode[Lookup(Addr(KWlist), Value, NKW) + 1];
  3092.        end;
  3093.  
  3094.  
  3095.        {--------------------------------------------------------------}
  3096.        { Match a Specific Input String }
  3097.  
  3098.        procedure MatchString(x: string);
  3099.        begin
  3100.           if Value <> x then Expected('''' + x + '''');
  3101.        end;
  3102.  
  3103.  
  3104.        {--------------------------------------------------------------}
  3105.        { Output a String with Tab }
  3106.  
  3107.        procedure Emit(s: string);
  3108.        begin
  3109.           Write(TAB, s);
  3110.        end;
  3111.  
  3112.  
  3113.        {--------------------------------------------------------------}
  3114.        { Output a String with Tab and CRLF }
  3115.  
  3116.        procedure EmitLn(s: string);
  3117.        begin
  3118.           Emit(s);
  3119.           WriteLn;
  3120.        end;
  3121.  
  3122.  
  3123.        {--------------------------------------------------------------}
  3124.        { Generate a Unique Label }
  3125.  
  3126.        function NewLabel: string;
  3127.        var S: string;
  3128.        begin
  3129.           Str(LCount, S);
  3130.           NewLabel := 'L' + S;
  3131.           Inc(LCount);
  3132.        end;
  3133.  
  3134.  
  3135.        {--------------------------------------------------------------}
  3136.        { Post a Label To Output }
  3137.  
  3138.        procedure PostLabel(L: string);
  3139.        begin
  3140.           WriteLn(L, ':');
  3141.        end;A*A*
  3142.                                     - 51 -
  3143.  
  3144. PA A
  3145.  
  3146.  
  3147.  
  3148.  
  3149.  
  3150.        {---------------------------------------------------------------}
  3151.        { Clear the Primary Register }
  3152.  
  3153.        procedure Clear;
  3154.        begin
  3155.           EmitLn('CLR D0');
  3156.        end;
  3157.  
  3158.  
  3159.        {---------------------------------------------------------------}
  3160.        { Negate the Primary Register }
  3161.  
  3162.        procedure Negate;
  3163.        begin
  3164.           EmitLn('NEG D0');
  3165.        end;
  3166.  
  3167.  
  3168.        {---------------------------------------------------------------}
  3169.        { Complement the Primary Register }
  3170.  
  3171.        procedure NotIt;
  3172.        begin
  3173.           EmitLn('NOT D0');
  3174.        end;
  3175.  
  3176.  
  3177.        {---------------------------------------------------------------}
  3178.        { Load a Constant Value to Primary Register }
  3179.  
  3180.        procedure LoadConst(n: integer);
  3181.        begin
  3182.           Emit('MOVE #');
  3183.           WriteLn(n, ',D0');
  3184.        end;
  3185.  
  3186.  
  3187.        {---------------------------------------------------------------}
  3188.        { Load a Variable to Primary Register }
  3189.  
  3190.        procedure LoadVar(Name: string);
  3191.        begin
  3192.           if not InTable(Name) then Undefined(Name);
  3193.           EmitLn('MOVE ' + Name + '(PC),D0');
  3194.        end;
  3195.  
  3196.  
  3197.        {---------------------------------------------------------------}
  3198.        { Push Primary onto Stack }
  3199.  
  3200.        procedure Push;
  3201.        begin
  3202.           EmitLn('MOVE D0,-(SP)');
  3203.        end;A*A*
  3204.                                     - 52 -
  3205.  
  3206. PA A
  3207.  
  3208.  
  3209.  
  3210.  
  3211.  
  3212.        {---------------------------------------------------------------}
  3213.        { Add Top of Stack to Primary }
  3214.  
  3215.        procedure PopAdd;
  3216.        begin
  3217.           EmitLn('ADD (SP)+,D0');
  3218.        end;
  3219.  
  3220.  
  3221.        {---------------------------------------------------------------}
  3222.        { Subtract Primary from Top of Stack }
  3223.  
  3224.        procedure PopSub;
  3225.        begin
  3226.           EmitLn('SUB (SP)+,D0');
  3227.           EmitLn('NEG D0');
  3228.        end;
  3229.  
  3230.  
  3231.        {---------------------------------------------------------------}
  3232.        { Multiply Top of Stack by Primary }
  3233.  
  3234.        procedure PopMul;
  3235.        begin
  3236.           EmitLn('MULS (SP)+,D0');
  3237.        end;
  3238.  
  3239.  
  3240.        {---------------------------------------------------------------}
  3241.        { Divide Top of Stack by Primary }
  3242.  
  3243.        procedure PopDiv;
  3244.        begin
  3245.           EmitLn('MOVE (SP)+,D7');
  3246.           EmitLn('EXT.L D7');
  3247.           EmitLn('DIVS D0,D7');
  3248.           EmitLn('MOVE D7,D0');
  3249.        end;
  3250.  
  3251.  
  3252.        {---------------------------------------------------------------}
  3253.        { AND Top of Stack with Primary }
  3254.  
  3255.        procedure PopAnd;
  3256.        begin
  3257.           EmitLn('AND (SP)+,D0');
  3258.        end;
  3259.  
  3260.  
  3261.        {---------------------------------------------------------------}
  3262.        { OR Top of Stack with Primary }
  3263.  
  3264.        procedure PopOr;
  3265.        beginA*A*
  3266.                                     - 53 -
  3267.  
  3268. PA A
  3269.  
  3270.  
  3271.  
  3272.  
  3273.  
  3274.           EmitLn('OR (SP)+,D0');
  3275.        end;
  3276.  
  3277.  
  3278.        {---------------------------------------------------------------}
  3279.        { XOR Top of Stack with Primary }
  3280.  
  3281.        procedure PopXor;
  3282.        begin
  3283.           EmitLn('EOR (SP)+,D0');
  3284.        end;
  3285.  
  3286.  
  3287.        {---------------------------------------------------------------}
  3288.        { Compare Top of Stack with Primary }
  3289.  
  3290.        procedure PopCompare;
  3291.        begin
  3292.           EmitLn('CMP (SP)+,D0');
  3293.        end;
  3294.  
  3295.  
  3296.        {---------------------------------------------------------------}
  3297.        { Set D0 If Compare was = }
  3298.  
  3299.        procedure SetEqual;
  3300.        begin
  3301.           EmitLn('SEQ D0');
  3302.           EmitLn('EXT D0');
  3303.        end;
  3304.  
  3305.  
  3306.        {---------------------------------------------------------------}
  3307.        { Set D0 If Compare was != }
  3308.  
  3309.        procedure SetNEqual;
  3310.        begin
  3311.           EmitLn('SNE D0');
  3312.           EmitLn('EXT D0');
  3313.        end;
  3314.  
  3315.  
  3316.        {---------------------------------------------------------------}
  3317.        { Set D0 If Compare was > }
  3318.  
  3319.        procedure SetGreater;
  3320.        begin
  3321.           EmitLn('SLT D0');
  3322.           EmitLn('EXT D0');
  3323.        end;
  3324.  
  3325.  
  3326.        {---------------------------------------------------------------}
  3327.        { Set D0 If Compare was < }A*A*
  3328.                                     - 54 -
  3329.  
  3330. PA A
  3331.  
  3332.  
  3333.  
  3334.  
  3335.  
  3336.        procedure SetLess;
  3337.        begin
  3338.           EmitLn('SGT D0');
  3339.           EmitLn('EXT D0');
  3340.        end;
  3341.  
  3342.  
  3343.        {---------------------------------------------------------------}
  3344.        { Set D0 If Compare was <= }
  3345.  
  3346.        procedure SetLessOrEqual;
  3347.        begin
  3348.           EmitLn('SGE D0');
  3349.           EmitLn('EXT D0');
  3350.        end;
  3351.  
  3352.  
  3353.        {---------------------------------------------------------------}
  3354.        { Set D0 If Compare was >= }
  3355.  
  3356.        procedure SetGreaterOrEqual;
  3357.        begin
  3358.           EmitLn('SLE D0');
  3359.           EmitLn('EXT D0');
  3360.        end;
  3361.  
  3362.  
  3363.        {---------------------------------------------------------------}
  3364.        { Store Primary to Variable }
  3365.  
  3366.        procedure Store(Name: string);
  3367.        begin
  3368.           if not InTable(Name) then Undefined(Name);
  3369.           EmitLn('LEA ' + Name + '(PC),A0');
  3370.           EmitLn('MOVE D0,(A0)')
  3371.        end;
  3372.  
  3373.  
  3374.        {---------------------------------------------------------------}
  3375.        { Branch Unconditional  }
  3376.  
  3377.        procedure Branch(L: string);
  3378.        begin
  3379.           EmitLn('BRA ' + L);
  3380.        end;
  3381.  
  3382.  
  3383.        {---------------------------------------------------------------}
  3384.        { Branch False }
  3385.  
  3386.        procedure BranchFalse(L: string);
  3387.        begin
  3388.           EmitLn('TST D0');
  3389.           EmitLn('BEQ ' + L);A*A*
  3390.                                     - 55 -
  3391.  
  3392. PA A
  3393.  
  3394.  
  3395.  
  3396.  
  3397.  
  3398.        end;
  3399.  
  3400.  
  3401.        {---------------------------------------------------------------}
  3402.        { Read Variable to Primary Register }
  3403.  
  3404.        procedure ReadVar;
  3405.        begin
  3406.           EmitLn('BSR READ');
  3407.           Store(Value[1]);
  3408.        end;
  3409.  
  3410.  
  3411.        { Write Variable from Primary Register }
  3412.  
  3413.        procedure WriteVar;
  3414.        begin
  3415.           EmitLn('BSR WRITE');
  3416.        end;
  3417.  
  3418.  
  3419.        {--------------------------------------------------------------}
  3420.        { Write Header Info }
  3421.  
  3422.        procedure Header;
  3423.        begin
  3424.           WriteLn('WARMST', TAB, 'EQU $A01E');
  3425.        end;
  3426.  
  3427.  
  3428.        {--------------------------------------------------------------}
  3429.        { Write the Prolog }
  3430.  
  3431.        procedure Prolog;
  3432.        begin
  3433.           PostLabel('MAIN');
  3434.        end;
  3435.  
  3436.  
  3437.        {--------------------------------------------------------------}
  3438.        { Write the Epilog }
  3439.  
  3440.        procedure Epilog;
  3441.        begin
  3442.           EmitLn('DC WARMST');
  3443.           EmitLn('END MAIN');
  3444.        end;
  3445.  
  3446.  
  3447.        {---------------------------------------------------------------}
  3448.        { Parse and Translate a Math Factor }
  3449.  
  3450.        procedure BoolExpression; Forward;A6A6
  3451.                                     - 56 -A*A*
  3452.  
  3453. PA A
  3454.  
  3455.  
  3456.  
  3457.  
  3458.  
  3459.        procedure Factor;
  3460.        begin
  3461.           if Look = '(' then begin
  3462.              Match('(');
  3463.              BoolExpression;
  3464.              Match(')');
  3465.              end
  3466.           else if IsAlpha(Look) then begin
  3467.              GetName;
  3468.              LoadVar(Value);
  3469.              end
  3470.           else
  3471.              LoadConst(GetNum);
  3472.        end;
  3473.  
  3474.  
  3475.        {--------------------------------------------------------------}
  3476.        { Parse and Translate a Negative Factor }
  3477.  
  3478.        procedure NegFactor;
  3479.        begin
  3480.           Match('-');
  3481.           if IsDigit(Look) then
  3482.              LoadConst(-GetNum)
  3483.           else begin
  3484.              Factor;
  3485.              Negate;
  3486.           end;
  3487.        end;
  3488.  
  3489.  
  3490.        {--------------------------------------------------------------}
  3491.        { Parse and Translate a Leading Factor }
  3492.  
  3493.        procedure FirstFactor;
  3494.        begin
  3495.           case Look of
  3496.             '+': begin
  3497.                     Match('+');
  3498.                     Factor;
  3499.                  end;
  3500.             '-': NegFactor;
  3501.           else  Factor;
  3502.           end;
  3503.        end;
  3504.  
  3505.  
  3506.        {--------------------------------------------------------------}
  3507.        { Recognize and Translate a Multiply }
  3508.  
  3509.        procedure Multiply;
  3510.        begin
  3511.           Match('*');
  3512.           Factor;A*A*
  3513.                                     - 57 -
  3514.  
  3515. PA A
  3516.  
  3517.  
  3518.  
  3519.  
  3520.  
  3521.           PopMul;
  3522.        end;
  3523.  
  3524.  
  3525.        {-------------------------------------------------------------}
  3526.        { Recognize and Translate a Divide }
  3527.  
  3528.        procedure Divide;
  3529.        begin
  3530.           Match('/');
  3531.           Factor;
  3532.           PopDiv;
  3533.        end;
  3534.  
  3535.  
  3536.        {---------------------------------------------------------------}
  3537.        { Common Code Used by Term and FirstTerm }
  3538.  
  3539.        procedure Term1;
  3540.        begin
  3541.           while IsMulop(Look) do begin
  3542.              Push;
  3543.              case Look of
  3544.               '*': Multiply;
  3545.               '/': Divide;
  3546.              end;
  3547.           end;
  3548.        end;
  3549.  
  3550.  
  3551.        {---------------------------------------------------------------}
  3552.        { Parse and Translate a Math Term }
  3553.  
  3554.        procedure Term;
  3555.        begin
  3556.           Factor;
  3557.           Term1;
  3558.        end;
  3559.  
  3560.  
  3561.        {---------------------------------------------------------------}
  3562.        { Parse and Translate a Leading Term }
  3563.  
  3564.        procedure FirstTerm;
  3565.        begin
  3566.           FirstFactor;
  3567.           Term1;
  3568.        end;
  3569.  
  3570.  
  3571.        {--------------------------------------------------------------}
  3572.        { Recognize and Translate an Add }
  3573.  
  3574.        procedure Add;A*A*
  3575.                                     - 58 -
  3576.  
  3577. PA A
  3578.  
  3579.  
  3580.  
  3581.  
  3582.  
  3583.        begin
  3584.           Match('+');
  3585.           Term;
  3586.           PopAdd;
  3587.        end;
  3588.  
  3589.  
  3590.        {-------------------------------------------------------------}
  3591.        { Recognize and Translate a Subtract }
  3592.  
  3593.        procedure Subtract;
  3594.        begin
  3595.           Match('-');
  3596.           Term;
  3597.           PopSub;
  3598.        end;
  3599.  
  3600.  
  3601.        {---------------------------------------------------------------}
  3602.        { Parse and Translate an Expression }
  3603.  
  3604.        procedure Expression;
  3605.        begin
  3606.           FirstTerm;
  3607.           while IsAddop(Look) do begin
  3608.              Push;
  3609.              case Look of
  3610.               '+': Add;
  3611.               '-': Subtract;
  3612.              end;
  3613.           end;
  3614.        end;
  3615.  
  3616.  
  3617.        {---------------------------------------------------------------}
  3618.        { Recognize and Translate a Relational "Equals" }
  3619.  
  3620.        procedure Equal;
  3621.        begin
  3622.           Match('=');
  3623.           Expression;
  3624.           PopCompare;
  3625.           SetEqual;
  3626.        end;
  3627.  
  3628.  
  3629.        {---------------------------------------------------------------}
  3630.        { Recognize and Translate a Relational "Less Than or Equal" }
  3631.  
  3632.        procedure LessOrEqual;
  3633.        begin
  3634.           Match('=');
  3635.           Expression;
  3636.           PopCompare;A*A*
  3637.                                     - 59 -
  3638.  
  3639. PA A
  3640.  
  3641.  
  3642.  
  3643.  
  3644.  
  3645.           SetLessOrEqual;
  3646.        end;
  3647.  
  3648.  
  3649.        {---------------------------------------------------------------}
  3650.        { Recognize and Translate a Relational "Not Equals" }
  3651.  
  3652.        procedure NotEqual;
  3653.        begin
  3654.           Match('>');
  3655.           Expression;
  3656.           PopCompare;
  3657.           SetNEqual;
  3658.        end;
  3659.  
  3660.  
  3661.        {---------------------------------------------------------------}
  3662.        { Recognize and Translate a Relational "Less Than" }
  3663.  
  3664.        procedure Less;
  3665.        begin
  3666.           Match('<');
  3667.           case Look of
  3668.             '=': LessOrEqual;
  3669.             '>': NotEqual;
  3670.           else begin
  3671.                   Expression;
  3672.                   PopCompare;
  3673.                   SetLess;
  3674.                end;
  3675.           end;
  3676.        end;
  3677.  
  3678.  
  3679.        {---------------------------------------------------------------}
  3680.        { Recognize and Translate a Relational "Greater Than" }
  3681.  
  3682.        procedure Greater;
  3683.        begin
  3684.           Match('>');
  3685.           if Look = '=' then begin
  3686.              Match('=');
  3687.              Expression;
  3688.              PopCompare;
  3689.              SetGreaterOrEqual;
  3690.              end
  3691.           else begin
  3692.              Expression;
  3693.              PopCompare;
  3694.              SetGreater;
  3695.           end;
  3696.        end;ABAB
  3697.                                     - 60 -A*A*
  3698.  
  3699. PA A
  3700.  
  3701.  
  3702.  
  3703.  
  3704.  
  3705.        {---------------------------------------------------------------}
  3706.        { Parse and Translate a Relation }
  3707.  
  3708.  
  3709.        procedure Relation;
  3710.        begin
  3711.           Expression;
  3712.           if IsRelop(Look) then begin
  3713.              Push;
  3714.              case Look of
  3715.               '=': Equal;
  3716.               '<': Less;
  3717.               '>': Greater;
  3718.              end;
  3719.           end;
  3720.        end;
  3721.  
  3722.  
  3723.        {---------------------------------------------------------------}
  3724.        { Parse and Translate a Boolean Factor with Leading NOT }
  3725.  
  3726.        procedure NotFactor;
  3727.        begin
  3728.           if Look = '!' then begin
  3729.              Match('!');
  3730.              Relation;
  3731.              NotIt;
  3732.              end
  3733.           else
  3734.              Relation;
  3735.        end;
  3736.  
  3737.  
  3738.        {---------------------------------------------------------------}
  3739.        { Parse and Translate a Boolean Term }
  3740.  
  3741.        procedure BoolTerm;
  3742.        begin
  3743.           NotFactor;
  3744.           while Look = '&' do begin
  3745.              Push;
  3746.              Match('&');
  3747.              NotFactor;
  3748.              PopAnd;
  3749.           end;
  3750.        end;
  3751.  
  3752.  
  3753.        {--------------------------------------------------------------}
  3754.        { Recognize and Translate a Boolean OR }
  3755.  
  3756.        procedure BoolOr;
  3757.        begin
  3758.           Match('|');A*A*
  3759.                                     - 61 -
  3760.  
  3761. PA A
  3762.  
  3763.  
  3764.  
  3765.  
  3766.  
  3767.           BoolTerm;
  3768.           PopOr;
  3769.        end;
  3770.  
  3771.  
  3772.        {--------------------------------------------------------------}
  3773.        { Recognize and Translate an Exclusive Or }
  3774.  
  3775.        procedure BoolXor;
  3776.        begin
  3777.           Match('~');
  3778.           BoolTerm;
  3779.           PopXor;
  3780.        end;
  3781.  
  3782.  
  3783.        {---------------------------------------------------------------}
  3784.        { Parse and Translate a Boolean Expression }
  3785.  
  3786.        procedure BoolExpression;
  3787.        begin
  3788.           BoolTerm;
  3789.           while IsOrOp(Look) do begin
  3790.              Push;
  3791.              case Look of
  3792.               '|': BoolOr;
  3793.               '~': BoolXor;
  3794.              end;
  3795.           end;
  3796.        end;
  3797.  
  3798.  
  3799.        {--------------------------------------------------------------}
  3800.        { Parse and Translate an Assignment Statement }
  3801.  
  3802.        procedure Assignment;
  3803.        var Name: string;
  3804.        begin
  3805.           Name := Value;
  3806.           Match('=');
  3807.           BoolExpression;
  3808.           Store(Name);
  3809.        end;
  3810.  
  3811.  
  3812.        {---------------------------------------------------------------}
  3813.        { Recognize and Translate an IF Construct }
  3814.  
  3815.        procedure Block; Forward;
  3816.  
  3817.  
  3818.        procedure DoIf;
  3819.        var L1, L2: string;
  3820.        beginA*A*
  3821.                                     - 62 -
  3822.  
  3823. PA A
  3824.  
  3825.  
  3826.  
  3827.  
  3828.  
  3829.           BoolExpression;
  3830.           L1 := NewLabel;
  3831.           L2 := L1;
  3832.           BranchFalse(L1);
  3833.           Block;
  3834.           if Token = 'l' then begin
  3835.              L2 := NewLabel;
  3836.              Branch(L2);
  3837.              PostLabel(L1);
  3838.              Block;
  3839.           end;
  3840.           PostLabel(L2);
  3841.           MatchString('ENDIF');
  3842.        end;
  3843.  
  3844.  
  3845.        {--------------------------------------------------------------}
  3846.        { Parse and Translate a WHILE Statement }
  3847.  
  3848.        procedure DoWhile;
  3849.        var L1, L2: string;
  3850.        begin
  3851.           L1 := NewLabel;
  3852.           L2 := NewLabel;
  3853.           PostLabel(L1);
  3854.           BoolExpression;
  3855.           BranchFalse(L2);
  3856.           Block;
  3857.           MatchString('ENDWHILE');
  3858.           Branch(L1);
  3859.           PostLabel(L2);
  3860.        end;
  3861.  
  3862.  
  3863.        {--------------------------------------------------------------}
  3864.        { Process a Read Statement }
  3865.  
  3866.        procedure DoRead;
  3867.        begin
  3868.           Match('(');
  3869.           GetName;
  3870.           ReadVar;
  3871.           while Look = ',' do begin
  3872.              Match(',');
  3873.              GetName;
  3874.              ReadVar;
  3875.           end;
  3876.           Match(')');
  3877.        end;
  3878.  
  3879.  
  3880.        {--------------------------------------------------------------}
  3881.        { Process a Write Statement }A6A6
  3882.                                     - 63 -A*A*
  3883.  
  3884. PA A
  3885.  
  3886.  
  3887.  
  3888.  
  3889.  
  3890.        procedure DoWrite;
  3891.        begin
  3892.           Match('(');
  3893.           Expression;
  3894.           WriteVar;
  3895.           while Look = ',' do begin
  3896.              Match(',');
  3897.              Expression;
  3898.              WriteVar;
  3899.           end;
  3900.           Match(')');
  3901.        end;
  3902.  
  3903.  
  3904.        {--------------------------------------------------------------}
  3905.        { Parse and Translate a Block of Statements }
  3906.  
  3907.        procedure Block;
  3908.        begin
  3909.           Scan;
  3910.           while not(Token in ['e', 'l']) do begin
  3911.              case Token of
  3912.               'i': DoIf;
  3913.               'w': DoWhile;
  3914.               'R': DoRead;
  3915.               'W': DoWrite;
  3916.              else Assignment;
  3917.              end;
  3918.              Scan;
  3919.           end;
  3920.        end;
  3921.  
  3922.  
  3923.        {--------------------------------------------------------------}
  3924.        { Allocate Storage for a Variable }
  3925.  
  3926.        procedure Alloc(N: Symbol);
  3927.        begin
  3928.           if InTable(N) then Abort('Duplicate Variable Name ' + N);
  3929.           AddEntry(N, 'v');
  3930.           Write(N, ':', TAB, 'DC ');
  3931.           if Look = '=' then begin
  3932.              Match('=');
  3933.              If Look = '-' then begin
  3934.                 Write(Look);
  3935.                 Match('-');
  3936.              end;
  3937.              WriteLn(GetNum);
  3938.              end
  3939.           else
  3940.              WriteLn('0');
  3941.        end;ABAB
  3942.                                     - 64 -A*A*
  3943.  
  3944. PA A
  3945.  
  3946.  
  3947.  
  3948.  
  3949.  
  3950.        {--------------------------------------------------------------}
  3951.        { Parse and Translate a Data Declaration }
  3952.  
  3953.        procedure Decl;
  3954.        begin
  3955.           GetName;
  3956.           Alloc(Value);
  3957.           while Look = ',' do begin
  3958.              Match(',');
  3959.              GetName;
  3960.              Alloc(Value);
  3961.           end;
  3962.        end;
  3963.  
  3964.  
  3965.        {--------------------------------------------------------------}
  3966.        { Parse and Translate Global Declarations }
  3967.  
  3968.        procedure TopDecls;
  3969.        begin
  3970.           Scan;
  3971.           while Token <> 'b' do begin
  3972.              case Token of
  3973.                'v': Decl;
  3974.              else Abort('Unrecognized Keyword ' + Value);
  3975.              end;
  3976.              Scan;
  3977.           end;
  3978.        end;
  3979.  
  3980.  
  3981.        {--------------------------------------------------------------}
  3982.        { Parse and Translate a Main Program }
  3983.  
  3984.        procedure Main;
  3985.        begin
  3986.           MatchString('BEGIN');
  3987.           Prolog;
  3988.           Block;
  3989.           MatchString('END');
  3990.           Epilog;
  3991.        end;
  3992.  
  3993.  
  3994.        {--------------------------------------------------------------}
  3995.        {  Parse and Translate a Program }
  3996.  
  3997.        procedure Prog;
  3998.        begin
  3999.           MatchString('PROGRAM');
  4000.           Header;
  4001.           TopDecls;
  4002.           Main;
  4003.           Match('.');A*A*
  4004.                                     - 65 -
  4005.  
  4006. PA A
  4007.  
  4008.  
  4009.  
  4010.  
  4011.  
  4012.        end;
  4013.  
  4014.  
  4015.        {--------------------------------------------------------------}
  4016.        { Initialize }
  4017.  
  4018.        procedure Init;
  4019.        var i: integer;
  4020.        begin
  4021.           for i := 1 to MaxEntry do begin
  4022.              ST[i] := '';
  4023.              SType[i] := ' ';
  4024.           end;
  4025.           GetChar;
  4026.           Scan;
  4027.        end;
  4028.  
  4029.  
  4030.        {--------------------------------------------------------------}
  4031.        { Main Program }
  4032.  
  4033.        begin
  4034.           Init;
  4035.           Prog;
  4036.           if Look <> CR then Abort('Unexpected data after ''.''');
  4037.        end.
  4038.        {--------------------------------------------------------------}
  4039.  
  4040.  
  4041.  
  4042.        *****************************************************************
  4043.        *                                                               *
  4044.        *                        COPYRIGHT NOTICE                       *
  4045.        *                                                               *
  4046.        *   Copyright (C) 1989 Jack W. Crenshaw. All rights reserved.   *
  4047.        *                                                               *
  4048.        *****************************************************************ARAR
  4049.  
  4050.  
  4051.                                     - 66 -A*A*
  4052. @