Contexts: Improving selection behavior

Although we have successfully treated the case of parentheses, we still have one last problem to tackle: selection behavior. In a Centaur editor, when you click on a token, you select the tree node which generated this token during pretty printing, and all descendents of this node. Thus, in the following rule:

op(*op1,*op2) -> [ *op1 "*" [ "$"*op2]]

clicking on either * or $ selects the op node and all its subtrees. On the other hand, clicking on either *op1 or *op2 means clicking on tokens that were unparsed farther down the tree, and thus the appropriate subtrees would be selected.

If we pretty print parentheses for the prod operator as follows:

 
   *x where *x in {prod(*exp1,*exp2)}  ->  

     [<hv> if prec(*exp1) > prec(*x) then
              [<h> "(" *exp1 ")"]
           else
              *exp1
           end if
           "*"
           if prec(*exp2) >= prec(*x) then
              [<h> "(" *exp2 ")"]
           else
              *exp2
           end if] ;

clicking on either parenthesis will always select the operator prod and its descendents:

whereas the preferred response would be to select only the expression in parentheses, i.e., the plus operator:

* 24

To remedy this behavior, we introduce a context into our program. A context is a subset of rules within a pretty printer specification. So far we have worked in the default context, but we may define others. Passing into a context restricts Ppml's vision to rules whose patterns are prefaced by the context name. For example, the second of the following rules belongs to mycontext:

 
                op(*x) -> [<h> "hello" *x] ;
     mycontext::op(*x) -> [<h> "goodbye" *x] ;

Any time we have passed into mycontext, the first rule is ignored. To pass into another context, we preface variable names (recursive calls) in the formatting section of a rule with the desired context name. Thus,

 
   prod(*e1,*e2) -> 
          [<h> mycontext::*e1  "*" mycontext::*e2] ;

passes into mycontext to treat the variables *e1 and *e2.

When no rule is found in the current pretty printer for a matching a context, other pretty printers are examined in the order they are specified for the parent modular pretty printer.

How do we use contexts for We know at what moment we want to pretty print parentheses thanks to the precedence condition. We don't want to pretty print parentheses in this rule because of the mouse behavior, so we write another rule to print parentheses around any operator in the phylum EXP:

*x where *x in EXP -> [ "(" *x ")"] ;

This rule, however, would hide all other rules if at the beginning of a program, and would itself be hidden at the end of a program. If we define a context paren, we may pass into paren, apply this rule, and return to the original context to continue pretty printing. Passing into paren thus eliminates the problem of where to put the rule that prints parentheses. Thus, we apply the rule:

paren:: *x where *x in EXP -> [ "(" *x ")"] ;

any time we are certain that we want to print parentheses. Here is the full rule for the operator prod which relies on the above paren rule:

 
   *x where *x in {prod(*exp1,*exp2)}  ->  

     [<hv> if prec(*exp1) > prec(*x) then
              paren:: *exp1
           else
              *exp1
           end if
           "*"
           if prec(*exp2) >= prec(*x) then
              paren:: *exp2
           else
              *exp2
           end if] ;

Combining everything we've seen, our second version of Exp-basic.ppml is:

 
   prettyprinter basic of Exp is
 
   constant
      tab = 2;

   default
      <hv 1,tab,0>;

   function prec is
      prec (*op where *op in {assign}) = 4;
      prec (*op where *op in {plus,minus}) = 3;
      prec (*op where *op in {prod}) = 2;
      prec (*op where *op in {uminus}) = 1;
      prec (*op) = 0;
   end prec ;

   rules
      *x !0 -> [<h> "..."] ;
 
      exp_s(**exps,*exp) -> [<v> ([<h> **exps ";"]) *exp] ;

      paren:: *x where *x in EXP -> [<h> "(" *x ")"] ;

      *x where *x in {uminus(*exp)} ->
         [<hv> getsymbol(*x) if prec(*exp) > prec(*x) then
                             paren:: *exp
                          else
                             *exp
                          end if] ;

      *x where *x in
         {plus(*exp1,*exp2), prod(*exp1,*exp2),
          minus(*exp1,*exp2), assign(*exp1,*exp2)} ->
         [<hv> if prec(*exp1) > prec(*x) then
              paren:: *exp1
           else
              *exp1
           end if
           getsymbol(*x)
           if prec(*exp2) >= prec(*x) then
              paren:: *exp2
           else
              *exp2
           end if] ;
   end prettyprinter


                  



Tutorial