The Z operator

The Z operator is similar to the V operator, with the difference illustrated by the following figure:

V: …aaa
bbb
bbb
c…

Z: …aaa
bbb
bbb
c…

where ... is (part of) a sibling box.

As seen in the example, the difference between V and Z is that the whole V box is aligned to the last character of the box preceding it, while the Z box is aligned to the left margin, except for the first line.

The I operator

So what defines the left margin? That is where the second new operator enters the playing field: the I operator.

A variant of the I operator is also present in “CWI BOX”.
In that BOX dialect, the I operator is (one of a few) context-sensitive operators: only in a vertical context it indents its children. Regardless of the context, it is equivalent to an H box.
This gives rise to some peculiarities: for example, an I is=2 box with two children "hello" and "world" in a vertical context results in the string " hello world", as both children are prefixed with two spaces, and then horizontally composed.

The I operator I propose behaves a bit different. First, it is not context-sensitive: in any context, I is=N b is equivalent to `H hs=N [S(""), b], modulo side effects. Second, it only takes a single child box, not a list of child boxes, so there is no need to decide whether its children should be composed horizontally or vertically (or line wrapped, etc.): that choice is delegated to the child box.

So what makes the I operator slightly different from indentation using H hs=N [S(""), ...] or V is=N [...] is its single side effect: the I operator records the left margin as its x position plus the value of its is option. This left margin is then used by any nested Z operators.

Rationale

These Z and I operators are exactly the right abstraction for expressing constructs such as compound statement in control flow statements in languages with curly braces. For example, in

if (cond) |{

}|

the part between the pipe symbols (|) could be a compound statement, pretty printed using a Z box. The Z box allows the opening bracket to be placed on the same line as the if. This code style is not possible to generate with BOX as is, without complicated transformations that lift the opening bracket from the V/Z box of the compound statement to the H box of the if statement.

The example

else |if (cond) |{

}||

suggests the need for the I operator. While the preceding example could possibly be implemented by aligning the Z box contents to the parents’ left margin, this second example would require the Z box contents to be aligned to the grandparents’ left margin. A separate I operator that makes indentation explicit solves this, as both the condition of the nested if statement and the opening bracket haven’t been shifted to the right through indentation, but through other character data.

This addition to BOX allows all possible brace styles to be generated. (those are: 1 - GNU style; 2 - braces on separate line, aligned with the “if” keyword; 3 - opening brace on same line as “if” keyword, else on new line; 4 - same as 3, but “else” is put on the same line as the closing brace)

Combined with a pattern matching pretty printer with BOX as back end this addition to BOX allows all possible brace styles in combination with all possible styles for putting a non-compound statement in a control flow statement (i.e., same line or next line).

Implementation

I would integrate the following two strategies with libstratego-gpp:


signature constructors
Z : S-OPTIONS * BOX-LIST -> BOX
I : S-OPTIONS * BOX -> BOX

strategies
// I is=N b' is equivalent toH hs=N [S(""), b]’
// NOTE: this I box is not context sensitive!
// NOTE: this I box takes a single box as argument, not a list of boxes!
abox2text-I(rec : t * t -> t | xpos, width) =
?I(sopt, bs)

; {|Indent:
      <add> (xpos, <gpp-is-length> sopt) => left-xpos
    ; rules( GppIndent := left-xpos )
    ; <fetch-elem(?SOpt(IS(), <id>)) <+ !"0"> sopt => value
    ; <rec(|xpos)> H([SOpt(HS(), value)], [S(""), bs])
  |}

abox2text-Z(rec : t * t -> t | xpos, width) =
?Z(sopt, bs)

; GppIndent => indent
; <add> (indent, <gpp-is-length> sopt) => left-xpos
; <filter(gpp-is-real-vbox)> bs
; if ?[] then
    !([], xpos)
  else
    gpp-do-vbox-children(rec | xpos, left-xpos)
    ; gpp-format-vbox(|sopt, indent)
  end

I haven’t made any grammar changes for PP yet, but I could add those if that is desired.

So, any comments?

Can I commit those changes to libstratego-gpp? :-)

Submitted by Tobi Vollebregt on 19 December 2011 at 20:28

On 19 December 2011 at 21:32 Tobi Vollebregt commented:

For consistency an HZ operator might be useful too, though I didn’t implement this. (HZ would be to HV what Z is to V.)


On 27 February 2012 at 15:46 Tobi Vollebregt commented:

Got a complete patch now, including HZ operator, changes to PP syntax, etc.

So, okay to commit?


On 29 February 2012 at 10:29 Lennart Kats commented:

Yes, sounds like good stuff Tobi.

Log in to post comments