CodeWriter.ii

uses io.OutputStream

interface I {
// A CodeWriter formats text onto an output stream o while 
// keeping the width of the output within width characters 
// if possible.

    print(s: string)
	// Print the string s on the output stream
    begin(indent: int)
	// Start a new block with indentation increased
	// by n characters
    end()
	// Terminate the most recent outstanding begin
    allowBreak(n: int, alt: string)
	// Allow a newline. Indentation will be preserved.
    newline(n: int)
	// Force a newline. Indentation will be preserved.
    flush()
	// Send out the current batch of text
	// to be formatted, closing all
	// outstanding begin's and resetting
	// the indentation level to 0.
}

create(o: OutputStream, width: int): I
    // create a new pretty-printer that writes its output to the
    // underlying output stream o

CodeWriter.im

/**
 * CodeWriter -- Andrew C. Myers, April 2001
 * For use in Cornell University Computer Science CS 412/413
 */

create(o: OutputStream, w: int): I = (new C).init(o, w)

class C implements I {
    output: OutputStream
    width: int
    input, current: Block

    init(o: OutputStream, w: int): C = (
    	output = o;
	width = w;
	input = current = new Block.init(null, 0);
	this
    )

    print(s: string) = current.add(new StringItem.init(s))

    begin(n: int) = (
        b: Block = new Block.init(current, n);
        current.add(b);
        current = b
    )
        
    end() = (current = current.parent)

    allowBreak(n: int, alt: string) = 
        current.add(new AllowBreak.init(n, alt))

    newline(n: int) = current.add(new Newline.init(n))

    flush() = (
	format(input,0, 0, width, width, true, true);
        input.sendOutput(output, 0, 0);
        output.flush();
        current = input = new Block.init(null, 0);
    )
}

interface FmtResult {
    success(): bool
    pos(): int
    overrun(): int
}

class Success implements FmtResult {
    success(): bool = true
    pos_: int
    pos(): int = pos_
    overrun(): int = 0
}

succeeded(p: int): Success = (
    ret: Success = new Success;
    ret.pos_ = p;
    ret
)

class Failure implements FmtResult {
    overrun_: int
    success(): bool = false
    pos(): int = 0
    overrun(): int = overrun_
}

failed(o: int): Failure = (
    ret: Failure = new Failure;
    ret.overrun_ = o;
    ret
)

interface Item {
    formatN(lmargin, pos, fin, rmargin: int,
	   can_break, no_fail: bool): FmtResult
    sendOutput(o: OutputStream, lmargin, pos: int): int
    isBreak(): bool
    setNext(it: Item)
}

format(it: Item, lmargin, pos, rmargin, fin: int,
		 can_break, no_fail: bool): FmtResult = (
    if (!no_fail & pos > rmargin) ( // overrun
	return failed(pos - rmargin)
    );
    if (!it) ( // no items to format. Check against final position.
	if (!no_fail & pos > fin) return failed(pos - fin);
	return succeeded(pos);
    );
    it.formatN(lmargin, pos, rmargin, fin, can_break, no_fail)
)

/**
 * A Block is a formatting unit containing a list of other items
 * to be formatted.
 */
class Block implements Item {
    parent: Block
    first, last, next: Item
    indent: int
        
    init(p: Block, i: int): Block = (
        parent = p;
        first = last = next = null;
        indent = i;
	this
    )
    add(it: Item) = (
        if (!first) (// just do first = it if you don't have "cast" working yet
	    first = it;
	) else (
	    s: StringItem = cast(it, StringItem);
	    ls: StringItem = cast(last, StringItem);
	    if (s & ls) (
		ls.appendString(s.s);
		return
	    );
	    last.setNext(it)
	);
        last = it
    )
    formatN(lmargin, pos, rmargin, fin: int,
	    can_break, no_fail: bool): FmtResult = (
	this_fin: int = rmargin;
	this_nofail, this_break: bool = false;
	while (true) (
	    first_failed: bool = true;
	    next_pos: int;
	    while (first_failed) (
		r: FmtResult = format(first, pos + indent, pos, rmargin,
				      this_fin, this_break,
				      this_nofail & this_break);
		if (r.success()) (
		    first_failed = false;
		    next_pos = r.pos()
		) else (
		    if (!can_break) return r;
		    if (!this_break) this_break = true
		    else if (no_fail) this_nofail = true
		    else return r
		)
	    );
	    r: FmtResult = format(next, lmargin, next_pos, rmargin, fin,
				  can_break, no_fail);
	    if (r.success() | !can_break | next.isBreak()) return r;
	    this_break = true;
	    if (next_pos > this_fin) next_pos = this_fin;
	    this_fin = next_pos - r.overrun();
	)
    )
    sendOutput(o: OutputStream, lmargin, pos: int): int = (
        it: Item = first;
	pos = first.sendOutput(o, pos+indent, pos);
	if (next)
	    pos = next.sendOutput(o, lmargin, pos);
	pos
    )
    isBreak(): bool = false
    setNext(it: Item) = (next = it)
}

