home *** CD-ROM | disk | FTP | other *** search
/ Programmer Power Tools / Programmer Power Tools.iso / turbopas / tutorpas.arc / TUTOR4.DOC < prev    next >
Encoding:
Text File  |  1988-08-11  |  30.6 KB  |  793 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.                                   24 July 1988
  36.  
  37.  
  38.                              Part IV: INTERPRETERS
  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 first three installments of this series,  we've  looked at
  87.        parsing and  compiling math expressions, and worked our way grad-
  88.        ually and methodically from dealing  with  very  simple one-term,
  89.        one-character "expressions" up through more general ones, finally
  90.        arriving at a very complete parser that could parse and translate
  91.        complete  assignment  statements,  with  multi-character  tokens,
  92.        embedded white space, and function calls.  This  time,  I'm going
  93.        to walk you through the process one more time, only with the goal
  94.        of interpreting rather than compiling object code.
  95.  
  96.        Since this is a series on compilers, why should  we  bother  with
  97.        interpreters?  Simply because I want you to see how the nature of
  98.        the  parser changes as we change the goals.  I also want to unify
  99.        the concepts of the two types of translators, so that you can see
  100.        not only the differences, but also the similarities.
  101.  
  102.        Consider the assignment statement
  103.  
  104.                       x = 2 * y + 3
  105.  
  106.        In a compiler, we want the target CPU to execute  this assignment
  107.        at EXECUTION time.  The translator itself doesn't  do  any arith-
  108.        metic ... it only issues the object code that will cause  the CPU
  109.        to do it when the code is executed.  For  the  example above, the
  110.        compiler would issue code to compute the expression and store the
  111.        results in variable x.
  112.  
  113.        For an interpreter,  on  the  other  hand, no object code is gen-
  114.        erated.   Instead, the arithmetic is computed immediately, as the
  115.        parsing is going on.  For the example, by the time parsing of the
  116.        statement is complete, x will have a new value.
  117.  
  118.        The approach we've been  taking  in  this  whole series is called
  119.        "syntax-driven translation."  As you are aware by now, the struc-
  120.        ture of the  parser  is  very  closely  tied to the syntax of the
  121.        productions we parse.  We  have built Pascal procedures that rec-
  122.        ognize every language  construct.   Associated with each of these
  123.        constructs (and procedures) is  a  corresponding  "action," which
  124.        does  whatever  makes  sense to do  once  a  construct  has  been
  125.        recognized.    In  our  compiler  so far, every  action  involves
  126.        emitting object code, to be executed later at execution time.  In
  127.        an interpreter, every action  involves  something  to be done im-
  128.        mediately.A*A*
  129.                                      - 2 -
  130.  
  131. PA A
  132.  
  133.  
  134.  
  135.  
  136.  
  137.        What I'd like you to see here is that the  layout  ... the struc-
  138.        ture ... of  the  parser  doesn't  change.  It's only the actions
  139.        that change.   So  if  you  can  write an interpreter for a given
  140.        language, you can also write a compiler, and vice versa.  Yet, as
  141.        you  will  see,  there  ARE  differences,  and  significant ones.
  142.        Because the actions are different,  the  procedures  that  do the
  143.        recognizing end up being written differently.    Specifically, in
  144.        the interpreter  the recognizing procedures end up being coded as
  145.        FUNCTIONS that return numeric values to their callers.    None of
  146.        the parsing routines for our compiler did that.
  147.  
  148.        Our compiler, in fact,  is  what we might call a "pure" compiler.
  149.        Each time a construct is recognized, the object  code  is emitted
  150.        IMMEDIATELY.  (That's one reason the code is not very efficient.)
  151.        The interpreter we'll be building  here is a pure interpreter, in
  152.        the sense that there is  no  translation,  such  as "tokenizing,"
  153.        performed on the source code.  These represent  the  two extremes
  154.        of translation.  In  the  real  world,  translators are rarely so
  155.        pure, but tend to have bits of each technique.
  156.  
  157.        I can think of  several  examples.    I've already mentioned one:
  158.        most interpreters, such as Microsoft BASIC,  for  example, trans-
  159.        late the source code (tokenize it) into an  intermediate  form so
  160.        that it'll be easier to parse real time.
  161.  
  162.        Another example is an assembler.  The purpose of an assembler, of
  163.        course, is to produce object code, and it normally does that on a
  164.        one-to-one basis: one object instruction per line of source code.
  165.        But almost every assembler also permits expressions as arguments.
  166.        In this case, the expressions  are  always  constant expressions,
  167.        and  so the assembler isn't supposed to  issue  object  code  for
  168.        them.  Rather,  it  "interprets" the expressions and computes the
  169.        corresponding constant result, which is what it actually emits as
  170.        object code.
  171.  
  172.        As a matter of fact, we  could  use  a bit of that ourselves. The
  173.        translator we built in the  previous  installment  will dutifully
  174.        spit out object code  for  complicated  expressions,  even though
  175.        every term in  the  expression  is  a  constant.  In that case it
  176.        would be far better if the translator behaved a bit more  like an
  177.        interpreter, and just computed the equivalent constant result.
  178.  
  179.        There is  a concept in compiler theory called "lazy" translation.
  180.        The  idea is that you typically don't just  emit  code  at  every
  181.        action.  In fact, at the extreme you don't emit anything  at all,
  182.        until  you  absolutely  have to.  To accomplish this, the actions
  183.        associated with the parsing routines  typically  don't  just emit
  184.        code.  Sometimes  they  do,  but  often  they  simply  return in-
  185.        formation back to the caller.  Armed with  such  information, the
  186.        caller can then make a better choice of what to do.
  187.  
  188.        For example, given the statement
  189.  
  190.                       x = x + 3 - 2 - (5 - 4)  ,A*A*
  191.                                      - 3 -
  192.  
  193. PA A
  194.  
  195.  
  196.  
  197.  
  198.  
  199.        our compiler will dutifully spit  out a stream of 18 instructions
  200.        to load each parameter into  registers,  perform  the arithmetic,
  201.        and store the result.  A lazier evaluation  would  recognize that
  202.        the arithmetic involving constants can  be  evaluated  at compile
  203.        time, and would reduce the expression to
  204.  
  205.                       x = x + 0  .
  206.  
  207.        An  even  lazier  evaluation would then be smart enough to figure
  208.        out that this is equivalent to
  209.  
  210.                       x = x  ,
  211.  
  212.        which  calls  for  no  action  at  all.   We could reduce 18  in-
  213.        structions to zero!
  214.  
  215.        Note that there is no chance of optimizing this way in our trans-
  216.        lator as it stands, because every action takes place immediately.
  217.  
  218.        Lazy  expression  evaluation  can  produce  significantly  better
  219.        object code than  we  have  been  able  to  so  far.  I warn you,
  220.        though: it complicates the parser code considerably, because each
  221.        routine now has to make decisions as to whether  to  emit  object
  222.        code or not.  Lazy evaluation is certainly not named that because
  223.        it's easier on the compiler writer!
  224.  
  225.        Since we're operating mainly on  the KISS principle here, I won't
  226.        go  into much more depth on this subject.  I just want you to  be
  227.        aware  that  you  can get some code optimization by combining the
  228.        techniques of compiling and  interpreting.    In  particular, you
  229.        should know that the parsing  routines  in  a  smarter translator
  230.        will generally  return  things  to  their  caller,  and sometimes
  231.        expect things as  well.    That's  the main reason for going over
  232.        interpretation in this installment.
  233.  
  234.  
  235.        THE INTERPRETER
  236.  
  237.        OK, now that you know WHY we're going into all this, let's do it.
  238.        Just to give you practice, we're going to start over with  a bare
  239.        cradle and build up the translator all over again.  This time, of
  240.        course, we can go a bit faster.
  241.  
  242.        Since we're now going  to  do arithmetic, the first thing we need
  243.        to do is to change function GetNum, which up till now  has always
  244.        returned a character  (or  string).    Now, it's better for it to
  245.        return an integer.    MAKE  A  COPY of the cradle (for goodness's
  246.        sake, don't change the version  in  Cradle  itself!!)  and modify
  247.        GetNum as follows:
  248.  
  249.  
  250.        {--------------------------------------------------------------}
  251.        { Get a Number }A6A6
  252.                                      - 4 -A*A*
  253.  
  254. PA A
  255.  
  256.  
  257.  
  258.  
  259.  
  260.        function GetNum: integer;
  261.        begin
  262.           if not IsDigit(Look) then Expected('Integer');
  263.           GetNum := Ord(Look) - Ord('0');
  264.           GetChar;
  265.        end;
  266.        {--------------------------------------------------------------}
  267.  
  268.  
  269.        Now, write the following version of Expression:
  270.  
  271.  
  272.        {---------------------------------------------------------------}
  273.        { Parse and Translate an Expression }
  274.  
  275.        function Expression: integer;
  276.        begin
  277.           Expression := GetNum;
  278.        end;
  279.        {--------------------------------------------------------------}
  280.  
  281.  
  282.        Finally, insert the statement
  283.  
  284.  
  285.           Writeln(Expression);
  286.  
  287.  
  288.        at the end of the main program.  Now compile and test.
  289.  
  290.        All this program  does  is  to  "parse"  and  translate  a single
  291.        integer  "expression."    As always, you should make sure that it
  292.        does that with the digits 0..9, and gives an  error  message  for
  293.        anything else.  Shouldn't take you very long!
  294.  
  295.        OK, now let's extend this to include addops.    Change Expression
  296.        to read:
  297.  
  298.  
  299.        {---------------------------------------------------------------}
  300.        { Parse and Translate an Expression }
  301.  
  302.        function Expression: integer;
  303.        var Value: integer;
  304.        begin
  305.           if IsAddop(Look) then
  306.              Value := 0
  307.           else
  308.              Value := GetNum;
  309.           while IsAddop(Look) do begin
  310.              case Look of
  311.               '+': begin
  312.                       Match('+');
  313.                       Value := Value + GetNum;A*A*
  314.                                      - 5 -
  315.  
  316. PA A
  317.  
  318.  
  319.  
  320.  
  321.  
  322.                    end;
  323.               '-': begin
  324.                       Match('-');
  325.                       Value := Value - GetNum;
  326.                    end;
  327.              end;
  328.           end;
  329.           Expression := Value;
  330.        end;
  331.        {--------------------------------------------------------------}
  332.  
  333.  
  334.        The structure of Expression, of  course,  parallels  what  we did
  335.        before,  so  we  shouldn't have too much  trouble  debugging  it.
  336.        There's  been  a  SIGNIFICANT  development, though, hasn't there?
  337.        Procedures Add and Subtract went away!  The reason  is  that  the
  338.        action to be taken  requires  BOTH arguments of the operation.  I
  339.        could have chosen to retain the procedures and pass into them the
  340.        value of the expression to date,  which  is Value.  But it seemed
  341.        cleaner to me to  keep  Value as strictly a local variable, which
  342.        meant that the code for Add and Subtract had to be moved in line.
  343.        This result suggests  that,  while the structure we had developed
  344.        was nice and  clean  for our simple-minded translation scheme, it
  345.        probably  wouldn't do for use with lazy  evaluation.    That's  a
  346.        little tidbit we'll probably want to keep in mind for later.
  347.  
  348.        OK,  did the translator work?  Then let's  take  the  next  step.
  349.        It's not hard to  figure  out what procedure Term should now look
  350.        like.  Change every call to GetNum in function  Expression  to  a
  351.        call to Term, and then enter the following form for Term:
  352.  
  353.  
  354.        {---------------------------------------------------------------}
  355.        { Parse and Translate a Math Term }
  356.  
  357.        function Term: integer;
  358.        var Value: integer;
  359.        begin
  360.           Value := GetNum;
  361.           while Look in ['*', '/'] do begin
  362.              case Look of
  363.               '*': begin
  364.                       Match('*');
  365.                       Value := Value * GetNum;
  366.                    end;
  367.               '/': begin
  368.                       Match('/');
  369.                       Value := Value div GetNum;
  370.                    end;
  371.              end;
  372.           end;
  373.           Term := Value;
  374.        end;
  375.        {--------------------------------------------------------------}A*A*
  376.                                      - 6 -
  377.  
  378. PA A
  379.  
  380.  
  381.  
  382.  
  383.  
  384.        Now, try it out.    Don't forget two things: first, we're dealing
  385.        with integer division, so, for example, 1/3 should come out zero.
  386.        Second, even  though we can output multi-digit results, our input
  387.        is still restricted to single digits.
  388.  
  389.        That seems like a silly restriction at this point, since  we have
  390.        already  seen how easily function GetNum can  be  extended.    So
  391.        let's go ahead and fix it right now.  The new version is
  392.  
  393.  
  394.        {--------------------------------------------------------------}
  395.        { Get a Number }
  396.  
  397.        function GetNum: integer;
  398.        var Value: integer;
  399.        begin
  400.           Value := 0;
  401.           if not IsDigit(Look) then Expected('Integer');
  402.           while IsDigit(Look) do begin
  403.              Value := 10 * Value + Ord(Look) - Ord('0');
  404.              GetChar;
  405.           end;
  406.           GetNum := Value;
  407.        end;
  408.        {--------------------------------------------------------------}
  409.  
  410.  
  411.        If you've compiled and  tested  this  version of the interpreter,
  412.        the  next  step  is to install function Factor, complete with pa-
  413.        renthesized  expressions.  We'll hold off a  bit  longer  on  the
  414.        variable  names.    First, change the references  to  GetNum,  in
  415.        function Term, so that they call Factor instead.   Now  code  the
  416.        following version of Factor:
  417.  
  418.  
  419.        {---------------------------------------------------------------}
  420.        { Parse and Translate a Math Factor }
  421.  
  422.        function Expression: integer; Forward;
  423.  
  424.        function Factor: integer;
  425.        begin
  426.           if Look = '(' then begin
  427.              Match('(');
  428.              Factor := Expression;
  429.              Match(')');
  430.              end
  431.           else
  432.               Factor := GetNum;
  433.        end;
  434.        {---------------------------------------------------------------}ANAN
  435.                                      - 7 -A*A*
  436.  
  437. PA A
  438.  
  439.  
  440.  
  441.  
  442.  
  443.        That was pretty easy, huh?  We're rapidly closing in on  a useful
  444.        interpreter.
  445.  
  446.  
  447.        A LITTLE PHILOSOPHY
  448.  
  449.        Before going any further, there's something I'd like  to  call to
  450.        your attention.  It's a concept that we've been making use  of in
  451.        all these sessions, but I haven't explicitly mentioned it up till
  452.        now.  I think it's time, because it's a concept so useful, and so
  453.        powerful,  that  it  makes all the difference  between  a  parser
  454.        that's trivially easy, and one that's too complex to deal with.
  455.  
  456.        In the early days of compiler technology, people  had  a terrible
  457.        time  figuring  out  how to deal with things like operator prece-
  458.        dence  ...  the  way  that  multiply  and  divide operators  take
  459.        precedence over add and subtract, etc.  I remember a colleague of
  460.        some  thirty years ago, and how excited he was to find out how to
  461.        do it.  The technique used involved building two  stacks,    upon
  462.        which you pushed each operator  or operand.  Associated with each
  463.        operator was a precedence level,  and the rules required that you
  464.        only actually performed an operation  ("reducing"  the  stack) if
  465.        the precedence level showing on top of the stack was correct.  To
  466.        make life more interesting,  an  operator  like ')' had different
  467.        precedence levels, depending  upon  whether or not it was already
  468.        on the stack.  You  had to give it one value before you put it on
  469.        the stack, and another to decide when to take it  off.   Just for
  470.        the experience, I worked all of  this  out for myself a few years
  471.        ago, and I can tell you that it's very tricky.
  472.  
  473.        We haven't  had  to  do  anything like that.  In fact, by now the
  474.        parsing of an arithmetic statement should seem like child's play.
  475.        How did we get so lucky?  And where did the precedence stacks go?
  476.  
  477.        A similar thing is going on  in  our interpreter above.  You just
  478.        KNOW that in  order  for  it  to do the computation of arithmetic
  479.        statements (as opposed to the parsing of them), there have  to be
  480.        numbers pushed onto a stack somewhere.  But where is the stack?
  481.  
  482.        Finally,  in compiler textbooks, there are  a  number  of  places
  483.        where  stacks  and  other structures are discussed.  In the other
  484.        leading parsing method (LR), an explicit stack is used.  In fact,
  485.        the technique is very  much  like the old way of doing arithmetic
  486.        expressions.  Another concept  is  that of a parse tree.  Authors
  487.        like to draw diagrams  of  the  tokens  in a statement, connected
  488.        into a tree with  operators  at the internal nodes.  Again, where
  489.        are the trees and stacks in our technique?  We haven't seen any.
  490.        The answer in all cases is that the structures are  implicit, not
  491.        explicit.    In  any computer language, there is a stack involved
  492.        every  time  you  call  a  subroutine.  Whenever a subroutine  is
  493.        called, the return address is pushed onto the CPU stack.   At the
  494.        end of the subroutine, the address is popped back off and control
  495.        is  transferred  there.   In a recursive language such as Pascal,A6A6
  496.                                      - 8 -A*A*
  497.  
  498. PA A
  499.  
  500.  
  501.  
  502.  
  503.  
  504.        there can also be local data pushed onto the stack, and  it, too,
  505.        returns when it's needed.
  506.  
  507.        For example,  function  Expression  contains  a  local  parameter
  508.        called  Value, which it fills by a call to Term.  Suppose, in its
  509.        next call to  Term  for  the  second  argument,  that  Term calls
  510.        Factor, which recursively  calls  Expression  again.    That "in-
  511.        stance" of Expression gets another value for its  copy  of Value.
  512.        What happens  to  the  first  Value?    Answer: it's still on the
  513.        stack, and  will  be  there  again  when  we return from our call
  514.        sequence.
  515.  
  516.        In other words, the reason things look so simple  is  that  we've
  517.        been making maximum use of the resources of the  language.    The
  518.        hierarchy levels  and  the  parse trees are there, all right, but
  519.        they're hidden within the  structure  of  the parser, and they're
  520.        taken care of by the order with which the various  procedures are
  521.        called.  Now that you've seen how we do it, it's probably hard to
  522.        imagine doing it  any other way.  But I can tell you that it took
  523.        a lot of years for compiler writers to get that smart.  The early
  524.        compilers were too complex  too  imagine.    Funny how things get
  525.        easier with a little practice.
  526.  
  527.        The reason  I've  brought  all  this up is as both a lesson and a
  528.        warning.  The lesson: things can be easy when you do  them right.
  529.        The warning: take a look at what you're doing.  If, as you branch
  530.        out on  your  own,  you  begin to find a real need for a separate
  531.        stack or tree structure, it may be time to ask yourself if you're
  532.        looking at things the right way.  Maybe you just aren't using the
  533.        facilities of the language as well as you could be.
  534.  
  535.  
  536.        The next step is to add variable names.  Now,  though,  we have a
  537.        slight problem.  For  the  compiler, we had no problem in dealing
  538.        with variable names ... we just issued the names to the assembler
  539.        and let the rest  of  the program take care of allocating storage
  540.        for  them.  Here, on the other hand, we need to be able to  fetch
  541.        the values of the variables and return them as the  return values
  542.        of Factor.  We need a storage mechanism for these variables.
  543.  
  544.        Back in the early days of personal computing,  Tiny  BASIC lived.
  545.        It had  a  grand  total  of  26  possible variables: one for each
  546.        letter of the  alphabet.    This  fits nicely with our concept of
  547.        single-character tokens, so we'll  try  the  same  trick.  In the
  548.        beginning of your  interpreter,  just  after  the  declaration of
  549.        variable Look, insert the line:
  550.  
  551.                       Table: Array['A'..'Z'] of integer;
  552.  
  553.        We also need to initialize the array, so add this procedure:
  554.  
  555.  
  556.        {---------------------------------------------------------------}
  557.        { Initialize the Variable Area }A*A*
  558.                                      - 9 -
  559.  
  560. PA A
  561.  
  562.  
  563.  
  564.  
  565.  
  566.        procedure InitTable;
  567.        var i: char;
  568.        begin
  569.           for i := 'A' to 'Z' do
  570.              Table[i] := 0;
  571.        end;
  572.        {---------------------------------------------------------------}
  573.  
  574.  
  575.        You must also insert a call to InitTable, in procedure Init.
  576.        DON'T FORGET to do that, or the results may surprise you!
  577.  
  578.        Now that we have an array  of  variables, we can modify Factor to
  579.        use it.  Since we don't have a way (so far) to set the variables,
  580.        Factor  will always return zero values for  them,  but  let's  go
  581.        ahead and extend it anyway.  Here's the new version:
  582.  
  583.  
  584.        {---------------------------------------------------------------}
  585.        { Parse and Translate a Math Factor }
  586.  
  587.        function Expression: integer; Forward;
  588.  
  589.        function Factor: integer;
  590.        begin
  591.           if Look = '(' then begin
  592.              Match('(');
  593.              Factor := Expression;
  594.              Match(')');
  595.              end
  596.           else if IsAlpha(Look) then
  597.              Factor := Table[GetName]
  598.           else
  599.               Factor := GetNum;
  600.        end;
  601.        {---------------------------------------------------------------}
  602.  
  603.  
  604.        As always, compile and test this version of the  program.    Even
  605.        though all the variables are now zeros, at least we can correctly
  606.        parse the complete expressions, as well as catch any badly formed
  607.        expressions.
  608.  
  609.        I suppose you realize the next step: we need to do  an assignment
  610.        statement so we can  put  something INTO the variables.  For now,
  611.        let's  stick  to  one-liners,  though  we will soon  be  handling
  612.        multiple statements.
  613.  
  614.        The assignment statement parallels what we did before:
  615.  
  616.  
  617.        {--------------------------------------------------------------}
  618.        { Parse and Translate an Assignment Statement }A6A6
  619.                                     - 10 -A*A*
  620.  
  621. PA A
  622.  
  623.  
  624.  
  625.  
  626.  
  627.        procedure Assignment;
  628.        var Name: char;
  629.        begin
  630.           Name := GetName;
  631.           Match('=');
  632.           Table[Name] := Expression;
  633.        end;
  634.        {--------------------------------------------------------------}
  635.  
  636.  
  637.        To test this,  I  added  a  temporary write statement in the main
  638.        program,  to  print out the value of A.  Then I  tested  it  with
  639.        various assignments to it.
  640.  
  641.        Of course, an interpretive language that can only accept a single
  642.        line of program  is not of much value.  So we're going to want to
  643.        handle multiple statements.  This  merely  means  putting  a loop
  644.        around  the  call  to Assignment.  So let's do that now. But what
  645.        should be the loop exit criterion?  Glad you  asked,  because  it
  646.        brings up a point we've been able to ignore up till now.
  647.  
  648.        One of the most tricky things  to  handle in any translator is to
  649.        determine when to bail out of  a  given construct and go look for
  650.        something else.  This hasn't been a problem for us so far because
  651.        we've only allowed for  a  single kind of construct ... either an
  652.        expression  or an assignment statement.   When  we  start  adding
  653.        loops and different kinds of statements, you'll find that we have
  654.        to be very careful that things terminate properly.  If we put our
  655.        interpreter in a loop, we need a way to quit.    Terminating on a
  656.        newline is no good, because that's what sends us back for another
  657.        line.  We could always let an unrecognized character take us out,
  658.        but that would cause every run to end in an error  message, which
  659.        certainly seems uncool.
  660.  
  661.        What we need  is  a  termination  character.  I vote for Pascal's
  662.        ending period ('.').   A  minor  complication  is that Turbo ends
  663.        every normal line  with  TWO characters, the carriage return (CR)
  664.        and line feed (LF).   At  the  end  of  each line, we need to eat
  665.        these characters before processing the next one.   A  natural way
  666.        to do this would  be  with  procedure  Match, except that Match's
  667.        error  message  prints  the character, which of course for the CR
  668.        and/or  LF won't look so great.  What we need is a special proce-
  669.        dure for this, which we'll no doubt be using over and over.  Here
  670.        it is:
  671.  
  672.  
  673.        {--------------------------------------------------------------}
  674.        { Recognize and Skip Over a Newline }
  675.  
  676.        procedure NewLine;
  677.        begin
  678.           if Look = CR then begin
  679.              GetChar;
  680.              if Look = LF thenA*A*
  681.                                     - 11 -
  682.  
  683. PA A
  684.  
  685.  
  686.  
  687.  
  688.  
  689.                 GetChar;
  690.           end;
  691.        end;
  692.        {--------------------------------------------------------------}
  693.  
  694.  
  695.        Insert this procedure at any convenient spot ... I put  mine just
  696.        after Match.  Now, rewrite the main program to look like this:
  697.  
  698.  
  699.        {--------------------------------------------------------------}
  700.        { Main Program }
  701.  
  702.        begin
  703.           Init;
  704.           repeat
  705.              Assignment;
  706.              NewLine;
  707.           until Look = '.';
  708.        end.
  709.        {--------------------------------------------------------------}
  710.  
  711.  
  712.        Note that the  test for a CR is now gone, and that there are also
  713.        no  error tests within NewLine itself.   That's  OK,  though  ...
  714.        whatever is left over in terms of bogus characters will be caught
  715.        at the beginning of the next assignment statement.
  716.  
  717.        Well, we now have a functioning interpreter.  It doesn't do  us a
  718.        lot of  good,  however,  since  we have no way to read data in or
  719.        write it out.  Sure would help to have some I/O!
  720.  
  721.        Let's wrap this session  up,  then,  by  adding the I/O routines.
  722.        Since we're  sticking to single-character tokens, I'll use '?' to
  723.        stand for a read statement, and  '!'  for a write, with the char-
  724.        acter  immediately  following  them  to  be used as  a  one-token
  725.        "parameter list."  Here are the routines:
  726.  
  727.        {--------------------------------------------------------------}
  728.        { Input Routine }
  729.  
  730.        procedure Input;
  731.        begin
  732.           Match('?');
  733.           Read(Table[GetName]);
  734.        end;
  735.  
  736.  
  737.        {--------------------------------------------------------------}
  738.        { Output Routine }
  739.  
  740.        procedure Output;
  741.        begin
  742.           Match('!');A*A*
  743.                                     - 12 -
  744.  
  745. PA A
  746.  
  747.  
  748.  
  749.  
  750.  
  751.           WriteLn(Table[GetName]);
  752.        end;
  753.        {--------------------------------------------------------------}
  754.  
  755.        They aren't very fancy, I admit ... no prompt character on input,
  756.        for example ... but they get the job done.
  757.  
  758.        The corresponding changes in  the  main  program are shown below.
  759.        Note that we use the usual  trick  of a case statement based upon
  760.        the current lookahead character, to decide what to do.
  761.  
  762.  
  763.        {--------------------------------------------------------------}
  764.        { Main Program }
  765.  
  766.        begin
  767.           Init;
  768.           repeat
  769.              case Look of
  770.               '?': Input;
  771.               '!': Output;
  772.               else Assignment;
  773.              end;
  774.              NewLine;
  775.           until Look = '.';
  776.        end.
  777.        {--------------------------------------------------------------}
  778.  
  779.  
  780.        You have now completed a  real, working interpreter.  It's pretty
  781.        sparse, but it works just like the "big boys."  It includes three
  782.        kinds of program statements  (and  can  tell the difference!), 26
  783.        variables,  and  I/O  statements.  The only things that it lacks,
  784.        really, are control statements,  subroutines,    and some kind of
  785.        program editing function.  The program editing part, I'm going to
  786.        pass on.  After all, we're  not  here  to build a product, but to
  787.        learn  things.    The control statements, we'll cover in the next
  788.        installment, and the subroutines soon  after.  I'm anxious to get
  789.        on with that, so we'll leave the interpreter as it stands.
  790.  
  791.        I hope that by  now  you're convinced that the limitation of sin-
  792.        gle-character names  and the processing of white space are easily
  793.        taken  care  of, as we did in the last session.   This  time,  if
  794.        you'd like to play around with these extensions, be my  guest ...
  795.        they're  "left as an exercise for the student."    See  you  next
  796.        time.
  797.  
  798.        *****************************************************************
  799.        *                                                               *
  800.        *                        COPYRIGHT NOTICE                       *
  801.        *                                                               *
  802.        *   Copyright (C) 1988 Jack W. Crenshaw. All rights reserved.   *
  803.        *                                                               *
  804.        *****************************************************************A*A*
  805.                                     - 13 -
  806. @