Tuesday, March 13, 2007

Prettier prettyprinting

So what have I been working on for the last few days? The prettyprinter, which is invoked any time you use a word such as . or see.

This concept does not even exist in many languages, but it is central to Factor. The idea is that given an object, you can print it in a form which can then be parsed back in. Doing this is easy enough. The hard part is formatting the result nicely so that it is useful for human as well as machine consumption.

The prettyprinter is still not as good as a human programmer, but my goal is to make it as close as possible -- when the structure editor is implemented, it will use the prettyprinter to display the definitions being edited. There are more improvements I plan on making, but at some point any the only word definitions which are still formatted badly will be the longer ones, and they can be factored and cleaned up.

Now I will describe some of the improvements I have made.

Keeping quotations together
A very common idiom is to push a pair of quotations and then pass them to a combinator, for example if. The old prettyprinter didn't do anything special in this case, it would just insert line breaks wherever it had to so that the whole thing would fit within the margin:
IN: sequences : head? ( seq begin -- ? ) 
2dup [ length ] 2apply < [ 2drop f ]
[ [ length head-slice ] keep sequence= ] if ;

Now it tries to avoid leaving a quotation at the end of the line:
IN: sequences
: head? ( seq begin -- ? )
2dup [ length ] 2apply <
[ 2drop f ] [ [ length head-slice ] keep sequence= ] if ;


Grouping lines by stack flow
While well-factored definitions tend to be short, sometimes a longer one crops up. This happens often in the compiler backend, where you have words which emit a series of assembly instructions, with no good way of splitting this up into several words. The old prettyprinter would turn everything into a big soup:
\ (%dispatch) see
IN: generator : (%dispatch) ( word-table# -- )
"n" operand dup 1 SRAWI 0 "scratch" operand LOAD32
rc-absolute-ppc-2/2 rel-dispatch "n" operand dup "scratch"
operand ADD "n" operand dup 0 LWZ ;

The new prettyprinter looks at word stack effects and tries to lay things out using a variety of heuristics:
IN: generator
: (%dispatch) ( word-table# -- )
"n" operand dup 1 SRAWI 0 "scratch" operand LOAD32
rc-absolute-ppc-2/2 rel-dispatch
"n" operand dup "scratch" operand ADD
"n" operand dup 0 LWZ ;


Support for C:
The prettyprinter used to display tuple constructors in their internal, expanded form:
IN: parser : <parse-error> ( msg -- error ) 
\ parse-error 6 <tuple> file get over set-parse-error-file
line-number get over set-parse-error-line column-number get
over set-parse-error-col line-text get over
set-parse-error-text [ set-delegate ] keep ;

This confused a few people in the last (what is the <tuple> word? What does that number mean? etc). Now, the prettyprinter knows what is a constructor and displays it properly; also note the improved layout:
IN: parser
C: parse-error ( msg -- error )
file get over set-parse-error-file
line-number get over set-parse-error-line
column-number get over set-parse-error-col
line-text over set-parse-error-text
[ set-delegate ] keep ;


Better display of >r / r>
The prettyprinter now tries to place a block enclosed in >r and r> on one line if possible.

Before:
M: live-search handle-gesture* 
drop over search-gesture dup [
over find-workspace hide-popup >r live-search-list
list-value r> invoke-command f
] [ 2drop t ] if ;

After:
M: live-search handle-gesture*
drop over search-gesture dup [
over find-workspace hide-popup
>r live-search-list list-value r>
invoke-command f
] [ 2drop t ] if ;

1 comment:

Anonymous said...

Just FYI: The tuple tag in the code for tuple constructors does not display.