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

View File

@@ -163,6 +163,13 @@ jobs:
cd tests\internal
call build.bat
timeout-minutes: 10
- name: Odin documentation tests
shell: cmd
run: |
call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Auxiliary\Build\vcvars64.bat
cd tests\documentation
call build.bat
timeout-minutes: 10
- name: core:math/big tests
shell: cmd
run: |

View File

@@ -11,6 +11,8 @@ package util
*/
import "core:mem"
// Keep vet happy
_ :: mem
// @note(bp): this can replace the other two
cast_slice :: #force_inline proc "contextless" ($D: typeid/[]$DE, src: $S/[]$SE) -> D {

View File

@@ -12,10 +12,10 @@ Ascii_Set :: distinct [8]u32
/*
Creates an Ascii_Set with unique characters from the input string.
**Inputs**
Inputs:
- chars: A string containing characters to include in the Ascii_Set.
**Returns**
Returns:
- as: An Ascii_Set with unique characters from the input string.
- ok: false if any character in the input string is not a valid ASCII character.
*/
@@ -33,11 +33,12 @@ ascii_set_make :: proc(chars: string) -> (as: Ascii_Set, ok: bool) #no_bounds_ch
/*
Determines if a given char is contained within an Ascii_Set.
**Inputs**
Inputs:
- as: The Ascii_Set to search.
- c: The char to check for in the Ascii_Set.
**Returns** A boolean indicating if the byte is contained in the Ascii_Set (true) or not (false).
Returns:
A boolean indicating if the byte is contained in the Ascii_Set (true) or not (false).
*/
ascii_set_contains :: proc(as: Ascii_Set, c: byte) -> bool #no_bounds_check {
return as[c>>5] & (1<<(c&31)) != 0

View File

