Livt packages
Livt.Base
Version 1.2.1
Foundational bit, array, string, conversion, formatting, and timing helpers for Livt hardware designs.
Published files (32)
src/String.lvt
namespace Livt.String
/**
* String search, comparison, and parsing helpers.
*
* Strings in Livt are most conveniently worked with as encoded byte arrays.
* Use "text".Encode() to convert a string literal to a byte array, and
* byteArray.Decode() to convert back to a string variable.
*
* These helpers operate directly on byte arrays so they work with both
* encode/decode patterns and raw protocol data.
*
* Example — HTTP method matching:
* var str: StringHelper = new StringHelper()
* var method: byte[3] = "GET".Encode()
* if (str.StartsWith(rxBuf, 128, method, 3)) { ... }
*/
component StringHelper
{
/**
* Returns true if the first prefixSize bytes of data equal prefix.
* Returns false if prefixSize is greater than dataSize.
*/
public fn StartsWith(data: byte[], dataSize: int, prefix: byte[], prefixSize: int) bool
{
if (prefixSize > dataSize) {
return false
}
for (var i = 0; i < prefixSize; i++) {
var d: byte = data[i]
var p: byte = prefix[i]
if (d != p) {
return false
}
}
return true
}
/**
* Returns true if the last suffixSize bytes of data equal suffix.
* Returns false if suffixSize is greater than dataSize.
*/
public fn EndsWith(data: byte[], dataSize: int, suffix: byte[], suffixSize: int) bool
{
if (suffixSize > dataSize) {
return false
}
var offset: int = dataSize - suffixSize
for (var i = 0; i < suffixSize; i++) {
var idx: int = offset + i
var d: byte = data[idx]
var s: byte = suffix[i]
if (d != s) {
return false
}
}
return true
}
/**
* Returns true if sub occurs anywhere within the first dataSize bytes of data.
* Returns false if subSize is greater than dataSize, or if subSize is zero.
*/
public fn Contains(data: byte[], dataSize: int, sub: byte[], subSize: int) bool
{
if (subSize > dataSize) {
return false
}
if (subSize == 0) {
return false
}
var limit: int = dataSize - subSize + 1
var matches: bool = false
for (var i = 0; i < limit; i++) {
matches = true
for (var j = 0; j < subSize; j++) {
var cidx: int = i + j
var cd: byte = data[cidx]
var cs: byte = sub[j]
if (cd != cs) {
matches = false
}
}
if (matches == true) {
return true
}
}
return false
}
/**
* Returns the index of the first occurrence of sub within the first
* dataSize bytes of data. Returns -1 if sub is not found, if subSize
* is greater than dataSize, or if subSize is zero.
*/
public fn IndexOf(data: byte[], dataSize: int, sub: byte[], subSize: int) int
{
if (subSize > dataSize) {
return -1
}
if (subSize == 0) {
return -1
}
var limit: int = dataSize - subSize + 1
var imatch: bool = false
for (var ii = 0; ii < limit; ii++) {
imatch = true
for (var jj = 0; jj < subSize; jj++) {
var iidx: int = ii + jj
var iid: byte = data[iidx]
var iis: byte = sub[jj]
if (iid != iis) {
imatch = false
}
}
if (imatch == true) {
return ii
}
}
return -1
}
/**
* Returns the index of the last occurrence of sub within the first
* dataSize bytes of data. Returns -1 if sub is not found, if subSize
* is greater than dataSize, or if subSize is zero.
*
* Example — find last path separator:
* var sep: byte[1] = "/".Encode()
* var pos: int = str.LastIndexOf(path, pathLen, sep, 1)
*/
public fn LastIndexOf(data: byte[], dataSize: int, sub: byte[], subSize: int) int
{
if (subSize > dataSize) {
return -1
}
if (subSize == 0) {
return -1
}
var llimit: int = dataSize - subSize + 1
var lastIdx: int = -1
var lmatch: bool = false
for (var li = 0; li < llimit; li++) {
lmatch = true
for (var lj = 0; lj < subSize; lj++) {
var lidx: int = li + lj
var ld: byte = data[lidx]
var ls: byte = sub[lj]
if (ld != ls) {
lmatch = false
}
}
if (lmatch == true) {
lastIdx = li
}
}
return lastIdx
}
/**
* Returns true if sub matches the bytes of data starting at position pos.
* Returns false if pos is negative, if pos + subSize exceeds dataSize, or
* if subSize is zero.
*
* Example — check HTTP header field at known offset:
* var colon: byte[1] = ":".Encode()
* if (str.EqualsAt(header, 64, colonPos, colon, 1)) { ... }
*/
public fn EqualsAt(data: byte[], dataSize: int, pos: int, sub: byte[], subSize: int) bool
{
if (subSize == 0) {
return false
}
if (pos < 0) {
return false
}
var eend: int = pos + subSize
if (eend > dataSize) {
return false
}
for (var ei = 0; ei < subSize; ei++) {
var eidx: int = pos + ei
var ed: byte = data[eidx]
var es: byte = sub[ei]
if (ed != es) {
return false
}
}
return true
}
/**
* Returns true if the first aSize bytes of a equal the first bSize bytes of b,
* ignoring ASCII case (A-Z treated same as a-z). Returns false if aSize != bSize.
*
* Example — case-insensitive HTTP header name matching:
* var name: byte[13] = "Content-Type:".Encode()
* if (str.EqualsIgnoreCase(header, 13, name, 13)) { ... }
*/
public fn EqualsIgnoreCase(a: byte[], aSize: int, b: byte[], bSize: int) bool
{
if (aSize != bSize) {
return false
}
for (var ici = 0; ici < aSize; ici++) {
var ica: int = a[ici] as int
var icb: int = b[ici] as int
if (ica >= 65) {
if (ica <= 90) {
ica = ica + 32
}
}
if (icb >= 65) {
if (icb <= 90) {
icb = icb + 32
}
}
if (ica != icb) {
return false
}
}
return true
}
/**
* Returns the number of bytes before the first null byte (0x00) in data,
* searching up to maxSize bytes. Returns maxSize if no null byte is found.
* Returns 0 if the first byte is null or if maxSize is zero.
*
* Example — measure a UART command received into a buffer:
* var len: int = str.NullTerminatedLength(rxBuf, 64)
*/
public fn NullTerminatedLength(data: byte[], maxSize: int) int
{
for (var ni = 0; ni < maxSize; ni++) {
var nb: byte = data[ni]
var nbi: int = nb as int
if (nbi == 0) {
return ni
}
}
return maxSize
}
/**
* Parses an ASCII decimal unsigned integer from the first dataSize bytes of data.
* Returns -1 if dataSize is zero or if any byte is not '0'-'9'.
* No overflow protection: caller must ensure the value fits in a signed int.
*
* Example — parse TCP port from UART config "8080":
* var port: int = str.ParseDecimalUInt(buf, 4)
*/
public fn ParseDecimalUInt(data: byte[], dataSize: int) int
{
if (dataSize == 0) {
return -1
}
var dresult: int = 0
for (var di = 0; di < dataSize; di++) {
var dc: byte = data[di]
var dci: int = dc as int
if (dci < 48) {
return -1
}
if (dci > 57) {
return -1
}
var ddigit: int = dci - 48
dresult = dresult * 10 + ddigit
}
return dresult
}
/**
* Parses an ASCII hexadecimal unsigned integer from the first dataSize bytes of data.
* Accepts '0'-'9', 'a'-'f', and 'A'-'F'. Leading "0x" prefix is not accepted.
* Returns -1 if dataSize is zero or if any byte is not a valid hex character.
* No overflow protection: caller must ensure the value fits in a signed int.
*
* Example — parse a register address from UART input "1a2f":
* var addr: int = str.ParseHexUInt(buf, 4)
*/
public fn ParseHexUInt(data: byte[], dataSize: int) int
{
if (dataSize == 0) {
return -1
}
var hresult: int = 0
for (var hi = 0; hi < dataSize; hi++) {
var hc: byte = data[hi]
var hci: int = hc as int
var hdigit: int = 0
var hvalid: bool = false
if (hci >= 48) {
if (hci <= 57) {
hdigit = hci - 48
hvalid = true
}
}
if (hci >= 65) {
if (hci <= 70) {
hdigit = hci - 55
hvalid = true
}
}
if (hci >= 97) {
if (hci <= 102) {
hdigit = hci - 87
hvalid = true
}
}
if (hvalid == false) {
return -1
}
hresult = hresult * 16 + hdigit
}
return hresult
}
}