EMILUA(3em)

πŸ“¦ version 0.12.1, in nixos 26.05, nixpkgs unstable

🌸 EMILUA(3em) β€” emilua, section 3em, the Emilua reference 🌸

okay so bestie, welcome to emilua(3em) β€” this is the page for byte_span, and honestly? it's iconic. it's Emilua's Lua execution engine territory, so grab a snack and let's get into it πŸ’…

✨ NAME ✨

Emilua β€” Lua execution engine. that's the whole name section, short and slay πŸŽ€

πŸ¦‹ DESCRIPTION πŸ¦‹

πŸ’‘ Note: byte_span is a global, so you don't need to require() it. it's just there for you, like a good bestie 🫢

so what even is it? it's a span of bytes. and in Emilua, these are used as network buffers. periodt. ✨

πŸ‘‘ Note: plugin authors, this one's for you This class is intended for network buffers in a proactor-based network API β€” and by that we mean true asynchronous IO. a NIC could literally be writing to this memory region while the program is running 😭 like, that's a real thing that can happen. the effect is the same as another thread writing to the same memory region. no cap. if you're writing state machines, do not construct the state machine on top of the memory region that a byte_span points to. it is not safe to store state here, because buggy Lua applications could mutate this area in a racy way πŸ’€ only use the memory region as the result of operations. that's it. that's the rule. a future Emilua release could introduce read-write locks, but as of right now I'm unconvinced of their advantages here πŸ€·β€β™€οΈ

and the lore? it's modeled after Golang's slices β€” http://blog.golang.org/2011/01/go-slices-usage-and-internals.html β€” however, the plot twist: 1-indexed access is used 😌 as it should be, lbr.


πŸ’– FUNCTIONS πŸ’–

  • new(length: integer[, capacity: integer]) β†’ byte_span β€” the constructor, the main character πŸ‘‘ when you leave out the capacity argument, it just defaults to the length you specified. easy.
  • with_zeros(length: integer[, capacity: integer]) β†’ byte_span β€” also a constructor πŸ’… it initializes capacity bytes with zero (the NUL byte). and if length and capacity are different, then it goes beyond the first length bytes to initialize the whole memory region pointed to by the returned byte_span β€” yes, the whole thing, she's thorough 🌈

and if length and capacity are equal? then it's identical to new(length):fill(0). same vibe, same result.

  • sub(self[, start: integer, end: integer]) β†’ byte_span β€” returns a brand new byte_span that points to a slice of the same memory region. sharing is caring 🫢

start and end are optional, obvi β€” they default to 1 and the byte_span's length respectively.

fun fact: we can grow a byte_span to its capacity by slicing it again. glow-up behavior, honestly ✨

⚠️ invalid ranges β€” like start below 1, a byte_span running beyond its capacity, negative indexes, the usual suspects β€” will raise EINVAL. no exceptions. well, except that one.
  • first(self, count: integer) β†’ byte_span β€” returns a new byte_span that points to the first count bytes of the same memory region. front of the line energy πŸŽ€
  • last(self, count: integer) β†’ byte_span β€” returns a new byte_span that points to the last count bytes of the same memory region. saving the best for last, literally πŸ’…
  • copy(self, src: byte_span|string) β†’ integer β€” copies src into self πŸ”₯ and it returns the number of elements copied, so you always know what happened.

copying between slices of different lengths is supported β€” it'll copy only up to the smaller number of elements. and better yet, it can handle source and destination spans that share the same underlying memory, handling overlapping spans correctly πŸ‘ like, overlap-aware. we love a considerate function.

  • append() β†’ byte_span β€” two overloads, bestie, keep up 😭
function append(self, ...: byte_span|string|nil) -> byte_span (1)
function append(...: byte_span|string|nil) -> byte_span       (2)

returns a new byte_span by appending the trailing arguments into self. and here's the iykyk part: if self's capacity is enough to hold all the data, the underlying memory is modified in place. otherwise, the returned byte_span will point to newly allocated memory[1] 🧚

for the second overload β€” that's the non-member function β€” a new byte span is created from scratch. fresh start, new me 🌸

  • fill(self, byte: integer) β†’ byte_span β€” as in C's memset(), it fills the memory area pointed to by self with byte. and it returns self, because of course it does, she's generous πŸ’œ

🌈 FUNCTIONS (STRING ALGORITHMS) 🌈

