home *** CD-ROM | disk | FTP | other *** search
/ Programmer 7500 / MAX_PROGRAMMERS.iso / INFO / TURBOPAS / TUTORPAS.ARC / TUTOR6.DOC < prev    next >
Encoding:
Text File  |  1988-09-04  |  43.4 KB  |  1,247 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.                                  31 August 1988
  36.  
  37.  
  38.                           Part VI: BOOLEAN 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 Part V of this series,  we  took a look at control constructs,
  87.        and developed parsing  routines  to  translate  them  into object
  88.        code.    We  ended  up  with  a  nice,  relatively  rich  set  of
  89.        constructs.
  90.  
  91.        As we left  the  parser,  though,  there  was one big hole in our
  92.        capabilities:  we  did  not  address  the  issue  of  the  branch
  93.        condition.  To fill the void,  I  introduced to you a dummy parse
  94.        routine called Condition, which only served as a place-keeper for
  95.        the real thing.
  96.  
  97.        One of the things we'll do in this session is  to  plug that hole
  98.        by expanding Condition into a true parser/translator.
  99.  
  100.  
  101.        THE PLAN
  102.  
  103.        We're going to  approach  this installment a bit differently than
  104.        any of the others.    In those other installments, we started out
  105.        immediately with experiments  using the Pascal compiler, building
  106.        up the parsers from  very  rudimentary  beginnings to their final
  107.        forms, without spending much time in planning  beforehand. That's
  108.        called coding without specs, and it's usually frowned  upon.   We
  109.        could get away with it before because the rules of arithmetic are
  110.        pretty well established ...  we  know what a '+' sign is supposed
  111.        to mean without having to discuss it at length.  The same is true
  112.        for branches and  loops.    But  the  ways  in  which programming
  113.        languages  implement  logic  vary quite a bit  from  language  to
  114.        language.  So before we begin serious coding,  we'd  better first
  115.        make up our minds what it is we want.  And the way to do  that is
  116.        at the level of the BNF syntax rules (the GRAMMAR).
  117.  
  118.  
  119.        THE GRAMMAR
  120.  
  121.        For some time  now,  we've been implementing BNF syntax equations
  122.        for arithmetic expressions, without  ever  actually  writing them
  123.        down all in one place.  It's time that we did so.  They are:
  124.  
  125.  
  126.             <expression> ::= <unary op> <term> [<addop> <term>]*
  127.             <term>       ::= <factor> [<mulop> factor]*
  128.             <factor>     ::= <integer> | <variable> | ( <expression> )A*A*
  129.                                      - 2 -
  130.  
  131. PA A
  132.  
  133.  
  134.  
  135.  
  136.  
  137.        (Remember, the nice thing about  this grammar is that it enforces
  138.        the operator precedence hierarchy  that  we  normally  expect for
  139.        algebra.)
  140.  
  141.        Actually,  while we're on the subject, I'd  like  to  amend  this
  142.        grammar a bit right now.   The  way we've handled the unary minus
  143.        is  a  bit  awkward.  I've found that it's better  to  write  the
  144.        grammar this way:
  145.  
  146.  
  147.          <expression>    ::= <term> [<addop> <term>]*
  148.          <term>          ::= <signed factor> [<mulop> factor]*
  149.          <signed factor> ::= [<addop>] <factor>
  150.          <factor>        ::= <integer> | <variable> | (<expression>)
  151.  
  152.  
  153.        This puts the job of handling the unary minus onto  Factor, which
  154.        is where it really belongs.
  155.  
  156.        This  doesn't  mean  that  you  have  to  go  back and recode the
  157.        programs you've already written, although you're free to do so if
  158.        you like.  But I will be using the new syntax from now on.
  159.  
  160.        Now, it probably won't come as  a  shock  to you to learn that we
  161.        can define an analogous grammar for Boolean algebra.    A typical
  162.        set or rules is:
  163.  
  164.  
  165.         <b-expression>::= <b-term> [<orop> <b-term>]*
  166.         <b-term>      ::= <not-factor> [AND <not-factor>]*
  167.         <not-factor>  ::= [NOT] <b-factor>
  168.         <b-factor>    ::= <b-literal> | <b-variable> | (<b-expression>)
  169.  
  170.  
  171.        Notice that in this  grammar,  the  operator  AND is analogous to
  172.        '*',  and  OR  (and exclusive OR) to '+'.  The  NOT  operator  is
  173.        analogous to a unary  minus.    This  hierarchy is not absolutely
  174.        standard ...  some  languages,  notably  Ada,  treat  all logical
  175.        operators  as  having  the same precedence level ... but it seems
  176.        natural.
  177.  
  178.        Notice also the slight difference between the way the NOT and the
  179.        unary  minus  are  handled.    In  algebra,  the unary  minus  is
  180.        considered to go with the whole term, and so  never  appears  but
  181.        once in a given term. So an expression like
  182.  
  183.                            a * -b
  184.  
  185.        or worse yet,
  186.                            a - -b
  187.  
  188.        is not allowed.  In Boolean algebra, though, the expression
  189.  
  190.                            a AND NOT bA*A*
  191.                                      - 3 -
  192.  
  193. PA A
  194.  
  195.  
  196.  
  197.  
  198.  
  199.        makes perfect sense, and the syntax shown allows for that.
  200.  
  201.  
  202.        RELOPS
  203.  
  204.        OK, assuming that you're willing to accept the grammar I've shown
  205.        here,  we  now  have syntax rules for both arithmetic and Boolean
  206.        algebra.    The  sticky part comes in when we have to combine the
  207.        two.  Why do we have to do that?  Well, the whole subject came up
  208.        because of the  need  to  process  the  "predicates" (conditions)
  209.        associated with control statements such as the IF.  The predicate
  210.        is required to have a Boolean value; that is, it must evaluate to
  211.        either TRUE or FALSE.  The branch is  then  taken  or  not taken,
  212.        depending  on  that  value.  What we expect to see  going  on  in
  213.        procedure  Condition,  then,  is  the  evaluation  of  a  Boolean
  214.        expression.
  215.  
  216.        But there's more to it than that.  A pure Boolean  expression can
  217.        indeed be the predicate of a control statement ... things like
  218.  
  219.  
  220.                  IF a AND NOT b THEN ....
  221.  
  222.  
  223.        But more often, we see Boolean algebra show up in such things as
  224.  
  225.  
  226.             IF (x >= 0) and (x <= 100) THEN ...
  227.  
  228.  
  229.        Here,  the  two  terms in parens are Boolean expressions, but the
  230.        individual terms being compared:  x,  0, and 100,  are NUMERIC in
  231.        nature.  The RELATIONAL OPERATORS >= and <= are the  catalysts by
  232.        which the  Boolean  and  the  arithmetic  ingredients  get merged
  233.        together.
  234.  
  235.        Now,  in the example above, the terms  being  compared  are  just
  236.        that:  terms.    However,  in  general  each  side  can be a math
  237.        expression.  So we can define a RELATION to be:
  238.  
  239.  
  240.             <relation> ::= <expression> <relop> <expression>  ,
  241.  
  242.  
  243.        where  the  expressions  we're  talking  about here are  the  old
  244.        numeric type, and the relops are any of the usual symbols
  245.  
  246.  
  247.                       =, <> (or !=), <, >, <=, and >=
  248.  
  249.  
  250.        If you think about it a  bit,  you'll agree that, since this kind
  251.        of predicate has a single Boolean value, TRUE or  FALSE,  as  itsA6A6
  252.                                      - 4 -A*A*
  253.  
  254. PA A
  255.  
  256.  
  257.  
  258.  
  259.  
  260.        result, it is  really  just  another  kind  of factor.  So we can
  261.        expand the definition of a Boolean factor above to read:
  262.  
  263.  
  264.            <b-factor> ::=    <b-literal>
  265.                            | <b-variable>
  266.                            | (<b-expression>)
  267.                            | <relation>
  268.  
  269.  
  270.        THAT's the connection!  The relops and the  relation  they define
  271.        serve to wed the two kinds of algebra.  It  is  worth noting that
  272.        this implies a hierarchy  where  the  arithmetic expression has a
  273.        HIGHER precedence that  a  Boolean factor, and therefore than all
  274.        the  Boolean operators.    If you write out the precedence levels
  275.        for all the operators, you arrive at the following list:
  276.  
  277.  
  278.                  Level   Syntax Element     Operator
  279.  
  280.                  0       factor             literal, variable
  281.                  1       signed factor      unary minus
  282.                  2       term               *, /
  283.                  3       expression         +, -
  284.                  4       b-factor           literal, variable, relop
  285.                  5       not-factor         NOT
  286.                  6       b-term             AND
  287.                  7       b-expression       OR, XOR
  288.  
  289.  
  290.        If  we're willing to accept that  many  precedence  levels,  this
  291.        grammar seems reasonable.  Unfortunately,  it  won't  work!   The
  292.        grammar may be great in theory,  but  it's  no good at all in the
  293.        practice of a top-down parser.  To see the problem,  consider the
  294.        code fragment:
  295.  
  296.  
  297.             IF ((((((A + B + C) < 0 ) AND ....
  298.  
  299.  
  300.        When the parser is parsing this code, it knows after it  sees the
  301.        IF token that a Boolean expression is supposed to be next.  So it
  302.        can set up to begin evaluating such an expression.  But the first
  303.        expression in the example is an ARITHMETIC expression, A + B + C.
  304.        What's worse, at the point that the parser has read this  much of
  305.        the input line:
  306.  
  307.  
  308.             IF ((((((A   ,
  309.  
  310.  
  311.        it  still has no way of knowing which  kind  of  expression  it's
  312.        dealing  with.  That won't do, because  we  must  have  different
  313.        recognizers  for the two cases.  The  situation  can  be  handledA*A*
  314.                                      - 5 -
  315.  
  316. PA A
  317.  
  318.  
  319.  
  320.  
  321.  
  322.        without  changing  any  of  our  definitions, but only  if  we're
  323.        willing to accept an arbitrary amount of backtracking to work our
  324.        way out of bad guesses.  No compiler  writer  in  his  right mind
  325.        would agree to that.
  326.  
  327.        What's going  on  here  is  that  the  beauty and elegance of BNF
  328.        grammar  has  met  face  to  face with the realities of  compiler
  329.        technology.
  330.  
  331.        To  deal  with  this situation, compiler writers have had to make
  332.        compromises  so  that  a  single  parser can handle  the  grammar
  333.        without backtracking.
  334.  
  335.  
  336.        FIXING THE GRAMMAR
  337.  
  338.        The  problem  that  we've  encountered  comes   up   because  our
  339.        definitions of both arithmetic and Boolean factors permit the use
  340.        of   parenthesized  expressions.    Since  the  definitions   are
  341.        recursive,  we  can  end  up  with  any  number   of   levels  of
  342.        parentheses, and the  parser  can't know which kind of expression
  343.        it's dealing with.
  344.  
  345.        The  solution is simple, although it  ends  up  causing  profound
  346.        changes to our  grammar.    We  can only allow parentheses in one
  347.        kind  of factor.  The way to do  that  varies  considerably  from
  348.        language  to  language.  This is one  place  where  there  is  NO
  349.        agreement or convention to help us.
  350.  
  351.        When Niklaus Wirth designed Pascal, the desire was  to  limit the
  352.        number of levels of precedence (fewer parse routines, after all).
  353.        So the OR  and  exclusive  OR  operators are treated just like an
  354.        Addop  and  processed   at   the  level  of  a  math  expression.
  355.        Similarly, the AND is  treated  like  a  Mulop and processed with
  356.        Term.  The precedence levels are
  357.  
  358.  
  359.                  Level   Syntax Element     Operator
  360.  
  361.                  0       factor             literal, variable
  362.                  1       signed factor      unary minus, NOT
  363.                  2       term               *, /, AND
  364.                  3       expression         +, -, OR
  365.  
  366.  
  367.        Notice that there is only ONE set of syntax  rules,  applying  to
  368.        both  kinds  of  operators.    According to this  grammar,  then,
  369.        expressions like
  370.  
  371.             x + (y AND NOT z) DIV 3
  372.  
  373.        are perfectly legal.  And, in  fact,  they  ARE ... as far as the
  374.        parser  is  concerned.    Pascal  doesn't  allow  the  mixing  of
  375.        arithmetic and Boolean variables, and things like this are caughtA*A*
  376.                                      - 6 -
  377.  
  378. PA A
  379.  
  380.  
  381.  
  382.  
  383.  
  384.        at the SEMANTIC level, when it comes time to  generate  code  for
  385.        them, rather than at the syntax level.
  386.  
  387.        The authors of C took  a  diametrically  opposite  approach: they
  388.        treat the operators as  different,  and  have something much more
  389.        akin  to our seven levels of precedence.  In fact, in C there are
  390.        no fewer than 17 levels!  That's because C also has the operators
  391.        '=', '+=' and its kin, '<<', '>>', '++', '--', etc.   Ironically,
  392.        although in C the  arithmetic  and  Boolean operators are treated
  393.        separately, the variables are  NOT  ...  there  are no Boolean or
  394.        logical variables in  C,  so  a  Boolean  test can be made on any
  395.        integer value.
  396.  
  397.        We'll do something that's  sort  of  in-between.   I'm tempted to
  398.        stick  mostly  with  the Pascal approach, since  that  seems  the
  399.        simplest from an implementation point  of view, but it results in
  400.        some funnies that I never liked very much, such as the fact that,
  401.        in the expression
  402.  
  403.             IF (c >= 'A') and (c <= 'Z') then ...
  404.  
  405.        the  parens  above  are REQUIRED.  I never understood why before,
  406.        and  neither my compiler nor any human  ever  explained  it  very
  407.        well, either.  But now, we  can  all see that the 'and' operator,
  408.        having the precedence of a multiply, has a higher  one  than  the
  409.        relational operators, so without  the  parens  the  expression is
  410.        equivalent to
  411.  
  412.             IF c >= ('A' and c) <= 'Z' then
  413.  
  414.        which doesn't make sense.
  415.  
  416.        In  any  case,  I've  elected  to  separate  the  operators  into
  417.        different levels, although not as many as in C.
  418.  
  419.  
  420.         <b-expression> ::= <b-term> [<orop> <b-term>]*
  421.         <b-term>       ::= <not-factor> [AND <not-factor>]*
  422.         <not-factor>   ::= [NOT] <b-factor>
  423.         <b-factor>     ::= <b-literal> | <b-variable> | <relation>
  424.         <relation>     ::= | <expression> [<relop> <expression]
  425.         <expression>   ::= <term> [<addop> <term>]*
  426.         <term>         ::= <signed factor> [<mulop> factor]*
  427.         <signed factor>::= [<addop>] <factor>
  428.         <factor>       ::= <integer> | <variable> | (<b-expression>)
  429.  
  430.  
  431.        This grammar  results  in  the  same  set  of seven levels that I
  432.        showed earlier.  Really, it's almost the same grammar ...  I just
  433.        removed the option of parenthesized b-expressions  as  a possible
  434.        b-factor, and added the relation as a legal form of b-factor.
  435.  
  436.        There is one subtle but crucial difference, which  is  what makes
  437.        the  whole  thing  work.    Notice  the  square brackets  in  theA*A*
  438.                                      - 7 -
  439.  
  440. PA A
  441.  
  442.  
  443.  
  444.  
  445.  
  446.        definition  of a relation.  This means that  the  relop  and  the
  447.        second expression are OPTIONAL.
  448.  
  449.        A strange consequence of this grammar (and one shared  by  C)  is
  450.        that EVERY expression  is  potentially a Boolean expression.  The
  451.        parser will always be looking  for a Boolean expression, but will
  452.        "settle" for an arithmetic one.  To be honest,  that's  going  to
  453.        slow down the parser, because it has to wade through  more layers
  454.        of procedure calls.  That's  one reason why Pascal compilers tend
  455.        to compile faster than C compilers.  If it's raw speed  you want,
  456.        stick with the Pascal syntax.
  457.  
  458.  
  459.        THE PARSER
  460.  
  461.        Now that we've gotten through the decision-making process, we can
  462.        press on with development of a parser.  You've done this  with me
  463.        several times now, so you know  the  drill: we begin with a fresh
  464.        copy of the cradle, and begin  adding  procedures one by one.  So
  465.        let's do it.
  466.  
  467.        We begin, as we did in the arithmetic case, by dealing  only with
  468.        Boolean literals rather than variables.  This gives us a new kind
  469.        of input token, so we're also going to need a new recognizer, and
  470.        a  new procedure to read instances of that  token  type.    Let's
  471.        start by defining the two new procedures:
  472.  
  473.  
  474.        {--------------------------------------------------------------}
  475.        { Recognize a Boolean Literal }
  476.  
  477.        function IsBoolean(c: char): Boolean;
  478.        begin
  479.           IsBoolean := UpCase(c) in ['T', 'F'];
  480.        end;
  481.  
  482.  
  483.        {--------------------------------------------------------------}
  484.        { Get a Boolean Literal }
  485.  
  486.        function GetBoolean: Boolean;
  487.        var c: char;
  488.        begin
  489.           if not IsBoolean(Look) then Expected('Boolean Literal');
  490.           GetBoolean := UpCase(Look) = 'T';
  491.           GetChar;
  492.        end;
  493.        {--------------------------------------------------------------}
  494.  
  495.  
  496.        Type  these routines into your program.  You  can  test  them  by
  497.        adding into the main program the print statementABAB
  498.                                      - 8 -A*A*
  499.  
  500. PA A
  501.  
  502.  
  503.  
  504.  
  505.  
  506.           WriteLn(GetBoolean);
  507.  
  508.  
  509.        OK, compile the program and test it.   As  usual,  it's  not very
  510.        impressive so far, but it soon will be.
  511.  
  512.        Now, when we were dealing with numeric data we had to  arrange to
  513.        generate code to load the values into D0.  We need to do the same
  514.        for Boolean data.   The  usual way to encode Boolean variables is
  515.        to let 0 stand for FALSE,  and  some  other value for TRUE.  Many
  516.        languages, such as C, use an  integer  1  to represent it.  But I
  517.        prefer FFFF hex  (or  -1),  because  a bitwise NOT also becomes a
  518.        Boolean  NOT.  So now we need to emit the right assembler code to
  519.        load  those  values.    The  first cut at the Boolean  expression
  520.        parser (BoolExpression, of course) is:
  521.  
  522.  
  523.        {---------------------------------------------------------------}
  524.        { Parse and Translate a Boolean Expression }
  525.  
  526.        procedure BoolExpression;
  527.        begin
  528.           if not IsBoolean(Look) then Expected('Boolean Literal');
  529.           if GetBoolean then
  530.              EmitLn('MOVE #-1,D0')
  531.           else
  532.              EmitLn('CLR D0');
  533.        end;
  534.        {---------------------------------------------------------------}
  535.  
  536.  
  537.        Add  this procedure to your parser, and call  it  from  the  main
  538.        program (replacing the  print  statement you had just put there).
  539.        As you  can  see,  we  still don't have much of a parser, but the
  540.        output code is starting to look more realistic.
  541.  
  542.        Next, of course, we have to expand the definition  of  a  Boolean
  543.        expression.  We already have the BNF rule:
  544.  
  545.  
  546.         <b-expression> ::= <b-term> [<orop> <b-term>]*
  547.  
  548.  
  549.        I prefer the Pascal versions of the "orops",  OR  and  XOR.   But
  550.        since we are keeping to single-character tokens here, I'll encode
  551.        those with '|' and  '~'.  The  next  version of BoolExpression is
  552.        almost a direct copy of the arithmetic procedure Expression:A?A?
  553.  
  554.                                      - 9 -A*A*
  555.  
  556. PA A
  557.  
  558.  
  559.  
  560.  
  561.  
  562.        {--------------------------------------------------------------}
  563.        { Recognize and Translate a Boolean OR }
  564.  
  565.        procedure BoolOr;
  566.        begin
  567.           Match('|');
  568.           BoolTerm;
  569.           EmitLn('OR (SP)+,D0');
  570.        end;
  571.  
  572.  
  573.        {--------------------------------------------------------------}
  574.        { Recognize and Translate an Exclusive Or }
  575.  
  576.        procedure BoolXor;
  577.        begin
  578.           Match('~');
  579.           BoolTerm;
  580.           EmitLn('EOR (SP)+,D0');
  581.        end;
  582.  
  583.  
  584.        {---------------------------------------------------------------}
  585.        { Parse and Translate a Boolean Expression }
  586.  
  587.        procedure BoolExpression;
  588.        begin
  589.           BoolTerm;
  590.           while IsOrOp(Look) do begin
  591.              EmitLn('MOVE D0,-(SP)');
  592.              case Look of
  593.               '|': BoolOr;
  594.               '~': BoolXor;
  595.              end;
  596.           end;
  597.        end;
  598.        {---------------------------------------------------------------}
  599.  
  600.  
  601.        Note the new recognizer  IsOrOp,  which is also a copy, this time
  602.        of IsAddOp:
  603.  
  604.  
  605.        {--------------------------------------------------------------}
  606.        { Recognize a Boolean Orop }
  607.  
  608.        function IsOrop(c: char): Boolean;
  609.        begin
  610.           IsOrop := c in ['|', '~'];
  611.        end;
  612.        {--------------------------------------------------------------}ANAN
  613.                                     - 10 -A*A*
  614.  
  615. PA A
  616.  
  617.  
  618.  
  619.  
  620.  
  621.        OK, rename the old  version  of  BoolExpression to BoolTerm, then
  622.        enter  the  code  above.  Compile and test this version.  At this
  623.        point, the  output  code  is  starting  to  look pretty good.  Of
  624.        course, it doesn't make much sense to do a lot of Boolean algebra
  625.        on  constant values, but we'll soon be  expanding  the  types  of
  626.        Booleans we deal with.
  627.  
  628.        You've  probably  already  guessed  what  the next step  is:  The
  629.        Boolean version of Term.
  630.  
  631.        Rename the current procedure BoolTerm to NotFactor, and enter the
  632.        following new version of BoolTerm.  Note that is is  much simpler
  633.        than  the  numeric  version,  since  there  is  no equivalent  of
  634.        division.
  635.  
  636.  
  637.        {---------------------------------------------------------------}
  638.        { Parse and Translate a Boolean Term }
  639.  
  640.        procedure BoolTerm;
  641.        begin
  642.           NotFactor;
  643.           while Look = '&' do begin
  644.              EmitLn('MOVE D0,-(SP)');
  645.              Match('&');
  646.              NotFactor;
  647.              EmitLn('AND (SP)+,D0');
  648.           end;
  649.        end;
  650.        {--------------------------------------------------------------}
  651.  
  652.  
  653.        Now,  we're  almost  home.  We are  translating  complex  Boolean
  654.        expressions, although only for constant values.  The next step is
  655.        to allow for the NOT.  Write the following procedure:
  656.  
  657.  
  658.        {--------------------------------------------------------------}
  659.        { Parse and Translate a Boolean Factor with NOT }
  660.  
  661.        procedure NotFactor;
  662.        begin
  663.           if Look = '!' then begin
  664.              Match('!');
  665.              BoolFactor;
  666.              EmitLn('EOR #-1,D0');
  667.              end
  668.           else
  669.              BoolFactor;
  670.        end;
  671.        {--------------------------------------------------------------}ANAN
  672.                                     - 11 -A*A*
  673.  
  674. PA A
  675.  
  676.  
  677.  
  678.  
  679.  
  680.        And  rename  the  earlier procedure to BoolFactor.  Now try that.
  681.        At this point  the  parser  should  be able to handle any Boolean
  682.        expression you care to throw at it.  Does it?  Does it trap badly
  683.        formed expressions?
  684.  
  685.        If you've  been  following  what  we  did  in the parser for math
  686.        expressions, you know  that  what  we  did next was to expand the
  687.        definition of a factor to include variables and parens.  We don't
  688.        have  to do that for the Boolean  factor,  because  those  little
  689.        items get taken care of by the next step.  It  takes  just  a one
  690.        line addition to BoolFactor to take care of relations:
  691.  
  692.  
  693.        {--------------------------------------------------------------}
  694.        { Parse and Translate a Boolean Factor }
  695.  
  696.        procedure BoolFactor;
  697.        begin
  698.           if IsBoolean(Look) then
  699.              if GetBoolean then
  700.                 EmitLn('MOVE #-1,D0')
  701.              else
  702.                 EmitLn('CLR D0')
  703.              else Relation;
  704.        end;
  705.        {--------------------------------------------------------------}
  706.  
  707.  
  708.        You  might be wondering when I'm going  to  provide  for  Boolean
  709.        variables and parenthesized Boolean expressions.  The  answer is,
  710.        I'm NOT!   Remember,  we  took  those out of the grammar earlier.
  711.        Right now all I'm  doing  is  encoding  the grammar we've already
  712.        agreed  upon.    The compiler itself can't  tell  the  difference
  713.        between a Boolean variable  or  expression  and an arithmetic one
  714.        ... all of those will be handled by Relation, either way.
  715.  
  716.  
  717.        Of course, it would help to have some code for Relation.  I don't
  718.        feel comfortable, though,  adding  any  more  code  without first
  719.        checking out what we already have.  So for now let's just write a
  720.        dummy  version  of  Relation  that  does nothing except  eat  the
  721.        current character, and write a little message:
  722.  
  723.  
  724.        {---------------------------------------------------------------}
  725.        { Parse and Translate a Relation }
  726.  
  727.        procedure Relation;
  728.        begin
  729.           WriteLn('<Relation>');
  730.           GetChar;
  731.        end;
  732.        {--------------------------------------------------------------}A6A6
  733.                                     - 12 -A*A*
  734.  
  735. PA A
  736.  
  737.  
  738.  
  739.  
  740.  
  741.        OK, key  in  this  code  and  give  it a try.  All the old things
  742.        should still work ... you should be able to generate the code for
  743.        ANDs, ORs, and  NOTs.    In  addition, if you type any alphabetic
  744.        character you should get a little <Relation>  place-holder, where
  745.        a  Boolean factor should be.  Did you get that?  Fine, then let's
  746.        move on to the full-blown version of Relation.
  747.  
  748.        To  get  that,  though, there is a bit of groundwork that we must
  749.        lay first.  Recall that a relation has the form
  750.  
  751.  
  752.         <relation>     ::= | <expression> [<relop> <expression]
  753.  
  754.  
  755.        Since  we have a new kind of operator, we're also going to need a
  756.        new Boolean function to  recognize  it.    That function is shown
  757.        below.  Because of the single-character limitation,  I'm sticking
  758.        to the four operators  that  can be encoded with such a character
  759.        (the "not equals" is encoded by '#').
  760.  
  761.  
  762.        {--------------------------------------------------------------}
  763.        { Recognize a Relop }
  764.  
  765.        function IsRelop(c: char): Boolean;
  766.        begin
  767.           IsRelop := c in ['=', '#', '<', '>'];
  768.        end;
  769.        {--------------------------------------------------------------}
  770.  
  771.  
  772.        Now, recall  that  we're  using  a zero or a -1 in register D0 to
  773.        represent  a Boolean value, and also  that  the  loop  constructs
  774.        expect the flags to be set to correspond.   In  implementing  all
  775.        this on the 68000, things get a a little bit tricky.
  776.  
  777.        Since the loop constructs operate only on the flags, it  would be
  778.        nice (and also quite  efficient)  just to set up those flags, and
  779.        not load  anything  into  D0  at all.  This would be fine for the
  780.        loops  and  branches,  but remember that the relation can be used
  781.        ANYWHERE a Boolean factor could be  used.   We may be storing its
  782.        result to a Boolean variable.  Since we can't know at  this point
  783.        how the result is going to be used, we must allow for BOTH cases.
  784.  
  785.        Comparing numeric data  is  easy  enough  ...  the  68000  has an
  786.        operation  for  that ... but it sets  the  flags,  not  a  value.
  787.        What's more,  the  flags  will  always  be  set the same (zero if
  788.        equal, etc.), while we need the zero flag set differently for the
  789.        each of the different relops.
  790.  
  791.        The solution is found in the 68000 instruction Scc, which  sets a
  792.        byte value to 0000 or FFFF (funny how that works!) depending upon
  793.        the  result  of  the  specified   condition.    If  we  make  the
  794.        destination byte to be D0, we get the Boolean value needed.A*A*
  795.                                     - 13 -
  796.  
  797. PA A
  798.  
  799.  
  800.  
  801.  
  802.  
  803.        Unfortunately,  there's one  final  complication:  unlike  almost
  804.        every other instruction in the 68000 set, Scc does NOT  reset the
  805.        condition flags to match the data being stored.  So we have to do
  806.        one last step, which is to test D0 and set the flags to match it.
  807.        It must seem to be a trip around the moon to get what we want: we
  808.        first perform the test, then test the flags to set data  into D0,
  809.        then test D0 to set the flags again.  It  is  sort of roundabout,
  810.        but it's the most straightforward way to get the flags right, and
  811.        after all it's only a couple of instructions.
  812.  
  813.        I  might  mention  here that this area is, in my opinion, the one
  814.        that represents the biggest difference between the  efficiency of
  815.        hand-coded assembler language and  compiler-generated  code.   We
  816.        have  seen  already  that  we  lose   efficiency   in  arithmetic
  817.        operations, although later I plan to show you how to improve that
  818.        a  bit.    We've also seen that the control constructs themselves
  819.        can be done quite efficiently  ... it's usually very difficult to
  820.        improve  on  the  code generated for an  IF  or  a  WHILE.    But
  821.        virtually every compiler I've ever seen generates  terrible code,
  822.        compared to assembler, for the computation of a Boolean function,
  823.        and particularly for relations.    The  reason  is just what I've
  824.        hinted at above.  When I'm writing code in assembler, I  go ahead
  825.        and perform the test the most convenient way I can, and  then set
  826.        up the branch so that it goes the way it should.    In  effect, I
  827.        "tailor"  every  branch  to the situation.  The compiler can't do
  828.        that (practically), and it also can't know that we don't  want to
  829.        store the result of the test as a Boolean variable.    So it must
  830.        generate  the  code  in a very strict order, and it often ends up
  831.        loading  the  result  as  a  Boolean  that  never gets  used  for
  832.        anything.
  833.  
  834.        In  any  case,  we're now ready to look at the code for Relation.
  835.        It's shown below with its companion procedures:
  836.  
  837.  
  838.        {---------------------------------------------------------------}
  839.        { Recognize and Translate a Relational "Equals" }
  840.  
  841.        procedure Equals;
  842.        begin
  843.           Match('=');
  844.           Expression;
  845.           EmitLn('CMP (SP)+,D0');
  846.           EmitLn('SEQ D0');
  847.        end;AKAK
  848.  
  849.                                     - 14 -A*A*
  850.  
  851. PA A
  852.  
  853.  
  854.  
  855.  
  856.  
  857.        {---------------------------------------------------------------}
  858.        { Recognize and Translate a Relational "Not Equals" }
  859.  
  860.        procedure NotEquals;
  861.        begin
  862.           Match('#');
  863.           Expression;
  864.           EmitLn('CMP (SP)+,D0');
  865.           EmitLn('SNE D0');
  866.        end;
  867.  
  868.  
  869.        {---------------------------------------------------------------}
  870.        { Recognize and Translate a Relational "Less Than" }
  871.  
  872.        procedure Less;
  873.        begin
  874.           Match('<');
  875.           Expression;
  876.           EmitLn('CMP (SP)+,D0');
  877.           EmitLn('SGE D0');
  878.        end;
  879.  
  880.  
  881.        {---------------------------------------------------------------}
  882.        { Recognize and Translate a Relational "Greater Than" }
  883.  
  884.        procedure Greater;
  885.        begin
  886.           Match('>');
  887.           Expression;
  888.           EmitLn('CMP (SP)+,D0');
  889.           EmitLn('SLE D0');
  890.        end;
  891.  
  892.  
  893.        {---------------------------------------------------------------}
  894.        { Parse and Translate a Relation }
  895.  
  896.        procedure Relation;
  897.        begin
  898.           Expression;
  899.           if IsRelop(Look) then begin
  900.              EmitLn('MOVE D0,-(SP)');
  901.              case Look of
  902.               '=': Equals;
  903.               '#': NotEquals;
  904.               '<': Less;
  905.               '>': Greater;
  906.              end;
  907.           EmitLn('TST D0');
  908.           end;
  909.        end;
  910.        {---------------------------------------------------------------}A*A*
  911.                                     - 15 -
  912.  
  913. PA A
  914.  
  915.  
  916.  
  917.  
  918.  
  919.        Now, that call to  Expression  looks familiar!  Here is where the
  920.        editor of your system comes in handy.  We have  already generated
  921.        code  for  Expression  and its buddies in previous sessions.  You
  922.        can  copy  them  into your file now.  Remember to use the single-
  923.        character  versions.  Just to be  certain,  I've  duplicated  the
  924.        arithmetic procedures below.  If  you're  observant,  you'll also
  925.        see that I've changed them a little to make  them  correspond  to
  926.        the latest version of the syntax.  This change is  NOT necessary,
  927.        so  you  may  prefer  to  hold  off  on  that  until you're  sure
  928.        everything is working.
  929.  
  930.  
  931.        {---------------------------------------------------------------}
  932.        { Parse and Translate an Identifier }
  933.  
  934.        procedure Ident;
  935.        var Name: char;
  936.        begin
  937.           Name:= GetName;
  938.           if Look = '(' then begin
  939.              Match('(');
  940.              Match(')');
  941.              EmitLn('BSR ' + Name);
  942.              end
  943.           else
  944.              EmitLn('MOVE ' + Name + '(PC),D0');
  945.        end;
  946.  
  947.  
  948.        {---------------------------------------------------------------}
  949.        { Parse and Translate a Math Factor }
  950.  
  951.        procedure Expression; Forward;
  952.  
  953.        procedure Factor;
  954.        begin
  955.           if Look = '(' then begin
  956.              Match('(');
  957.              Expression;
  958.              Match(')');
  959.              end
  960.           else if IsAlpha(Look) then
  961.              Ident
  962.           else
  963.              EmitLn('MOVE #' + GetNum + ',D0');
  964.        end;AEAE
  965.  
  966.                                     - 16 -A*A*
  967.  
  968. PA A
  969.  
  970.  
  971.  
  972.  
  973.  
  974.        {---------------------------------------------------------------}
  975.        { Parse and Translate the First Math Factor }
  976.  
  977.  
  978.        procedure SignedFactor;
  979.        begin
  980.           if Look = '+' then
  981.              GetChar;
  982.           if Look = '-' then begin
  983.              GetChar;
  984.              if IsDigit(Look) then
  985.                 EmitLn('MOVE #-' + GetNum + ',D0')
  986.              else begin
  987.                 Factor;
  988.                 EmitLn('NEG D0');
  989.              end;
  990.           end
  991.           else Factor;
  992.        end;
  993.  
  994.  
  995.        {--------------------------------------------------------------}
  996.        { Recognize and Translate a Multiply }
  997.  
  998.        procedure Multiply;
  999.        begin
  1000.           Match('*');
  1001.           Factor;
  1002.           EmitLn('MULS (SP)+,D0');
  1003.        end;
  1004.  
  1005.  
  1006.        {-------------------------------------------------------------}
  1007.        { Recognize and Translate a Divide }
  1008.  
  1009.        procedure Divide;
  1010.        begin
  1011.           Match('/');
  1012.           Factor;
  1013.           EmitLn('MOVE (SP)+,D1');
  1014.           EmitLn('EXS.L D0');
  1015.           EmitLn('DIVS D1,D0');
  1016.        end;A:A:
  1017.  
  1018.  
  1019.                                     - 17 -A*A*
  1020.  
  1021. PA A
  1022.  
  1023.  
  1024.  
  1025.  
  1026.  
  1027.        {---------------------------------------------------------------}
  1028.        { Parse and Translate a Math Term }
  1029.  
  1030.        procedure Term;
  1031.        begin
  1032.           SignedFactor;
  1033.           while Look in ['*', '/'] do begin
  1034.              EmitLn('MOVE D0,-(SP)');
  1035.              case Look of
  1036.               '*': Multiply;
  1037.               '/': Divide;
  1038.              end;
  1039.           end;
  1040.        end;
  1041.  
  1042.  
  1043.        {---------------------------------------------------------------}
  1044.        { Recognize and Translate an Add }
  1045.  
  1046.        procedure Add;
  1047.        begin
  1048.           Match('+');
  1049.           Term;
  1050.           EmitLn('ADD (SP)+,D0');
  1051.        end;
  1052.  
  1053.  
  1054.        {---------------------------------------------------------------}
  1055.        { Recognize and Translate a Subtract }
  1056.  
  1057.        procedure Subtract;
  1058.        begin
  1059.           Match('-');
  1060.           Term;
  1061.           EmitLn('SUB (SP)+,D0');
  1062.           EmitLn('NEG D0');
  1063.        end;
  1064.  
  1065.  
  1066.        {---------------------------------------------------------------}
  1067.        { Parse and Translate an Expression }
  1068.  
  1069.        procedure Expression;
  1070.        begin
  1071.           Term;
  1072.           while IsAddop(Look) do begin
  1073.              EmitLn('MOVE D0,-(SP)');
  1074.              case Look of
  1075.               '+': Add;
  1076.               '-': Subtract;
  1077.              end;
  1078.           end;
  1079.        end;
  1080.        {---------------------------------------------------------------}A*A*
  1081.                                     - 18 -
  1082.  
  1083. PA A
  1084.  
  1085.  
  1086.  
  1087.  
  1088.  
  1089.        There you have it ... a parser that can  handle  both  arithmetic
  1090.        AND Boolean algebra, and things  that combine the two through the
  1091.        use of relops.   I suggest you file away a copy of this parser in
  1092.        a safe place for future reference, because in our next step we're
  1093.        going to be chopping it up.
  1094.  
  1095.  
  1096.        MERGING WITH CONTROL CONSTRUCTS
  1097.  
  1098.        At this point, let's go back to the file we had  previously built
  1099.        that parses control  constructs.    Remember  those  little dummy
  1100.        procedures called Condition and  Expression?    Now you know what
  1101.        goes in their places!
  1102.  
  1103.        I  warn you, you're going to have to  do  some  creative  editing
  1104.        here, so take your time and get it right.  What you need to do is
  1105.        to copy all of  the  procedures from the logic parser, from Ident
  1106.        through  BoolExpression, into the parser for control  constructs.
  1107.        Insert  them  at  the current location of Condition.  Then delete
  1108.        that  procedure,  as  well as the dummy Expression.  Next, change
  1109.        every call  to  Condition  to  refer  to  BoolExpression instead.
  1110.        Finally, copy the procedures IsMulop, IsOrOp, IsRelop, IsBoolean,
  1111.        and GetBoolean into place.  That should do it.
  1112.  
  1113.        Compile  the  resulting program and give it  a  try.    Since  we
  1114.        haven't  used  this  program in awhile, don't forget that we used
  1115.        single-character tokens for IF,  WHILE,  etc.   Also don't forget
  1116.        that any letter not a keyword just gets echoed as a block.
  1117.  
  1118.        Try
  1119.  
  1120.             ia=bxlye
  1121.  
  1122.        which stands for "IF a=b X ELSE Y ENDIF".
  1123.  
  1124.        What do you think?  Did it work?  Try some others.
  1125.  
  1126.  
  1127.        ADDING ASSIGNMENTS
  1128.  
  1129.        As long as we're this far,  and  we already have the routines for
  1130.        expressions in place, we might  as well replace the "blocks" with
  1131.        real assignment statements.    We've already done that before, so
  1132.        it won't be too hard.   Before  taking that step, though, we need
  1133.        to fix something else.
  1134.  
  1135.        We're soon going to find  that the one-line "programs" that we're
  1136.        having to write here will really cramp our style.  At  the moment
  1137.        we  have  no  cure for that, because our parser doesn't recognize
  1138.        the end-of-line characters, the carriage return (CR) and the line
  1139.        feed (LF).  So before going any further let's plug that hole.
  1140.  
  1141.        There are  a  couple  of  ways to deal with the CR/LFs.  One (the
  1142.        C/Unix approach) is just to  treat them as additional white spaceA*A*
  1143.                                     - 19 -
  1144.  
  1145. PA A
  1146.  
  1147.  
  1148.  
  1149.  
  1150.  
  1151.        characters  and  ignore  them.    That's actually not such a  bad
  1152.        approach,  but  it  does  sort  of produce funny results for  our
  1153.        parser as  it  stands  now.   If it were reading its input from a
  1154.        source file as  any  self-respecting  REAL  compiler  does, there
  1155.        would be no problem.  But we're reading input from  the keyboard,
  1156.        and we're sort of conditioned  to expect something to happen when
  1157.        we hit the return key.  It won't, if we just skip over the CR and
  1158.        LF  (try it).  So I'm going to use a different method here, which
  1159.        is NOT necessarily the  best  approach in the long run.  Consider
  1160.        it a temporary kludge until we're further along.
  1161.  
  1162.        Instead of skipping the CR/LF,  We'll let the parser go ahead and
  1163.        catch them, then  introduce  a  special  procedure,  analogous to
  1164.        SkipWhite, that skips them only in specified "legal" spots.
  1165.  
  1166.        Here's the procedure:
  1167.  
  1168.  
  1169.        {--------------------------------------------------------------}
  1170.        { Skip a CRLF }
  1171.  
  1172.        procedure Fin;
  1173.        begin
  1174.           if Look = CR then GetChar;
  1175.           if Look = LF then GetChar;
  1176.        end;
  1177.  
  1178.        {--------------------------------------------------------------}
  1179.  
  1180.  
  1181.        Now, add two calls to Fin in procedure Block, like this:
  1182.  
  1183.  
  1184.        {--------------------------------------------------------------}
  1185.        { Recognize and Translate a Statement Block }
  1186.  
  1187.        procedure Block(L: string);
  1188.        begin
  1189.           while not(Look in ['e', 'l', 'u']) do begin
  1190.              Fin;
  1191.              case Look of
  1192.               'i': DoIf(L);
  1193.               'w': DoWhile;
  1194.               'p': DoLoop;
  1195.               'r': DoRepeat;
  1196.               'f': DoFor;
  1197.               'd': DoDo;
  1198.               'b': DoBreak(L);
  1199.               else Other;
  1200.              end;
  1201.              Fin;
  1202.         end;
  1203.        end;
  1204.        {--------------------------------------------------------------}A*A*
  1205.                                     - 20 -
  1206.  
  1207. PA A
  1208.  
  1209.  
  1210.  
  1211.  
  1212.  
  1213.        Now, you'll find that you  can use multiple-line "programs."  The
  1214.        only restriction is that you can't separate an IF or  WHILE token
  1215.        from its predicate.
  1216.  
  1217.        Now we're ready to include  the  assignment  statements.   Simply
  1218.        change  that  call  to  Other  in  procedure  Block  to a call to
  1219.        Assignment, and add  the  following procedure, copied from one of
  1220.        our  earlier  programs.     Note   that   Assignment   now  calls
  1221.        BoolExpression, so that we can assign Boolean variables.
  1222.  
  1223.  
  1224.        {--------------------------------------------------------------}
  1225.        { Parse and Translate an Assignment Statement }
  1226.  
  1227.        procedure Assignment;
  1228.        var Name: char;
  1229.        begin
  1230.           Name := GetName;
  1231.           Match('=');
  1232.           BoolExpression;
  1233.           EmitLn('LEA ' + Name + '(PC),A0');
  1234.           EmitLn('MOVE D0,(A0)');
  1235.        end;
  1236.        {--------------------------------------------------------------}
  1237.  
  1238.  
  1239.        With  that change, you should now be  able  to  write  reasonably
  1240.        realistic-looking  programs,  subject  only  to our limitation on
  1241.        single-character tokens.  My original intention was to get rid of
  1242.        that limitation for you, too.  However, that's going to require a
  1243.        fairly major change to what we've  done  so  far.  We need a true
  1244.        lexical scanner, and that requires some structural changes.  They
  1245.        are not BIG changes that require us to  throw  away  all  of what
  1246.        we've done so far ... with care, it can be done with very minimal
  1247.        changes, in fact.  But it does require that care.
  1248.  
  1249.        This installment  has already gotten pretty long, and it contains
  1250.        some pretty heavy stuff, so I've decided to leave that step until
  1251.        next  time, when you've had a little more  time  to  digest  what
  1252.        we've done and are ready to start fresh.
  1253.  
  1254.        In the next installment, then,  we'll build a lexical scanner and
  1255.        eliminate the single-character  barrier  once and for all.  We'll
  1256.        also write our first complete  compiler, based on what we've done
  1257.        in this session.  See you then.
  1258.  
  1259.  
  1260.        *****************************************************************
  1261.        *                                                               *
  1262.        *                        COPYRIGHT NOTICE                       *
  1263.        *                                                               *
  1264.        *   Copyright (C) 1988 Jack W. Crenshaw. All rights reserved.   *
  1265.        *                                                               *
  1266.        *****************************************************************A*A*
  1267.                                     - 21 -
  1268. @