{
:new_writer
:new_writer2
:write_all
:write_byte
:write
:flush
:with_writer
}

Where

Define (with_writer fd size func)
    Let w (new_writer2 fd size)
    In
    Begin {
        (func w)
        (IO.flush w)
    }

Where

Define (new_writer2 fd size)
    Define (write w bytes start size)
        (write w.state bytes start size)
    Define (write_all w bytes)
        (write_all w.state bytes)
    Define (write_byte w byte)
        (write_byte w.state byte)
    Define (flush w)
        (flush w.state)
    In
    {
    :interface {:write :write_all :write_byte :flush}
    :state (new_writer fd size)
    }

Where

Define (write_all w bytes)
    (write w bytes 0 (CHUNK.size bytes))

Where

Define (write_byte w byte)
    Begin {
        When [(fetch_top w) = (size w)] { (flush w) }
        Begin {
            Let top (fetch_top w)
            (CHUNK.store_byte w [8 + top] byte)
            (store_top w [top + 1])
        }
    }

Define (write w bytes start count)
    Let fd (fetch_fd w)
    Let size (size w)
    In
    Iterate {start count}
        Let top (fetch_top w)
        In
        Let room [size - top]
        In
        Begin Cond {
        | [count > room]
            (Return
                Begin Cond {
                | [top = 0]
                    Let r (OS.write fd bytes start count)
                    When [r <= 0] { (OS.die "Failed to write bytes.") }
                    (Continue [start + r] [count - r])
                | True
                    (CHUNK.store_bytes w [8 + top] bytes start room)
                    (store_top w size)
                    (flush w)
                    (Continue [start + room] [count - room])
                })
        | True
            (CHUNK.store_bytes w [8 + top] bytes start count)
            (store_top w [top + count])
        }

Where

Define (new_writer fd size)
    Begin {
        Let w (alloc size)
        (init w fd)
        (Return w)
    }

Define (flush w)
    Let fd (fetch_fd w)
    Let top (fetch_top w)
    In
    When [top > 0] {
        Let r (OS.write fd w 8 top)
        When [r != top] { (OS.die "Failed to flush buffer.") }
        (store_top w 0)
    }

Where

Define (alloc size)
    (CHUNK.new_rw [8 + size])

Define (init w fd)
    Begin {
        (store_fd w fd)
    }

Where

Define (store_fd w fd)
    (CHUNK.store_uint32 w 0 fd)

Define (fetch_fd w)
    (CHUNK.fetch_uint32 w 0)

Define (store_top w top)
    (CHUNK.store_uint32 w 4 top)

Define (fetch_top w)
    (CHUNK.fetch_uint32 w 4)

Define (size w)
    [(CHUNK.size w) - 8]

Where

Open Z
    {
    :Infix <=
    :Infix >
    :Infix =
    :Infix !=
    :Infix +
    :Infix -
    }

Open LIST {:Infix &}

Where

Let CHUNK Package "chunk"
Let IO Package "io"
Let LIST Package "list"
Let OS Package "os"
Let STDIO Package "stdio"
Let Z Package "z"