home *** CD-ROM | disk | FTP | other *** search
/ Programmer 7500 / MAX_PROGRAMMERS.iso / INFO / TURBOPAS / TUTORPAS.ARC / TUTOR3.DOC < prev    next >
Encoding:
Text File  |  1988-08-11  |  33.2 KB  |  1,079 lines

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