mirror of
https://github.com/odin-lang/Odin.git
synced 2026-02-15 07:43:13 +00:00
Merge remote-tracking branch 'origin' into wsapoll
This commit is contained in:
7
.github/workflows/ci.yml
vendored
7
.github/workflows/ci.yml
vendored
@@ -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: |
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
|
||||
@@ -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
100
core/text/table/doc.odin
Normal 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
384
core/text/table/table.odin
Normal 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
core/text/table/utility.odin
Normal file
13
core/text/table/utility.odin
Normal 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)
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
13
tests/documentation/build.bat
Normal file
13
tests/documentation/build.bat
Normal 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
|
||||
421
tests/documentation/documentation_tester.odin
Normal file
421
tests/documentation/documentation_tester.odin
Normal 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
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
4
tests/vendor/build.bat
vendored
4
tests/vendor/build.bat
vendored
@@ -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
|
||||
Reference in New Issue
Block a user