aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJon Lipstate <Jon@Lipstate.com>2023-04-03 23:57:28 -0700
committerJon Lipstate <Jon@Lipstate.com>2023-04-03 23:57:28 -0700
commit2550918f27e9c6fc06ee25fb0b2f5d8a7663bdd7 (patch)
tree7e754eb8d18b5edb8c20538c66e897ce22e5e3b3
parent24493e89ad581e6e095179bf89b0c01fdd3e415c (diff)
parentadcaace03cc03cc4ba9c2a9e3ffa585369f6a20e (diff)
Merge remote-tracking branch 'origin' into wsapoll
-rw-r--r--.github/workflows/ci.yml7
-rw-r--r--core/crypto/util/util.odin2
-rw-r--r--core/strings/ascii_set.odin9
-rw-r--r--core/strings/builder.odin224
-rw-r--r--core/strings/conversion.odin96
-rw-r--r--core/strings/intern.odin19
-rw-r--r--core/strings/reader.odin61
-rw-r--r--core/strings/strings.odin660
-rw-r--r--core/sys/windows/types.odin22
-rw-r--r--core/sys/windows/winmm.odin161
-rw-r--r--core/text/table/doc.odin100
-rw-r--r--core/text/table/table.odin384
-rw-r--r--core/text/table/utility.odin13
-rw-r--r--src/main.cpp6
-rw-r--r--src/parser.cpp8
-rw-r--r--tests/core/build.bat43
-rw-r--r--tests/core/compress/test_core_compress.odin7
-rw-r--r--tests/documentation/build.bat13
-rw-r--r--tests/documentation/documentation_tester.odin421
-rw-r--r--tests/internal/build.bat2
-rw-r--r--tests/issues/run.bat13
-rw-r--r--tests/vendor/build.bat4
22 files changed, 1792 insertions, 483 deletions
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index dc2691d80..5fb98fca4 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -163,6 +163,13 @@ jobs:
cd tests\internal
call build.bat
timeout-minutes: 10
+ - name: Odin documentation tests
+ shell: cmd
+ run: |
+ call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Auxiliary\Build\vcvars64.bat
+ cd tests\documentation
+ call build.bat
+ timeout-minutes: 10
- name: core:math/big tests
shell: cmd
run: |
diff --git a/core/crypto/util/util.odin b/core/crypto/util/util.odin
index 6273a232e..b9b80124a 100644
--- a/core/crypto/util/util.odin
+++ b/core/crypto/util/util.odin
@@ -11,6 +11,8 @@ package util
*/
import "core:mem"
+// Keep vet happy
+_ :: mem
// @note(bp): this can replace the other two
cast_slice :: #force_inline proc "contextless" ($D: typeid/[]$DE, src: $S/[]$SE) -> D {
diff --git a/core/strings/ascii_set.odin b/core/strings/ascii_set.odin
index 11ad8b947..c65ef1c61 100644
--- a/core/strings/ascii_set.odin
+++ b/core/strings/ascii_set.odin
@@ -12,10 +12,10 @@ Ascii_Set :: distinct [8]u32
/*
Creates an Ascii_Set with unique characters from the input string.
-**Inputs**
+Inputs:
- chars: A string containing characters to include in the Ascii_Set.
-**Returns**
+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.
*/
@@ -33,11 +33,12 @@ ascii_set_make :: proc(chars: string) -> (as: Ascii_Set, ok: bool) #no_bounds_ch
/*
Determines if a given char is contained within an Ascii_Set.
-**Inputs**
+Inputs:
- as: The Ascii_Set to search.
- c: The char to check for in the Ascii_Set.
-**Returns** A boolean indicating if the byte is contained in the Ascii_Set (true) or not (false).
+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
diff --git a/core/strings/builder.odin b/core/strings/builder.odin
index 15bda1b2d..32442c21a 100644
--- a/core/strings/builder.odin
+++ b/core/strings/builder.odin
@@ -7,10 +7,11 @@ import "core:io"
/*
Type definition for a procedure that flushes a Builder
-**Inputs**
+Inputs:
- b: A pointer to the Builder
-**Returns** A boolean indicating whether the Builder should be reset
+Returns:
+A boolean indicating whether the Builder should be reset
*/
Builder_Flush_Proc :: #type proc(b: ^Builder) -> (do_reset: bool)
/*
@@ -26,10 +27,11 @@ Produces a Builder with a default length of 0 and cap of 16
*Allocates Using Provided Allocator*
-**Inputs**
+Inputs:
- allocator: (default is context.allocator)
-**Returns** A new Builder
+Returns:
+A new Builder
*/
builder_make_none :: proc(allocator := context.allocator) -> Builder {
return Builder{buf=make([dynamic]byte, allocator)}
@@ -39,11 +41,12 @@ Produces a Builder with a specified length and cap of max(16,len) byte buffer
*Allocates Using Provided Allocator*
-**Inputs**
+Inputs:
- len: The desired length of the Builder's buffer
- allocator: (default is context.allocator)
-**Returns** A new Builder
+Returns:
+A new Builder
*/
builder_make_len :: proc(len: int, allocator := context.allocator) -> Builder {
return Builder{buf=make([dynamic]byte, len, allocator)}
@@ -53,12 +56,13 @@ Produces a Builder with a specified length and cap
*Allocates Using Provided Allocator*
-**Inputs**
+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
+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)}
@@ -75,11 +79,12 @@ It replaces the existing `buf`
*Allocates Using Provided Allocator*
-**Inputs**
+Inputs:
- b: A pointer to the Builder
- allocator: (default is context.allocator)
-**Returns** initialized ^Builder
+Returns:
+initialized ^Builder
*/
builder_init_none :: proc(b: ^Builder, allocator := context.allocator) -> ^Builder {
b.buf = make([dynamic]byte, allocator)
@@ -91,12 +96,13 @@ It replaces the existing `buf`
*Allocates Using Provided Allocator*
-**Inputs**
+Inputs:
- b: A pointer to the Builder
- len: The desired length of the Builder's buffer
- allocator: (default is context.allocator)
-**Returns** Initialized ^Builder
+Returns:
+Initialized ^Builder
*/
builder_init_len :: proc(b: ^Builder, len: int, allocator := context.allocator) -> ^Builder {
b.buf = make([dynamic]byte, len, allocator)
@@ -106,13 +112,14 @@ builder_init_len :: proc(b: ^Builder, len: int, allocator := context.allocator)
Initializes a Builder with a specified length and cap
It replaces the existing `buf`
-**Inputs**
+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)
-**Returns** A pointer to the initialized Builder
+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)
@@ -158,10 +165,11 @@ _builder_stream_vtable := &_builder_stream_vtable_obj
/*
Returns an io.Stream from a Builder
-**Inputs**
+Inputs:
- b: A pointer to the Builder
-**Returns** An io.Stream
+Returns:
+An io.Stream
*/
to_stream :: proc(b: ^Builder) -> io.Stream {
return io.Stream{stream_vtable=_builder_stream_vtable, stream_data=b}
@@ -169,10 +177,11 @@ to_stream :: proc(b: ^Builder) -> io.Stream {
/*
Returns an io.Writer from a Builder
-**Inputs**
+Inputs:
- b: A pointer to the Builder
-**Returns** An io.Writer
+Returns:
+An io.Writer
*/
to_writer :: proc(b: ^Builder) -> io.Writer {
return io.to_writer(to_stream(b))
@@ -180,7 +189,7 @@ to_writer :: proc(b: ^Builder) -> io.Writer {
/*
Deletes the Builder byte buffer content
-**Inputs**
+Inputs:
- b: A pointer to the Builder
*/
builder_destroy :: proc(b: ^Builder) {
@@ -190,7 +199,7 @@ builder_destroy :: proc(b: ^Builder) {
/*
Reserves the Builder byte buffer to a specific capacity, when it's higher than before
-**Inputs**
+Inputs:
- b: A pointer to the Builder
- cap: The desired capacity for the Builder's buffer
*/
@@ -200,7 +209,7 @@ builder_grow :: proc(b: ^Builder, cap: int) {
/*
Clears the Builder byte buffer content (sets len to zero)
-**Inputs**
+Inputs:
- b: A pointer to the Builder
*/
builder_reset :: proc(b: ^Builder) {
@@ -211,18 +220,23 @@ Creates a Builder from a slice of bytes with the same slice length as its capaci
*Uses Nil Allocator - Does NOT allocate*
-**Inputs**
+Inputs:
- backing: A slice of bytes to be used as the backing buffer
+Returns:
+A new Builder
+
Example:
import "core:fmt"
import "core:strings"
- strings_builder_from_bytes_example :: proc() {
+ 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"
+ strings.write_byte(&builder, 'a')
+ fmt.println(strings.to_string(builder)) // -> "a"
+ strings.write_byte(&builder, 'b')
+ fmt.println(strings.to_string(builder)) // -> "ab"
}
Output:
@@ -230,7 +244,6 @@ Output:
a
ab
-**Returns** A new Builder
*/
builder_from_bytes :: proc(backing: []byte) -> Builder {
s := transmute(runtime.Raw_Slice)backing
@@ -249,10 +262,11 @@ builder_from_slice :: builder_from_bytes
/*
Casts the Builder byte buffer to a string and returns it
-**Inputs**
+Inputs:
- b: A Builder
-**Returns** The contents of the Builder's buffer, as a string
+Returns:
+The contents of the Builder's buffer, as a string
*/
to_string :: proc(b: Builder) -> string {
return string(b.buf[:])
@@ -260,10 +274,11 @@ to_string :: proc(b: Builder) -> string {
/*
Returns the length of the Builder's buffer, in bytes
-**Inputs**
+Inputs:
- b: A Builder
-**Returns** The length of the Builder's buffer
+Returns:
+The length of the Builder's buffer
*/
builder_len :: proc(b: Builder) -> int {
return len(b.buf)
@@ -271,10 +286,11 @@ builder_len :: proc(b: Builder) -> int {
/*
Returns the capacity of the Builder's buffer, in bytes
-**Inputs**
+Inputs:
- b: A Builder
-**Returns** The capacity of the Builder's buffer
+Returns:
+The capacity of the Builder's buffer
*/
builder_cap :: proc(b: Builder) -> int {
return cap(b.buf)
@@ -282,10 +298,11 @@ builder_cap :: proc(b: Builder) -> int {
/*
The free space left in the Builder's buffer, in bytes
-**Inputs**
+Inputs:
- b: A Builder
-**Returns** The available space left in the Builder's buffer
+Returns:
+The available space left in the Builder's buffer
*/
builder_space :: proc(b: Builder) -> int {
return cap(b.buf) - len(b.buf)
@@ -293,16 +310,21 @@ builder_space :: proc(b: Builder) -> int {
/*
Appends a byte to the Builder and returns the number of bytes appended
-**Inputs**
+Inputs:
- b: A pointer to the Builder
- x: The byte to be appended
+Returns:
+The number of bytes appended
+
+NOTE: The backing dynamic array may be fixed in capacity or fail to resize, `n` states the number actually written.
+
Example:
import "core:fmt"
import "core:strings"
- strings_write_byte_example :: proc() {
+ write_byte_example :: proc() {
builder := strings.builder_make()
strings.write_byte(&builder, 'a') // 1
strings.write_byte(&builder, 'b') // 1
@@ -313,9 +335,6 @@ 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)
@@ -326,7 +345,7 @@ write_byte :: proc(b: ^Builder, x: byte) -> (n: int) {
/*
Appends a slice of bytes to the Builder and returns the number of bytes appended
-**Inputs**
+Inputs:
- b: A pointer to the Builder
- x: The slice of bytes to be appended
@@ -335,7 +354,7 @@ Example:
import "core:fmt"
import "core:strings"
- strings_write_bytes_example :: proc() {
+ write_bytes_example :: proc() {
builder := strings.builder_make()
bytes := [?]byte { 'a', 'b', 'c' }
strings.write_bytes(&builder, bytes[:]) // 3
@@ -344,7 +363,8 @@ Example:
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
+Returns:
+The number of bytes appended
*/
write_bytes :: proc(b: ^Builder, x: []byte) -> (n: int) {
n0 := len(b.buf)
@@ -355,16 +375,21 @@ write_bytes :: proc(b: ^Builder, x: []byte) -> (n: int) {
/*
Appends a single rune to the Builder and returns the number of bytes written and an `io.Error`
-**Inputs**
+Inputs:
- b: A pointer to the Builder
- r: The rune to be appended
+Returns:
+The number of bytes written and an io.Error (if any)
+
+NOTE: The backing dynamic array may be fixed in capacity or fail to resize, `n` states the number actually written.
+
Example:
import "core:fmt"
import "core:strings"
- strings_write_rune_example :: proc() {
+ write_rune_example :: proc() {
builder := strings.builder_make()
strings.write_rune(&builder, 'ä') // 2 None
strings.write_rune(&builder, 'b') // 1 None
@@ -375,9 +400,6 @@ 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)
@@ -385,16 +407,21 @@ write_rune :: proc(b: ^Builder, r: rune) -> (int, io.Error) {
/*
Appends a quoted rune to the Builder and returns the number of bytes written
-**Inputs**
+Inputs:
- b: A pointer to the Builder
- r: The rune to be appended
+Returns:
+The number of bytes written
+
+NOTE: The backing dynamic array may be fixed in capacity or fail to resize, `n` states the number actually written.
+
Example:
import "core:fmt"
import "core:strings"
- strings_write_quoted_rune_example :: proc() {
+ write_quoted_rune_example :: proc() {
builder := strings.builder_make()
strings.write_string(&builder, "abc") // 3
strings.write_quoted_rune(&builder, 'ä') // 4
@@ -406,9 +433,6 @@ 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)
@@ -416,16 +440,21 @@ write_quoted_rune :: proc(b: ^Builder, r: rune) -> (n: int) {
/*
Appends a string to the Builder and returns the number of bytes written
-**Inputs**
+Inputs:
- b: A pointer to the Builder
- s: The string to be appended
+Returns:
+The number of bytes written
+
+NOTE: The backing dynamic array may be fixed in capacity or fail to resize, `n` states the number actually written.
+
Example:
import "core:fmt"
import "core:strings"
- strings_write_string_example :: proc() {
+ write_string_example :: proc() {
builder := strings.builder_make()
strings.write_string(&builder, "a") // 1
strings.write_string(&builder, "bc") // 2
@@ -436,9 +465,6 @@ 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)
@@ -449,10 +475,11 @@ write_string :: proc(b: ^Builder, s: string) -> (n: int) {
/*
Pops and returns the last byte in the Builder or 0 when the Builder is empty
-**Inputs**
+Inputs:
- b: A pointer to the Builder
-**Returns** The last byte in the Builder or 0 if empty
+Returns:
+The last byte in the Builder or 0 if empty
*/
pop_byte :: proc(b: ^Builder) -> (r: byte) {
if len(b.buf) == 0 {
@@ -467,10 +494,11 @@ pop_byte :: proc(b: ^Builder) -> (r: byte) {
/*
Pops the last rune in the Builder and returns the popped rune and its rune width or (0, 0) if empty
-**Inputs**
+Inputs:
- b: A pointer to the Builder
-**Returns** The popped rune and its rune width or (0, 0) if 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 {
@@ -485,17 +513,22 @@ pop_rune :: proc(b: ^Builder) -> (r: rune, width: int) {
@(private)
DIGITS_LOWER := "0123456789abcdefx"
/*
-**Inputs**
+Inputs:
- b: A pointer to the Builder
- str: The string to be quoted and appended
- quote: The optional quote character (default is double quotes)
+Returns:
+The number of bytes written
+
+NOTE: The backing dynamic array may be fixed in capacity or fail to resize, `n` states the number actually written.
+
Example:
import "core:fmt"
import "core:strings"
- strings_write_quoted_string_example :: proc() {
+ write_quoted_string_example :: proc() {
builder := strings.builder_make()
strings.write_quoted_string(&builder, "a") // 3
strings.write_quoted_string(&builder, "bc", '\'') // 4
@@ -507,9 +540,6 @@ Output:
"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)
@@ -518,11 +548,16 @@ write_quoted_string :: proc(b: ^Builder, str: string, quote: byte = '"') -> (n:
/*
Appends a rune to the Builder and returns the number of bytes written
-**Inputs**
+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)
+Returns:
+The number of bytes written
+
+NOTE: The backing dynamic array may be fixed in capacity or fail to resize, `n` states the number actually written.
+
Example:
import "core:fmt"
@@ -540,9 +575,6 @@ Output:
a'"'x
-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)
@@ -552,7 +584,7 @@ write_encoded_rune :: proc(b: ^Builder, r: rune, write_quote := true) -> (n: int
/*
Appends an escaped rune to the Builder and returns the number of bytes written
-**Inputs**
+Inputs:
- b: A pointer to the Builder
- r: The rune to be appended
- quote: The quote character
@@ -565,7 +597,8 @@ Appends an escaped rune to the Builder and returns the number of bytes written
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
+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)
@@ -574,7 +607,7 @@ write_escaped_rune :: proc(b: ^Builder, r: rune, quote: byte, html_safe := false
/*
Writes a f64 value to the Builder and returns the number of characters written
-**Inputs**
+Inputs:
- b: A pointer to the Builder
- f: The f64 value to be appended
- fmt: The format byte
@@ -584,7 +617,8 @@ Writes a f64 value to the Builder and returns the number of characters written
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
+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
@@ -599,7 +633,7 @@ write_float :: proc(b: ^Builder, f: f64, fmt: byte, prec, bit_size: int, always_
/*
Writes a f16 value to the Builder and returns the number of characters written
-**Inputs**
+Inputs:
- b: A pointer to the Builder
- f: The f16 value to be appended
- fmt: The format byte
@@ -607,7 +641,8 @@ Writes a f16 value to the Builder and returns the number of characters written
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
+Returns:
+The number of characters written
*/
write_f16 :: proc(b: ^Builder, f: f16, fmt: byte, always_signed := false) -> (n: int) {
buf: [384]byte
@@ -620,18 +655,23 @@ write_f16 :: proc(b: ^Builder, f: f16, fmt: byte, always_signed := false) -> (n:
/*
Writes a f32 value to the Builder and returns the number of characters written
-**Inputs**
+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
+Returns:
+The number of characters written
+
+NOTE: The backing dynamic array may be fixed in capacity or fail to resize, `n` states the number actually written.
+
Example:
import "core:fmt"
import "core:strings"
- strings_write_f32_example :: proc() {
+ write_f32_example :: proc() {
builder := strings.builder_make()
strings.write_f32(&builder, 3.14159, 'f') // 6
strings.write_string(&builder, " - ") // 3
@@ -643,9 +683,6 @@ Output:
3.14159012 - -1.23000003e-01
-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
@@ -658,7 +695,7 @@ write_f32 :: proc(b: ^Builder, f: f32, fmt: byte, always_signed := false) -> (n:
/*
Writes a f32 value to the Builder and returns the number of characters written
-**Inputs**
+Inputs:
- b: A pointer to the Builder
- f: The f32 value to be appended
- fmt: The format byte
@@ -666,7 +703,8 @@ Writes a f32 value to the Builder and returns the number of characters written
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
+Returns:
+The number of characters written
*/
write_f64 :: proc(b: ^Builder, f: f64, fmt: byte, always_signed := false) -> (n: int) {
buf: [384]byte
@@ -679,14 +717,15 @@ write_f64 :: proc(b: ^Builder, f: f64, fmt: byte, always_signed := false) -> (n:
/*
Writes a u64 value to the Builder and returns the number of characters written
-**Inputs**
+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.
-**Returns** The number of characters written
+Returns:
+The number of characters written
*/
write_u64 :: proc(b: ^Builder, i: u64, base: int = 10) -> (n: int) {
buf: [32]byte
@@ -696,14 +735,15 @@ write_u64 :: proc(b: ^Builder, i: u64, base: int = 10) -> (n: int) {
/*
Writes a i64 value to the Builder and returns the number of characters written
-**Inputs**
+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.
-**Returns** The number of characters written
+Returns:
+The number of characters written
*/
write_i64 :: proc(b: ^Builder, i: i64, base: int = 10) -> (n: int) {
buf: [32]byte
@@ -713,14 +753,15 @@ write_i64 :: proc(b: ^Builder, i: i64, base: int = 10) -> (n: int) {
/*
Writes a uint value to the Builder and returns the number of characters written
-**Inputs**
+Inputs:
- b: A pointer to the Builder
- i: The uint 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.
-**Returns** The number of characters 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)
@@ -728,14 +769,15 @@ write_uint :: proc(b: ^Builder, i: uint, base: int = 10) -> (n: int) {
/*
Writes a int value to the Builder and returns the number of characters written
-**Inputs**
+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.
-**Returns** The number of characters written
+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 d71dc4724..0160c8a60 100644
--- a/core/strings/conversion.odin
+++ b/core/strings/conversion.odin
@@ -9,14 +9,15 @@ Converts invalid UTF-8 sequences in the input string `s` to the `replacement` st
*Allocates Using Provided Allocator*
-**Inputs**
+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`.
+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 {
@@ -76,16 +77,19 @@ Converts the input string `s` to all lowercase characters.
*Allocates Using Provided Allocator*
-**Inputs**
+Inputs:
- s: Input string to be converted.
- allocator: (default: context.allocator).
+Returns:
+A new string with all characters converted to lowercase.
+
Example:
import "core:fmt"
import "core:strings"
- strings_to_lower_example :: proc() {
+ to_lower_example :: proc() {
fmt.println(strings.to_lower("TeST"))
}
@@ -93,7 +97,6 @@ Output:
test
-**Returns** A new string with all characters converted to lowercase.
*/
to_lower :: proc(s: string, allocator := context.allocator) -> string {
b: Builder
@@ -108,16 +111,19 @@ Converts the input string `s` to all uppercase characters.
*Allocates Using Provided Allocator*
-**Inputs**
+Inputs:
- s: Input string to be converted.
- allocator: (default: context.allocator).
+Returns:
+A new string with all characters converted to uppercase.
+
Example:
import "core:fmt"
import "core:strings"
- strings_to_upper_example :: proc() {
+ to_upper_example :: proc() {
fmt.println(strings.to_upper("Test"))
}
@@ -125,7 +131,6 @@ Output:
TEST
-**Returns** A new string with all characters converted to uppercase.
*/
to_upper :: proc(s: string, allocator := context.allocator) -> string {
b: Builder
@@ -138,10 +143,11 @@ to_upper :: proc(s: string, allocator := context.allocator) -> string {
/*
Checks if the rune `r` is a delimiter (' ', '-', or '_').
-**Inputs**
+Inputs:
- r: Rune to check for delimiter status.
-**Returns** True if `r` is a delimiter, false otherwise.
+Returns:
+True if `r` is a delimiter, false otherwise.
*/
is_delimiter :: proc(r: rune) -> bool {
return r == '-' || r == '_' || is_space(r)
@@ -149,10 +155,11 @@ is_delimiter :: proc(r: rune) -> bool {
/*
Checks if the rune `r` is a non-alphanumeric or space character.
-**Inputs**
+Inputs:
- r: Rune to check for separator status.
-**Returns** True if `r` 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 {
@@ -179,7 +186,7 @@ is_separator :: proc(r: rune) -> bool {
/*
Iterates over a string, calling a callback for each rune with the previous, current, and next runes as arguments.
-**Inputs**
+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).
@@ -191,7 +198,7 @@ Example:
import "core:strings"
import "core:io"
- strings_string_case_iterator_example :: proc() {
+ string_case_iterator_example :: proc() {
my_callback :: proc(w: io.Writer, prev, curr, next: rune) {
fmt.println("my_callback", curr) // <-- Custom logic here
}
@@ -241,11 +248,12 @@ Converts the input string `s` to "lowerCamelCase".
*Allocates Using Provided Allocator*
-**Inputs**
+Inputs:
- s: Input string to be converted.
- allocator: (default: context.allocator).
-**Returns** A "lowerCamelCase" formatted string.
+Returns:
+A "lowerCamelCase" formatted string.
*/
to_camel_case :: proc(s: string, allocator := context.allocator) -> string {
s := s
@@ -275,11 +283,12 @@ Converts the input string `s` to "UpperCamelCase" (PascalCase).
*Allocates Using Provided Allocator*
-**Inputs**
+Inputs:
- s: Input string to be converted.
- allocator: (default: context.allocator).
-**Returns** A "PascalCase" formatted string.
+Returns:
+A "PascalCase" formatted string.
*/
to_pascal_case :: proc(s: string, allocator := context.allocator) -> string {
s := s
@@ -307,18 +316,21 @@ Returns a string converted to a delimiter-separated case with configurable casin
*Allocates Using Provided Allocator*
-**Inputs**
+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).
+Returns:
+The converted string
+
Example:
import "core:fmt"
import "core:strings"
- strings_to_delimiter_case_example :: proc() {
+ 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))
@@ -328,9 +340,8 @@ Output:
hello_world
HELLO WORLD
- a_b_c
+ a_bc
-**Returns** The converted string
*/
to_delimiter_case :: proc(
s: string,
@@ -380,16 +391,19 @@ Converts a string to "snake_case" with all runes lowercased
*Allocates Using Provided Allocator*
-**Inputs**
+Inputs:
- s: The input string to be converted
- allocator: (default: context.allocator).
+Returns:
+The converted string
+
Example:
import "core:fmt"
import "core:strings"
- strings_to_snake_case_example :: proc() {
+ to_snake_case_example :: proc() {
fmt.println(strings.to_snake_case("HelloWorld"))
fmt.println(strings.to_snake_case("Hello World"))
}
@@ -399,8 +413,6 @@ 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)
@@ -412,16 +424,19 @@ Converts a string to "SNAKE_CASE" with all runes uppercased
*Allocates Using Provided Allocator*
-**Inputs**
+Inputs:
- s: The input string to be converted
- allocator: (default: context.allocator).
+Returns:
+The converted string
+
Example:
import "core:fmt"
import "core:strings"
- strings_to_upper_snake_case_example :: proc() {
+ to_upper_snake_case_example :: proc() {
fmt.println(strings.to_upper_snake_case("HelloWorld"))
}
@@ -429,7 +444,6 @@ 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)
@@ -439,16 +453,19 @@ Converts a string to "kebab-case" with all runes lowercased
*Allocates Using Provided Allocator*
-**Inputs**
+Inputs:
- s: The input string to be converted
- allocator: (default: context.allocator).
+Returns:
+The converted string
+
Example:
import "core:fmt"
import "core:strings"
- strings_to_kebab_case_example :: proc() {
+ to_kebab_case_example :: proc() {
fmt.println(strings.to_kebab_case("HelloWorld"))
}
@@ -456,7 +473,6 @@ Output:
hello-world
-**Returns** The converted string
*/
to_kebab_case :: proc(s: string, allocator := context.allocator) -> string {
return to_delimiter_case(s, '-', false, allocator)
@@ -466,16 +482,19 @@ Converts a string to "KEBAB-CASE" with all runes uppercased
*Allocates Using Provided Allocator*
-**Inputs**
+Inputs:
- s: The input string to be converted
- allocator: (default: context.allocator).
+Returns:
+The converted string
+
Example:
import "core:fmt"
import "core:strings"
- strings_to_upper_kebab_case_example :: proc() {
+ to_upper_kebab_case_example :: proc() {
fmt.println(strings.to_upper_kebab_case("HelloWorld"))
}
@@ -483,7 +502,6 @@ 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)
@@ -493,16 +511,19 @@ Converts a string to "Ada_Case"
*Allocates Using Provided Allocator*
-**Inputs**
+Inputs:
- s: The input string to be converted
- allocator: (default: context.allocator).
+Returns:
+The converted string
+
Example:
import "core:fmt"
import "core:strings"
- strings_to_upper_kebab_case_example :: proc() {
+ to_ada_case_example :: proc() {
fmt.println(strings.to_ada_case("HelloWorld"))
}
@@ -510,7 +531,6 @@ Output:
Hello_World
-**Returns** The converted string
*/
to_ada_case :: proc(s: string, allocator := context.allocator) -> string {
s := s
diff --git a/core/strings/intern.odin b/core/strings/intern.odin
index 59395824a..463abeb1e 100644
--- a/core/strings/intern.odin
+++ b/core/strings/intern.odin
@@ -25,7 +25,7 @@ Initializes the entries map and sets the allocator for the string entries
*Allocates Using Provided Allocators*
-**Inputs**
+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)
@@ -37,7 +37,7 @@ intern_init :: proc(m: ^Intern, allocator := context.allocator, map_allocator :=
/*
Frees the map and all its content allocated using the `.allocator`.
-**Inputs**
+Inputs:
- m: A pointer to the Intern struct to be destroyed
*/
intern_destroy :: proc(m: ^Intern) {
@@ -51,13 +51,14 @@ Returns an interned copy of the given text, adding it to the map if not already
*Allocate using the Intern's Allocator (First time string is seen only)*
-**Inputs**
+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 interned string and an allocator error if any
+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
@@ -68,13 +69,14 @@ Returns an interned copy of the given text as a cstring, adding it to the map if
*Allocate using the Intern's Allocator (First time string is seen only)*
-**Inputs**
+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
+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
@@ -86,11 +88,12 @@ Sets and allocates the entry if it wasn't set yet
*Allocate using the Intern's Allocator (First time string is seen only)*
-**Inputs**
+Inputs:
- m: A pointer to the Intern struct
- text: The string to be looked up or interned
-**Returns** The new or existing interned entry and an allocator error if any
+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 {
diff --git a/core/strings/reader.odin b/core/strings/reader.odin
index 3e543cb9d..715e57ada 100644
--- a/core/strings/reader.odin
+++ b/core/strings/reader.odin
@@ -16,7 +16,7 @@ Reader :: struct {
/*
Initializes a string Reader with the provided string
-**Inputs**
+Inputs:
- r: A pointer to a Reader struct
- s: The input string to be read
*/
@@ -28,10 +28,11 @@ reader_init :: proc(r: ^Reader, s: string) {
/*
Converts a Reader into an `io.Stream`
-**Inputs**
+Inputs:
- r: A pointer to a Reader struct
-**Returns** An io.Stream for the given Reader
+Returns:
+An io.Stream for the given Reader
*/
reader_to_stream :: proc(r: ^Reader) -> (s: io.Stream) {
s.stream_data = r
@@ -41,11 +42,12 @@ reader_to_stream :: proc(r: ^Reader) -> (s: io.Stream) {
/*
Initializes a string Reader and returns an `io.Reader` for the given string
-**Inputs**
+Inputs:
- r: A pointer to a Reader struct
- s: The input string to be read
-**Returns** An io.Reader for the given string
+Returns:
+An io.Reader for the given string
*/
to_reader :: proc(r: ^Reader, s: string) -> io.Reader {
reader_init(r, s)
@@ -55,11 +57,12 @@ to_reader :: proc(r: ^Reader, s: string) -> io.Reader {
/*
Initializes a string Reader and returns an `io.Reader_At` for the given string
-**Inputs**
+Inputs:
- r: A pointer to a Reader struct
- s: The input string to be read
-**Returns** An `io.Reader_At` for the given string
+Returns:
+An `io.Reader_At` for the given string
*/
to_reader_at :: proc(r: ^Reader, s: string) -> io.Reader_At {
reader_init(r, s)
@@ -69,10 +72,11 @@ to_reader_at :: proc(r: ^Reader, s: string) -> io.Reader_At {
/*
Returns the remaining length of the Reader
-**Inputs**
+Inputs:
- r: A pointer to a Reader struct
-**Returns** The remaining length of the Reader
+Returns:
+The remaining length of the Reader
*/
reader_length :: proc(r: ^Reader) -> int {
if r.i >= i64(len(r.s)) {
@@ -83,10 +87,11 @@ reader_length :: proc(r: ^Reader) -> int {
/*
Returns the length of the string stored in the Reader
-**Inputs**
+Inputs:
- r: A pointer to a Reader struct
-**Returns** The length of the string stored in the Reader
+Returns:
+The length of the string stored in the Reader
*/
reader_size :: proc(r: ^Reader) -> i64 {
return i64(len(r.s))
@@ -94,11 +99,11 @@ reader_size :: proc(r: ^Reader) -> i64 {
/*
Reads len(p) bytes from the Reader's string and copies into the provided slice.
-**Inputs**
+Inputs:
- r: A pointer to a Reader struct
- p: A byte slice to copy data into
-**Returns**
+Returns:
- n: The number of bytes read
- err: An `io.Error` if an error occurs while reading, including `.EOF`, otherwise `nil` denotes success.
*/
@@ -114,12 +119,12 @@ reader_read :: proc(r: ^Reader, p: []byte) -> (n: int, err: io.Error) {
/*
Reads len(p) bytes from the Reader's string and copies into the provided slice, at the specified offset from the current index.
-**Inputs**
+Inputs:
- r: A pointer to a Reader struct
- p: A byte slice to copy data into
- off: The offset from which to read
-**Returns**
+Returns:
- n: The number of bytes read
- err: An `io.Error` if an error occurs while reading, including `.EOF`, otherwise `nil` denotes success.
*/
@@ -139,10 +144,10 @@ reader_read_at :: proc(r: ^Reader, p: []byte, off: i64) -> (n: int, err: io.Erro
/*
Reads and returns a single byte from the Reader's string
-**Inputs**
+Inputs:
- r: A pointer to a Reader struct
-**Returns**
+Returns:
- The byte read from the Reader
- err: An `io.Error` if an error occurs while reading, including `.EOF`, otherwise `nil` denotes success.
*/
@@ -158,10 +163,11 @@ reader_read_byte :: proc(r: ^Reader) -> (byte, io.Error) {
/*
Decrements the Reader's index (i) by 1
-**Inputs**
+Inputs:
- r: A pointer to a Reader struct
-**Returns** An `io.Error` if `r.i <= 0` (`.Invalid_Unread`), otherwise `nil` denotes success.
+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 {
@@ -174,10 +180,10 @@ reader_unread_byte :: proc(r: ^Reader) -> io.Error {
/*
Reads and returns a single rune and its `size` from the Reader's string
-**Inputs**
+Inputs:
- r: A pointer to a Reader struct
-**Returns**
+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
@@ -199,12 +205,13 @@ reader_read_rune :: proc(r: ^Reader) -> (rr: rune, size: int, err: io.Error) {
/*
Decrements the Reader's index (i) by the size of the last read rune
-**Inputs**
+Inputs:
- r: A pointer to a Reader struct
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.
+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 {
@@ -220,12 +227,12 @@ reader_unread_rune :: proc(r: ^Reader) -> io.Error {
/*
Seeks the Reader's index to a new position
-**Inputs**
+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**
+Returns:
- The absolute offset after seeking
- err: An `io.Error` if an error occurs while seeking (`.Invalid_Whence`, `.Invalid_Offset`)
*/
@@ -252,13 +259,13 @@ reader_seek :: proc(r: ^Reader, offset: i64, whence: io.Seek_From) -> (i64, io.E
/*
Writes the remaining content of the Reader's string into the provided `io.Writer`
-**Inputs**
+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**
+Returns:
- n: The number of bytes written
- err: An io.Error if an error occurs while writing (`.Short_Write`)
*/
diff --git a/core/strings/strings.odin b/core/strings/strings.odin
index 8193f4de1..3c55374b7 100644
--- a/core/strings/strings.odin
+++ b/core/strings/strings.odin
@@ -11,12 +11,13 @@ Clones a string
*Allocates Using Provided Allocator*
-**Inputs**
+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
+Returns:
+A cloned string
*/
clone :: proc(s: string, allocator := context.allocator, loc := #caller_location) -> string {
c := make([]byte, len(s), allocator, loc)
@@ -28,12 +29,12 @@ Clones a string safely (returns early with an allocation error on failure)
*Allocates Using Provided Allocator*
-**Inputs**
+Inputs:
- s: The string to be cloned
- allocator: (default: context.allocator)
- loc: The caller location for debugging purposes (default: #caller_location)
-**Returns**
+Returns:
- str: A cloned string
- err: A mem.Allocator_Error if an error occurs during allocation
*/
@@ -47,12 +48,13 @@ Clones a string and appends a null-byte to make it a cstring
*Allocates Using Provided Allocator*
-**Inputs**
+Inputs:
- s: The string to be cloned
- allocator: (default: context.allocator)
- loc: The caller location for debugging purposes (default: #caller_location)
-**Returns** A cloned cstring with an appended null-byte
+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)
@@ -63,28 +65,30 @@ clone_to_cstring :: proc(s: string, allocator := context.allocator, loc := #call
/*
Transmutes a raw pointer into a string. Non-allocating.
-**Inputs**
+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
+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
+Transmutes a raw pointer (null-terminated) into a string. Non-allocating. Searches for a null-byte from `0..<len`, otherwise `len` will be the end size
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.
+ The string is truncated at the first null-byte encountered.
-**Inputs**
+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
+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}
@@ -94,10 +98,11 @@ string_from_null_terminated_ptr :: proc(ptr: ^byte, len: int) -> string {
/*
Gets the raw byte pointer for the start of a string `str`
-**Inputs**
+Inputs:
- str: The input string
-**Returns** A pointer to the start of the string's bytes
+Returns:
+A pointer to the start of the string's bytes
*/
ptr_from_string :: proc(str: string) -> ^byte {
d := transmute(mem.Raw_String)str
@@ -106,12 +111,13 @@ ptr_from_string :: proc(str: string) -> ^byte {
/*
Converts a string `str` to a cstring
-**Inputs**
+Inputs:
- str: The input string
WARNING: This is unsafe because the original string may not contain a null-byte.
-**Returns** The converted cstring
+Returns:
+The converted cstring
*/
unsafe_string_to_cstring :: proc(str: string) -> cstring {
d := transmute(mem.Raw_String)str
@@ -120,13 +126,14 @@ unsafe_string_to_cstring :: proc(str: string) -> cstring {
/*
Truncates a string `str` at the first occurrence of char/byte `b`
-**Inputs**
+Inputs:
- str: The input string
- b: The byte to truncate the string at
NOTE: Failure to find the byte results in returning the entire string.
-**Returns** The truncated string
+Returns:
+The truncated string
*/
truncate_to_byte :: proc(str: string, b: byte) -> string {
n := index_byte(str, b)
@@ -138,11 +145,12 @@ truncate_to_byte :: proc(str: string, b: byte) -> string {
/*
Truncates a string `str` at the first occurrence of rune `r` as a slice of the original, entire string if not found
-**Inputs**
+Inputs:
- str: The input string
- r: The rune to truncate the string at
-**Returns** The truncated string
+Returns:
+The truncated string
*/
truncate_to_rune :: proc(str: string, r: rune) -> string {
n := index_rune(str, r)
@@ -156,12 +164,13 @@ Clones a byte array `s` and appends a null-byte
*Allocates Using Provided Allocator*
-**Inputs**
+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
+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)
@@ -174,12 +183,13 @@ Clones a cstring `s` as a string
*Allocates Using Provided Allocator*
-**Inputs**
+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
+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)
@@ -189,7 +199,7 @@ Clones a string from a byte pointer `ptr` and a byte length `len`
*Allocates Using Provided Allocator*
-**Inputs**
+Inputs:
- ptr: A pointer to the start of the byte sequence
- len: The length of the byte sequence
- allocator: (default: context.allocator)
@@ -197,7 +207,8 @@ Clones a string from a byte pointer `ptr` and a byte length `len`
NOTE: Same as `string_from_ptr`, but perform an additional `clone` operation
-**Returns** A cloned string from the byte pointer and length
+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)
@@ -215,7 +226,7 @@ Clones a string from a null-terminated cstring `ptr` and a byte length `len`
*Allocates Using Provided Allocator*
-**Inputs**
+Inputs:
- ptr: A pointer to the start of the null-terminated cstring
- len: The byte length of the cstring
- allocator: (default: context.allocator)
@@ -223,7 +234,8 @@ Clones a string from a null-terminated cstring `ptr` and a byte length `len`
NOTE: Truncates at the first null-byte encountered or the byte length.
-**Returns** A cloned string from the null-terminated cstring and 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)
@@ -234,11 +246,12 @@ clone_from_cstring_bounded :: proc(ptr: cstring, len: int, allocator := context.
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**
+Inputs:
- lhs: First string for comparison
- rhs: Second string for comparison
-**Returns** -1 if `lhs` comes first, 1 if `rhs` comes first, 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)
@@ -246,11 +259,12 @@ compare :: proc(lhs, rhs: string) -> int {
/*
Returns the byte offset of the rune `r` in the string `s`, -1 when not found
-**Inputs**
+Inputs:
- s: The input string
- r: The rune to search for
-**Returns** The byte offset of the rune `r` in the string `s`, or -1 if 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 {
@@ -263,16 +277,19 @@ contains_rune :: proc(s: string, r: rune) -> int {
/*
Returns true when the string `substr` is contained inside the string `s`
-**Inputs**
+Inputs:
- s: The input string
- substr: The substring to search for
+Returns:
+`true` if `substr` is contained inside the string `s`, `false` otherwise
+
Example:
import "core:fmt"
import "core:strings"
- strings_contains_example :: proc() {
+ contains_example :: proc() {
fmt.println(strings.contains("testing", "test"))
fmt.println(strings.contains("testing", "ing"))
fmt.println(strings.contains("testing", "text"))
@@ -284,7 +301,6 @@ Output:
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
@@ -292,16 +308,19 @@ contains :: proc(s, substr: string) -> bool {
/*
Returns `true` when the string `s` contains any of the characters inside the string `chars`
-**Inputs**
+Inputs:
- s: The input string
- chars: The characters to search for
+Returns:
+`true` if the string `s` contains any of the characters in `chars`, `false` otherwise
+
Example:
import "core:fmt"
import "core:strings"
- strings_contains_any_example :: proc() {
+ 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"))
@@ -315,7 +334,6 @@ Output:
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
@@ -323,15 +341,18 @@ contains_any :: proc(s, chars: string) -> bool {
/*
Returns the UTF-8 rune count of the string `s`
-**Inputs**
+Inputs:
- s: The input string
+Returns:
+The UTF-8 rune count of the string `s`
+
Example:
import "core:fmt"
import "core:strings"
- strings_rune_count_example :: proc() {
+ rune_count_example :: proc() {
fmt.println(strings.rune_count("test"))
fmt.println(strings.rune_count("testö")) // where len("testö") == 6
}
@@ -341,7 +362,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)
@@ -350,16 +370,19 @@ rune_count :: proc(s: string) -> int {
Returns whether the strings `u` and `v` are the same alpha characters, ignoring different casings
Works with UTF-8 string content
-**Inputs**
+Inputs:
- u: The first string for comparison
- v: The second string for comparison
+Returns:
+`true` if the strings `u` and `v` are the same alpha characters (ignoring case)
+
Example:
import "core:fmt"
import "core:strings"
- strings_equal_fold_example :: proc() {
+ 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"))
@@ -373,7 +396,6 @@ Output:
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
@@ -420,16 +442,19 @@ equal_fold :: proc(u, v: string) -> bool {
/*
Returns the prefix length common between strings `a` and `b`
-**Inputs**
+Inputs:
- a: The first input string
- b: The second input string
+Returns:
+The prefix length common between strings `a` and `b`
+
Example:
import "core:fmt"
import "core:strings"
- strings_prefix_length_example :: proc() {
+ 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"))
@@ -443,7 +468,6 @@ Output:
2
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))
@@ -471,16 +495,19 @@ prefix_length :: proc(a, b: string) -> (n: int) {
/*
Determines if a string `s` starts with a given `prefix`
-**Inputs**
+Inputs:
- s: The string to check for the `prefix`
- prefix: The prefix to look for
+Returns:
+`true` if the string `s` starts with the `prefix`, otherwise `false`
+
Example:
import "core:fmt"
import "core:strings"
- strings_has_prefix_example :: proc() {
+ 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"))
@@ -494,7 +521,6 @@ Output:
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
@@ -502,12 +528,19 @@ has_prefix :: proc(s, prefix: string) -> bool {
/*
Determines if a string `s` ends with a given `suffix`
+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`
+
Example:
import "core:fmt"
import "core:strings"
- strings_has_suffix_example :: proc() {
+ 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"))
@@ -519,11 +552,6 @@ Output:
false
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
@@ -533,12 +561,20 @@ Joins a slice of strings `a` with a `sep` string
*Allocates Using Provided Allocator*
+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
+
Example:
import "core:fmt"
import "core:strings"
- strings_join_example :: proc() {
+ join_example :: proc() {
a := [?]string { "a", "b", "c" }
fmt.println(strings.join(a[:], " "))
fmt.println(strings.join(a[:], "-"))
@@ -551,12 +587,6 @@ Output:
a-b-c
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 {
@@ -581,12 +611,12 @@ Joins a slice of strings `a` with a `sep` string, returns an error on allocation
*Allocates Using Provided Allocator*
-**Inputs**
+Inputs:
- a: A slice of strings to join
- sep: The separator string
- allocator: (default is context.allocator)
-**Returns**
+Returns:
- str: A combined string from the slice of strings `a` separated with the `sep` string
- err: An error if allocation failed, otherwise `nil`
*/
@@ -613,16 +643,19 @@ Returns a combined string from the slice of strings `a` without a separator
*Allocates Using Provided Allocator*
-**Inputs**
+Inputs:
- a: A slice of strings to concatenate
- allocator: (default is context.allocator)
+Returns:
+The concatenated string
+
Example:
import "core:fmt"
import "core:strings"
- strings_concatenate_example :: proc() {
+ concatenate_example :: proc() {
a := [?]string { "a", "b", "c" }
fmt.println(strings.concatenate(a[:]))
}
@@ -631,7 +664,6 @@ Output:
abc
-**Returns** The concatenated string
*/
concatenate :: proc(a: []string, allocator := context.allocator) -> string {
if len(a) == 0 {
@@ -654,11 +686,12 @@ Returns a combined string from the slice of strings `a` without a separator, or
*Allocates Using Provided Allocator*
-**Inputs**
+Inputs:
- a: A slice of strings to concatenate
- allocator: (default is context.allocator)
-**Returns** The concatenated string, and an error if allocation fails
+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 {
@@ -681,21 +714,24 @@ Returns a substring of the input string `s` with the specified rune offset and l
*Allocates Using Provided Allocator*
-**Inputs**
+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)
+Returns:
+The substring
+
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"
+ cut_example :: proc() {
+ fmt.println(strings.cut("some example text", 0, 4)) // -> "some"
+ fmt.println(strings.cut("some example text", 2, 2)) // -> "me"
+ fmt.println(strings.cut("some example text", 5, 7)) // -> "example"
}
Output:
@@ -704,7 +740,6 @@ Output:
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
@@ -764,7 +799,7 @@ Splits the input string `s` into a slice of substrings separated by the specifie
*Used Internally - Private Function*
-**Inputs**
+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
@@ -773,7 +808,8 @@ Splits the input string `s` into a slice of substrings separated by the specifie
NOTE: Allocation occurs for the array, the splits are all views of the original string.
-**Returns** A slice of substrings
+Returns:
+A slice of substrings
*/
@private
_split :: proc(s_, sep: string, sep_save, n_: int, allocator := context.allocator) -> []string {
@@ -827,17 +863,21 @@ Splits a string into parts based on a separator.
*Allocates Using Provided Allocator*
-**Inputs**
+Inputs:
- s: The string to split.
- sep: The separator string used to split the input string.
- allocator: (default is context.allocator).
+Returns: A slice of strings, each representing a part of the split string.
+
+NOTE: Allocation occurs for the array, the splits are all views of the original string.
+
Example:
import "core:fmt"
import "core:strings"
- strings_split_example :: proc() {
+ split_example :: proc() {
s := "aaa.bbb.ccc.ddd.eee" // 5 parts
ss := strings.split(s, ".")
fmt.println(ss)
@@ -847,9 +887,6 @@ 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)
@@ -859,17 +896,21 @@ Splits a string into parts based on a separator. If n < count of seperators, the
*Allocates Using Provided Allocator*
-**Inputs**
+Inputs:
- s: The string to split.
- sep: The separator string used to split the input string.
- allocator: (default is context.allocator)
+Returns: A slice of strings, each representing a part of the split string.
+
+NOTE: Allocation occurs for the array, the splits are all views of the original string.
+
Example:
import "core:fmt"
import "core:strings"
- strings_split_n_example :: proc() {
+ 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)
@@ -879,9 +920,6 @@ 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)
@@ -891,17 +929,22 @@ Splits a string into parts after the separator, retaining it in the substrings.
*Allocates Using Provided Allocator*
-**Inputs**
+Inputs:
- s: The string to split.
- sep: The separator string used to split the input string.
- allocator: (default is context.allocator).
+Returns:
+A slice of strings, each representing a part of the split string after the separator.
+
+NOTE: Allocation occurs for the array, the splits are all views of the original string.
+
Example:
import "core:fmt"
import "core:strings"
- strings_split_after_example :: proc() {
+ split_after_example :: proc() {
a := "aaa.bbb.ccc.ddd.eee" // 5 parts
aa := strings.split_after(a, ".")
fmt.println(aa)
@@ -911,9 +954,6 @@ 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 after the separator.
*/
split_after :: proc(s, sep: string, allocator := context.allocator) -> []string {
return _split(s, sep, len(sep), -1, allocator)
@@ -923,18 +963,23 @@ Splits a string into a total of `n` parts after the separator.
*Allocates Using Provided Allocator*
-**Inputs**
+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)
+Returns:
+A slice of strings with `n` parts or fewer if there weren't
+
+NOTE: Allocation occurs for the array, the splits are all views of the original string.
+
Example:
import "core:fmt"
import "core:strings"
- strings_split_after_n_example :: proc() {
+ split_after_n_example :: proc() {
a := "aaa.bbb.ccc.ddd.eee"
aa := strings.split_after_n(a, ".", 3)
fmt.println(aa)
@@ -944,9 +989,6 @@ 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)
@@ -957,12 +999,13 @@ up to (but not including) the separator, as well as a boolean indicating success
*Used Internally - Private Function*
-**Inputs**
+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.
+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) {
@@ -994,16 +1037,19 @@ _split_iterator :: proc(s: ^string, sep: string, sep_save: int) -> (res: string,
/*
Splits the input string by the byte separator in an iterator fashion.
-**Inputs**
+Inputs:
- s: Pointer to the input string, which is modified during the search.
- sep: The byte separator to search for.
+Returns:
+A tuple containing the resulting substring and a boolean indicating success.
+
Example:
import "core:fmt"
import "core:strings"
- strings_split_by_byte_iterator_example :: proc() {
+ 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
@@ -1018,7 +1064,6 @@ Output:
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)
@@ -1038,16 +1083,19 @@ split_by_byte_iterator :: proc(s: ^string, sep: u8) -> (res: string, ok: bool) {
Splits the input string by the separator string in an iterator fashion.
Destructively consumes the original string until the end.
-**Inputs**
+Inputs:
- s: Pointer to the input string, which is modified during the search.
- sep: The separator string to search for.
+Returns:
+A tuple containing the resulting substring and a boolean indicating success.
+
Example:
import "core:fmt"
import "core:strings"
- strings_split_iterator_example :: proc() {
+ split_iterator_example :: proc() {
text := "a.b.c.d.e"
for str in strings.split_iterator(&text, ".") {
fmt.println(str)
@@ -1062,7 +1110,6 @@ Output:
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)
@@ -1071,16 +1118,19 @@ split_iterator :: proc(s: ^string, sep: string) -> (string, bool) {
Splits the input string after every separator string in an iterator fashion.
Destructively consumes the original string until the end.
-**Inputs**
+Inputs:
- s: Pointer to the input string, which is modified during the search.
- sep: The separator string to search for.
+Returns:
+A tuple containing the resulting substring and a boolean indicating success.
+
Example:
import "core:fmt"
import "core:strings"
- strings_split_after_iterator_example :: proc() {
+ split_after_iterator_example :: proc() {
text := "a.b.c.d.e"
for str in strings.split_after_iterator(&text, ".") {
fmt.println(str)
@@ -1095,7 +1145,6 @@ Output:
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))
@@ -1105,10 +1154,11 @@ Trims the carriage return character from the end of the input string.
*Used Internally - Private Function*
-**Inputs**
+Inputs:
- s: The input string to trim.
-**Returns** The trimmed string as a slice of the original.
+Returns:
+The trimmed string as a slice of the original.
*/
@(private)
_trim_cr :: proc(s: string) -> string {
@@ -1125,16 +1175,19 @@ Splits the input string at every line break `\n`.
*Allocates Using Provided Allocator*
-**Inputs**
+Inputs:
- s: The input string to split.
- allocator: (default is context.allocator)
+Returns:
+A slice (allocated) of the split string (slices into original string)
+
Example:
import "core:fmt"
import "core:strings"
- strings_split_lines_example :: proc() {
+ split_lines_example :: proc() {
a := "a\nb\nc\nd\ne"
b := strings.split_lines(a)
fmt.println(b)
@@ -1144,7 +1197,6 @@ Output:
["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"
@@ -1159,17 +1211,22 @@ Splits the input string at every line break `\n` for `n` parts.
*Allocates Using Provided Allocator*
-**Inputs**
+Inputs:
- s: The input string to split.
- n: The number of parts to split into.
- allocator: (default is context.allocator)
+Returns:
+A slice (allocated) of the split string (slices into original string)
+
+NOTE: Allocation occurs for the array, the splits are all views of the original string.
+
Example:
import "core:fmt"
import "core:strings"
- strings_split_lines_n_example :: proc() {
+ split_lines_n_example :: proc() {
a := "a\nb\nc\nd\ne"
b := strings.split_lines_n(a, 3)
fmt.println(b)
@@ -1179,9 +1236,6 @@ Output:
["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"
@@ -1196,16 +1250,21 @@ Splits the input string at every line break `\n` leaving the `\n` in the resulti
*Allocates Using Provided Allocator*
-**Inputs**
+Inputs:
- s: The input string to split.
- allocator: (default is context.allocator)
+Returns:
+A slice (allocated) of the split string (slices into original string), with `\n` included.
+
+NOTE: Allocation occurs for the array, the splits are all views of the original string.
+
Example:
import "core:fmt"
import "core:strings"
- strings_split_lines_after_example :: proc() {
+ split_lines_after_example :: proc() {
a := "a\nb\nc\nd\ne"
b := strings.split_lines_after(a)
fmt.println(b)
@@ -1215,9 +1274,6 @@ 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"
@@ -1233,17 +1289,22 @@ Only runs for n parts.
*Allocates Using Provided Allocator*
-**Inputs**
+Inputs:
- s: The input string to split.
- n: The number of parts to split into.
- allocator: (default is context.allocator)
+Returns:
+A slice (allocated) of the split string (slices into original string), with `\n` included.
+
+NOTE: Allocation occurs for the array, the splits are all views of the original string.
+
Example:
import "core:fmt"
import "core:strings"
- strings_split_lines_after_n_example :: proc() {
+ split_lines_after_n_example :: proc() {
a := "a\nb\nc\nd\ne"
b := strings.split_lines_after_n(a, 3)
fmt.println(b)
@@ -1253,9 +1314,6 @@ 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"
@@ -1269,26 +1327,29 @@ split_lines_after_n :: proc(s: string, n: int, allocator := context.allocator) -
Splits the input string at every line break `\n`.
Returns the current split string every iteration until the string is consumed.
-**Inputs**
+Inputs:
- s: Pointer to the input string, which is modified during the search.
+Returns:
+A tuple containing the resulting substring and a boolean indicating success.
+
Example:
import "core:fmt"
import "core:strings"
- strings_split_lines_iterator_example :: proc() {
+ 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
}
+ fmt.print("\n")
}
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"
@@ -1299,16 +1360,19 @@ split_lines_iterator :: proc(s: ^string) -> (line: string, ok: bool) {
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.
-**Inputs**
+Inputs:
- s: Pointer to the input string, which is modified during the search.
+Returns:
+A tuple containing the resulting substring with line breaks included and a boolean indicating success.
+
Example:
import "core:fmt"
import "core:strings"
- strings_split_lines_after_iterator_example :: proc() {
- text := "a\nb\nc\nd\ne"
+ split_lines_after_iterator_example :: proc() {
+ text := "a\nb\nc\nd\ne\n"
for str in strings.split_lines_after_iterator(&text) {
fmt.print(str) // every loop -> a\n b\n c\n d\n e\n
}
@@ -1322,7 +1386,6 @@ Output:
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"
@@ -1333,16 +1396,19 @@ split_lines_after_iterator :: proc(s: ^string) -> (line: string, ok: bool) {
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**
+Inputs:
- s: The input string to search in.
- c: The byte to search for.
+Returns:
+The byte offset of the first occurrence of `c` in `s`, or -1 if not found.
+
Example:
import "core:fmt"
import "core:strings"
- strings_index_byte_example :: proc() {
+ 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'))
@@ -1356,7 +1422,6 @@ Output:
-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 {
@@ -1368,6 +1433,14 @@ index_byte :: proc(s: string, c: byte) -> int {
}
/*
Returns the byte offset of the last byte `c` in the string `s`, -1 when not found.
+
+Inputs:
+- s: The input string to search in.
+- c: The byte to search for.
+
+Returns:
+The byte offset of the last occurrence of `c` in `s`, or -1 if not found.
+
NOTE: Can't find UTF-8 based runes.
Example:
@@ -1375,7 +1448,7 @@ Example:
import "core:fmt"
import "core:strings"
- strings_last_index_byte_example :: proc() {
+ 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'))
@@ -1389,7 +1462,6 @@ Output:
-1
-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 {
@@ -1403,12 +1475,19 @@ last_index_byte :: proc(s: string, c: byte) -> int {
Returns the byte offset of the first rune `r` in the string `s` it finds, -1 when not found.
Invalid runes return -1
+Inputs:
+- s: The input string to search in.
+- r: The rune to search for.
+
+Returns:
+The byte offset of the first occurrence of `r` in `s`, or -1 if not found.
+
Example:
import "core:fmt"
import "core:strings"
- strings_index_rune_example :: proc() {
+ 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'))
@@ -1425,11 +1504,11 @@ Output:
0
1
2
+ 3
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 {
@@ -1456,12 +1535,19 @@ index_rune :: proc(s: string, r: rune) -> int {
/*
Returns the byte offset of the string `substr` in the string `s`, -1 when not found.
+Inputs:
+- s: The input string to search in.
+- substr: The substring to search for.
+
+Returns:
+The byte offset of the first occurrence of `substr` in `s`, or -1 if not found.
+
Example:
import "core:fmt"
import "core:strings"
- strings_index_example :: proc() {
+ index_example :: proc() {
fmt.println(strings.index("test", "t"))
fmt.println(strings.index("test", "te"))
fmt.println(strings.index("test", "st"))
@@ -1475,7 +1561,6 @@ Output:
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) {
@@ -1529,12 +1614,19 @@ index :: proc(s, substr: string) -> int {
/*
Returns the last byte offset of the string `substr` in the string `s`, -1 when not found.
+Inputs:
+- s: The input string to search in.
+- substr: The substring to search for.
+
+Returns:
+The byte offset of the last occurrence of `substr` in `s`, or -1 if not found.
+
Example:
import "core:fmt"
import "core:strings"
- strings_last_index_example :: proc() {
+ 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"))
@@ -1548,7 +1640,6 @@ Output:
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) {
@@ -1600,12 +1691,19 @@ last_index :: proc(s, substr: string) -> int {
/*
Returns the index of any first char of `chars` found in `s`, -1 if not found.
+Inputs:
+- s: The input string to search in.
+- chars: The characters to look for
+
+Returns:
+The index of the first character of `chars` found in `s`, or -1 if not found.
+
Example:
import "core:fmt"
import "core:strings"
- strings_index_any_example :: proc() {
+ 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"))
@@ -1621,7 +1719,6 @@ Output:
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 == "" {
@@ -1657,16 +1754,19 @@ index_any :: proc(s, chars: string) -> int {
/*
Finds the last occurrence of any character in `chars` within `s`. Iterates in reverse.
-**Inputs**
+Inputs:
- s: The string to search in
- chars: The characters to look for
+Returns:
+The index of the last matching character, or -1 if not found
+
Example:
import "core:fmt"
import "core:strings"
- strings_last_index_any_example :: proc() {
+ 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"))
@@ -1682,7 +1782,6 @@ Output:
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 == "" {
@@ -1735,11 +1834,12 @@ last_index_any :: proc(s, chars: string) -> int {
/*
Finds the first occurrence of any substring in `substrs` within `s`
-**Inputs**
+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)
+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
@@ -1773,16 +1873,19 @@ index_multi :: proc(s: string, substrs: []string) -> (idx: int, width: int) {
/*
Counts the number of non-overlapping occurrences of `substr` in `s`
-**Inputs**
+Inputs:
- s: The string to search in
- substr: The substring to count
+Returns:
+The number of occurrences of `substr` in `s`, returns the rune_count + 1 of the string `s` on empty `substr`
+
Example:
import "core:fmt"
import "core:strings"
- strings_count_example :: proc() {
+ count_example :: proc() {
fmt.println(strings.count("abbccc", "a"))
fmt.println(strings.count("abbccc", "b"))
fmt.println(strings.count("abbccc", "c"))
@@ -1798,7 +1901,6 @@ Output:
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
@@ -1839,11 +1941,14 @@ Repeats the string `s` `count` times, concatenating the result
*Allocates Using Provided Allocator*
-**Inputs**
+Inputs:
- s: The string to repeat
- count: The number of times to repeat `s`
- allocator: (default is context.allocator)
+Returns:
+The concatenated repeated string
+
WARNING: Panics if count < 0
Example:
@@ -1851,7 +1956,7 @@ Example:
import "core:fmt"
import "core:strings"
- strings_repeat_example :: proc() {
+ repeat_example :: proc() {
fmt.println(strings.repeat("abc", 2))
}
@@ -1859,7 +1964,6 @@ Output:
abcabc
-**Returns** The concatenated repeated string
*/
repeat :: proc(s: string, count: int, allocator := context.allocator) -> string {
if count < 0 {
@@ -1881,18 +1985,21 @@ Replaces all occurrences of `old` in `s` with `new`
*Allocates Using Provided Allocator*
-**Inputs**
+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)
+Returns:
+A tuple containing the modified string and a boolean indicating if an allocation occurred during the replacement
+
Example:
import "core:fmt"
import "core:strings"
- strings_replace_all_example :: proc() {
+ 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"))
@@ -1904,7 +2011,6 @@ Output:
xyzxyz false
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)
@@ -1914,19 +2020,22 @@ Replaces n instances of old in the string s with the new string
*Allocates Using Provided Allocator*
-**Inputs**
+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)
+Returns:
+A tuple containing the modified string and a boolean indicating if an allocation occurred during the replacement
+
Example:
import "core:fmt"
import "core:strings"
- strings_replace_example :: proc() {
+ 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))
@@ -1940,7 +2049,6 @@ Output:
xyzxyz false
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 {
@@ -1986,18 +2094,21 @@ Removes the key string `n` times from the `s` string
*Allocates Using Provided Allocator*
-**Inputs**
+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)
+Returns:
+A tuple containing the modified string and a boolean indicating if an allocation occurred during the removal
+
Example:
import "core:fmt"
import "core:strings"
- strings_remove_example :: proc() {
+ remove_example :: proc() {
fmt.println(strings.remove("abcabc", "abc", 1))
fmt.println(strings.remove("abcabc", "abc", -1))
fmt.println(strings.remove("abcabc", "a", -1))
@@ -2007,11 +2118,10 @@ Example:
Output:
abc true
- true
+ true
bcbc true
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)
@@ -2021,17 +2131,20 @@ Removes all the `key` string instances from the `s` string
*Allocates Using Provided Allocator*
-**Inputs**
+Inputs:
- s: The input string
- key: The substring to be removed
- allocator: (default: context.allocator)
+Returns:
+A tuple containing the modified string and a boolean indicating if an allocation occurred during the removal
+
Example:
import "core:fmt"
import "core:strings"
- strings_remove_all_example :: proc() {
+ 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"))
@@ -2043,7 +2156,6 @@ Output:
bcbc true
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)
@@ -2083,17 +2195,20 @@ is_null :: proc(r: rune) -> bool {
/*
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**
+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`)
+Returns:
+The index of the first matching rune, or -1 if no match was found
+
Example:
import "core:fmt"
import "core:strings"
- strings_index_proc_example :: proc() {
+ index_proc_example :: proc() {
call :: proc(r: rune) -> bool {
return r == 'a'
}
@@ -2112,7 +2227,6 @@ Output:
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 {
@@ -2158,27 +2272,29 @@ last_index_proc_with_state :: proc(s: string, p: proc(rawptr, rune) -> bool, sta
/*
Trims the input string `s` from the left until the procedure `p` returns false
-**Inputs**
+Inputs:
- s: The input string
- p: A procedure that takes a rune and returns a boolean
+Returns:
+The trimmed string as a slice of the original
+
Example:
import "core:fmt"
import "core:strings"
- strings_trim_left_proc_example :: proc() {
+ trim_left_proc_example :: proc() {
find :: proc(r: rune) -> bool {
- return r != 'i'
+ return r == 'x'
}
- strings.trim_left_proc("testing", find)
+ fmt.println(strings.trim_left_proc("xxxxxxtesting", find))
}
Output:
- ing
+ testing
-**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)
@@ -2190,12 +2306,13 @@ trim_left_proc :: proc(s: string, p: proc(rune) -> bool) -> string {
/*
Trims the input string `s` from the left until the procedure `p` with state returns false
-**Inputs**
+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
+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)
@@ -2207,16 +2324,19 @@ trim_left_proc_with_state :: proc(s: string, p: proc(rawptr, rune) -> bool, stat
/*
Trims the input string `s` from the right until the procedure `p` returns `false`
-**Inputs**
+Inputs:
- s: The input string
- p: A procedure that takes a rune and returns a boolean
+Returns:
+The trimmed string as a slice of the original
+
Example:
import "core:fmt"
import "core:strings"
- strings_trim_right_proc_example :: proc() {
+ trim_right_proc_example :: proc() {
find :: proc(r: rune) -> bool {
return r != 't'
}
@@ -2227,7 +2347,6 @@ 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)
@@ -2242,12 +2361,13 @@ trim_right_proc :: proc(s: string, p: proc(rune) -> bool) -> string {
/*
Trims the input string `s` from the right until the procedure `p` with state returns `false`
-**Inputs**
+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
+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)
@@ -2275,11 +2395,12 @@ is_in_cutset :: proc(state: rawptr, r: rune) -> bool {
/*
Trims the cutset string from the `s` string
-**Inputs**
+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
+Returns:
+The trimmed string as a slice of the original
*/
trim_left :: proc(s: string, cutset: string) -> string {
if s == "" || cutset == "" {
@@ -2291,11 +2412,12 @@ trim_left :: proc(s: string, cutset: string) -> string {
/*
Trims the cutset string from the `s` string from the right
-**Inputs**
+Inputs:
- s: The input string
- cutset: The set of characters to be trimmed from the right of the input string
-**Returns** The trimmed string as a slice of the original
+Returns:
+The trimmed string as a slice of the original
*/
trim_right :: proc(s: string, cutset: string) -> string {
if s == "" || cutset == "" {
@@ -2307,11 +2429,12 @@ trim_right :: proc(s: string, cutset: string) -> string {
/*
Trims the cutset string from the `s` string, both from left and right
-**Inputs**
+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
+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)
@@ -2319,10 +2442,11 @@ trim :: proc(s: string, cutset: string) -> string {
/*
Trims until a valid non-space rune from the left, "\t\txyz\t\t" -> "xyz\t\t"
-**Inputs**
+Inputs:
- s: The input string
-**Returns** The trimmed string as a slice of the original
+Returns:
+The trimmed string as a slice of the original
*/
trim_left_space :: proc(s: string) -> string {
return trim_left_proc(s, is_space)
@@ -2330,10 +2454,11 @@ trim_left_space :: proc(s: string) -> string {
/*
Trims from the right until a valid non-space rune, "\t\txyz\t\t" -> "\t\txyz"
-**Inputs**
+Inputs:
- s: The input string
-**Returns** The trimmed string as a slice of the original
+Returns:
+The trimmed string as a slice of the original
*/
trim_right_space :: proc(s: string) -> string {
return trim_right_proc(s, is_space)
@@ -2341,10 +2466,11 @@ trim_right_space :: proc(s: string) -> string {
/*
Trims from both sides until a valid non-space rune, "\t\txyz\t\t" -> "xyz"
-**Inputs**
+Inputs:
- s: The input string
-**Returns** The trimmed string as a slice of the original
+Returns:
+The trimmed string as a slice of the original
*/
trim_space :: proc(s: string) -> string {
return trim_right_space(trim_left_space(s))
@@ -2352,10 +2478,11 @@ trim_space :: proc(s: string) -> string {
/*
Trims null runes from the left, "\x00\x00testing\x00\x00" -> "testing\x00\x00"
-**Inputs**
+Inputs:
- s: The input string
-**Returns** The trimmed string as a slice of the original
+Returns:
+The trimmed string as a slice of the original
*/
trim_left_null :: proc(s: string) -> string {
return trim_left_proc(s, is_null)
@@ -2363,10 +2490,11 @@ trim_left_null :: proc(s: string) -> string {
/*
Trims null runes from the right, "\x00\x00testing\x00\x00" -> "\x00\x00testing"
-**Inputs**
+Inputs:
- s: The input string
-**Returns** The trimmed string as a slice of the original
+Returns:
+The trimmed string as a slice of the original
*/
trim_right_null :: proc(s: string) -> string {
return trim_right_proc(s, is_null)
@@ -2374,9 +2502,10 @@ trim_right_null :: proc(s: string) -> string {
/*
Trims null runes from both sides, "\x00\x00testing\x00\x00" -> "testing"
-**Inputs**
+Inputs:
- s: The input string
-**Returns** The trimmed string as a slice of the original
+Returns:
+The trimmed string as a slice of the original
*/
trim_null :: proc(s: string) -> string {
return trim_right_null(trim_left_null(s))
@@ -2384,16 +2513,19 @@ trim_null :: proc(s: string) -> string {
/*
Trims a `prefix` string from the start of the `s` string and returns the trimmed string
-**Inputs**
+Inputs:
- s: The input string
- prefix: The prefix string to be removed
+Returns:
+The trimmed string as a slice of original, or the input string if no prefix was found
+
Example:
import "core:fmt"
import "core:strings"
- strings_trim_prefix_example :: proc() {
+ trim_prefix_example :: proc() {
fmt.println(strings.trim_prefix("testing", "test"))
fmt.println(strings.trim_prefix("testing", "abc"))
}
@@ -2403,7 +2535,6 @@ Output:
ing
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) {
@@ -2414,16 +2545,19 @@ trim_prefix :: proc(s, prefix: string) -> string {
/*
Trims a `suffix` string from the end of the `s` string and returns the trimmed string
-**Inputs**
+Inputs:
- s: The input string
- suffix: The suffix string to be removed
+Returns:
+The trimmed string as a slice of original, or the input string if no suffix was found
+
Example:
import "core:fmt"
import "core:strings"
- strings_trim_suffix_example :: proc() {
+ trim_suffix_example :: proc() {
fmt.println(strings.trim_suffix("todo.txt", ".txt"))
fmt.println(strings.trim_suffix("todo.doc", ".txt"))
}
@@ -2433,7 +2567,6 @@ 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) {
@@ -2446,11 +2579,14 @@ Splits the input string `s` by all possible `substrs` and returns an allocated a
*Allocates Using Provided Allocator*
-**Inputs**
+Inputs:
- s: The input string
- substrs: An array of substrings used for splitting
- allocator: (default is context.allocator)
+Returns:
+An array of strings, or nil on empty substring or no matches
+
NOTE: Allocation occurs for the array, the splits are all views of the original string.
Example:
@@ -2458,7 +2594,7 @@ Example:
import "core:fmt"
import "core:strings"
- strings_split_multi_example :: proc() {
+ 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]
@@ -2468,7 +2604,6 @@ 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 {
@@ -2513,16 +2648,19 @@ split_multi :: proc(s: string, substrs: []string, allocator := context.allocator
/*
Splits the input string `s` by all possible `substrs` in an iterator fashion. The full string is returned if no match.
-**Inputs**
+Inputs:
- it: A pointer to the input string
- substrs: An array of substrings used for splitting
+Returns:
+A tuple containing the split string and a boolean indicating success or failure
+
Example:
import "core:fmt"
import "core:strings"
- strings_split_multi_iterate_example :: proc() {
+ split_multi_iterate_example :: proc() {
it := "testing,this.out_nice---done~~~last"
splits := [?]string { "---", "~~~", ".", "_", "," }
for str in strings.split_multi_iterate(&it, splits[:]) {
@@ -2539,7 +2677,6 @@ Output:
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 {
@@ -2571,17 +2708,20 @@ Replaces invalid UTF-8 characters in the input string with a specified replaceme
*Allocates Using Provided Allocator*
-**Inputs**
+Inputs:
- s: The input string
- replacement: The string used to replace invalid UTF-8 characters
- allocator: (default is context.allocator)
+Returns:
+A new string with invalid UTF-8 characters replaced
+
Example:
import "core:fmt"
import "core:strings"
- strings_scrub_example :: proc() {
+ scrub_example :: proc() {
text := "Hello\xC0\x80World"
fmt.println(strings.scrub(text, "?")) // -> "Hello?World"
}
@@ -2590,7 +2730,6 @@ Output:
Hello?
-**Returns** A new string with invalid UTF-8 characters replaced
*/
scrub :: proc(s: string, replacement: string, allocator := context.allocator) -> string {
str := s
@@ -2628,16 +2767,19 @@ Reverses the input string `s`
*Allocates Using Provided Allocator*
-**Inputs**
+Inputs:
- s: The input string
- allocator: (default is context.allocator)
+Returns:
+A reversed version of the input string
+
Example:
import "core:fmt"
import "core:strings"
- strings_reverse_example :: proc() {
+ reverse_example :: proc() {
a := "abcxyz"
b := strings.reverse(a)
fmt.println(a, b)
@@ -2647,7 +2789,6 @@ Output:
abcxyz zyxcba
-**Returns** A reversed version of the input string
*/
reverse :: proc(s: string, allocator := context.allocator) -> string {
str := s
@@ -2668,17 +2809,22 @@ Expands the input string by replacing tab characters with spaces to align to a s
*Allocates Using Provided Allocator*
-**Inputs**
+Inputs:
- s: The input string
- tab_size: The number of spaces to use for each tab character
- allocator: (default is context.allocator)
+Returns:
+A new string with tab characters expanded to the specified tab size
+
+WARNING: Panics if tab_size <= 0
+
Example:
import "core:fmt"
import "core:strings"
- strings_expand_tabs_example :: proc() {
+ expand_tabs_example :: proc() {
text := "abc1\tabc2\tabc3"
fmt.println(strings.expand_tabs(text, 4))
}
@@ -2687,9 +2833,6 @@ 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 {
@@ -2735,20 +2878,28 @@ expand_tabs :: proc(s: string, tab_size: int, allocator := context.allocator) ->
/*
Splits the input string `str` by the separator `sep` string and returns 3 parts. The values are slices of the original string.
-**Inputs**
+Inputs:
- str: The input string
- sep: The separator string
+Returns:
+A tuple with `head` (before the split), `match` (the separator), and `tail` (the end of the split) strings
+
Example:
import "core:fmt"
import "core:strings"
- strings_partition_example :: proc() {
+ 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: ""
+ head, match, tail := strings.partition(text, " this ") // -> head: "testing", match: " this ", tail: "out"
+ fmt.println(head, match, tail)
+ head, match, tail = strings.partition(text, "hi") // -> head: "testing t", match: "hi", tail: "s out"
+ fmt.println(head, match, tail)
+ head, match, tail = strings.partition(text, "xyz") // -> head: "testing this out", match: "", tail: ""
+ fmt.println(head)
+ fmt.println(match == "")
+ fmt.println(tail == "")
}
Output:
@@ -2756,8 +2907,9 @@ Output:
testing this out
testing t hi s out
testing this out
+ true
+ true
-**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)
@@ -2778,13 +2930,14 @@ Centers the input string within a field of specified length by adding pad string
*Allocates Using Provided Allocator*
-**Inputs**
+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
+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)
@@ -2812,13 +2965,14 @@ Left-justifies the input string within a field of specified length by adding pad
*Allocates Using Provided Allocator*
-**Inputs**
+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
+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)
@@ -2845,13 +2999,14 @@ Right-justifies the input string within a field of specified length by adding pa
*Allocates Using Provided Allocator*
-**Inputs**
+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)
-**Returns** A new string right-justified within a field of the specified 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)
@@ -2876,7 +3031,7 @@ right_justify :: proc(str: string, length: int, pad: string, allocator := contex
/*
Writes a given pad string a specified number of times to an `io.Writer`
-**Inputs**
+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
@@ -2904,11 +3059,12 @@ Splits a string into a slice of substrings at each instance of one or more conse
*Allocates Using Provided Allocator*
-**Inputs**
+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
+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
@@ -2963,14 +3119,15 @@ Splits a string into a slice of substrings at each run of unicode code points `r
*Allocates Using Provided Allocator*
-**Inputs**
+Inputs:
- s: The input string
- f: A predicate function to determine the split points
- allocator: (default is context.allocator)
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
+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)
@@ -3001,10 +3158,10 @@ fields_proc :: proc(s: string, f: proc(rune) -> bool, allocator := context.alloc
/*
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**
+Inputs:
- s: A mutable string reference to be iterated
-**Returns**
+Returns:
- field: The first non-space substring found
- ok: A boolean indicating if a non-space substring was found
*/
@@ -3043,11 +3200,12 @@ Computes the Levenshtein edit distance between two strings
NOTE: Does not perform internal allocation if length of string `b`, in runes, is smaller than 64
-**Inputs**
+Inputs:
- a, b: The two strings to compare
- allocator: (default is context.allocator)
-**Returns** The Levenshtein edit distance between the two strings
+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.
*/
diff --git a/core/sys/windows/types.odin b/core/sys/windows/types.odin
index e6d641c89..cd8bb4060 100644
--- a/core/sys/windows/types.odin
+++ b/core/sys/windows/types.odin
@@ -145,8 +145,6 @@ PCONDITION_VARIABLE :: ^CONDITION_VARIABLE
PLARGE_INTEGER :: ^LARGE_INTEGER
PSRWLOCK :: ^SRWLOCK
-MMRESULT :: UINT
-
CREATE_WAITABLE_TIMER_MANUAL_RESET :: 0x00000001
CREATE_WAITABLE_TIMER_HIGH_RESOLUTION :: 0x00000002
@@ -261,26 +259,6 @@ GET_FILEEX_INFO_LEVELS :: distinct i32
GetFileExInfoStandard: GET_FILEEX_INFO_LEVELS : 0
GetFileExMaxInfoLevel: GET_FILEEX_INFO_LEVELS : 1
-// String resource number bases (internal use)
-
-MMSYSERR_BASE :: 0
-WAVERR_BASE :: 32
-MIDIERR_BASE :: 64
-TIMERR_BASE :: 96
-JOYERR_BASE :: 160
-MCIERR_BASE :: 256
-MIXERR_BASE :: 1024
-
-MCI_STRING_OFFSET :: 512
-MCI_VD_OFFSET :: 1024
-MCI_CD_OFFSET :: 1088
-MCI_WAVE_OFFSET :: 1152
-MCI_SEQ_OFFSET :: 1216
-
-// timer error return values
-TIMERR_NOERROR :: 0 // no error
-TIMERR_NOCANDO :: TIMERR_BASE + 1 // request not completed
-TIMERR_STRUCT :: TIMERR_BASE + 33 // time struct size
DIAGNOSTIC_REASON_VERSION :: 0
diff --git a/core/sys/windows/winmm.odin b/core/sys/windows/winmm.odin
index 64ace19fc..445470f6e 100644
--- a/core/sys/windows/winmm.odin
+++ b/core/sys/windows/winmm.odin
@@ -3,9 +3,170 @@ package sys_windows
foreign import winmm "system:Winmm.lib"
+MMRESULT :: UINT
+
@(default_calling_convention="stdcall")
foreign winmm {
+ timeGetDevCaps :: proc(ptc: LPTIMECAPS, cbtc: UINT) -> MMRESULT ---
timeBeginPeriod :: proc(uPeriod: UINT) -> MMRESULT ---
timeEndPeriod :: proc(uPeriod: UINT) -> MMRESULT ---
timeGetTime :: proc() -> DWORD ---
}
+
+LPTIMECAPS :: ^TIMECAPS
+TIMECAPS :: struct {
+ wPeriodMin: UINT,
+ wPeriodMax: UINT,
+}
+
+// String resource number bases (internal use)
+MMSYSERR_BASE :: 0
+WAVERR_BASE :: 32
+MIDIERR_BASE :: 64
+TIMERR_BASE :: 96
+JOYERR_BASE :: 160
+MCIERR_BASE :: 256
+MIXERR_BASE :: 1024
+
+MCI_STRING_OFFSET :: 512
+MCI_VD_OFFSET :: 1024
+MCI_CD_OFFSET :: 1088
+MCI_WAVE_OFFSET :: 1152
+MCI_SEQ_OFFSET :: 1216
+
+/* general error return values */
+MMSYSERR_NOERROR :: 0 /* no error */
+MMSYSERR_ERROR :: MMSYSERR_BASE + 1 /* unspecified error */
+MMSYSERR_BADDEVICEID :: MMSYSERR_BASE + 2 /* device ID out of range */
+MMSYSERR_NOTENABLED :: MMSYSERR_BASE + 3 /* driver failed enable */
+MMSYSERR_ALLOCATED :: MMSYSERR_BASE + 4 /* device already allocated */
+MMSYSERR_INVALHANDLE :: MMSYSERR_BASE + 5 /* device handle is invalid */
+MMSYSERR_NODRIVER :: MMSYSERR_BASE + 6 /* no device driver present */
+MMSYSERR_NOMEM :: MMSYSERR_BASE + 7 /* memory allocation error */
+MMSYSERR_NOTSUPPORTED :: MMSYSERR_BASE + 8 /* function isn't supported */
+MMSYSERR_BADERRNUM :: MMSYSERR_BASE + 9 /* error value out of range */
+MMSYSERR_INVALFLAG :: MMSYSERR_BASE + 10 /* invalid flag passed */
+MMSYSERR_INVALPARAM :: MMSYSERR_BASE + 11 /* invalid parameter passed */
+MMSYSERR_HANDLEBUSY :: MMSYSERR_BASE + 12 /* handle being used simultaneously on another thread (eg callback) */
+MMSYSERR_INVALIDALIAS :: MMSYSERR_BASE + 13 /* specified alias not found */
+MMSYSERR_BADDB :: MMSYSERR_BASE + 14 /* bad registry database */
+MMSYSERR_KEYNOTFOUND :: MMSYSERR_BASE + 15 /* registry key not found */
+MMSYSERR_READERROR :: MMSYSERR_BASE + 16 /* registry read error */
+MMSYSERR_WRITEERROR :: MMSYSERR_BASE + 17 /* registry write error */
+MMSYSERR_DELETEERROR :: MMSYSERR_BASE + 18 /* registry delete error */
+MMSYSERR_VALNOTFOUND :: MMSYSERR_BASE + 19 /* registry value not found */
+MMSYSERR_NODRIVERCB :: MMSYSERR_BASE + 20 /* driver does not call DriverCallback */
+MMSYSERR_MOREDATA :: MMSYSERR_BASE + 21 /* more data to be returned */
+MMSYSERR_LASTERROR :: MMSYSERR_BASE + 21 /* last error in range */
+
+/* waveform audio error return values */
+WAVERR_BADFORMAT :: WAVERR_BASE + 0 /* unsupported wave format */
+WAVERR_STILLPLAYING :: WAVERR_BASE + 1 /* still something playing */
+WAVERR_UNPREPARED :: WAVERR_BASE + 2 /* header not prepared */
+WAVERR_SYNC :: WAVERR_BASE + 3 /* device is synchronous */
+WAVERR_LASTERROR :: WAVERR_BASE + 3 /* last error in range */
+
+/* MIDI error return values */
+MIDIERR_UNPREPARED :: MIDIERR_BASE + 0 /* header not prepared */
+MIDIERR_STILLPLAYING :: MIDIERR_BASE + 1 /* still something playing */
+MIDIERR_NOMAP :: MIDIERR_BASE + 2 /* no configured instruments */
+MIDIERR_NOTREADY :: MIDIERR_BASE + 3 /* hardware is still busy */
+MIDIERR_NODEVICE :: MIDIERR_BASE + 4 /* port no longer connected */
+MIDIERR_INVALIDSETUP :: MIDIERR_BASE + 5 /* invalid MIF */
+MIDIERR_BADOPENMODE :: MIDIERR_BASE + 6 /* operation unsupported w/ open mode */
+MIDIERR_DONT_CONTINUE :: MIDIERR_BASE + 7 /* thru device 'eating' a message */
+MIDIERR_LASTERROR :: MIDIERR_BASE + 7 /* last error in range */
+
+/* timer error return values */
+TIMERR_NOERROR :: 0 /* no error */
+TIMERR_NOCANDO :: TIMERR_BASE + 1 /* request not completed */
+TIMERR_STRUCT :: TIMERR_BASE + 33 /* time struct size */
+
+/* joystick error return values */
+JOYERR_NOERROR :: 0 /* no error */
+JOYERR_PARMS :: JOYERR_BASE + 5 /* bad parameters */
+JOYERR_NOCANDO :: JOYERR_BASE + 6 /* request not completed */
+JOYERR_UNPLUGGED :: JOYERR_BASE + 7 /* joystick is unplugged */
+
+/* MCI error return values */
+MCIERR_INVALID_DEVICE_ID :: MCIERR_BASE + 1
+MCIERR_UNRECOGNIZED_KEYWORD :: MCIERR_BASE + 3
+MCIERR_UNRECOGNIZED_COMMAND :: MCIERR_BASE + 5
+MCIERR_HARDWARE :: MCIERR_BASE + 6
+MCIERR_INVALID_DEVICE_NAME :: MCIERR_BASE + 7
+MCIERR_OUT_OF_MEMORY :: MCIERR_BASE + 8
+MCIERR_DEVICE_OPEN :: MCIERR_BASE + 9
+MCIERR_CANNOT_LOAD_DRIVER :: MCIERR_BASE + 10
+MCIERR_MISSING_COMMAND_STRING :: MCIERR_BASE + 11
+MCIERR_PARAM_OVERFLOW :: MCIERR_BASE + 12
+MCIERR_MISSING_STRING_ARGUMENT :: MCIERR_BASE + 13
+MCIERR_BAD_INTEGER :: MCIERR_BASE + 14
+MCIERR_PARSER_INTERNAL :: MCIERR_BASE + 15
+MCIERR_DRIVER_INTERNAL :: MCIERR_BASE + 16
+MCIERR_MISSING_PARAMETER :: MCIERR_BASE + 17
+MCIERR_UNSUPPORTED_FUNCTION :: MCIERR_BASE + 18
+MCIERR_FILE_NOT_FOUND :: MCIERR_BASE + 19
+MCIERR_DEVICE_NOT_READY :: MCIERR_BASE + 20
+MCIERR_INTERNAL :: MCIERR_BASE + 21
+MCIERR_DRIVER :: MCIERR_BASE + 22
+MCIERR_CANNOT_USE_ALL :: MCIERR_BASE + 23
+MCIERR_MULTIPLE :: MCIERR_BASE + 24
+MCIERR_EXTENSION_NOT_FOUND :: MCIERR_BASE + 25
+MCIERR_OUTOFRANGE :: MCIERR_BASE + 26
+MCIERR_FLAGS_NOT_COMPATIBLE :: MCIERR_BASE + 28
+MCIERR_FILE_NOT_SAVED :: MCIERR_BASE + 30
+MCIERR_DEVICE_TYPE_REQUIRED :: MCIERR_BASE + 31
+MCIERR_DEVICE_LOCKED :: MCIERR_BASE + 32
+MCIERR_DUPLICATE_ALIAS :: MCIERR_BASE + 33
+MCIERR_BAD_CONSTANT :: MCIERR_BASE + 34
+MCIERR_MUST_USE_SHAREABLE :: MCIERR_BASE + 35
+MCIERR_MISSING_DEVICE_NAME :: MCIERR_BASE + 36
+MCIERR_BAD_TIME_FORMAT :: MCIERR_BASE + 37
+MCIERR_NO_CLOSING_QUOTE :: MCIERR_BASE + 38
+MCIERR_DUPLICATE_FLAGS :: MCIERR_BASE + 39
+MCIERR_INVALID_FILE :: MCIERR_BASE + 40
+MCIERR_NULL_PARAMETER_BLOCK :: MCIERR_BASE + 41
+MCIERR_UNNAMED_RESOURCE :: MCIERR_BASE + 42
+MCIERR_NEW_REQUIRES_ALIAS :: MCIERR_BASE + 43
+MCIERR_NOTIFY_ON_AUTO_OPEN :: MCIERR_BASE + 44
+MCIERR_NO_ELEMENT_ALLOWED :: MCIERR_BASE + 45
+MCIERR_NONAPPLICABLE_FUNCTION :: MCIERR_BASE + 46
+MCIERR_ILLEGAL_FOR_AUTO_OPEN :: MCIERR_BASE + 47
+MCIERR_FILENAME_REQUIRED :: MCIERR_BASE + 48
+MCIERR_EXTRA_CHARACTERS :: MCIERR_BASE + 49
+MCIERR_DEVICE_NOT_INSTALLED :: MCIERR_BASE + 50
+MCIERR_GET_CD :: MCIERR_BASE + 51
+MCIERR_SET_CD :: MCIERR_BASE + 52
+MCIERR_SET_DRIVE :: MCIERR_BASE + 53
+MCIERR_DEVICE_LENGTH :: MCIERR_BASE + 54
+MCIERR_DEVICE_ORD_LENGTH :: MCIERR_BASE + 55
+MCIERR_NO_INTEGER :: MCIERR_BASE + 56
+MCIERR_WAVE_OUTPUTSINUSE :: MCIERR_BASE + 64
+MCIERR_WAVE_SETOUTPUTINUSE :: MCIERR_BASE + 65
+MCIERR_WAVE_INPUTSINUSE :: MCIERR_BASE + 66
+MCIERR_WAVE_SETINPUTINUSE :: MCIERR_BASE + 67
+MCIERR_WAVE_OUTPUTUNSPECIFIED :: MCIERR_BASE + 68
+MCIERR_WAVE_INPUTUNSPECIFIED :: MCIERR_BASE + 69
+MCIERR_WAVE_OUTPUTSUNSUITABLE :: MCIERR_BASE + 70
+MCIERR_WAVE_SETOUTPUTUNSUITABLE :: MCIERR_BASE + 71
+MCIERR_WAVE_INPUTSUNSUITABLE :: MCIERR_BASE + 72
+MCIERR_WAVE_SETINPUTUNSUITABLE :: MCIERR_BASE + 73
+MCIERR_SEQ_DIV_INCOMPATIBLE :: MCIERR_BASE + 80
+MCIERR_SEQ_PORT_INUSE :: MCIERR_BASE + 81
+MCIERR_SEQ_PORT_NONEXISTENT :: MCIERR_BASE + 82
+MCIERR_SEQ_PORT_MAPNODEVICE :: MCIERR_BASE + 83
+MCIERR_SEQ_PORT_MISCERROR :: MCIERR_BASE + 84
+MCIERR_SEQ_TIMER :: MCIERR_BASE + 85
+MCIERR_SEQ_PORTUNSPECIFIED :: MCIERR_BASE + 86
+MCIERR_SEQ_NOMIDIPRESENT :: MCIERR_BASE + 87
+MCIERR_NO_WINDOW :: MCIERR_BASE + 90
+MCIERR_CREATEWINDOW :: MCIERR_BASE + 91
+MCIERR_FILE_READ :: MCIERR_BASE + 92
+MCIERR_FILE_WRITE :: MCIERR_BASE + 93
+MCIERR_NO_IDENTITY :: MCIERR_BASE + 94
+
+/* MMRESULT error return values specific to the mixer API */
+MIXERR_INVALLINE :: (MIXERR_BASE + 0)
+MIXERR_INVALCONTROL :: (MIXERR_BASE + 1)
+MIXERR_INVALVALUE :: (MIXERR_BASE + 2)
+MIXERR_LASTERROR :: (MIXERR_BASE + 2) \ No newline at end of file
diff --git a/core/text/table/doc.odin b/core/text/table/doc.odin
new file mode 100644
index 000000000..9b5c1f932
--- /dev/null
+++ b/core/text/table/doc.odin
@@ -0,0 +1,100 @@
+/*
+ package table implements ascii/markdown/html/custom rendering of tables.
+
+ ---
+
+ Custom rendering example:
+
+ ```odin
+ tbl := init(&Table{})
+ padding(tbl, 0, 1)
+ row(tbl, "A_LONG_ENUM", "= 54,", "// A comment about A_LONG_ENUM")
+ row(tbl, "AN_EVEN_LONGER_ENUM", "= 1,", "// A comment about AN_EVEN_LONGER_ENUM")
+ build(tbl)
+ for row in 0..<tbl.nr_rows {
+ for col in 0..<tbl.nr_cols {
+ write_table_cell(stdio_writer(), tbl, row, col)
+ }
+ io.write_byte(stdio_writer(), '\n')
+ }
+ ```
+
+ This outputs:
+ ```
+ A_LONG_ENUM = 54, // A comment about A_LONG_ENUM
+ AN_EVEN_LONGER_ENUM = 1, // A comment about AN_EVEN_LONGER_ENUM
+ ```
+
+ ---
+
+ ASCII rendering example:
+
+ ```odin
+ tbl := init(&Table{})
+ defer destroy(tbl)
+
+ caption(tbl, "This is a table caption and it is very long")
+
+ padding(tbl, 1, 1) // Left/right padding of cells
+
+ header(tbl, "AAAAAAAAA", "B")
+ header(tbl, "C") // Appends to previous header row. Same as if done header("AAAAAAAAA", "B", "C") from start.
+
+ // Create a row with two values. Since there are three columns the third
+ // value will become the empty string.
+ //
+ // NOTE: header() is not allowed anymore after this.
+ row(tbl, 123, "foo")
+
+ // Use `format()` if you need custom formatting. This will allocate into
+ // the arena specified at init.
+ row(tbl,
+ format(tbl, "%09d", 5),
+ format(tbl, "%.6f", 6.28318530717958647692528676655900576))
+
+ // A row with zero values is allowed as long as a previous row or header
+ // exist. The value and alignment of each cell can then be set
+ // individually.
+ row(tbl)
+ set_cell_value_and_alignment(tbl, last_row(tbl), 0, "a", .Center)
+ set_cell_value(tbl, last_row(tbl), 1, "bbb")
+ set_cell_value(tbl, last_row(tbl), 2, "c")
+
+ // Headers are regular cells, too. Use header_row() as row index to modify
+ // header cells.
+ set_cell_alignment(tbl, header_row(tbl), 1, .Center) // Sets alignment of 'B' column to Center.
+ set_cell_alignment(tbl, header_row(tbl), 2, .Right) // Sets alignment of 'C' column to Right.
+
+ build(tbl)
+
+ write_ascii_table(stdio_writer(), tbl)
+ write_markdown_table(stdio_writer(), tbl)
+ ```
+
+ This outputs:
+ ```
+ +-----------------------------------------------+
+ | This is a table caption and it is very long |
+ +------------------+-----------------+----------+
+ | AAAAAAAAA | B | C |
+ +------------------+-----------------+----------+
+ | 123 | foo | |
+ | 000000005 | 6.283185 | |
+ | a | bbb | c |
+ +------------------+-----------------+----------+
+ ```
+
+ and
+
+ ```
+ | AAAAAAAAA | B | C |
+ |:-----------------|:---------------:|---------:|
+ | 123 | foo | |
+ | 000000005 | 6.283185 | |
+ | a | bbb | c |
+ ```
+
+ respectively.
+*/
+
+package text_table
diff --git a/core/text/table/table.odin b/core/text/table/table.odin
new file mode 100644
index 000000000..df93ee44e
--- /dev/null
+++ b/core/text/table/table.odin
@@ -0,0 +1,384 @@
+/*
+ Copyright 2023 oskarnp <oskarnp@proton.me>
+ Made available under Odin's BSD-3 license.
+
+ List of contributors:
+ oskarnp: Initial implementation.
+*/
+
+package text_table
+
+import "core:io"
+import "core:os"
+import "core:fmt"
+import "core:mem"
+import "core:mem/virtual"
+import "core:runtime"
+import "core:strings"
+
+Cell :: struct {
+ text: string,
+ alignment: Cell_Alignment,
+}
+
+Cell_Alignment :: enum {
+ Left,
+ Center,
+ Right,
+}
+
+Table :: struct {
+ lpad, rpad: int, // Cell padding (left/right)
+ cells: [dynamic]Cell,
+ caption: string,
+ nr_rows, nr_cols: int,
+ has_header_row: bool,
+ table_allocator: runtime.Allocator, // Used for allocating cells/colw
+ format_allocator: runtime.Allocator, // Used for allocating Cell.text when applicable
+
+ dirty: bool, // True if build() needs to be called before rendering
+
+ // The following are computed on build()
+ colw: [dynamic]int, // Width of each column (including padding, excluding borders)
+ tblw: int, // Width of entire table (including padding, excluding borders)
+}
+
+init :: proc{init_with_allocator, init_with_virtual_arena, init_with_mem_arena}
+
+init_with_allocator :: proc(tbl: ^Table, format_allocator := context.temp_allocator, table_allocator := context.allocator) -> ^Table {
+ tbl.table_allocator = table_allocator
+ tbl.cells = make([dynamic]Cell, tbl.table_allocator)
+ tbl.colw = make([dynamic]int, tbl.table_allocator)
+ tbl.format_allocator = format_allocator
+ return tbl
+}
+init_with_virtual_arena :: proc(tbl: ^Table, format_arena: ^virtual.Arena, table_allocator := context.allocator) -> ^Table {
+ return init_with_allocator(tbl, virtual.arena_allocator(format_arena), table_allocator)
+}
+init_with_mem_arena :: proc(tbl: ^Table, format_arena: ^mem.Arena, table_allocator := context.allocator) -> ^Table {
+ return init_with_allocator(tbl, mem.arena_allocator(format_arena), table_allocator)
+}
+
+destroy :: proc(tbl: ^Table) {
+ free_all(tbl.format_allocator)
+ delete(tbl.cells)
+ delete(tbl.colw)
+}
+
+caption :: proc(tbl: ^Table, value: string) {
+ tbl.caption = value
+ tbl.dirty = true
+}
+
+padding :: proc(tbl: ^Table, lpad, rpad: int) {
+ tbl.lpad = lpad
+ tbl.rpad = rpad
+ tbl.dirty = true
+}
+
+get_cell :: proc(tbl: ^Table, row, col: int, loc := #caller_location) -> ^Cell {
+ assert(col >= 0 && col < tbl.nr_cols, "cell column out of range", loc)
+ assert(row >= 0 && row < tbl.nr_rows, "cell row out of range", loc)
+ resize(&tbl.cells, tbl.nr_cols * tbl.nr_rows)
+ return &tbl.cells[row*tbl.nr_cols + col]
+}
+
+set_cell_value_and_alignment :: proc(tbl: ^Table, row, col: int, value: string, alignment: Cell_Alignment) {
+ cell := get_cell(tbl, row, col)
+ cell.text = format(tbl, "%v", value)
+ cell.alignment = alignment
+ tbl.dirty = true
+}
+
+set_cell_value :: proc(tbl: ^Table, row, col: int, value: any, loc := #caller_location) {
+ cell := get_cell(tbl, row, col, loc)
+ switch val in value {
+ case nil:
+ cell.text = ""
+ case string:
+ cell.text = string(val)
+ case cstring:
+ cell.text = string(val)
+ case:
+ cell.text = format(tbl, "%v", val)
+ if cell.text == "" {
+ fmt.eprintf("{} text/table: format() resulted in empty string (arena out of memory?)\n", loc)
+ }
+ }
+ tbl.dirty = true
+}
+
+set_cell_alignment :: proc(tbl: ^Table, row, col: int, alignment: Cell_Alignment, loc := #caller_location) {
+ cell := get_cell(tbl, row, col, loc)
+ cell.alignment = alignment
+ tbl.dirty = true
+}
+
+format :: proc(tbl: ^Table, _fmt: string, args: ..any, loc := #caller_location) -> string {
+ context.allocator = tbl.format_allocator
+ return fmt.aprintf(fmt = _fmt, args = args)
+}
+
+header :: proc(tbl: ^Table, values: ..any, loc := #caller_location) {
+ if (tbl.has_header_row && tbl.nr_rows != 1) || (!tbl.has_header_row && tbl.nr_rows != 0) {
+ panic("Cannot add headers after rows have been added", loc)
+ }
+
+ if tbl.nr_rows == 0 {
+ tbl.nr_rows += 1
+ tbl.has_header_row = true
+ }
+
+ col := tbl.nr_cols
+ tbl.nr_cols += len(values)
+ for val in values {
+ set_cell_value(tbl, header_row(tbl), col, val, loc)
+ col += 1
+ }
+
+ tbl.dirty = true
+}
+
+row :: proc(tbl: ^Table, values: ..any, loc := #caller_location) {
+ if tbl.nr_cols == 0 {
+ if len(values) == 0 {
+ panic("Cannot create row without values unless knowing amount of columns in advance")
+ } else {
+ tbl.nr_cols = len(values)
+ }
+ }
+ tbl.nr_rows += 1
+ for col in 0..<tbl.nr_cols {
+ val := values[col] if col < len(values) else nil
+ set_cell_value(tbl, last_row(tbl), col, val)
+ }
+ tbl.dirty = true
+}
+
+last_row :: proc(tbl: ^Table) -> int {
+ return tbl.nr_rows - 1
+}
+
+header_row :: proc(tbl: ^Table) -> int {
+ return 0 if tbl.has_header_row else -1
+}
+
+first_row :: proc(tbl: ^Table) -> int {
+ return header_row(tbl)+1 if tbl.has_header_row else 0
+}
+
+build :: proc(tbl: ^Table) {
+ tbl.dirty = false
+
+ resize(&tbl.colw, tbl.nr_cols)
+ mem.zero_slice(tbl.colw[:])
+
+ for row in 0..<tbl.nr_rows {
+ for col in 0..<tbl.nr_cols {
+ cell := get_cell(tbl, row, col)
+ if w := len(cell.text) + tbl.lpad + tbl.rpad; w > tbl.colw[col] {
+ tbl.colw[col] = w
+ }
+ }
+ }
+
+ colw_sum := 0
+ for v in tbl.colw {
+ colw_sum += v
+ }
+
+ tbl.tblw = max(colw_sum, len(tbl.caption) + tbl.lpad + tbl.rpad)
+
+ // Resize columns to match total width of table
+ remain := tbl.tblw-colw_sum
+ for col := 0; remain > 0; col = (col + 1) % tbl.nr_cols {
+ tbl.colw[col] += 1
+ remain -= 1
+ }
+
+ return
+}
+
+write_html_table :: proc(w: io.Writer, tbl: ^Table) {
+ if tbl.dirty {
+ build(tbl)
+ }
+
+ io.write_string(w, "<table>\n")
+ if tbl.caption != "" {
+ io.write_string(w, "<caption>")
+ io.write_string(w, tbl.caption)
+ io.write_string(w, "</caption>\n")
+ }
+
+ align_attribute :: proc(cell: ^Cell) -> string {
+ switch cell.alignment {
+ case .Left: return ` align="left"`
+ case .Center: return ` align="center"`
+ case .Right: return ` align="right"`
+ }
+ unreachable()
+ }
+
+ if tbl.has_header_row {
+ io.write_string(w, "<thead>\n")
+ io.write_string(w, " <tr>\n")
+ for col in 0..<tbl.nr_cols {
+ cell := get_cell(tbl, header_row(tbl), col)
+ io.write_string(w, " <th")
+ io.write_string(w, align_attribute(cell))
+ io.write_string(w, ">")
+ io.write_string(w, cell.text)
+ io.write_string(w, "</th>\n")
+ }
+ io.write_string(w, " </tr>\n")
+ io.write_string(w, "</thead>\n")
+ }
+
+ io.write_string(w, "<tbody>\n")
+ for row in 0..<tbl.nr_rows {
+ if tbl.has_header_row && row == header_row(tbl) {
+ continue
+ }
+ io.write_string(w, " <tr>\n")
+ for col in 0..<tbl.nr_cols {
+ cell := get_cell(tbl, row, col)
+ io.write_string(w, " <td")
+ io.write_string(w, align_attribute(cell))
+ io.write_string(w, ">")
+ io.write_string(w, cell.text)
+ io.write_string(w, "</td>\n")
+ }
+ io.write_string(w, " </tr>\n")
+ }
+ io.write_string(w, " </tbody>\n")
+
+ io.write_string(w, "</table>\n")
+}
+
+write_ascii_table :: proc(w: io.Writer, tbl: ^Table) {
+ if tbl.dirty {
+ build(tbl)
+ }
+
+ write_caption_separator :: proc(w: io.Writer, tbl: ^Table) {
+ io.write_byte(w, '+')
+ write_byte_repeat(w, tbl.tblw + tbl.nr_cols - 1, '-')
+ io.write_byte(w, '+')
+ io.write_byte(w, '\n')
+ }
+
+ write_table_separator :: proc(w: io.Writer, tbl: ^Table) {
+ for col in 0..<tbl.nr_cols {
+ if col == 0 {
+ io.write_byte(w, '+')
+ }
+ write_byte_repeat(w, tbl.colw[col], '-')
+ io.write_byte(w, '+')
+ }
+ io.write_byte(w, '\n')
+ }
+
+ if tbl.caption != "" {
+ write_caption_separator(w, tbl)
+ io.write_byte(w, '|')
+ write_text_align(w, tbl.tblw - tbl.lpad - tbl.rpad + tbl.nr_cols - 1,
+ tbl.lpad, tbl.rpad, tbl.caption, .Center)
+ io.write_byte(w, '|')
+ io.write_byte(w, '\n')
+ }
+
+ write_table_separator(w, tbl)
+ for row in 0..<tbl.nr_rows {
+ for col in 0..<tbl.nr_cols {
+ if col == 0 {
+ io.write_byte(w, '|')
+ }
+ write_table_cell(w, tbl, row, col)
+ io.write_byte(w, '|')
+ }
+ io.write_byte(w, '\n')
+ if tbl.has_header_row && row == header_row(tbl) {
+ write_table_separator(w, tbl)
+ }
+ }
+ write_table_separator(w, tbl)
+}
+
+// Renders table according to GitHub Flavored Markdown (GFM) specification
+write_markdown_table :: proc(w: io.Writer, tbl: ^Table) {
+ // NOTE(oskar): Captions or colspans are not supported by GFM as far as I can tell.
+
+ if tbl.dirty {
+ build(tbl)
+ }
+
+ for row in 0..<tbl.nr_rows {
+ for col in 0..<tbl.nr_cols {
+ cell := get_cell(tbl, row, col)
+ if col == 0 {
+ io.write_byte(w, '|')
+ }
+ write_text_align(w, tbl.colw[col] - tbl.lpad - tbl.rpad, tbl.lpad, tbl.rpad, cell.text,
+ .Center if tbl.has_header_row && row == header_row(tbl) else .Left)
+ io.write_string(w, "|")
+ }
+ io.write_byte(w, '\n')
+
+ if tbl.has_header_row && row == header_row(tbl) {
+ for col in 0..<tbl.nr_cols {
+ cell := get_cell(tbl, row, col)
+ if col == 0 {
+ io.write_byte(w, '|')
+ }
+ switch cell.alignment {
+ case .Left:
+ io.write_byte(w, ':')
+ write_byte_repeat(w, max(1, tbl.colw[col]-1), '-')
+ case .Center:
+ io.write_byte(w, ':')
+ write_byte_repeat(w, max(1, tbl.colw[col]-2), '-')
+ io.write_byte(w, ':')
+ case .Right:
+ write_byte_repeat(w, max(1, tbl.colw[col]-1), '-')
+ io.write_byte(w, ':')
+ }
+ io.write_byte(w, '|')
+ }
+ io.write_byte(w, '\n')
+ }
+ }
+}
+
+write_byte_repeat :: proc(w: io.Writer, n: int, b: byte) {
+ for _ in 0..<n {
+ io.write_byte(w, b)
+ }
+}
+
+write_table_cell :: proc(w: io.Writer, tbl: ^Table, row, col: int) {
+ if tbl.dirty {
+ build(tbl)
+ }
+ cell := get_cell(tbl, row, col)
+ write_text_align(w, tbl.colw[col]-tbl.lpad-tbl.rpad, tbl.lpad, tbl.rpad, cell.text, cell.alignment)
+}
+
+write_text_align :: proc(w: io.Writer, colw, lpad, rpad: int, text: string, alignment: Cell_Alignment) {
+ write_byte_repeat(w, lpad, ' ')
+ switch alignment {
+ case .Left:
+ io.write_string(w, text)
+ write_byte_repeat(w, colw - len(text), ' ')
+ case .Center:
+ pad := colw - len(text)
+ odd := pad & 1 != 0
+ write_byte_repeat(w, pad/2, ' ')
+ io.write_string(w, text)
+ write_byte_repeat(w, pad/2 + 1 if odd else pad/2, ' ')
+ case .Right:
+ write_byte_repeat(w, colw - len(text), ' ')
+ io.write_string(w, text)
+ }
+ write_byte_repeat(w, rpad, ' ')
+}
diff --git a/core/text/table/utility.odin b/core/text/table/utility.odin
new file mode 100644
index 000000000..0e56fd968
--- /dev/null
+++ b/core/text/table/utility.odin
@@ -0,0 +1,13 @@
+package text_table
+
+import "core:io"
+import "core:os"
+import "core:strings"
+
+stdio_writer :: proc() -> io.Writer {
+ return io.to_writer(os.stream_from_handle(os.stdout))
+}
+
+strings_builder_writer :: proc(b: ^strings.Builder) -> io.Writer {
+ return strings.to_writer(b)
+}
diff --git a/src/main.cpp b/src/main.cpp
index bbb28cdf8..5ab6ed66c 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -594,13 +594,13 @@ gb_internal Array<String> setup_args(int argc, char const **argv) {
gb_internal void print_usage_line(i32 indent, char const *fmt, ...) {
while (indent --> 0) {
- gb_printf_err("\t");
+ gb_printf("\t");
}
va_list va;
va_start(va, fmt);
- gb_printf_err_va(fmt, va);
+ gb_printf_va(fmt, va);
va_end(va);
- gb_printf_err("\n");
+ gb_printf("\n");
}
gb_internal void usage(String argv0) {
diff --git a/src/parser.cpp b/src/parser.cpp
index 86f955254..50a9ba766 100644
--- a/src/parser.cpp
+++ b/src/parser.cpp
@@ -3693,9 +3693,11 @@ gb_internal bool allow_field_separator(AstFile *f) {
if (allow_token(f, Token_Comma)) {
return true;
}
- if (ALLOW_NEWLINE && token.kind == Token_Semicolon && !token_is_newline(token)) {
- String p = token_to_string(token);
- syntax_error(token_end_of_line(f, f->prev_token), "Expected a comma, got a %.*s", LIT(p));
+ if (ALLOW_NEWLINE && token.kind == Token_Semicolon) {
+ if (!token_is_newline(token)) {
+ String p = token_to_string(token);
+ syntax_error(token_end_of_line(f, f->prev_token), "Expected a comma, got a %.*s", LIT(p));
+ }
advance_token(f);
return true;
}
diff --git a/tests/core/build.bat b/tests/core/build.bat
index 5a86e5b41..1d146c8a4 100644
--- a/tests/core/build.bat
+++ b/tests/core/build.bat
@@ -6,87 +6,82 @@ python3 download_assets.py
echo ---
echo Running core:image tests
echo ---
-%PATH_TO_ODIN% run image %COMMON% -out:test_core_image.exe
+%PATH_TO_ODIN% run image %COMMON% -out:test_core_image.exe || exit /b
echo ---
echo Running core:compress tests
echo ---
-%PATH_TO_ODIN% run compress %COMMON% -out:test_core_compress.exe
+%PATH_TO_ODIN% run compress %COMMON% -out:test_core_compress.exe || exit /b
echo ---
echo Running core:strings tests
echo ---
-%PATH_TO_ODIN% run strings %COMMON% -out:test_core_strings.exe
+%PATH_TO_ODIN% run strings %COMMON% -out:test_core_strings.exe || exit /b
echo ---
echo Running core:hash tests
echo ---
-%PATH_TO_ODIN% run hash %COMMON% -o:size -out:test_core_hash.exe
+%PATH_TO_ODIN% run hash %COMMON% -o:size -out:test_core_hash.exe || exit /b
echo ---
echo Running core:odin tests
echo ---
-%PATH_TO_ODIN% run odin %COMMON% -o:size -out:test_core_odin.exe
+%PATH_TO_ODIN% run odin %COMMON% -o:size -out:test_core_odin.exe || exit /b
echo ---
echo Running core:crypto hash tests
echo ---
-%PATH_TO_ODIN% run crypto %COMMON% -out:test_crypto_hash.exe
+%PATH_TO_ODIN% run crypto %COMMON% -out:test_crypto_hash.exe || exit /b
echo ---
echo Running core:encoding tests
echo ---
-%PATH_TO_ODIN% run encoding/hxa %COMMON% %COLLECTION% -out:test_hxa.exe
-%PATH_TO_ODIN% run encoding/json %COMMON% -out:test_json.exe
-%PATH_TO_ODIN% run encoding/varint %COMMON% -out:test_varint.exe
-%PATH_TO_ODIN% run encoding/xml %COMMON% -out:test_xml.exe
+%PATH_TO_ODIN% run encoding/hxa %COMMON% %COLLECTION% -out:test_hxa.exe || exit /b
+%PATH_TO_ODIN% run encoding/json %COMMON% -out:test_json.exe || exit /b
+%PATH_TO_ODIN% run encoding/varint %COMMON% -out:test_varint.exe || exit /b
+%PATH_TO_ODIN% run encoding/xml %COMMON% -out:test_xml.exe || exit /b
echo ---
echo Running core:math/noise tests
echo ---
-%PATH_TO_ODIN% run math/noise %COMMON% -out:test_noise.exe
+%PATH_TO_ODIN% run math/noise %COMMON% -out:test_noise.exe || exit /b
echo ---
echo Running core:math tests
echo ---
-%PATH_TO_ODIN% run math %COMMON% %COLLECTION% -out:test_core_math.exe
+%PATH_TO_ODIN% run math %COMMON% %COLLECTION% -out:test_core_math.exe || exit /b
echo ---
echo Running core:math/linalg/glsl tests
echo ---
-%PATH_TO_ODIN% run math/linalg/glsl %COMMON% %COLLECTION% -out:test_linalg_glsl.exe
+%PATH_TO_ODIN% run math/linalg/glsl %COMMON% %COLLECTION% -out:test_linalg_glsl.exe || exit /b
echo ---
echo Running core:path/filepath tests
echo ---
-%PATH_TO_ODIN% run path/filepath %COMMON% %COLLECTION% -out:test_core_filepath.exe
+%PATH_TO_ODIN% run path/filepath %COMMON% %COLLECTION% -out:test_core_filepath.exe || exit /b
echo ---
echo Running core:reflect tests
echo ---
-%PATH_TO_ODIN% run reflect %COMMON% %COLLECTION% -out:test_core_reflect.exe
+%PATH_TO_ODIN% run reflect %COMMON% %COLLECTION% -out:test_core_reflect.exe || exit /b
echo ---
echo Running core:text/i18n tests
echo ---
-%PATH_TO_ODIN% run text\i18n %COMMON% -out:test_core_i18n.exe
+%PATH_TO_ODIN% run text\i18n %COMMON% -out:test_core_i18n.exe || exit /b
echo ---
echo Running core:net
echo ---
-%PATH_TO_ODIN% run net %COMMON% -out:test_core_net.exe
-
-echo ---
-echo Running core:text/lua tests
-echo ---
-%PATH_TO_ODIN% run text\lua %COMMON% -out:test_core_lua_strlib.exe
+%PATH_TO_ODIN% run net %COMMON% -out:test_core_net.exe || exit /b
echo ---
echo Running core:slice tests
echo ---
-%PATH_TO_ODIN% run slice %COMMON% -out:test_core_slice.exe
+%PATH_TO_ODIN% run slice %COMMON% -out:test_core_slice.exe || exit /b
echo ---
echo Running core:container tests
echo ---
-%PATH_TO_ODIN% run container %COMMON% %COLLECTION% -out:test_core_container.exe
+%PATH_TO_ODIN% run container %COMMON% %COLLECTION% -out:test_core_container.exe || exit /b
diff --git a/tests/core/compress/test_core_compress.odin b/tests/core/compress/test_core_compress.odin
index ee7233e52..ac7555e9a 100644
--- a/tests/core/compress/test_core_compress.odin
+++ b/tests/core/compress/test_core_compress.odin
@@ -151,6 +151,13 @@ shoco_test :: proc(t: ^testing.T) {
}
for v in Shoco_Tests {
+ when ODIN_OS == .Windows {
+ v := v
+ // Compressed source files are not encoded with carriage returns but git replaces raw files lf with crlf on commit (on windows only)
+ // So replace crlf with lf on windows
+ v.raw, _ = bytes.replace_all(v.raw, { 0xD, 0xA }, { 0xA })
+ }
+
expected_raw := len(v.raw)
expected_compressed := len(v.compressed)
diff --git a/tests/documentation/build.bat b/tests/documentation/build.bat
new file mode 100644
index 000000000..6ca90fbad
--- /dev/null
+++ b/tests/documentation/build.bat
@@ -0,0 +1,13 @@
+@echo off
+set PATH_TO_ODIN==..\..\odin
+
+echo ---
+echo Building Documentation File
+echo ---
+%PATH_TO_ODIN% doc ..\..\examples\all -all-packages -doc-format || exit /b
+
+
+echo ---
+echo Running Documentation Tester
+echo ---
+%PATH_TO_ODIN% run documentation_tester.odin -file -vet -strict-style -- %PATH_TO_ODIN% || exit /b
diff --git a/tests/documentation/documentation_tester.odin b/tests/documentation/documentation_tester.odin
new file mode 100644
index 000000000..09c565a51
--- /dev/null
+++ b/tests/documentation/documentation_tester.odin
@@ -0,0 +1,421 @@
+package documentation_tester
+
+import "core:os"
+import "core:io"
+import "core:fmt"
+import "core:strings"
+import "core:odin/ast"
+import "core:odin/parser"
+import "core:c/libc"
+import doc "core:odin/doc-format"
+
+Example_Test :: struct {
+ entity_name: string,
+ package_name: string,
+ example_code: []string,
+ expected_output: []string,
+}
+
+g_header: ^doc.Header
+g_bad_doc: bool
+g_examples_to_verify: [dynamic]Example_Test
+g_path_to_odin: string
+
+array :: proc(a: $A/doc.Array($T)) -> []T {
+ return doc.from_array(g_header, a)
+}
+
+str :: proc(s: $A/doc.String) -> string {
+ return doc.from_string(g_header, s)
+}
+
+common_prefix :: proc(strs: []string) -> string {
+ if len(strs) == 0 {
+ return ""
+ }
+ n := max(int)
+ for str in strs {
+ n = min(n, len(str))
+ }
+
+ prefix := strs[0][:n]
+ for str in strs[1:] {
+ for len(prefix) != 0 && str[:len(prefix)] != prefix {
+ prefix = prefix[:len(prefix)-1]
+ }
+ if len(prefix) == 0 {
+ break
+ }
+ }
+ return prefix
+}
+
+errorf :: proc(format: string, args: ..any) -> ! {
+ fmt.eprintf("%s ", os.args[0])
+ fmt.eprintf(format, ..args)
+ fmt.eprintln()
+ os.exit(1)
+}
+
+main :: proc() {
+ if len(os.args) != 2 {
+ errorf("expected path to odin executable")
+ }
+ g_path_to_odin = os.args[1]
+ data, ok := os.read_entire_file("all.odin-doc")
+ if !ok {
+ errorf("unable to read file: all.odin-doc")
+ }
+ err: doc.Reader_Error
+ g_header, err = doc.read_from_bytes(data)
+ switch err {
+ case .None:
+ case .Header_Too_Small:
+ errorf("file is too small for the file format")
+ case .Invalid_Magic:
+ errorf("invalid magic for the file format")
+ case .Data_Too_Small:
+ errorf("data is too small for the file format")
+ case .Invalid_Version:
+ errorf("invalid file format version")
+ }
+ pkgs := array(g_header.pkgs)
+ entities := array(g_header.entities)
+
+ path_prefix: string
+ {
+ fullpaths: [dynamic]string
+ defer delete(fullpaths)
+
+ for pkg in pkgs[1:] {
+ append(&fullpaths, str(pkg.fullpath))
+ }
+ path_prefix = common_prefix(fullpaths[:])
+ }
+
+ for pkg in pkgs[1:] {
+ entries_array := array(pkg.entries)
+ fullpath := str(pkg.fullpath)
+ path := strings.trim_prefix(fullpath, path_prefix)
+ if ! strings.has_prefix(path, "core/") {
+ continue
+ }
+ trimmed_path := strings.trim_prefix(path, "core/")
+ if strings.has_prefix(trimmed_path, "sys") {
+ continue
+ }
+ if strings.contains(trimmed_path, "/_") {
+ continue
+ }
+ for entry in entries_array {
+ entity := entities[entry.entity]
+ find_and_add_examples(
+ docs = str(entity.docs),
+ package_name = str(pkg.name),
+ entity_name = str(entity.name),
+ )
+ }
+ }
+ write_test_suite(g_examples_to_verify[:])
+ if g_bad_doc {
+ errorf("We created bad documentation!")
+ }
+
+ if ! run_test_suite() {
+ errorf("Test suite failed!")
+ }
+ fmt.println("Examples verified")
+}
+
+// NOTE: this is a pretty close copy paste from the website pkg documentation on parsing the docs
+find_and_add_examples :: proc(docs: string, package_name: string, entity_name: string) {
+ if docs == "" {
+ return
+ }
+ Block_Kind :: enum {
+ Other,
+ Example,
+ Output,
+ }
+ Block :: struct {
+ kind: Block_Kind,
+ lines: []string,
+ }
+ lines := strings.split_lines(docs)
+ curr_block_kind := Block_Kind.Other
+ start := 0
+
+ example_block: Block // when set the kind should be Example
+ output_block: Block // when set the kind should be Output
+ // rely on zii that the kinds have not been set
+ assert(example_block.kind != .Example)
+ assert(output_block.kind != .Output)
+
+ insert_block :: proc(block: Block, example: ^Block, output: ^Block, name: string) {
+ switch block.kind {
+ case .Other:
+ case .Example:
+ if example.kind == .Example {
+ fmt.eprintf("The documentation for %q has multiple examples which is not allowed\n", name)
+ g_bad_doc = true
+ }
+ example^ = block
+ case .Output: output^ = block
+ if example.kind == .Output {
+ fmt.eprintf("The documentation for %q has multiple output which is not allowed\n", name)
+ g_bad_doc = true
+ }
+ output^ = block
+ }
+ }
+
+ for line, i in lines {
+ text := strings.trim_space(line)
+ next_block_kind := curr_block_kind
+
+ switch curr_block_kind {
+ case .Other:
+ switch {
+ case strings.has_prefix(line, "Example:"): next_block_kind = .Example
+ case strings.has_prefix(line, "Output:"): next_block_kind = .Output
+ }
+ case .Example:
+ switch {
+ case strings.has_prefix(line, "Output:"): next_block_kind = .Output
+ case ! (text == "" || strings.has_prefix(line, "\t")): next_block_kind = .Other
+ }
+ case .Output:
+ switch {
+ case strings.has_prefix(line, "Example:"): next_block_kind = .Example
+ case ! (text == "" || strings.has_prefix(line, "\t")): next_block_kind = .Other
+ }
+ }
+
+ if i-start > 0 && (curr_block_kind != next_block_kind) {
+ insert_block(Block{curr_block_kind, lines[start:i]}, &example_block, &output_block, entity_name)
+ curr_block_kind, start = next_block_kind, i
+ }
+ }
+
+ if start < len(lines) {
+ insert_block(Block{curr_block_kind, lines[start:]}, &example_block, &output_block, entity_name)
+ }
+
+ if output_block.kind == .Output && example_block.kind != .Example {
+ fmt.eprintf("The documentation for %q has an output block but no example\n", entity_name)
+ g_bad_doc = true
+ }
+
+ // Write example and output block if they're both present
+ if example_block.kind == .Example && output_block.kind == .Output {
+ {
+ // Example block starts with
+ // `Example:` and a number of white spaces,
+ lines := &example_block.lines
+ for len(lines) > 0 && (strings.trim_space(lines[0]) == "" || strings.has_prefix(lines[0], "Example:")) {
+ lines^ = lines[1:]
+ }
+ }
+ {
+ // Output block starts with
+ // `Output:` and a number of white spaces,
+ lines := &output_block.lines
+ for len(lines) > 0 && (strings.trim_space(lines[0]) == "" || strings.has_prefix(lines[0], "Output:")) {
+ lines^ = lines[1:]
+ }
+ // Additionally we need to strip all empty lines at the end of output to not include those in the expected output
+ for len(lines) > 0 && (strings.trim_space(lines[len(lines) - 1]) == "") {
+ lines^ = lines[:len(lines) - 1]
+ }
+ }
+ // Remove first layer of tabs which are always present
+ for line in &example_block.lines {
+ line = strings.trim_prefix(line, "\t")
+ }
+ for line in &output_block.lines {
+ line = strings.trim_prefix(line, "\t")
+ }
+ append(&g_examples_to_verify, Example_Test {
+ entity_name = entity_name,
+ package_name = package_name,
+ example_code = example_block.lines,
+ expected_output = output_block.lines,
+ })
+ }
+}
+
+
+write_test_suite :: proc(example_tests: []Example_Test) {
+ TEST_SUITE_DIRECTORY :: "verify"
+ os.remove_directory(TEST_SUITE_DIRECTORY)
+ os.make_directory(TEST_SUITE_DIRECTORY)
+
+ example_build := strings.builder_make()
+ test_runner := strings.builder_make()
+
+ strings.write_string(&test_runner,
+`//+private
+package documentation_verification
+
+import "core:os"
+import "core:mem"
+import "core:io"
+import "core:fmt"
+import "core:thread"
+import "core:sync"
+import "core:intrinsics"
+
+@(private="file")
+_read_pipe: os.Handle
+@(private="file")
+_write_pipe: os.Handle
+@(private="file")
+_pipe_reader_semaphore: sync.Sema
+@(private="file")
+_out_data: string
+@(private="file")
+_out_buffer: [mem.Megabyte]byte
+@(private="file")
+_bad_test_found: bool
+
+@(private="file")
+_spawn_pipe_reader :: proc() {
+ thread.create_and_start(proc(^thread.Thread) {
+ stream := os.stream_from_handle(_read_pipe)
+ reader := io.to_reader(stream)
+ sync.post(&_pipe_reader_semaphore) // notify thread is ready
+ for {
+ n_read := 0
+ read_to_null_byte := 0
+ finished_reading := false
+ for ! finished_reading {
+ just_read, err := io.read(reader, _out_buffer[n_read:], &n_read); if err != .None {
+ panic("We got an IO error!")
+ }
+ for b in _out_buffer[n_read - just_read: n_read] {
+ if b == 0 {
+ finished_reading = true
+ break
+ }
+ read_to_null_byte += 1
+ }
+ }
+ intrinsics.volatile_store(&_out_data, transmute(string)_out_buffer[:read_to_null_byte])
+ sync.post(&_pipe_reader_semaphore) // notify we read the null byte
+ }
+ })
+
+ sync.wait(&_pipe_reader_semaphore) // wait for thread to be ready
+}
+
+@(private="file")
+_check :: proc(test_name: string, expected: string) {
+ null_byte: [1]byte
+ os.write(_write_pipe, null_byte[:])
+ os.flush(_write_pipe)
+ sync.wait(&_pipe_reader_semaphore)
+ output := intrinsics.volatile_load(&_out_data) // wait for thread to read null byte
+ if expected != output {
+ fmt.eprintf("Test %q got unexpected output:\n%q\n", test_name, output)
+ fmt.eprintf("Expected:\n%q\n", expected)
+ _bad_test_found = true
+ }
+}
+
+main :: proc() {
+ _read_pipe, _write_pipe, _ = os.pipe()
+ os.stdout = _write_pipe
+ _spawn_pipe_reader()
+`)
+ for test in example_tests {
+ strings.builder_reset(&example_build)
+ strings.write_string(&example_build, "package documentation_verification\n\n")
+ for line in test.example_code {
+ strings.write_string(&example_build, line)
+ strings.write_byte(&example_build, '\n')
+ }
+
+ code_string := strings.to_string(example_build)
+
+ example_ast := ast.File { src = code_string }
+ odin_parser := parser.default_parser()
+
+ if ! parser.parse_file(&odin_parser, &example_ast) {
+ g_bad_doc = true
+ continue
+ }
+ if odin_parser.error_count > 0 {
+ fmt.eprintf("Errors on the following code generated for %q:\n%v\n", test.entity_name, code_string)
+ g_bad_doc = true
+ continue
+ }
+
+ enforced_name := fmt.tprintf("%v_example", test.entity_name)
+ index_of_proc_name: int
+ code_test_name: string
+
+ for d in example_ast.decls {
+ value_decl, is_value := d.derived.(^ast.Value_Decl); if ! is_value {
+ continue
+ }
+ if len(value_decl.values) != 1 {
+ continue
+ }
+ proc_lit, is_proc_lit := value_decl.values[0].derived_expr.(^ast.Proc_Lit); if ! is_proc_lit {
+ continue
+ }
+ if len(proc_lit.type.params.list) > 0 {
+ continue
+ }
+ this_procedure_name := code_string[value_decl.names[0].pos.offset:value_decl.names[0].end.offset]
+ if this_procedure_name != enforced_name {
+ continue
+ }
+ index_of_proc_name = value_decl.names[0].pos.offset
+ code_test_name = this_procedure_name
+ break
+ }
+
+ if code_test_name == "" {
+ fmt.eprintf("We could not any find procedure literals with no arguments with the identifier %q for the example for %q\n", enforced_name, test.entity_name)
+ g_bad_doc = true
+ continue
+ }
+
+ fmt.sbprintf(&test_runner, "\t%v_%v()\n", test.package_name, code_test_name)
+ fmt.sbprintf(&test_runner, "\t_check(%q, `", code_test_name)
+ for line in test.expected_output {
+ strings.write_string(&test_runner, line)
+ strings.write_string(&test_runner, "\n")
+ }
+ strings.write_string(&test_runner, "`)\n")
+ save_path := fmt.tprintf("verify/test_%v_%v.odin", test.package_name, code_test_name)
+
+ test_file_handle, err := os.open(save_path, os.O_WRONLY | os.O_CREATE); if err != 0 {
+ fmt.eprintf("We could not open the file to the path %q for writing\n", save_path)
+ g_bad_doc = true
+ continue
+ }
+ defer os.close(test_file_handle)
+ stream := os.stream_from_handle(test_file_handle)
+ writer, ok := io.to_writer(stream); if ! ok {
+ fmt.eprintf("We could not make the writer for the path %q\n", save_path)
+ g_bad_doc = true
+ continue
+ }
+ fmt.wprintf(writer, "%v%v_%v", code_string[:index_of_proc_name], test.package_name, code_string[index_of_proc_name:])
+ }
+
+ strings.write_string(&test_runner,
+`
+ if _bad_test_found {
+ fmt.eprintln("One or more tests failed")
+ os.exit(1)
+ }
+}`)
+ os.write_entire_file("verify/main.odin", transmute([]byte)strings.to_string(test_runner))
+}
+
+run_test_suite :: proc() -> bool {
+ return libc.system(fmt.caprintf("%v run verify", g_path_to_odin)) == 0
+} \ No newline at end of file
diff --git a/tests/internal/build.bat b/tests/internal/build.bat
index 313e1dbb5..995086523 100644
--- a/tests/internal/build.bat
+++ b/tests/internal/build.bat
@@ -1,4 +1,4 @@
@echo off
set PATH_TO_ODIN==..\..\odin
-%PATH_TO_ODIN% run test_map.odin -file -vet -strict-style -o:minimal
+%PATH_TO_ODIN% run test_map.odin -file -vet -strict-style -o:minimal || exit /b
rem -define:SEED=42 \ No newline at end of file
diff --git a/tests/issues/run.bat b/tests/issues/run.bat
index c526fd472..ea5feddaa 100644
--- a/tests/issues/run.bat
+++ b/tests/issues/run.bat
@@ -5,19 +5,14 @@ pushd build
set COMMON=-collection:tests=..\..
-set ERROR_DID_OCCUR=0
-
@echo on
-..\..\..\odin test ..\test_issue_829.odin %COMMON% -file
-..\..\..\odin test ..\test_issue_1592.odin %COMMON% -file
-..\..\..\odin test ..\test_issue_2087.odin %COMMON% -file
-..\..\..\odin build ..\test_issue_2113.odin %COMMON% -file -debug
+..\..\..\odin test ..\test_issue_829.odin %COMMON% -file || exit /b
+..\..\..\odin test ..\test_issue_1592.odin %COMMON% -file || exit /b
+..\..\..\odin test ..\test_issue_2087.odin %COMMON% -file || exit /b
+..\..\..\odin build ..\test_issue_2113.odin %COMMON% -file -debug || exit /b
@echo off
-if %ERRORLEVEL% NEQ 0 set ERROR_DID_OCCUR=1
-
popd
rmdir /S /Q build
-if %ERROR_DID_OCCUR% NEQ 0 EXIT /B 1
diff --git a/tests/vendor/build.bat b/tests/vendor/build.bat
index d92a5eaea..09754fc40 100644
--- a/tests/vendor/build.bat
+++ b/tests/vendor/build.bat
@@ -5,9 +5,9 @@ set PATH_TO_ODIN==..\..\odin
echo ---
echo Running vendor:botan tests
echo ---
-%PATH_TO_ODIN% run botan %COMMON% -out:vendor_botan.exe
+%PATH_TO_ODIN% run botan %COMMON% -out:vendor_botan.exe || exit /b
echo ---
echo Running vendor:glfw tests
echo ---
-%PATH_TO_ODIN% run glfw %COMMON% -out:vendor_glfw.exe \ No newline at end of file
+%PATH_TO_ODIN% run glfw %COMMON% -out:vendor_glfw.exe || exit /b \ No newline at end of file