quick vibe check before we start: these functions operate in terms of octets/bytes β€” kinda like an 8-bit ASCII situation β€” and they have no concept of UTF-8 encoding. so don't come here expecting unicode magic, she's byte-pilled and that's the tea πŸ“

  • starts_with(self, prefix: string|byte_span) β†’ boolean β€” returns whether self begins with prefix. yes/no, that's the whole gig.
  • ends_with(self, suffix: string|byte_span) β†’ boolean β€” returns whether self ends with suffix. every story needs a good ending πŸŽ€
  • find(self, tgt: string|byte_span[, start: integer]) β†’ integer|nil β€” finds the first substring equal to tgt and returns its index, or nil if it's not found. no luck? nil. bye πŸ‘‹
  • rfind(self, tgt: string|byte_span[, end_: integer]) β†’ integer|nil β€” finds the last substring equal to tgt and returns its index, or nil if not found. reverse search, reverse card πŸ”„
  • find_first_of(self, strlist: string|byte_span[, start: integer]) β†’ integer|nil β€” finds the first octet equal to any of the octets within strlist and returns its index, or nil if not found. it's a whole cast of characters and she's looking for the first one on set ✨
  • find_last_of(self, strlist: string|byte_span[, end_: integer]) β†’ integer|nil β€” finds the last octet equal to any of the octets within strlist and returns its index, or nil if not found. last one to leave the party πŸŽ‰
  • find_first_not_of(self, strlist: string|byte_span[, start: integer]) β†’ integer|nil β€” finds the first octet not equal to any of the octets within strlist and returns its index, or nil if not found. the not-like-the-other-girls search πŸ’…
  • find_last_not_of(self, strlist: string|byte_span[, end: integer]) β†’ integer|nil β€” finds the last octet not equal to any of the octets within strlist and returns its index, or nil if not found. same energy, other end πŸ¦‹
  • trimmed(self[, lws: string|byte_span = " \f\n\r\t\v"]) β†’ byte_span β€” returns a slice from self that doesn't start nor end with any octet from lws. the default lws is " \f\n\r\t\v" β€” like, space, form feed, newline, carriage return, tab and vertical tab. she's neat, she's tidy, she's trimmed 🧚
  • inplace_lower(self) β€” converts every upper ASCII character from self to its lower version. lowercase era 😌
  • inplace_upper(self) β€” converts every lower ASCII character from self to its upper version. SHOUTING ERA πŸ“£πŸ”₯

FUNCTIONS (PRIMITIVE TYPES SERIALIZATION)

okay bestie, these ones are the byte-nerd girlies πŸ’… they operate in terms of bytes, and they're endianness-aware (yes they know the difference, they're not messy). Hand them a byte_span of the wrong size and they will throw EINVAL at you, no cap. The cute part? Data doesn't need to be aligned ✨ lowkey iconic.

the unsigned getters 🍼

  • get_u16be(self) β†’ integer β€” reads self (has to be 2 bytes long, she checks) as an unsigned 16-bit integer in big endian order and gives you the result 🫢
  • get_u16le(self) β†’ integer β€” same 2-byte requirement, but this time it's an unsigned 16-bit integer in little endian order, and you get the result back
  • get_u24be(self) β†’ integer β€” self (3 bytes long, periodt) interpreted as an unsigned 24-bit integer, big endian order, returns the result ✨
  • get_u24le(self) β†’ integer β€” 3 bytes of self, unsigned 24-bit integer, little endian order, result returned
  • get_u32be(self) β†’ integer β€” self (4 bytes long, exactly) as an unsigned 32-bit integer, big endian order, and boom, the result 🌸
  • get_u32le(self) β†’ integer β€” 4 bytes long, unsigned 32-bit integer, little endian order, result returned
  • get_u40be(self) β†’ integer β€” 5 bytes long, unsigned 40-bit integer, big endian order, result returned πŸ¦‹
  • get_u40le(self) β†’ integer β€” 5 bytes long, unsigned 40-bit integer, little endian order, result returned
  • get_u48be(self) β†’ integer β€” 6 bytes long, unsigned 48-bit integer, big endian order, result returned πŸ’œ
  • get_u48le(self) β†’ integer β€” 6 bytes long, unsigned 48-bit integer, little endian order, result returned

the signed getters 😭

  • get_i8(self) β†’ integer β€” self (must be 1 byte long) interpreted as a signed 8-bit integer, result returned
πŸ“Œ note β€” get_u8() doesn’t exist as you can just index instead.
  • get_i16be(self) β†’ integer β€” 2 bytes long, signed 16-bit integer, big endian order, result returned
  • get_i16le(self) β†’ integer β€” 2 bytes long, signed 16-bit integer, little endian order, result returned
  • get_i24be(self) β†’ integer β€” 3 bytes long, signed 24-bit integer, big endian order, result returned
  • get_i24le(self) β†’ integer β€” 3 bytes long, signed 24-bit integer, little endian order, result returned
  • get_i32be(self) β†’ integer β€” 4 bytes long, signed 32-bit integer, big endian order, result returned πŸ”₯
  • get_i32le(self) β†’ integer β€” 4 bytes long, signed 32-bit integer, little endian order, result returned
  • get_i40be(self) β†’ integer β€” 5 bytes long, signed 40-bit integer, big endian order, result returned
  • get_i40le(self) β†’ integer β€” 5 bytes long, signed 40-bit integer, little endian order, result returned
  • get_i48be(self) β†’ integer β€” 6 bytes long, signed 48-bit integer, big endian order, result returned
  • get_i48le(self) β†’ integer β€” 6 bytes long, signed 48-bit integer, little endian order, result returned

