Merge remote-tracking branch 'origin' into wsapoll

This commit is contained in:
Jon Lipstate
2023-04-03 23:57:28 -07:00
22 changed files with 1792 additions and 483 deletions
+7
View File
@@ -163,6 +163,13 @@ jobs:
cd tests\internal cd tests\internal
call build.bat call build.bat
timeout-minutes: 10 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 - name: core:math/big tests
shell: cmd shell: cmd
run: | run: |
+2
View File
@@ -11,6 +11,8 @@ package util
*/ */
import "core:mem" import "core:mem"
// Keep vet happy
_ :: mem
// @note(bp): this can replace the other two // @note(bp): this can replace the other two
cast_slice :: #force_inline proc "contextless" ($D: typeid/[]$DE, src: $S/[]$SE) -> D { cast_slice :: #force_inline proc "contextless" ($D: typeid/[]$DE, src: $S/[]$SE) -> D {
+5 -4
View File
@@ -12,10 +12,10 @@ Ascii_Set :: distinct [8]u32
/* /*
Creates an Ascii_Set with unique characters from the input string. Creates an Ascii_Set with unique characters from the input string.
**Inputs** Inputs:
- chars: A string containing characters to include in the Ascii_Set. - chars: A string containing characters to include in the Ascii_Set.
**Returns** Returns:
- as: An Ascii_Set with unique characters from the input string. - 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. - 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. Determines if a given char is contained within an Ascii_Set.
**Inputs** Inputs:
- as: The Ascii_Set to search. - as: The Ascii_Set to search.
- c: The char to check for in the Ascii_Set. - 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 { ascii_set_contains :: proc(as: Ascii_Set, c: byte) -> bool #no_bounds_check {
return as[c>>5] & (1<<(c&31)) != 0 return as[c>>5] & (1<<(c&31)) != 0
+133 -91
View File
@@ -7,10 +7,11 @@ import "core:io"
/* /*
Type definition for a procedure that flushes a Builder Type definition for a procedure that flushes a Builder
**Inputs** Inputs:
- b: A pointer to the Builder - 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) 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* *Allocates Using Provided Allocator*
**Inputs** Inputs:
- allocator: (default is context.allocator) - allocator: (default is context.allocator)
**Returns** A new Builder Returns:
A new Builder
*/ */
builder_make_none :: proc(allocator := context.allocator) -> Builder { builder_make_none :: proc(allocator := context.allocator) -> Builder {
return Builder{buf=make([dynamic]byte, allocator)} 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* *Allocates Using Provided Allocator*
**Inputs** Inputs:
- len: The desired length of the Builder's buffer - len: The desired length of the Builder's buffer
- allocator: (default is context.allocator) - allocator: (default is context.allocator)
**Returns** A new Builder Returns:
A new Builder
*/ */
builder_make_len :: proc(len: int, allocator := context.allocator) -> Builder { builder_make_len :: proc(len: int, allocator := context.allocator) -> Builder {
return Builder{buf=make([dynamic]byte, len, allocator)} 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* *Allocates Using Provided Allocator*
**Inputs** Inputs:
- len: The desired length of the Builder's buffer - len: The desired length of the Builder's buffer
- cap: The desired capacity of the Builder's buffer, cap is max(cap, len) - cap: The desired capacity of the Builder's buffer, cap is max(cap, len)
- allocator: (default is context.allocator) - 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 { builder_make_len_cap :: proc(len, cap: int, allocator := context.allocator) -> Builder {
return Builder{buf=make([dynamic]byte, len, cap, allocator)} return Builder{buf=make([dynamic]byte, len, cap, allocator)}
@@ -75,11 +79,12 @@ It replaces the existing `buf`
*Allocates Using Provided Allocator* *Allocates Using Provided Allocator*
**Inputs** Inputs:
- b: A pointer to the Builder - b: A pointer to the Builder
- allocator: (default is context.allocator) - allocator: (default is context.allocator)
**Returns** initialized ^Builder Returns:
initialized ^Builder
*/ */
builder_init_none :: proc(b: ^Builder, allocator := context.allocator) -> ^Builder { builder_init_none :: proc(b: ^Builder, allocator := context.allocator) -> ^Builder {
b.buf = make([dynamic]byte, allocator) b.buf = make([dynamic]byte, allocator)
@@ -91,12 +96,13 @@ It replaces the existing `buf`
*Allocates Using Provided Allocator* *Allocates Using Provided Allocator*
**Inputs** Inputs:
- b: A pointer to the Builder - b: A pointer to the Builder
- len: The desired length of the Builder's buffer - len: The desired length of the Builder's buffer
- allocator: (default is context.allocator) - allocator: (default is context.allocator)
**Returns** Initialized ^Builder Returns:
Initialized ^Builder
*/ */
builder_init_len :: proc(b: ^Builder, len: int, allocator := context.allocator) -> ^Builder { builder_init_len :: proc(b: ^Builder, len: int, allocator := context.allocator) -> ^Builder {
b.buf = make([dynamic]byte, len, allocator) 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 Initializes a Builder with a specified length and cap
It replaces the existing `buf` It replaces the existing `buf`
**Inputs** Inputs:
- b: A pointer to the Builder - b: A pointer to the Builder
- len: The desired length of the Builder's buffer - len: The desired length of the Builder's buffer
- cap: The desired capacity of the Builder's buffer, actual max(len,cap) - cap: The desired capacity of the Builder's buffer, actual max(len,cap)
- allocator: (default is context.allocator) - 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 { builder_init_len_cap :: proc(b: ^Builder, len, cap: int, allocator := context.allocator) -> ^Builder {
b.buf = make([dynamic]byte, len, cap, allocator) 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 Returns an io.Stream from a Builder
**Inputs** Inputs:
- b: A pointer to the Builder - b: A pointer to the Builder
**Returns** An io.Stream Returns:
An io.Stream
*/ */
to_stream :: proc(b: ^Builder) -> io.Stream { to_stream :: proc(b: ^Builder) -> io.Stream {
return io.Stream{stream_vtable=_builder_stream_vtable, stream_data=b} 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 Returns an io.Writer from a Builder
**Inputs** Inputs:
- b: A pointer to the Builder - b: A pointer to the Builder
**Returns** An io.Writer Returns:
An io.Writer
*/ */
to_writer :: proc(b: ^Builder) -> io.Writer { to_writer :: proc(b: ^Builder) -> io.Writer {
return io.to_writer(to_stream(b)) return io.to_writer(to_stream(b))
@@ -180,7 +189,7 @@ to_writer :: proc(b: ^Builder) -> io.Writer {
/* /*
Deletes the Builder byte buffer content Deletes the Builder byte buffer content
**Inputs** Inputs:
- b: A pointer to the Builder - b: A pointer to the Builder
*/ */
builder_destroy :: proc(b: ^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 Reserves the Builder byte buffer to a specific capacity, when it's higher than before
**Inputs** Inputs:
- b: A pointer to the Builder - b: A pointer to the Builder
- cap: The desired capacity for the Builder's buffer - 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) Clears the Builder byte buffer content (sets len to zero)
**Inputs** Inputs:
- b: A pointer to the Builder - b: A pointer to the Builder
*/ */
builder_reset :: proc(b: ^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* *Uses Nil Allocator - Does NOT allocate*
**Inputs** Inputs:
- backing: A slice of bytes to be used as the backing buffer - backing: A slice of bytes to be used as the backing buffer
Returns:
A new Builder
Example: Example:
import "core:fmt" import "core:fmt"
import "core:strings" import "core:strings"
strings_builder_from_bytes_example :: proc() { builder_from_bytes_example :: proc() {
bytes: [8]byte // <-- gets filled bytes: [8]byte // <-- gets filled
builder := strings.builder_from_bytes(bytes[:]) builder := strings.builder_from_bytes(bytes[:])
fmt.println(strings.write_byte(&builder, 'a')) // -> "a" strings.write_byte(&builder, 'a')
fmt.println(strings.write_byte(&builder, 'b')) // -> "ab" fmt.println(strings.to_string(builder)) // -> "a"
strings.write_byte(&builder, 'b')
fmt.println(strings.to_string(builder)) // -> "ab"
} }
Output: Output:
@@ -230,7 +244,6 @@ Output:
a a
ab ab
**Returns** A new Builder
*/ */
builder_from_bytes :: proc(backing: []byte) -> Builder { builder_from_bytes :: proc(backing: []byte) -> Builder {
s := transmute(runtime.Raw_Slice)backing 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 Casts the Builder byte buffer to a string and returns it
**Inputs** Inputs:
- b: A Builder - 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 { to_string :: proc(b: Builder) -> string {
return string(b.buf[:]) return string(b.buf[:])
@@ -260,10 +274,11 @@ to_string :: proc(b: Builder) -> string {
/* /*
Returns the length of the Builder's buffer, in bytes Returns the length of the Builder's buffer, in bytes
**Inputs** Inputs:
- b: A Builder - 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 { builder_len :: proc(b: Builder) -> int {
return len(b.buf) return len(b.buf)
@@ -271,10 +286,11 @@ builder_len :: proc(b: Builder) -> int {
/* /*
Returns the capacity of the Builder's buffer, in bytes Returns the capacity of the Builder's buffer, in bytes
**Inputs** Inputs:
- b: A Builder - 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 { builder_cap :: proc(b: Builder) -> int {
return cap(b.buf) 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 The free space left in the Builder's buffer, in bytes
**Inputs** Inputs:
- b: A Builder - 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 { builder_space :: proc(b: Builder) -> int {
return cap(b.buf) - len(b.buf) 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 Appends a byte to the Builder and returns the number of bytes appended
**Inputs** Inputs:
- b: A pointer to the Builder - b: A pointer to the Builder
- x: The byte to be appended - 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: Example:
import "core:fmt" import "core:fmt"
import "core:strings" import "core:strings"
strings_write_byte_example :: proc() { write_byte_example :: proc() {
builder := strings.builder_make() builder := strings.builder_make()
strings.write_byte(&builder, 'a') // 1 strings.write_byte(&builder, 'a') // 1
strings.write_byte(&builder, 'b') // 1 strings.write_byte(&builder, 'b') // 1
@@ -313,9 +335,6 @@ Output:
ab 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) { write_byte :: proc(b: ^Builder, x: byte) -> (n: int) {
n0 := len(b.buf) 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 Appends a slice of bytes to the Builder and returns the number of bytes appended
**Inputs** Inputs:
- b: A pointer to the Builder - b: A pointer to the Builder
- x: The slice of bytes to be appended - x: The slice of bytes to be appended
@@ -335,7 +354,7 @@ Example:
import "core:fmt" import "core:fmt"
import "core:strings" import "core:strings"
strings_write_bytes_example :: proc() { write_bytes_example :: proc() {
builder := strings.builder_make() builder := strings.builder_make()
bytes := [?]byte { 'a', 'b', 'c' } bytes := [?]byte { 'a', 'b', 'c' }
strings.write_bytes(&builder, bytes[:]) // 3 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. 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) { write_bytes :: proc(b: ^Builder, x: []byte) -> (n: int) {
n0 := len(b.buf) 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` 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 - b: A pointer to the Builder
- r: The rune to be appended - 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: Example:
import "core:fmt" import "core:fmt"
import "core:strings" import "core:strings"
strings_write_rune_example :: proc() { write_rune_example :: proc() {
builder := strings.builder_make() builder := strings.builder_make()
strings.write_rune(&builder, 'ä') // 2 None strings.write_rune(&builder, 'ä') // 2 None
strings.write_rune(&builder, 'b') // 1 None strings.write_rune(&builder, 'b') // 1 None
@@ -375,9 +400,6 @@ Output:
äb ä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) { write_rune :: proc(b: ^Builder, r: rune) -> (int, io.Error) {
return io.write_rune(to_writer(b), r) 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 Appends a quoted rune to the Builder and returns the number of bytes written
**Inputs** Inputs:
- b: A pointer to the Builder - b: A pointer to the Builder
- r: The rune to be appended - 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: Example:
import "core:fmt" import "core:fmt"
import "core:strings" import "core:strings"
strings_write_quoted_rune_example :: proc() { write_quoted_rune_example :: proc() {
builder := strings.builder_make() builder := strings.builder_make()
strings.write_string(&builder, "abc") // 3 strings.write_string(&builder, "abc") // 3
strings.write_quoted_rune(&builder, 'ä') // 4 strings.write_quoted_rune(&builder, 'ä') // 4
@@ -406,9 +433,6 @@ Output:
abc'ä'abc 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) { write_quoted_rune :: proc(b: ^Builder, r: rune) -> (n: int) {
return io.write_quoted_rune(to_writer(b), r) 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 Appends a string to the Builder and returns the number of bytes written
**Inputs** Inputs:
- b: A pointer to the Builder - b: A pointer to the Builder
- s: The string to be appended - 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: Example:
import "core:fmt" import "core:fmt"
import "core:strings" import "core:strings"
strings_write_string_example :: proc() { write_string_example :: proc() {
builder := strings.builder_make() builder := strings.builder_make()
strings.write_string(&builder, "a") // 1 strings.write_string(&builder, "a") // 1
strings.write_string(&builder, "bc") // 2 strings.write_string(&builder, "bc") // 2
@@ -436,9 +465,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_string :: proc(b: ^Builder, s: string) -> (n: int) { write_string :: proc(b: ^Builder, s: string) -> (n: int) {
n0 := len(b.buf) 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 Pops and returns the last byte in the Builder or 0 when the Builder is empty
**Inputs** Inputs:
- b: A pointer to the Builder - 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) { pop_byte :: proc(b: ^Builder) -> (r: byte) {
if len(b.buf) == 0 { 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 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 - 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) { pop_rune :: proc(b: ^Builder) -> (r: rune, width: int) {
if len(b.buf) == 0 { if len(b.buf) == 0 {
@@ -485,17 +513,22 @@ pop_rune :: proc(b: ^Builder) -> (r: rune, width: int) {
@(private) @(private)
DIGITS_LOWER := "0123456789abcdefx" DIGITS_LOWER := "0123456789abcdefx"
/* /*
**Inputs** Inputs:
- b: A pointer to the Builder - b: A pointer to the Builder
- str: The string to be quoted and appended - str: The string to be quoted and appended
- quote: The optional quote character (default is double quotes) - 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: Example:
import "core:fmt" import "core:fmt"
import "core:strings" import "core:strings"
strings_write_quoted_string_example :: proc() { write_quoted_string_example :: proc() {
builder := strings.builder_make() builder := strings.builder_make()
strings.write_quoted_string(&builder, "a") // 3 strings.write_quoted_string(&builder, "a") // 3
strings.write_quoted_string(&builder, "bc", '\'') // 4 strings.write_quoted_string(&builder, "bc", '\'') // 4
@@ -507,9 +540,6 @@ Output:
"a"'bc'"xyz" "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) { write_quoted_string :: proc(b: ^Builder, str: string, quote: byte = '"') -> (n: int) {
n, _ = io.write_quoted_string(to_writer(b), str, quote) 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 Appends a rune to the Builder and returns the number of bytes written
**Inputs** Inputs:
- b: A pointer to the Builder - b: A pointer to the Builder
- r: The rune to be appended - r: The rune to be appended
- write_quote: Optional boolean flag to wrap in single-quotes (') (default is true) - 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: Example:
import "core:fmt" import "core:fmt"
@@ -540,9 +575,6 @@ Output:
a'"'x 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) { write_encoded_rune :: proc(b: ^Builder, r: rune, write_quote := true) -> (n: int) {
n, _ = io.write_encoded_rune(to_writer(b), r, write_quote) 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 Appends an escaped rune to the Builder and returns the number of bytes written
**Inputs** Inputs:
- b: A pointer to the Builder - b: A pointer to the Builder
- r: The rune to be appended - r: The rune to be appended
- quote: The quote character - 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. 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) { 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) 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 Writes a f64 value to the Builder and returns the number of characters written
**Inputs** Inputs:
- b: A pointer to the Builder - b: A pointer to the Builder
- f: The f64 value to be appended - f: The f64 value to be appended
- fmt: The format byte - 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. 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) { write_float :: proc(b: ^Builder, f: f64, fmt: byte, prec, bit_size: int, always_signed := false) -> (n: int) {
buf: [384]byte 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 Writes a f16 value to the Builder and returns the number of characters written
**Inputs** Inputs:
- b: A pointer to the Builder - b: A pointer to the Builder
- f: The f16 value to be appended - f: The f16 value to be appended
- fmt: The format byte - 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. 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) { write_f16 :: proc(b: ^Builder, f: f16, fmt: byte, always_signed := false) -> (n: int) {
buf: [384]byte 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 Writes a f32 value to the Builder and returns the number of characters written
**Inputs** Inputs:
- b: A pointer to the Builder - b: A pointer to the Builder
- f: The f32 value to be appended - f: The f32 value to be appended
- fmt: The format byte - fmt: The format byte
- always_signed: Optional boolean flag to always include the sign - 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: Example:
import "core:fmt" import "core:fmt"
import "core:strings" import "core:strings"
strings_write_f32_example :: proc() { write_f32_example :: proc() {
builder := strings.builder_make() builder := strings.builder_make()
strings.write_f32(&builder, 3.14159, 'f') // 6 strings.write_f32(&builder, 3.14159, 'f') // 6
strings.write_string(&builder, " - ") // 3 strings.write_string(&builder, " - ") // 3
@@ -643,9 +683,6 @@ Output:
3.14159012 - -1.23000003e-01 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) { write_f32 :: proc(b: ^Builder, f: f32, fmt: byte, always_signed := false) -> (n: int) {
buf: [384]byte 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 Writes a f32 value to the Builder and returns the number of characters written
**Inputs** Inputs:
- b: A pointer to the Builder - b: A pointer to the Builder
- f: The f32 value to be appended - f: The f32 value to be appended
- fmt: The format byte - 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. 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) { write_f64 :: proc(b: ^Builder, f: f64, fmt: byte, always_signed := false) -> (n: int) {
buf: [384]byte 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 Writes a u64 value to the Builder and returns the number of characters written
**Inputs** Inputs:
- b: A pointer to the Builder - b: A pointer to the Builder
- i: The u64 value to be appended - i: The u64 value to be appended
- base: The optional base for the numeric representation - 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. 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) { write_u64 :: proc(b: ^Builder, i: u64, base: int = 10) -> (n: int) {
buf: [32]byte 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 Writes a i64 value to the Builder and returns the number of characters written
**Inputs** Inputs:
- b: A pointer to the Builder - b: A pointer to the Builder
- i: The i64 value to be appended - i: The i64 value to be appended
- base: The optional base for the numeric representation - 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. 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) { write_i64 :: proc(b: ^Builder, i: i64, base: int = 10) -> (n: int) {
buf: [32]byte 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 Writes a uint value to the Builder and returns the number of characters written
**Inputs** Inputs:
- b: A pointer to the Builder - b: A pointer to the Builder
- i: The uint value to be appended - i: The uint value to be appended
- base: The optional base for the numeric representation - 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. 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) { write_uint :: proc(b: ^Builder, i: uint, base: int = 10) -> (n: int) {
return write_u64(b, u64(i), base) 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 Writes a int value to the Builder and returns the number of characters written
**Inputs** Inputs:
- b: A pointer to the Builder - b: A pointer to the Builder
- i: The int value to be appended - i: The int value to be appended
- base: The optional base for the numeric representation - 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. 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) { write_int :: proc(b: ^Builder, i: int, base: int = 10) -> (n: int) {
return write_i64(b, i64(i), base) return write_i64(b, i64(i), base)
+58 -38
View File
@@ -9,14 +9,15 @@ Converts invalid UTF-8 sequences in the input string `s` to the `replacement` st
*Allocates Using Provided Allocator* *Allocates Using Provided Allocator*
**Inputs** Inputs:
- s: Input string that may contain invalid UTF-8 sequences. - s: Input string that may contain invalid UTF-8 sequences.
- replacement: String to replace invalid UTF-8 sequences with. - replacement: String to replace invalid UTF-8 sequences with.
- allocator: (default: context.allocator). - allocator: (default: context.allocator).
WARNING: Allocation does not occur when len(s) == 0 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 { to_valid_utf8 :: proc(s, replacement: string, allocator := context.allocator) -> string {
if len(s) == 0 { if len(s) == 0 {
@@ -76,16 +77,19 @@ Converts the input string `s` to all lowercase characters.
*Allocates Using Provided Allocator* *Allocates Using Provided Allocator*
**Inputs** Inputs:
- s: Input string to be converted. - s: Input string to be converted.
- allocator: (default: context.allocator). - allocator: (default: context.allocator).
Returns:
A new string with all characters converted to lowercase.
Example: Example:
import "core:fmt" import "core:fmt"
import "core:strings" import "core:strings"
strings_to_lower_example :: proc() { to_lower_example :: proc() {
fmt.println(strings.to_lower("TeST")) fmt.println(strings.to_lower("TeST"))
} }
@@ -93,7 +97,6 @@ Output:
test test
**Returns** A new string with all characters converted to lowercase.
*/ */
to_lower :: proc(s: string, allocator := context.allocator) -> string { to_lower :: proc(s: string, allocator := context.allocator) -> string {
b: Builder b: Builder
@@ -108,16 +111,19 @@ Converts the input string `s` to all uppercase characters.
*Allocates Using Provided Allocator* *Allocates Using Provided Allocator*
**Inputs** Inputs:
- s: Input string to be converted. - s: Input string to be converted.
- allocator: (default: context.allocator). - allocator: (default: context.allocator).
Returns:
A new string with all characters converted to uppercase.
Example: Example:
import "core:fmt" import "core:fmt"
import "core:strings" import "core:strings"
strings_to_upper_example :: proc() { to_upper_example :: proc() {
fmt.println(strings.to_upper("Test")) fmt.println(strings.to_upper("Test"))
} }
@@ -125,7 +131,6 @@ Output:
TEST TEST
**Returns** A new string with all characters converted to uppercase.
*/ */
to_upper :: proc(s: string, allocator := context.allocator) -> string { to_upper :: proc(s: string, allocator := context.allocator) -> string {
b: Builder b: Builder
@@ -138,10 +143,11 @@ to_upper :: proc(s: string, allocator := context.allocator) -> string {
/* /*
Checks if the rune `r` is a delimiter (' ', '-', or '_'). Checks if the rune `r` is a delimiter (' ', '-', or '_').
**Inputs** Inputs:
- r: Rune to check for delimiter status. - 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 { is_delimiter :: proc(r: rune) -> bool {
return r == '-' || r == '_' || is_space(r) 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. Checks if the rune `r` is a non-alphanumeric or space character.
**Inputs** Inputs:
- r: Rune to check for separator status. - 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 { is_separator :: proc(r: rune) -> bool {
if r <= 0x7f { 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. 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. - w: An io.Writer to be used by the callback for writing output.
- s: The input string to be iterated over. - 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). - 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:strings"
import "core:io" import "core:io"
strings_string_case_iterator_example :: proc() { string_case_iterator_example :: proc() {
my_callback :: proc(w: io.Writer, prev, curr, next: rune) { my_callback :: proc(w: io.Writer, prev, curr, next: rune) {
fmt.println("my_callback", curr) // <-- Custom logic here fmt.println("my_callback", curr) // <-- Custom logic here
} }
@@ -241,11 +248,12 @@ Converts the input string `s` to "lowerCamelCase".
*Allocates Using Provided Allocator* *Allocates Using Provided Allocator*
**Inputs** Inputs:
- s: Input string to be converted. - s: Input string to be converted.
- allocator: (default: context.allocator). - allocator: (default: context.allocator).
**Returns** A "lowerCamelCase" formatted string. Returns:
A "lowerCamelCase" formatted string.
*/ */
to_camel_case :: proc(s: string, allocator := context.allocator) -> string { to_camel_case :: proc(s: string, allocator := context.allocator) -> string {
s := s s := s
@@ -275,11 +283,12 @@ Converts the input string `s` to "UpperCamelCase" (PascalCase).
*Allocates Using Provided Allocator* *Allocates Using Provided Allocator*
**Inputs** Inputs:
- s: Input string to be converted. - s: Input string to be converted.
- allocator: (default: context.allocator). - allocator: (default: context.allocator).
**Returns** A "PascalCase" formatted string. Returns:
A "PascalCase" formatted string.
*/ */
to_pascal_case :: proc(s: string, allocator := context.allocator) -> string { to_pascal_case :: proc(s: string, allocator := context.allocator) -> string {
s := s s := s
@@ -307,18 +316,21 @@ Returns a string converted to a delimiter-separated case with configurable casin
*Allocates Using Provided Allocator* *Allocates Using Provided Allocator*
**Inputs** Inputs:
- s: The input string to be converted - s: The input string to be converted
- delimiter: The rune to be used as the delimiter between words - 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) - all_upper_case: A boolean indicating if the output should be all uppercased (true) or lowercased (false)
- allocator: (default: context.allocator). - allocator: (default: context.allocator).
Returns:
The converted string
Example: Example:
import "core:fmt" import "core:fmt"
import "core:strings" 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", '_', false))
fmt.println(strings.to_delimiter_case("Hello World", ' ', true)) fmt.println(strings.to_delimiter_case("Hello World", ' ', true))
fmt.println(strings.to_delimiter_case("aBC", '_', false)) fmt.println(strings.to_delimiter_case("aBC", '_', false))
@@ -328,9 +340,8 @@ Output:
hello_world hello_world
HELLO WORLD HELLO WORLD
a_b_c a_bc
**Returns** The converted string
*/ */
to_delimiter_case :: proc( to_delimiter_case :: proc(
s: string, s: string,
@@ -380,16 +391,19 @@ Converts a string to "snake_case" with all runes lowercased
*Allocates Using Provided Allocator* *Allocates Using Provided Allocator*
**Inputs** Inputs:
- s: The input string to be converted - s: The input string to be converted
- allocator: (default: context.allocator). - allocator: (default: context.allocator).
Returns:
The converted string
Example: Example:
import "core:fmt" import "core:fmt"
import "core:strings" 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("HelloWorld"))
fmt.println(strings.to_snake_case("Hello World")) fmt.println(strings.to_snake_case("Hello World"))
} }
@@ -399,8 +413,6 @@ Output:
hello_world hello_world
hello_world hello_world
```
**Returns** The converted string
*/ */
to_snake_case :: proc(s: string, allocator := context.allocator) -> string { to_snake_case :: proc(s: string, allocator := context.allocator) -> string {
return to_delimiter_case(s, '_', false, allocator) 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* *Allocates Using Provided Allocator*
**Inputs** Inputs:
- s: The input string to be converted - s: The input string to be converted
- allocator: (default: context.allocator). - allocator: (default: context.allocator).
Returns:
The converted string
Example: Example:
import "core:fmt" import "core:fmt"
import "core:strings" import "core:strings"
strings_to_upper_snake_case_example :: proc() { to_upper_snake_case_example :: proc() {
fmt.println(strings.to_upper_snake_case("HelloWorld")) fmt.println(strings.to_upper_snake_case("HelloWorld"))
} }
@@ -429,7 +444,6 @@ Output:
HELLO_WORLD HELLO_WORLD
**Returns** The converted string
*/ */
to_upper_snake_case :: proc(s: string, allocator := context.allocator) -> string { to_upper_snake_case :: proc(s: string, allocator := context.allocator) -> string {
return to_delimiter_case(s, '_', true, allocator) 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* *Allocates Using Provided Allocator*
**Inputs** Inputs:
- s: The input string to be converted - s: The input string to be converted
- allocator: (default: context.allocator). - allocator: (default: context.allocator).
Returns:
The converted string
Example: Example:
import "core:fmt" import "core:fmt"
import "core:strings" import "core:strings"
strings_to_kebab_case_example :: proc() { to_kebab_case_example :: proc() {
fmt.println(strings.to_kebab_case("HelloWorld")) fmt.println(strings.to_kebab_case("HelloWorld"))
} }
@@ -456,7 +473,6 @@ Output:
hello-world hello-world
**Returns** The converted string
*/ */
to_kebab_case :: proc(s: string, allocator := context.allocator) -> string { to_kebab_case :: proc(s: string, allocator := context.allocator) -> string {
return to_delimiter_case(s, '-', false, allocator) 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* *Allocates Using Provided Allocator*
**Inputs** Inputs:
- s: The input string to be converted - s: The input string to be converted
- allocator: (default: context.allocator). - allocator: (default: context.allocator).
Returns:
The converted string
Example: Example:
import "core:fmt" import "core:fmt"
import "core:strings" import "core:strings"
strings_to_upper_kebab_case_example :: proc() { to_upper_kebab_case_example :: proc() {
fmt.println(strings.to_upper_kebab_case("HelloWorld")) fmt.println(strings.to_upper_kebab_case("HelloWorld"))
} }
@@ -483,7 +502,6 @@ Output:
HELLO-WORLD HELLO-WORLD
**Returns** The converted string
*/ */
to_upper_kebab_case :: proc(s: string, allocator := context.allocator) -> string { to_upper_kebab_case :: proc(s: string, allocator := context.allocator) -> string {
return to_delimiter_case(s, '-', true, allocator) return to_delimiter_case(s, '-', true, allocator)
@@ -493,16 +511,19 @@ Converts a string to "Ada_Case"
*Allocates Using Provided Allocator* *Allocates Using Provided Allocator*
**Inputs** Inputs:
- s: The input string to be converted - s: The input string to be converted
- allocator: (default: context.allocator). - allocator: (default: context.allocator).
Returns:
The converted string
Example: Example:
import "core:fmt" import "core:fmt"
import "core:strings" import "core:strings"
strings_to_upper_kebab_case_example :: proc() { to_ada_case_example :: proc() {
fmt.println(strings.to_ada_case("HelloWorld")) fmt.println(strings.to_ada_case("HelloWorld"))
} }
@@ -510,7 +531,6 @@ Output:
Hello_World Hello_World
**Returns** The converted string
*/ */
to_ada_case :: proc(s: string, allocator := context.allocator) -> string { to_ada_case :: proc(s: string, allocator := context.allocator) -> string {
s := s s := s
+11 -8
View File
@@ -25,7 +25,7 @@ Initializes the entries map and sets the allocator for the string entries
*Allocates Using Provided Allocators* *Allocates Using Provided Allocators*
**Inputs** Inputs:
- m: A pointer to the Intern struct to be initialized - m: A pointer to the Intern struct to be initialized
- allocator: The allocator for the Intern_Entry strings (Default: context.allocator) - allocator: The allocator for the Intern_Entry strings (Default: context.allocator)
- map_allocator: The allocator for the map of entries (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`. Frees the map and all its content allocated using the `.allocator`.
**Inputs** Inputs:
- m: A pointer to the Intern struct to be destroyed - m: A pointer to the Intern struct to be destroyed
*/ */
intern_destroy :: proc(m: ^Intern) { 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)* *Allocate using the Intern's Allocator (First time string is seen only)*
**Inputs** Inputs:
- m: A pointer to the Intern struct - m: A pointer to the Intern struct
- text: The string to be interned - text: The string to be interned
NOTE: The returned string lives as long as the map entry lives. 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) { intern_get :: proc(m: ^Intern, text: string) -> (str: string, err: runtime.Allocator_Error) {
entry := _intern_get_entry(m, text) or_return 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)* *Allocate using the Intern's Allocator (First time string is seen only)*
**Inputs** Inputs:
- m: A pointer to the Intern struct - m: A pointer to the Intern struct
- text: The string to be interned - text: The string to be interned
NOTE: The returned cstring lives as long as the map entry lives 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) { intern_get_cstring :: proc(m: ^Intern, text: string) -> (str: cstring, err: runtime.Allocator_Error) {
entry := _intern_get_entry(m, text) or_return 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)* *Allocate using the Intern's Allocator (First time string is seen only)*
**Inputs** Inputs:
- m: A pointer to the Intern struct - m: A pointer to the Intern struct
- text: The string to be looked up or interned - 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 { _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 { if prev, ok := m.entries[text]; ok {
+34 -27
View File
@@ -16,7 +16,7 @@ Reader :: struct {
/* /*
Initializes a string Reader with the provided string Initializes a string Reader with the provided string
**Inputs** Inputs:
- r: A pointer to a Reader struct - r: A pointer to a Reader struct
- s: The input string to be read - 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` Converts a Reader into an `io.Stream`
**Inputs** Inputs:
- r: A pointer to a Reader struct - 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) { reader_to_stream :: proc(r: ^Reader) -> (s: io.Stream) {
s.stream_data = r 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 Initializes a string Reader and returns an `io.Reader` for the given string
**Inputs** Inputs:
- r: A pointer to a Reader struct - r: A pointer to a Reader struct
- s: The input string to be read - 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 { to_reader :: proc(r: ^Reader, s: string) -> io.Reader {
reader_init(r, s) 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 Initializes a string Reader and returns an `io.Reader_At` for the given string
**Inputs** Inputs:
- r: A pointer to a Reader struct - r: A pointer to a Reader struct
- s: The input string to be read - 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 { to_reader_at :: proc(r: ^Reader, s: string) -> io.Reader_At {
reader_init(r, s) 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 Returns the remaining length of the Reader
**Inputs** Inputs:
- r: A pointer to a Reader struct - 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 { reader_length :: proc(r: ^Reader) -> int {
if r.i >= i64(len(r.s)) { 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 Returns the length of the string stored in the Reader
**Inputs** Inputs:
- r: A pointer to a Reader struct - 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 { reader_size :: proc(r: ^Reader) -> i64 {
return i64(len(r.s)) 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. Reads len(p) bytes from the Reader's string and copies into the provided slice.
**Inputs** Inputs:
- r: A pointer to a Reader struct - r: A pointer to a Reader struct
- p: A byte slice to copy data into - p: A byte slice to copy data into
**Returns** Returns:
- n: The number of bytes read - n: The number of bytes read
- err: An `io.Error` if an error occurs while reading, including `.EOF`, otherwise `nil` denotes success. - 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. 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 - r: A pointer to a Reader struct
- p: A byte slice to copy data into - p: A byte slice to copy data into
- off: The offset from which to read - off: The offset from which to read
**Returns** Returns:
- n: The number of bytes read - n: The number of bytes read
- err: An `io.Error` if an error occurs while reading, including `.EOF`, otherwise `nil` denotes success. - 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 Reads and returns a single byte from the Reader's string
**Inputs** Inputs:
- r: A pointer to a Reader struct - r: A pointer to a Reader struct
**Returns** Returns:
- The byte read from the Reader - The byte read from the Reader
- err: An `io.Error` if an error occurs while reading, including `.EOF`, otherwise `nil` denotes success. - 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 Decrements the Reader's index (i) by 1
**Inputs** Inputs:
- r: A pointer to a Reader struct - 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 { reader_unread_byte :: proc(r: ^Reader) -> io.Error {
if r.i <= 0 { 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 Reads and returns a single rune and its `size` from the Reader's string
**Inputs** Inputs:
- r: A pointer to a Reader struct - r: A pointer to a Reader struct
**Returns** Returns:
- rr: The rune read from the Reader - rr: The rune read from the Reader
- size: The size of the rune in bytes - size: The size of the rune in bytes
- err: An `io.Error` if an error occurs while reading - 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 Decrements the Reader's index (i) by the size of the last read rune
**Inputs** Inputs:
- r: A pointer to a Reader struct - r: A pointer to a Reader struct
WARNING: May only be used once and after a valid `read_rune` call WARNING: May only be used once and after a valid `read_rune` call
**Returns** An `io.Error` if an error occurs while unreading (`.Invalid_Unread`), else `nil` denotes success. Returns:
An `io.Error` if an error occurs while unreading (`.Invalid_Unread`), else `nil` denotes success.
*/ */
reader_unread_rune :: proc(r: ^Reader) -> io.Error { reader_unread_rune :: proc(r: ^Reader) -> io.Error {
if r.i <= 0 { 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 Seeks the Reader's index to a new position
**Inputs** Inputs:
- r: A pointer to a Reader struct - r: A pointer to a Reader struct
- offset: The new offset position - offset: The new offset position
- whence: The reference point for the new position (`.Start`, `.Current`, or `.End`) - whence: The reference point for the new position (`.Start`, `.Current`, or `.End`)
**Returns** Returns:
- The absolute offset after seeking - The absolute offset after seeking
- err: An `io.Error` if an error occurs while seeking (`.Invalid_Whence`, `.Invalid_Offset`) - 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` Writes the remaining content of the Reader's string into the provided `io.Writer`
**Inputs** Inputs:
- r: A pointer to a Reader struct - r: A pointer to a Reader struct
- w: The io.Writer to write the remaining content into - w: The io.Writer to write the remaining content into
WARNING: Panics if writer writes more bytes than remainig length of string. WARNING: Panics if writer writes more bytes than remainig length of string.
**Returns** Returns:
- n: The number of bytes written - n: The number of bytes written
- err: An io.Error if an error occurs while writing (`.Short_Write`) - err: An io.Error if an error occurs while writing (`.Short_Write`)
*/ */
+409 -251
View File
File diff suppressed because it is too large Load Diff
-22
View File
@@ -145,8 +145,6 @@ PCONDITION_VARIABLE :: ^CONDITION_VARIABLE
PLARGE_INTEGER :: ^LARGE_INTEGER PLARGE_INTEGER :: ^LARGE_INTEGER
PSRWLOCK :: ^SRWLOCK PSRWLOCK :: ^SRWLOCK
MMRESULT :: UINT
CREATE_WAITABLE_TIMER_MANUAL_RESET :: 0x00000001 CREATE_WAITABLE_TIMER_MANUAL_RESET :: 0x00000001
CREATE_WAITABLE_TIMER_HIGH_RESOLUTION :: 0x00000002 CREATE_WAITABLE_TIMER_HIGH_RESOLUTION :: 0x00000002
@@ -261,26 +259,6 @@ GET_FILEEX_INFO_LEVELS :: distinct i32
GetFileExInfoStandard: GET_FILEEX_INFO_LEVELS : 0 GetFileExInfoStandard: GET_FILEEX_INFO_LEVELS : 0
GetFileExMaxInfoLevel: GET_FILEEX_INFO_LEVELS : 1 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 DIAGNOSTIC_REASON_VERSION :: 0
+161
View File
@@ -3,9 +3,170 @@ package sys_windows
foreign import winmm "system:Winmm.lib" foreign import winmm "system:Winmm.lib"
MMRESULT :: UINT
@(default_calling_convention="stdcall") @(default_calling_convention="stdcall")
foreign winmm { foreign winmm {
timeGetDevCaps :: proc(ptc: LPTIMECAPS, cbtc: UINT) -> MMRESULT ---
timeBeginPeriod :: proc(uPeriod: UINT) -> MMRESULT --- timeBeginPeriod :: proc(uPeriod: UINT) -> MMRESULT ---
timeEndPeriod :: proc(uPeriod: UINT) -> MMRESULT --- timeEndPeriod :: proc(uPeriod: UINT) -> MMRESULT ---
timeGetTime :: proc() -> DWORD --- 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)
+100
View File
@@ -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
+384
View File
@@ -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, ' ')
}
+13
View File
@@ -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)
}
+3 -3
View File
@@ -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, ...) { gb_internal void print_usage_line(i32 indent, char const *fmt, ...) {
while (indent --> 0) { while (indent --> 0) {
gb_printf_err("\t"); gb_printf("\t");
} }
va_list va; va_list va;
va_start(va, fmt); va_start(va, fmt);
gb_printf_err_va(fmt, va); gb_printf_va(fmt, va);
va_end(va); va_end(va);
gb_printf_err("\n"); gb_printf("\n");
} }
gb_internal void usage(String argv0) { gb_internal void usage(String argv0) {
+5 -3
View File
@@ -3693,9 +3693,11 @@ gb_internal bool allow_field_separator(AstFile *f) {
if (allow_token(f, Token_Comma)) { if (allow_token(f, Token_Comma)) {
return true; return true;
} }
if (ALLOW_NEWLINE && token.kind == Token_Semicolon && !token_is_newline(token)) { if (ALLOW_NEWLINE && token.kind == Token_Semicolon) {
String p = token_to_string(token); if (!token_is_newline(token)) {
syntax_error(token_end_of_line(f, f->prev_token), "Expected a comma, got a %.*s", LIT(p)); 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); advance_token(f);
return true; return true;
} }
+19 -24
View File
@@ -6,87 +6,82 @@ python3 download_assets.py
echo --- echo ---
echo Running core:image tests echo Running core:image tests
echo --- 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 ---
echo Running core:compress tests echo Running core:compress tests
echo --- 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 ---
echo Running core:strings tests echo Running core:strings tests
echo --- 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 ---
echo Running core:hash tests echo Running core:hash tests
echo --- 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 ---
echo Running core:odin tests echo Running core:odin tests
echo --- 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 ---
echo Running core:crypto hash tests echo Running core:crypto hash tests
echo --- 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 ---
echo Running core:encoding tests echo Running core:encoding tests
echo --- echo ---
%PATH_TO_ODIN% run encoding/hxa %COMMON% %COLLECTION% -out:test_hxa.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 %PATH_TO_ODIN% run encoding/json %COMMON% -out:test_json.exe || exit /b
%PATH_TO_ODIN% run encoding/varint %COMMON% -out:test_varint.exe %PATH_TO_ODIN% run encoding/varint %COMMON% -out:test_varint.exe || exit /b
%PATH_TO_ODIN% run encoding/xml %COMMON% -out:test_xml.exe %PATH_TO_ODIN% run encoding/xml %COMMON% -out:test_xml.exe || exit /b
echo --- echo ---
echo Running core:math/noise tests echo Running core:math/noise tests
echo --- 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 ---
echo Running core:math tests echo Running core:math tests
echo --- 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 ---
echo Running core:math/linalg/glsl tests echo Running core:math/linalg/glsl tests
echo --- 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 ---
echo Running core:path/filepath tests echo Running core:path/filepath tests
echo --- 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 ---
echo Running core:reflect tests echo Running core:reflect tests
echo --- 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 ---
echo Running core:text/i18n tests echo Running core:text/i18n tests
echo --- 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 ---
echo Running core:net echo Running core:net
echo --- echo ---
%PATH_TO_ODIN% run net %COMMON% -out:test_core_net.exe %PATH_TO_ODIN% run net %COMMON% -out:test_core_net.exe || exit /b
echo ---
echo Running core:text/lua tests
echo ---
%PATH_TO_ODIN% run text\lua %COMMON% -out:test_core_lua_strlib.exe
echo --- echo ---
echo Running core:slice tests echo Running core:slice tests
echo --- 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 ---
echo Running core:container tests echo Running core:container tests
echo --- 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
@@ -151,6 +151,13 @@ shoco_test :: proc(t: ^testing.T) {
} }
for v in Shoco_Tests { 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_raw := len(v.raw)
expected_compressed := len(v.compressed) expected_compressed := len(v.compressed)
+13
View File
@@ -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
@@ -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
}
+1 -1
View File
@@ -1,4 +1,4 @@
@echo off @echo off
set PATH_TO_ODIN==..\..\odin 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 rem -define:SEED=42
+4 -9
View File
@@ -5,19 +5,14 @@ pushd build
set COMMON=-collection:tests=..\.. set COMMON=-collection:tests=..\..
set ERROR_DID_OCCUR=0
@echo on @echo on
..\..\..\odin test ..\test_issue_829.odin %COMMON% -file ..\..\..\odin test ..\test_issue_829.odin %COMMON% -file || exit /b
..\..\..\odin test ..\test_issue_1592.odin %COMMON% -file ..\..\..\odin test ..\test_issue_1592.odin %COMMON% -file || exit /b
..\..\..\odin test ..\test_issue_2087.odin %COMMON% -file ..\..\..\odin test ..\test_issue_2087.odin %COMMON% -file || exit /b
..\..\..\odin build ..\test_issue_2113.odin %COMMON% -file -debug ..\..\..\odin build ..\test_issue_2113.odin %COMMON% -file -debug || exit /b
@echo off @echo off
if %ERRORLEVEL% NEQ 0 set ERROR_DID_OCCUR=1
popd popd
rmdir /S /Q build rmdir /S /Q build
if %ERROR_DID_OCCUR% NEQ 0 EXIT /B 1
+2 -2
View File
@@ -5,9 +5,9 @@ set PATH_TO_ODIN==..\..\odin
echo --- echo ---
echo Running vendor:botan tests echo Running vendor:botan tests
echo --- 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 ---
echo Running vendor:glfw tests echo Running vendor:glfw tests
echo --- echo ---
%PATH_TO_ODIN% run glfw %COMMON% -out:vendor_glfw.exe %PATH_TO_ODIN% run glfw %COMMON% -out:vendor_glfw.exe || exit /b