@@ -7,10 +7,11 @@ import "core:io"
/*
Type definition for a procedure that flushes a Builder
**Inputs**
Inputs:
- b: A pointer to the Builder
**Returns** A boolean indicating whether the Builder should be reset
Returns:
A boolean indicating whether the Builder should be reset
*/
Builder_Flush_Proc :: #type proc(b: ^Builder) -> (do_reset: bool)
/*
@@ -26,10 +27,11 @@ Produces a Builder with a default length of 0 and cap of 16
*Allocates Using Provided Allocator*
**Inputs**
Inputs:
- allocator: (default is context.allocator)
**Returns** A new Builder
Returns:
A new Builder
*/
builder_make_none :: proc(allocator := context.allocator) -> Builder {
return Builder{buf=make([dynamic]byte, allocator)}
@@ -39,11 +41,12 @@ Produces a Builder with a specified length and cap of max(16,len) byte buffer
*Allocates Using Provided Allocator*
**Inputs**
Inputs:
- len: The desired length of the Builder's buffer
- allocator: (default is context.allocator)
**Returns** A new Builder
Returns:
A new Builder
*/
builder_make_len :: proc(len: int, allocator := context.allocator) -> Builder {
return Builder{buf=make([dynamic]byte, len, allocator)}
@@ -53,12 +56,13 @@ Produces a Builder with a specified length and cap
*Allocates Using Provided Allocator*
**Inputs**
Inputs:
- len: The desired length of the Builder's buffer
- cap: The desired capacity of the Builder's buffer, cap is max(cap, len)
- allocator: (default is context.allocator)
**Returns** A new Builder
Returns:
A new Builder
*/
builder_make_len_cap :: proc(len, cap: int, allocator := context.allocator) -> Builder {
return Builder{buf=make([dynamic]byte, len, cap, allocator)}
@@ -75,11 +79,12 @@ It replaces the existing `buf`
*Allocates Using Provided Allocator*
**Inputs**
Inputs:
- b: A pointer to the Builder
- allocator: (default is context.allocator)
**Returns** initialized ^Builder
Returns:
initialized ^Builder
*/
builder_init_none :: proc(b: ^Builder, allocator := context.allocator) -> ^Builder {
b.buf = make([dynamic]byte, allocator)
@@ -91,12 +96,13 @@ It replaces the existing `buf`
*Allocates Using Provided Allocator*
**Inputs**
Inputs:
- b: A pointer to the Builder
- len: The desired length of the Builder's buffer
- allocator: (default is context.allocator)
**Returns** Initialized ^Builder
Returns:
Initialized ^Builder
*/
builder_init_len :: proc(b: ^Builder, len: int, allocator := context.allocator) -> ^Builder {
b.buf = make([dynamic]byte, len, allocator)
@@ -106,13 +112,14 @@ builder_init_len :: proc(b: ^Builder, len: int, allocator := context.allocator)
Initializes a Builder with a specified length and cap
It replaces the existing `buf`
**Inputs**
Inputs:
- b: A pointer to the Builder
- len: The desired length of the Builder's buffer
- cap: The desired capacity of the Builder's buffer, actual max(len,cap)
- allocator: (default is context.allocator)
**Returns** A pointer to the initialized Builder
Returns:
A pointer to the initialized Builder
*/
builder_init_len_cap :: proc(b: ^Builder, len, cap: int, allocator := context.allocator) -> ^Builder {
b.buf = make([dynamic]byte, len, cap, allocator)
@@ -158,10 +165,11 @@ _builder_stream_vtable := &_builder_stream_vtable_obj
/*
Returns an io.Stream from a Builder
**Inputs**
Inputs:
- b: A pointer to the Builder
**Returns** An io.Stream
Returns:
An io.Stream
*/
to_stream :: proc(b: ^Builder) -> io.Stream {
return io.Stream{stream_vtable=_builder_stream_vtable, stream_data=b}
@@ -169,10 +177,11 @@ to_stream :: proc(b: ^Builder) -> io.Stream {
/*
Returns an io.Writer from a Builder
**Inputs**
Inputs:
- b: A pointer to the Builder
**Returns** An io.Writer
Returns:
An io.Writer
*/
to_writer :: proc(b: ^Builder) -> io.Writer {
return io.to_writer(to_stream(b))
@@ -180,7 +189,7 @@ to_writer :: proc(b: ^Builder) -> io.Writer {
/*
Deletes the Builder byte buffer content
**Inputs**
Inputs:
- b: A pointer to the Builder
*/
builder_destroy :: proc(b: ^Builder) {
@@ -190,7 +199,7 @@ builder_destroy :: proc(b: ^Builder) {
/*
Reserves the Builder byte buffer to a specific capacity, when it's higher than before
**Inputs**
Inputs:
- b: A pointer to the Builder
- cap: The desired capacity for the Builder's buffer
*/
@@ -200,7 +209,7 @@ builder_grow :: proc(b: ^Builder, cap: int) {
/*
Clears the Builder byte buffer content (sets len to zero)
**Inputs**
Inputs:
- b: A pointer to the Builder
*/
builder_reset :: proc(b: ^Builder) {
@@ -211,18 +220,23 @@ Creates a Builder from a slice of bytes with the same slice length as its capaci
*Uses Nil Allocator - Does NOT allocate*
**Inputs**
Inputs:
- backing: A slice of bytes to be used as the backing buffer
Returns:
A new Builder
Example:
import "core:fmt"
import "core:strings"
strings_builder_from_bytes_example :: proc() {
builder_from_bytes_example :: proc() {
bytes: [8]byte // <-- gets filled
builder := strings.builder_from_bytes(bytes[:])
fmt.println(strings.write_byte(&builder, 'a')) // -> "a"
fmt.println(strings.write_byte(&builder, 'b')) // -> "ab"
strings.write_byte(&builder, 'a')
fmt.println(strings.to_string(builder)) // -> "a"
strings.write_byte(&builder, 'b')
fmt.println(strings.to_string(builder)) // -> "ab"
}
Output:
@@ -230,7 +244,6 @@ Output:
a
ab
**Returns** A new Builder
*/
builder_from_bytes :: proc(backing: []byte) -> Builder {
s := transmute(runtime.Raw_Slice)backing
@@ -249,10 +262,11 @@ builder_from_slice :: builder_from_bytes
/*
Casts the Builder byte buffer to a string and returns it
**Inputs**
Inputs:
- b: A Builder
**Returns** The contents of the Builder's buffer, as a string
Returns:
The contents of the Builder's buffer, as a string
*/
to_string :: proc(b: Builder) -> string {
return string(b.buf[:])
@@ -260,10 +274,11 @@ to_string :: proc(b: Builder) -> string {
/*
Returns the length of the Builder's buffer, in bytes
**Inputs**
Inputs:
- b: A Builder
**Returns** The length of the Builder's buffer
Returns:
The length of the Builder's buffer
*/
builder_len :: proc(b: Builder) -> int {
return len(b.buf)
@@ -271,10 +286,11 @@ builder_len :: proc(b: Builder) -> int {
/*
Returns the capacity of the Builder's buffer, in bytes
**Inputs**
Inputs:
- b: A Builder
**Returns** The capacity of the Builder's buffer
Returns:
The capacity of the Builder's buffer
*/
builder_cap :: proc(b: Builder) -> int {
return cap(b.buf)
@@ -282,10 +298,11 @@ builder_cap :: proc(b: Builder) -> int {
/*
The free space left in the Builder's buffer, in bytes
**Inputs**
Inputs:
- b: A Builder
**Returns** The available space left in the Builder's buffer
Returns:
The available space left in the Builder's buffer
*/
builder_space :: proc(b: Builder) -> int {
return cap(b.buf) - len(b.buf)
@@ -293,16 +310,21 @@ builder_space :: proc(b: Builder) -> int {
/*
Appends a byte to the Builder and returns the number of bytes appended
**Inputs**
Inputs:
- b: A pointer to the Builder
- x: The byte to be appended
Returns:
The number of bytes appended
NOTE: The backing dynamic array may be fixed in capacity or fail to resize, `n` states the number actually written.
Example:
import "core:fmt"
import "core:strings"
strings_write_byte_example :: proc() {
write_byte_example :: proc() {
builder := strings.builder_make()
strings.write_byte(&builder, 'a') // 1
strings.write_byte(&builder, 'b') // 1
@@ -313,9 +335,6 @@ Output:
ab
NOTE: The backing dynamic array may be fixed in capacity or fail to resize, `n` states the number actually written.
**Returns** The number of bytes appended
*/
write_byte :: proc(b: ^Builder, x: byte) -> (n: int) {
n0 := len(b.buf)
@@ -326,7 +345,7 @@ write_byte :: proc(b: ^Builder, x: byte) -> (n: int) {
/*
Appends a slice of bytes to the Builder and returns the number of bytes appended
**Inputs**
Inputs:
- b: A pointer to the Builder
- x: The slice of bytes to be appended
@@ -335,7 +354,7 @@ Example:
import "core:fmt"
import "core:strings"
strings_write_bytes_example :: proc() {
write_bytes_example :: proc() {
builder := strings.builder_make()
bytes := [?]byte { 'a', 'b', 'c' }
strings.write_bytes(&builder, bytes[:]) // 3
@@ -344,7 +363,8 @@ Example:
NOTE: The backing dynamic array may be fixed in capacity or fail to resize, `n` states the number actually written.
**Returns** The number of bytes appended
Returns:
The number of bytes appended
*/
write_bytes :: proc(b: ^Builder, x: []byte) -> (n: int) {
n0 := len(b.buf)
@@ -355,16 +375,21 @@ write_bytes :: proc(b: ^Builder, x: []byte) -> (n: int) {
/*
Appends a single rune to the Builder and returns the number of bytes written and an `io.Error`
**Inputs**
Inputs:
- b: A pointer to the Builder
- r: The rune to be appended
Returns:
The number of bytes written and an io.Error (if any)
NOTE: The backing dynamic array may be fixed in capacity or fail to resize, `n` states the number actually written.
Example:
import "core:fmt"
import "core:strings"
strings_write_rune_example :: proc() {
write_rune_example :: proc() {
builder := strings.builder_make()
strings.write_rune(&builder, 'ä') // 2 None
strings.write_rune(&builder, 'b') // 1 None
@@ -375,9 +400,6 @@ Output:
äb
NOTE: The backing dynamic array may be fixed in capacity or fail to resize, `n` states the number actually written.
**Returns** The number of bytes written and an io.Error (if any)
*/
write_rune :: proc(b: ^Builder, r: rune) -> (int, io.Error) {
return io.write_rune(to_writer(b), r)
@@ -385,16 +407,21 @@ write_rune :: proc(b: ^Builder, r: rune) -> (int, io.Error) {
/*
Appends a quoted rune to the Builder and returns the number of bytes written
**Inputs**
Inputs:
- b: A pointer to the Builder
- r: The rune to be appended
Returns:
The number of bytes written
NOTE: The backing dynamic array may be fixed in capacity or fail to resize, `n` states the number actually written.
Example:
import "core:fmt"
import "core:strings"
strings_write_quoted_rune_example :: proc() {
write_quoted_rune_example :: proc() {
builder := strings.builder_make()
strings.write_string(&builder, "abc") // 3
strings.write_quoted_rune(&builder, 'ä') // 4
@@ -406,9 +433,6 @@ Output:
abc'ä'abc
NOTE: The backing dynamic array may be fixed in capacity or fail to resize, `n` states the number actually written.
**Returns** The number of bytes written
*/
write_quoted_rune :: proc(b: ^Builder, r: rune) -> (n: int) {
return io.write_quoted_rune(to_writer(b), r)
@@ -416,16 +440,21 @@ write_quoted_rune :: proc(b: ^Builder, r: rune) -> (n: int) {
/*
Appends a string to the Builder and returns the number of bytes written
**Inputs**
Inputs:
- b: A pointer to the Builder
- s: The string to be appended
Returns:
The number of bytes written
NOTE: The backing dynamic array may be fixed in capacity or fail to resize, `n` states the number actually written.
Example:
import "core:fmt"
import "core:strings"
strings_write_string_example :: proc() {
write_string_example :: proc() {
builder := strings.builder_make()
strings.write_string(&builder, "a") // 1
strings.write_string(&builder, "bc") // 2
@@ -436,9 +465,6 @@ Output:
abc
NOTE: The backing dynamic array may be fixed in capacity or fail to resize, `n` states the number actually written.
**Returns** The number of bytes written
*/
write_string :: proc(b: ^Builder, s: string) -> (n: int) {
n0 := len(b.buf)
@@ -449,10 +475,11 @@ write_string :: proc(b: ^Builder, s: string) -> (n: int) {
/*
Pops and returns the last byte in the Builder or 0 when the Builder is empty
**Inputs**
Inputs:
- b: A pointer to the Builder
**Returns** The last byte in the Builder or 0 if empty
Returns:
The last byte in the Builder or 0 if empty
*/
pop_byte :: proc(b: ^Builder) -> (r: byte) {
if len(b.buf) == 0 {
@@ -467,10 +494,11 @@ pop_byte :: proc(b: ^Builder) -> (r: byte) {
/*
Pops the last rune in the Builder and returns the popped rune and its rune width or (0, 0) if empty
**Inputs**
Inputs:
- b: A pointer to the Builder
**Returns** The popped rune and its rune width or (0, 0) if empty
Returns:
The popped rune and its rune width or (0, 0) if empty
*/
pop_rune :: proc(b: ^Builder) -> (r: rune, width: int) {
if len(b.buf) == 0 {
@@ -485,17 +513,22 @@ pop_rune :: proc(b: ^Builder) -> (r: rune, width: int) {
@(private)
DIGITS_LOWER := "0123456789abcdefx"
/*
**Inputs**
Inputs:
- b: A pointer to the Builder
- str: The string to be quoted and appended
- quote: The optional quote character (default is double quotes)
Returns:
The number of bytes written
NOTE: The backing dynamic array may be fixed in capacity or fail to resize, `n` states the number actually written.
Example:
import "core:fmt"
import "core:strings"
strings_write_quoted_string_example :: proc() {
write_quoted_string_example :: proc() {
builder := strings.builder_make()
strings.write_quoted_string(&builder, "a") // 3
strings.write_quoted_string(&builder, "bc", '\'') // 4
@@ -507,9 +540,6 @@ Output:
"a"'bc'"xyz"
NOTE: The backing dynamic array may be fixed in capacity or fail to resize, `n` states the number actually written.
**Returns** The number of bytes written
*/
write_quoted_string :: proc(b: ^Builder, str: string, quote: byte = '"') -> (n: int) {
n, _ = io.write_quoted_string(to_writer(b), str, quote)
@@ -518,11 +548,16 @@ write_quoted_string :: proc(b: ^Builder, str: string, quote: byte = '"') -> (n:
/*
Appends a rune to the Builder and returns the number of bytes written
**Inputs**
Inputs:
- b: A pointer to the Builder
- r: The rune to be appended
- write_quote: Optional boolean flag to wrap in single-quotes (') (default is true)
Returns:
The number of bytes written
NOTE: The backing dynamic array may be fixed in capacity or fail to resize, `n` states the number actually written.
Example:
import "core:fmt"
@@ -540,9 +575,6 @@ Output:
a'"'x
NOTE: The backing dynamic array may be fixed in capacity or fail to resize, `n` states the number actually written.
**Returns** The number of bytes written
*/
write_encoded_rune :: proc(b: ^Builder, r: rune, write_quote := true) -> (n: int) {
n, _ = io.write_encoded_rune(to_writer(b), r, write_quote)
@@ -552,7 +584,7 @@ write_encoded_rune :: proc(b: ^Builder, r: rune, write_quote := true) -> (n: int
/*
Appends an escaped rune to the Builder and returns the number of bytes written
**Inputs**
Inputs:
- b: A pointer to the Builder
- r: The rune to be appended
- quote: The quote character
@@ -565,7 +597,8 @@ Appends an escaped rune to the Builder and returns the number of bytes written
NOTE: The backing dynamic array may be fixed in capacity or fail to resize, `n` states the number actually written.
**Returns** The number of bytes written
Returns:
The number of bytes written
*/
write_escaped_rune :: proc(b: ^Builder, r: rune, quote: byte, html_safe := false) -> (n: int) {
n, _ = io.write_escaped_rune(to_writer(b), r, quote, html_safe)
@@ -574,7 +607,7 @@ write_escaped_rune :: proc(b: ^Builder, r: rune, quote: byte, html_safe := false
/*
Writes a f64 value to the Builder and returns the number of characters written
**Inputs**
Inputs:
- b: A pointer to the Builder
- f: The f64 value to be appended
- fmt: The format byte
@@ -584,7 +617,8 @@ Writes a f64 value to the Builder and returns the number of characters written
NOTE: The backing dynamic array may be fixed in capacity or fail to resize, `n` states the number actually written.
**Returns** The number of characters written
Returns:
The number of characters written
*/
write_float :: proc(b: ^Builder, f: f64, fmt: byte, prec, bit_size: int, always_signed := false) -> (n: int) {
buf: [384]byte
@@ -599,7 +633,7 @@ write_float :: proc(b: ^Builder, f: f64, fmt: byte, prec, bit_size: int, always_
/*
Writes a f16 value to the Builder and returns the number of characters written
**Inputs**
Inputs:
- b: A pointer to the Builder
- f: The f16 value to be appended
- fmt: The format byte
@@ -607,7 +641,8 @@ Writes a f16 value to the Builder and returns the number of characters written
NOTE: The backing dynamic array may be fixed in capacity or fail to resize, `n` states the number actually written.
**Returns** The number of characters written
Returns:
The number of characters written
*/
write_f16 :: proc(b: ^Builder, f: f16, fmt: byte, always_signed := false) -> (n: int) {
buf: [384]byte
@@ -620,18 +655,23 @@ write_f16 :: proc(b: ^Builder, f: f16, fmt: byte, always_signed := false) -> (n:
/*
Writes a f32 value to the Builder and returns the number of characters written
**Inputs**
Inputs:
- b: A pointer to the Builder
- f: The f32 value to be appended
- fmt: The format byte
- always_signed: Optional boolean flag to always include the sign
Returns:
The number of characters written
NOTE: The backing dynamic array may be fixed in capacity or fail to resize, `n` states the number actually written.
Example:
import "core:fmt"
import "core:strings"
strings_write_f32_example :: proc() {
write_f32_example :: proc() {
builder := strings.builder_make()
strings.write_f32(&builder, 3.14159, 'f') // 6
strings.write_string(&builder, " - ") // 3
@@ -643,9 +683,6 @@ Output:
3.14159012 - -1.23000003e-01
NOTE: The backing dynamic array may be fixed in capacity or fail to resize, `n` states the number actually written.
**Returns** The number of characters written
*/
write_f32 :: proc(b: ^Builder, f: f32, fmt: byte, always_signed := false) -> (n: int) {
buf: [384]byte
@@ -658,7 +695,7 @@ write_f32 :: proc(b: ^Builder, f: f32, fmt: byte, always_signed := false) -> (n:
/*
Writes a f32 value to the Builder and returns the number of characters written
**Inputs**
Inputs:
- b: A pointer to the Builder
- f: The f32 value to be appended
- fmt: The format byte
@@ -666,7 +703,8 @@ Writes a f32 value to the Builder and returns the number of characters written
NOTE: The backing dynamic array may be fixed in capacity or fail to resize, `n` states the number actually written.
**Returns** The number of characters written
Returns:
The number of characters written
*/
write_f64 :: proc(b: ^Builder, f: f64, fmt: byte, always_signed := false) -> (n: int) {
buf: [384]byte
@@ -679,14 +717,15 @@ write_f64 :: proc(b: ^Builder, f: f64, fmt: byte, always_signed := false) -> (n:
/*
Writes a u64 value to the Builder and returns the number of characters written
**Inputs**
Inputs:
- b: A pointer to the Builder
- i: The u64 value to be appended
- base: The optional base for the numeric representation
NOTE: The backing dynamic array may be fixed in capacity or fail to resize, `n` states the number actually written.
**Returns** The number of characters written
Returns:
The number of characters written
*/
write_u64 :: proc(b: ^Builder, i: u64, base: int = 10) -> (n: int) {
buf: [32]byte
@@ -696,14 +735,15 @@ write_u64 :: proc(b: ^Builder, i: u64, base: int = 10) -> (n: int) {
/*
Writes a i64 value to the Builder and returns the number of characters written
**Inputs**
Inputs:
- b: A pointer to the Builder
- i: The i64 value to be appended
- base: The optional base for the numeric representation
NOTE: The backing dynamic array may be fixed in capacity or fail to resize, `n` states the number actually written.
**Returns** The number of characters written
Returns:
The number of characters written
*/
write_i64 :: proc(b: ^Builder, i: i64, base: int = 10) -> (n: int) {
buf: [32]byte
@@ -713,14 +753,15 @@ write_i64 :: proc(b: ^Builder, i: i64, base: int = 10) -> (n: int) {
/*
Writes a uint value to the Builder and returns the number of characters written
**Inputs**
Inputs:
- b: A pointer to the Builder
- i: The uint value to be appended
- base: The optional base for the numeric representation
NOTE: The backing dynamic array may be fixed in capacity or fail to resize, `n` states the number actually written.
**Returns** The number of characters written
Returns:
The number of characters written
*/
write_uint :: proc(b: ^Builder, i: uint, base: int = 10) -> (n: int) {
return write_u64(b, u64(i), base)
@@ -728,14 +769,15 @@ write_uint :: proc(b: ^Builder, i: uint, base: int = 10) -> (n: int) {
/*
Writes a int value to the Builder and returns the number of characters written
**Inputs**
Inputs:
- b: A pointer to the Builder
- i: The int value to be appended
- base: The optional base for the numeric representation
NOTE: The backing dynamic array may be fixed in capacity or fail to resize, `n` states the number actually written.
**Returns** The number of characters written
Returns:
The number of characters written
*/
write_int :: proc(b: ^Builder, i: int, base: int = 10) -> (n: int) {
return write_i64(b, i64(i), base)

View File

@@ -9,14 +9,15 @@ Converts invalid UTF-8 sequences in the input string `s` to the `replacement` st
*Allocates Using Provided Allocator*
**Inputs**
Inputs:
- s: Input string that may contain invalid UTF-8 sequences.
- replacement: String to replace invalid UTF-8 sequences with.
- allocator: (default: context.allocator).
WARNING: Allocation does not occur when len(s) == 0
**Returns** A valid UTF-8 string with invalid sequences replaced by `replacement`.
Returns:
A valid UTF-8 string with invalid sequences replaced by `replacement`.
*/
to_valid_utf8 :: proc(s, replacement: string, allocator := context.allocator) -> string {
if len(s) == 0 {
@@ -76,16 +77,19 @@ Converts the input string `s` to all lowercase characters.
*Allocates Using Provided Allocator*
**Inputs**
Inputs:
- s: Input string to be converted.
- allocator: (default: context.allocator).
Returns:
A new string with all characters converted to lowercase.
Example:
import "core:fmt"
import "core:strings"
strings_to_lower_example :: proc() {
to_lower_example :: proc() {
fmt.println(strings.to_lower("TeST"))
}
@@ -93,7 +97,6 @@ Output:
test
**Returns** A new string with all characters converted to lowercase.
*/
to_lower :: proc(s: string, allocator := context.allocator) -> string {
b: Builder
@@ -108,16 +111,19 @@ Converts the input string `s` to all uppercase characters.
*Allocates Using Provided Allocator*
**Inputs**
Inputs:
- s: Input string to be converted.
- allocator: (default: context.allocator).
Returns:
A new string with all characters converted to uppercase.
Example:
import "core:fmt"
import "core:strings"
strings_to_upper_example :: proc() {
to_upper_example :: proc() {
fmt.println(strings.to_upper("Test"))
}
@@ -125,7 +131,6 @@ Output:
TEST
**Returns** A new string with all characters converted to uppercase.
*/
to_upper :: proc(s: string, allocator := context.allocator) -> string {
b: Builder
@@ -138,10 +143,11 @@ to_upper :: proc(s: string, allocator := context.allocator) -> string {
/*
Checks if the rune `r` is a delimiter (' ', '-', or '_').
**Inputs**
Inputs:
- r: Rune to check for delimiter status.
**Returns** True if `r` is a delimiter, false otherwise.
Returns:
True if `r` is a delimiter, false otherwise.
*/
is_delimiter :: proc(r: rune) -> bool {
return r == '-' || r == '_' || is_space(r)
@@ -149,10 +155,11 @@ is_delimiter :: proc(r: rune) -> bool {
/*
Checks if the rune `r` is a non-alphanumeric or space character.
**Inputs**
Inputs:
- r: Rune to check for separator status.
**Returns** True if `r` is a non-alpha or `unicode.is_space` rune.
Returns:
True if `r` is a non-alpha or `unicode.is_space` rune.
*/
is_separator :: proc(r: rune) -> bool {
if r <= 0x7f {
@@ -179,7 +186,7 @@ is_separator :: proc(r: rune) -> bool {
/*
Iterates over a string, calling a callback for each rune with the previous, current, and next runes as arguments.
**Inputs**
Inputs:
- w: An io.Writer to be used by the callback for writing output.
- s: The input string to be iterated over.
- callback: A procedure to be called for each rune in the string, with arguments (w: io.Writer, prev, curr, next: rune).
@@ -191,7 +198,7 @@ Example:
import "core:strings"
import "core:io"
strings_string_case_iterator_example :: proc() {
string_case_iterator_example :: proc() {
my_callback :: proc(w: io.Writer, prev, curr, next: rune) {
fmt.println("my_callback", curr) // <-- Custom logic here
}
@@ -241,11 +248,12 @@ Converts the input string `s` to "lowerCamelCase".
*Allocates Using Provided Allocator*
**Inputs**
Inputs:
- s: Input string to be converted.
- allocator: (default: context.allocator).
**Returns** A "lowerCamelCase" formatted string.
Returns:
A "lowerCamelCase" formatted string.
*/
to_camel_case :: proc(s: string, allocator := context.allocator) -> string {
s := s
@@ -275,11 +283,12 @@ Converts the input string `s` to "UpperCamelCase" (PascalCase).
*Allocates Using Provided Allocator*
**Inputs**
Inputs:
- s: Input string to be converted.
- allocator: (default: context.allocator).
**Returns** A "PascalCase" formatted string.
Returns:
A "PascalCase" formatted string.
*/
to_pascal_case :: proc(s: string, allocator := context.allocator) -> string {
s := s
@@ -307,18 +316,21 @@ Returns a string converted to a delimiter-separated case with configurable casin
*Allocates Using Provided Allocator*
**Inputs**
Inputs:
- s: The input string to be converted
- delimiter: The rune to be used as the delimiter between words
- all_upper_case: A boolean indicating if the output should be all uppercased (true) or lowercased (false)
- allocator: (default: context.allocator).
Returns:
The converted string
Example:
import "core:fmt"
import "core:strings"
strings_to_delimiter_case_example :: proc() {
to_delimiter_case_example :: proc() {
fmt.println(strings.to_delimiter_case("Hello World", '_', false))
fmt.println(strings.to_delimiter_case("Hello World", ' ', true))
fmt.println(strings.to_delimiter_case("aBC", '_', false))
@@ -328,9 +340,8 @@ Output:
hello_world
HELLO WORLD
a_b_c
a_bc
**Returns** The converted string
*/
to_delimiter_case :: proc(
s: string,
@@ -380,16 +391,19 @@ Converts a string to "snake_case" with all runes lowercased
*Allocates Using Provided Allocator*
**Inputs**
Inputs:
- s: The input string to be converted
- allocator: (default: context.allocator).
Returns:
The converted string
Example:
import "core:fmt"
import "core:strings"
strings_to_snake_case_example :: proc() {
to_snake_case_example :: proc() {
fmt.println(strings.to_snake_case("HelloWorld"))
fmt.println(strings.to_snake_case("Hello World"))
}
@@ -399,8 +413,6 @@ Output:
hello_world
hello_world
```
**Returns** The converted string
*/
to_snake_case :: proc(s: string, allocator := context.allocator) -> string {
return to_delimiter_case(s, '_', false, allocator)
@@ -412,16 +424,19 @@ Converts a string to "SNAKE_CASE" with all runes uppercased
*Allocates Using Provided Allocator*
**Inputs**
Inputs:
- s: The input string to be converted
- allocator: (default: context.allocator).
Returns:
The converted string
Example:
import "core:fmt"
import "core:strings"
strings_to_upper_snake_case_example :: proc() {
to_upper_snake_case_example :: proc() {
fmt.println(strings.to_upper_snake_case("HelloWorld"))
}
@@ -429,7 +444,6 @@ Output:
HELLO_WORLD
**Returns** The converted string
*/
to_upper_snake_case :: proc(s: string, allocator := context.allocator) -> string {
return to_delimiter_case(s, '_', true, allocator)
@@ -439,16 +453,19 @@ Converts a string to "kebab-case" with all runes lowercased
*Allocates Using Provided Allocator*
**Inputs**
Inputs:
- s: The input string to be converted
- allocator: (default: context.allocator).
Returns:
The converted string
Example:
import "core:fmt"
import "core:strings"
strings_to_kebab_case_example :: proc() {
to_kebab_case_example :: proc() {
fmt.println(strings.to_kebab_case("HelloWorld"))
}
@@ -456,7 +473,6 @@ Output:
hello-world
**Returns** The converted string
*/
to_kebab_case :: proc(s: string, allocator := context.allocator) -> string {
return to_delimiter_case(s, '-', false, allocator)
@@ -466,16 +482,19 @@ Converts a string to "KEBAB-CASE" with all runes uppercased
*Allocates Using Provided Allocator*
**Inputs**
Inputs:
- s: The input string to be converted
- allocator: (default: context.allocator).
Returns:
The converted string
Example:
import "core:fmt"
import "core:strings"
strings_to_upper_kebab_case_example :: proc() {
to_upper_kebab_case_example :: proc() {
fmt.println(strings.to_upper_kebab_case("HelloWorld"))
}
@@ -483,7 +502,6 @@ Output:
HELLO-WORLD
**Returns** The converted string
*/
to_upper_kebab_case :: proc(s: string, allocator := context.allocator) -> string {
return to_delimiter_case(s, '-', true, allocator)
@@ -493,16 +511,19 @@ Converts a string to "Ada_Case"
*Allocates Using Provided Allocator*
**Inputs**
Inputs:
- s: The input string to be converted
- allocator: (default: context.allocator).
Returns:
The converted string
Example:
import "core:fmt"
import "core:strings"
strings_to_upper_kebab_case_example :: proc() {
to_ada_case_example :: proc() {
fmt.println(strings.to_ada_case("HelloWorld"))
}
@@ -510,7 +531,6 @@ Output:
Hello_World
**Returns** The converted string
*/
to_ada_case :: proc(s: string, allocator := context.allocator) -> string {
s := s

View File

@@ -25,7 +25,7 @@ Initializes the entries map and sets the allocator for the string entries
*Allocates Using Provided Allocators*
**Inputs**
Inputs:
- m: A pointer to the Intern struct to be initialized
- allocator: The allocator for the Intern_Entry strings (Default: context.allocator)
- map_allocator: The allocator for the map of entries (Default: context.allocator)
@@ -37,7 +37,7 @@ intern_init :: proc(m: ^Intern, allocator := context.allocator, map_allocator :=
/*
Frees the map and all its content allocated using the `.allocator`.
**Inputs**
Inputs:
- m: A pointer to the Intern struct to be destroyed
*/
intern_destroy :: proc(m: ^Intern) {
@@ -51,13 +51,14 @@ Returns an interned copy of the given text, adding it to the map if not already
*Allocate using the Intern's Allocator (First time string is seen only)*
**Inputs**
Inputs:
- m: A pointer to the Intern struct
- text: The string to be interned
NOTE: The returned string lives as long as the map entry lives.
**Returns** The interned string and an allocator error if any
Returns:
The interned string and an allocator error if any
*/
intern_get :: proc(m: ^Intern, text: string) -> (str: string, err: runtime.Allocator_Error) {
entry := _intern_get_entry(m, text) or_return
@@ -68,13 +69,14 @@ Returns an interned copy of the given text as a cstring, adding it to the map if
*Allocate using the Intern's Allocator (First time string is seen only)*
**Inputs**
Inputs:
- m: A pointer to the Intern struct
- text: The string to be interned
NOTE: The returned cstring lives as long as the map entry lives
**Returns** The interned cstring and an allocator error if any
Returns:
The interned cstring and an allocator error if any
*/
intern_get_cstring :: proc(m: ^Intern, text: string) -> (str: cstring, err: runtime.Allocator_Error) {
entry := _intern_get_entry(m, text) or_return
@@ -86,11 +88,12 @@ Sets and allocates the entry if it wasn't set yet
*Allocate using the Intern's Allocator (First time string is seen only)*
**Inputs**
Inputs:
- m: A pointer to the Intern struct
- text: The string to be looked up or interned
**Returns** The new or existing interned entry and an allocator error if any
Returns:
The new or existing interned entry and an allocator error if any
*/
_intern_get_entry :: proc(m: ^Intern, text: string) -> (new_entry: ^Intern_Entry, err: runtime.Allocator_Error) #no_bounds_check {
if prev, ok := m.entries[text]; ok {

View File

@@ -16,7 +16,7 @@ Reader :: struct {
/*
Initializes a string Reader with the provided string
**Inputs**
Inputs:
- r: A pointer to a Reader struct
- s: The input string to be read
*/
@@ -28,10 +28,11 @@ reader_init :: proc(r: ^Reader, s: string) {
/*
Converts a Reader into an `io.Stream`
**Inputs**
Inputs:
- r: A pointer to a Reader struct
**Returns** An io.Stream for the given Reader
Returns:
An io.Stream for the given Reader
*/
reader_to_stream :: proc(r: ^Reader) -> (s: io.Stream) {
s.stream_data = r
@@ -41,11 +42,12 @@ reader_to_stream :: proc(r: ^Reader) -> (s: io.Stream) {
/*
Initializes a string Reader and returns an `io.Reader` for the given string
**Inputs**
Inputs:
- r: A pointer to a Reader struct
- s: The input string to be read
**Returns** An io.Reader for the given string
Returns:
An io.Reader for the given string
*/
to_reader :: proc(r: ^Reader, s: string) -> io.Reader {
reader_init(r, s)
@@ -55,11 +57,12 @@ to_reader :: proc(r: ^Reader, s: string) -> io.Reader {
/*
Initializes a string Reader and returns an `io.Reader_At` for the given string
**Inputs**
Inputs:
- r: A pointer to a Reader struct
- s: The input string to be read
**Returns** An `io.Reader_At` for the given string
Returns:
An `io.Reader_At` for the given string
*/
to_reader_at :: proc(r: ^Reader, s: string) -> io.Reader_At {
reader_init(r, s)
@@ -69,10 +72,11 @@ to_reader_at :: proc(r: ^Reader, s: string) -> io.Reader_At {
/*
Returns the remaining length of the Reader
**Inputs**
Inputs:
- r: A pointer to a Reader struct
**Returns** The remaining length of the Reader
Returns:
The remaining length of the Reader
*/
reader_length :: proc(r: ^Reader) -> int {
if r.i >= i64(len(r.s)) {
@@ -83,10 +87,11 @@ reader_length :: proc(r: ^Reader) -> int {
/*
Returns the length of the string stored in the Reader
**Inputs**
Inputs:
- r: A pointer to a Reader struct
**Returns** The length of the string stored in the Reader
Returns:
The length of the string stored in the Reader
*/
reader_size :: proc(r: ^Reader) -> i64 {
return i64(len(r.s))
@@ -94,11 +99,11 @@ reader_size :: proc(r: ^Reader) -> i64 {
/*
Reads len(p) bytes from the Reader's string and copies into the provided slice.
**Inputs**
Inputs:
- r: A pointer to a Reader struct
- p: A byte slice to copy data into
**Returns**
Returns:
- n: The number of bytes read
- err: An `io.Error` if an error occurs while reading, including `.EOF`, otherwise `nil` denotes success.
*/
@@ -114,12 +119,12 @@ reader_read :: proc(r: ^Reader, p: []byte) -> (n: int, err: io.Error) {
/*
Reads len(p) bytes from the Reader's string and copies into the provided slice, at the specified offset from the current index.
**Inputs**
Inputs:
- r: A pointer to a Reader struct
- p: A byte slice to copy data into
- off: The offset from which to read
**Returns**
Returns:
- n: The number of bytes read
- err: An `io.Error` if an error occurs while reading, including `.EOF`, otherwise `nil` denotes success.
*/
@@ -139,10 +144,10 @@ reader_read_at :: proc(r: ^Reader, p: []byte, off: i64) -> (n: int, err: io.Erro
/*
Reads and returns a single byte from the Reader's string
**Inputs**
Inputs:
- r: A pointer to a Reader struct
**Returns**
Returns:
- The byte read from the Reader
- err: An `io.Error` if an error occurs while reading, including `.EOF`, otherwise `nil` denotes success.
*/
@@ -158,10 +163,11 @@ reader_read_byte :: proc(r: ^Reader) -> (byte, io.Error) {
/*
Decrements the Reader's index (i) by 1
**Inputs**
Inputs:
- r: A pointer to a Reader struct
**Returns** An `io.Error` if `r.i <= 0` (`.Invalid_Unread`), otherwise `nil` denotes success.
Returns:
An `io.Error` if `r.i <= 0` (`.Invalid_Unread`), otherwise `nil` denotes success.
*/
reader_unread_byte :: proc(r: ^Reader) -> io.Error {
if r.i <= 0 {
@@ -174,10 +180,10 @@ reader_unread_byte :: proc(r: ^Reader) -> io.Error {
/*
Reads and returns a single rune and its `size` from the Reader's string
**Inputs**
Inputs:
- r: A pointer to a Reader struct
**Returns**
Returns:
- rr: The rune read from the Reader
- size: The size of the rune in bytes
- err: An `io.Error` if an error occurs while reading
@@ -199,12 +205,13 @@ reader_read_rune :: proc(r: ^Reader) -> (rr: rune, size: int, err: io.Error) {
/*
Decrements the Reader's index (i) by the size of the last read rune
**Inputs**
Inputs:
- r: A pointer to a Reader struct
WARNING: May only be used once and after a valid `read_rune` call
**Returns** An `io.Error` if an error occurs while unreading (`.Invalid_Unread`), else `nil` denotes success.
Returns:
An `io.Error` if an error occurs while unreading (`.Invalid_Unread`), else `nil` denotes success.
*/
reader_unread_rune :: proc(r: ^Reader) -> io.Error {
if r.i <= 0 {
@@ -220,12 +227,12 @@ reader_unread_rune :: proc(r: ^Reader) -> io.Error {
/*
Seeks the Reader's index to a new position
**Inputs**
Inputs:
- r: A pointer to a Reader struct
- offset: The new offset position
- whence: The reference point for the new position (`.Start`, `.Current`, or `.End`)
**Returns**
Returns:
- The absolute offset after seeking
- err: An `io.Error` if an error occurs while seeking (`.Invalid_Whence`, `.Invalid_Offset`)
*/
@@ -252,13 +259,13 @@ reader_seek :: proc(r: ^Reader, offset: i64, whence: io.Seek_From) -> (i64, io.E
/*
Writes the remaining content of the Reader's string into the provided `io.Writer`
**Inputs**
Inputs:
- r: A pointer to a Reader struct
- w: The io.Writer to write the remaining content into
WARNING: Panics if writer writes more bytes than remainig length of string.
**Returns**
Returns:
- n: The number of bytes written
- err: An io.Error if an error occurs while writing (`.Short_Write`)
*/

File diff suppressed because it is too large Load Diff

View File

@@ -145,8 +145,6 @@ PCONDITION_VARIABLE :: ^CONDITION_VARIABLE
PLARGE_INTEGER :: ^LARGE_INTEGER
PSRWLOCK :: ^SRWLOCK
MMRESULT :: UINT
CREATE_WAITABLE_TIMER_MANUAL_RESET :: 0x00000001
CREATE_WAITABLE_TIMER_HIGH_RESOLUTION :: 0x00000002
@@ -261,26 +259,6 @@ GET_FILEEX_INFO_LEVELS :: distinct i32
GetFileExInfoStandard: GET_FILEEX_INFO_LEVELS : 0
GetFileExMaxInfoLevel: GET_FILEEX_INFO_LEVELS : 1
// String resource number bases (internal use)
MMSYSERR_BASE :: 0
WAVERR_BASE :: 32
MIDIERR_BASE :: 64
TIMERR_BASE :: 96
JOYERR_BASE :: 160
MCIERR_BASE :: 256
MIXERR_BASE :: 1024
MCI_STRING_OFFSET :: 512
MCI_VD_OFFSET :: 1024
MCI_CD_OFFSET :: 1088
MCI_WAVE_OFFSET :: 1152
MCI_SEQ_OFFSET :: 1216
// timer error return values
TIMERR_NOERROR :: 0 // no error
TIMERR_NOCANDO :: TIMERR_BASE + 1 // request not completed
TIMERR_STRUCT :: TIMERR_BASE + 33 // time struct size
DIAGNOSTIC_REASON_VERSION :: 0

View File

@@ -3,9 +3,170 @@ package sys_windows
foreign import winmm "system:Winmm.lib"
MMRESULT :: UINT
@(default_calling_convention="stdcall")
foreign winmm {
timeGetDevCaps :: proc(ptc: LPTIMECAPS, cbtc: UINT) -> MMRESULT ---
timeBeginPeriod :: proc(uPeriod: UINT) -> MMRESULT ---
timeEndPeriod :: proc(uPeriod: UINT) -> MMRESULT ---
timeGetTime :: proc() -> DWORD ---
}
LPTIMECAPS :: ^TIMECAPS
TIMECAPS :: struct {
wPeriodMin: UINT,
wPeriodMax: UINT,
}
// String resource number bases (internal use)
MMSYSERR_BASE :: 0
WAVERR_BASE :: 32
MIDIERR_BASE :: 64
TIMERR_BASE :: 96
JOYERR_BASE :: 160
MCIERR_BASE :: 256
MIXERR_BASE :: 1024
MCI_STRING_OFFSET :: 512
MCI_VD_OFFSET :: 1024
MCI_CD_OFFSET :: 1088
MCI_WAVE_OFFSET :: 1152
MCI_SEQ_OFFSET :: 1216
/* general error return values */
MMSYSERR_NOERROR :: 0 /* no error */
MMSYSERR_ERROR :: MMSYSERR_BASE + 1 /* unspecified error */
MMSYSERR_BADDEVICEID :: MMSYSERR_BASE + 2 /* device ID out of range */
MMSYSERR_NOTENABLED :: MMSYSERR_BASE + 3 /* driver failed enable */
MMSYSERR_ALLOCATED :: MMSYSERR_BASE + 4 /* device already allocated */
MMSYSERR_INVALHANDLE :: MMSYSERR_BASE + 5 /* device handle is invalid */
MMSYSERR_NODRIVER :: MMSYSERR_BASE + 6 /* no device driver present */
MMSYSERR_NOMEM :: MMSYSERR_BASE + 7 /* memory allocation error */
MMSYSERR_NOTSUPPORTED :: MMSYSERR_BASE + 8 /* function isn't supported */
MMSYSERR_BADERRNUM :: MMSYSERR_BASE + 9 /* error value out of range */
MMSYSERR_INVALFLAG :: MMSYSERR_BASE + 10 /* invalid flag passed */
MMSYSERR_INVALPARAM :: MMSYSERR_BASE + 11 /* invalid parameter passed */
MMSYSERR_HANDLEBUSY :: MMSYSERR_BASE + 12 /* handle being used simultaneously on another thread (eg callback) */
MMSYSERR_INVALIDALIAS :: MMSYSERR_BASE + 13 /* specified alias not found */
MMSYSERR_BADDB :: MMSYSERR_BASE + 14 /* bad registry database */
MMSYSERR_KEYNOTFOUND :: MMSYSERR_BASE + 15 /* registry key not found */
MMSYSERR_READERROR :: MMSYSERR_BASE + 16 /* registry read error */
MMSYSERR_WRITEERROR :: MMSYSERR_BASE + 17 /* registry write error */
MMSYSERR_DELETEERROR :: MMSYSERR_BASE + 18 /* registry delete error */
MMSYSERR_VALNOTFOUND :: MMSYSERR_BASE + 19 /* registry value not found */
MMSYSERR_NODRIVERCB :: MMSYSERR_BASE + 20 /* driver does not call DriverCallback */
MMSYSERR_MOREDATA :: MMSYSERR_BASE + 21 /* more data to be returned */
MMSYSERR_LASTERROR :: MMSYSERR_BASE + 21 /* last error in range */
/* waveform audio error return values */
WAVERR_BADFORMAT :: WAVERR_BASE + 0 /* unsupported wave format */
WAVERR_STILLPLAYING :: WAVERR_BASE + 1 /* still something playing */
WAVERR_UNPREPARED :: WAVERR_BASE + 2 /* header not prepared */
WAVERR_SYNC :: WAVERR_BASE + 3 /* device is synchronous */
WAVERR_LASTERROR :: WAVERR_BASE + 3 /* last error in range */
/* MIDI error return values */
MIDIERR_UNPREPARED :: MIDIERR_BASE + 0 /* header not prepared */
MIDIERR_STILLPLAYING :: MIDIERR_BASE + 1 /* still something playing */
MIDIERR_NOMAP :: MIDIERR_BASE + 2 /* no configured instruments */
MIDIERR_NOTREADY :: MIDIERR_BASE + 3 /* hardware is still busy */
MIDIERR_NODEVICE :: MIDIERR_BASE + 4 /* port no longer connected */
MIDIERR_INVALIDSETUP :: MIDIERR_BASE + 5 /* invalid MIF */
MIDIERR_BADOPENMODE :: MIDIERR_BASE + 6 /* operation unsupported w/ open mode */
MIDIERR_DONT_CONTINUE :: MIDIERR_BASE + 7 /* thru device 'eating' a message */
MIDIERR_LASTERROR :: MIDIERR_BASE + 7 /* last error in range */
/* timer error return values */
TIMERR_NOERROR :: 0 /* no error */
TIMERR_NOCANDO :: TIMERR_BASE + 1 /* request not completed */
TIMERR_STRUCT :: TIMERR_BASE + 33 /* time struct size */
/* joystick error return values */
JOYERR_NOERROR :: 0 /* no error */
JOYERR_PARMS :: JOYERR_BASE + 5 /* bad parameters */
JOYERR_NOCANDO :: JOYERR_BASE + 6 /* request not completed */
JOYERR_UNPLUGGED :: JOYERR_BASE + 7 /* joystick is unplugged */
/* MCI error return values */
MCIERR_INVALID_DEVICE_ID :: MCIERR_BASE + 1
MCIERR_UNRECOGNIZED_KEYWORD :: MCIERR_BASE + 3
MCIERR_UNRECOGNIZED_COMMAND :: MCIERR_BASE + 5
MCIERR_HARDWARE :: MCIERR_BASE + 6
MCIERR_INVALID_DEVICE_NAME :: MCIERR_BASE + 7
MCIERR_OUT_OF_MEMORY :: MCIERR_BASE + 8
MCIERR_DEVICE_OPEN :: MCIERR_BASE + 9
MCIERR_CANNOT_LOAD_DRIVER :: MCIERR_BASE + 10
MCIERR_MISSING_COMMAND_STRING :: MCIERR_BASE + 11
MCIERR_PARAM_OVERFLOW :: MCIERR_BASE + 12
MCIERR_MISSING_STRING_ARGUMENT :: MCIERR_BASE + 13
MCIERR_BAD_INTEGER :: MCIERR_BASE + 14
MCIERR_PARSER_INTERNAL :: MCIERR_BASE + 15
MCIERR_DRIVER_INTERNAL :: MCIERR_BASE + 16
MCIERR_MISSING_PARAMETER :: MCIERR_BASE + 17
MCIERR_UNSUPPORTED_FUNCTION :: MCIERR_BASE + 18
MCIERR_FILE_NOT_FOUND :: MCIERR_BASE + 19
MCIERR_DEVICE_NOT_READY :: MCIERR_BASE + 20
MCIERR_INTERNAL :: MCIERR_BASE + 21
MCIERR_DRIVER :: MCIERR_BASE + 22
MCIERR_CANNOT_USE_ALL :: MCIERR_BASE + 23
MCIERR_MULTIPLE :: MCIERR_BASE + 24
MCIERR_EXTENSION_NOT_FOUND :: MCIERR_BASE + 25
MCIERR_OUTOFRANGE :: MCIERR_BASE + 26
MCIERR_FLAGS_NOT_COMPATIBLE :: MCIERR_BASE + 28
MCIERR_FILE_NOT_SAVED :: MCIERR_BASE + 30
MCIERR_DEVICE_TYPE_REQUIRED :: MCIERR_BASE + 31
MCIERR_DEVICE_LOCKED :: MCIERR_BASE + 32
MCIERR_DUPLICATE_ALIAS :: MCIERR_BASE + 33
MCIERR_BAD_CONSTANT :: MCIERR_BASE + 34
MCIERR_MUST_USE_SHAREABLE :: MCIERR_BASE + 35
MCIERR_MISSING_DEVICE_NAME :: MCIERR_BASE + 36
MCIERR_BAD_TIME_FORMAT :: MCIERR_BASE + 37
MCIERR_NO_CLOSING_QUOTE :: MCIERR_BASE + 38
MCIERR_DUPLICATE_FLAGS :: MCIERR_BASE + 39
MCIERR_INVALID_FILE :: MCIERR_BASE + 40
MCIERR_NULL_PARAMETER_BLOCK :: MCIERR_BASE + 41
MCIERR_UNNAMED_RESOURCE :: MCIERR_BASE + 42
MCIERR_NEW_REQUIRES_ALIAS :: MCIERR_BASE + 43
MCIERR_NOTIFY_ON_AUTO_OPEN :: MCIERR_BASE + 44
MCIERR_NO_ELEMENT_ALLOWED :: MCIERR_BASE + 45
MCIERR_NONAPPLICABLE_FUNCTION :: MCIERR_BASE + 46
MCIERR_ILLEGAL_FOR_AUTO_OPEN :: MCIERR_BASE + 47
MCIERR_FILENAME_REQUIRED :: MCIERR_BASE + 48
MCIERR_EXTRA_CHARACTERS :: MCIERR_BASE + 49
MCIERR_DEVICE_NOT_INSTALLED :: MCIERR_BASE + 50
MCIERR_GET_CD :: MCIERR_BASE + 51
MCIERR_SET_CD :: MCIERR_BASE + 52
MCIERR_SET_DRIVE :: MCIERR_BASE + 53
MCIERR_DEVICE_LENGTH :: MCIERR_BASE + 54
MCIERR_DEVICE_ORD_LENGTH :: MCIERR_BASE + 55
MCIERR_NO_INTEGER :: MCIERR_BASE + 56
MCIERR_WAVE_OUTPUTSINUSE :: MCIERR_BASE + 64
MCIERR_WAVE_SETOUTPUTINUSE :: MCIERR_BASE + 65
MCIERR_WAVE_INPUTSINUSE :: MCIERR_BASE + 66
MCIERR_WAVE_SETINPUTINUSE :: MCIERR_BASE + 67
MCIERR_WAVE_OUTPUTUNSPECIFIED :: MCIERR_BASE + 68
MCIERR_WAVE_INPUTUNSPECIFIED :: MCIERR_BASE + 69
MCIERR_WAVE_OUTPUTSUNSUITABLE :: MCIERR_BASE + 70
MCIERR_WAVE_SETOUTPUTUNSUITABLE :: MCIERR_BASE + 71
MCIERR_WAVE_INPUTSUNSUITABLE :: MCIERR_BASE + 72
MCIERR_WAVE_SETINPUTUNSUITABLE :: MCIERR_BASE + 73
MCIERR_SEQ_DIV_INCOMPATIBLE :: MCIERR_BASE + 80
MCIERR_SEQ_PORT_INUSE :: MCIERR_BASE + 81
MCIERR_SEQ_PORT_NONEXISTENT :: MCIERR_BASE + 82
MCIERR_SEQ_PORT_MAPNODEVICE :: MCIERR_BASE + 83
MCIERR_SEQ_PORT_MISCERROR :: MCIERR_BASE + 84
MCIERR_SEQ_TIMER :: MCIERR_BASE + 85
MCIERR_SEQ_PORTUNSPECIFIED :: MCIERR_BASE + 86
MCIERR_SEQ_NOMIDIPRESENT :: MCIERR_BASE + 87
MCIERR_NO_WINDOW :: MCIERR_BASE + 90
MCIERR_CREATEWINDOW :: MCIERR_BASE + 91
MCIERR_FILE_READ :: MCIERR_BASE + 92
MCIERR_FILE_WRITE :: MCIERR_BASE + 93
MCIERR_NO_IDENTITY :: MCIERR_BASE + 94
/* MMRESULT error return values specific to the mixer API */
MIXERR_INVALLINE :: (MIXERR_BASE + 0)
MIXERR_INVALCONTROL :: (MIXERR_BASE + 1)
MIXERR_INVALVALUE :: (MIXERR_BASE + 2)
MIXERR_LASTERROR :: (MIXERR_BASE + 2)

100
core/text/table/doc.odin Normal file
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
core/text/table/table.odin Normal file
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, ' ')
}

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)
}

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, ...) {
while (indent --> 0) {
gb_printf_err("\t");
gb_printf("\t");
}
va_list va;
va_start(va, fmt);
gb_printf_err_va(fmt, va);
gb_printf_va(fmt, va);
va_end(va);
gb_printf_err("\n");
gb_printf("\n");
}
gb_internal void usage(String argv0) {

View File

@@ -3693,9 +3693,11 @@ gb_internal bool allow_field_separator(AstFile *f) {
if (allow_token(f, Token_Comma)) {
return true;
}
if (ALLOW_NEWLINE && token.kind == Token_Semicolon && !token_is_newline(token)) {
String p = token_to_string(token);
syntax_error(token_end_of_line(f, f->prev_token), "Expected a comma, got a %.*s", LIT(p));
if (ALLOW_NEWLINE && token.kind == Token_Semicolon) {
if (!token_is_newline(token)) {
String p = token_to_string(token);
syntax_error(token_end_of_line(f, f->prev_token), "Expected a comma, got a %.*s", LIT(p));
}
advance_token(f);
return true;
}

View File

@@ -6,87 +6,82 @@ python3 download_assets.py
echo ---
echo Running core:image tests
echo ---
%PATH_TO_ODIN% run image %COMMON% -out:test_core_image.exe
%PATH_TO_ODIN% run image %COMMON% -out:test_core_image.exe || exit /b
echo ---
echo Running core:compress tests
echo ---
%PATH_TO_ODIN% run compress %COMMON% -out:test_core_compress.exe
%PATH_TO_ODIN% run compress %COMMON% -out:test_core_compress.exe || exit /b
echo ---
echo Running core:strings tests
echo ---
%PATH_TO_ODIN% run strings %COMMON% -out:test_core_strings.exe
%PATH_TO_ODIN% run strings %COMMON% -out:test_core_strings.exe || exit /b
echo ---
echo Running core:hash tests
echo ---
%PATH_TO_ODIN% run hash %COMMON% -o:size -out:test_core_hash.exe
%PATH_TO_ODIN% run hash %COMMON% -o:size -out:test_core_hash.exe || exit /b
echo ---
echo Running core:odin tests
echo ---
%PATH_TO_ODIN% run odin %COMMON% -o:size -out:test_core_odin.exe
%PATH_TO_ODIN% run odin %COMMON% -o:size -out:test_core_odin.exe || exit /b
echo ---
echo Running core:crypto hash tests
echo ---
%PATH_TO_ODIN% run crypto %COMMON% -out:test_crypto_hash.exe
%PATH_TO_ODIN% run crypto %COMMON% -out:test_crypto_hash.exe || exit /b
echo ---
echo Running core:encoding tests
echo ---
%PATH_TO_ODIN% run encoding/hxa %COMMON% %COLLECTION% -out:test_hxa.exe
%PATH_TO_ODIN% run encoding/json %COMMON% -out:test_json.exe
%PATH_TO_ODIN% run encoding/varint %COMMON% -out:test_varint.exe
%PATH_TO_ODIN% run encoding/xml %COMMON% -out:test_xml.exe
%PATH_TO_ODIN% run encoding/hxa %COMMON% %COLLECTION% -out:test_hxa.exe || exit /b
%PATH_TO_ODIN% run encoding/json %COMMON% -out:test_json.exe || exit /b
%PATH_TO_ODIN% run encoding/varint %COMMON% -out:test_varint.exe || exit /b
%PATH_TO_ODIN% run encoding/xml %COMMON% -out:test_xml.exe || exit /b
echo ---
echo Running core:math/noise tests
echo ---
%PATH_TO_ODIN% run math/noise %COMMON% -out:test_noise.exe
%PATH_TO_ODIN% run math/noise %COMMON% -out:test_noise.exe || exit /b
echo ---
echo Running core:math tests
echo ---
%PATH_TO_ODIN% run math %COMMON% %COLLECTION% -out:test_core_math.exe
%PATH_TO_ODIN% run math %COMMON% %COLLECTION% -out:test_core_math.exe || exit /b
echo ---
echo Running core:math/linalg/glsl tests
echo ---
%PATH_TO_ODIN% run math/linalg/glsl %COMMON% %COLLECTION% -out:test_linalg_glsl.exe
%PATH_TO_ODIN% run math/linalg/glsl %COMMON% %COLLECTION% -out:test_linalg_glsl.exe || exit /b
echo ---
echo Running core:path/filepath tests
echo ---
%PATH_TO_ODIN% run path/filepath %COMMON% %COLLECTION% -out:test_core_filepath.exe
%PATH_TO_ODIN% run path/filepath %COMMON% %COLLECTION% -out:test_core_filepath.exe || exit /b
echo ---
echo Running core:reflect tests
echo ---
%PATH_TO_ODIN% run reflect %COMMON% %COLLECTION% -out:test_core_reflect.exe
%PATH_TO_ODIN% run reflect %COMMON% %COLLECTION% -out:test_core_reflect.exe || exit /b
echo ---
echo Running core:text/i18n tests
echo ---
%PATH_TO_ODIN% run text\i18n %COMMON% -out:test_core_i18n.exe
%PATH_TO_ODIN% run text\i18n %COMMON% -out:test_core_i18n.exe || exit /b
echo ---
echo Running core:net
echo ---
%PATH_TO_ODIN% run net %COMMON% -out:test_core_net.exe
echo ---
echo Running core:text/lua tests
echo ---
%PATH_TO_ODIN% run text\lua %COMMON% -out:test_core_lua_strlib.exe
%PATH_TO_ODIN% run net %COMMON% -out:test_core_net.exe || exit /b
echo ---
echo Running core:slice tests
echo ---
%PATH_TO_ODIN% run slice %COMMON% -out:test_core_slice.exe
%PATH_TO_ODIN% run slice %COMMON% -out:test_core_slice.exe || exit /b
echo ---
echo Running core:container tests
echo ---
%PATH_TO_ODIN% run container %COMMON% %COLLECTION% -out:test_core_container.exe
%PATH_TO_ODIN% run container %COMMON% %COLLECTION% -out:test_core_container.exe || exit /b

View File

@@ -151,6 +151,13 @@ shoco_test :: proc(t: ^testing.T) {
}
for v in Shoco_Tests {
when ODIN_OS == .Windows {
v := v
// Compressed source files are not encoded with carriage returns but git replaces raw files lf with crlf on commit (on windows only)
// So replace crlf with lf on windows
v.raw, _ = bytes.replace_all(v.raw, { 0xD, 0xA }, { 0xA })
}
expected_raw := len(v.raw)
expected_compressed := len(v.compressed)

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

View File

@@ -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
}

View File

@@ -1,4 +1,4 @@
@echo off
set PATH_TO_ODIN==..\..\odin
%PATH_TO_ODIN% run test_map.odin -file -vet -strict-style -o:minimal
%PATH_TO_ODIN% run test_map.odin -file -vet -strict-style -o:minimal || exit /b
rem -define:SEED=42

View File

@@ -5,19 +5,14 @@ pushd build
set COMMON=-collection:tests=..\..
set ERROR_DID_OCCUR=0
@echo on
..\..\..\odin test ..\test_issue_829.odin %COMMON% -file
..\..\..\odin test ..\test_issue_1592.odin %COMMON% -file
..\..\..\odin test ..\test_issue_2087.odin %COMMON% -file
..\..\..\odin build ..\test_issue_2113.odin %COMMON% -file -debug
..\..\..\odin test ..\test_issue_829.odin %COMMON% -file || exit /b
..\..\..\odin test ..\test_issue_1592.odin %COMMON% -file || exit /b
..\..\..\odin test ..\test_issue_2087.odin %COMMON% -file || exit /b
..\..\..\odin build ..\test_issue_2113.odin %COMMON% -file -debug || exit /b
@echo off
if %ERRORLEVEL% NEQ 0 set ERROR_DID_OCCUR=1
popd
rmdir /S /Q build
if %ERROR_DID_OCCUR% NEQ 0 EXIT /B 1

View File

@@ -5,9 +5,9 @@ set PATH_TO_ODIN==..\..\odin
echo ---
echo Running vendor:botan tests
echo ---
%PATH_TO_ODIN% run botan %COMMON% -out:vendor_botan.exe
%PATH_TO_ODIN% run botan %COMMON% -out:vendor_botan.exe || exit /b
echo ---
echo Running vendor:glfw tests
echo ---
%PATH_TO_ODIN% run glfw %COMMON% -out:vendor_glfw.exe
%PATH_TO_ODIN% run glfw %COMMON% -out:vendor_glfw.exe || exit /b