the float getters 🧚

  • get_f32be(self) β†’ number β€” self (4 bytes long) read as a 32-bit floating point number, big endian order, result returned ✨
  • get_f32le(self) β†’ number β€” 4 bytes long, 32-bit floating point number, little endian order, result returned
  • get_f64be(self) β†’ number β€” 8 bytes long, 64-bit floating point number, big endian order, result returned πŸ’–
  • get_f64le(self) β†’ number β€” 8 bytes long, 64-bit floating point number, little endian order, result returned

the unsigned setters πŸ’…

bestie, these ones write into the stored byte sequence instead of reading it. same energy, opposite direction.

  • set_u16be(self, n: integer) β€” sets the stored byte sequence (must be 2 bytes long) to represent the unsigned 16-bit integer n, big endian order 🫢
  • set_u16le(self, n: integer) β€” 2 bytes long, unsigned 16-bit integer n, little endian order
  • set_u24be(self, n: integer) β€” 3 bytes long, unsigned 24-bit integer n, big endian order ✨
  • set_u24le(self, n: integer) β€” 3 bytes long, unsigned 24-bit integer n, little endian order
  • set_u32be(self, n: integer) β€” 4 bytes long, unsigned 32-bit integer n, big endian order 🌸
  • set_u32le(self, n: integer) β€” 4 bytes long, unsigned 32-bit integer n, little endian order
  • set_u40be(self, n: integer) β€” 5 bytes long, unsigned 40-bit integer n, big endian order πŸ¦‹
  • set_u40le(self, n: integer) β€” 5 bytes long, unsigned 40-bit integer n, little endian order
  • set_u48be(self, n: integer) β€” 6 bytes long, unsigned 48-bit integer n, big endian order πŸ’œ
  • set_u48le(self, n: integer) β€” 6 bytes long, unsigned 48-bit integer n, little endian order

the signed setters πŸŽ€

  • set_i8(self, n: integer) β€” sets the stored byte sequence (must be 1 bytes long) to represent the signed byte n
πŸ“Œ note β€” set_u8() doesn’t exist as you can just index instead.
  • set_i16be(self, n: integer) β€” 2 bytes long, signed 16-bit integer n, big endian order
  • set_i16le(self, n: integer) β€” 2 bytes long, signed 16-bit integer n, little endian order
  • set_i24be(self, n: integer) β€” 3 bytes long, signed 24-bit integer n, big endian order
  • set_i24le(self, n: integer) β€” 3 bytes long, signed 24-bit integer n, little endian order
  • set_i32be(self, n: integer) β€” 4 bytes long, signed 32-bit integer n, big endian order πŸ”₯
  • set_i32le(self, n: integer) β€” 4 bytes long, signed 32-bit integer n, little endian order
  • set_i40be(self, n: integer) β€” 5 bytes long, signed 40-bit integer n, big endian order
  • set_i40le(self, n: integer) β€” 5 bytes long, signed 40-bit integer n, little endian order
  • set_i48be(self, n: integer) β€” 6 bytes long, signed 48-bit integer n, big endian order
  • set_i48le(self, n: integer) β€” 6 bytes long, signed 48-bit integer n, little endian order

the float setters 🌈

  • set_f32be(self, n: number) β€” sets the stored byte sequence (must be 4 bytes long) to represent the 32-bit floating point number n, big endian order ✨
  • set_f32le(self, n: number) β€” 4 bytes long, 32-bit floating point number n, little endian order
  • set_f64be(self, n: number) β€” 8 bytes long, 64-bit floating point number n, big endian order πŸ’–
  • set_f64le(self, n: number) β€” 8 bytes long, 64-bit floating point number n, little endian order

✨ PROPERTIES ✨

capacity : integer

the capacity. that's it. that's the whole property πŸ’… she said "I contain multitudes but today I'm just gonna tell you how much I can hold".


πŸ¦‹ METAMETHODS πŸ¦‹

she's got the full set, bestie, the whole personality:

  • __tostring()
  • __len()
  • __index()
  • __newindex()
  • __eq()
πŸ“Œ note β€” you can index the spans by numerical valued keys, and the numerical (ASCII) value for the underlying byte will be returned (or assigned on __newindex()). iykyk 🫢

🌸 NOTES 🌸

  • [1] Allocation strategy (the new byte_span's capacity) is left unspecified and may change among Emilua releases. so don't build your whole life plan around it, fr fr πŸ’€

and that's the whole page, bestie!! πŸŽ€ mwah πŸ’‹

this was Emilua 0.12.1-nixpkgs1, dated 1980-01-01, and yeah β€” it's EMILUA(3em), the one and only πŸ‘‘

πŸ“¦ where this page comes from