class StringItem implements Item {
    s: string
    next: Item
    init(s_: string): StringItem = (s = s_; this)
        
    formatN(lmargin, pos, rmargin, fin: int,
	    can_break, no_fail: bool): FmtResult =
	format(next, lmargin, pos + length s, rmargin, fin, can_break, no_fail)

    sendOutput(o: OutputStream, lmargin, pos: int): int = (
        o.print(s);
	pos = pos + length s;
	if (next)
	    pos = next.sendOutput(o, lmargin, pos);
	pos
    )
    appendString(s_: string) = (s = s + s_)
    isBreak(): bool = false
    setNext(it: Item) = (next = it)
}

class AllowBreak implements Item {
    indent: int
    broken: bool
    alt: string
    next: Item
        
    init(n: int, a: string): AllowBreak = (
	indent = n;
	alt = a;
	broken = true;
	this
    )
    formatN(lmargin, pos, rmargin, fin: int,
	    can_break, no_fail: bool): FmtResult = (
        if (can_break) (pos = lmargin + indent; broken = true)
        else (pos = pos + length alt; broken = false);
	format(next, lmargin, pos, rmargin, fin, can_break, no_fail)
    )
    sendOutput(o: OutputStream, lmargin, pos: int): int = (
        if (broken) (
            o.print("\N");
	    i: int = 0;
	    while (i < lmargin + indent) (o.print(" "); i++);
            pos = lmargin + indent
        ) else (
	    o.print(alt);
            pos = pos + length alt
        );
	if (next)
	    pos = next.sendOutput(o, lmargin, pos);
	pos
    )
    isBreak(): bool = true
    setNext(it: Item) = (next = it)
}

class Newline implements Item {
    indent: int
    broken: bool
    next: Item

    init(n: int): Newline = (indent = n; broken = true; this)
        
    formatN(lmargin, pos, rmargin, fin: int,
	    can_break, no_fail: bool): FmtResult = (
	if (!can_break) return failed(1);
	format(next, lmargin, lmargin + indent, rmargin, fin,
			can_break, no_fail)
    )
    sendOutput(o: OutputStream, lmargin, pos: int): int = (
        o.print("\N");
	i: int = 0;
	while (i < lmargin + indent) (o.print(" "); i++);
        pos = lmargin + indent;
	if (next)
	    pos = next.sendOutput(o, lmargin, pos);
	pos
    )
    isBreak(): bool = true
    setNext(it: Item) = (next = it)
}

cwTest.im

uses CodeWriter = CodeWriter.I,
     create_CodeWriter = CodeWriter.create,
     io.stdout, io.print

/**
 * A testing driver for the CodeWriter module
 */
main(args:array[string]):int = (
    //create a new CodeWriter object, set the right
    //margin(line width) as 60.
    cw: CodeWriter = create_CodeWriter(stdout, 60);

    //Begin a new block.
    cw.begin(4);
    cw.print("(This is block 1:");
    cw.allowBreak(0, " ");
    cw.print("item1,");
    cw.allowBreak(0, " ");
    cw.print("item2,");
    cw.allowBreak(0, " ");
    cw.print("item3)");
    cw.end();

    //start a new line
    cw.newline(0);

    //Begin another block
    cw.begin(4);
    //The first line will start at the current position without
    //indentation.
    cw.print("(This is block 2:");
    cw.allowBreak(0, " ");
    cw.print("long item 1**********,");
    cw.allowBreak(0, " ");
    cw.print("long item 2**********,");
    cw.allowBreak(0, " ");
    cw.print("long item 3**********,");
    cw.allowBreak(0, " ");

    cw.begin(4);
    cw.print("(This is an embeded block.");
    cw.allowBreak(0, " ");
    cw.print("subitem1)");
    cw.allowBreak(0, " ");
    cw.end();
    cw.allowBreak(0, " ");

    cw.begin(4);
    cw.print("(This is another embeded block.");
    cw.allowBreak(0, " ");
    cw.print("subitem1,");
    cw.allowBreak(0, " ");
    cw.print("subitem2,");
    cw.allowBreak(0, " ");
    cw.print("subitem3)");
    cw.end();
    cw.end();

    cw.allowBreak(0, " ");

    //Begin another block, virtually similar to the first block
    //except replacing a "allowBreak" with a "newLine". Because
    //there exists one "newLine" item, every other "allowBreak"
    //items will cause a line change.
    cw.begin(4);
    cw.print("(This is block 3:");
    cw.allowBreak(0, " ");
    cw.print("item1,");
    cw.newline(0);
    cw.print("item2,");
    cw.allowBreak(0, " ");
    cw.print("item3)");
    cw.end();

    cw.flush();
    0
)