home *** CD-ROM | disk | FTP | other *** search
/ Programmer Power Tools / Programmer Power Tools.iso / turbopas / tutorpas.arc / TUTOR11.DOC < prev    next >
Encoding:
Text File  |  1989-06-03  |  53.3 KB  |  1,976 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.                                   3 June 1989
  37.  
  38.  
  39.                         Part XI: LEXICAL SCAN REVISITED
  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.        I've got some  good news and some bad news.  The bad news is that
  87.        this installment is  not  the  one  I promised last time.  What's
  88.        more, the one after this one won't be, either.
  89.  
  90.        The good news is the reason for this installment:  I've  found  a
  91.        way  to simplify and improve the lexical  scanning  part  of  the
  92.        compiler.  Let me explain.
  93.  
  94.  
  95.        BACKGROUND
  96.  
  97.        If  you'll remember, we talked at length  about  the  subject  of
  98.        lexical  scanners in Part VII, and I left you with a design for a
  99.        distributed scanner that I felt was about as simple  as  I  could
  100.        make it ... more than most that I've  seen  elsewhere.    We used
  101.        that idea in Part X.  The compiler structure  that  resulted  was
  102.        simple, and it got the job done.
  103.  
  104.        Recently, though, I've begun  to  have  problems, and they're the
  105.        kind that send a message that you might be doing something wrong.
  106.  
  107.        The  whole thing came to a head when I tried to address the issue
  108.        of  semicolons.  Several people have asked  me  about  them,  and
  109.        whether or not KISS will have them separating the statements.  My
  110.        intention has been NOT to  use semicolons, simply because I don't
  111.        like them and, as you can see, they have not proved necessary.
  112.  
  113.        But I know that many of you, like me, have  gotten  used to them,
  114.        and so  I  set  out  to write a short installment to show you how
  115.        they could easily be added, if you were so inclined.
  116.  
  117.        Well, it  turned  out  that  they weren't easy to add at all.  In
  118.        fact it was darned difficult.
  119.  
  120.        I guess I should have  realized that something was wrong, because
  121.        of the issue  of  newlines.    In the last couple of installments
  122.        we've addressed that issue,  and  I've shown you how to deal with
  123.        newlines with a  procedure called, appropriately enough, NewLine.
  124.        In  TINY  Version  1.0,  I  sprinkled calls to this procedure  in
  125.        strategic spots in the code.
  126.  
  127.        It  seems  that  every time I've addressed the issue of newlines,
  128.        though,  I've found it to be tricky,  and  the  resulting  parserA*A*
  129.                                      - 2 -
  130.  
  131. PA A
  132.  
  133.  
  134.  
  135.  
  136.  
  137.        turned out to be quite fragile ... one addition or  deletion here
  138.        or  there and things tended to go to pot.  Looking back on it,  I
  139.        realize that  there  was  a  message  in  this that I just wasn't
  140.        paying attention to.
  141.  
  142.        When I tried to add semicolons  on  top of the newlines, that was
  143.        the last straw.   I ended up with much too complex a solution.  I
  144.        began to realize that something fundamental had to change.
  145.  
  146.        So,  in  a  way this installment will cause us to backtrack a bit
  147.        and revisit the issue of scanning all over again.    Sorry  about
  148.        that.  That's the price you pay for watching me  do  this in real
  149.        time.  But the new version is definitely an improvement, and will
  150.        serve us well for what is to come.
  151.  
  152.        As  I said, the scanner we used in Part X was about as simple  as
  153.        one can get.  But anything can be improved.   The  new scanner is
  154.        more like the classical  scanner,  and  not  as simple as before.
  155.        But the overall  compiler  structure is even simpler than before.
  156.        It's also more robust, and easier to add  to  and/or  modify.   I
  157.        think that's worth the time spent in this digression.  So in this
  158.        installment, I'll be showing  you  the  new  structure.  No doubt
  159.        you'll  be  happy  to  know  that, while the changes affect  many
  160.        procedures, they aren't very profound  and so we lose very little
  161.        of what's been done so far.
  162.  
  163.        Ironically, the new scanner  is  much  more conventional than the
  164.        old one, and is very much like the more generic scanner  I showed
  165.        you  earlier  in  Part VII.  Then I started trying to get clever,
  166.        and I almost clevered myself clean out of business.   You'd think
  167.        one day I'd learn: K-I-S-S!
  168.  
  169.  
  170.        THE PROBLEM
  171.  
  172.        The problem begins to show  itself in procedure Block, which I've
  173.        reproduced below:
  174.  
  175.  
  176.        {--------------------------------------------------------------}
  177.        { Parse and Translate a Block of Statements }
  178.  
  179.        procedure Block;
  180.        begin
  181.           Scan;
  182.           while not(Token in ['e', 'l']) do begin
  183.              case Token of
  184.               'i': DoIf;
  185.               'w': DoWhile;
  186.               'R': DoRead;
  187.               'W': DoWrite;
  188.              else Assignment;
  189.              end;
  190.              Scan;A*A*
  191.                                      - 3 -
  192.  
  193. PA A
  194.  
  195.  
  196.  
  197.  
  198.  
  199.           end;
  200.        end;
  201.        {--------------------------------------------------------------}
  202.  
  203.  
  204.        As  you   can  see,  Block  is  oriented  to  individual  program
  205.        statements.  At each pass through  the  loop, we know that we are
  206.        at  the beginning of a statement.  We exit the block when we have
  207.        scanned an END or an ELSE.
  208.  
  209.        But suppose that we see a semicolon instead.   The  procedure  as
  210.        it's shown above  can't  handle that, because procedure Scan only
  211.        expects and can only accept tokens that begin with a letter.
  212.  
  213.        I  tinkered  around for quite awhile to come up with a  fix.    I
  214.        found many possible approaches, but none were very satisfying.  I
  215.        finally figured out the reason.
  216.  
  217.        Recall that when we started with our single-character parsers, we
  218.        adopted a convention that the lookahead character would always be
  219.        prefetched.    That   is,   we  would  have  the  character  that
  220.        corresponds to our  current  position in the input stream fetched
  221.        into the global character Look, so that we could  examine  it  as
  222.        many  times  as  needed.    The  rule  we  adopted was that EVERY
  223.        recognizer, if it found its target token, would  advance  Look to
  224.        the next character in the input stream.
  225.  
  226.        That simple and fixed convention served us very well when  we had
  227.        single-character tokens, and it still does.  It would make  a lot
  228.        of sense to apply the same rule to multi-character tokens.
  229.  
  230.        But when we got into lexical scanning, I began  to  violate  that
  231.        simple rule.  The scanner of Part X  did  indeed  advance  to the
  232.        next token if it found an identifier or keyword, but it DIDN'T do
  233.        that if it found a carriage return, a whitespace character, or an
  234.        operator.
  235.  
  236.        Now, that sort of mixed-mode  operation gets us into deep trouble
  237.        in procedure Block, because whether or not the  input  stream has
  238.        been advanced depends upon the kind of token we  encounter.    If
  239.        it's  a keyword or the target of  an  assignment  statement,  the
  240.        "cursor," as defined by the contents of Look,  has  been advanced
  241.        to  the next token OR to the beginning of whitespace.  If, on the
  242.        other  hand,  the  token  is  a  semicolon,  or if we have hit  a
  243.        carriage return, the cursor has NOT advanced.
  244.  
  245.        Needless to say, we can add enough logic  to  keep  us  on track.
  246.        But it's tricky, and makes the whole parser very fragile.
  247.  
  248.        There's a much  better  way,  and  that's just to adopt that same
  249.        rule that's worked so well before, to apply to TOKENS as  well as
  250.        single characters.  In other words, we'll prefetch tokens just as
  251.        we've always done for  characters.   It seems so obvious once you
  252.        think about it that way.A*A*
  253.                                      - 4 -
  254.  
  255. PA A
  256.  
  257.  
  258.  
  259.  
  260.  
  261.        Interestingly enough, if we do things this way  the  problem that
  262.        we've had with newline characters goes away.  We  can  just  lump
  263.        them in as  whitespace  characters, which means that the handling
  264.        of  newlines  becomes  very trivial, and MUCH less prone to error
  265.        than we've had to deal with in the past.
  266.  
  267.  
  268.        THE SOLUTION
  269.  
  270.        Let's  begin  to  fix  the  problem  by  re-introducing  the  two
  271.        procedures:
  272.  
  273.        {--------------------------------------------------------------}
  274.        { Get an Identifier }
  275.  
  276.        procedure GetName;
  277.        begin
  278.           SkipWhite;
  279.           if Not IsAlpha(Look) then Expected('Identifier');
  280.           Token := 'x';
  281.           Value := '';
  282.           repeat
  283.              Value := Value + UpCase(Look);
  284.              GetChar;
  285.           until not IsAlNum(Look);
  286.        end;
  287.  
  288.  
  289.        {--------------------------------------------------------------}
  290.        { Get a Number }
  291.  
  292.        procedure GetNum;
  293.        begin
  294.           SkipWhite;
  295.           if not IsDigit(Look) then Expected('Number');
  296.           Token := '#';
  297.           Value := '';
  298.           repeat
  299.              Value := Value + Look;
  300.              GetChar;
  301.           until not IsDigit(Look);
  302.        end;
  303.        {--------------------------------------------------------------}
  304.  
  305.  
  306.        These two procedures are  functionally  almost  identical  to the
  307.        ones  I  showed  you in Part VII.  They each  fetch  the  current
  308.        token, either an identifier or a number, into  the  global string
  309.        Value.    They  also  set  the  encoded  version, Token,  to  the
  310.        appropriate code.  The input  stream is left with Look containing
  311.        the first character NOT part of the token.
  312.  
  313.        We  can do the same thing  for  operators,  even  multi-character
  314.        operators, with a procedure such as:A*A*
  315.                                      - 5 -
  316.  
  317. PA A
  318.  
  319.  
  320.  
  321.  
  322.  
  323.        {--------------------------------------------------------------}
  324.        { Get an Operator }
  325.  
  326.        procedure GetOp;
  327.        begin
  328.           Token := Look;
  329.           Value := '';
  330.           repeat
  331.              Value := Value + Look;
  332.              GetChar;
  333.           until IsAlpha(Look) or IsDigit(Look) or IsWhite(Look);
  334.        end;
  335.        {--------------------------------------------------------------}
  336.  
  337.        Note  that  GetOp  returns,  as  its  encoded  token,  the  FIRST
  338.        character of the operator.  This is important,  because  it means
  339.        that we can now use that single character to  drive  the  parser,
  340.        instead of the lookahead character.
  341.  
  342.        We need to tie these  procedures together into a single procedure
  343.        that can handle all three  cases.  The  following  procedure will
  344.        read any one of the token types and always leave the input stream
  345.        advanced beyond it:
  346.  
  347.  
  348.        {--------------------------------------------------------------}
  349.        { Get the Next Input Token }
  350.  
  351.        procedure Next;
  352.        begin
  353.           SkipWhite;
  354.           if IsAlpha(Look) then GetName
  355.           else if IsDigit(Look) then GetNum
  356.           else GetOp;
  357.        end;
  358.        {--------------------------------------------------------------}
  359.  
  360.  
  361.        ***NOTE  that  here  I have put SkipWhite BEFORE the calls rather
  362.        than after.  This means that, in general, the variable  Look will
  363.        NOT have a meaningful value in it, and therefore  we  should  NOT
  364.        use it as a test value for parsing, as we have been doing so far.
  365.        That's the big departure from our normal approach.
  366.  
  367.        Now, remember that before I was careful not to treat the carriage
  368.        return (CR) and line  feed  (LF) characters as white space.  This
  369.        was  because,  with  SkipWhite  called  as the last thing in  the
  370.        scanner, the encounter with  LF  would  trigger a read statement.
  371.        If we were on the last line of the program,  we  couldn't get out
  372.        until we input another line with a non-white  character.   That's
  373.        why I needed the second procedure, NewLine, to handle the CRLF's.
  374.  
  375.        But now, with the call  to SkipWhite coming first, that's exactly
  376.        the behavior we want.    The  compiler  must know there's anotherA*A*
  377.                                      - 6 -
  378.  
  379. PA A
  380.  
  381.  
  382.  
  383.  
  384.  
  385.        token coming or it wouldn't be calling Next.  In other words,  it
  386.        hasn't found the terminating  END  yet.  So we're going to insist
  387.        on more data until we find something.
  388.  
  389.        All this means that we can greatly simplify both the  program and
  390.        the concepts, by treating CR and LF as whitespace characters, and
  391.        eliminating NewLine.  You  can  do  that  simply by modifying the
  392.        function IsWhite:
  393.  
  394.  
  395.        {--------------------------------------------------------------}
  396.        { Recognize White Space }
  397.  
  398.        function IsWhite(c: char): boolean;
  399.        begin
  400.           IsWhite := c in [' ', TAB, CR, LF];
  401.        end;
  402.        {--------------------------------------------------------------}
  403.  
  404.  
  405.        We've already tried similar routines in Part VII,  but  you might
  406.        as well try these new ones out.  Add them to a copy of the Cradle
  407.        and call Next with the following main program:
  408.  
  409.  
  410.        {--------------------------------------------------------------}
  411.        { Main Program }
  412.  
  413.        begin
  414.           Init;
  415.           repeat
  416.              Next;
  417.              WriteLn(Token, ' ', Value);
  418.           until Token = '.';
  419.        end.
  420.        {--------------------------------------------------------------}
  421.  
  422.  
  423.        Compile  it and verify that you can separate  a  program  into  a
  424.        series of tokens, and that you get the right  encoding  for  each
  425.        token.
  426.  
  427.        This ALMOST works,  but  not  quite.    There  are  two potential
  428.        problems:    First,  in KISS/TINY almost all of our operators are
  429.        single-character operators.  The only exceptions  are  the relops
  430.        >=, <=, and <>.  It seems  a  shame  to  treat  all  operators as
  431.        strings and do a  string  compare,  when  only a single character
  432.        compare  will  almost  always  suffice.   Second, and  much  more
  433.        important, the  thing  doesn't  WORK  when  two  operators appear
  434.        together, as in (a+b)*(c+d).  Here the string following 'b' would
  435.        be interpreted as a single operator ")*(."
  436.  
  437.        It's possible to fix that problem.  For example,  we  could  just
  438.        give GetOp a  list  of  legal  characters, and we could treat theA*A*
  439.                                      - 7 -
  440.  
  441. PA A
  442.  
  443.  
  444.  
  445.  
  446.  
  447.        parentheses as different operator types  than  the  others.   But
  448.        this begins to get messy.
  449.  
  450.        Fortunately, there's a  better  way that solves all the problems.
  451.        Since almost  all the operators are single characters, let's just
  452.        treat  them  that  way, and let GetOp get only one character at a
  453.        time.  This not only simplifies GetOp, but also speeds  things up
  454.        quite a  bit.    We  still have the problem of the relops, but we
  455.        were treating them as special cases anyway.
  456.  
  457.        So here's the final version of GetOp:
  458.  
  459.  
  460.        {--------------------------------------------------------------}
  461.        { Get an Operator }
  462.  
  463.        procedure GetOp;
  464.        begin
  465.           SkipWhite;
  466.           Token := Look;
  467.           Value := Look;
  468.           GetChar;
  469.        end;
  470.        {--------------------------------------------------------------}
  471.  
  472.  
  473.        Note that I still give the string Value a value.  If you're truly
  474.        concerned about efficiency, you could leave this out.  When we're
  475.        expecting an operator, we will only be testing  Token  anyhow, so
  476.        the  value of the string won't matter.  But to me it seems to  be
  477.        good practice to give the thing a value just in case.
  478.  
  479.        Try  this  new  version with some realistic-looking  code.    You
  480.        should  be  able  to  separate  any program into  its  individual
  481.        tokens, with the  caveat  that the two-character relops will scan
  482.        into two separate tokens.  That's OK ... we'll  parse  them  that
  483.        way.
  484.  
  485.        Now, in Part VII the function of Next was combined with procedure
  486.        Scan,  which  also  checked every identifier against  a  list  of
  487.        keywords and encoded each one that was found.  As I  mentioned at
  488.        the time, the last thing we would want  to  do  is  to use such a
  489.        procedure in places where keywords  should not appear, such as in
  490.        expressions.  If we  did  that, the keyword list would be scanned
  491.        for every identifier appearing in the code.  Not good.
  492.  
  493.        The  right  way  to  deal  with  that  is  to simply separate the
  494.        functions  of  fetching  tokens and looking for  keywords.    The
  495.        version of Scan shown below  does NOTHING but check for keywords.
  496.        Notice that it operates on the current token and does NOT advance
  497.        the input stream.
  498.  
  499.  
  500.        {--------------------------------------------------------------}A*A*
  501.                                      - 8 -
  502.  
  503. PA A
  504.  
  505.  
  506.  
  507.  
  508.  
  509.        { Scan the Current Identifier for Keywords }
  510.  
  511.        procedure Scan;
  512.        begin
  513.           if Token = 'x' then
  514.              Token := KWcode[Lookup(Addr(KWlist), Value, NKW) + 1];
  515.        end;
  516.        {--------------------------------------------------------------}
  517.  
  518.  
  519.        There is one last detail.  In the compiler there are a few places
  520.        that we must  actually  check  the  string  value  of  the token.
  521.        Mainly, this  is done to distinguish between the different END's,
  522.        but there are a couple  of  other  places.    (I  should  note in
  523.        passing that we could always  eliminate the need for matching END
  524.        characters by encoding each one  to a different character.  Right
  525.        now we are definitely taking the lazy man's route.)
  526.  
  527.        The  following  version  of MatchString takes the  place  of  the
  528.        character-oriented Match.  Note that, like Match, it DOES advance
  529.        the input stream.
  530.  
  531.  
  532.        {--------------------------------------------------------------}
  533.        { Match a Specific Input String }
  534.  
  535.        procedure MatchString(x: string);
  536.        begin
  537.           if Value <> x then Expected('''' + x + '''');
  538.           Next;
  539.        end;
  540.        {--------------------------------------------------------------}
  541.  
  542.  
  543.        FIXING UP THE COMPILER
  544.  
  545.        Armed with these new scanner procedures, we can now begin  to fix
  546.        the compiler to  use  them  properly.   The changes are all quite
  547.        minor,  but  there  are quite a  few  places  where  changes  are
  548.        necessary.  Rather than  showing  you each place, I will give you
  549.        the general idea and then just give the finished product.
  550.  
  551.  
  552.        First of all, the code for procedure Block doesn't change, though
  553.        its function does:
  554.  
  555.  
  556.        {--------------------------------------------------------------}
  557.        { Parse and Translate a Block of Statements }
  558.  
  559.        procedure Block;
  560.        begin
  561.           Scan;
  562.           while not(Token in ['e', 'l']) do beginA*A*
  563.                                      - 9 -
  564.  
  565. PA A
  566.  
  567.  
  568.  
  569.  
  570.  
  571.              case Token of
  572.               'i': DoIf;
  573.               'w': DoWhile;
  574.               'R': DoRead;
  575.               'W': DoWrite;
  576.              else Assignment;
  577.              end;
  578.              Scan;
  579.           end;
  580.        end;
  581.        {--------------------------------------------------------------}
  582.  
  583.  
  584.        Remember that the new version of Scan doesn't  advance  the input
  585.        stream, it only  scans  for  keywords.   The input stream must be
  586.        advanced by each procedure that Block calls.
  587.  
  588.        In general, we have to replace every test on Look with  a similar
  589.        test on Token.  For example:
  590.  
  591.  
  592.        {---------------------------------------------------------------}
  593.        { Parse and Translate a Boolean Expression }
  594.  
  595.        procedure BoolExpression;
  596.        begin
  597.           BoolTerm;
  598.           while IsOrOp(Token) do begin
  599.              Push;
  600.              case Token of
  601.               '|': BoolOr;
  602.               '~': BoolXor;
  603.              end;
  604.           end;
  605.        end;
  606.        {--------------------------------------------------------------}
  607.  
  608.  
  609.        In procedures like Add, we don't  have  to use Match anymore.  We
  610.        need only call Next to advance the input stream:
  611.  
  612.  
  613.        {--------------------------------------------------------------}
  614.        { Recognize and Translate an Add }
  615.  
  616.        procedure Add;
  617.        begin
  618.           Next;
  619.           Term;
  620.           PopAdd;
  621.        end;
  622.        {-------------------------------------------------------------}ABAB
  623.                                     - 10 -A*A*
  624.  
  625. PA A
  626.  
  627.  
  628.  
  629.  
  630.  
  631.        Control  structures  are  actually simpler.  We just call Next to
  632.        advance over the control keywords:
  633.  
  634.  
  635.        {---------------------------------------------------------------}
  636.        { Recognize and Translate an IF Construct }
  637.  
  638.        procedure Block; Forward;
  639.  
  640.        procedure DoIf;
  641.        var L1, L2: string;
  642.        begin
  643.           Next;
  644.           BoolExpression;
  645.           L1 := NewLabel;
  646.           L2 := L1;
  647.           BranchFalse(L1);
  648.           Block;
  649.           if Token = 'l' then begin
  650.              Next;
  651.              L2 := NewLabel;
  652.              Branch(L2);
  653.              PostLabel(L1);
  654.              Block;
  655.           end;
  656.           PostLabel(L2);
  657.           MatchString('ENDIF');
  658.        end;
  659.        {--------------------------------------------------------------}
  660.  
  661.  
  662.        That's about the extent of the REQUIRED changes.  In  the listing
  663.        of TINY  Version  1.1  below,  I've  also  made a number of other
  664.        "improvements" that  aren't really required.  Let me explain them
  665.        briefly:
  666.  
  667.         (1)  I've deleted the two procedures Prog and Main, and combined
  668.              their functions into the main program.  They didn't seem to
  669.              add  to program clarity ... in fact  they  seemed  to  just
  670.              muddy things up a little.
  671.  
  672.         (2)  I've  deleted  the  keywords  PROGRAM  and  BEGIN  from the
  673.              keyword list.  Each  one  only occurs in one place, so it's
  674.              not necessary to search for it.
  675.  
  676.         (3)  Having been  bitten  by  an  overdose  of  cleverness, I've
  677.              reminded myself that TINY  is  supposed  to be a minimalist
  678.              program.  Therefore I've  replaced  the  fancy  handling of
  679.              unary minus with the dumbest one I could think of.  A giant
  680.              step backwards in code quality, but a  great simplification
  681.              of the compiler.  KISS is the right place to use  the other
  682.              version.ABAB
  683.                                     - 11 -A*A*
  684.  
  685. PA A
  686.  
  687.  
  688.  
  689.  
  690.  
  691.         (4)  I've added some  error-checking routines such as CheckTable
  692.              and CheckDup, and  replaced  in-line code by calls to them.
  693.              This cleans up a number of routines.
  694.  
  695.         (5)  I've  taken  the  error  checking  out  of  code generation
  696.              routines  like Store, and put it in  the  parser  where  it
  697.              belongs.  See Assignment, for example.
  698.  
  699.         (6)  There was an error in InTable and Locate  that  caused them
  700.              to search all locations  instead  of  only those with valid
  701.              data  in them.  They now search only  valid  cells.    This
  702.              allows us to eliminate  the  initialization  of  the symbol
  703.              table, which was done in Init.
  704.  
  705.         (7)  Procedure AddEntry now has two  arguments,  which  helps to
  706.              make things a bit more modular.
  707.  
  708.         (8)  I've cleaned up the  code  for  the relational operators by
  709.              the addition of the  new  procedures  CompareExpression and
  710.              NextExpression.
  711.  
  712.         (9)  I fixed an error in the Read routine ... the  earlier value
  713.              did not check for a valid variable name.
  714.  
  715.  
  716.         CONCLUSION
  717.  
  718.        The resulting compiler for  TINY  is given below.  Other than the
  719.        removal  of  the  keyword PROGRAM, it parses the same language as
  720.        before.    It's  just  a  bit cleaner, and more importantly  it's
  721.        considerably more robust.  I feel good about it.
  722.  
  723.        The next installment will be another  digression:  the discussion
  724.        of  semicolons  and  such that got me into this mess in the first
  725.        place.  THEN we'll press on  into  procedures and types.  Hang in
  726.        there with me.  The addition of those features will go a long way
  727.        towards removing KISS from  the  "toy  language" category.  We're
  728.        getting very close to being able to write a serious compiler.
  729.  
  730.  
  731.        TINY VERSION 1.1
  732.  
  733.  
  734.        {--------------------------------------------------------------}
  735.        program Tiny11;
  736.  
  737.        {--------------------------------------------------------------}
  738.        { Constant Declarations }
  739.  
  740.        const TAB = ^I;
  741.              CR  = ^M;
  742.              LF  = ^J;
  743.  
  744.              LCount: integer = 0;A*A*
  745.                                     - 12 -
  746.  
  747. PA A
  748.  
  749.  
  750.  
  751.  
  752.  
  753.              NEntry: integer = 0;
  754.  
  755.  
  756.        {--------------------------------------------------------------}
  757.        { Type Declarations }
  758.  
  759.        type Symbol = string[8];
  760.  
  761.             SymTab = array[1..1000] of Symbol;
  762.  
  763.             TabPtr = ^SymTab;
  764.  
  765.  
  766.        {--------------------------------------------------------------}
  767.        { Variable Declarations }
  768.  
  769.        var Look : char;             { Lookahead Character }
  770.            Token: char;             { Encoded Token       }
  771.            Value: string[16];       { Unencoded Token     }
  772.  
  773.  
  774.        const MaxEntry = 100;
  775.  
  776.        var ST   : array[1..MaxEntry] of Symbol;
  777.            SType: array[1..MaxEntry] of char;
  778.  
  779.  
  780.        {--------------------------------------------------------------}
  781.        { Definition of Keywords and Token Types }
  782.  
  783.        const NKW =   9;
  784.              NKW1 = 10;
  785.  
  786.        const KWlist: array[1..NKW] of Symbol =
  787.                      ('IF', 'ELSE', 'ENDIF', 'WHILE', 'ENDWHILE',
  788.                       'READ', 'WRITE', 'VAR', 'END');
  789.  
  790.        const KWcode: string[NKW1] = 'xileweRWve';
  791.  
  792.  
  793.        {--------------------------------------------------------------}
  794.        { Read New Character From Input Stream }
  795.  
  796.        procedure GetChar;
  797.        begin
  798.           Read(Look);
  799.        end;
  800.  
  801.        {--------------------------------------------------------------}
  802.        { Report an Error }
  803.  
  804.        procedure Error(s: string);
  805.        begin
  806.           WriteLn;A*A*
  807.                                     - 13 -
  808.  
  809. PA A
  810.  
  811.  
  812.  
  813.  
  814.  
  815.           WriteLn(^G, 'Error: ', s, '.');
  816.        end;
  817.  
  818.  
  819.        {--------------------------------------------------------------}
  820.        { Report Error and Halt }
  821.  
  822.        procedure Abort(s: string);
  823.        begin
  824.           Error(s);
  825.           Halt;
  826.        end;
  827.  
  828.  
  829.        {--------------------------------------------------------------}
  830.        { Report What Was Expected }
  831.  
  832.        procedure Expected(s: string);
  833.        begin
  834.           Abort(s + ' Expected');
  835.        end;
  836.  
  837.        {--------------------------------------------------------------}
  838.        { Report an Undefined Identifier }
  839.  
  840.        procedure Undefined(n: string);
  841.        begin
  842.           Abort('Undefined Identifier ' + n);
  843.        end;
  844.  
  845.  
  846.        {--------------------------------------------------------------}
  847.        { Report a Duplicate Identifier }
  848.  
  849.        procedure Duplicate(n: string);
  850.        begin
  851.           Abort('Duplicate Identifier ' + n);
  852.        end;
  853.  
  854.  
  855.        {--------------------------------------------------------------}
  856.        { Check to Make Sure the Current Token is an Identifier }
  857.  
  858.        procedure CheckIdent;
  859.        begin
  860.           if Token <> 'x' then Expected('Identifier');
  861.        end;
  862.  
  863.  
  864.        {--------------------------------------------------------------}
  865.        { Recognize an Alpha Character }
  866.  
  867.        function IsAlpha(c: char): boolean;
  868.        beginA*A*
  869.                                     - 14 -
  870.  
  871. PA A
  872.  
  873.  
  874.  
  875.  
  876.  
  877.           IsAlpha := UpCase(c) in ['A'..'Z'];
  878.        end;
  879.  
  880.  
  881.        {--------------------------------------------------------------}
  882.        { Recognize a Decimal Digit }
  883.  
  884.        function IsDigit(c: char): boolean;
  885.        begin
  886.           IsDigit := c in ['0'..'9'];
  887.        end;
  888.  
  889.  
  890.        {--------------------------------------------------------------}
  891.        { Recognize an AlphaNumeric Character }
  892.  
  893.        function IsAlNum(c: char): boolean;
  894.        begin
  895.           IsAlNum := IsAlpha(c) or IsDigit(c);
  896.        end;
  897.  
  898.  
  899.        {--------------------------------------------------------------}
  900.        { Recognize an Addop }
  901.  
  902.        function IsAddop(c: char): boolean;
  903.        begin
  904.           IsAddop := c in ['+', '-'];
  905.        end;
  906.  
  907.  
  908.        {--------------------------------------------------------------}
  909.        { Recognize a Mulop }
  910.  
  911.        function IsMulop(c: char): boolean;
  912.        begin
  913.           IsMulop := c in ['*', '/'];
  914.        end;
  915.  
  916.  
  917.        {--------------------------------------------------------------}
  918.        { Recognize a Boolean Orop }
  919.  
  920.        function IsOrop(c: char): boolean;
  921.        begin
  922.           IsOrop := c in ['|', '~'];
  923.        end;
  924.  
  925.  
  926.        {--------------------------------------------------------------}
  927.        { Recognize a Relop }
  928.  
  929.        function IsRelop(c: char): boolean;
  930.        beginA*A*
  931.                                     - 15 -
  932.  
  933. PA A
  934.  
  935.  
  936.  
  937.  
  938.  
  939.           IsRelop := c in ['=', '#', '<', '>'];
  940.        end;
  941.  
  942.  
  943.        {--------------------------------------------------------------}
  944.        { Recognize White Space }
  945.  
  946.        function IsWhite(c: char): boolean;
  947.        begin
  948.           IsWhite := c in [' ', TAB, CR, LF];
  949.        end;
  950.  
  951.  
  952.        {--------------------------------------------------------------}
  953.        { Skip Over Leading White Space }
  954.  
  955.        procedure SkipWhite;
  956.        begin
  957.           while IsWhite(Look) do
  958.              GetChar;
  959.        end;
  960.  
  961.  
  962.        {--------------------------------------------------------------}
  963.        { Table Lookup }
  964.  
  965.        function Lookup(T: TabPtr; s: string; n: integer): integer;
  966.        var i: integer;
  967.            found: Boolean;
  968.        begin
  969.           found := false;
  970.           i := n;
  971.           while (i > 0) and not found do
  972.              if s = T^[i] then
  973.                 found := true
  974.              else
  975.                 dec(i);
  976.           Lookup := i;
  977.        end;
  978.  
  979.  
  980.        {--------------------------------------------------------------}
  981.        { Locate a Symbol in Table }
  982.        { Returns the index of the entry.  Zero if not present. }
  983.  
  984.        function Locate(N: Symbol): integer;
  985.        begin
  986.           Locate := Lookup(@ST, n, NEntry);
  987.        end;
  988.  
  989.  
  990.        {--------------------------------------------------------------}
  991.        { Look for Symbol in Table }A6A6
  992.                                     - 16 -A*A*
  993.  
  994. PA A
  995.  
  996.  
  997.  
  998.  
  999.  
  1000.        function InTable(n: Symbol): Boolean;
  1001.        begin
  1002.           InTable := Lookup(@ST, n, NEntry) <> 0;
  1003.        end;
  1004.  
  1005.  
  1006.        {--------------------------------------------------------------}
  1007.        { Check to See if an Identifier is in the Symbol Table         }
  1008.        { Report an error if it's not. }
  1009.  
  1010.  
  1011.        procedure CheckTable(N: Symbol);
  1012.        begin
  1013.           if not InTable(N) then Undefined(N);
  1014.        end;
  1015.  
  1016.  
  1017.        {--------------------------------------------------------------}
  1018.        { Check the Symbol Table for a Duplicate Identifier }
  1019.        { Report an error if identifier is already in table. }
  1020.  
  1021.  
  1022.        procedure CheckDup(N: Symbol);
  1023.        begin
  1024.           if InTable(N) then Duplicate(N);
  1025.        end;
  1026.  
  1027.  
  1028.        {--------------------------------------------------------------}
  1029.        { Add a New Entry to Symbol Table }
  1030.  
  1031.        procedure AddEntry(N: Symbol; T: char);
  1032.        begin
  1033.           CheckDup(N);
  1034.           if NEntry = MaxEntry then Abort('Symbol Table Full');
  1035.           Inc(NEntry);
  1036.           ST[NEntry] := N;
  1037.           SType[NEntry] := T;
  1038.        end;
  1039.  
  1040.  
  1041.        {--------------------------------------------------------------}
  1042.        { Get an Identifier }
  1043.  
  1044.        procedure GetName;
  1045.        begin
  1046.           SkipWhite;
  1047.           if Not IsAlpha(Look) then Expected('Identifier');
  1048.           Token := 'x';
  1049.           Value := '';
  1050.           repeat
  1051.              Value := Value + UpCase(Look);
  1052.              GetChar;
  1053.           until not IsAlNum(Look);A*A*
  1054.                                     - 17 -
  1055.  
  1056. PA A
  1057.  
  1058.  
  1059.  
  1060.  
  1061.  
  1062.        end;
  1063.  
  1064.  
  1065.        {--------------------------------------------------------------}
  1066.        { Get a Number }
  1067.  
  1068.        procedure GetNum;
  1069.        begin
  1070.           SkipWhite;
  1071.           if not IsDigit(Look) then Expected('Number');
  1072.           Token := '#';
  1073.           Value := '';
  1074.           repeat
  1075.              Value := Value + Look;
  1076.              GetChar;
  1077.           until not IsDigit(Look);
  1078.        end;
  1079.  
  1080.  
  1081.        {--------------------------------------------------------------}
  1082.        { Get an Operator }
  1083.  
  1084.        procedure GetOp;
  1085.        begin
  1086.           SkipWhite;
  1087.           Token := Look;
  1088.           Value := Look;
  1089.           GetChar;
  1090.        end;
  1091.  
  1092.  
  1093.        {--------------------------------------------------------------}
  1094.        { Get the Next Input Token }
  1095.  
  1096.        procedure Next;
  1097.        begin
  1098.           SkipWhite;
  1099.           if IsAlpha(Look) then GetName
  1100.           else if IsDigit(Look) then GetNum
  1101.           else GetOp;
  1102.        end;
  1103.  
  1104.  
  1105.        {--------------------------------------------------------------}
  1106.        { Scan the Current Identifier for Keywords }
  1107.  
  1108.        procedure Scan;
  1109.        begin
  1110.           if Token = 'x' then
  1111.              Token := KWcode[Lookup(Addr(KWlist), Value, NKW) + 1];
  1112.        end;
  1113.  
  1114.  
  1115.        {--------------------------------------------------------------}A*A*
  1116.                                     - 18 -
  1117.  
  1118. PA A
  1119.  
  1120.  
  1121.  
  1122.  
  1123.  
  1124.        { Match a Specific Input String }
  1125.  
  1126.        procedure MatchString(x: string);
  1127.        begin
  1128.           if Value <> x then Expected('''' + x + '''');
  1129.           Next;
  1130.        end;
  1131.  
  1132.  
  1133.        {--------------------------------------------------------------}
  1134.        { Output a String with Tab }
  1135.  
  1136.        procedure Emit(s: string);
  1137.        begin
  1138.           Write(TAB, s);
  1139.        end;
  1140.  
  1141.  
  1142.        {--------------------------------------------------------------}
  1143.        { Output a String with Tab and CRLF }
  1144.  
  1145.        procedure EmitLn(s: string);
  1146.        begin
  1147.           Emit(s);
  1148.           WriteLn;
  1149.        end;
  1150.  
  1151.  
  1152.        {--------------------------------------------------------------}
  1153.        { Generate a Unique Label }
  1154.  
  1155.        function NewLabel: string;
  1156.        var S: string;
  1157.        begin
  1158.           Str(LCount, S);
  1159.           NewLabel := 'L' + S;
  1160.           Inc(LCount);
  1161.        end;
  1162.  
  1163.  
  1164.        {--------------------------------------------------------------}
  1165.        { Post a Label To Output }
  1166.  
  1167.        procedure PostLabel(L: string);
  1168.        begin
  1169.           WriteLn(L, ':');
  1170.        end;
  1171.  
  1172.  
  1173.        {---------------------------------------------------------------}
  1174.        { Clear the Primary Register }
  1175.  
  1176.        procedure Clear;
  1177.        beginA*A*
  1178.                                     - 19 -
  1179.  
  1180. PA A
  1181.  
  1182.  
  1183.  
  1184.  
  1185.  
  1186.           EmitLn('CLR D0');
  1187.        end;
  1188.  
  1189.  
  1190.        {---------------------------------------------------------------}
  1191.        { Negate the Primary Register }
  1192.  
  1193.        procedure Negate;
  1194.        begin
  1195.           EmitLn('NEG D0');
  1196.        end;
  1197.  
  1198.  
  1199.        {---------------------------------------------------------------}
  1200.        { Complement the Primary Register }
  1201.  
  1202.        procedure NotIt;
  1203.        begin
  1204.           EmitLn('NOT D0');
  1205.        end;
  1206.  
  1207.  
  1208.        {---------------------------------------------------------------}
  1209.        { Load a Constant Value to Primary Register }
  1210.  
  1211.        procedure LoadConst(n: string);
  1212.        begin
  1213.           Emit('MOVE #');
  1214.           WriteLn(n, ',D0');
  1215.        end;
  1216.  
  1217.  
  1218.        {---------------------------------------------------------------}
  1219.        { Load a Variable to Primary Register }
  1220.  
  1221.        procedure LoadVar(Name: string);
  1222.        begin
  1223.           if not InTable(Name) then Undefined(Name);
  1224.           EmitLn('MOVE ' + Name + '(PC),D0');
  1225.        end;
  1226.  
  1227.  
  1228.        {---------------------------------------------------------------}
  1229.        { Push Primary onto Stack }
  1230.  
  1231.        procedure Push;
  1232.        begin
  1233.           EmitLn('MOVE D0,-(SP)');
  1234.        end;
  1235.  
  1236.  
  1237.        {---------------------------------------------------------------}
  1238.        { Add Top of Stack to Primary }A6A6
  1239.                                     - 20 -A*A*
  1240.  
  1241. PA A
  1242.  
  1243.  
  1244.  
  1245.  
  1246.  
  1247.        procedure PopAdd;
  1248.        begin
  1249.           EmitLn('ADD (SP)+,D0');
  1250.        end;
  1251.  
  1252.  
  1253.        {---------------------------------------------------------------}
  1254.        { Subtract Primary from Top of Stack }
  1255.  
  1256.        procedure PopSub;
  1257.        begin
  1258.           EmitLn('SUB (SP)+,D0');
  1259.           EmitLn('NEG D0');
  1260.        end;
  1261.  
  1262.  
  1263.        {---------------------------------------------------------------}
  1264.        { Multiply Top of Stack by Primary }
  1265.  
  1266.        procedure PopMul;
  1267.        begin
  1268.           EmitLn('MULS (SP)+,D0');
  1269.        end;
  1270.  
  1271.  
  1272.        {---------------------------------------------------------------}
  1273.        { Divide Top of Stack by Primary }
  1274.  
  1275.        procedure PopDiv;
  1276.        begin
  1277.           EmitLn('MOVE (SP)+,D7');
  1278.           EmitLn('EXT.L D7');
  1279.           EmitLn('DIVS D0,D7');
  1280.           EmitLn('MOVE D7,D0');
  1281.        end;
  1282.  
  1283.  
  1284.        {---------------------------------------------------------------}
  1285.        { AND Top of Stack with Primary }
  1286.  
  1287.        procedure PopAnd;
  1288.        begin
  1289.           EmitLn('AND (SP)+,D0');
  1290.        end;
  1291.  
  1292.  
  1293.        {---------------------------------------------------------------}
  1294.        { OR Top of Stack with Primary }
  1295.  
  1296.        procedure PopOr;
  1297.        begin
  1298.           EmitLn('OR (SP)+,D0');
  1299.        end;A6A6
  1300.                                     - 21 -A*A*
  1301.  
  1302. PA A
  1303.  
  1304.  
  1305.  
  1306.  
  1307.  
  1308.        {---------------------------------------------------------------}
  1309.        { XOR Top of Stack with Primary }
  1310.  
  1311.        procedure PopXor;
  1312.        begin
  1313.           EmitLn('EOR (SP)+,D0');
  1314.        end;
  1315.  
  1316.  
  1317.        {---------------------------------------------------------------}
  1318.        { Compare Top of Stack with Primary }
  1319.  
  1320.        procedure PopCompare;
  1321.        begin
  1322.           EmitLn('CMP (SP)+,D0');
  1323.        end;
  1324.  
  1325.  
  1326.        {---------------------------------------------------------------}
  1327.        { Set D0 If Compare was = }
  1328.  
  1329.        procedure SetEqual;
  1330.        begin
  1331.           EmitLn('SEQ D0');
  1332.           EmitLn('EXT D0');
  1333.        end;
  1334.  
  1335.  
  1336.        {---------------------------------------------------------------}
  1337.        { Set D0 If Compare was != }
  1338.  
  1339.        procedure SetNEqual;
  1340.        begin
  1341.           EmitLn('SNE D0');
  1342.           EmitLn('EXT D0');
  1343.        end;
  1344.  
  1345.  
  1346.        {---------------------------------------------------------------}
  1347.        { Set D0 If Compare was > }
  1348.  
  1349.        procedure SetGreater;
  1350.        begin
  1351.           EmitLn('SLT D0');
  1352.           EmitLn('EXT D0');
  1353.        end;
  1354.  
  1355.  
  1356.        {---------------------------------------------------------------}
  1357.        { Set D0 If Compare was < }
  1358.  
  1359.        procedure SetLess;
  1360.        begin
  1361.           EmitLn('SGT D0');A*A*
  1362.                                     - 22 -
  1363.  
  1364. PA A
  1365.  
  1366.  
  1367.  
  1368.  
  1369.  
  1370.           EmitLn('EXT D0');
  1371.        end;
  1372.  
  1373.  
  1374.        {---------------------------------------------------------------}
  1375.        { Set D0 If Compare was <= }
  1376.  
  1377.        procedure SetLessOrEqual;
  1378.        begin
  1379.           EmitLn('SGE D0');
  1380.           EmitLn('EXT D0');
  1381.        end;
  1382.  
  1383.  
  1384.        {---------------------------------------------------------------}
  1385.        { Set D0 If Compare was >= }
  1386.  
  1387.        procedure SetGreaterOrEqual;
  1388.        begin
  1389.           EmitLn('SLE D0');
  1390.           EmitLn('EXT D0');
  1391.        end;
  1392.  
  1393.  
  1394.        {---------------------------------------------------------------}
  1395.        { Store Primary to Variable }
  1396.  
  1397.        procedure Store(Name: string);
  1398.        begin
  1399.           EmitLn('LEA ' + Name + '(PC),A0');
  1400.           EmitLn('MOVE D0,(A0)')
  1401.        end;
  1402.  
  1403.  
  1404.        {---------------------------------------------------------------}
  1405.        { Branch Unconditional  }
  1406.  
  1407.        procedure Branch(L: string);
  1408.        begin
  1409.           EmitLn('BRA ' + L);
  1410.        end;
  1411.  
  1412.  
  1413.        {---------------------------------------------------------------}
  1414.        { Branch False }
  1415.  
  1416.        procedure BranchFalse(L: string);
  1417.        begin
  1418.           EmitLn('TST D0');
  1419.           EmitLn('BEQ ' + L);
  1420.        end;
  1421.  
  1422.  
  1423.        {---------------------------------------------------------------}A*A*
  1424.                                     - 23 -
  1425.  
  1426. PA A
  1427.  
  1428.  
  1429.  
  1430.  
  1431.  
  1432.        { Read Variable to Primary Register }
  1433.  
  1434.        procedure ReadIt(Name: string);
  1435.        begin
  1436.           EmitLn('BSR READ');
  1437.           Store(Name);
  1438.        end;
  1439.  
  1440.  
  1441.        { Write from Primary Register }
  1442.  
  1443.        procedure WriteIt;
  1444.        begin
  1445.           EmitLn('BSR WRITE');
  1446.        end;
  1447.  
  1448.  
  1449.        {--------------------------------------------------------------}
  1450.        { Write Header Info }
  1451.  
  1452.        procedure Header;
  1453.        begin
  1454.           WriteLn('WARMST', TAB, 'EQU $A01E');
  1455.        end;
  1456.  
  1457.  
  1458.        {--------------------------------------------------------------}
  1459.        { Write the Prolog }
  1460.  
  1461.        procedure Prolog;
  1462.        begin
  1463.           PostLabel('MAIN');
  1464.        end;
  1465.  
  1466.  
  1467.        {--------------------------------------------------------------}
  1468.        { Write the Epilog }
  1469.  
  1470.        procedure Epilog;
  1471.        begin
  1472.           EmitLn('DC WARMST');
  1473.           EmitLn('END MAIN');
  1474.        end;
  1475.  
  1476.  
  1477.        {---------------------------------------------------------------}
  1478.        { Allocate Storage for a Static Variable }
  1479.  
  1480.        procedure Allocate(Name, Val: string);
  1481.        begin
  1482.           WriteLn(Name, ':', TAB, 'DC ', Val);
  1483.        end;ABAB
  1484.                                     - 24 -A*A*
  1485.  
  1486. PA A
  1487.  
  1488.  
  1489.  
  1490.  
  1491.  
  1492.        {---------------------------------------------------------------}
  1493.        { Parse and Translate a Math Factor }
  1494.  
  1495.        procedure BoolExpression; Forward;
  1496.  
  1497.        procedure Factor;
  1498.        begin
  1499.           if Token = '(' then begin
  1500.              Next;
  1501.              BoolExpression;
  1502.              MatchString(')');
  1503.              end
  1504.           else begin
  1505.              if Token = 'x' then
  1506.                 LoadVar(Value)
  1507.              else if Token = '#' then
  1508.                 LoadConst(Value)
  1509.              else Expected('Math Factor');
  1510.              Next;
  1511.           end;
  1512.        end;
  1513.  
  1514.  
  1515.        {--------------------------------------------------------------}
  1516.        { Recognize and Translate a Multiply }
  1517.  
  1518.        procedure Multiply;
  1519.        begin
  1520.           Next;
  1521.           Factor;
  1522.           PopMul;
  1523.        end;
  1524.  
  1525.  
  1526.        {-------------------------------------------------------------}
  1527.        { Recognize and Translate a Divide }
  1528.  
  1529.        procedure Divide;
  1530.        begin
  1531.           Next;
  1532.           Factor;
  1533.           PopDiv;
  1534.        end;
  1535.  
  1536.  
  1537.        {---------------------------------------------------------------}
  1538.        { Parse and Translate a Math Term }
  1539.  
  1540.        procedure Term;
  1541.        begin
  1542.           Factor;
  1543.           while IsMulop(Token) do begin
  1544.              Push;
  1545.              case Token ofA*A*
  1546.                                     - 25 -
  1547.  
  1548. PA A
  1549.  
  1550.  
  1551.  
  1552.  
  1553.  
  1554.               '*': Multiply;
  1555.               '/': Divide;
  1556.              end;
  1557.           end;
  1558.        end;
  1559.  
  1560.  
  1561.        {--------------------------------------------------------------}
  1562.        { Recognize and Translate an Add }
  1563.  
  1564.        procedure Add;
  1565.        begin
  1566.           Next;
  1567.           Term;
  1568.           PopAdd;
  1569.        end;
  1570.  
  1571.  
  1572.        {-------------------------------------------------------------}
  1573.        { Recognize and Translate a Subtract }
  1574.  
  1575.        procedure Subtract;
  1576.        begin
  1577.           Next;
  1578.           Term;
  1579.           PopSub;
  1580.        end;
  1581.  
  1582.  
  1583.        {---------------------------------------------------------------}
  1584.        { Parse and Translate an Expression }
  1585.  
  1586.        procedure Expression;
  1587.        begin
  1588.           if IsAddop(Token) then
  1589.              Clear
  1590.           else
  1591.              Term;
  1592.           while IsAddop(Token) do begin
  1593.              Push;
  1594.              case Token of
  1595.               '+': Add;
  1596.               '-': Subtract;
  1597.              end;
  1598.           end;
  1599.        end;
  1600.  
  1601.  
  1602.        {---------------------------------------------------------------}
  1603.        { Get Another Expression and Compare }
  1604.  
  1605.        procedure CompareExpression;
  1606.        begin
  1607.           Expression;A*A*
  1608.                                     - 26 -
  1609.  
  1610. PA A
  1611.  
  1612.  
  1613.  
  1614.  
  1615.  
  1616.           PopCompare;
  1617.        end;
  1618.  
  1619.  
  1620.        {---------------------------------------------------------------}
  1621.        { Get The Next Expression and Compare }
  1622.  
  1623.        procedure NextExpression;
  1624.        begin
  1625.           Next;
  1626.           CompareExpression;
  1627.        end;
  1628.  
  1629.  
  1630.        {---------------------------------------------------------------}
  1631.        { Recognize and Translate a Relational "Equals" }
  1632.  
  1633.        procedure Equal;
  1634.        begin
  1635.           NextExpression;
  1636.           SetEqual;
  1637.        end;
  1638.  
  1639.  
  1640.        {---------------------------------------------------------------}
  1641.        { Recognize and Translate a Relational "Less Than or Equal" }
  1642.  
  1643.        procedure LessOrEqual;
  1644.        begin
  1645.           NextExpression;
  1646.           SetLessOrEqual;
  1647.        end;
  1648.  
  1649.  
  1650.        {---------------------------------------------------------------}
  1651.        { Recognize and Translate a Relational "Not Equals" }
  1652.  
  1653.        procedure NotEqual;
  1654.        begin
  1655.           NextExpression;
  1656.           SetNEqual;
  1657.        end;
  1658.  
  1659.  
  1660.        {---------------------------------------------------------------}
  1661.        { Recognize and Translate a Relational "Less Than" }
  1662.  
  1663.        procedure Less;
  1664.        begin
  1665.           Next;
  1666.           case Token of
  1667.             '=': LessOrEqual;
  1668.             '>': NotEqual;
  1669.           else beginA*A*
  1670.                                     - 27 -
  1671.  
  1672. PA A
  1673.  
  1674.  
  1675.  
  1676.  
  1677.  
  1678.                   CompareExpression;
  1679.                   SetLess;
  1680.                end;
  1681.           end;
  1682.        end;
  1683.  
  1684.  
  1685.        {---------------------------------------------------------------}
  1686.        { Recognize and Translate a Relational "Greater Than" }
  1687.  
  1688.        procedure Greater;
  1689.        begin
  1690.           Next;
  1691.           if Token = '=' then begin
  1692.              NextExpression;
  1693.              SetGreaterOrEqual;
  1694.              end
  1695.           else begin
  1696.              CompareExpression;
  1697.              SetGreater;
  1698.           end;
  1699.        end;
  1700.  
  1701.  
  1702.        {---------------------------------------------------------------}
  1703.        { Parse and Translate a Relation }
  1704.  
  1705.  
  1706.        procedure Relation;
  1707.        begin
  1708.           Expression;
  1709.           if IsRelop(Token) then begin
  1710.              Push;
  1711.              case Token of
  1712.               '=': Equal;
  1713.               '<': Less;
  1714.               '>': Greater;
  1715.              end;
  1716.           end;
  1717.        end;
  1718.  
  1719.  
  1720.        {---------------------------------------------------------------}
  1721.        { Parse and Translate a Boolean Factor with Leading NOT }
  1722.  
  1723.        procedure NotFactor;
  1724.        begin
  1725.           if Token = '!' then begin
  1726.              Next;
  1727.              Relation;
  1728.              NotIt;
  1729.              end
  1730.           else
  1731.              Relation;A*A*
  1732.                                     - 28 -
  1733.  
  1734. PA A
  1735.  
  1736.  
  1737.  
  1738.  
  1739.  
  1740.        end;
  1741.  
  1742.  
  1743.        {---------------------------------------------------------------}
  1744.        { Parse and Translate a Boolean Term }
  1745.  
  1746.        procedure BoolTerm;
  1747.        begin
  1748.           NotFactor;
  1749.           while Token = '&' do begin
  1750.              Push;
  1751.              Next;
  1752.              NotFactor;
  1753.              PopAnd;
  1754.           end;
  1755.        end;
  1756.  
  1757.  
  1758.        {--------------------------------------------------------------}
  1759.        { Recognize and Translate a Boolean OR }
  1760.  
  1761.        procedure BoolOr;
  1762.        begin
  1763.           Next;
  1764.           BoolTerm;
  1765.           PopOr;
  1766.        end;
  1767.  
  1768.  
  1769.        {--------------------------------------------------------------}
  1770.        { Recognize and Translate an Exclusive Or }
  1771.  
  1772.        procedure BoolXor;
  1773.        begin
  1774.           Next;
  1775.           BoolTerm;
  1776.           PopXor;
  1777.        end;
  1778.  
  1779.  
  1780.        {---------------------------------------------------------------}
  1781.        { Parse and Translate a Boolean Expression }
  1782.  
  1783.        procedure BoolExpression;
  1784.        begin
  1785.           BoolTerm;
  1786.           while IsOrOp(Token) do begin
  1787.              Push;
  1788.              case Token of
  1789.               '|': BoolOr;
  1790.               '~': BoolXor;
  1791.              end;
  1792.           end;
  1793.        end;A*A*
  1794.                                     - 29 -
  1795.  
  1796. PA A
  1797.  
  1798.  
  1799.  
  1800.  
  1801.  
  1802.        {--------------------------------------------------------------}
  1803.        { Parse and Translate an Assignment Statement }
  1804.  
  1805.        procedure Assignment;
  1806.        var Name: string;
  1807.        begin
  1808.           CheckTable(Value);
  1809.           Name := Value;
  1810.           Next;
  1811.           MatchString('=');
  1812.           BoolExpression;
  1813.           Store(Name);
  1814.        end;
  1815.  
  1816.  
  1817.        {---------------------------------------------------------------}
  1818.        { Recognize and Translate an IF Construct }
  1819.  
  1820.        procedure Block; Forward;
  1821.  
  1822.        procedure DoIf;
  1823.        var L1, L2: string;
  1824.        begin
  1825.           Next;
  1826.           BoolExpression;
  1827.           L1 := NewLabel;
  1828.           L2 := L1;
  1829.           BranchFalse(L1);
  1830.           Block;
  1831.           if Token = 'l' then begin
  1832.              Next;
  1833.              L2 := NewLabel;
  1834.              Branch(L2);
  1835.              PostLabel(L1);
  1836.              Block;
  1837.           end;
  1838.           PostLabel(L2);
  1839.           MatchString('ENDIF');
  1840.        end;
  1841.  
  1842.  
  1843.        {--------------------------------------------------------------}
  1844.        { Parse and Translate a WHILE Statement }
  1845.  
  1846.        procedure DoWhile;
  1847.        var L1, L2: string;
  1848.        begin
  1849.           Next;
  1850.           L1 := NewLabel;
  1851.           L2 := NewLabel;
  1852.           PostLabel(L1);
  1853.           BoolExpression;
  1854.           BranchFalse(L2);
  1855.           Block;A*A*
  1856.                                     - 30 -
  1857.  
  1858. PA A
  1859.  
  1860.  
  1861.  
  1862.  
  1863.  
  1864.           MatchString('ENDWHILE');
  1865.           Branch(L1);
  1866.           PostLabel(L2);
  1867.        end;
  1868.  
  1869.  
  1870.        {--------------------------------------------------------------}
  1871.        { Read a Single Variable }
  1872.  
  1873.        procedure ReadVar;
  1874.        begin
  1875.           CheckIdent;
  1876.           CheckTable(Value);
  1877.           ReadIt(Value);
  1878.           Next;
  1879.        end;
  1880.  
  1881.  
  1882.        {--------------------------------------------------------------}
  1883.        { Process a Read Statement }
  1884.  
  1885.        procedure DoRead;
  1886.        begin
  1887.           Next;
  1888.           MatchString('(');
  1889.           ReadVar;
  1890.           while Token = ',' do begin
  1891.              Next;
  1892.              ReadVar;
  1893.           end;
  1894.           MatchString(')');
  1895.        end;
  1896.  
  1897.  
  1898.        {--------------------------------------------------------------}
  1899.        { Process a Write Statement }
  1900.  
  1901.        procedure DoWrite;
  1902.        begin
  1903.           Next;
  1904.           MatchString('(');
  1905.           Expression;
  1906.           WriteIt;
  1907.           while Token = ',' do begin
  1908.              Next;
  1909.              Expression;
  1910.              WriteIt;
  1911.           end;
  1912.           MatchString(')');
  1913.        end;
  1914.  
  1915.  
  1916.        {--------------------------------------------------------------}
  1917.        { Parse and Translate a Block of Statements }A*A*
  1918.                                     - 31 -
  1919.  
  1920. PA A
  1921.  
  1922.  
  1923.  
  1924.  
  1925.  
  1926.        procedure Block;
  1927.        begin
  1928.           Scan;
  1929.           while not(Token in ['e', 'l']) do begin
  1930.              case Token of
  1931.               'i': DoIf;
  1932.               'w': DoWhile;
  1933.               'R': DoRead;
  1934.               'W': DoWrite;
  1935.              else Assignment;
  1936.              end;
  1937.              Scan;
  1938.           end;
  1939.        end;
  1940.  
  1941.  
  1942.        {--------------------------------------------------------------}
  1943.        { Allocate Storage for a Variable }
  1944.  
  1945.        procedure Alloc;
  1946.        begin
  1947.           Next;
  1948.           if Token <> 'x' then Expected('Variable Name');
  1949.           CheckDup(Value);
  1950.           AddEntry(Value, 'v');
  1951.           Allocate(Value, '0');
  1952.           Next;
  1953.        end;
  1954.  
  1955.  
  1956.        {--------------------------------------------------------------}
  1957.        { Parse and Translate Global Declarations }
  1958.  
  1959.        procedure TopDecls;
  1960.        begin
  1961.           Scan;
  1962.           while Token = 'v' do
  1963.              Alloc;
  1964.              while Token = ',' do
  1965.                 Alloc;
  1966.        end;
  1967.  
  1968.  
  1969.        {--------------------------------------------------------------}
  1970.        { Initialize }
  1971.  
  1972.        procedure Init;
  1973.        begin
  1974.           GetChar;
  1975.           Next;
  1976.        end;
  1977.  
  1978.  
  1979.        {--------------------------------------------------------------}A*A*
  1980.                                     - 32 -
  1981.  
  1982. PA A
  1983.  
  1984.  
  1985.  
  1986.  
  1987.  
  1988.        { Main Program }
  1989.  
  1990.        begin
  1991.           Init;
  1992.           MatchString('PROGRAM');
  1993.           Header;
  1994.           TopDecls;
  1995.           MatchString('BEGIN');
  1996.           Prolog;
  1997.           Block;
  1998.           MatchString('END');
  1999.           Epilog;
  2000.        end.
  2001.        {--------------------------------------------------------------}AUAU
  2002.  
  2003.  
  2004.  
  2005.  
  2006.  
  2007. A A
  2008.                                     - 33 -A*A*
  2009. @