poke package:bytezap

Poke newtype wrapper.
Unboxed buffer write operation. The next offset must be greater than or equal to the input buffer offset. This is not checked. Note that the only way to find out the length of a write is to perform it. But you can't perform a length without providing a correctly-sized buffer. Thus, you may only use a Poke# when you have a buffer large enough to fit its maximum write length-- which in turn means means you must track write lengths separately. (Write does this.) I provide this highly unsafe, seemingly unhelpful type because it's a requirement for Write, and here I can guarantee performance better because I don't need to worry about laziness. We cannot be polymorphic on the pointer type unless we box the pointer. We thus limit ourselves to writing to Addr#s, and not MutableByteArray#s. (I figure we're most interested in ByteStrings, which use Addr#.) Note that if we did provide write length, then the next offset might appear superfluous. But that next offset is usually already calculated, and may be passed directly to sequenced writes, unlike if we returned a write length which would need to be added to the original offset.
A struct poker: base address (constant), byte offset, state token. We could combine base address and byte offset, but we're aiming for code that stores the address in a register and uses immediates to access fields (like a good C compiler will do for its structs). So by keeping them separate, I'm hoping that we can nudge GHC towards such behaviour.
Use a struct poke as a regular poke. To do this, we must associate a constant byte length with an existing poker. Note that pokers don't expose the type of the data they are serializing, so this is a very clumsy operation by itself. You should only be using this when you have such types in scope, and the constant length should be obtained in a sensible manner (e.g. KnownSizeOf for generic struct pokers, or your own constant size class if you're doing funky stuff).
Use a struct poke as a regular poke by throwing away the return offset.
Execute a Poke at a pointer. Returns the number of bytes written. The pointer must be a mutable buffer with enough space to hold the poke. Absolutely none of this is checked. Use with caution. Sensible uses:
  • implementing pokes to ByteStrings and the like
  • executing known-length (!!) pokes to known-length (!!) buffers e.g. together with allocaBytes
Execute a Poke at a fresh ByteString of the given length.
Execute a Poke at a fresh ByteString of the given maximum length. Does not reallocate if final size is less than estimated.
unsafePokeIndexed pokeAt off n performs n indexed pokes starting from off. Does not check bounds. Largely intended for bytewise pokes where some work needs to be performed for each byte (e.g. escaping text and poking inline).
The empty poke. Provided here as we can't provide it via empty.
Sequence two Pokes. We only require the length of the left poke.
Execute a Poke at a pointer. Returns the number of bytes written. The pointer must be a mutable buffer with enough space to hold the poke. Absolutely none of this is checked. Use with caution. Sensible uses:
  • implementing pokes to ByteStrings and the like
  • executing known-length (!!) pokes to known-length (!!) buffers e.g. together with allocaBytes
Class for holding info on class to use for poking base cases. The type is just used to map to class info. It is never instantiated. By packing KnownSizeOf into here, we don't need to enforce a type-level solution! Now it's up to you how you want to track your constant lengths. We stay unboxed here because the internals are unboxed, just for convenience. Maybe this is bad, let me know.