diff options
| author | Jeroen van Rijn <Kelimion@users.noreply.github.com> | 2023-03-28 21:05:13 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2023-03-28 21:05:13 +0200 |
| commit | ab7e1e01de2d62173b520535d7d87c48eec3f6a6 (patch) | |
| tree | 4fbf3bac33130741f821291988b8e899e8a0ba42 /core/strings | |
| parent | 4c12addcafc5be4337d1b4c8be8f0c8be76a1203 (diff) | |
| parent | bbafc3fbd62124b4d6602424025f2226a99f408b (diff) | |
Merge pull request #2420 from jon-lipstate/string_docs
string code docs
Diffstat (limited to 'core/strings')
| -rw-r--r-- | core/strings/ascii_set.odin | 26 | ||||
| -rw-r--r-- | core/strings/builder.odin | 565 | ||||
| -rw-r--r-- | core/strings/conversion.odin | 338 | ||||
| -rw-r--r-- | core/strings/intern.odin | 69 | ||||
| -rw-r--r-- | core/strings/reader.odin | 154 | ||||
| -rw-r--r-- | core/strings/strings.odin | 1965 |
6 files changed, 2552 insertions, 565 deletions
diff --git a/core/strings/ascii_set.odin b/core/strings/ascii_set.odin index 9b59666f3..11ad8b947 100644 --- a/core/strings/ascii_set.odin +++ b/core/strings/ascii_set.odin @@ -3,9 +3,22 @@ package strings import "core:unicode/utf8" +/* +Ascii_Set is designed to store ASCII characters efficiently as a bit-array +Each bit in the array corresponds to a specific ASCII character, where the value of the bit (0 or 1) +indicates if the character is present in the set or not. +*/ Ascii_Set :: distinct [8]u32 +/* +Creates an Ascii_Set with unique characters from the input string. -// create an ascii set of all unique characters in the string +**Inputs** +- chars: A string containing characters to include in the Ascii_Set. + +**Returns** +- as: An Ascii_Set with unique characters from the input string. +- ok: false if any character in the input string is not a valid ASCII character. +*/ ascii_set_make :: proc(chars: string) -> (as: Ascii_Set, ok: bool) #no_bounds_check { for i in 0..<len(chars) { c := chars[i] @@ -17,8 +30,15 @@ ascii_set_make :: proc(chars: string) -> (as: Ascii_Set, ok: bool) #no_bounds_ch ok = true return } +/* +Determines if a given char is contained within an Ascii_Set. + +**Inputs** +- as: The Ascii_Set to search. +- c: The char to check for in the Ascii_Set. -// returns true when the `c` byte is contained in the `as` ascii set +**Returns** A boolean indicating if the byte is contained in the Ascii_Set (true) or not (false). +*/ ascii_set_contains :: proc(as: Ascii_Set, c: byte) -> bool #no_bounds_check { return as[c>>5] & (1<<(c&31)) != 0 -}
\ No newline at end of file +} diff --git a/core/strings/builder.odin b/core/strings/builder.odin index a6d5b78b4..15bda1b2d 100644 --- a/core/strings/builder.odin +++ b/core/strings/builder.odin @@ -4,68 +4,126 @@ import "core:runtime" import "core:unicode/utf8" import "core:strconv" import "core:io" +/* +Type definition for a procedure that flushes a Builder -Builder_Flush_Proc :: #type proc(b: ^Builder) -> (do_reset: bool) +**Inputs** +- b: A pointer to the Builder +**Returns** A boolean indicating whether the Builder should be reset +*/ +Builder_Flush_Proc :: #type proc(b: ^Builder) -> (do_reset: bool) /* - dynamic byte buffer / string builder with helper procedures - the dynamic array is wrapped inside the struct to be more opaque - you can use `fmt.sbprint*` procedures with a `^strings.Builder` directly +A dynamic byte buffer / string builder with helper procedures +The dynamic array is wrapped inside the struct to be more opaque +You can use `fmt.sbprint*` procedures with a `^strings.Builder` directly */ Builder :: struct { buf: [dynamic]byte, } +/* +Produces a Builder with a default length of 0 and cap of 16 + +*Allocates Using Provided Allocator* -// return a builder, default length 0 / cap 16 are done through make +**Inputs** +- allocator: (default is context.allocator) + +**Returns** A new Builder +*/ builder_make_none :: proc(allocator := context.allocator) -> Builder { return Builder{buf=make([dynamic]byte, allocator)} } +/* +Produces a Builder with a specified length and cap of max(16,len) byte buffer + +*Allocates Using Provided Allocator* + +**Inputs** +- len: The desired length of the Builder's buffer +- allocator: (default is context.allocator) -// return a builder, with a set length `len` and cap 16 byte buffer +**Returns** A new Builder +*/ builder_make_len :: proc(len: int, allocator := context.allocator) -> Builder { return Builder{buf=make([dynamic]byte, len, allocator)} } +/* +Produces a Builder with a specified length and cap + +*Allocates Using Provided Allocator* -// return a builder, with a set length `len` byte buffer and a custom `cap` +**Inputs** +- len: The desired length of the Builder's buffer +- cap: The desired capacity of the Builder's buffer, cap is max(cap, len) +- allocator: (default is context.allocator) + +**Returns** A new Builder +*/ builder_make_len_cap :: proc(len, cap: int, allocator := context.allocator) -> Builder { return Builder{buf=make([dynamic]byte, len, cap, allocator)} } - // overload simple `builder_make_*` with or without len / cap parameters builder_make :: proc{ builder_make_none, builder_make_len, builder_make_len_cap, } +/* +Initializes a Builder with a length of 0 and cap of 16 +It replaces the existing `buf` + +*Allocates Using Provided Allocator* + +**Inputs** +- b: A pointer to the Builder +- allocator: (default is context.allocator) -// initialize a builder, default length 0 / cap 16 are done through make -// replaces the existing `buf` +**Returns** initialized ^Builder +*/ builder_init_none :: proc(b: ^Builder, allocator := context.allocator) -> ^Builder { b.buf = make([dynamic]byte, allocator) return b } +/* +Initializes a Builder with a specified length and cap, which is max(len,16) +It replaces the existing `buf` -// initialize a builder, with a set length `len` and cap 16 byte buffer -// replaces the existing `buf` +*Allocates Using Provided Allocator* + +**Inputs** +- b: A pointer to the Builder +- len: The desired length of the Builder's buffer +- allocator: (default is context.allocator) + +**Returns** Initialized ^Builder +*/ builder_init_len :: proc(b: ^Builder, len: int, allocator := context.allocator) -> ^Builder { b.buf = make([dynamic]byte, len, allocator) return b } +/* +Initializes a Builder with a specified length and cap +It replaces the existing `buf` + +**Inputs** +- b: A pointer to the Builder +- len: The desired length of the Builder's buffer +- cap: The desired capacity of the Builder's buffer, actual max(len,cap) +- allocator: (default is context.allocator) -// initialize a builder, with a set length `len` byte buffer and a custom `cap` -// replaces the existing `buf` +**Returns** A pointer to the initialized Builder +*/ builder_init_len_cap :: proc(b: ^Builder, len, cap: int, allocator := context.allocator) -> ^Builder { b.buf = make([dynamic]byte, len, cap, allocator) return b } - -// overload simple `builder_init_*` with or without len / ap parameters +// Overload simple `builder_init_*` with or without len / ap parameters builder_init :: proc{ builder_init_none, builder_init_len, builder_init_len_cap, } - @(private) _builder_stream_vtable_obj := io.Stream_VTable{ impl_write = proc(s: io.Stream, p: []byte) -> (n: int, err: io.Error) { @@ -90,50 +148,89 @@ _builder_stream_vtable_obj := io.Stream_VTable{ }, impl_destroy = proc(s: io.Stream) -> io.Error { b := (^Builder)(s.stream_data) - delete(b.buf) + builder_destroy(b) return .None }, } - // NOTE(dweiler): Work around a miscompilation bug on Linux still. @(private) _builder_stream_vtable := &_builder_stream_vtable_obj +/* +Returns an io.Stream from a Builder + +**Inputs** +- b: A pointer to the Builder -// return an `io.Stream` from a builder +**Returns** An io.Stream +*/ to_stream :: proc(b: ^Builder) -> io.Stream { return io.Stream{stream_vtable=_builder_stream_vtable, stream_data=b} } +/* +Returns an io.Writer from a Builder + +**Inputs** +- b: A pointer to the Builder -// return an `io.Writer` from a builder +**Returns** An io.Writer +*/ to_writer :: proc(b: ^Builder) -> io.Writer { return io.to_writer(to_stream(b)) } +/* +Deletes the Builder byte buffer content -// delete and clear the builder byte buffer content +**Inputs** +- b: A pointer to the Builder +*/ builder_destroy :: proc(b: ^Builder) { delete(b.buf) - clear(&b.buf) + b.buf = nil } +/* +Reserves the Builder byte buffer to a specific capacity, when it's higher than before -// reserve the builfer byte buffer to a specific cap, when it's higher than before +**Inputs** +- b: A pointer to the Builder +- cap: The desired capacity for the Builder's buffer +*/ builder_grow :: proc(b: ^Builder, cap: int) { reserve(&b.buf, cap) } +/* +Clears the Builder byte buffer content (sets len to zero) -// clear the builder byte buffer content +**Inputs** +- b: A pointer to the Builder +*/ builder_reset :: proc(b: ^Builder) { clear(&b.buf) } - /* - create an empty builder with the same slice length as its cap - uses the `mem.nil_allocator` to avoid allocation and keep a fixed length - used in `fmt.bprint*` - - bytes: [8]byte // <-- gets filled - builder := strings.builder_from_bytes(bytes[:]) - strings.write_byte(&builder, 'a') -> "a" - strings.write_byte(&builder, 'b') -> "ab" +Creates a Builder from a slice of bytes with the same slice length as its capacity. Used in fmt.bprint* + +*Uses Nil Allocator - Does NOT allocate* + +**Inputs** +- backing: A slice of bytes to be used as the backing buffer + +Example: + + import "core:fmt" + import "core:strings" + strings_builder_from_bytes_example :: proc() { + bytes: [8]byte // <-- gets filled + builder := strings.builder_from_bytes(bytes[:]) + fmt.println(strings.write_byte(&builder, 'a')) // -> "a" + fmt.println(strings.write_byte(&builder, 'b')) // -> "ab" + } + +Output: + + a + ab + +**Returns** A new Builder */ builder_from_bytes :: proc(backing: []byte) -> Builder { s := transmute(runtime.Raw_Slice)backing @@ -147,36 +244,78 @@ builder_from_bytes :: proc(backing: []byte) -> Builder { buf = transmute([dynamic]byte)d, } } +// Alias to `builder_from_bytes` builder_from_slice :: builder_from_bytes +/* +Casts the Builder byte buffer to a string and returns it -// cast the builder byte buffer to a string and return it +**Inputs** +- b: A Builder + +**Returns** The contents of the Builder's buffer, as a string +*/ to_string :: proc(b: Builder) -> string { return string(b.buf[:]) } +/* +Returns the length of the Builder's buffer, in bytes -// return the length of the builder byte buffer +**Inputs** +- b: A Builder + +**Returns** The length of the Builder's buffer +*/ builder_len :: proc(b: Builder) -> int { return len(b.buf) } +/* +Returns the capacity of the Builder's buffer, in bytes + +**Inputs** +- b: A Builder -// return the cap of the builder byte buffer +**Returns** The capacity of the Builder's buffer +*/ builder_cap :: proc(b: Builder) -> int { return cap(b.buf) } +/* +The free space left in the Builder's buffer, in bytes + +**Inputs** +- b: A Builder -// returns the space left in the builder byte buffer to use up +**Returns** The available space left in the Builder's buffer +*/ builder_space :: proc(b: Builder) -> int { return cap(b.buf) - len(b.buf) } - /* - appends a byte to the builder, returns the append diff +Appends a byte to the Builder and returns the number of bytes appended + +**Inputs** +- b: A pointer to the Builder +- x: The byte to be appended - builder := strings.builder_make() - strings.write_byte(&builder, 'a') // 1 - strings.write_byte(&builder, 'b') // 1 - strings.write_byte(&builder, 'c') // 1 - fmt.println(strings.to_string(builder)) // -> abc +Example: + + import "core:fmt" + import "core:strings" + + strings_write_byte_example :: proc() { + builder := strings.builder_make() + strings.write_byte(&builder, 'a') // 1 + strings.write_byte(&builder, 'b') // 1 + fmt.println(strings.to_string(builder)) // -> ab + } + +Output: + + ab + +NOTE: The backing dynamic array may be fixed in capacity or fail to resize, `n` states the number actually written. + +**Returns** The number of bytes appended */ write_byte :: proc(b: ^Builder, x: byte) -> (n: int) { n0 := len(b.buf) @@ -184,14 +323,28 @@ write_byte :: proc(b: ^Builder, x: byte) -> (n: int) { n1 := len(b.buf) return n1-n0 } - /* - appends a slice of bytes to the builder, returns the append diff +Appends a slice of bytes to the Builder and returns the number of bytes appended - builder := strings.builder_make() - bytes := [?]byte { 'a', 'b', 'c' } - strings.write_bytes(&builder, bytes[:]) // 3 - fmt.println(strings.to_string(builder)) // -> abc +**Inputs** +- b: A pointer to the Builder +- x: The slice of bytes to be appended + +Example: + + import "core:fmt" + import "core:strings" + + strings_write_bytes_example :: proc() { + builder := strings.builder_make() + bytes := [?]byte { 'a', 'b', 'c' } + strings.write_bytes(&builder, bytes[:]) // 3 + fmt.println(strings.to_string(builder)) // -> abc + } + +NOTE: The backing dynamic array may be fixed in capacity or fail to resize, `n` states the number actually written. + +**Returns** The number of bytes appended */ write_bytes :: proc(b: ^Builder, x: []byte) -> (n: int) { n0 := len(b.buf) @@ -199,42 +352,93 @@ write_bytes :: proc(b: ^Builder, x: []byte) -> (n: int) { n1 := len(b.buf) return n1-n0 } - /* - appends a single rune into the builder, returns written rune size and an `io.Error` +Appends a single rune to the Builder and returns the number of bytes written and an `io.Error` + +**Inputs** +- b: A pointer to the Builder +- r: The rune to be appended + +Example: + + import "core:fmt" + import "core:strings" - builder := strings.builder_make() - strings.write_rune(&builder, 'ä') // 2 None - strings.write_rune(&builder, 'b') // 1 None - strings.write_rune(&builder, 'c') // 1 None - fmt.println(strings.to_string(builder)) // -> äbc + strings_write_rune_example :: proc() { + builder := strings.builder_make() + strings.write_rune(&builder, 'ä') // 2 None + strings.write_rune(&builder, 'b') // 1 None + fmt.println(strings.to_string(builder)) // -> äb + } + +Output: + + äb + +NOTE: The backing dynamic array may be fixed in capacity or fail to resize, `n` states the number actually written. + +**Returns** The number of bytes written and an io.Error (if any) */ write_rune :: proc(b: ^Builder, r: rune) -> (int, io.Error) { return io.write_rune(to_writer(b), r) } - /* - appends a quoted rune into the builder, returns written size +Appends a quoted rune to the Builder and returns the number of bytes written + +**Inputs** +- b: A pointer to the Builder +- r: The rune to be appended + +Example: + + import "core:fmt" + import "core:strings" + + strings_write_quoted_rune_example :: proc() { + builder := strings.builder_make() + strings.write_string(&builder, "abc") // 3 + strings.write_quoted_rune(&builder, 'ä') // 4 + strings.write_string(&builder, "abc") // 3 + fmt.println(strings.to_string(builder)) // -> abc'ä'abc + } - builder := strings.builder_make() - strings.write_string(&builder, "abc") // 3 - strings.write_quoted_rune(&builder, 'ä') // 4 - strings.write_string(&builder, "abc") // 3 - fmt.println(strings.to_string(builder)) // -> abc'ä'abc +Output: + + abc'ä'abc + +NOTE: The backing dynamic array may be fixed in capacity or fail to resize, `n` states the number actually written. + +**Returns** The number of bytes written */ write_quoted_rune :: proc(b: ^Builder, r: rune) -> (n: int) { return io.write_quoted_rune(to_writer(b), r) } +/* +Appends a string to the Builder and returns the number of bytes written +**Inputs** +- b: A pointer to the Builder +- s: The string to be appended -/* - appends a string to the builder, return the written byte size - - builder := strings.builder_make() - strings.write_string(&builder, "a") // 1 - strings.write_string(&builder, "bc") // 2 - strings.write_string(&builder, "xyz") // 3 - fmt.println(strings.to_string(builder)) // -> abcxyz +Example: + + import "core:fmt" + import "core:strings" + + strings_write_string_example :: proc() { + builder := strings.builder_make() + strings.write_string(&builder, "a") // 1 + strings.write_string(&builder, "bc") // 2 + fmt.println(strings.to_string(builder)) // -> abc + } + +Output: + + abc + +NOTE: The backing dynamic array may be fixed in capacity or fail to resize, `n` states the number actually written. + +**Returns** The number of bytes written */ write_string :: proc(b: ^Builder, s: string) -> (n: int) { n0 := len(b.buf) @@ -242,10 +446,14 @@ write_string :: proc(b: ^Builder, s: string) -> (n: int) { n1 := len(b.buf) return n1-n0 } +/* +Pops and returns the last byte in the Builder or 0 when the Builder is empty +**Inputs** +- b: A pointer to the Builder -// pops and returns the last byte in the builder -// returns 0 when the builder is empty +**Returns** The last byte in the Builder or 0 if empty +*/ pop_byte :: proc(b: ^Builder) -> (r: byte) { if len(b.buf) == 0 { return 0 @@ -256,9 +464,14 @@ pop_byte :: proc(b: ^Builder) -> (r: byte) { d.len = max(d.len-1, 0) return } +/* +Pops the last rune in the Builder and returns the popped rune and its rune width or (0, 0) if empty + +**Inputs** +- b: A pointer to the Builder -// pops the last rune in the builder and returns the popped rune and its rune width -// returns 0, 0 when the builder is empty +**Returns** The popped rune and its rune width or (0, 0) if empty +*/ pop_rune :: proc(b: ^Builder) -> (r: rune, width: int) { if len(b.buf) == 0 { return 0, 0 @@ -269,41 +482,110 @@ pop_rune :: proc(b: ^Builder) -> (r: rune, width: int) { d.len = max(d.len-width, 0) return } - @(private) DIGITS_LOWER := "0123456789abcdefx" - /* - append a quoted string into the builder, return the written byte size +**Inputs** +- b: A pointer to the Builder +- str: The string to be quoted and appended +- quote: The optional quote character (default is double quotes) + +Example: + + import "core:fmt" + import "core:strings" + + strings_write_quoted_string_example :: proc() { + builder := strings.builder_make() + strings.write_quoted_string(&builder, "a") // 3 + strings.write_quoted_string(&builder, "bc", '\'') // 4 + strings.write_quoted_string(&builder, "xyz") // 5 + fmt.println(strings.to_string(builder)) + } + +Output: + + "a"'bc'"xyz" - builder := strings.builder_make() - strings.write_quoted_string(&builder, "a") // 3 - strings.write_quoted_string(&builder, "bc", '\'') // 4 - strings.write_quoted_string(&builder, "xyz") // 5 - fmt.println(strings.to_string(builder)) // -> "a"'bc'xyz" +NOTE: The backing dynamic array may be fixed in capacity or fail to resize, `n` states the number actually written. + +**Returns** The number of bytes written */ write_quoted_string :: proc(b: ^Builder, str: string, quote: byte = '"') -> (n: int) { n, _ = io.write_quoted_string(to_writer(b), str, quote) return } +/* +Appends a rune to the Builder and returns the number of bytes written + +**Inputs** +- b: A pointer to the Builder +- r: The rune to be appended +- write_quote: Optional boolean flag to wrap in single-quotes (') (default is true) + +Example: + + import "core:fmt" + import "core:strings" + write_encoded_rune_example :: proc() { + builder := strings.builder_make() + strings.write_encoded_rune(&builder, 'a', false) // 1 + strings.write_encoded_rune(&builder, '\"', true) // 3 + strings.write_encoded_rune(&builder, 'x', false) // 1 + fmt.println(strings.to_string(builder)) + } + +Output: + + a'"'x -// appends a rune to the builder, optional `write_quote` boolean tag, returns the written rune size +NOTE: The backing dynamic array may be fixed in capacity or fail to resize, `n` states the number actually written. + +**Returns** The number of bytes written +*/ write_encoded_rune :: proc(b: ^Builder, r: rune, write_quote := true) -> (n: int) { n, _ = io.write_encoded_rune(to_writer(b), r, write_quote) return } +/* +Appends an escaped rune to the Builder and returns the number of bytes written + +**Inputs** +- b: A pointer to the Builder +- r: The rune to be appended +- quote: The quote character +- html_safe: Optional boolean flag to encode '<', '>', '&' as digits (default is false) + +**Usage** +- '\a' will be written as such +- `r` and `quote` match and `quote` is `\\` - they will be written as two slashes +- `html_safe` flag in case the runes '<', '>', '&' should be encoded as digits e.g. `\u0026` -// appends a rune to the builder, fully written out in case of escaped runes e.g. '\a' will be written as such -// when `r` and `quote` match and `quote` is `\\` - they will be written as two slashes -// `html_safe` flag in case the runes '<', '>', '&' should be encoded as digits e.g. `\u0026` +NOTE: The backing dynamic array may be fixed in capacity or fail to resize, `n` states the number actually written. + +**Returns** The number of bytes written +*/ write_escaped_rune :: proc(b: ^Builder, r: rune, quote: byte, html_safe := false) -> (n: int) { n, _ = io.write_escaped_rune(to_writer(b), r, quote, html_safe) return } +/* +Writes a f64 value to the Builder and returns the number of characters written -// writes a f64 value into the builder, returns the written amount of characters +**Inputs** +- b: A pointer to the Builder +- f: The f64 value to be appended +- fmt: The format byte +- prec: The precision +- bit_size: The bit size +- always_signed: Optional boolean flag to always include the sign (default is false) + +NOTE: The backing dynamic array may be fixed in capacity or fail to resize, `n` states the number actually written. + +**Returns** The number of characters written +*/ write_float :: proc(b: ^Builder, f: f64, fmt: byte, prec, bit_size: int, always_signed := false) -> (n: int) { buf: [384]byte s := strconv.append_float(buf[:], f, fmt, prec, bit_size) @@ -314,8 +596,19 @@ write_float :: proc(b: ^Builder, f: f64, fmt: byte, prec, bit_size: int, always_ } return write_string(b, s) } +/* +Writes a f16 value to the Builder and returns the number of characters written + +**Inputs** +- b: A pointer to the Builder +- f: The f16 value to be appended +- fmt: The format byte +- always_signed: Optional boolean flag to always include the sign -// writes a f16 value into the builder, returns the written amount of characters +NOTE: The backing dynamic array may be fixed in capacity or fail to resize, `n` states the number actually written. + +**Returns** The number of characters written +*/ write_f16 :: proc(b: ^Builder, f: f16, fmt: byte, always_signed := false) -> (n: int) { buf: [384]byte s := strconv.append_float(buf[:], f64(f), fmt, 2*size_of(f), 8*size_of(f)) @@ -324,8 +617,36 @@ write_f16 :: proc(b: ^Builder, f: f16, fmt: byte, always_signed := false) -> (n: } return write_string(b, s) } +/* +Writes a f32 value to the Builder and returns the number of characters written + +**Inputs** +- b: A pointer to the Builder +- f: The f32 value to be appended +- fmt: The format byte +- always_signed: Optional boolean flag to always include the sign + +Example: + + import "core:fmt" + import "core:strings" + + strings_write_f32_example :: proc() { + builder := strings.builder_make() + strings.write_f32(&builder, 3.14159, 'f') // 6 + strings.write_string(&builder, " - ") // 3 + strings.write_f32(&builder, -0.123, 'e') // 8 + fmt.println(strings.to_string(builder)) // -> 3.14159012 - -1.23000003e-01 + } + +Output: + + 3.14159012 - -1.23000003e-01 -// writes a f32 value into the builder, returns the written amount of characters +NOTE: The backing dynamic array may be fixed in capacity or fail to resize, `n` states the number actually written. + +**Returns** The number of characters written +*/ write_f32 :: proc(b: ^Builder, f: f32, fmt: byte, always_signed := false) -> (n: int) { buf: [384]byte s := strconv.append_float(buf[:], f64(f), fmt, 2*size_of(f), 8*size_of(f)) @@ -334,8 +655,19 @@ write_f32 :: proc(b: ^Builder, f: f32, fmt: byte, always_signed := false) -> (n: } return write_string(b, s) } +/* +Writes a f32 value to the Builder and returns the number of characters written -// writes a f64 value into the builder, returns the written amount of characters +**Inputs** +- b: A pointer to the Builder +- f: The f32 value to be appended +- fmt: The format byte +- always_signed: Optional boolean flag to always include the sign + +NOTE: The backing dynamic array may be fixed in capacity or fail to resize, `n` states the number actually written. + +**Returns** The number of characters written +*/ write_f64 :: proc(b: ^Builder, f: f64, fmt: byte, always_signed := false) -> (n: int) { buf: [384]byte s := strconv.append_float(buf[:], f64(f), fmt, 2*size_of(f), 8*size_of(f)) @@ -344,30 +676,67 @@ write_f64 :: proc(b: ^Builder, f: f64, fmt: byte, always_signed := false) -> (n: } return write_string(b, s) } +/* +Writes a u64 value to the Builder and returns the number of characters written +**Inputs** +- b: A pointer to the Builder +- i: The u64 value to be appended +- base: The optional base for the numeric representation +NOTE: The backing dynamic array may be fixed in capacity or fail to resize, `n` states the number actually written. -// writes a u64 value `i` in `base` = 10 into the builder, returns the written amount of characters +**Returns** The number of characters written +*/ write_u64 :: proc(b: ^Builder, i: u64, base: int = 10) -> (n: int) { buf: [32]byte s := strconv.append_bits(buf[:], i, base, false, 64, strconv.digits, nil) return write_string(b, s) } +/* +Writes a i64 value to the Builder and returns the number of characters written + +**Inputs** +- b: A pointer to the Builder +- i: The i64 value to be appended +- base: The optional base for the numeric representation + +NOTE: The backing dynamic array may be fixed in capacity or fail to resize, `n` states the number actually written. -// writes a i64 value `i` in `base` = 10 into the builder, returns the written amount of characters +**Returns** The number of characters written +*/ write_i64 :: proc(b: ^Builder, i: i64, base: int = 10) -> (n: int) { buf: [32]byte s := strconv.append_bits(buf[:], u64(i), base, true, 64, strconv.digits, nil) return write_string(b, s) } +/* +Writes a uint value to the Builder and returns the number of characters written + +**Inputs** +- b: A pointer to the Builder +- i: The uint value to be appended +- base: The optional base for the numeric representation -// writes a uint value `i` in `base` = 10 into the builder, returns the written amount of characters +NOTE: The backing dynamic array may be fixed in capacity or fail to resize, `n` states the number actually written. + +**Returns** The number of characters written +*/ write_uint :: proc(b: ^Builder, i: uint, base: int = 10) -> (n: int) { return write_u64(b, u64(i), base) } +/* +Writes a int value to the Builder and returns the number of characters written + +**Inputs** +- b: A pointer to the Builder +- i: The int value to be appended +- base: The optional base for the numeric representation + +NOTE: The backing dynamic array may be fixed in capacity or fail to resize, `n` states the number actually written. -// writes a int value `i` in `base` = 10 into the builder, returns the written amount of characters +**Returns** The number of characters written +*/ write_int :: proc(b: ^Builder, i: int, base: int = 10) -> (n: int) { return write_i64(b, i64(i), base) } - diff --git a/core/strings/conversion.odin b/core/strings/conversion.odin index df03442c5..d71dc4724 100644 --- a/core/strings/conversion.odin +++ b/core/strings/conversion.odin @@ -4,6 +4,20 @@ import "core:io" import "core:unicode" import "core:unicode/utf8" +/* +Converts invalid UTF-8 sequences in the input string `s` to the `replacement` string. + +*Allocates Using Provided Allocator* + +**Inputs** +- s: Input string that may contain invalid UTF-8 sequences. +- replacement: String to replace invalid UTF-8 sequences with. +- allocator: (default: context.allocator). + +WARNING: Allocation does not occur when len(s) == 0 + +**Returns** A valid UTF-8 string with invalid sequences replaced by `replacement`. +*/ to_valid_utf8 :: proc(s, replacement: string, allocator := context.allocator) -> string { if len(s) == 0 { return "" @@ -33,7 +47,7 @@ to_valid_utf8 :: proc(s, replacement: string, allocator := context.allocator) -> invalid := false - for i := 0; i < len(s); /**/ { + for i := 0; i < len(s); /**/{ c := s[i] if c < utf8.RUNE_SELF { i += 1 @@ -57,13 +71,29 @@ to_valid_utf8 :: proc(s, replacement: string, allocator := context.allocator) -> } return to_string(b) } - /* - returns the input string `s` with all runes set to lowered case - always allocates using the `allocator` +Converts the input string `s` to all lowercase characters. + +*Allocates Using Provided Allocator* + +**Inputs** +- s: Input string to be converted. +- allocator: (default: context.allocator). + +Example: + + import "core:fmt" + import "core:strings" + + strings_to_lower_example :: proc() { + fmt.println(strings.to_lower("TeST")) + } + +Output: - strings.to_lower("test") -> test - strings.to_lower("Test") -> test + test + +**Returns** A new string with all characters converted to lowercase. */ to_lower :: proc(s: string, allocator := context.allocator) -> string { b: Builder @@ -73,13 +103,29 @@ to_lower :: proc(s: string, allocator := context.allocator) -> string { } return to_string(b) } - /* - returns the input string `s` with all runes set to upper case - always allocates using the `allocator` +Converts the input string `s` to all uppercase characters. + +*Allocates Using Provided Allocator* + +**Inputs** +- s: Input string to be converted. +- allocator: (default: context.allocator). - strings.to_upper("test") -> TEST - strings.to_upper("Test") -> TEST +Example: + + import "core:fmt" + import "core:strings" + + strings_to_upper_example :: proc() { + fmt.println(strings.to_upper("Test")) + } + +Output: + + TEST + +**Returns** A new string with all characters converted to uppercase. */ to_upper :: proc(s: string, allocator := context.allocator) -> string { b: Builder @@ -89,21 +135,36 @@ to_upper :: proc(s: string, allocator := context.allocator) -> string { } return to_string(b) } +/* +Checks if the rune `r` is a delimiter (' ', '-', or '_'). + +**Inputs** +- r: Rune to check for delimiter status. -// returns true when the `c` rune is a space, '-' or '_' -// useful when treating strings like words in a text editor or html paths -is_delimiter :: proc(c: rune) -> bool { - return c == '-' || c == '_' || is_space(c) +**Returns** True if `r` is a delimiter, false otherwise. +*/ +is_delimiter :: proc(r: rune) -> bool { + return r == '-' || r == '_' || is_space(r) } +/* +Checks if the rune `r` is a non-alphanumeric or space character. + +**Inputs** +- r: Rune to check for separator status. -// returns true when the `r` rune is a non alpha or `unicode.is_space` rune +**Returns** True if `r` is a non-alpha or `unicode.is_space` rune. +*/ is_separator :: proc(r: rune) -> bool { if r <= 0x7f { switch r { - case '0'..='9': return false - case 'a'..='z': return false - case 'A'..='Z': return false - case '_': return false + case '0' ..= '9': + return false + case 'a' ..= 'z': + return false + case 'A' ..= 'Z': + return false + case '_': + return false } return true } @@ -115,12 +176,46 @@ is_separator :: proc(r: rune) -> bool { return unicode.is_space(r) } - /* - iterator that loops through the string and calls the callback with the `prev`, `curr` and `next` rune - on empty string `s` the callback gets called once with empty runes +Iterates over a string, calling a callback for each rune with the previous, current, and next runes as arguments. + +**Inputs** +- w: An io.Writer to be used by the callback for writing output. +- s: The input string to be iterated over. +- callback: A procedure to be called for each rune in the string, with arguments (w: io.Writer, prev, curr, next: rune). +The callback can utilize the provided io.Writer to write output during the iteration. + +Example: + + import "core:fmt" + import "core:strings" + import "core:io" + + strings_string_case_iterator_example :: proc() { + my_callback :: proc(w: io.Writer, prev, curr, next: rune) { + fmt.println("my_callback", curr) // <-- Custom logic here + } + s := "hello" + b: strings.Builder + strings.builder_init_len(&b, len(s)) + w := strings.to_writer(&b) + strings.string_case_iterator(w, s, my_callback) + } + +Output: + + my_callback h + my_callback e + my_callback l + my_callback l + my_callback o + */ -string_case_iterator :: proc(w: io.Writer, s: string, callback: proc(w: io.Writer, prev, curr, next: rune)) { +string_case_iterator :: proc( + w: io.Writer, + s: string, + callback: proc(w: io.Writer, prev, curr, next: rune), +) { prev, curr: rune for next in s { if curr == 0 { @@ -139,10 +234,19 @@ string_case_iterator :: proc(w: io.Writer, s: string, callback: proc(w: io.Write callback(w, prev, curr, 0) } } - +// Alias to `to_camel_case` to_lower_camel_case :: to_camel_case +/* +Converts the input string `s` to "lowerCamelCase". + +*Allocates Using Provided Allocator* -// converts the `s` string to "lowerCamelCase" +**Inputs** +- s: Input string to be converted. +- allocator: (default: context.allocator). + +**Returns** A "lowerCamelCase" formatted string. +*/ to_camel_case :: proc(s: string, allocator := context.allocator) -> string { s := s s = trim_space(s) @@ -164,10 +268,19 @@ to_camel_case :: proc(s: string, allocator := context.allocator) -> string { return to_string(b) } - +// Alias to `to_pascal_case` to_upper_camel_case :: to_pascal_case +/* +Converts the input string `s` to "UpperCamelCase" (PascalCase). + +*Allocates Using Provided Allocator* -// converts the `s` string to "PascalCase" +**Inputs** +- s: Input string to be converted. +- allocator: (default: context.allocator). + +**Returns** A "PascalCase" formatted string. +*/ to_pascal_case :: proc(s: string, allocator := context.allocator) -> string { s := s s = trim_space(s) @@ -189,17 +302,42 @@ to_pascal_case :: proc(s: string, allocator := context.allocator) -> string { return to_string(b) } +/* +Returns a string converted to a delimiter-separated case with configurable casing + +*Allocates Using Provided Allocator* + +**Inputs** +- s: The input string to be converted +- delimiter: The rune to be used as the delimiter between words +- all_upper_case: A boolean indicating if the output should be all uppercased (true) or lowercased (false) +- allocator: (default: context.allocator). + +Example: -/* - returns the `s` string to words seperated by the given `delimiter` rune - all runes will be upper or lowercased based on the `all_uppercase` bool + import "core:fmt" + import "core:strings" - strings.to_delimiter_case("Hello World", '_', false) -> hello_world - strings.to_delimiter_case("Hello World", ' ', true) -> HELLO WORLD - strings.to_delimiter_case("Hello World", ' ', true) -> HELLO WORLD - strings.to_delimiter_case("aBC", '_', false) -> a_b_c + strings_to_delimiter_case_example :: proc() { + fmt.println(strings.to_delimiter_case("Hello World", '_', false)) + fmt.println(strings.to_delimiter_case("Hello World", ' ', true)) + fmt.println(strings.to_delimiter_case("aBC", '_', false)) + } + +Output: + + hello_world + HELLO WORLD + a_b_c + +**Returns** The converted string */ -to_delimiter_case :: proc(s: string, delimiter: rune, all_upper_case: bool, allocator := context.allocator) -> string { +to_delimiter_case :: proc( + s: string, + delimiter: rune, + all_upper_case: bool, + allocator := context.allocator, +) -> string { s := s s = trim_space(s) b: Builder @@ -237,35 +375,143 @@ to_delimiter_case :: proc(s: string, delimiter: rune, all_upper_case: bool, allo return to_string(b) } +/* +Converts a string to "snake_case" with all runes lowercased + +*Allocates Using Provided Allocator* + +**Inputs** +- s: The input string to be converted +- allocator: (default: context.allocator). -/* - converts the `s` string to "snake_case" with all runes lowercased - - strings.to_snake_case("HelloWorld") -> hello_world - strings.to_snake_case("Hello World") -> hello_world +Example: + + import "core:fmt" + import "core:strings" + + strings_to_snake_case_example :: proc() { + fmt.println(strings.to_snake_case("HelloWorld")) + fmt.println(strings.to_snake_case("Hello World")) + } + +Output: + + hello_world + hello_world + +``` +**Returns** The converted string */ to_snake_case :: proc(s: string, allocator := context.allocator) -> string { return to_delimiter_case(s, '_', false, allocator) } - +// Alias for `to_upper_snake_case` to_screaming_snake_case :: to_upper_snake_case +/* +Converts a string to "SNAKE_CASE" with all runes uppercased + +*Allocates Using Provided Allocator* + +**Inputs** +- s: The input string to be converted +- allocator: (default: context.allocator). + +Example: + + import "core:fmt" + import "core:strings" + + strings_to_upper_snake_case_example :: proc() { + fmt.println(strings.to_upper_snake_case("HelloWorld")) + } -// converts the `s` string to "SNAKE_CASE" with all runes uppercased +Output: + + HELLO_WORLD + +**Returns** The converted string +*/ to_upper_snake_case :: proc(s: string, allocator := context.allocator) -> string { return to_delimiter_case(s, '_', true, allocator) } +/* +Converts a string to "kebab-case" with all runes lowercased + +*Allocates Using Provided Allocator* + +**Inputs** +- s: The input string to be converted +- allocator: (default: context.allocator). -// converts the `s` string to "kebab-case" with all runes lowercased +Example: + + import "core:fmt" + import "core:strings" + + strings_to_kebab_case_example :: proc() { + fmt.println(strings.to_kebab_case("HelloWorld")) + } + +Output: + + hello-world + +**Returns** The converted string +*/ to_kebab_case :: proc(s: string, allocator := context.allocator) -> string { return to_delimiter_case(s, '-', false, allocator) } +/* +Converts a string to "KEBAB-CASE" with all runes uppercased + +*Allocates Using Provided Allocator* + +**Inputs** +- s: The input string to be converted +- allocator: (default: context.allocator). + +Example: + + import "core:fmt" + import "core:strings" + + strings_to_upper_kebab_case_example :: proc() { + fmt.println(strings.to_upper_kebab_case("HelloWorld")) + } -// converts the `s` string to "KEBAB-CASE" with all runes uppercased +Output: + + HELLO-WORLD + +**Returns** The converted string +*/ to_upper_kebab_case :: proc(s: string, allocator := context.allocator) -> string { return to_delimiter_case(s, '-', true, allocator) } +/* +Converts a string to "Ada_Case" + +*Allocates Using Provided Allocator* + +**Inputs** +- s: The input string to be converted +- allocator: (default: context.allocator). + +Example: -// converts the `s` string to "Ada_Case" + import "core:fmt" + import "core:strings" + + strings_to_upper_kebab_case_example :: proc() { + fmt.println(strings.to_ada_case("HelloWorld")) + } + +Output: + + Hello_World + +**Returns** The converted string +*/ to_ada_case :: proc(s: string, allocator := context.allocator) -> string { s := s s = trim_space(s) diff --git a/core/strings/intern.odin b/core/strings/intern.odin index 5e9193a0d..59395824a 100644 --- a/core/strings/intern.odin +++ b/core/strings/intern.odin @@ -2,49 +2,96 @@ package strings import "core:runtime" -// custom string entry struct +// Custom string entry struct Intern_Entry :: struct { len: int, str: [1]byte, // string is allocated inline with the entry to keep allocations simple } +/* +Intern is a more memory efficient string map -// "intern" is a more memory efficient string map -// `allocator` is used to allocate the actual `Intern_Entry` strings +Uses Specified Allocator for `Intern_Entry` strings + +Fields: +- allocator: The allocator used for the Intern_Entry strings +- entries: A map of strings to interned string entries +*/ Intern :: struct { allocator: runtime.Allocator, entries: map[string]^Intern_Entry, } +/* +Initializes the entries map and sets the allocator for the string entries + +*Allocates Using Provided Allocators* -// initialize the entries map and set the allocator for the string entries +**Inputs** +- m: A pointer to the Intern struct to be initialized +- allocator: The allocator for the Intern_Entry strings (Default: context.allocator) +- map_allocator: The allocator for the map of entries (Default: context.allocator) +*/ intern_init :: proc(m: ^Intern, allocator := context.allocator, map_allocator := context.allocator) { m.allocator = allocator m.entries = make(map[string]^Intern_Entry, 16, map_allocator) } +/* +Frees the map and all its content allocated using the `.allocator`. -// free the map and all its content allocated using the `.allocator` +**Inputs** +- m: A pointer to the Intern struct to be destroyed +*/ intern_destroy :: proc(m: ^Intern) { for _, value in m.entries { free(value, m.allocator) } delete(m.entries) } +/* +Returns an interned copy of the given text, adding it to the map if not already present. + +*Allocate using the Intern's Allocator (First time string is seen only)* + +**Inputs** +- m: A pointer to the Intern struct +- text: The string to be interned + +NOTE: The returned string lives as long as the map entry lives. -// returns the `text` string from the intern map - gets set if it didnt exist yet -// the returned string lives as long as the map entry lives +**Returns** The interned string and an allocator error if any +*/ intern_get :: proc(m: ^Intern, text: string) -> (str: string, err: runtime.Allocator_Error) { entry := _intern_get_entry(m, text) or_return #no_bounds_check return string(entry.str[:entry.len]), nil } +/* +Returns an interned copy of the given text as a cstring, adding it to the map if not already present. -// returns the `text` cstring from the intern map - gets set if it didnt exist yet -// the returned cstring lives as long as the map entry lives +*Allocate using the Intern's Allocator (First time string is seen only)* + +**Inputs** +- m: A pointer to the Intern struct +- text: The string to be interned + +NOTE: The returned cstring lives as long as the map entry lives + +**Returns** The interned cstring and an allocator error if any +*/ intern_get_cstring :: proc(m: ^Intern, text: string) -> (str: cstring, err: runtime.Allocator_Error) { entry := _intern_get_entry(m, text) or_return return cstring(&entry.str[0]), nil } +/* +Internal function to lookup whether the text string exists in the map, returns the entry +Sets and allocates the entry if it wasn't set yet + +*Allocate using the Intern's Allocator (First time string is seen only)* + +**Inputs** +- m: A pointer to the Intern struct +- text: The string to be looked up or interned -// looks up wether the `text` string exists in the map, returns the entry -// sets & allocates the entry if it wasnt set yet +**Returns** The new or existing interned entry and an allocator error if any +*/ _intern_get_entry :: proc(m: ^Intern, text: string) -> (new_entry: ^Intern_Entry, err: runtime.Allocator_Error) #no_bounds_check { if prev, ok := m.entries[text]; ok { return prev, nil diff --git a/core/strings/reader.odin b/core/strings/reader.odin index 038740526..3e543cb9d 100644 --- a/core/strings/reader.odin +++ b/core/strings/reader.odin @@ -4,59 +4,104 @@ import "core:io" import "core:unicode/utf8" /* - io stream data for a string reader that can read based on bytes or runes - implements the vtable when using the io.Reader variants - "read" calls advance the current reading offset `i` +io stream data for a string reader that can read based on bytes or runes +implements the vtable when using the `io.Reader` variants +"read" calls advance the current reading offset `i` */ Reader :: struct { s: string, // read-only buffer i: i64, // current reading index prev_rune: int, // previous reading index of rune or < 0 } +/* +Initializes a string Reader with the provided string -// init the reader to the string `s` +**Inputs** +- r: A pointer to a Reader struct +- s: The input string to be read +*/ reader_init :: proc(r: ^Reader, s: string) { r.s = s r.i = 0 r.prev_rune = -1 } +/* +Converts a Reader into an `io.Stream` -// returns a stream from the reader data +**Inputs** +- r: A pointer to a Reader struct + +**Returns** An io.Stream for the given Reader +*/ reader_to_stream :: proc(r: ^Reader) -> (s: io.Stream) { s.stream_data = r s.stream_vtable = &_reader_vtable return } +/* +Initializes a string Reader and returns an `io.Reader` for the given string -// init a reader to the string `s` and return an io.Reader +**Inputs** +- r: A pointer to a Reader struct +- s: The input string to be read + +**Returns** An io.Reader for the given string +*/ to_reader :: proc(r: ^Reader, s: string) -> io.Reader { reader_init(r, s) rr, _ := io.to_reader(reader_to_stream(r)) return rr } +/* +Initializes a string Reader and returns an `io.Reader_At` for the given string -// init a reader to the string `s` and return an io.Reader_At +**Inputs** +- r: A pointer to a Reader struct +- s: The input string to be read + +**Returns** An `io.Reader_At` for the given string +*/ to_reader_at :: proc(r: ^Reader, s: string) -> io.Reader_At { reader_init(r, s) rr, _ := io.to_reader_at(reader_to_stream(r)) return rr } +/* +Returns the remaining length of the Reader -// remaining length of the reader +**Inputs** +- r: A pointer to a Reader struct + +**Returns** The remaining length of the Reader +*/ reader_length :: proc(r: ^Reader) -> int { if r.i >= i64(len(r.s)) { return 0 } return int(i64(len(r.s)) - r.i) } +/* +Returns the length of the string stored in the Reader -// returns the string length stored by the reader +**Inputs** +- r: A pointer to a Reader struct + +**Returns** The length of the string stored in the Reader +*/ reader_size :: proc(r: ^Reader) -> i64 { return i64(len(r.s)) } +/* +Reads len(p) bytes from the Reader's string and copies into the provided slice. -// reads len(p) bytes into the slice from the string in the reader -// returns `n` amount of read bytes and an io.Error +**Inputs** +- r: A pointer to a Reader struct +- p: A byte slice to copy data into + +**Returns** +- n: The number of bytes read +- err: An `io.Error` if an error occurs while reading, including `.EOF`, otherwise `nil` denotes success. +*/ reader_read :: proc(r: ^Reader, p: []byte) -> (n: int, err: io.Error) { if r.i >= i64(len(r.s)) { return 0, .EOF @@ -66,9 +111,18 @@ reader_read :: proc(r: ^Reader, p: []byte) -> (n: int, err: io.Error) { r.i += i64(n) return } +/* +Reads len(p) bytes from the Reader's string and copies into the provided slice, at the specified offset from the current index. -// reads len(p) bytes into the slice from the string in the reader at an offset -// returns `n` amount of read bytes and an io.Error +**Inputs** +- r: A pointer to a Reader struct +- p: A byte slice to copy data into +- off: The offset from which to read + +**Returns** +- n: The number of bytes read +- err: An `io.Error` if an error occurs while reading, including `.EOF`, otherwise `nil` denotes success. +*/ reader_read_at :: proc(r: ^Reader, p: []byte, off: i64) -> (n: int, err: io.Error) { if off < 0 { return 0, .Invalid_Offset @@ -82,8 +136,16 @@ reader_read_at :: proc(r: ^Reader, p: []byte, off: i64) -> (n: int, err: io.Erro } return } +/* +Reads and returns a single byte from the Reader's string + +**Inputs** +- r: A pointer to a Reader struct -// reads and returns a single byte - error when out of bounds +**Returns** +- The byte read from the Reader +- err: An `io.Error` if an error occurs while reading, including `.EOF`, otherwise `nil` denotes success. +*/ reader_read_byte :: proc(r: ^Reader) -> (byte, io.Error) { r.prev_rune = -1 if r.i >= i64(len(r.s)) { @@ -93,8 +155,14 @@ reader_read_byte :: proc(r: ^Reader) -> (byte, io.Error) { r.i += 1 return b, nil } +/* +Decrements the Reader's index (i) by 1 + +**Inputs** +- r: A pointer to a Reader struct -// decreases the reader offset - error when below 0 +**Returns** An `io.Error` if `r.i <= 0` (`.Invalid_Unread`), otherwise `nil` denotes success. +*/ reader_unread_byte :: proc(r: ^Reader) -> io.Error { if r.i <= 0 { return .Invalid_Unread @@ -103,9 +171,18 @@ reader_unread_byte :: proc(r: ^Reader) -> io.Error { r.i -= 1 return nil } +/* +Reads and returns a single rune and its `size` from the Reader's string + +**Inputs** +- r: A pointer to a Reader struct -// reads and returns a single rune and the rune size - error when out bounds -reader_read_rune :: proc(r: ^Reader) -> (ch: rune, size: int, err: io.Error) { +**Returns** +- rr: The rune read from the Reader +- size: The size of the rune in bytes +- err: An `io.Error` if an error occurs while reading +*/ +reader_read_rune :: proc(r: ^Reader) -> (rr: rune, size: int, err: io.Error) { if r.i >= i64(len(r.s)) { r.prev_rune = -1 return 0, 0, .EOF @@ -115,13 +192,20 @@ reader_read_rune :: proc(r: ^Reader) -> (ch: rune, size: int, err: io.Error) { r.i += 1 return rune(c), 1, nil } - ch, size = utf8.decode_rune_in_string(r.s[r.i:]) + rr, size = utf8.decode_rune_in_string(r.s[r.i:]) r.i += i64(size) return } +/* +Decrements the Reader's index (i) by the size of the last read rune + +**Inputs** +- r: A pointer to a Reader struct -// decreases the reader offset by the last rune -// can only be used once and after a valid read_rune call +WARNING: May only be used once and after a valid `read_rune` call + +**Returns** An `io.Error` if an error occurs while unreading (`.Invalid_Unread`), else `nil` denotes success. +*/ reader_unread_rune :: proc(r: ^Reader) -> io.Error { if r.i <= 0 { return .Invalid_Unread @@ -133,8 +217,18 @@ reader_unread_rune :: proc(r: ^Reader) -> io.Error { r.prev_rune = -1 return nil } +/* +Seeks the Reader's index to a new position -// seeks the reader offset to a wanted offset +**Inputs** +- r: A pointer to a Reader struct +- offset: The new offset position +- whence: The reference point for the new position (`.Start`, `.Current`, or `.End`) + +**Returns** +- The absolute offset after seeking +- err: An `io.Error` if an error occurs while seeking (`.Invalid_Whence`, `.Invalid_Offset`) +*/ reader_seek :: proc(r: ^Reader, offset: i64, whence: io.Seek_From) -> (i64, io.Error) { r.prev_rune = -1 abs: i64 @@ -155,8 +249,19 @@ reader_seek :: proc(r: ^Reader, offset: i64, whence: io.Seek_From) -> (i64, io.E r.i = abs return abs, nil } +/* +Writes the remaining content of the Reader's string into the provided `io.Writer` -// writes the string content left to read into the io.Writer `w` +**Inputs** +- r: A pointer to a Reader struct +- w: The io.Writer to write the remaining content into + +WARNING: Panics if writer writes more bytes than remainig length of string. + +**Returns** +- n: The number of bytes written +- err: An io.Error if an error occurs while writing (`.Short_Write`) +*/ reader_write_to :: proc(r: ^Reader, w: io.Writer) -> (n: i64, err: io.Error) { r.prev_rune = -1 if r.i >= i64(len(r.s)) { @@ -175,7 +280,12 @@ reader_write_to :: proc(r: ^Reader, w: io.Writer) -> (n: i64, err: io.Error) { } return } +/* +VTable containing implementations for various `io.Stream` methods +This VTable is used by the Reader struct to provide its functionality +as an `io.Stream`. +*/ @(private) _reader_vtable := io.Stream_VTable{ impl_size = proc(s: io.Stream) -> i64 { diff --git a/core/strings/strings.odin b/core/strings/strings.odin index 33cdafef3..8193f4de1 100644 --- a/core/strings/strings.odin +++ b/core/strings/strings.odin @@ -1,4 +1,4 @@ -// simple procedures to manipulate UTF-8 encoded strings +// Procedures to manipulate UTF-8 encoded strings package strings import "core:io" @@ -6,58 +6,128 @@ import "core:mem" import "core:unicode" import "core:unicode/utf8" -// returns a clone of the string `s` allocated using the `allocator` +/* +Clones a string + +*Allocates Using Provided Allocator* + +**Inputs** +- s: The string to be cloned +- allocator: (default: context.allocator) +- loc: The caller location for debugging purposes (default: #caller_location) + +**Returns** A cloned string +*/ clone :: proc(s: string, allocator := context.allocator, loc := #caller_location) -> string { c := make([]byte, len(s), allocator, loc) copy(c, s) return string(c[:len(s)]) } +/* +Clones a string safely (returns early with an allocation error on failure) + +*Allocates Using Provided Allocator* -// returns a clone of the string `s` allocated using the `allocator` +**Inputs** +- s: The string to be cloned +- allocator: (default: context.allocator) +- loc: The caller location for debugging purposes (default: #caller_location) + +**Returns** +- str: A cloned string +- err: A mem.Allocator_Error if an error occurs during allocation +*/ clone_safe :: proc(s: string, allocator := context.allocator, loc := #caller_location) -> (str: string, err: mem.Allocator_Error) { c := make([]byte, len(s), allocator, loc) or_return copy(c, s) return string(c[:len(s)]), nil } +/* +Clones a string and appends a null-byte to make it a cstring + +*Allocates Using Provided Allocator* + +**Inputs** +- s: The string to be cloned +- allocator: (default: context.allocator) +- loc: The caller location for debugging purposes (default: #caller_location) -// returns a clone of the string `s` allocated using the `allocator` as a cstring -// a nul byte is appended to the clone, to make the cstring safe +**Returns** A cloned cstring with an appended null-byte +*/ clone_to_cstring :: proc(s: string, allocator := context.allocator, loc := #caller_location) -> cstring { c := make([]byte, len(s)+1, allocator, loc) copy(c, s) c[len(s)] = 0 return cstring(&c[0]) } +/* +Transmutes a raw pointer into a string. Non-allocating. -// returns a string from a byte pointer `ptr` and byte length `len` -// the string is valid as long as the parameters stay alive +**Inputs** +- ptr: A pointer to the start of the byte sequence +- len: The length of the byte sequence + +NOTE: The created string is only valid as long as the pointer and length are valid. + +**Returns** A string created from the byte pointer and length +*/ string_from_ptr :: proc(ptr: ^byte, len: int) -> string { return transmute(string)mem.Raw_String{ptr, len} } +/* +Transmutes a raw pointer (null-terminated) into a string. Non-allocating. Searches for a null-byte from `0..<len`, otherwhise `len` will be the end size -// returns a string from a byte pointer `ptr and byte length `len` -// searches for a nul byte from 0..<len, otherwhise `len` will be the end size -string_from_nul_terminated_ptr :: proc(ptr: ^byte, len: int) -> string { +NOTE: The created string is only valid as long as the pointer and length are valid. + The string is truncated at the first null-byte encountered. + +**Inputs** +- ptr: A pointer to the start of the null-terminated byte sequence +- len: The length of the byte sequence + +**Returns** A string created from the null-terminated byte pointer and length +*/ +string_from_null_terminated_ptr :: proc(ptr: ^byte, len: int) -> string { s := transmute(string)mem.Raw_String{ptr, len} s = truncate_to_byte(s, 0) return s } +/* +Gets the raw byte pointer for the start of a string `str` + +**Inputs** +- str: The input string -// returns the raw ^byte start of the string `str` +**Returns** A pointer to the start of the string's bytes +*/ ptr_from_string :: proc(str: string) -> ^byte { d := transmute(mem.Raw_String)str return d.data } +/* +Converts a string `str` to a cstring + +**Inputs** +- str: The input string + +WARNING: This is unsafe because the original string may not contain a null-byte. -// returns the transmute of string `str` to a cstring -// not safe since the origin string may not contain a nul byte +**Returns** The converted cstring +*/ unsafe_string_to_cstring :: proc(str: string) -> cstring { d := transmute(mem.Raw_String)str return cstring(d.data) } +/* +Truncates a string `str` at the first occurrence of char/byte `b` + +**Inputs** +- str: The input string +- b: The byte to truncate the string at -// returns a string truncated to the first time it finds the byte `b` -// uses the `len` of the string `str` when it couldn't find the input +NOTE: Failure to find the byte results in returning the entire string. + +**Returns** The truncated string +*/ truncate_to_byte :: proc(str: string, b: byte) -> string { n := index_byte(str, b) if n < 0 { @@ -65,9 +135,15 @@ truncate_to_byte :: proc(str: string, b: byte) -> string { } return str[:n] } +/* +Truncates a string `str` at the first occurrence of rune `r` as a slice of the original, entire string if not found + +**Inputs** +- str: The input string +- r: The rune to truncate the string at -// returns a string truncated to the first time it finds the rune `r` -// uses the `len` of the string `str` when it couldn't find the input +**Returns** The truncated string +*/ truncate_to_rune :: proc(str: string, r: rune) -> string { n := index_rune(str, r) if n < 0 { @@ -75,51 +151,107 @@ truncate_to_rune :: proc(str: string, r: rune) -> string { } return str[:n] } +/* +Clones a byte array `s` and appends a null-byte -// returns a cloned string of the byte array `s` using the `allocator` -// appends a leading nul byte +*Allocates Using Provided Allocator* + +**Inputs** +- s: The byte array to be cloned +- allocator: (default: context.allocator) +- loc: The caller location for debugging purposes (default: `#caller_location`) + +**Returns** A cloned string from the byte array with a null-byte +*/ clone_from_bytes :: proc(s: []byte, allocator := context.allocator, loc := #caller_location) -> string { c := make([]byte, len(s)+1, allocator, loc) copy(c, s) c[len(s)] = 0 return string(c[:len(s)]) } +/* +Clones a cstring `s` as a string + +*Allocates Using Provided Allocator* -// returns a clone of the cstring `s` using the `allocator` as a string +**Inputs** +- s: The cstring to be cloned +- allocator: (default: context.allocator) +- loc: The caller location for debugging purposes (default: `#caller_location`) + +**Returns** A cloned string from the cstring +*/ clone_from_cstring :: proc(s: cstring, allocator := context.allocator, loc := #caller_location) -> string { return clone(string(s), allocator, loc) } +/* +Clones a string from a byte pointer `ptr` and a byte length `len` + +*Allocates Using Provided Allocator* -// returns a cloned string from the pointer `ptr` and a byte length `len` using the `allocator` -// same to `string_from_ptr` but allocates +**Inputs** +- ptr: A pointer to the start of the byte sequence +- len: The length of the byte sequence +- allocator: (default: context.allocator) +- loc: The caller location for debugging purposes (default: `#caller_location`) + +NOTE: Same as `string_from_ptr`, but perform an additional `clone` operation + +**Returns** A cloned string from the byte pointer and length +*/ clone_from_ptr :: proc(ptr: ^byte, len: int, allocator := context.allocator, loc := #caller_location) -> string { s := string_from_ptr(ptr, len) return clone(s, allocator, loc) } - -// overload to clone from a `string`, `[]byte`, `cstring` or a `^byte + length` to a string +// Overloaded procedure to clone from a string, `[]byte`, `cstring` or a `^byte` + length clone_from :: proc{ clone, clone_from_bytes, clone_from_cstring, clone_from_ptr, } +/* +Clones a string from a null-terminated cstring `ptr` and a byte length `len` -// returns a cloned string from the cstring `ptr` and a byte length `len` using the `allocator` -// truncates till the first nul byte it finds or the byte len +*Allocates Using Provided Allocator* + +**Inputs** +- ptr: A pointer to the start of the null-terminated cstring +- len: The byte length of the cstring +- allocator: (default: context.allocator) +- loc: The caller location for debugging purposes (default: `#caller_location`) + +NOTE: Truncates at the first null-byte encountered or the byte length. + +**Returns** A cloned string from the null-terminated cstring and byte length +*/ clone_from_cstring_bounded :: proc(ptr: cstring, len: int, allocator := context.allocator, loc := #caller_location) -> string { s := string_from_ptr((^u8)(ptr), len) s = truncate_to_byte(s, 0) return clone(s, allocator, loc) } +/* +Compares two strings, returning a value representing which one comes first lexicographically. +-1 for `lhs`; 1 for `rhs`, or 0 if they are equal. + +**Inputs** +- lhs: First string for comparison +- rhs: Second string for comparison -// Compares two strings, returning a value representing which one comes first lexiographically. -// -1 for `lhs`; 1 for `rhs`, or 0 if they are equal. +**Returns** -1 if `lhs` comes first, 1 if `rhs` comes first, or 0 if they are equal +*/ compare :: proc(lhs, rhs: string) -> int { return mem.compare(transmute([]byte)lhs, transmute([]byte)rhs) } +/* +Returns the byte offset of the rune `r` in the string `s`, -1 when not found + +**Inputs** +- s: The input string +- r: The rune to search for -// returns the byte offset of the rune `r` in the string `s`, -1 when not found +**Returns** The byte offset of the rune `r` in the string `s`, or -1 if not found +*/ contains_rune :: proc(s: string, r: rune) -> int { for c, offset in s { if c == r { @@ -128,48 +260,120 @@ contains_rune :: proc(s: string, r: rune) -> int { } return -1 } - /* - returns true when the string `substr` is contained inside the string `s` +Returns true when the string `substr` is contained inside the string `s` + +**Inputs** +- s: The input string +- substr: The substring to search for + +Example: - strings.contains("testing", "test") -> true - strings.contains("testing", "ing") -> true - strings.contains("testing", "text") -> false + import "core:fmt" + import "core:strings" + + strings_contains_example :: proc() { + fmt.println(strings.contains("testing", "test")) + fmt.println(strings.contains("testing", "ing")) + fmt.println(strings.contains("testing", "text")) + } + +Output: + + true + true + false + +**Returns** `true` if `substr` is contained inside the string `s`, `false` otherwise */ contains :: proc(s, substr: string) -> bool { return index(s, substr) >= 0 } - /* - returns true when the string `s` contains any of the characters inside the string `chars` - - strings.contains_any("test", "test") -> true - strings.contains_any("test", "ts") -> true - strings.contains_any("test", "et") -> true - strings.contains_any("test", "a") -> false +Returns `true` when the string `s` contains any of the characters inside the string `chars` + +**Inputs** +- s: The input string +- chars: The characters to search for + +Example: + + import "core:fmt" + import "core:strings" + + strings_contains_any_example :: proc() { + fmt.println(strings.contains_any("test", "test")) + fmt.println(strings.contains_any("test", "ts")) + fmt.println(strings.contains_any("test", "et")) + fmt.println(strings.contains_any("test", "a")) + } + +Output: + + true + true + true + false + +**Returns** `true` if the string `s` contains any of the characters in `chars`, `false` otherwise */ contains_any :: proc(s, chars: string) -> bool { return index_any(s, chars) >= 0 } - /* - returns the utf8 rune count of the string `s` +Returns the UTF-8 rune count of the string `s` + +**Inputs** +- s: The input string + +Example: - strings.rune_count("test") -> 4 - strings.rune_count("testö") -> 5, where len("testö") -> 6 + import "core:fmt" + import "core:strings" + + strings_rune_count_example :: proc() { + fmt.println(strings.rune_count("test")) + fmt.println(strings.rune_count("testö")) // where len("testö") == 6 + } + +Output: + + 4 + 5 + +**Returns** The UTF-8 rune count of the string `s` */ rune_count :: proc(s: string) -> int { return utf8.rune_count_in_string(s) } - /* - returns wether the strings `u` and `v` are the same alpha characters - works with utf8 string content and ignores different casings +Returns whether the strings `u` and `v` are the same alpha characters, ignoring different casings +Works with UTF-8 string content + +**Inputs** +- u: The first string for comparison +- v: The second string for comparison + +Example: + + import "core:fmt" + import "core:strings" - strings.equal_fold("test", "test") -> true - strings.equal_fold("Test", "test") -> true - strings.equal_fold("Test", "tEsT") -> true - strings.equal_fold("test", "tes") -> false + strings_equal_fold_example :: proc() { + fmt.println(strings.equal_fold("test", "test")) + fmt.println(strings.equal_fold("Test", "test")) + fmt.println(strings.equal_fold("Test", "tEsT")) + fmt.println(strings.equal_fold("test", "tes")) + } + +Output: + + true + true + true + false + +**Returns** `true` if the strings `u` and `v` are the same alpha characters (ignoring case) */ equal_fold :: proc(u, v: string) -> bool { s, t := u, v @@ -213,14 +417,33 @@ equal_fold :: proc(u, v: string) -> bool { return s == t } - /* - return the prefix length common between strings `a` and `b`. +Returns the prefix length common between strings `a` and `b` + +**Inputs** +- a: The first input string +- b: The second input string + +Example: + + import "core:fmt" + import "core:strings" + + strings_prefix_length_example :: proc() { + fmt.println(strings.prefix_length("testing", "test")) + fmt.println(strings.prefix_length("testing", "te")) + fmt.println(strings.prefix_length("telephone", "te")) + fmt.println(strings.prefix_length("testing", "est")) + } + +Output: + + 4 + 2 + 2 + 0 - strings.prefix_length("testing", "test") -> 4 - strings.prefix_length("testing", "te") -> 2 - strings.prefix_length("telephone", "te") -> 2 - strings.prefix_length("testing", "est") -> 0 +**Returns** The prefix length common between strings `a` and `b` */ prefix_length :: proc(a, b: string) -> (n: int) { _len := min(len(a), len(b)) @@ -245,39 +468,95 @@ prefix_length :: proc(a, b: string) -> (n: int) { } return } - /* - return true when the string `prefix` is contained at the start of the string `s` +Determines if a string `s` starts with a given `prefix` + +**Inputs** +- s: The string to check for the `prefix` +- prefix: The prefix to look for + +Example: + + import "core:fmt" + import "core:strings" + + strings_has_prefix_example :: proc() { + fmt.println(strings.has_prefix("testing", "test")) + fmt.println(strings.has_prefix("testing", "te")) + fmt.println(strings.has_prefix("telephone", "te")) + fmt.println(strings.has_prefix("testing", "est")) + } - strings.has_prefix("testing", "test") -> true - strings.has_prefix("testing", "te") -> true - strings.has_prefix("telephone", "te") -> true - strings.has_prefix("testing", "est") -> false +Output: + + true + true + true + false + +**Returns** `true` if the string `s` starts with the `prefix`, otherwise `false` */ has_prefix :: proc(s, prefix: string) -> bool { return len(s) >= len(prefix) && s[0:len(prefix)] == prefix } - /* - returns true when the string `suffix` is contained at the end of the string `s` - good example to use this is for file extensions +Determines if a string `s` ends with a given `suffix` + +Example: + + import "core:fmt" + import "core:strings" + + strings_has_suffix_example :: proc() { + fmt.println(strings.has_suffix("todo.txt", ".txt")) + fmt.println(strings.has_suffix("todo.doc", ".txt")) + fmt.println(strings.has_suffix("todo.doc.txt", ".txt")) + } + +Output: + + true + false + true - strings.has_suffix("todo.txt", ".txt") -> true - strings.has_suffix("todo.doc", ".txt") -> false - strings.has_suffix("todo.doc.txt", ".txt") -> true +**Inputs** +- s: The string to check for the `suffix` +- suffix: The suffix to look for + +**Returns** `true` if the string `s` ends with the `suffix`, otherwise `false` */ has_suffix :: proc(s, suffix: string) -> bool { return len(s) >= len(suffix) && s[len(s)-len(suffix):] == suffix } - /* - returns a combined string from the slice of strings `a` seperated with the `sep` string - allocates the string using the `allocator` +Joins a slice of strings `a` with a `sep` string + +*Allocates Using Provided Allocator* + +Example: + + import "core:fmt" + import "core:strings" + + strings_join_example :: proc() { + a := [?]string { "a", "b", "c" } + fmt.println(strings.join(a[:], " ")) + fmt.println(strings.join(a[:], "-")) + fmt.println(strings.join(a[:], "...")) + } + +Output: + + a b c + a-b-c + a...b...c - a := [?]string { "a", "b", "c" } - b := strings.join(a[:], " ") -> "a b c" - c := strings.join(a[:], "-") -> "a-b-c" - d := strings.join(a[:], "...") -> "a...b...c" +**Inputs** +- a: A slice of strings to join +- sep: The separator string +- allocator: (default is context.allocator) + +**Returns** A combined string from the slice of strings `a` separated with the `sep` string */ join :: proc(a: []string, sep: string, allocator := context.allocator) -> string { if len(a) == 0 { @@ -297,7 +576,20 @@ join :: proc(a: []string, sep: string, allocator := context.allocator) -> string } return string(b) } +/* +Joins a slice of strings `a` with a `sep` string, returns an error on allocation failure + +*Allocates Using Provided Allocator* +**Inputs** +- a: A slice of strings to join +- sep: The separator string +- allocator: (default is context.allocator) + +**Returns** +- str: A combined string from the slice of strings `a` separated with the `sep` string +- err: An error if allocation failed, otherwise `nil` +*/ join_safe :: proc(a: []string, sep: string, allocator := context.allocator) -> (str: string, err: mem.Allocator_Error) { if len(a) == 0 { return "", nil @@ -316,14 +608,30 @@ join_safe :: proc(a: []string, sep: string, allocator := context.allocator) -> ( } return string(b), nil } - /* - returns a combined string from the slice of strings `a` without a seperator - allocates the string using the `allocator` - +Returns a combined string from the slice of strings `a` without a separator + +*Allocates Using Provided Allocator* + +**Inputs** +- a: A slice of strings to concatenate +- allocator: (default is context.allocator) - a := [?]string { "a", "b", "c" } - b := strings.concatenate(a[:]) -> "abc" +Example: + + import "core:fmt" + import "core:strings" + + strings_concatenate_example :: proc() { + a := [?]string { "a", "b", "c" } + fmt.println(strings.concatenate(a[:])) + } + +Output: + + abc + +**Returns** The concatenated string */ concatenate :: proc(a: []string, allocator := context.allocator) -> string { if len(a) == 0 { @@ -341,7 +649,17 @@ concatenate :: proc(a: []string, allocator := context.allocator) -> string { } return string(b) } +/* +Returns a combined string from the slice of strings `a` without a separator, or an error if allocation fails + +*Allocates Using Provided Allocator* +**Inputs** +- a: A slice of strings to concatenate +- allocator: (default is context.allocator) + +**Returns** The concatenated string, and an error if allocation fails +*/ concatenate_safe :: proc(a: []string, allocator := context.allocator) -> (res: string, err: mem.Allocator_Error) { if len(a) == 0 { return "", nil @@ -358,14 +676,35 @@ concatenate_safe :: proc(a: []string, allocator := context.allocator) -> (res: s } return string(b), nil } - /* - `rune_offset` and `rune_length` are in runes, not bytes. - If `rune_length` <= 0, then it'll return the remainder of the string starting at `rune_offset`. +Returns a substring of the input string `s` with the specified rune offset and length + +*Allocates Using Provided Allocator* + +**Inputs** +- s: The input string to cut +- rune_offset: The starting rune index (default is 0). In runes, not bytes. +- rune_length: The number of runes to include in the substring (default is 0, which returns the remainder of the string). In runes, not bytes. +- allocator: (default is context.allocator) + +Example: + + import "core:fmt" + import "core:strings" + + strings_cut_example :: proc() { + strings.cut("some example text", 0, 4) // -> "some" + strings.cut("some example text", 2, 2) // -> "me" + strings.cut("some example text", 5, 7) // -> "example" + } - strings.cut("some example text", 0, 4) -> "some" - strings.cut("some example text", 2, 2) -> "me" - strings.cut("some example text", 5, 7) -> "example" +Output: + + some + me + example + +**Returns** The substring */ cut :: proc(s: string, rune_offset := int(0), rune_length := int(0), allocator := context.allocator) -> (res: string) { s := s; rune_length := rune_length @@ -418,7 +757,24 @@ cut :: proc(s: string, rune_offset := int(0), rune_length := int(0), allocator : } return string(buf[:byte_offset]) } +/* +Splits the input string `s` into a slice of substrings separated by the specified `sep` string + +*Allocates Using Provided Allocator* + +*Used Internally - Private Function* +**Inputs** +- s: The input string to split +- sep: The separator string +- sep_save: A flag determining if the separator should be saved in the resulting substrings +- n: The maximum number of substrings to return, returns `nil` without alloc when `n=0` +- allocator: (default is context.allocator) + +NOTE: Allocation occurs for the array, the splits are all views of the original string. + +**Returns** A slice of substrings +*/ @private _split :: proc(s_, sep: string, sep_save, n_: int, allocator := context.allocator) -> []string { s, n := s_, n_ @@ -466,58 +822,148 @@ _split :: proc(s_, sep: string, sep_save, n_: int, allocator := context.allocato return res[:i+1] } - /* - Splits a string into parts, based on a separator. - Returned strings are substrings of 's'. - ``` - s := "aaa.bbb.ccc.ddd.eee" // 5 parts - ss := split(s, ".") - fmt.println(ss) // [aaa, bbb, ccc, ddd, eee] - ``` +Splits a string into parts based on a separator. + +*Allocates Using Provided Allocator* + +**Inputs** +- s: The string to split. +- sep: The separator string used to split the input string. +- allocator: (default is context.allocator). + +Example: + + import "core:fmt" + import "core:strings" + + strings_split_example :: proc() { + s := "aaa.bbb.ccc.ddd.eee" // 5 parts + ss := strings.split(s, ".") + fmt.println(ss) + } + +Output: + + ["aaa", "bbb", "ccc", "ddd", "eee"] + +NOTE: Allocation occurs for the array, the splits are all views of the original string. + +**Returns** A slice of strings, each representing a part of the split string. */ split :: proc(s, sep: string, allocator := context.allocator) -> []string { return _split(s, sep, 0, -1, allocator) } - /* - Splits a string into a total of 'n' parts, based on a separator. - Returns fewer parts if there wasn't enough occurrences of the separator. - Returned strings are substrings of 's'. - ``` - s := "aaa.bbb.ccc.ddd.eee" // 5 parts present - ss := split_n(s, ".", 3) // total of 3 wanted - fmt.println(ss) // [aaa, bbb, ccc.ddd.eee] - ``` +Splits a string into parts based on a separator. If n < count of seperators, the remainder of the string is returned in the last entry. + +*Allocates Using Provided Allocator* + +**Inputs** +- s: The string to split. +- sep: The separator string used to split the input string. +- allocator: (default is context.allocator) + +Example: + + import "core:fmt" + import "core:strings" + + strings_split_n_example :: proc() { + s := "aaa.bbb.ccc.ddd.eee" // 5 parts present + ss := strings.split_n(s, ".",3) // total of 3 wanted + fmt.println(ss) + } + +Output: + + ["aaa", "bbb", "ccc.ddd.eee"] + +NOTE: Allocation occurs for the array, the splits are all views of the original string. + +**Returns** A slice of strings, each representing a part of the split string. */ split_n :: proc(s, sep: string, n: int, allocator := context.allocator) -> []string { return _split(s, sep, 0, n, allocator) } - /* - splits the string `s` after the seperator string `sep` appears - returns the slice of split strings allocated using `allocator` +Splits a string into parts after the separator, retaining it in the substrings. + +*Allocates Using Provided Allocator* + +**Inputs** +- s: The string to split. +- sep: The separator string used to split the input string. +- allocator: (default is context.allocator). + +Example: + + import "core:fmt" + import "core:strings" + + strings_split_after_example :: proc() { + a := "aaa.bbb.ccc.ddd.eee" // 5 parts + aa := strings.split_after(a, ".") + fmt.println(aa) + } + +Output: + + ["aaa.", "bbb.", "ccc.", "ddd.", "eee"] - a := "aaa.bbb.ccc.ddd.eee" - aa := strings.split_after(a, ".") - fmt.eprintln(aa) // [aaa., bbb., ccc., ddd., eee] +NOTE: Allocation occurs for the array, the splits are all views of the original string. + +**Returns** A slice of strings, each representing a part of the split string after the separator. */ split_after :: proc(s, sep: string, allocator := context.allocator) -> []string { return _split(s, sep, len(sep), -1, allocator) } - /* - splits the string `s` after the seperator string `sep` appears into a total of `n` parts - returns the slice of split strings allocated using `allocator` +Splits a string into a total of `n` parts after the separator. + +*Allocates Using Provided Allocator* + +**Inputs** +- s: The string to split. +- sep: The separator string used to split the input string. +- n: The maximum number of parts to split the string into. +- allocator: (default is context.allocator) + +Example: + + import "core:fmt" + import "core:strings" - a := "aaa.bbb.ccc.ddd.eee" - aa := strings.split_after(a, ".") - fmt.eprintln(aa) // [aaa., bbb., ccc., ddd., eee] + strings_split_after_n_example :: proc() { + a := "aaa.bbb.ccc.ddd.eee" + aa := strings.split_after_n(a, ".", 3) + fmt.println(aa) + } + +Output: + + ["aaa.", "bbb.", "ccc.ddd.eee"] + +NOTE: Allocation occurs for the array, the splits are all views of the original string. + +**Returns** A slice of strings with `n` parts or fewer if there weren't */ split_after_n :: proc(s, sep: string, n: int, allocator := context.allocator) -> []string { return _split(s, sep, len(sep), n, allocator) } +/* +Searches for the first occurrence of `sep` in the given string and returns the substring +up to (but not including) the separator, as well as a boolean indicating success. + +*Used Internally - Private Function* +**Inputs** +- s: Pointer to the input string, which is modified during the search. +- sep: The separator string to search for. +- sep_save: Number of characters from the separator to include in the result. + +**Returns** A tuple containing the resulting substring and a boolean indicating success. +*/ @private _split_iterator :: proc(s: ^string, sep: string, sep_save: int) -> (res: string, ok: bool) { // stop once the string is empty or nil @@ -545,15 +991,34 @@ _split_iterator :: proc(s: ^string, sep: string, sep_save: int) -> (res: string, } return } - /* - split the ^string `s` by the byte seperator `sep` in an iterator fashion - consumes the original string till the end, leaving the string `s` with len == 0 +Splits the input string by the byte separator in an iterator fashion. + +**Inputs** +- s: Pointer to the input string, which is modified during the search. +- sep: The byte separator to search for. + +Example: + + import "core:fmt" + import "core:strings" - text := "a.b.c.d.e" - for str in strings.split_by_byte_iterator(&text, '.') { - fmt.eprintln(str) // every loop -> a b c d e + strings_split_by_byte_iterator_example :: proc() { + text := "a.b.c.d.e" + for str in strings.split_by_byte_iterator(&text, '.') { + fmt.println(str) // every loop -> a b c d e + } } + +Output: + + a + b + c + d + e + +**Returns** A tuple containing the resulting substring and a boolean indicating success. */ split_by_byte_iterator :: proc(s: ^string, sep: u8) -> (res: string, ok: bool) { m := index_byte(s^, sep) @@ -569,34 +1034,82 @@ split_by_byte_iterator :: proc(s: ^string, sep: u8) -> (res: string, ok: bool) { } return } - /* - split the ^string `s` by the seperator string `sep` in an iterator fashion - consumes the original string till the end +Splits the input string by the separator string in an iterator fashion. +Destructively consumes the original string until the end. + +**Inputs** +- s: Pointer to the input string, which is modified during the search. +- sep: The separator string to search for. + +Example: - text := "a.b.c.d.e" - for str in strings.split_iterator(&text, ".") { - fmt.eprintln(str) // every loop -> a b c d e + import "core:fmt" + import "core:strings" + + strings_split_iterator_example :: proc() { + text := "a.b.c.d.e" + for str in strings.split_iterator(&text, ".") { + fmt.println(str) + } } + +Output: + + a + b + c + d + e + +**Returns** A tuple containing the resulting substring and a boolean indicating success. */ split_iterator :: proc(s: ^string, sep: string) -> (string, bool) { return _split_iterator(s, sep, 0) } - /* - split the ^string `s` after every seperator string `sep` in an iterator fashion - consumes the original string till the end +Splits the input string after every separator string in an iterator fashion. +Destructively consumes the original string until the end. - text := "a.b.c.d.e" - for str in strings.split_after_iterator(&text, ".") { - fmt.eprintln(str) // every loop -> a. b. c. d. e +**Inputs** +- s: Pointer to the input string, which is modified during the search. +- sep: The separator string to search for. + +Example: + + import "core:fmt" + import "core:strings" + + strings_split_after_iterator_example :: proc() { + text := "a.b.c.d.e" + for str in strings.split_after_iterator(&text, ".") { + fmt.println(str) + } } + +Output: + + a. + b. + c. + d. + e + +**Returns** A tuple containing the resulting substring and a boolean indicating success. */ split_after_iterator :: proc(s: ^string, sep: string) -> (string, bool) { return _split_iterator(s, sep, len(sep)) } +/* +Trims the carriage return character from the end of the input string. + +*Used Internally - Private Function* +**Inputs** +- s: The input string to trim. +**Returns** The trimmed string as a slice of the original. +*/ @(private) _trim_cr :: proc(s: string) -> string { n := len(s) @@ -607,14 +1120,31 @@ _trim_cr :: proc(s: string) -> string { } return s } - /* - split the string `s` at every line break '\n' - return an allocated slice of strings +Splits the input string at every line break `\n`. + +*Allocates Using Provided Allocator* + +**Inputs** +- s: The input string to split. +- allocator: (default is context.allocator) + +Example: + + import "core:fmt" + import "core:strings" + + strings_split_lines_example :: proc() { + a := "a\nb\nc\nd\ne" + b := strings.split_lines(a) + fmt.println(b) + } + +Output: - a := "a\nb\nc\nd\ne" - b := strings.split_lines(a) - fmt.eprintln(b) // [a, b, c, d, e] + ["a", "b", "c", "d", "e"] + +**Returns** A slice (allocated) of the split string (slices into original string) */ split_lines :: proc(s: string, allocator := context.allocator) -> []string { sep :: "\n" @@ -624,14 +1154,34 @@ split_lines :: proc(s: string, allocator := context.allocator) -> []string { } return lines } - /* - split the string `s` at every line break '\n' for `n` parts - return an allocated slice of strings +Splits the input string at every line break `\n` for `n` parts. + +*Allocates Using Provided Allocator* + +**Inputs** +- s: The input string to split. +- n: The number of parts to split into. +- allocator: (default is context.allocator) + +Example: + + import "core:fmt" + import "core:strings" + + strings_split_lines_n_example :: proc() { + a := "a\nb\nc\nd\ne" + b := strings.split_lines_n(a, 3) + fmt.println(b) + } + +Output: - a := "a\nb\nc\nd\ne" - b := strings.split_lines_n(a, 3) - fmt.eprintln(b) // [a, b, c, d\ne\n] + ["a", "b", "c\nd\ne"] + +NOTE: Allocation occurs for the array, the splits are all views of the original string. + +**Returns** A slice (allocated) of the split string (slices into original string) */ split_lines_n :: proc(s: string, n: int, allocator := context.allocator) -> []string { sep :: "\n" @@ -641,14 +1191,33 @@ split_lines_n :: proc(s: string, n: int, allocator := context.allocator) -> []st } return lines } - /* - split the string `s` at every line break '\n' leaving the '\n' in the resulting strings - return an allocated slice of strings +Splits the input string at every line break `\n` leaving the `\n` in the resulting strings. + +*Allocates Using Provided Allocator* + +**Inputs** +- s: The input string to split. +- allocator: (default is context.allocator) + +Example: + + import "core:fmt" + import "core:strings" + + strings_split_lines_after_example :: proc() { + a := "a\nb\nc\nd\ne" + b := strings.split_lines_after(a) + fmt.println(b) + } - a := "a\nb\nc\nd\ne" - b := strings.split_lines_after(a) - fmt.eprintln(b) // [a\n, b\n, c\n, d\n, e\n] +Output: + + ["a\n", "b\n", "c\n", "d\n", "e"] + +NOTE: Allocation occurs for the array, the splits are all views of the original string. + +**Returns** A slice (allocated) of the split string (slices into original string), with `\n` included. */ split_lines_after :: proc(s: string, allocator := context.allocator) -> []string { sep :: "\n" @@ -658,15 +1227,35 @@ split_lines_after :: proc(s: string, allocator := context.allocator) -> []string } return lines } - /* - split the string `s` at every line break '\n' leaving the '\n' in the resulting strings - only runs for `n` parts - return an allocated slice of strings +Splits the input string at every line break `\n` leaving the `\n` in the resulting strings. +Only runs for n parts. + +*Allocates Using Provided Allocator* - a := "a\nb\nc\nd\ne" - b := strings.split_lines_after_n(a, 3) - fmt.eprintln(b) // [a\n, b\n, c\n, d\ne\n] +**Inputs** +- s: The input string to split. +- n: The number of parts to split into. +- allocator: (default is context.allocator) + +Example: + + import "core:fmt" + import "core:strings" + + strings_split_lines_after_n_example :: proc() { + a := "a\nb\nc\nd\ne" + b := strings.split_lines_after_n(a, 3) + fmt.println(b) + } + +Output: + + ["a\n", "b\n", "c\nd\ne"] + +NOTE: Allocation occurs for the array, the splits are all views of the original string. + +**Returns** A slice (allocated) of the split string (slices into original string), with `\n` included. */ split_lines_after_n :: proc(s: string, n: int, allocator := context.allocator) -> []string { sep :: "\n" @@ -676,45 +1265,98 @@ split_lines_after_n :: proc(s: string, n: int, allocator := context.allocator) - } return lines } - /* - split the string `s` at every line break '\n' - returns the current split string every iteration till the string is consumed +Splits the input string at every line break `\n`. +Returns the current split string every iteration until the string is consumed. - text := "a\nb\nc\nd\ne" - for str in strings.split_lines_iterator(&text) { - fmt.eprintln(text) // every loop -> a b c d e +**Inputs** +- s: Pointer to the input string, which is modified during the search. + +Example: + + import "core:fmt" + import "core:strings" + + strings_split_lines_iterator_example :: proc() { + text := "a\nb\nc\nd\ne" + for str in strings.split_lines_iterator(&text) { + fmt.print(str) // every loop -> a b c d e + } } + +Output: + + abcde + +**Returns** A tuple containing the resulting substring and a boolean indicating success. */ split_lines_iterator :: proc(s: ^string) -> (line: string, ok: bool) { sep :: "\n" line = _split_iterator(s, sep, 0) or_return return _trim_cr(line), true } - /* - split the string `s` at every line break '\n' - returns the current split string every iteration till the string is consumed +Splits the input string at every line break `\n`. +Returns the current split string with line breaks included every iteration until the string is consumed. - text := "a\nb\nc\nd\ne" - for str in strings.split_lines_after_iterator(&text) { - fmt.eprintln(text) // every loop -> a\n b\n c\n d\n e\n +**Inputs** +- s: Pointer to the input string, which is modified during the search. + +Example: + + import "core:fmt" + import "core:strings" + + strings_split_lines_after_iterator_example :: proc() { + text := "a\nb\nc\nd\ne" + for str in strings.split_lines_after_iterator(&text) { + fmt.print(str) // every loop -> a\n b\n c\n d\n e\n + } } + +Output: + + a + b + c + d + e + +**Returns** A tuple containing the resulting substring with line breaks included and a boolean indicating success. */ split_lines_after_iterator :: proc(s: ^string) -> (line: string, ok: bool) { sep :: "\n" line = _split_iterator(s, sep, len(sep)) or_return return _trim_cr(line), true } - /* - returns the byte offset of the first byte `c` in the string `s` it finds, -1 when not found - can't find utf8 based runes +Returns the byte offset of the first byte `c` in the string s it finds, -1 when not found. +NOTE: Can't find UTF-8 based runes. + +**Inputs** +- s: The input string to search in. +- c: The byte to search for. + +Example: + + import "core:fmt" + import "core:strings" + + strings_index_byte_example :: proc() { + fmt.println(strings.index_byte("test", 't')) + fmt.println(strings.index_byte("test", 'e')) + fmt.println(strings.index_byte("test", 'x')) + fmt.println(strings.index_byte("teäst", 'ä')) + } - strings.index_byte("test", 't') -> 0 - strings.index_byte("test", 'e') -> 1 - strings.index_byte("test", 'x') -> -1 - strings.index_byte("teäst", 'ä') -> -1 +Output: + + 0 + 1 + -1 + -1 + +**Returns** The byte offset of the first occurrence of `c` in `s`, or -1 if not found. */ index_byte :: proc(s: string, c: byte) -> int { for i := 0; i < len(s); i += 1 { @@ -724,15 +1366,30 @@ index_byte :: proc(s: string, c: byte) -> int { } return -1 } - /* - returns the byte offset of the last byte `c` in the string `s` it finds, -1 when not found - can't find utf8 based runes +Returns the byte offset of the last byte `c` in the string `s`, -1 when not found. +NOTE: Can't find UTF-8 based runes. + +Example: + + import "core:fmt" + import "core:strings" + + strings_last_index_byte_example :: proc() { + fmt.println(strings.last_index_byte("test", 't')) + fmt.println(strings.last_index_byte("test", 'e')) + fmt.println(strings.last_index_byte("test", 'x')) + fmt.println(strings.last_index_byte("teäst", 'ä')) + } + +Output: + + 3 + 1 + -1 + -1 - strings.index_byte("test", 't') -> 3 - strings.index_byte("test", 'e') -> 1 - strings.index_byte("test", 'x') -> -1 - strings.index_byte("teäst", 'ä') -> -1 +**Returns** The byte offset of the last occurrence of `c` in `s`, or -1 if not found. */ last_index_byte :: proc(s: string, c: byte) -> int { for i := len(s)-1; i >= 0; i -= 1 { @@ -742,20 +1399,37 @@ last_index_byte :: proc(s: string, c: byte) -> int { } return -1 } +/* +Returns the byte offset of the first rune `r` in the string `s` it finds, -1 when not found. +Invalid runes return -1 +Example: -/* - returns the byte offset of the first rune `r` in the string `s` it finds, -1 when not found - avoids invalid runes + import "core:fmt" + import "core:strings" + + strings_index_rune_example :: proc() { + fmt.println(strings.index_rune("abcädef", 'x')) + fmt.println(strings.index_rune("abcädef", 'a')) + fmt.println(strings.index_rune("abcädef", 'b')) + fmt.println(strings.index_rune("abcädef", 'c')) + fmt.println(strings.index_rune("abcädef", 'ä')) + fmt.println(strings.index_rune("abcädef", 'd')) + fmt.println(strings.index_rune("abcädef", 'e')) + fmt.println(strings.index_rune("abcädef", 'f')) + } + +Output: - strings.index_rune("abcädef", 'x') -> -1 - strings.index_rune("abcädef", 'a') -> 0 - strings.index_rune("abcädef", 'b') -> 1 - strings.index_rune("abcädef", 'c') -> 2 - strings.index_rune("abcädef", 'ä') -> 3 - strings.index_rune("abcädef", 'd') -> 5 - strings.index_rune("abcädef", 'e') -> 6 - strings.index_rune("abcädef", 'f') -> 7 + -1 + 0 + 1 + 2 + 5 + 6 + 7 + +**Returns** The byte offset of the first occurrence of `r` in `s`, or -1 if not found. */ index_rune :: proc(s: string, r: rune) -> int { switch { @@ -779,14 +1453,29 @@ index_rune :: proc(s: string, r: rune) -> int { } @private PRIME_RABIN_KARP :: 16777619 - /* - returns the byte offset of the string `substr` in the string `s`, -1 when not found - - strings.index("test", "t") -> 0 - strings.index("test", "te") -> 0 - strings.index("test", "st") -> 2 - strings.index("test", "tt") -> -1 +Returns the byte offset of the string `substr` in the string `s`, -1 when not found. + +Example: + + import "core:fmt" + import "core:strings" + + strings_index_example :: proc() { + fmt.println(strings.index("test", "t")) + fmt.println(strings.index("test", "te")) + fmt.println(strings.index("test", "st")) + fmt.println(strings.index("test", "tt")) + } + +Output: + + 0 + 0 + 2 + -1 + +**Returns** The byte offset of the first occurrence of `substr` in `s`, or -1 if not found. */ index :: proc(s, substr: string) -> int { hash_str_rabin_karp :: proc(s: string) -> (hash: u32 = 0, pow: u32 = 1) { @@ -837,14 +1526,29 @@ index :: proc(s, substr: string) -> int { } return -1 } - /* - returns the last byte offset of the string `substr` in the string `s`, -1 when not found - - strings.index("test", "t") -> 3 - strings.index("test", "te") -> 0 - strings.index("test", "st") -> 2 - strings.index("test", "tt") -> -1 +Returns the last byte offset of the string `substr` in the string `s`, -1 when not found. + +Example: + + import "core:fmt" + import "core:strings" + + strings_last_index_example :: proc() { + fmt.println(strings.last_index("test", "t")) + fmt.println(strings.last_index("test", "te")) + fmt.println(strings.last_index("test", "st")) + fmt.println(strings.last_index("test", "tt")) + } + +Output: + + 3 + 0 + 2 + -1 + +**Returns** The byte offset of the last occurrence of `substr` in `s`, or -1 if not found. */ last_index :: proc(s, substr: string) -> int { hash_str_rabin_karp_reverse :: proc(s: string) -> (hash: u32 = 0, pow: u32 = 1) { @@ -893,15 +1597,31 @@ last_index :: proc(s, substr: string) -> int { } return -1 } - /* - returns the index of any first char of `chars` found in `s`, -1 if not found - - strings.index_any("test", "s") -> 2 - strings.index_any("test", "se") -> 1 - strings.index_any("test", "et") -> 0 - strings.index_any("test", "set") -> 0 - strings.index_any("test", "x") -> -1 +Returns the index of any first char of `chars` found in `s`, -1 if not found. + +Example: + + import "core:fmt" + import "core:strings" + + strings_index_any_example :: proc() { + fmt.println(strings.index_any("test", "s")) + fmt.println(strings.index_any("test", "se")) + fmt.println(strings.index_any("test", "et")) + fmt.println(strings.index_any("test", "set")) + fmt.println(strings.index_any("test", "x")) + } + +Output: + + 2 + 1 + 0 + 0 + -1 + +**Returns** The index of the first character of `chars` found in `s`, or -1 if not found. */ index_any :: proc(s, chars: string) -> int { if chars == "" { @@ -934,16 +1654,35 @@ index_any :: proc(s, chars: string) -> int { } return -1 } - /* - returns the last matching index in `s` of any char in `chars` found in `s`, -1 if not found - iterates the string in reverse +Finds the last occurrence of any character in `chars` within `s`. Iterates in reverse. + +**Inputs** +- s: The string to search in +- chars: The characters to look for + +Example: - strings.last_index_any("test", "s") -> 2 - strings.last_index_any("test", "se") -> 2 - strings.last_index_any("test", "et") -> 3 - strings.last_index_any("test", "set") -> 3 - strings.last_index_any("test", "x") -> -1 + import "core:fmt" + import "core:strings" + + strings_last_index_any_example :: proc() { + fmt.println(strings.last_index_any("test", "s")) + fmt.println(strings.last_index_any("test", "se")) + fmt.println(strings.last_index_any("test", "et")) + fmt.println(strings.last_index_any("test", "set")) + fmt.println(strings.last_index_any("test", "x")) + } + +Output: + + 2 + 2 + 3 + 3 + -1 + +**Returns** The index of the last matching character, or -1 if not found */ last_index_any :: proc(s, chars: string) -> int { if chars == "" { @@ -993,7 +1732,15 @@ last_index_any :: proc(s, chars: string) -> int { } return -1 } +/* +Finds the first occurrence of any substring in `substrs` within `s` + +**Inputs** +- s: The string to search in +- substrs: The substrings to look for +**Returns** A tuple containing the index of the first matching substring, and its length (width) +*/ index_multi :: proc(s: string, substrs: []string) -> (idx: int, width: int) { idx = -1 if s == "" || len(substrs) <= 0 { @@ -1023,16 +1770,35 @@ index_multi :: proc(s: string, substrs: []string) -> (idx: int, width: int) { } return } - /* - returns the count of the string `substr` found in the string `s` - returns the rune_count + 1 of the string `s` on empty `substr` +Counts the number of non-overlapping occurrences of `substr` in `s` - strings.count("abbccc", "a") -> 1 - strings.count("abbccc", "b") -> 2 - strings.count("abbccc", "c") -> 3 - strings.count("abbccc", "ab") -> 1 - strings.count("abbccc", " ") -> 0 +**Inputs** +- s: The string to search in +- substr: The substring to count + +Example: + + import "core:fmt" + import "core:strings" + + strings_count_example :: proc() { + fmt.println(strings.count("abbccc", "a")) + fmt.println(strings.count("abbccc", "b")) + fmt.println(strings.count("abbccc", "c")) + fmt.println(strings.count("abbccc", "ab")) + fmt.println(strings.count("abbccc", " ")) + } + +Output: + + 1 + 2 + 3 + 1 + 0 + +**Returns** The number of occurrences of `substr` in `s`, returns the rune_count + 1 of the string `s` on empty `substr` */ count :: proc(s, substr: string) -> int { if len(substr) == 0 { // special case @@ -1068,12 +1834,32 @@ count :: proc(s, substr: string) -> int { } return n } - /* - repeats the string `s` multiple `count` times and returns the allocated string - panics when `count` is below 0 +Repeats the string `s` `count` times, concatenating the result - strings.repeat("abc", 2) -> "abcabc" +*Allocates Using Provided Allocator* + +**Inputs** +- s: The string to repeat +- count: The number of times to repeat `s` +- allocator: (default is context.allocator) + +WARNING: Panics if count < 0 + +Example: + + import "core:fmt" + import "core:strings" + + strings_repeat_example :: proc() { + fmt.println(strings.repeat("abc", 2)) + } + +Output: + + abcabc + +**Returns** The concatenated repeated string */ repeat :: proc(s: string, count: int, allocator := context.allocator) -> string { if count < 0 { @@ -1090,28 +1876,71 @@ repeat :: proc(s: string, count: int, allocator := context.allocator) -> string } return string(b) } - /* - replaces all instances of `old` in the string `s` with the `new` string - returns the `output` string and true when an a allocation through a replace happened +Replaces all occurrences of `old` in `s` with `new` + +*Allocates Using Provided Allocator* + +**Inputs** +- s: The string to modify +- old: The substring to replace +- new: The substring to replace `old` with +- allocator: The allocator to use for the new string (default is context.allocator) + +Example: + + import "core:fmt" + import "core:strings" + + strings_replace_all_example :: proc() { + fmt.println(strings.replace_all("xyzxyz", "xyz", "abc")) + fmt.println(strings.replace_all("xyzxyz", "abc", "xyz")) + fmt.println(strings.replace_all("xyzxyz", "xy", "z")) + } + +Output: + + abcabc true + xyzxyz false + zzzz true - strings.replace_all("xyzxyz", "xyz", "abc") -> "abcabc", true - strings.replace_all("xyzxyz", "abc", "xyz") -> "xyzxyz", false - strings.replace_all("xyzxyz", "xy", "z") -> "zzzz", true +**Returns** A tuple containing the modified string and a boolean indicating if an allocation occurred during the replacement */ replace_all :: proc(s, old, new: string, allocator := context.allocator) -> (output: string, was_allocation: bool) { return replace(s, old, new, -1, allocator) } - /* - replaces `n` instances of `old` in the string `s` with the `new` string - if n < 0, no limit on the number of replacements - returns the `output` string and true when an a allocation through a replace happened +Replaces n instances of old in the string s with the new string + +*Allocates Using Provided Allocator* + +**Inputs** +- s: The input string +- old: The substring to be replaced +- new: The replacement string +- n: The number of instances to replace (if `n < 0`, no limit on the number of replacements) +- allocator: (default: context.allocator) + +Example: + + import "core:fmt" + import "core:strings" + + strings_replace_example :: proc() { + fmt.println(strings.replace("xyzxyz", "xyz", "abc", 2)) + fmt.println(strings.replace("xyzxyz", "xyz", "abc", 1)) + fmt.println(strings.replace("xyzxyz", "abc", "xyz", -1)) + fmt.println(strings.replace("xyzxyz", "xy", "z", -1)) + } + +Output: + + abcabc true + abcxyz true + xyzxyz false + zzzz true - strings.replace("xyzxyz", "xyz", "abc", 2) -> "abcabc", true - strings.replace("xyzxyz", "xyz", "abc", 1) -> "abcxyz", true - strings.replace("xyzxyz", "abc", "xyz", -1) -> "xyzxyz", false - strings.replace("xyzxyz", "xy", "z", -1) -> "zzzz", true +**Returns** A tuple containing the modified string and a boolean indicating if an allocation occurred during the replacement */ replace :: proc(s, old, new: string, n: int, allocator := context.allocator) -> (output: string, was_allocation: bool) { if old == new || n == 0 { @@ -1152,44 +1981,84 @@ replace :: proc(s, old, new: string, n: int, allocator := context.allocator) -> output = string(t[0:w]) return } - /* - removes the `key` string `n` times from the `s` string - if n < 0, no limit on the number of removes - returns the `output` string and true when an a allocation through a remove happened +Removes the key string `n` times from the `s` string + +*Allocates Using Provided Allocator* + +**Inputs** +- s: The input string +- key: The substring to be removed +- n: The number of instances to remove (if `n < 0`, no limit on the number of removes) +- allocator: (default: context.allocator) + +Example: + + import "core:fmt" + import "core:strings" + + strings_remove_example :: proc() { + fmt.println(strings.remove("abcabc", "abc", 1)) + fmt.println(strings.remove("abcabc", "abc", -1)) + fmt.println(strings.remove("abcabc", "a", -1)) + fmt.println(strings.remove("abcabc", "x", -1)) + } + +Output: + + abc true + true + bcbc true + abcabc false - strings.remove("abcabc", "abc", 1) -> "abc", true - strings.remove("abcabc", "abc", -1) -> "", true - strings.remove("abcabc", "a", -1) -> "bcbc", true - strings.remove("abcabc", "x", -1) -> "abcabc", false +**Returns** A tuple containing the modified string and a boolean indicating if an allocation occurred during the removal */ remove :: proc(s, key: string, n: int, allocator := context.allocator) -> (output: string, was_allocation: bool) { return replace(s, key, "", n, allocator) } - /* - removes all the `key` string instanes from the `s` string - returns the `output` string and true when an a allocation through a remove happened +Removes all the `key` string instances from the `s` string + +*Allocates Using Provided Allocator* + +**Inputs** +- s: The input string +- key: The substring to be removed +- allocator: (default: context.allocator) + +Example: + + import "core:fmt" + import "core:strings" + + strings_remove_all_example :: proc() { + fmt.println(strings.remove_all("abcabc", "abc")) + fmt.println(strings.remove_all("abcabc", "a")) + fmt.println(strings.remove_all("abcabc", "x")) + } + +Output: + + true + bcbc true + abcabc false - strings.remove("abcabc", "abc") -> "", true - strings.remove("abcabc", "a") -> "bcbc", true - strings.remove("abcabc", "x") -> "abcabc", false +**Returns** A tuple containing the modified string and a boolean indicating if an allocation occurred during the removal */ remove_all :: proc(s, key: string, allocator := context.allocator) -> (output: string, was_allocation: bool) { return remove(s, key, -1, allocator) } - +// Returns true if is an ASCII space character ('\t', '\n', '\v', '\f', '\r', ' ') @(private) _ascii_space := [256]bool{'\t' = true, '\n' = true, '\v' = true, '\f' = true, '\r' = true, ' ' = true} -// return true when the `r` rune is '\t', '\n', '\v', '\f', '\r' or ' ' +// Returns true when the `r` rune is '\t', '\n', '\v', '\f', '\r' or ' ' is_ascii_space :: proc(r: rune) -> bool { if r < utf8.RUNE_SELF { return _ascii_space[u8(r)] } return false } - -// returns true when the `r` rune is any asci or utf8 based whitespace +// Returns true if the `r` rune is any ASCII or UTF-8 based whitespace character is_space :: proc(r: rune) -> bool { if r < 0x2000 { switch r { @@ -1207,24 +2076,43 @@ is_space :: proc(r: rune) -> bool { } return false } - -// returns true when the `r` rune is a nul byte +// Returns true if the `r` rune is a null-byte (`0x0`) is_null :: proc(r: rune) -> bool { return r == 0x0000 } - /* - runs trough the `s` string linearly and watches wether the `p` procedure matches the `truth` bool - returns the rune offset or -1 when no match was found +Find the index of the first rune `r` in string `s` for which procedure `p` returns the same as truth, or -1 if no such rune appears. + +**Inputs** +- s: The input string +- p: A procedure that takes a rune and returns a boolean +- truth: The boolean value to be matched (default: `true`) - call :: proc(r: rune) -> bool { - return r == 'a' +Example: + + import "core:fmt" + import "core:strings" + + strings_index_proc_example :: proc() { + call :: proc(r: rune) -> bool { + return r == 'a' + } + fmt.println(strings.index_proc("abcabc", call)) + fmt.println(strings.index_proc("cbacba", call)) + fmt.println(strings.index_proc("cbacba", call, false)) + fmt.println(strings.index_proc("abcabc", call, false)) + fmt.println(strings.index_proc("xyz", call)) } - strings.index_proc("abcabc", call) -> 0 - strings.index_proc("cbacba", call) -> 2 - strings.index_proc("cbacba", call, false) -> 0 - strings.index_proc("abcabc", call, false) -> 1 - strings.index_proc("xyz", call) -> -1 + +Output: + + 0 + 2 + 0 + 1 + -1 + +**Returns** The index of the first matching rune, or -1 if no match was found */ index_proc :: proc(s: string, p: proc(rune) -> bool, truth := true) -> int { for r, i in s { @@ -1234,8 +2122,7 @@ index_proc :: proc(s: string, p: proc(rune) -> bool, truth := true) -> int { } return -1 } - -// same as `index_proc` but with a `p` procedure taking a rawptr for state +// Same as `index_proc`, but the procedure p takes a raw pointer for state index_proc_with_state :: proc(s: string, p: proc(rawptr, rune) -> bool, state: rawptr, truth := true) -> int { for r, i in s { if p(state, r) == truth { @@ -1244,8 +2131,7 @@ index_proc_with_state :: proc(s: string, p: proc(rawptr, rune) -> bool, state: r } return -1 } - -// same as `index_proc` but runs through the string in reverse +// Finds the index of the *last* rune in the string s for which the procedure p returns the same value as truth last_index_proc :: proc(s: string, p: proc(rune) -> bool, truth := true) -> int { // TODO(bill): Probably use Rabin-Karp Search for i := len(s); i > 0; { @@ -1257,8 +2143,7 @@ last_index_proc :: proc(s: string, p: proc(rune) -> bool, truth := true) -> int } return -1 } - -// same as `index_proc_with_state` but runs through the string in reverse +// Same as `index_proc_with_state`, runs through the string in reverse last_index_proc_with_state :: proc(s: string, p: proc(rawptr, rune) -> bool, state: rawptr, truth := true) -> int { // TODO(bill): Probably use Rabin-Karp Search for i := len(s); i > 0; { @@ -1270,16 +2155,30 @@ last_index_proc_with_state :: proc(s: string, p: proc(rawptr, rune) -> bool, sta } return -1 } - /* - trims the input string `s` until the procedure `p` returns false - does not allocate - only returns a cut variant of the input string - returns an empty string when no match was found at all +Trims the input string `s` from the left until the procedure `p` returns false + +**Inputs** +- s: The input string +- p: A procedure that takes a rune and returns a boolean + +Example: + + import "core:fmt" + import "core:strings" - find :: proc(r: rune) -> bool { - return r != 'i' + strings_trim_left_proc_example :: proc() { + find :: proc(r: rune) -> bool { + return r != 'i' + } + strings.trim_left_proc("testing", find) } - strings.trim_left_proc("testing", find) -> "ing" + +Output: + + ing + +**Returns** The trimmed string as a slice of the original */ trim_left_proc :: proc(s: string, p: proc(rune) -> bool) -> string { i := index_proc(s, p, false) @@ -1288,10 +2187,15 @@ trim_left_proc :: proc(s: string, p: proc(rune) -> bool) -> string { } return s[i:] } - /* - trims the input string `s` until the procedure `p` with state returns false - returns an empty string when no match was found at all +Trims the input string `s` from the left until the procedure `p` with state returns false + +**Inputs** +- s: The input string +- p: A procedure that takes a raw pointer and a rune and returns a boolean +- state: The raw pointer to be passed to the procedure `p` + +**Returns** The trimmed string as a slice of the original */ trim_left_proc_with_state :: proc(s: string, p: proc(rawptr, rune) -> bool, state: rawptr) -> string { i := index_proc_with_state(s, p, state, false) @@ -1300,16 +2204,30 @@ trim_left_proc_with_state :: proc(s: string, p: proc(rawptr, rune) -> bool, stat } return s[i:] } - /* - trims the input string `s` from the right until the procedure `p` returns false - does not allocate - only returns a cut variant of the input string - returns an empty string when no match was found at all +Trims the input string `s` from the right until the procedure `p` returns `false` - find :: proc(r: rune) -> bool { - return r != 't' +**Inputs** +- s: The input string +- p: A procedure that takes a rune and returns a boolean + +Example: + + import "core:fmt" + import "core:strings" + + strings_trim_right_proc_example :: proc() { + find :: proc(r: rune) -> bool { + return r != 't' + } + fmt.println(strings.trim_right_proc("testing", find)) } - strings.trim_left_proc("testing", find) -> "test" + +Output: + + test + +**Returns** The trimmed string as a slice of the original */ trim_right_proc :: proc(s: string, p: proc(rune) -> bool) -> string { i := last_index_proc(s, p, false) @@ -1321,10 +2239,15 @@ trim_right_proc :: proc(s: string, p: proc(rune) -> bool) -> string { } return s[0:i] } - /* - trims the input string `s` from the right until the procedure `p` with state returns false - returns an empty string when no match was found at all +Trims the input string `s` from the right until the procedure `p` with state returns `false` + +**Inputs** +- s: The input string +- p: A procedure that takes a raw pointer and a rune and returns a boolean +- state: The raw pointer to be passed to the procedure `p` + +**Returns** The trimmed string as a slice of the original, empty when no match */ trim_right_proc_with_state :: proc(s: string, p: proc(rawptr, rune) -> bool, state: rawptr) -> string { i := last_index_proc_with_state(s, p, state, false) @@ -1336,8 +2259,7 @@ trim_right_proc_with_state :: proc(s: string, p: proc(rawptr, rune) -> bool, sta } return s[0:i] } - -// procedure for `trim_*_proc` variants, which has a string rawptr cast + rune comparison +// Procedure for `trim_*_proc` variants, which has a string rawptr cast + rune comparison is_in_cutset :: proc(state: rawptr, r: rune) -> bool { if state == nil { return false @@ -1350,8 +2272,15 @@ is_in_cutset :: proc(state: rawptr, r: rune) -> bool { } return false } +/* +Trims the cutset string from the `s` string -// trims the `cutset` string from the `s` string +**Inputs** +- s: The input string +- cutset: The set of characters to be trimmed from the left of the input string + +**Returns** The trimmed string as a slice of the original +*/ trim_left :: proc(s: string, cutset: string) -> string { if s == "" || cutset == "" { return s @@ -1359,8 +2288,15 @@ trim_left :: proc(s: string, cutset: string) -> string { state := cutset return trim_left_proc_with_state(s, is_in_cutset, &state) } +/* +Trims the cutset string from the `s` string from the right + +**Inputs** +- s: The input string +- cutset: The set of characters to be trimmed from the right of the input string -// trims the `cutset` string from the `s` string from the right +**Returns** The trimmed string as a slice of the original +*/ trim_right :: proc(s: string, cutset: string) -> string { if s == "" || cutset == "" { return s @@ -1368,48 +2304,106 @@ trim_right :: proc(s: string, cutset: string) -> string { state := cutset return trim_right_proc_with_state(s, is_in_cutset, &state) } +/* +Trims the cutset string from the `s` string, both from left and right -// trims the `cutset` string from the `s` string, both from left and right +**Inputs** +- s: The input string +- cutset: The set of characters to be trimmed from both sides of the input string + +**Returns** The trimmed string as a slice of the original +*/ trim :: proc(s: string, cutset: string) -> string { return trim_right(trim_left(s, cutset), cutset) } +/* +Trims until a valid non-space rune from the left, "\t\txyz\t\t" -> "xyz\t\t" -// trims until a valid non space rune: "\t\txyz\t\t" -> "xyz\t\t" +**Inputs** +- s: The input string + +**Returns** The trimmed string as a slice of the original +*/ trim_left_space :: proc(s: string) -> string { return trim_left_proc(s, is_space) } +/* +Trims from the right until a valid non-space rune, "\t\txyz\t\t" -> "\t\txyz" + +**Inputs** +- s: The input string -// trims from the right until a valid non space rune: "\t\txyz\t\t" -> "\t\txyz" +**Returns** The trimmed string as a slice of the original +*/ trim_right_space :: proc(s: string) -> string { return trim_right_proc(s, is_space) } +/* +Trims from both sides until a valid non-space rune, "\t\txyz\t\t" -> "xyz" -// trims from both sides until a valid non space rune: "\t\txyz\t\t" -> "xyz" +**Inputs** +- s: The input string + +**Returns** The trimmed string as a slice of the original +*/ trim_space :: proc(s: string) -> string { return trim_right_space(trim_left_space(s)) } +/* +Trims null runes from the left, "\x00\x00testing\x00\x00" -> "testing\x00\x00" -// trims nul runes from the left: "\x00\x00testing\x00\x00" -> "testing\x00\x00" +**Inputs** +- s: The input string + +**Returns** The trimmed string as a slice of the original +*/ trim_left_null :: proc(s: string) -> string { return trim_left_proc(s, is_null) } +/* +Trims null runes from the right, "\x00\x00testing\x00\x00" -> "\x00\x00testing" + +**Inputs** +- s: The input string -// trims nul runes from the right: "\x00\x00testing\x00\x00" -> "\x00\x00testing" +**Returns** The trimmed string as a slice of the original +*/ trim_right_null :: proc(s: string) -> string { return trim_right_proc(s, is_null) } +/* +Trims null runes from both sides, "\x00\x00testing\x00\x00" -> "testing" -// trims nul runes from both sides: "\x00\x00testing\x00\x00" -> "testing" +**Inputs** +- s: The input string +**Returns** The trimmed string as a slice of the original +*/ trim_null :: proc(s: string) -> string { return trim_right_null(trim_left_null(s)) } - /* - trims a `prefix` string from the start of the `s` string and returns the trimmed string - returns the input string `s` when no prefix was found +Trims a `prefix` string from the start of the `s` string and returns the trimmed string + +**Inputs** +- s: The input string +- prefix: The prefix string to be removed + +Example: + + import "core:fmt" + import "core:strings" + + strings_trim_prefix_example :: proc() { + fmt.println(strings.trim_prefix("testing", "test")) + fmt.println(strings.trim_prefix("testing", "abc")) + } + +Output: + + ing + testing - strings.trim_prefix("testing", "test") -> "ing" - strings.trim_prefix("testing", "abc") -> "testing" +**Returns** The trimmed string as a slice of original, or the input string if no prefix was found */ trim_prefix :: proc(s, prefix: string) -> string { if has_prefix(s, prefix) { @@ -1417,13 +2411,29 @@ trim_prefix :: proc(s, prefix: string) -> string { } return s } - /* - trims a `suffix` string from the end of the `s` string and returns the trimmed string - returns the input string `s` when no suffix was found +Trims a `suffix` string from the end of the `s` string and returns the trimmed string + +**Inputs** +- s: The input string +- suffix: The suffix string to be removed + +Example: - strings.trim_suffix("todo.txt", ".txt") -> "todo" - strings.trim_suffix("todo.doc", ".txt") -> "todo.doc" + import "core:fmt" + import "core:strings" + + strings_trim_suffix_example :: proc() { + fmt.println(strings.trim_suffix("todo.txt", ".txt")) + fmt.println(strings.trim_suffix("todo.doc", ".txt")) + } + +Output: + + todo + todo.doc + +**Returns** The trimmed string as a slice of original, or the input string if no suffix was found */ trim_suffix :: proc(s, suffix: string) -> string { if has_suffix(s, suffix) { @@ -1431,14 +2441,34 @@ trim_suffix :: proc(s, suffix: string) -> string { } return s } - /* - splits the input string `s` by all possible `substrs` []string - returns the allocated []string, nil on any empty substring or no matches +Splits the input string `s` by all possible `substrs` and returns an allocated array of strings - splits := [?]string { "---", "~~~", ".", "_", "," } - res := strings.split_multi("testing,this.out_nice---done~~~last", splits[:]) - fmt.eprintln(res) // -> [testing, this, out, nice, done, last] +*Allocates Using Provided Allocator* + +**Inputs** +- s: The input string +- substrs: An array of substrings used for splitting +- allocator: (default is context.allocator) + +NOTE: Allocation occurs for the array, the splits are all views of the original string. + +Example: + + import "core:fmt" + import "core:strings" + + strings_split_multi_example :: proc() { + splits := [?]string { "---", "~~~", ".", "_", "," } + res := strings.split_multi("testing,this.out_nice---done~~~last", splits[:]) + fmt.println(res) // -> [testing, this, out, nice, done, last] + } + +Output: + + ["testing", "this", "out", "nice", "done", "last"] + +**Returns** An array of strings, or nil on empty substring or no matches */ split_multi :: proc(s: string, substrs: []string, allocator := context.allocator) -> []string #no_bounds_check { if s == "" || len(substrs) <= 0 { @@ -1480,15 +2510,36 @@ split_multi :: proc(s: string, substrs: []string, allocator := context.allocator assert(len(results) == n) return results[:] } - /* - splits the input string `s` by all possible `substrs` []string in an iterator fashion - returns the split string every iteration, the full string on no match - splits := [?]string { "---", "~~~", ".", "_", "," } - it := "testing,this.out_nice---done~~~last" - for str in strings.split_multi_iterate(&it, splits[:]) { - fmt.eprintln(str) // every iteration -> [testing, this, out, nice, done, last] +Splits the input string `s` by all possible `substrs` in an iterator fashion. The full string is returned if no match. + +**Inputs** +- it: A pointer to the input string +- substrs: An array of substrings used for splitting + +Example: + + import "core:fmt" + import "core:strings" + + strings_split_multi_iterate_example :: proc() { + it := "testing,this.out_nice---done~~~last" + splits := [?]string { "---", "~~~", ".", "_", "," } + for str in strings.split_multi_iterate(&it, splits[:]) { + fmt.println(str) + } } + +Output: + + testing + this + out + nice + done + last + +**Returns** A tuple containing the split string and a boolean indicating success or failure */ split_multi_iterate :: proc(it: ^string, substrs: []string) -> (res: string, ok: bool) #no_bounds_check { if it == nil || len(it) == 0 || len(substrs) <= 0 { @@ -1515,9 +2566,32 @@ split_multi_iterate :: proc(it: ^string, substrs: []string) -> (res: string, ok: ok = true return } +/* +Replaces invalid UTF-8 characters in the input string with a specified replacement string. Adjacent invalid bytes are only replaced once. + +*Allocates Using Provided Allocator* + +**Inputs** +- s: The input string +- replacement: The string used to replace invalid UTF-8 characters +- allocator: (default is context.allocator) + +Example: + + import "core:fmt" + import "core:strings" + + strings_scrub_example :: proc() { + text := "Hello\xC0\x80World" + fmt.println(strings.scrub(text, "?")) // -> "Hello?World" + } + +Output: -// scrub scruvs invalid utf-8 characters and replaces them with the replacement string -// Adjacent invalid bytes are only replaced once + Hello? + +**Returns** A new string with invalid UTF-8 characters replaced +*/ scrub :: proc(s: string, replacement: string, allocator := context.allocator) -> string { str := s b: Builder @@ -1549,13 +2623,31 @@ scrub :: proc(s: string, replacement: string, allocator := context.allocator) -> return to_string(b) } - /* - returns a reversed version of the `s` string +Reverses the input string `s` + +*Allocates Using Provided Allocator* + +**Inputs** +- s: The input string +- allocator: (default is context.allocator) + +Example: + + import "core:fmt" + import "core:strings" + + strings_reverse_example :: proc() { + a := "abcxyz" + b := strings.reverse(a) + fmt.println(a, b) + } - a := "abcxyz" - b := strings.reverse(a) - fmt.eprintln(a, b) // abcxyz zyxcba +Output: + + abcxyz zyxcba + +**Returns** A reversed version of the input string */ reverse :: proc(s: string, allocator := context.allocator) -> string { str := s @@ -1571,14 +2663,33 @@ reverse :: proc(s: string, allocator := context.allocator) -> string { } return string(buf) } - /* - expands the string to a grid spaced by `tab_size` whenever a `\t` character appears - returns the tabbed string, panics on tab_size <= 0 +Expands the input string by replacing tab characters with spaces to align to a specified tab size + +*Allocates Using Provided Allocator* + +**Inputs** +- s: The input string +- tab_size: The number of spaces to use for each tab character +- allocator: (default is context.allocator) + +Example: + + import "core:fmt" + import "core:strings" + + strings_expand_tabs_example :: proc() { + text := "abc1\tabc2\tabc3" + fmt.println(strings.expand_tabs(text, 4)) + } - strings.expand_tabs("abc1\tabc2\tabc3", 4) -> abc1 abc2 abc3 - strings.expand_tabs("abc1\tabc2\tabc3", 5) -> abc1 abc2 abc3 - strings.expand_tabs("abc1\tabc2\tabc3", 6) -> abc1 abc2 abc3 +Output: + + abc1 abc2 abc3 + +WARNING: Panics if tab_size <= 0 + +**Returns** A new string with tab characters expanded to the specified tab size */ expand_tabs :: proc(s: string, tab_size: int, allocator := context.allocator) -> string { if tab_size <= 0 { @@ -1621,16 +2732,32 @@ expand_tabs :: proc(s: string, tab_size: int, allocator := context.allocator) -> return to_string(b) } - /* - splits the `str` string by the seperator `sep` string and returns 3 parts - `head`: before the split, `match`: the seperator, `tail`: the end of the split - returns the input string when the `sep` was not found +Splits the input string `str` by the separator `sep` string and returns 3 parts. The values are slices of the original string. + +**Inputs** +- str: The input string +- sep: The separator string + +Example: + + import "core:fmt" + import "core:strings" + + strings_partition_example :: proc() { + text := "testing this out" + strings.partition(text, " this ") // -> head: "testing", match: " this ", tail: "out" + strings.partition(text, "hi") // -> head: "testing t", match: "hi", tail: "s out" + strings.partition(text, "xyz") // -> head: "testing this out", match: "", tail: "" + } - text := "testing this out" - strings.partition(text, " this ") -> head: "testing", match: " this ", tail: "out" - strings.partition(text, "hi") -> head: "testing t", match: "hi", tail: "s out" - strings.partition(text, "xyz") -> head: "testing this out", match: "", tail: "" +Output: + + testing this out + testing t hi s out + testing this out + +**Returns** A tuple with `head` (before the split), `match` (the separator), and `tail` (the end of the split) strings */ partition :: proc(str, sep: string) -> (head, match, tail: string) { i := index(str, sep) @@ -1644,10 +2771,21 @@ partition :: proc(str, sep: string) -> (head, match, tail: string) { tail = str[i+len(sep):] return } - +// Alias for centre_justify center_justify :: centre_justify // NOTE(bill): Because Americans exist +/* +Centers the input string within a field of specified length by adding pad string on both sides, if its length is less than the target length. + +*Allocates Using Provided Allocator* -// centre_justify returns a string with a pad string at boths sides if the str's rune length is smaller than length +**Inputs** +- str: The input string +- length: The desired length of the centered string, in runes +- pad: The string used for padding on both sides +- allocator: (default is context.allocator) + +**Returns** A new string centered within a field of the specified length +*/ centre_justify :: proc(str: string, length: int, pad: string, allocator := context.allocator) -> string { n := rune_count(str) if n >= length || pad == "" { @@ -1669,8 +2807,19 @@ centre_justify :: proc(str: string, length: int, pad: string, allocator := conte return to_string(b) } +/* +Left-justifies the input string within a field of specified length by adding pad string on the right side, if its length is less than the target length. + +*Allocates Using Provided Allocator* -// left_justify returns a string with a pad string at right side if the str's rune length is smaller than length +**Inputs** +- str: The input string +- length: The desired length of the left-justified string +- pad: The string used for padding on the right side +- allocator: (default is context.allocator) + +**Returns** A new string left-justified within a field of the specified length +*/ left_justify :: proc(str: string, length: int, pad: string, allocator := context.allocator) -> string { n := rune_count(str) if n >= length || pad == "" { @@ -1691,8 +2840,19 @@ left_justify :: proc(str: string, length: int, pad: string, allocator := context return to_string(b) } +/* +Right-justifies the input string within a field of specified length by adding pad string on the left side, if its length is less than the target length. + +*Allocates Using Provided Allocator* + +**Inputs** +- str: The input string +- length: The desired length of the right-justified string +- pad: The string used for padding on the left side +- allocator: (default is context.allocator) -// right_justify returns a string with a pad string at left side if the str's rune length is smaller than length +**Returns** A new string right-justified within a field of the specified length +*/ right_justify :: proc(str: string, length: int, pad: string, allocator := context.allocator) -> string { n := rune_count(str) if n >= length || pad == "" { @@ -1713,10 +2873,15 @@ right_justify :: proc(str: string, length: int, pad: string, allocator := contex return to_string(b) } +/* +Writes a given pad string a specified number of times to an `io.Writer` - - - +**Inputs** +- w: The io.Writer to write the pad string to +- pad: The pad string to be written +- pad_len: The length of the pad string, in runes +- remains: The number of times to write the pad string, in runes +*/ @private write_pad_string :: proc(w: io.Writer, pad: string, pad_len, remains: int) { repeats := remains / pad_len @@ -1734,10 +2899,17 @@ write_pad_string :: proc(w: io.Writer, pad: string, pad_len, remains: int) { p = p[width:] } } +/* +Splits a string into a slice of substrings at each instance of one or more consecutive white space characters, as defined by `unicode.is_space` +*Allocates Using Provided Allocator* -// fields splits the string s around each instance of one or more consecutive white space character, defined by unicode.is_space -// returning a slice of substrings of s or an empty slice if s only contains white space +**Inputs** +- s: The input string +- allocator: (default is context.allocator) + +**Returns** A slice of substrings of the input string, or an empty slice if the input string only contains white space +*/ fields :: proc(s: string, allocator := context.allocator) -> []string #no_bounds_check { n := 0 was_space := 1 @@ -1786,14 +2958,20 @@ fields :: proc(s: string, allocator := context.allocator) -> []string #no_bounds } return a } +/* +Splits a string into a slice of substrings at each run of unicode code points `r` satisfying the predicate `f(r)` + +*Allocates Using Provided Allocator* +**Inputs** +- s: The input string +- f: A predicate function to determine the split points +- allocator: (default is context.allocator) -// fields_proc splits the string s at each run of unicode code points `ch` satisfying f(ch) -// returns a slice of substrings of s -// If all code points in s satisfy f(ch) or string is empty, an empty slice is returned -// -// fields_proc makes no guarantee about the order in which it calls f(ch) -// it assumes that `f` always returns the same value for a given ch +NOTE: fields_proc makes no guarantee about the order in which it calls `f(r)`, it assumes that `f` always returns the same value for a given `r` + +**Returns** A slice of substrings of the input string, or an empty slice if all code points in the input string satisfy the predicate or if the input string is empty +*/ fields_proc :: proc(s: string, f: proc(rune) -> bool, allocator := context.allocator) -> []string #no_bounds_check { substrings := make([dynamic]string, 0, 32, allocator) @@ -1820,10 +2998,16 @@ fields_proc :: proc(s: string, f: proc(rune) -> bool, allocator := context.alloc return substrings[:] } +/* +Retrieves the first non-space substring from a mutable string reference and advances the reference. `s` is advanced from any space after the substring, or be an empty string if the substring was the remaining characters +**Inputs** +- s: A mutable string reference to be iterated -// `fields_iterator` returns the first run of characters in `s` that does not contain white space, defined by `unicode.is_space` -// `s` will then start from any space after the substring, or be an empty string if the substring was the remaining characters +**Returns** +- field: The first non-space substring found +- ok: A boolean indicating if a non-space substring was found +*/ fields_iterator :: proc(s: ^string) -> (field: string, ok: bool) { start, end := -1, -1 for r, offset in s { @@ -1852,10 +3036,21 @@ fields_iterator :: proc(s: ^string) -> (field: string, ok: bool) { s^ = s[len(s):] return } +/* +Computes the Levenshtein edit distance between two strings + +*Allocates Using Provided Allocator (deletion occurs internal to proc)* -// `levenshtein_distance` returns the Levenshtein edit distance between 2 strings. -// This is a single-row-version of the Wagner–Fischer algorithm, based on C code by Martin Ettl. -// Note: allocator isn't used if the length of string b in runes is smaller than 64. +NOTE: Does not perform internal allocation if length of string `b`, in runes, is smaller than 64 + +**Inputs** +- a, b: The two strings to compare +- allocator: (default is context.allocator) + +**Returns** The Levenshtein edit distance between the two strings + +NOTE: This implementation is a single-row-version of the Wagner–Fischer algorithm, based on C code by Martin Ettl. +*/ levenshtein_distance :: proc(a, b: string, allocator := context.allocator) -> int { LEVENSHTEIN_DEFAULT_COSTS: []int : { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, |