home *** CD-ROM | disk | FTP | other *** search
/ Power Programming / powerprogramming1994.iso / progtool / turbopas / tutorpas.arc / TUTOR3.DOC < prev    next >
Text File  |  1988-08-11  |  34KB  |  1,079 lines

  1. OPA A
  2.  
  3.  
  4.  
  5.  
  6.  
  7.  
  8.  
  9.  
  10.  
  11.  
  12.  
  13.  
  14.  
  15.  
  16.  
  17.  
  18.  
  19.  
  20.  
  21.  
  22.  
  23.  
  24.  
  25.  
  26.  
  27.  
  28.                             LET'S BUILD A COMPILER!
  29.  
  30.                                        By
  31.  
  32.                             Jack W. Crenshaw, Ph.D.
  33.  
  34.                                    4 Aug 1988
  35.  
  36.  
  37.                            Part III: MORE EXPRESSIONS
  38.  
  39.  
  40.  
  41.  
  42.  
  43.  
  44.  
  45.  
  46.  
  47.  
  48.  
  49.  
  50.  
  51.  
  52.  
  53.  
  54.  
  55.  
  56.  
  57.  
  58.  
  59.  
  60.  
  61.  
  62.  
  63.  
  64.  
  65.  
  66.  
  67. PA A
  68.  
  69.  
  70.  
  71.  
  72.  
  73.        *****************************************************************
  74.        *                                                               *
  75.        *                        COPYRIGHT NOTICE                       *
  76.        *                                                               *
  77.        *   Copyright (C) 1988 Jack W. Crenshaw. All rights reserved.   *
  78.        *                                                               *
  79.        *****************************************************************
  80.  
  81.  
  82.        INTRODUCTION
  83.  
  84.        In the last installment, we examined the techniques used to parse
  85.        and  translate a general math expression.  We  ended  up  with  a
  86.        simple parser that  could handle arbitrarily complex expressions,
  87.        with two restrictions:
  88.  
  89.          o No variables were allowed, only numeric factors
  90.  
  91.          o The numeric factors were limited to single digits
  92.  
  93.        In this installment, we'll get  rid of those restrictions.  We'll
  94.        also extend what  we've  done  to  include  assignment statements
  95.        function  calls  and.    Remember,   though,   that   the  second
  96.        restriction was  mainly self-imposed  ... a choice of convenience
  97.        on our part, to make life easier and to let us concentrate on the
  98.        fundamental concepts.    As  you'll  see  in  a bit, it's an easy
  99.        restriction to get rid of, so don't get  too  hung  up  about it.
  100.        We'll use the trick when it serves us to do so, confident that we
  101.        can discard it when we're ready to.
  102.  
  103.  
  104.        VARIABLES
  105.  
  106.        Most expressions  that we see in practice involve variables, such
  107.        as
  108.  
  109.                       b * b + 4 * a * c
  110.  
  111.        No  parser is much good without being able  to  deal  with  them.
  112.        Fortunately, it's also quite easy to do.
  113.  
  114.        Remember that in our parser as it currently stands, there are two
  115.        kinds of  factors  allowed:  integer  constants  and  expressions
  116.        within parentheses.  In BNF notation,
  117.  
  118.             <factor> ::= <number> | (<expression>)
  119.  
  120.        The '|' stands for "or", meaning of course that either form  is a
  121.        legal form for a factor.   Remember,  too, that we had no trouble
  122.        knowing which was which  ...  the  lookahead  character is a left
  123.        paren '(' in one case, and a digit in the other.ANAN
  124.                                      - 2 -A*A*
  125. PA A
  126.  
  127.  
  128.  
  129.  
  130.  
  131.        It probably won't come as too much of a surprise that  a variable
  132.        is just another kind of factor.    So  we extend the BNF above to
  133.        read:
  134.  
  135.  
  136.             <factor> ::= <number> | (<expression>) | <variable>
  137.  
  138.  
  139.        Again, there is no  ambiguity:  if  the  lookahead character is a
  140.        letter,  we  have  a variable; if a digit, we have a number. Back
  141.        when we translated the number, we just issued code  to  load  the
  142.        number,  as immediate data, into D0.  Now we do the same, only we
  143.        load a variable.
  144.  
  145.        A minor complication in the  code generation arises from the fact
  146.        that most  68000 operating systems, including the SK*DOS that I'm
  147.        using, require the code to be  written  in "position-independent"
  148.        form, which  basically means that everything is PC-relative.  The
  149.        format for a load in this language is
  150.  
  151.                       MOVE X(PC),D0
  152.  
  153.        where X is, of course, the variable name.  Armed with that, let's
  154.        modify the current version of Factor to read:
  155.  
  156.  
  157.        {---------------------------------------------------------------}
  158.        { Parse and Translate a Math Factor }
  159.  
  160.        procedure Expression; Forward;
  161.  
  162.        procedure Factor;
  163.        begin
  164.           if Look = '(' then begin
  165.              Match('(');
  166.              Expression;
  167.              Match(')');
  168.              end
  169.           else if IsAlpha(Look) then
  170.              EmitLn('MOVE ' + GetName + '(PC),D0')
  171.           else
  172.              EmitLn('MOVE #' + GetNum + ',D0');
  173.        end;
  174.        {--------------------------------------------------------------}
  175.  
  176.  
  177.        I've  remarked before how easy it is to  add  extensions  to  the
  178.        parser, because of  the  way  it's  structured.  You can see that
  179.        this  still  holds true here.  This time it cost us  all  of  two
  180.        extra lines of code.  Notice, too, how the if-else-else structure
  181.        exactly parallels the BNF syntax equation.
  182.  
  183.        OK, compile and test this new version of the parser.  That didn't
  184.        hurt too badly, did it?A*A*
  185.                                      - 3 -
  186. PA A
  187.  
  188.  
  189.  
  190.  
  191.  
  192.        FUNCTIONS
  193.  
  194.        There is only one  other  common kind of factor supported by most
  195.        languages: the function call.  It's really too early  for  us  to
  196.        deal with functions well,  because  we  haven't yet addressed the
  197.        issue of parameter passing.  What's more, a "real" language would
  198.        include a mechanism to  support  more than one type, one of which
  199.        should be a function type.  We haven't gotten there  yet, either.
  200.        But I'd still like to deal with functions  now  for  a  couple of
  201.        reasons.    First,  it  lets  us  finally  wrap  up the parser in
  202.        something very close to its final form, and second, it  brings up
  203.        a new issue which is very much worth talking about.
  204.  
  205.        Up  till  now,  we've  been  able  to  write  what  is  called  a
  206.        "predictive parser."  That  means  that at any point, we can know
  207.        by looking at the current  lookahead character exactly what to do
  208.        next.  That isn't the case when we add functions.  Every language
  209.        has some naming rules  for  what  constitutes a legal identifier.
  210.        For the present, ours is simply that it  is  one  of  the letters
  211.        'a'..'z'.  The  problem  is  that  a variable name and a function
  212.        name obey  the  same  rules.   So how can we tell which is which?
  213.        One way is to require that they each be declared before  they are
  214.        used.    Pascal  takes that approach.  The other is that we might
  215.        require a function to be followed by a (possibly empty) parameter
  216.        list.  That's the rule used in C.
  217.  
  218.        Since  we  don't  yet have a mechanism for declaring types, let's
  219.        use the C  rule for now.  Since we also don't have a mechanism to
  220.        deal  with parameters, we can only handle  empty  lists,  so  our
  221.        function calls will have the form
  222.  
  223.                            x()  .
  224.  
  225.        Since  we're  not  dealing  with  parameter lists yet,  there  is
  226.        nothing  to do but to call the function, so we need only to issue
  227.        a BSR (call) instead of a MOVE.
  228.  
  229.        Now that there are two  possibilities for the "If IsAlpha" branch
  230.        of the test in Factor, let's treat them in a  separate procedure.
  231.        Modify Factor to read:
  232.  
  233.  
  234.        {---------------------------------------------------------------}
  235.        { Parse and Translate a Math Factor }
  236.  
  237.        procedure Expression; Forward;
  238.  
  239.        procedure Factor;
  240.        begin
  241.           if Look = '(' then begin
  242.              Match('(');
  243.              Expression;
  244.              Match(')');
  245.              endA*A*
  246.                                      - 4 -
  247. PA A
  248.  
  249.  
  250.  
  251.  
  252.  
  253.           else if IsAlpha(Look) then
  254.              Ident
  255.           else
  256.              EmitLn('MOVE #' + GetNum + ',D0');
  257.        end;
  258.        {--------------------------------------------------------------}
  259.  
  260.  
  261.        and insert before it the new procedure
  262.  
  263.  
  264.        {---------------------------------------------------------------}
  265.        { Parse and Translate an Identifier }
  266.  
  267.        procedure Ident;
  268.        var Name: char;
  269.        begin
  270.           Name := GetName;
  271.           if Look = '(' then begin
  272.              Match('(');
  273.              Match(')');
  274.              EmitLn('BSR ' + Name);
  275.              end
  276.           else
  277.              EmitLn('MOVE ' + Name + '(PC),D0')
  278.        end;
  279.        {---------------------------------------------------------------}
  280.  
  281.  
  282.        OK, compile and  test  this  version.  Does  it  parse  all legal
  283.        expressions?  Does it correctly flag badly formed ones?
  284.  
  285.        The important thing to notice is that even though  we  no  longer
  286.        have  a predictive parser, there is  little  or  no  complication
  287.        added with the recursive descent approach that we're  using.   At
  288.        the point where  Factor  finds an identifier (letter), it doesn't
  289.        know whether it's a variable name or a function name, nor does it
  290.        really care.  It simply passes it on to Ident and leaves it up to
  291.        that procedure to figure it out.  Ident, in  turn,  simply  tucks
  292.        away the identifier and then reads one more  character  to decide
  293.        which kind of identifier it's dealing with.
  294.  
  295.        Keep this approach in mind.  It's a very powerful concept, and it
  296.        should be used  whenever  you  encounter  an  ambiguous situation
  297.        requiring further lookahead.   Even  if  you  had to look several
  298.        tokens ahead, the principle would still work.
  299.  
  300.  
  301.        MORE ON ERROR HANDLING
  302.  
  303.        As long as we're talking  philosophy,  there's  another important
  304.        issue to point out:  error  handling.    Notice that although the
  305.        parser correctly rejects (almost)  every malformed  expression we
  306.        can  throw at it, with a meaningful  error  message,  we  haven'tA*A*
  307.                                      - 5 -
  308. PA A
  309.  
  310.  
  311.  
  312.  
  313.  
  314.        really had to  do much work to make that happen.  In fact, in the
  315.        whole parser per se (from  Ident  through  Expression)  there are
  316.        only two calls to the error routine, Expected.  Even those aren't
  317.        necessary ... if you'll look again in Term and Expression, you'll
  318.        see that those statements can't be reached.  I put them  in early
  319.        on as a  bit  of  insurance,  but  they're no longer needed.  Why
  320.        don't you delete them now?
  321.  
  322.        So how did we get this nice error handling  virtually  for  free?
  323.        It's simply  that  I've  carefully  avoided  reading  a character
  324.        directly  using  GetChar.  Instead,  I've  relied  on  the  error
  325.        handling in GetName,  GetNum,  and  Match  to  do  all  the error
  326.        checking for me.    Astute  readers  will notice that some of the
  327.        calls to Match (for example, the ones in Add  and  Subtract)  are
  328.        also unnecessary ... we already know what the character is by the
  329.        time  we get there ... but it maintains  a  certain  symmetry  to
  330.        leave them in, and  the  general rule to always use Match instead
  331.        of GetChar is a good one.
  332.  
  333.        I mentioned an "almost" above.   There  is a case where our error
  334.        handling  leaves a bit to be desired.  So far we haven't told our
  335.        parser what and  end-of-line  looks  like,  or  what  to  do with
  336.        embedded  white  space.  So  a  space  character  (or  any  other
  337.        character not part of the recognized character set) simply causes
  338.        the parser to terminate, ignoring the unrecognized characters.
  339.  
  340.        It  could  be  argued  that  this is reasonable behavior at  this
  341.        point.  In a "real"  compiler, there is usually another statement
  342.        following the one we're working on, so any characters not treated
  343.        as part of our expression will either be used for or  rejected as
  344.        part of the next one.
  345.  
  346.        But  it's  also a very easy thing to fix up, even  if  it's  only
  347.        temporary.   All  we  have  to  do  is assert that the expression
  348.        should end with an end-of-line , i.e., a carriage return.
  349.  
  350.        To see what I'm talking about, try the input line
  351.  
  352.                       1+2 <space> 3+4
  353.  
  354.        See  how the space was treated as a terminator?  Now, to make the
  355.        compiler properly flag this, add the line
  356.  
  357.                       if Look <> CR then Expected('Newline');
  358.  
  359.        in the main  program,  just  after  the call to Expression.  That
  360.        catches anything left over in the input stream.  Don't  forget to
  361.        define CR in the const statement:
  362.  
  363.                       CR = ^M;
  364.  
  365.        As usual, recompile the program and verify that it does what it's
  366.        supposed to.A6A6
  367.                                      - 6 -A*A*
  368. PA A
  369.  
  370.  
  371.  
  372.  
  373.  
  374.        ASSIGNMENT STATEMENTS
  375.  
  376.        OK,  at  this  point we have a parser that works very nicely. I'd
  377.        like to  point  out  that  we  got  it  using  only  88  lines of
  378.        executable code, not  counting  what  was  in  the  cradle.   The
  379.        compiled  object  file  is  a  whopping  4752  bytes.   Not  bad,
  380.        considering we weren't trying very  hard  to  save  either source
  381.        code or object size.  We just stuck to the KISS principle.
  382.  
  383.        Of course, parsing an expression  is not much good without having
  384.        something to do with it afterwards.  Expressions USUALLY (but not
  385.        always) appear in assignment statements, in the form
  386.  
  387.                  <Ident> = <Expression>
  388.  
  389.        We're only a breath  away  from being able to parse an assignment
  390.        statement, so let's take that  last  step.  Just  after procedure
  391.        Expression, add the following new procedure:
  392.  
  393.  
  394.        {--------------------------------------------------------------}
  395.        { Parse and Translate an Assignment Statement }
  396.  
  397.        procedure Assignment;
  398.        var Name: char;
  399.        begin
  400.           Name := GetName;
  401.           Match('=');
  402.           Expression;
  403.           EmitLn('LEA ' + Name + '(PC),A0');
  404.           EmitLn('MOVE D0,(A0)')
  405.        end;
  406.        {--------------------------------------------------------------}
  407.  
  408.  
  409.        Note again that the  code  exactly parallels the BNF.  And notice
  410.        further that  the error checking was painless, handled by GetName
  411.        and Match.
  412.  
  413.        The reason for the two  lines  of  assembler  has  to  do  with a
  414.        peculiarity in the  68000,  which requires this kind of construct
  415.        for PC-relative code.
  416.  
  417.        Now change the call to Expression, in the main program, to one to
  418.        Assignment.  That's all there is to it.
  419.  
  420.        Son of a gun!  We are actually  compiling  assignment statements.
  421.        If those were the only kind of statements in a language, all we'd
  422.        have to  do  is  put  this in a loop and we'd have a full-fledged
  423.        compiler!
  424.  
  425.        Well, of course they're not the only kind.  There are also little
  426.        items  like  control  statements  (IFs  and  loops),  procedures,
  427.        declarations, etc.  But cheer  up.    The  arithmetic expressionsA*A*
  428.                                      - 7 -
  429. PA A
  430.  
  431.  
  432.  
  433.  
  434.  
  435.        that we've been dealing with are among the most challenging  in a
  436.        language.      Compared  to  what  we've  already  done,  control
  437.        statements  will be easy.  I'll be covering  them  in  the  fifth
  438.        installment.  And the other statements will all fall in  line, as
  439.        long as we remember to KISS.
  440.  
  441.  
  442.        MULTI-CHARACTER TOKENS
  443.  
  444.        Throughout  this   series,   I've   been   carefully  restricting
  445.        everything  we  do  to  single-character  tokens,  all  the while
  446.        assuring  you  that  it wouldn't be difficult to extend to multi-
  447.        character ones.    I  don't  know if you believed me or not ... I
  448.        wouldn't  really blame you if you were a  bit  skeptical.    I'll
  449.        continue  to use  that approach in  the  sessions  which  follow,
  450.        because it helps keep complexity away.    But I'd like to back up
  451.        those  assurances, and wrap up this portion  of  the  parser,  by
  452.        showing you  just  how  easy  that  extension  really is.  In the
  453.        process, we'll also provide for embedded white space.  Before you
  454.        make  the  next  few changes, though, save the current version of
  455.        the parser away under another name.  I have some more uses for it
  456.        in  the  next  installment, and we'll be working with the single-
  457.        character version.
  458.  
  459.        Most compilers separate out the handling of the input stream into
  460.        a separate module called  the  lexical scanner.  The idea is that
  461.        the scanner deals with all the character-by-character  input, and
  462.        returns the separate units  (tokens)  of  the  stream.  There may
  463.        come a time when we'll want  to  do something like that, too, but
  464.        for  now  there  is  no  need. We can handle the  multi-character
  465.        tokens that we need by very slight and  very  local modifications
  466.        to GetName and GetNum.
  467.  
  468.        The usual definition of an identifier is that the first character
  469.        must be a letter, but the rest can be  alphanumeric  (letters  or
  470.        numbers).  To  deal  with  this,  we  need  one  other recognizer
  471.        function
  472.  
  473.  
  474.        {--------------------------------------------------------------}
  475.        { Recognize an Alphanumeric }
  476.  
  477.        function IsAlNum(c: char): boolean;
  478.        begin
  479.           IsAlNum := IsAlpha(c) or IsDigit(c);
  480.        end;
  481.        {--------------------------------------------------------------}
  482.  
  483.  
  484.        Add this function to your parser.  I put mine just after IsDigit.
  485.        While you're  at  it,  might  as  well  include it as a permanent
  486.        member of Cradle, too.ABAB
  487.                                      - 8 -A*A*
  488. PA A
  489.  
  490.  
  491.  
  492.  
  493.  
  494.        Now, we need  to  modify  function  GetName  to  return  a string
  495.        instead of a character:
  496.  
  497.  
  498.        {--------------------------------------------------------------}
  499.        { Get an Identifier }
  500.  
  501.        function GetName: string;
  502.        var Token: string;
  503.        begin
  504.           Token := '';
  505.           if not IsAlpha(Look) then Expected('Name');
  506.           while IsAlNum(Look) do begin
  507.              Token := Token + UpCase(Look);
  508.              GetChar;
  509.           end;
  510.           GetName := Token;
  511.        end;
  512.        {--------------------------------------------------------------}
  513.  
  514.  
  515.        Similarly, modify GetNum to read:
  516.  
  517.  
  518.        {--------------------------------------------------------------}
  519.        { Get a Number }
  520.  
  521.        function GetNum: string;
  522.        var Value: string;
  523.        begin
  524.           Value := '';
  525.           if not IsDigit(Look) then Expected('Integer');
  526.           while IsDigit(Look) do begin
  527.              Value := Value + Look;
  528.              GetChar;
  529.           end;
  530.           GetNum := Value;
  531.        end;
  532.        {--------------------------------------------------------------}
  533.  
  534.  
  535.        Amazingly enough, that  is  virtually all the changes required to
  536.        the  parser!  The local variable Name  in  procedures  Ident  and
  537.        Assignment was originally declared as  "char",  and  must  now be
  538.        declared string[8].  (Clearly,  we  could  make the string length
  539.        longer if we chose, but most assemblers limit the length anyhow.)
  540.        Make  this  change,  and  then  recompile and test. _NOW_ do  you
  541.        believe that it's a simple change?
  542.  
  543.  
  544.        WHITE SPACE
  545.  
  546.        Before we leave this parser for awhile, let's  address  the issue
  547.        of  white  space.   As it stands now, the parser  will  barf  (orA*A*
  548.                                      - 9 -
  549. PA A
  550.  
  551.  
  552.  
  553.  
  554.  
  555.        simply terminate) on a single space  character  embedded anywhere
  556.        in  the input stream.  That's pretty  unfriendly  behavior.    So
  557.        let's "productionize" the thing  a  bit  by eliminating this last
  558.        restriction.
  559.  
  560.        The  key  to easy handling of white space is to come  up  with  a
  561.        simple rule for how the parser should treat the input stream, and
  562.        to  enforce that rule everywhere.  Up  till  now,  because  white
  563.        space wasn't permitted, we've been able to assume that after each
  564.        parsing action, the lookahead character  Look  contains  the next
  565.        meaningful  character,  so  we could test it  immediately.    Our
  566.        design was based upon this principle.
  567.  
  568.        It still sounds like a good rule to me, so  that's  the one we'll
  569.        use.    This  means  that  every routine that advances the  input
  570.        stream must skip over white space, and leave  the  next non-white
  571.        character in Look.   Fortunately,  because  we've been careful to
  572.        use GetName, GetNum, and Match  for most of our input processing,
  573.        it is  only  those  three  routines  (plus  Init) that we need to
  574.        modify.
  575.  
  576.        Not  surprisingly,  we  start  with  yet  another  new recognizer
  577.        routine:
  578.  
  579.  
  580.        {--------------------------------------------------------------}
  581.        { Recognize White Space }
  582.  
  583.        function IsWhite(c: char): boolean;
  584.        begin
  585.           IsWhite := c in [' ', TAB];
  586.        end;
  587.        {--------------------------------------------------------------}
  588.  
  589.  
  590.        We  also need a routine that  will  eat  white-space  characters,
  591.        until it finds a non-white one:
  592.  
  593.  
  594.        {--------------------------------------------------------------}
  595.        { Skip Over Leading White Space }
  596.  
  597.        procedure SkipWhite;
  598.        begin
  599.           while IsWhite(Look) do
  600.              GetChar;
  601.        end;
  602.        {--------------------------------------------------------------}
  603.  
  604.  
  605.        Now,  add calls to SkipWhite to Match,  GetName,  and  GetNum  as
  606.        shown below:ABAB
  607.                                     - 10 -A*A*
  608. PA A
  609.  
  610.  
  611.  
  612.  
  613.  
  614.        {--------------------------------------------------------------}
  615.        { Match a Specific Input Character }
  616.  
  617.        procedure Match(x: char);
  618.        begin
  619.           if Look <> x then Expected('''' + x + '''')
  620.           else begin
  621.              GetChar;
  622.              SkipWhite;
  623.           end;
  624.        end;
  625.  
  626.  
  627.        {--------------------------------------------------------------}
  628.        { Get an Identifier }
  629.  
  630.        function GetName: string;
  631.        var Token: string;
  632.        begin
  633.           Token := '';
  634.           if not IsAlpha(Look) then Expected('Name');
  635.           while IsAlNum(Look) do begin
  636.              Token := Token + UpCase(Look);
  637.              GetChar;
  638.           end;
  639.           GetName := Token;
  640.           SkipWhite;
  641.        end;
  642.  
  643.  
  644.        {--------------------------------------------------------------}
  645.        { Get a Number }
  646.  
  647.        function GetNum: string;
  648.        var Value: string;
  649.        begin
  650.           Value := '';
  651.           if not IsDigit(Look) then Expected('Integer');
  652.           while IsDigit(Look) do begin
  653.              Value := Value + Look;
  654.              GetChar;
  655.           end;
  656.           GetNum := Value;
  657.           SkipWhite;
  658.        end;
  659.        {--------------------------------------------------------------}
  660.  
  661.        (Note  that  I  rearranged  Match  a  bit,  without changing  the
  662.        functionality.)
  663.  
  664.        Finally, we need to skip over leading blanks where we  "prime the
  665.        pump" in Init:ABAB
  666.                                     - 11 -A*A*
  667. PA A
  668.  
  669.  
  670.  
  671.  
  672.  
  673.        {--------------------------------------------------------------}
  674.        { Initialize }
  675.  
  676.        procedure Init;
  677.        begin
  678.           GetChar;
  679.           SkipWhite;
  680.        end;
  681.        {--------------------------------------------------------------}
  682.  
  683.  
  684.        Make these changes and recompile the program.  You will find that
  685.        you will have to move Match below SkipWhite, to  avoid  an  error
  686.        message from the Pascal compiler.  Test the program as  always to
  687.        make sure it works properly.
  688.  
  689.        Since we've made quite  a  few  changes  during this session, I'm
  690.        reproducing the entire parser below:
  691.  
  692.  
  693.        {--------------------------------------------------------------}
  694.        program parse;
  695.  
  696.        {--------------------------------------------------------------}
  697.        { Constant Declarations }
  698.  
  699.        const TAB = ^I;
  700.               CR = ^M;
  701.  
  702.        {--------------------------------------------------------------}
  703.        { Variable Declarations }
  704.  
  705.        var Look: char;              { Lookahead Character }
  706.  
  707.        {--------------------------------------------------------------}
  708.        { Read New Character From Input Stream }
  709.  
  710.        procedure GetChar;
  711.        begin
  712.           Read(Look);
  713.        end;
  714.  
  715.        {--------------------------------------------------------------}
  716.        { Report an Error }
  717.  
  718.        procedure Error(s: string);
  719.        begin
  720.           WriteLn;
  721.           WriteLn(^G, 'Error: ', s, '.');
  722.        end;
  723.  
  724.  
  725.        {--------------------------------------------------------------}
  726.        { Report Error and Halt }A*A*
  727.                                     - 12 -
  728. PA A
  729.  
  730.  
  731.  
  732.  
  733.  
  734.        procedure Abort(s: string);
  735.        begin
  736.           Error(s);
  737.           Halt;
  738.        end;
  739.  
  740.  
  741.        {--------------------------------------------------------------}
  742.        { Report What Was Expected }
  743.  
  744.        procedure Expected(s: string);
  745.        begin
  746.           Abort(s + ' Expected');
  747.        end;
  748.  
  749.  
  750.        {--------------------------------------------------------------}
  751.        { Recognize an Alpha Character }
  752.  
  753.        function IsAlpha(c: char): boolean;
  754.        begin
  755.           IsAlpha := UpCase(c) in ['A'..'Z'];
  756.        end;
  757.  
  758.  
  759.        {--------------------------------------------------------------}
  760.        { Recognize a Decimal Digit }
  761.  
  762.        function IsDigit(c: char): boolean;
  763.        begin
  764.           IsDigit := c in ['0'..'9'];
  765.        end;
  766.  
  767.  
  768.        {--------------------------------------------------------------}
  769.        { Recognize an Alphanumeric }
  770.  
  771.        function IsAlNum(c: char): boolean;
  772.        begin
  773.           IsAlNum := IsAlpha(c) or IsDigit(c);
  774.        end;
  775.  
  776.  
  777.        {--------------------------------------------------------------}
  778.        { Recognize an Addop }
  779.  
  780.        function IsAddop(c: char): boolean;
  781.        begin
  782.           IsAddop := c in ['+', '-'];
  783.        end;
  784.  
  785.  
  786.        {--------------------------------------------------------------}
  787.        { Recognize White Space }A*A*
  788.                                     - 13 -
  789. PA A
  790.  
  791.  
  792.  
  793.  
  794.  
  795.        function IsWhite(c: char): boolean;
  796.        begin
  797.           IsWhite := c in [' ', TAB];
  798.        end;
  799.  
  800.  
  801.        {--------------------------------------------------------------}
  802.        { Skip Over Leading White Space }
  803.  
  804.        procedure SkipWhite;
  805.        begin
  806.           while IsWhite(Look) do
  807.              GetChar;
  808.        end;
  809.  
  810.  
  811.        {--------------------------------------------------------------}
  812.        { Match a Specific Input Character }
  813.  
  814.        procedure Match(x: char);
  815.        begin
  816.           if Look <> x then Expected('''' + x + '''')
  817.           else begin
  818.              GetChar;
  819.              SkipWhite;
  820.           end;
  821.        end;
  822.  
  823.  
  824.        {--------------------------------------------------------------}
  825.        { Get an Identifier }
  826.  
  827.        function GetName: string;
  828.        var Token: string;
  829.        begin
  830.           Token := '';
  831.           if not IsAlpha(Look) then Expected('Name');
  832.           while IsAlNum(Look) do begin
  833.              Token := Token + UpCase(Look);
  834.              GetChar;
  835.           end;
  836.           GetName := Token;
  837.           SkipWhite;
  838.        end;
  839.  
  840.  
  841.        {--------------------------------------------------------------}
  842.        { Get a Number }
  843.  
  844.        function GetNum: string;
  845.        var Value: string;
  846.        begin
  847.           Value := '';
  848.           if not IsDigit(Look) then Expected('Integer');A*A*
  849.                                     - 14 -
  850. PA A
  851.  
  852.  
  853.  
  854.  
  855.  
  856.           while IsDigit(Look) do begin
  857.              Value := Value + Look;
  858.              GetChar;
  859.           end;
  860.           GetNum := Value;
  861.           SkipWhite;
  862.        end;
  863.  
  864.  
  865.        {--------------------------------------------------------------}
  866.        { Output a String with Tab }
  867.  
  868.        procedure Emit(s: string);
  869.        begin
  870.           Write(TAB, s);
  871.        end;
  872.  
  873.  
  874.        {--------------------------------------------------------------}
  875.        { Output a String with Tab and CRLF }
  876.  
  877.        procedure EmitLn(s: string);
  878.        begin
  879.           Emit(s);
  880.           WriteLn;
  881.        end;
  882.  
  883.  
  884.        {---------------------------------------------------------------}
  885.        { Parse and Translate a Identifier }
  886.  
  887.        procedure Ident;
  888.        var Name: string[8];
  889.        begin
  890.           Name:= GetName;
  891.           if Look = '(' then begin
  892.              Match('(');
  893.              Match(')');
  894.              EmitLn('BSR ' + Name);
  895.              end
  896.           else
  897.              EmitLn('MOVE ' + Name + '(PC),D0');
  898.        end;
  899.  
  900.  
  901.        {---------------------------------------------------------------}
  902.        { Parse and Translate a Math Factor }
  903.  
  904.        procedure Expression; Forward;
  905.  
  906.        procedure Factor;
  907.        begin
  908.           if Look = '(' then begin
  909.              Match('(');A*A*
  910.                                     - 15 -
  911. PA A
  912.  
  913.  
  914.  
  915.  
  916.  
  917.              Expression;
  918.              Match(')');
  919.              end
  920.           else if IsAlpha(Look) then
  921.              Ident
  922.           else
  923.              EmitLn('MOVE #' + GetNum + ',D0');
  924.        end;
  925.  
  926.  
  927.        {--------------------------------------------------------------}
  928.        { Recognize and Translate a Multiply }
  929.  
  930.        procedure Multiply;
  931.        begin
  932.           Match('*');
  933.           Factor;
  934.           EmitLn('MULS (SP)+,D0');
  935.        end;
  936.  
  937.  
  938.        {-------------------------------------------------------------}
  939.        { Recognize and Translate a Divide }
  940.  
  941.        procedure Divide;
  942.        begin
  943.           Match('/');
  944.           Factor;
  945.           EmitLn('MOVE (SP)+,D1');
  946.           EmitLn('EXS.L D0');
  947.           EmitLn('DIVS D1,D0');
  948.        end;
  949.  
  950.  
  951.        {---------------------------------------------------------------}
  952.        { Parse and Translate a Math Term }
  953.  
  954.        procedure Term;
  955.        begin
  956.           Factor;
  957.           while Look in ['*', '/'] do begin
  958.              EmitLn('MOVE D0,-(SP)');
  959.              case Look of
  960.               '*': Multiply;
  961.               '/': Divide;
  962.              end;
  963.           end;
  964.        end;
  965.  
  966.  
  967.        {--------------------------------------------------------------}
  968.        { Recognize and Translate an Add }
  969.  
  970.        procedure Add;A*A*
  971.                                     - 16 -
  972. PA A
  973.  
  974.  
  975.  
  976.  
  977.  
  978.        begin
  979.           Match('+');
  980.           Term;
  981.           EmitLn('ADD (SP)+,D0');
  982.        end;
  983.  
  984.  
  985.        {-------------------------------------------------------------}
  986.        { Recognize and Translate a Subtract }
  987.  
  988.        procedure Subtract;
  989.        begin
  990.           Match('-');
  991.           Term;
  992.           EmitLn('SUB (SP)+,D0');
  993.           EmitLn('NEG D0');
  994.        end;
  995.  
  996.  
  997.        {---------------------------------------------------------------}
  998.        { Parse and Translate an Expression }
  999.  
  1000.        procedure Expression;
  1001.        begin
  1002.           if IsAddop(Look) then
  1003.              EmitLn('CLR D0')
  1004.           else
  1005.              Term;
  1006.           while IsAddop(Look) do begin
  1007.              EmitLn('MOVE D0,-(SP)');
  1008.              case Look of
  1009.               '+': Add;
  1010.               '-': Subtract;
  1011.              end;
  1012.           end;
  1013.        end;
  1014.  
  1015.  
  1016.        {--------------------------------------------------------------}
  1017.        { Parse and Translate an Assignment Statement }
  1018.  
  1019.        procedure Assignment;
  1020.        var Name: string[8];
  1021.        begin
  1022.           Name := GetName;
  1023.           Match('=');
  1024.           Expression;
  1025.           EmitLn('LEA ' + Name + '(PC),A0');
  1026.           EmitLn('MOVE D0,(A0)')
  1027.        end;
  1028.  
  1029.  
  1030.        {--------------------------------------------------------------}
  1031.        { Initialize }A*A*
  1032.                                     - 17 -
  1033. PA A
  1034.  
  1035.  
  1036.  
  1037.  
  1038.  
  1039.        procedure Init;
  1040.        begin
  1041.           GetChar;
  1042.           SkipWhite;
  1043.        end;
  1044.  
  1045.  
  1046.        {--------------------------------------------------------------}
  1047.        { Main Program }
  1048.  
  1049.        begin
  1050.           Init;
  1051.           Assignment;
  1052.           If Look <> CR then Expected('NewLine');
  1053.        end.
  1054.        {--------------------------------------------------------------}
  1055.  
  1056.  
  1057.        Now the parser is complete.  It's got every feature we can put in
  1058.        a  one-line "compiler."  Tuck it away in a safe place.  Next time
  1059.        we'll move on to a new subject, but we'll still be  talking about
  1060.        expressions for quite awhile.  Next installment, I plan to talk a
  1061.        bit about interpreters as opposed  to compilers, and show you how
  1062.        the structure of the parser changes a bit as we change  what sort
  1063.        of action has to be taken.  The information we pick up there will
  1064.        serve  us in good stead later on, even if you have no interest in
  1065.        interpreters.  See you next time.
  1066.  
  1067.  
  1068.        *****************************************************************
  1069.        *                                                               *
  1070.        *                        COPYRIGHT NOTICE                       *
  1071.        *                                                               *
  1072.        *   Copyright (C) 1988 Jack W. Crenshaw. All rights reserved.   *
  1073.        *                                                               *
  1074.        *****************************************************************AUAU
  1075.  
  1076. A,A,
  1077.  
  1078.                                     - 18 -A*A*
  1079. @