home *** CD-ROM | disk | FTP | other *** search
/ ProfitPress Mega CDROM2 …eeware (MSDOS)(1992)(Eng) / ProfitPress-MegaCDROM2.B6I / PROG / PASCAL / TUTORPAS.ZIP / TUTOR2.DOC < prev    next >
Encoding:
Text File  |  1988-07-30  |  32.6 KB  |  900 lines

  1. O
  2. PA A
  3.  
  4.  
  5.  
  6.  
  7.  
  8.  
  9.  
  10.  
  11.  
  12.  
  13.  
  14.  
  15.  
  16.  
  17.  
  18.  
  19.  
  20.  
  21.  
  22.  
  23.  
  24.  
  25.  
  26.  
  27.  
  28.  
  29.  
  30.                             LET'S BUILD A COMPILER!
  31.  
  32.                                        By
  33.  
  34.                             Jack W. Crenshaw, Ph.D.
  35.  
  36.                                   24 July 1988
  37.  
  38.  
  39.                           Part II: EXPRESSION PARSING
  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 1988 (C) Jack W. Crenshaw. All rights reserved.   *
  80.        *                                                               *
  81.        *****************************************************************
  82.  
  83.  
  84.        GETTING STARTED
  85.  
  86.        If you've read the introduction document to this series, you will
  87.        already know what  we're  about.    You will also have copied the
  88.        cradle software  into your Turbo Pascal system, and have compiled
  89.        it.  So you should be ready to go.
  90.  
  91.  
  92.        The purpose of this article is for us to learn  how  to parse and
  93.        translate mathematical expressions.  What we would like to see as
  94.        output is a series of assembler-language statements  that perform
  95.        the desired actions.    For purposes of definition, an expression
  96.        is the right-hand side of an equation, as in
  97.  
  98.                       x = 2*y + 3/(4*z)
  99.  
  100.        In the early going, I'll be taking things in _VERY_  small steps.
  101.        That's  so  that  the beginners among you won't get totally lost.
  102.        There are also  some  very  good  lessons to be learned early on,
  103.        that will serve us well later.  For the more experienced readers:
  104.        bear with me.  We'll get rolling soon enough.
  105.  
  106.        SINGLE DIGITS
  107.  
  108.        In keeping with the whole theme of this series (KISS, remember?),
  109.        let's start with the absolutely most simple case we can think of.
  110.        That, to me, is an expression consisting of a single digit.
  111.  
  112.        Before starting to code, make sure you have a  baseline  copy  of
  113.        the  "cradle" that I gave last time.  We'll be using it again for
  114.        other experiments.  Then add this code:
  115.  
  116.  
  117.        {---------------------------------------------------------------}
  118.        { Parse and Translate a Math Expression }
  119.  
  120.        procedure Expression;
  121.        begin
  122.           EmitLn('MOVE #' + GetNum + ',D0')
  123.        end;
  124.        {---------------------------------------------------------------}
  125.  
  126.  
  127.        And add the  line  "Expression;"  to  the main program so that it
  128.        reads:A*A*
  129.                                      - 2 -
  130.  
  131. PA A
  132.  
  133.  
  134.  
  135.  
  136.  
  137.        {---------------------------------------------------------------}
  138.        begin
  139.           Init;
  140.           Expression;
  141.        end.
  142.        {---------------------------------------------------------------}
  143.  
  144.  
  145.        Now  run  the  program. Try any single-digit number as input. You
  146.        should get a single line of assembler-language output.    Now try
  147.        any  other character as input, and you'll  see  that  the  parser
  148.        properly reports an error.
  149.  
  150.  
  151.        CONGRATULATIONS! You have just written a working translator!
  152.  
  153.        OK, I grant you that it's pretty limited. But don't brush  it off
  154.        too  lightly.  This little "compiler" does,  on  a  very  limited
  155.        scale,  exactly  what  any larger compiler does:    it  correctly
  156.        recognizes legal  statements in the input "language" that we have
  157.        defined for it, and  it  produces  correct,  executable assembler
  158.        code,  suitable  for  assembling  into  object  format.  Just  as
  159.        importantly,  it correctly  recognizes  statements  that  are NOT
  160.        legal, and gives a  meaningful  error message.  Who could ask for
  161.        more?  As we expand our  parser,  we'd better make sure those two
  162.        characteristics always hold true.
  163.  
  164.        There  are  some  other  features  of  this  tiny  program  worth
  165.        mentioning.    First,  you  can  see that we don't separate  code
  166.        generation from parsing ...  as  soon as the parser knows what we
  167.        want  done, it generates the object code directly.    In  a  real
  168.        compiler, of course, the reads in GetChar would be  from  a  disk
  169.        file, and the writes to another  disk  file, but this way is much
  170.        easier to deal with while we're experimenting.
  171.  
  172.        Also note that an expression must leave a result somewhere.  I've
  173.        chosen the  68000  register  DO.    I  could have made some other
  174.        choices, but this one makes sense.
  175.  
  176.  
  177.        BINARY EXPRESSIONS
  178.  
  179.        Now that we have that under our belt,  let's  branch  out  a bit.
  180.        Admittedly, an "expression" consisting of only  one  character is
  181.        not going to meet our needs for long, so let's see what we can do
  182.        to extend it. Suppose we want to handle expressions of the form:
  183.  
  184.                                 1+2
  185.             or                  4-3
  186.             or, in general, <term> +/- <term>
  187.  
  188.        (That's a bit of Backus-Naur Form, or BNF.)ABAB
  189.                                      - 3 -A*A*
  190.  
  191. PA A
  192.  
  193.  
  194.  
  195.  
  196.  
  197.        To do this we need a procedure that recognizes a term  and leaves
  198.        its   result   somewhere,  and  another   that   recognizes   and
  199.        distinguishes  between   a  '+'  and  a  '-'  and  generates  the
  200.        appropriate code.  But if Expression is going to leave its result
  201.        in DO, where should Term leave its result?    Answer:    the same
  202.        place.  We're  going  to  have  to  save the first result of Term
  203.        somewhere before we get the next one.
  204.  
  205.        OK, basically what we want to  do  is have procedure Term do what
  206.        Expression was doing before.  So just RENAME procedure Expression
  207.        as Term, and enter the following new version of Expression:
  208.  
  209.  
  210.        {---------------------------------------------------------------}
  211.        { Parse and Translate an Expression }
  212.  
  213.        procedure Expression;
  214.        begin
  215.           Term;
  216.           EmitLn('MOVE D0,D1');
  217.           case Look of
  218.            '+': Add;
  219.            '-': Subtract;
  220.           else Expected('Addop');
  221.           end;
  222.        end;
  223.        {--------------------------------------------------------------}
  224.  
  225.  
  226.        Next, just above Expression enter these two procedures:
  227.  
  228.  
  229.        {--------------------------------------------------------------}
  230.        { Recognize and Translate an Add }
  231.  
  232.        procedure Add;
  233.        begin
  234.           Match('+');
  235.           Term;
  236.           EmitLn('ADD D1,D0');
  237.        end;
  238.  
  239.  
  240.        {-------------------------------------------------------------}
  241.        { Recognize and Translate a Subtract }
  242.  
  243.        procedure Subtract;
  244.        begin
  245.           Match('-');
  246.           Term;
  247.           EmitLn('SUB D1,D0');
  248.        end;
  249.        {-------------------------------------------------------------}A6A6
  250.                                      - 4 -A*A*
  251.  
  252. PA A
  253.  
  254.  
  255.  
  256.  
  257.  
  258.        When you're finished with that,  the order of the routines should
  259.        be:
  260.  
  261.         o Term (The OLD Expression)
  262.         o Add
  263.         o Subtract
  264.         o Expression
  265.  
  266.        Now run the program.  Try any combination you can think of of two
  267.        single digits,  separated  by  a  '+' or a '-'.  You should get a
  268.        series of four assembler-language instructions out  of  each run.
  269.        Now  try  some  expressions with deliberate errors in them.  Does
  270.        the parser catch the errors?
  271.  
  272.        Take  a  look  at the object  code  generated.    There  are  two
  273.        observations we can make.  First, the code generated is  NOT what
  274.        we would write ourselves.  The sequence
  275.  
  276.                MOVE #n,D0
  277.                MOVE D0,D1
  278.  
  279.        is inefficient.  If we were  writing  this code by hand, we would
  280.        probably just load the data directly to D1.
  281.  
  282.        There is a  message  here:  code  generated by our parser is less
  283.        efficient  than the code we would write by hand.  Get used to it.
  284.        That's going to be true throughout this series.  It's true of all
  285.        compilers to some extent.  Computer scientists have devoted whole
  286.        lifetimes to the issue of code optimization, and there are indeed
  287.        things that can be done to improve the quality  of  code  output.
  288.        Some compilers do quite well, but  there  is a heavy price to pay
  289.        in complexity, and it's  a  losing  battle  anyway ... there will
  290.        probably never come a time when  a  good  assembler-language pro-
  291.        grammer can't out-program a compiler.    Before  this  session is
  292.        over, I'll briefly mention some ways that we can do a  little op-
  293.        timization,  just  to  show you that we can indeed improve things
  294.        without too much trouble.  But remember, we're here to learn, not
  295.        to see how tight we can make  the  object  code.    For  now, and
  296.        really throughout  this  series  of  articles,  we'll  studiously
  297.        ignore optimization and  concentrate  on  getting  out  code that
  298.        works.
  299.  
  300.        Speaking of which: ours DOESN'T!  The code is _WRONG_!  As things
  301.        are working  now, the subtraction process subtracts D1 (which has
  302.        the FIRST argument in it) from D0 (which has the second).  That's
  303.        the wrong way, so we end up with the wrong  sign  for the result.
  304.        So let's fix up procedure Subtract with a  sign-changer,  so that
  305.        it readsA9A9
  306.  
  307.                                      - 5 -A*A*
  308.  
  309. PA A
  310.  
  311.  
  312.  
  313.  
  314.  
  315.        {-------------------------------------------------------------}
  316.        { Recognize and Translate a Subtract }
  317.  
  318.        procedure Subtract;
  319.        begin
  320.           Match('-');
  321.           Term;
  322.           EmitLn('SUB D1,D0');
  323.           EmitLn('NEG D0');
  324.        end;
  325.        {-------------------------------------------------------------}
  326.  
  327.  
  328.        Now  our  code  is even less efficient, but at least it gives the
  329.        right answer!  Unfortunately, the  rules that give the meaning of
  330.        math expressions require that the terms in an expression come out
  331.        in an inconvenient  order  for  us.    Again, this is just one of
  332.        those facts of life you learn to live with.   This  one will come
  333.        back to haunt us when we get to division.
  334.  
  335.        OK,  at this point we have a parser that can recognize the sum or
  336.        difference of two digits.    Earlier,  we  could only recognize a
  337.        single digit.  But  real  expressions can have either form (or an
  338.        infinity of others).  For kicks, go back and run the program with
  339.        the single input line '1'.
  340.  
  341.        Didn't work, did it?   And  why  should  it?    We  just finished
  342.        telling  our  parser  that the only kinds of expressions that are
  343.        legal are those  with  two  terms.    We  must  rewrite procedure
  344.        Expression to be a lot more broadminded, and this is where things
  345.        start to take the shape of a real parser.
  346.  
  347.  
  348.        GENERAL EXPRESSIONS
  349.  
  350.        In the  REAL  world,  an  expression  can  consist of one or more
  351.        terms, separated  by  "addops"  ('+'  or  '-').   In BNF, this is
  352.        written
  353.  
  354.                  <expression> ::= <term> [<addop> <term>]*
  355.  
  356.  
  357.        We  can  accomodate  this definition of an  expression  with  the
  358.        addition of a simple loop to procedure Expression:AQAQ
  359.  
  360.                                      - 6 -A*A*
  361.  
  362. PA A
  363.  
  364.  
  365.  
  366.  
  367.  
  368.        {---------------------------------------------------------------}
  369.        { Parse and Translate an Expression }
  370.  
  371.        procedure Expression;
  372.        begin
  373.           Term;
  374.           while Look in ['+', '-'] do begin
  375.              EmitLn('MOVE D0,D1');
  376.              case Look of
  377.               '+': Add;
  378.               '-': Subtract;
  379.              else Expected('Addop');
  380.              end;
  381.           end;
  382.        end;
  383.        {--------------------------------------------------------------}
  384.  
  385.  
  386.        NOW we're getting somewhere!   This version handles any number of
  387.        terms, and it only cost us two extra lines of code.  As we go on,
  388.        you'll discover that this is characteristic  of  top-down parsers
  389.        ... it only takes a few lines of code to accomodate extensions to
  390.        the  language.    That's  what  makes  our  incremental  approach
  391.        possible.  Notice, too, how well the code of procedure Expression
  392.        matches the BNF definition.   That, too, is characteristic of the
  393.        method.  As you get proficient in the approach, you'll  find that
  394.        you can turn BNF into parser code just about as  fast  as you can
  395.        type!
  396.  
  397.        OK, compile the new version of our parser, and give it a try.  As
  398.        usual,  verify  that  the  "compiler"   can   handle   any  legal
  399.        expression,  and  will  give a meaningful error  message  for  an
  400.        illegal one.  Neat, eh?  You might note that in our test version,
  401.        any error message comes  out  sort of buried in whatever code had
  402.        already been  generated. But remember, that's just because we are
  403.        using  the  CRT  as  our  "output  file"  for   this   series  of
  404.        experiments.  In a production version, the two  outputs  would be
  405.        separated ... one to the output file, and one to the screen.
  406.  
  407.  
  408.        USING THE STACK
  409.  
  410.        At  this  point  I'm going to  violate  my  rule  that  we  don't
  411.        introduce any complexity until  it's  absolutely  necessary, long
  412.        enough to point out a problem with the code we're generating.  As
  413.        things stand now, the parser  uses D0 for the "primary" register,
  414.        and D1 as  a place to store the partial sum.  That works fine for
  415.        now,  because  as  long as we deal with only the "addops" '+' and
  416.        '-', any new term can be added in as soon as it is found.  But in
  417.        general that isn't true.  Consider, for example, the expression
  418.  
  419.                       1+(2-(3+(4-5)))ABAB
  420.                                      - 7 -A*A*
  421.  
  422. PA A
  423.  
  424.  
  425.  
  426.  
  427.  
  428.        If we put the '1' in D1, where  do  we  put  the  '2'?    Since a
  429.        general expression can have any degree of complexity, we're going
  430.        to run out of registers fast!
  431.  
  432.        Fortunately,  there's  a  simple  solution.    Like  every modern
  433.        microprocessor, the 68000 has a stack, which is the perfect place
  434.        to save a variable number of items. So instead of moving the term
  435.        in D0 to  D1, let's just push it onto the stack.  For the benefit
  436.        of  those unfamiliar with 68000 assembler  language,  a  push  is
  437.        written
  438.  
  439.                       -(SP)
  440.  
  441.        and a pop,     (SP)+ .
  442.  
  443.  
  444.        So let's change the EmitLn in Expression to read:
  445.  
  446.                       EmitLn('MOVE D0,-(SP)');
  447.  
  448.        and the two lines in Add and Subtract to
  449.  
  450.                       EmitLn('ADD (SP)+,D0')
  451.  
  452.        and            EmitLn('SUB (SP)+,D0'),
  453.  
  454.        respectively.  Now try the parser again and make sure  we haven't
  455.        broken it.
  456.  
  457.        Once again, the generated code is less efficient than before, but
  458.        it's a necessary step, as you'll see.
  459.  
  460.  
  461.        MULTIPLICATION AND DIVISION
  462.  
  463.        Now let's get down to some REALLY serious business.  As  you  all
  464.        know,  there  are  other  math   operators   than   "addops"  ...
  465.        expressions can also have  multiply  and  divide operations.  You
  466.        also  know  that  there  is  an implied operator  PRECEDENCE,  or
  467.        hierarchy, associated with expressions, so that in  an expression
  468.        like
  469.  
  470.                            2 + 3 * 4,
  471.  
  472.        we know that we're supposed to multiply FIRST, then  add.    (See
  473.        why we needed the stack?)
  474.  
  475.        In the early days of compiler technology, people used some rather
  476.        complex techniques to insure that the  operator  precedence rules
  477.        were  obeyed.    It turns out,  though,  that  none  of  this  is
  478.        necessary ... the rules can be accommodated quite  nicely  by our
  479.        top-down  parsing technique.  Up till now,  the  only  form  that
  480.        we've considered for a term is that of a  single  decimal  digit.A6A6
  481.                                      - 8 -A*A*
  482.  
  483. PA A
  484.  
  485.  
  486.  
  487.  
  488.  
  489.        More generally, we  can  define  a  term as a PRODUCT of FACTORS;
  490.        i.e.,
  491.  
  492.                  <term> ::= <factor>  [ <mulop> <factor ]*
  493.  
  494.        What  is  a factor?  For now, it's what a term used to be  ...  a
  495.        single digit.
  496.  
  497.        Notice the symmetry: a  term  has the same form as an expression.
  498.        As a matter of fact, we can  add  to  our  parser  with  a little
  499.        judicious  copying and renaming.  But  to  avoid  confusion,  the
  500.        listing below is the complete set of parsing routines.  (Note the
  501.        way we handle the reversal of operands in Divide.)
  502.  
  503.  
  504.        {---------------------------------------------------------------}
  505.        { Parse and Translate a Math Factor }
  506.  
  507.        procedure Factor;
  508.        begin
  509.           EmitLn('MOVE #' + GetNum + ',D0')
  510.        end;
  511.  
  512.  
  513.        {--------------------------------------------------------------}
  514.        { Recognize and Translate a Multiply }
  515.  
  516.        procedure Multiply;
  517.        begin
  518.           Match('*');
  519.           Factor;
  520.           EmitLn('MULS (SP)+,D0');
  521.        end;
  522.  
  523.  
  524.        {-------------------------------------------------------------}
  525.        { Recognize and Translate a Divide }
  526.  
  527.        procedure Divide;
  528.        begin
  529.           Match('/');
  530.           Factor;
  531.           EmitLn('MOVE (SP)+,D1');
  532.           EmitLn('DIVS D1,D0');
  533.        end;AKAK
  534.  
  535.                                      - 9 -A*A*
  536.  
  537. PA A
  538.  
  539.  
  540.  
  541.  
  542.  
  543.        {---------------------------------------------------------------}
  544.        { Parse and Translate a Math Term }
  545.  
  546.        procedure Term;
  547.        begin
  548.           Factor;
  549.           while Look in ['*', '/'] do begin
  550.              EmitLn('MOVE D0,-(SP)');
  551.              case Look of
  552.               '*': Multiply;
  553.               '/': Divide;
  554.              else Expected('Mulop');
  555.              end;
  556.           end;
  557.        end;
  558.  
  559.  
  560.        {--------------------------------------------------------------}
  561.        { Recognize and Translate an Add }
  562.  
  563.        procedure Add;
  564.        begin
  565.           Match('+');
  566.           Term;
  567.           EmitLn('ADD (SP)+,D0');
  568.        end;
  569.  
  570.  
  571.        {-------------------------------------------------------------}
  572.        { Recognize and Translate a Subtract }
  573.  
  574.        procedure Subtract;
  575.        begin
  576.           Match('-');
  577.           Term;
  578.           EmitLn('SUB (SP)+,D0');
  579.           EmitLn('NEG D0');
  580.        end;ANAN
  581.  
  582.  
  583.                                     - 10 -A*A*
  584.  
  585. PA A
  586.  
  587.  
  588.  
  589.  
  590.  
  591.        {---------------------------------------------------------------}
  592.        { Parse and Translate an Expression }
  593.  
  594.        procedure Expression;
  595.        begin
  596.           Term;
  597.           while Look in ['+', '-'] do begin
  598.              EmitLn('MOVE D0,-(SP)');
  599.              case Look of
  600.               '+': Add;
  601.               '-': Subtract;
  602.              else Expected('Addop');
  603.              end;
  604.           end;
  605.        end;
  606.        {--------------------------------------------------------------}
  607.  
  608.  
  609.        Hot dog!  A NEARLY functional parser/translator, in only 55 lines
  610.        of Pascal!  The output is starting to look really useful,  if you
  611.        continue to overlook the inefficiency,  which  I  hope  you will.
  612.        Remember, we're not trying to produce tight code here.
  613.  
  614.  
  615.        PARENTHESES
  616.  
  617.        We  can  wrap  up this part of the parser with  the  addition  of
  618.        parentheses with  math expressions.  As you know, parentheses are
  619.        a  mechanism to force a desired operator  precedence.    So,  for
  620.        example, in the expression
  621.  
  622.                       2*(3+4) ,
  623.  
  624.        the parentheses force the addition  before  the  multiply.   Much
  625.        more importantly, though, parentheses  give  us  a  mechanism for
  626.        defining expressions of any degree of complexity, as in
  627.  
  628.                       (1+2)/((3+4)+(5-6))
  629.  
  630.        The  key  to  incorporating  parentheses  into our parser  is  to
  631.        realize that  no matter how complicated an expression enclosed by
  632.        parentheses may be,  to  the  rest  of  the world it looks like a
  633.        simple factor.  That is, one of the forms for a factor is:
  634.  
  635.                  <factor> ::= (<expression>)
  636.  
  637.        This is where the recursion comes in. An expression can contain a
  638.        factor which contains another expression which contains a factor,
  639.        etc., ad infinitum.
  640.  
  641.        Complicated or not, we can take care of this by adding just a few
  642.        lines of Pascal to procedure Factor:ABAB
  643.                                     - 11 -A*A*
  644.  
  645. PA A
  646.  
  647.  
  648.  
  649.  
  650.  
  651.        {---------------------------------------------------------------}
  652.        { Parse and Translate a Math Factor }
  653.  
  654.        procedure Expression; Forward;
  655.  
  656.        procedure Factor;
  657.        begin
  658.           if Look = '(' then begin
  659.              Match('(');
  660.              Expression;
  661.              Match(')');
  662.              end
  663.           else
  664.              EmitLn('MOVE #' + GetNum + ',D0');
  665.        end;
  666.        {--------------------------------------------------------------}
  667.  
  668.  
  669.        Note again how easily we can extend the parser, and how  well the
  670.        Pascal code matches the BNF syntax.
  671.  
  672.        As usual, compile the new version and make sure that it correctly
  673.        parses  legal sentences, and flags illegal  ones  with  an  error
  674.        message.
  675.  
  676.  
  677.        UNARY MINUS
  678.  
  679.        At  this  point,  we have a parser that can handle just about any
  680.        expression, right?  OK, try this input sentence:
  681.  
  682.                                 -1
  683.  
  684.        WOOPS!  It doesn't work, does it?   Procedure  Expression expects
  685.        everything to start with an integer, so it coughs up  the leading
  686.        minus  sign.  You'll find that +3 won't  work  either,  nor  will
  687.        something like
  688.  
  689.                            -(3-2) .
  690.  
  691.        There  are  a  couple of ways to fix the problem.    The  easiest
  692.        (although not necessarily the best)  way is to stick an imaginary
  693.        leading zero in  front  of  expressions  of this type, so that -3
  694.        becomes 0-3.  We can easily patch this into our  existing version
  695.        of Expression:AKAK
  696.  
  697.                                     - 12 -A*A*
  698.  
  699. PA A
  700.  
  701.  
  702.  
  703.  
  704.  
  705.        {---------------------------------------------------------------}
  706.        { Parse and Translate an Expression }
  707.  
  708.        procedure Expression;
  709.        begin
  710.           if IsAddop(Look) then
  711.              EmitLn('CLR D0')
  712.           else
  713.              Term;
  714.           while IsAddop(Look) do begin
  715.              EmitLn('MOVE D0,-(SP)');
  716.              case Look of
  717.               '+': Add;
  718.               '-': Subtract;
  719.              else Expected('Addop');
  720.              end;
  721.           end;
  722.        end;
  723.        {--------------------------------------------------------------}
  724.         
  725.  
  726.        I TOLD you that making changes  was  easy!   This time it cost us
  727.        only  three  new lines of Pascal.   Note  the  new  reference  to
  728.        function IsAddop.  Since the test for an addop appeared  twice, I
  729.        chose  to  embed  it in the new function.  The  form  of  IsAddop
  730.        should be apparent from that for IsAlpha.  Here it is:
  731.  
  732.  
  733.        {--------------------------------------------------------------}
  734.        { Recognize an Addop }
  735.  
  736.        function IsAddop(c: char): boolean;
  737.        begin
  738.           IsAddop := c in ['+', '-'];
  739.        end;
  740.        {--------------------------------------------------------------}
  741.  
  742.  
  743.        OK, make these changes to the program and recompile.   You should
  744.        also include IsAddop in your baseline copy of the cradle.   We'll
  745.        be needing  it  again  later.   Now try the input -1 again.  Wow!
  746.        The efficiency of the code is  pretty  poor ... six lines of code
  747.        just for loading a simple constant ... but at least it's correct.
  748.        Remember, we're not trying to replace Turbo Pascal here.
  749.  
  750.        At this point we're just about finished with the structure of our
  751.        expression parser.   This version of the program should correctly
  752.        parse and compile just about any expression you care to  throw at
  753.        it.    It's still limited in that  we  can  only  handle  factors
  754.        involving single decimal digits.    But I hope that by now you're
  755.        starting  to  get  the  message  that we can  accomodate  further
  756.        extensions  with  just  some  minor  changes to the parser.   You
  757.        probably won't be  surprised  to  hear  that a variable or even a
  758.        function call is just another kind of a factor.A*A*
  759.                                     - 13 -
  760.  
  761. PA A
  762.  
  763.  
  764.  
  765.  
  766.  
  767.        In  the next session, I'll show you just how easy it is to extend
  768.        our parser to take care of  these  things too, and I'll also show
  769.        you just  how easily we can accomodate multicharacter numbers and
  770.        variable names.  So you see,  we're  not  far at all from a truly
  771.        useful parser.
  772.  
  773.  
  774.        A WORD ABOUT OPTIMIZATION
  775.  
  776.        Earlier in this session, I promised to give you some hints  as to
  777.        how we can improve the quality of the generated code.  As I said,
  778.        the  production of tight code is not the  main  purpose  of  this
  779.        series of articles.  But you need to at least know that we aren't
  780.        just  wasting our time here ... that we  can  indeed  modify  the
  781.        parser further to  make  it produce better code, without throwing
  782.        away everything we've done to date.  As usual, it turns  out that
  783.        SOME optimization is not that difficult to do ... it simply takes
  784.        some extra code in the parser.
  785.  
  786.        There are two basic approaches we can take:
  787.  
  788.          o Try to fix up the code after it's generated
  789.  
  790.            This is  the concept of "peephole" optimization.  The general
  791.            idea it that we  know  what  combinations of instructions the
  792.            compiler  is  going  to generate, and we also know which ones
  793.            are pretty bad (such as the code for -1, above).    So all we
  794.            do  is  to   scan   the  produced  code,  looking  for  those
  795.            combinations, and replacing  them  by better ones.  It's sort
  796.            of   a   macro   expansion,   in   reverse,   and   a  fairly
  797.            straightforward  exercise  in   pattern-matching.   The  only
  798.            complication,  really, is that there may be  a  LOT  of  such
  799.            combinations to look for.  It's called  peephole optimization
  800.            simply because it only looks at a small group of instructions
  801.            at a time.  Peephole  optimization can have a dramatic effect
  802.            on  the  quality  of the code,  with  little  change  to  the
  803.            structure of the compiler  itself.   There is a price to pay,
  804.            though,  in  both  the  speed,   size, and complexity of  the
  805.            compiler.  Looking for all those combinations calls for a lot
  806.            of IF tests, each one of which is a source of error.  And, of
  807.            course, it takes time.
  808.  
  809.             In  the  classical  implementation  of a peephole optimizer,
  810.            it's done as a second pass to the compiler.  The  output code
  811.            is  written  to  disk,  and  then  the  optimizer  reads  and
  812.            processes the disk file again.  As a matter of fact,  you can
  813.            see that the optimizer could  even be a separate PROGRAM from
  814.            the compiler proper.  Since the optimizer only  looks  at the
  815.            code through a  small  "window"  of  instructions  (hence the
  816.            name), a better implementation would be to simply buffer up a
  817.            few lines of output, and scan the buffer after each EmitLn.
  818.  
  819.          o Try to generate better code in the first placeA6A6
  820.                                     - 14 -A*A*
  821.  
  822. PA A
  823.  
  824.  
  825.  
  826.  
  827.  
  828.            This approach calls for us to look for  special  cases BEFORE
  829.            we Emit them.  As a trivial example,  we  should  be  able to
  830.            identify a constant zero,  and  Emit a CLR instead of a load,
  831.            or even do nothing at all, as in an add of zero, for example.
  832.            Closer to home, if we had chosen to recognize the unary minus
  833.            in Factor  instead of in Expression, we could treat constants
  834.            like -1 as ordinary constants,  rather  then  generating them
  835.            from  positive  ones.   None of these things are difficult to
  836.            deal with ... they only add extra tests in the code, which is
  837.            why  I  haven't  included them in our program.  The way I see
  838.            it, once we get to the point that we have a working compiler,
  839.            generating useful code  that  executes, we can always go back
  840.            and tweak the thing to tighten up the code produced.   That's
  841.            why there are Release 2.0's in the world.
  842.  
  843.        There IS one more type  of  optimization  worth  mentioning, that
  844.        seems to promise pretty tight code without too much hassle.  It's
  845.        my "invention" in the  sense  that I haven't seen it suggested in
  846.        print anywhere, though I have  no  illusions  that  it's original
  847.        with me.
  848.  
  849.        This  is to avoid such a heavy use of the stack, by making better
  850.        use of the CPU registers.  Remember back when we were  doing only
  851.        addition  and  subtraction,  that we used registers  D0  and  D1,
  852.        rather than the stack?  It worked, because with  only  those  two
  853.        operations, the "stack" never needs more than two entries.
  854.  
  855.        Well,  the 68000 has eight data registers.  Why not use them as a
  856.        privately managed stack?  The key is to recognize  that,  at  any
  857.        point in its processing,  the  parser KNOWS how many items are on
  858.        the  stack, so it can indeed manage it properly.  We can define a
  859.        private "stack pointer" that keeps  track  of  which  stack level
  860.        we're at, and addresses the  corresponding  register.   Procedure
  861.        Factor,  for  example,  would  not  cause data to be loaded  into
  862.        register  D0,  but   into  whatever  the  current  "top-of-stack"
  863.        register happened to be.
  864.  
  865.        What we're doing in effect is to replace the CPU's RAM stack with
  866.        a  locally  managed  stack  made  up  of  registers.    For  most
  867.        expressions, the stack level  will  never  exceed eight, so we'll
  868.        get pretty good code out.  Of course, we also  have  to deal with
  869.        those  odd cases where the stack level  DOES  exceed  eight,  but
  870.        that's no problem  either.    We  simply let the stack spill over
  871.        into the CPU  stack.    For  levels  beyond eight, the code is no
  872.        worse  than  what  we're generating now, and for levels less than
  873.        eight, it's considerably better.
  874.  
  875.        For the record, I  have  implemented  this  concept, just to make
  876.        sure  it  works  before  I  mentioned  it to you.  It does.    In
  877.        practice, it turns out that you can't really use all eight levels
  878.        ... you need at least one register free to  reverse  the  operand
  879.        order for division  (sure  wish  the  68000 had an XTHL, like the
  880.        8080!).  For expressions  that  include  function calls, we wouldA6A6
  881.                                     - 15 -A*A*
  882.  
  883. PA A
  884.  
  885.  
  886.  
  887.  
  888.  
  889.        also need a register reserved for them. Still, there  is  a  nice
  890.        improvement in code size for most expressions.
  891.  
  892.        So, you see, getting  better  code  isn't  that difficult, but it
  893.        does add complexity to the our translator ...  complexity  we can
  894.        do without at this point.  For that reason,  I  STRONGLY  suggest
  895.        that we continue to ignore efficiency issues for the rest of this
  896.        series,  secure  in  the knowledge that we can indeed improve the
  897.        code quality without throwing away what we've done.
  898.  
  899.        Next lesson, I'll show you how to deal with variables factors and
  900.        function calls.  I'll also show you just how easy it is to handle
  901.        multicharacter tokens and embedded white space.
  902.  
  903.        *****************************************************************
  904.        *                                                               *
  905.        *                        COPYRIGHT NOTICE                       *
  906.        *                                                               *
  907.        *   Copyright 1988 (C) Jack W. Crenshaw. All rights reserved.   *
  908.        *                                                               *
  909.        *****************************************************************AIAI
  910.  
  911.  
  912.  
  913.  
  914.  
  915.                                     - 16 -A*A*
  916. @