PrettyPrinter.int:

//
// Simple PrettyPrinter -- Andrew C. Myers, March 1999
//   For use in Cornell University Computer Science 412/413
//
uses io.OutputStream
interface PrettyPrinter {
    // A pretty-printer formats text onto an
    // output stream "o" while keeping the width of the output
    // within "width" characters if possible 
    write(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)
        // 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.
}

createPrettyPrinter(o: OutputStream, width: int): PrettyPrinter
  // create a new PrettyPrinter that writes its output to the
  // underlying output stream "o".

PrettyPrinter.mod:

createPrettyPrinter(o: OutputStream, width: int): PrettyPrinter = 
    (new Impl).init(o, width)

class Impl extends PrettyPrinter {
    init(o: OutputStream, width_: int): Impl = {
        output = o;
        width = width_;
        current = input = (new Block).init(null, 0);
        this;
    }
    write(s: string) = current.add((new StringItem).init(s))
    begin(indent: int) = {
        b: Block = (new Block).init(current, indent);
        current.add(b);
        current = b;
    }
    end() = { current = current.parent; }
    allowBreak(n: int) = current.add((new AllowBreak).init(n))
    newline(n: int) = current.add((new Newline).init(n))
    flush() = {
        input.format(0, 0, width, true);
        input.sendOutput(output, 0, 0);
        output.flush();
        current = input = (new Block).init(null, 0);
    }

    output: OutputStream
    width: int
    input, current: Block
}

interface Item {
    next(): Item
    setNext(it: Item)
    format(lmargin, pos, rmargin: int, canBreak: bool): FmtResult
    sendOutput(o: OutputStream, lmargin, pos: int): int
}

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

class Success extends FmtResult {
    success(): bool = true
    pos_: int
    pos(): int = pos_
}

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

class Failure extends FmtResult {
    success(): bool = false
    pos(): int = 0
}

failure: Failure = (new Failure)

tryFormat(i: Item, lmargin, pos, rmargin: int, canBreak: bool): FmtResult = {
    if (pos > rmargin) return failure;
    ret: FmtResult = i.format(lmargin, pos, rmargin, canBreak);
    if (!ret.success() | !i.next()) return ret;
    tryFormat(i.next(), lmargin, pos, rmargin, canBreak);
}

class Block extends Item {
    parent: Block
    first, last: Item
    indent: int
    next_: Item
    next(): Item = next_
    setNext(it: Item) = {next_ = it;}

    init(parent_: Block, indent_: int): Block = {
        parent = parent_;
        first = last = null;
        indent = indent_;
        this;
    }
    add(it: Item) = {
        if (first == null) first = it; else last.setNext(it);
        last = it;
    }
    format(lmargin, pos, rmargin: int, canBreak: bool): FmtResult = {
        if (!first) return succeeded(pos);
        ret: FmtResult = tryFormat(first, pos+indent, pos, rmargin, true);
        if (ret.success()) return ret;
        if (!canBreak) failure;
        else tryFormat(first, pos+indent, pos, rmargin, true);
    }
    sendOutput(o: OutputStream, lmargin, pos: int): int = {
        it: Item = first;
        lmargin = pos + indent;
        while (it) {
            pos = it.sendOutput(o, lmargin, pos);
            it = it.next();
        }
        pos;
    }
}

class StringItem extends Item {
    next_: Item
   
    init(s_: string): StringItem = { s = s_; this; }
    next(): Item = next_
    setNext(it: Item) = {next_ = it;}
    format(lmargin, pos, rmargin: int, canBreak: bool): FmtResult = {
        pos = pos + length s;
        if (pos > rmargin) failure;
        else succeeded(pos);
    }
    sendOutput(o: OutputStream, lmargin, pos: int): int = {
        o.print(s);
        pos + length s;
    }

    s: string
}

class AllowBreak extends Item {
    next_: Item;
    init(n: int): AllowBreak = { indent = n; this; }
    next(): Item = next_
    setNext(it: Item) = {next_ = it;}
    format(lmargin, pos, rmargin: int, canBreak: bool): FmtResult = {
        broken = canBreak;
        if (canBreak) succeeded(lmargin + indent);
        else failure;
    }
    sendOutput(o: OutputStream, lmargin, pos:int): int = {
        if (broken) {
            o.print("\N");
            i: int = 0;
            while (i < lmargin) { o.print(" "); i++; }
            lmargin + indent;
        } else {
            pos;
        }
    }

    indent: int
    broken: bool
}

class Newline extends Item {
    next_: Item
    init(n: int): Newline = { indent = n; this; }
    next(): Item = next_
    setNext(it: Item) = {next_ = it;}
    format(lmargin, pos, rmargin: int, canBreak: bool): FmtResult = {
        if (canBreak) succeeded(lmargin+indent);
        else failure;
    }
    sendOutput(o: OutputStream, lmargin, pos:int): int = {
        o.print("\N");
        i: int = 0;
        while (i < lmargin) { o.print(" "); i++; }
        lmargin + indent;
    }

    indent: int
}