Merge remote-tracking branch 'offical/master'

This commit is contained in:
ed
2024-06-15 15:00:21 -04:00
38 changed files with 1432 additions and 1204 deletions
+22
View File
@@ -4,6 +4,7 @@ helper routines.
*/
package crypto
import "base:runtime"
import "core:mem"
// compare_constant_time returns 1 iff a and b are equal, 0 otherwise.
@@ -58,3 +59,24 @@ rand_bytes :: proc (dst: []byte) {
_rand_bytes(dst)
}
random_generator :: proc() -> runtime.Random_Generator {
return {
procedure = proc(data: rawptr, mode: runtime.Random_Generator_Mode, p: []byte) {
switch mode {
case .Read:
rand_bytes(p)
case .Reset:
// do nothing
case .Query_Info:
if len(p) != size_of(runtime.Random_Generator_Query_Info) {
return
}
info := (^runtime.Random_Generator_Query_Info)(raw_data(p))
info^ += {.Uniform, .Cryptographic, .External_Entropy}
}
},
data = nil,
}
}
+54 -69
View File
@@ -56,38 +56,27 @@ CDATA_END :: "]]>"
COMMENT_START :: "<!--"
COMMENT_END :: "-->"
/*
Default: CDATA and comments are passed through unchanged.
*/
// Default: CDATA and comments are passed through unchanged.
XML_Decode_Option :: enum u8 {
/*
Do not decode & entities. It decodes by default.
If given, overrides `Decode_CDATA`.
*/
// Do not decode & entities. It decodes by default. If given, overrides `Decode_CDATA`.
No_Entity_Decode,
/*
CDATA is unboxed.
*/
// CDATA is unboxed.
Unbox_CDATA,
/*
Unboxed CDATA is decoded as well.
Ignored if `.Unbox_CDATA` is not given.
*/
// Unboxed CDATA is decoded as well. Ignored if `.Unbox_CDATA` is not given.
Decode_CDATA,
/*
Comments are stripped.
*/
// Comments are stripped.
Comment_Strip,
// Normalize whitespace
Normalize_Whitespace,
}
XML_Decode_Options :: bit_set[XML_Decode_Option; u8]
/*
Decode a string that may include SGML/XML/HTML entities.
The caller has to free the result.
*/
// Decode a string that may include SGML/XML/HTML entities.
// The caller has to free the result.
decode_xml :: proc(input: string, options := XML_Decode_Options{}, allocator := context.allocator) -> (decoded: string, err: Error) {
context.allocator = allocator
@@ -100,14 +89,14 @@ decode_xml :: proc(input: string, options := XML_Decode_Options{}, allocator :=
t := Tokenizer{src=input}
in_data := false
prev: rune = ' '
loop: for {
advance(&t) or_return
if t.r < 0 { break loop }
/*
Below here we're never inside a CDATA tag.
At most we'll see the start of one, but that doesn't affect the logic.
*/
// Below here we're never inside a CDATA tag. At most we'll see the start of one,
// but that doesn't affect the logic.
switch t.r {
case '<':
/*
@@ -126,9 +115,7 @@ decode_xml :: proc(input: string, options := XML_Decode_Options{}, allocator :=
in_data = _handle_xml_special(&t, &builder, options) or_return
case ']':
/*
If we're unboxing _and_ decoding CDATA, we'll have to check for the end tag.
*/
// If we're unboxing _and_ decoding CDATA, we'll have to check for the end tag.
if in_data {
if t.read_offset + len(CDATA_END) < len(t.src) {
if string(t.src[t.offset:][:len(CDATA_END)]) == CDATA_END {
@@ -143,22 +130,16 @@ decode_xml :: proc(input: string, options := XML_Decode_Options{}, allocator :=
case:
if in_data && .Decode_CDATA not_in options {
/*
Unboxed, but undecoded.
*/
// Unboxed, but undecoded.
write_rune(&builder, t.r)
continue
}
if t.r == '&' {
if entity, entity_err := _extract_xml_entity(&t); entity_err != .None {
/*
We read to the end of the string without closing the entity.
Pass through as-is.
*/
// We read to the end of the string without closing the entity. Pass through as-is.
write_string(&builder, entity)
} else {
if .No_Entity_Decode not_in options {
if decoded, ok := xml_decode_entity(entity); ok {
write_rune(&builder, decoded)
@@ -166,19 +147,41 @@ decode_xml :: proc(input: string, options := XML_Decode_Options{}, allocator :=
}
}
/*
Literal passthrough because the decode failed or we want entities not decoded.
*/
// Literal passthrough because the decode failed or we want entities not decoded.
write_string(&builder, "&")
write_string(&builder, entity)
write_string(&builder, ";")
}
} else {
write_rune(&builder, t.r)
// Handle AV Normalization: https://www.w3.org/TR/2006/REC-xml11-20060816/#AVNormalize
if .Normalize_Whitespace in options {
switch t.r {
case ' ', '\r', '\n', '\t':
if prev != ' ' {
write_rune(&builder, ' ')
prev = ' '
}
case:
write_rune(&builder, t.r)
prev = t.r
}
} else {
// https://www.w3.org/TR/2006/REC-xml11-20060816/#sec-line-ends
switch t.r {
case '\n', 0x85, 0x2028:
write_rune(&builder, '\n')
case '\r': // Do nothing until next character
case:
if prev == '\r' { // Turn a single carriage return into a \n
write_rune(&builder, '\n')
}
write_rune(&builder, t.r)
}
prev = t.r
}
}
}
}
return strings.clone(strings.to_string(builder), allocator), err
}
@@ -253,24 +256,18 @@ xml_decode_entity :: proc(entity: string) -> (decoded: rune, ok: bool) {
return rune(val), true
case:
/*
Named entity.
*/
// Named entity.
return named_xml_entity_to_rune(entity)
}
}
/*
Private XML helper to extract `&<stuff>;` entity.
*/
// Private XML helper to extract `&<stuff>;` entity.
@(private="file")
_extract_xml_entity :: proc(t: ^Tokenizer) -> (entity: string, err: Error) {
assert(t != nil && t.r == '&')
/*
All of these would be in the ASCII range.
Even if one is not, it doesn't matter. All characters we need to compare to extract are.
*/
// All of these would be in the ASCII range.
// Even if one is not, it doesn't matter. All characters we need to compare to extract are.
length := len(t.src)
found := false
@@ -292,9 +289,7 @@ _extract_xml_entity :: proc(t: ^Tokenizer) -> (entity: string, err: Error) {
return string(t.src[t.offset : t.read_offset]), .Invalid_Entity_Encoding
}
/*
Private XML helper for CDATA and comments.
*/
// Private XML helper for CDATA and comments.
@(private="file")
_handle_xml_special :: proc(t: ^Tokenizer, builder: ^strings.Builder, options: XML_Decode_Options) -> (in_data: bool, err: Error) {
assert(t != nil && t.r == '<')
@@ -304,20 +299,14 @@ _handle_xml_special :: proc(t: ^Tokenizer, builder: ^strings.Builder, options: X
t.read_offset += len(CDATA_START) - 1
if .Unbox_CDATA in options && .Decode_CDATA in options {
/*
We're unboxing _and_ decoding CDATA
*/
// We're unboxing _and_ decoding CDATA
return true, .None
}
/*
CDATA is passed through.
*/
// CDATA is passed through.
offset := t.offset
/*
Scan until end of CDATA.
*/
// Scan until end of CDATA.
for {
advance(t) or_return
if t.r < 0 { return true, .CDATA_Not_Terminated }
@@ -341,14 +330,10 @@ _handle_xml_special :: proc(t: ^Tokenizer, builder: ^strings.Builder, options: X
} else if string(t.src[t.offset:][:len(COMMENT_START)]) == COMMENT_START {
t.read_offset += len(COMMENT_START)
/*
Comment is passed through by default.
*/
// Comment is passed through by default.
offset := t.offset
/*
Scan until end of Comment.
*/
// Scan until end of Comment.
for {
advance(t) or_return
if t.r < 0 { return true, .Comment_Not_Terminated }
+11 -32
View File
@@ -218,9 +218,7 @@ scan_identifier :: proc(t: ^Tokenizer) -> string {
for is_valid_identifier_rune(t.ch) {
advance_rune(t)
if t.ch == ':' {
/*
A namespaced attr can have at most two parts, `namespace:ident`.
*/
// A namespaced attr can have at most two parts, `namespace:ident`.
if namespaced {
break
}
@@ -268,14 +266,10 @@ scan_comment :: proc(t: ^Tokenizer) -> (comment: string, err: Error) {
return string(t.src[offset : t.offset - 1]), .None
}
/*
Skip CDATA
*/
// Skip CDATA
skip_cdata :: proc(t: ^Tokenizer) -> (err: Error) {
if t.read_offset + len(CDATA_START) >= len(t.src) {
/*
Can't be the start of a CDATA tag.
*/
// Can't be the start of a CDATA tag.
return .None
}
@@ -290,9 +284,7 @@ skip_cdata :: proc(t: ^Tokenizer) -> (err: Error) {
return .Premature_EOF
}
/*
Scan until the end of a CDATA tag.
*/
// Scan until the end of a CDATA tag.
if t.read_offset + len(CDATA_END) < len(t.src) {
if string(t.src[t.offset:][:len(CDATA_END)]) == CDATA_END {
t.read_offset += len(CDATA_END)
@@ -319,14 +311,10 @@ scan_string :: proc(t: ^Tokenizer, offset: int, close: rune = '<', consume_close
case '<':
if peek_byte(t) == '!' {
if peek_byte(t, 1) == '[' {
/*
Might be the start of a CDATA tag.
*/
// Might be the start of a CDATA tag.
skip_cdata(t) or_return
} else if peek_byte(t, 1) == '-' && peek_byte(t, 2) == '-' {
/*
Comment start. Eat comment.
*/
// Comment start. Eat comment.
t.read_offset += 3
_ = scan_comment(t) or_return
}
@@ -342,17 +330,13 @@ scan_string :: proc(t: ^Tokenizer, offset: int, close: rune = '<', consume_close
}
if t.ch == close {
/*
If it's not a CDATA or comment, it's the end of this body.
*/
// If it's not a CDATA or comment, it's the end of this body.
break loop
}
advance_rune(t)
}
/*
Strip trailing whitespace.
*/
// Strip trailing whitespace.
lit := string(t.src[offset : t.offset])
end := len(lit)
@@ -369,11 +353,6 @@ scan_string :: proc(t: ^Tokenizer, offset: int, close: rune = '<', consume_close
if consume_close {
advance_rune(t)
}
/*
TODO: Handle decoding escape characters and unboxing CDATA.
*/
return lit, err
}
@@ -384,7 +363,7 @@ peek :: proc(t: ^Tokenizer) -> (token: Token) {
return token
}
scan :: proc(t: ^Tokenizer) -> Token {
scan :: proc(t: ^Tokenizer, multiline_string := false) -> Token {
skip_whitespace(t)
offset := t.offset
@@ -418,7 +397,7 @@ scan :: proc(t: ^Tokenizer) -> Token {
case '"', '\'':
kind = .Invalid
lit, err = scan_string(t, t.offset, ch, true, false)
lit, err = scan_string(t, t.offset, ch, true, multiline_string)
if err == .None {
kind = .String
}
@@ -435,4 +414,4 @@ scan :: proc(t: ^Tokenizer) -> Token {
lit = string(t.src[offset : t.offset])
}
return Token{kind, lit, pos}
}
}
+14 -10
View File
@@ -203,9 +203,7 @@ parse_bytes :: proc(data: []u8, options := DEFAULT_OPTIONS, path := "", error_ha
doc.elements = make([dynamic]Element, 1024, 1024, allocator)
// strings.intern_init(&doc.intern, allocator, allocator)
err = .Unexpected_Token
err = .Unexpected_Token
element, parent: Element_ID
open: Token
@@ -259,8 +257,8 @@ parse_bytes :: proc(data: []u8, options := DEFAULT_OPTIONS, path := "", error_ha
case .Slash:
// Empty tag. Close it.
expect(t, .Gt) or_return
parent = doc.elements[element].parent
element = parent
parent = doc.elements[element].parent
element = parent
case:
error(t, t.offset, "Expected close tag, got: %#v\n", end_token)
@@ -276,8 +274,8 @@ parse_bytes :: proc(data: []u8, options := DEFAULT_OPTIONS, path := "", error_ha
error(t, t.offset, "Mismatched Closing Tag. Expected %v, got %v\n", doc.elements[element].ident, ident.text)
return doc, .Mismatched_Closing_Tag
}
parent = doc.elements[element].parent
element = parent
parent = doc.elements[element].parent
element = parent
} else if open.kind == .Exclaim {
// <!
@@ -463,8 +461,8 @@ validate_options :: proc(options: Options) -> (validated: Options, err: Error) {
return validated, .None
}
expect :: proc(t: ^Tokenizer, kind: Token_Kind) -> (tok: Token, err: Error) {
tok = scan(t)
expect :: proc(t: ^Tokenizer, kind: Token_Kind, multiline_string := false) -> (tok: Token, err: Error) {
tok = scan(t, multiline_string=multiline_string)
if tok.kind == kind { return tok, .None }
error(t, t.offset, "Expected \"%v\", got \"%v\".", kind, tok.kind)
@@ -480,7 +478,13 @@ parse_attribute :: proc(doc: ^Document) -> (attr: Attribute, offset: int, err: E
offset = t.offset - len(key.text)
_ = expect(t, .Eq) or_return
value := expect(t, .String) or_return
value := expect(t, .String, multiline_string=true) or_return
normalized, normalize_err := entity.decode_xml(value.text, {.Normalize_Whitespace}, doc.allocator)
if normalize_err == .None {
append(&doc.strings_to_free, normalized)
value.text = normalized
}
attr.key = key.text
attr.val = value.text
+1
View File
@@ -9,6 +9,7 @@ The verbs:
General:
%v the value in a default format
%#v an expanded format of %v with newlines and indentation
%w an Odin-syntax representation of the value
%T an Odin-syntax representation of the type of the value
%% a literal percent sign; consumes no value
{{ a literal open brace; consumes no value
+57
View File
@@ -0,0 +1,57 @@
//+build ignore
package custom_formatter_example
import "core:fmt"
import "core:io"
SomeType :: struct {
value: int,
}
My_Custom_Base_Type :: distinct u32
main :: proc() {
// Ensure the fmt._user_formatters map is initialized
fmt.set_user_formatters(new(map[typeid]fmt.User_Formatter))
// Register custom formatters for my favorite types
err := fmt.register_user_formatter(type_info_of(SomeType).id, SomeType_Formatter)
assert(err == .None)
err = fmt.register_user_formatter(type_info_of(My_Custom_Base_Type).id, My_Custom_Base_Formatter)
assert(err == .None)
// Use the custom formatters.
fmt.printfln("SomeType{{42}}: '%v'", SomeType{42})
fmt.printfln("My_Custom_Base_Type(0xdeadbeef): '%v'", My_Custom_Base_Type(0xdeadbeef))
}
SomeType_Formatter :: proc(fi: ^fmt.Info, arg: any, verb: rune) -> bool {
m := cast(^SomeType)arg.data
switch verb {
case 'v', 'd': // We handle `%v` and `%d`
fmt.fmt_int(fi, u64(m.value), true, 8 * size_of(SomeType), verb)
case:
return false
}
return true
}
My_Custom_Base_Formatter :: proc(fi: ^fmt.Info, arg: any, verb: rune) -> bool {
m := cast(^My_Custom_Base_Type)arg.data
switch verb {
case 'v', 'b':
value := u64(m^)
for value > 0 {
if value & 1 == 1 {
io.write_string(fi.writer, "Hellope!", &fi.n)
} else {
io.write_string(fi.writer, "Hellope?", &fi.n)
}
value >>= 1
}
case:
return false
}
return true
}
+16 -6
View File
@@ -1726,10 +1726,12 @@ fmt_bit_set :: proc(fi: ^Info, v: any, name: string = "", verb: rune = 'v') {
et := runtime.type_info_base(info.elem)
if name != "" {
io.write_string(fi.writer, name, &fi.n)
} else {
reflect.write_type(fi.writer, type_info, &fi.n)
if verb != 'w' {
if name != "" {
io.write_string(fi.writer, name, &fi.n)
} else {
reflect.write_type(fi.writer, type_info, &fi.n)
}
}
io.write_byte(fi.writer, '{', &fi.n)
defer io.write_byte(fi.writer, '}', &fi.n)
@@ -1746,9 +1748,17 @@ fmt_bit_set :: proc(fi: ^Info, v: any, name: string = "", verb: rune = 'v') {
}
if is_enum {
enum_name: string
if ti_named, is_named := info.elem.variant.(runtime.Type_Info_Named); is_named {
enum_name = ti_named.name
}
for ev, evi in e.values {
v := u64(ev)
if v == u64(i) {
if verb == 'w' {
io.write_string(fi.writer, enum_name, &fi.n)
io.write_byte(fi.writer, '.', &fi.n)
}
io.write_string(fi.writer, e.names[evi], &fi.n)
commas += 1
continue loop
@@ -2391,7 +2401,6 @@ fmt_named :: proc(fi: ^Info, v: any, verb: rune, info: runtime.Type_Info_Named)
runtime.Type_Info_Dynamic_Array,
runtime.Type_Info_Slice,
runtime.Type_Info_Struct,
runtime.Type_Info_Union,
runtime.Type_Info_Enum,
runtime.Type_Info_Map,
runtime.Type_Info_Bit_Set,
@@ -2498,8 +2507,9 @@ fmt_matrix :: proc(fi: ^Info, v: any, verb: rune, info: runtime.Type_Info_Matrix
}
} else {
// Printed in Row-Major layout to match text layout
row_separator := ", " if verb == 'w' else "; "
for row in 0..<info.row_count {
if row > 0 { io.write_string(fi.writer, "; ", &fi.n) }
if row > 0 { io.write_string(fi.writer, row_separator, &fi.n) }
for col in 0..<info.column_count {
if col > 0 { io.write_string(fi.writer, ", ", &fi.n) }
+5 -5
View File
@@ -362,11 +362,11 @@ platform_count_lsb :: #force_inline proc(a: $T) -> (count: int)
count_lsb :: proc { int_count_lsb, platform_count_lsb, }
int_random_digit :: proc(r: ^rnd.Rand = nil) -> (res: DIGIT) {
int_random_digit :: proc() -> (res: DIGIT) {
when _DIGIT_BITS == 60 { // DIGIT = u64
return DIGIT(rnd.uint64(r)) & _MASK
return DIGIT(rnd.uint64()) & _MASK
} else when _DIGIT_BITS == 28 { // DIGIT = u32
return DIGIT(rnd.uint32(r)) & _MASK
return DIGIT(rnd.uint32()) & _MASK
} else {
panic("Unsupported DIGIT size.")
}
@@ -374,12 +374,12 @@ int_random_digit :: proc(r: ^rnd.Rand = nil) -> (res: DIGIT) {
return 0 // We shouldn't get here.
}
int_random :: proc(dest: ^Int, bits: int, r: ^rnd.Rand = nil, allocator := context.allocator) -> (err: Error) {
int_random :: proc(dest: ^Int, bits: int, allocator := context.allocator) -> (err: Error) {
/*
Check that `a` is usable.
*/
assert_if_nil(dest)
return #force_inline internal_int_random(dest, bits, r, allocator)
return #force_inline internal_int_random(dest, bits, allocator)
}
random :: proc { int_random, }
+12 -7
View File
@@ -2178,15 +2178,20 @@ internal_int_grow :: proc(a: ^Int, digits: int, allow_shrink := false, allocator
}
/*
If not yet iniialized, initialize the `digit` backing with the allocator we were passed.
If not yet initialized, initialize the `digit` backing with the allocator we were passed.
*/
if cap == 0 {
a.digit = make([dynamic]DIGIT, needed, allocator)
} else if cap != needed {
} else if cap < needed {
/*
`[dynamic]DIGIT` already knows what allocator was used for it, so resize will do the right thing.
*/
resize(&a.digit, needed)
} else if cap > needed {
/*
Same applies to builtin.shrink here as resize above
*/
builtin.shrink(&a.digit, needed)
}
/*
Let's see if the allocation/resize worked as expected.
@@ -2812,11 +2817,11 @@ internal_platform_count_lsb :: #force_inline proc(a: $T) -> (count: int)
internal_count_lsb :: proc { internal_int_count_lsb, internal_platform_count_lsb, }
internal_int_random_digit :: proc(r: ^rnd.Rand = nil) -> (res: DIGIT) {
internal_int_random_digit :: proc() -> (res: DIGIT) {
when _DIGIT_BITS == 60 { // DIGIT = u64
return DIGIT(rnd.uint64(r)) & _MASK
return DIGIT(rnd.uint64()) & _MASK
} else when _DIGIT_BITS == 28 { // DIGIT = u32
return DIGIT(rnd.uint32(r)) & _MASK
return DIGIT(rnd.uint32()) & _MASK
} else {
panic("Unsupported DIGIT size.")
}
@@ -2824,7 +2829,7 @@ internal_int_random_digit :: proc(r: ^rnd.Rand = nil) -> (res: DIGIT) {
return 0 // We shouldn't get here.
}
internal_int_random :: proc(dest: ^Int, bits: int, r: ^rnd.Rand = nil, allocator := context.allocator) -> (err: Error) {
internal_int_random :: proc(dest: ^Int, bits: int, allocator := context.allocator) -> (err: Error) {
context.allocator = allocator
bits := bits
@@ -2841,7 +2846,7 @@ internal_int_random :: proc(dest: ^Int, bits: int, r: ^rnd.Rand = nil, allocator
#force_inline internal_grow(dest, digits) or_return
for i := 0; i < digits; i += 1 {
dest.digit[i] = int_random_digit(r) & _MASK
dest.digit[i] = int_random_digit() & _MASK
}
if bits > 0 {
dest.digit[digits - 1] &= ((1 << uint(bits)) - 1)
+3 -5
View File
@@ -12,8 +12,6 @@
package math_big
import rnd "core:math/rand"
/*
Determines if an Integer is divisible by one of the _PRIME_TABLE primes.
Returns true if it is, false if not.
@@ -315,7 +313,7 @@ internal_int_prime_miller_rabin :: proc(a, b: ^Int, allocator := context.allocat
Assumes `a` not to be `nil` and to have been initialized.
*/
internal_int_is_prime :: proc(a: ^Int, miller_rabin_trials := int(-1), miller_rabin_only := USE_MILLER_RABIN_ONLY, r: ^rnd.Rand = nil, allocator := context.allocator) -> (is_prime: bool, err: Error) {
internal_int_is_prime :: proc(a: ^Int, miller_rabin_trials := int(-1), miller_rabin_only := USE_MILLER_RABIN_ONLY, allocator := context.allocator) -> (is_prime: bool, err: Error) {
context.allocator = allocator
miller_rabin_trials := miller_rabin_trials
@@ -461,7 +459,7 @@ internal_int_is_prime :: proc(a: ^Int, miller_rabin_trials := int(-1), miller_ra
for ix := 0; ix < miller_rabin_trials; ix += 1 {
// rand() guarantees the first digit to be non-zero
internal_random(b, _DIGIT_TYPE_BITS, r) or_return
internal_random(b, _DIGIT_TYPE_BITS) or_return
// Reduce digit before casting because DIGIT might be bigger than
// an unsigned int and "mask" on the other side is most probably not.
@@ -1183,7 +1181,7 @@ internal_int_prime_next_prime :: proc(a: ^Int, trials: int, bbs_style: bool, all
This is possibly the mother of all prime generation functions, muahahahahaha!
*/
internal_random_prime :: proc(a: ^Int, size_in_bits: int, trials: int, flags := Primality_Flags{}, r: ^rnd.Rand = nil, allocator := context.allocator) -> (err: Error) {
internal_random_prime :: proc(a: ^Int, size_in_bits: int, trials: int, flags := Primality_Flags{}, allocator := context.allocator) -> (err: Error) {
context.allocator = allocator
flags := flags
trials := trials
+60 -60
View File
@@ -8,12 +8,12 @@ float32_uniform :: float32_range
// Triangular Distribution
// See: http://wikipedia.org/wiki/Triangular_distribution
@(require_results)
float64_triangular :: proc(lo, hi: f64, mode: Maybe(f64), r: ^Rand = nil) -> f64 {
float64_triangular :: proc(lo, hi: f64, mode: Maybe(f64)) -> f64 {
if hi-lo == 0 {
return lo
}
lo, hi := lo, hi
u := float64(r)
u := float64()
c := f64(0.5) if mode == nil else clamp((mode.?-lo) / (hi-lo), 0, 1)
if u > c {
u = 1-u
@@ -26,12 +26,12 @@ float64_triangular :: proc(lo, hi: f64, mode: Maybe(f64), r: ^Rand = nil) -> f64
// Triangular Distribution
// See: http://wikipedia.org/wiki/Triangular_distribution
@(require_results)
float32_triangular :: proc(lo, hi: f32, mode: Maybe(f32), r: ^Rand = nil) -> f32 {
float32_triangular :: proc(lo, hi: f32, mode: Maybe(f32)) -> f32 {
if hi-lo == 0 {
return lo
}
lo, hi := lo, hi
u := float32(r)
u := float32()
c := f32(0.5) if mode == nil else clamp((mode.?-lo) / (hi-lo), 0, 1)
if u > c {
u = 1-u
@@ -44,25 +44,25 @@ float32_triangular :: proc(lo, hi: f32, mode: Maybe(f32), r: ^Rand = nil) -> f32
// Normal/Gaussian Distribution
@(require_results)
float64_normal :: proc(mean, stddev: f64, r: ^Rand = nil) -> f64 {
return norm_float64(r) * stddev + mean
float64_normal :: proc(mean, stddev: f64) -> f64 {
return norm_float64() * stddev + mean
}
// Normal/Gaussian Distribution
@(require_results)
float32_normal :: proc(mean, stddev: f32, r: ^Rand = nil) -> f32 {
return f32(float64_normal(f64(mean), f64(stddev), r))
float32_normal :: proc(mean, stddev: f32) -> f32 {
return f32(float64_normal(f64(mean), f64(stddev)))
}
// Log Normal Distribution
@(require_results)
float64_log_normal :: proc(mean, stddev: f64, r: ^Rand = nil) -> f64 {
return math.exp(float64_normal(mean, stddev, r))
float64_log_normal :: proc(mean, stddev: f64) -> f64 {
return math.exp(float64_normal(mean, stddev))
}
// Log Normal Distribution
@(require_results)
float32_log_normal :: proc(mean, stddev: f32, r: ^Rand = nil) -> f32 {
return f32(float64_log_normal(f64(mean), f64(stddev), r))
float32_log_normal :: proc(mean, stddev: f32) -> f32 {
return f32(float64_log_normal(f64(mean), f64(stddev)))
}
@@ -72,8 +72,8 @@ float32_log_normal :: proc(mean, stddev: f32, r: ^Rand = nil) -> f32 {
// 0 to positive infinity if lambda > 0
// negative infinity to 0 if lambda <= 0
@(require_results)
float64_exponential :: proc(lambda: f64, r: ^Rand = nil) -> f64 {
return - math.ln(1 - float64(r)) / lambda
float64_exponential :: proc(lambda: f64) -> f64 {
return - math.ln(1 - float64()) / lambda
}
// Exponential Distribution
// `lambda` is 1.0/(desired mean). It should be non-zero.
@@ -81,8 +81,8 @@ float64_exponential :: proc(lambda: f64, r: ^Rand = nil) -> f64 {
// 0 to positive infinity if lambda > 0
// negative infinity to 0 if lambda <= 0
@(require_results)
float32_exponential :: proc(lambda: f32, r: ^Rand = nil) -> f32 {
return f32(float64_exponential(f64(lambda), r))
float32_exponential :: proc(lambda: f32) -> f32 {
return f32(float64_exponential(f64(lambda)))
}
@@ -96,7 +96,7 @@ float32_exponential :: proc(lambda: f32, r: ^Rand = nil) -> f32 {
//
// mean is alpha*beta, variance is math.pow(alpha*beta, 2)
@(require_results)
float64_gamma :: proc(alpha, beta: f64, r: ^Rand = nil) -> f64 {
float64_gamma :: proc(alpha, beta: f64) -> f64 {
if alpha <= 0 || beta <= 0 {
panic(#procedure + ": alpha and beta must be > 0.0")
}
@@ -112,11 +112,11 @@ float64_gamma :: proc(alpha, beta: f64, r: ^Rand = nil) -> f64 {
bbb := alpha - LOG4
ccc := alpha + ainv
for {
u1 := float64(r)
u1 := float64()
if !(1e-7 < u1 && u1 < 0.9999999) {
continue
}
u2 := 1 - float64(r)
u2 := 1 - float64()
v := math.ln(u1 / (1 - u1)) / ainv
x := alpha * math.exp(v)
z := u1 * u1 * u2
@@ -127,12 +127,12 @@ float64_gamma :: proc(alpha, beta: f64, r: ^Rand = nil) -> f64 {
}
case alpha == 1:
// float64_exponential(1/beta)
return -math.ln(1 - float64(r)) * beta
return -math.ln(1 - float64()) * beta
case:
// ALGORITHM GS of Statistical Computing - Kennedy & Gentle
x: f64
for {
u := float64(r)
u := float64()
b := (math.e + alpha) / math.e
p := b * u
if p <= 1 {
@@ -140,7 +140,7 @@ float64_gamma :: proc(alpha, beta: f64, r: ^Rand = nil) -> f64 {
} else {
x = -math.ln((b - p) / alpha)
}
u1 := float64(r)
u1 := float64()
if p > 1 {
if u1 <= math.pow(x, alpha-1) {
break
@@ -162,8 +162,8 @@ float64_gamma :: proc(alpha, beta: f64, r: ^Rand = nil) -> f64 {
//
// mean is alpha*beta, variance is math.pow(alpha*beta, 2)
@(require_results)
float32_gamma :: proc(alpha, beta: f32, r: ^Rand = nil) -> f32 {
return f32(float64_gamma(f64(alpha), f64(beta), r))
float32_gamma :: proc(alpha, beta: f32) -> f32 {
return f32(float64_gamma(f64(alpha), f64(beta)))
}
@@ -173,14 +173,14 @@ float32_gamma :: proc(alpha, beta: f32, r: ^Rand = nil) -> f32 {
//
// Return values range between 0 and 1
@(require_results)
float64_beta :: proc(alpha, beta: f64, r: ^Rand = nil) -> f64 {
float64_beta :: proc(alpha, beta: f64) -> f64 {
if alpha <= 0 || beta <= 0 {
panic(#procedure + ": alpha and beta must be > 0.0")
}
// Knuth Vol 2 Ed 3 pg 134 "the beta distribution"
y := float64_gamma(alpha, 1.0, r)
y := float64_gamma(alpha, 1.0)
if y != 0 {
return y / (y + float64_gamma(beta, 1.0, r))
return y / (y + float64_gamma(beta, 1.0))
}
return 0
}
@@ -190,35 +190,35 @@ float64_beta :: proc(alpha, beta: f64, r: ^Rand = nil) -> f64 {
//
// Return values range between 0 and 1
@(require_results)
float32_beta :: proc(alpha, beta: f32, r: ^Rand = nil) -> f32 {
return f32(float64_beta(f64(alpha), f64(beta), r))
float32_beta :: proc(alpha, beta: f32) -> f32 {
return f32(float64_beta(f64(alpha), f64(beta)))
}
// Pareto distribution, `alpha` is the shape parameter.
// https://wikipedia.org/wiki/Pareto_distribution
@(require_results)
float64_pareto :: proc(alpha: f64, r: ^Rand = nil) -> f64 {
return math.pow(1 - float64(r), -1.0 / alpha)
float64_pareto :: proc(alpha: f64) -> f64 {
return math.pow(1 - float64(), -1.0 / alpha)
}
// Pareto distribution, `alpha` is the shape parameter.
// https://wikipedia.org/wiki/Pareto_distribution
@(require_results)
float32_pareto :: proc(alpha, beta: f32, r: ^Rand = nil) -> f32 {
return f32(float64_pareto(f64(alpha), r))
float32_pareto :: proc(alpha, beta: f32) -> f32 {
return f32(float64_pareto(f64(alpha)))
}
// Weibull distribution, `alpha` is the scale parameter, `beta` is the shape parameter.
@(require_results)
float64_weibull :: proc(alpha, beta: f64, r: ^Rand = nil) -> f64 {
u := 1 - float64(r)
float64_weibull :: proc(alpha, beta: f64) -> f64 {
u := 1 - float64()
return alpha * math.pow(-math.ln(u), 1.0/beta)
}
// Weibull distribution, `alpha` is the scale parameter, `beta` is the shape parameter.
@(require_results)
float32_weibull :: proc(alpha, beta: f32, r: ^Rand = nil) -> f32 {
return f32(float64_weibull(f64(alpha), f64(beta), r))
float32_weibull :: proc(alpha, beta: f32) -> f32 {
return f32(float64_weibull(f64(alpha), f64(beta)))
}
@@ -227,23 +227,23 @@ float32_weibull :: proc(alpha, beta: f32, r: ^Rand = nil) -> f32 {
// `kappa` is the concentration parameter which must be >= 0
// When `kappa` is zero, the Distribution is a uniform Distribution over the range 0 to 2pi
@(require_results)
float64_von_mises :: proc(mean_angle, kappa: f64, r: ^Rand = nil) -> f64 {
float64_von_mises :: proc(mean_angle, kappa: f64) -> f64 {
// Fisher, N.I., "Statistical Analysis of Circular Data", Cambridge University Press, 1993.
mu := mean_angle
if kappa <= 1e-6 {
return math.TAU * float64(r)
return math.TAU * float64()
}
s := 0.5 / kappa
t := s + math.sqrt(1 + s*s)
z: f64
for {
u1 := float64(r)
u1 := float64()
z = math.cos(math.TAU * 0.5 * u1)
d := z / (t + z)
u2 := float64(r)
u2 := float64()
if u2 < 1 - d*d || u2 <= (1-d)*math.exp(d) {
break
}
@@ -251,7 +251,7 @@ float64_von_mises :: proc(mean_angle, kappa: f64, r: ^Rand = nil) -> f64 {
q := 1.0 / t
f := (q + z) / (1 + q*z)
u3 := float64(r)
u3 := float64()
if u3 > 0.5 {
return math.mod(mu + math.acos(f), math.TAU)
} else {
@@ -263,57 +263,57 @@ float64_von_mises :: proc(mean_angle, kappa: f64, r: ^Rand = nil) -> f64 {
// `kappa` is the concentration parameter which must be >= 0
// When `kappa` is zero, the Distribution is a uniform Distribution over the range 0 to 2pi
@(require_results)
float32_von_mises :: proc(mean_angle, kappa: f32, r: ^Rand = nil) -> f32 {
return f32(float64_von_mises(f64(mean_angle), f64(kappa), r))
float32_von_mises :: proc(mean_angle, kappa: f32) -> f32 {
return f32(float64_von_mises(f64(mean_angle), f64(kappa)))
}
// Cauchy-Lorentz Distribution
// `x_0` is the location, `gamma` is the scale where `gamma` > 0
@(require_results)
float64_cauchy_lorentz :: proc(x_0, gamma: f64, r: ^Rand = nil) -> f64 {
float64_cauchy_lorentz :: proc(x_0, gamma: f64) -> f64 {
assert(gamma > 0)
// Calculated from the inverse CDF
return math.tan(math.PI * (float64(r) - 0.5))*gamma + x_0
return math.tan(math.PI * (float64() - 0.5))*gamma + x_0
}
// Cauchy-Lorentz Distribution
// `x_0` is the location, `gamma` is the scale where `gamma` > 0
@(require_results)
float32_cauchy_lorentz :: proc(x_0, gamma: f32, r: ^Rand = nil) -> f32 {
return f32(float64_cauchy_lorentz(f64(x_0), f64(gamma), r))
float32_cauchy_lorentz :: proc(x_0, gamma: f32) -> f32 {
return f32(float64_cauchy_lorentz(f64(x_0), f64(gamma)))
}
// Log Cauchy-Lorentz Distribution
// `x_0` is the location, `gamma` is the scale where `gamma` > 0
@(require_results)
float64_log_cauchy_lorentz :: proc(x_0, gamma: f64, r: ^Rand = nil) -> f64 {
float64_log_cauchy_lorentz :: proc(x_0, gamma: f64) -> f64 {
assert(gamma > 0)
return math.exp(math.tan(math.PI * (float64(r) - 0.5))*gamma + x_0)
return math.exp(math.tan(math.PI * (float64() - 0.5))*gamma + x_0)
}
// Log Cauchy-Lorentz Distribution
// `x_0` is the location, `gamma` is the scale where `gamma` > 0
@(require_results)
float32_log_cauchy_lorentz :: proc(x_0, gamma: f32, r: ^Rand = nil) -> f32 {
return f32(float64_log_cauchy_lorentz(f64(x_0), f64(gamma), r))
float32_log_cauchy_lorentz :: proc(x_0, gamma: f32) -> f32 {
return f32(float64_log_cauchy_lorentz(f64(x_0), f64(gamma)))
}
// Laplace Distribution
// `b` is the scale where `b` > 0
@(require_results)
float64_laplace :: proc(mean, b: f64, r: ^Rand = nil) -> f64 {
float64_laplace :: proc(mean, b: f64) -> f64 {
assert(b > 0)
p := float64(r)-0.5
p := float64()-0.5
return -math.sign(p)*math.ln(1 - 2*abs(p))*b + mean
}
// Laplace Distribution
// `b` is the scale where `b` > 0
@(require_results)
float32_laplace :: proc(mean, b: f32, r: ^Rand = nil) -> f32 {
return f32(float64_laplace(f64(mean), f64(b), r))
float32_laplace :: proc(mean, b: f32) -> f32 {
return f32(float64_laplace(f64(mean), f64(b)))
}
@@ -321,18 +321,18 @@ float32_laplace :: proc(mean, b: f32, r: ^Rand = nil) -> f32 {
// `eta` is the shape, `b` is the scale
// Both `eta` and `b` must be > 0
@(require_results)
float64_gompertz :: proc(eta, b: f64, r: ^Rand = nil) -> f64 {
float64_gompertz :: proc(eta, b: f64) -> f64 {
if eta <= 0 || b <= 0 {
panic(#procedure + ": eta and b must be > 0.0")
}
p := float64(r)
p := float64()
return math.ln(1 - math.ln(1 - p)/eta)/b
}
// Gompertz Distribution
// `eta` is the shape, `b` is the scale
// Both `eta` and `b` must be > 0
@(require_results)
float32_gompertz :: proc(eta, b: f32, r: ^Rand = nil) -> f32 {
return f32(float64_gompertz(f64(eta), f64(b), r))
float32_gompertz :: proc(eta, b: f32) -> f32 {
return f32(float64_gompertz(f64(eta), f64(b)))
}
+4 -4
View File
@@ -16,7 +16,7 @@ import "core:math"
// https://www.jstatsoft.org/article/view/v005i08 [web page]
//
@(require_results)
exp_float64 :: proc(r: ^Rand = nil) -> f64 {
exp_float64 :: proc() -> f64 {
re :: 7.69711747013104972
@(static, rodata)
@@ -199,16 +199,16 @@ exp_float64 :: proc(r: ^Rand = nil) -> f64 {
}
for {
j := uint32(r)
j := uint32()
i := j & 0xFF
x := f64(j) * f64(we[i])
if j < ke[i] {
return x
}
if i == 0 {
return re - math.ln(float64(r))
return re - math.ln(float64())
}
if fe[i]+f32(float64(r))*(fe[i-1]-fe[i]) < f32(math.exp(-x)) {
if fe[i]+f32(float64())*(fe[i-1]-fe[i]) < f32(math.exp(-x)) {
return x
}
}
+5 -12
View File
@@ -18,7 +18,7 @@ import "core:math"
// https://www.jstatsoft.org/article/view/v005i08 [web page]
//
@(require_results)
norm_float64 :: proc(r: ^Rand = nil) -> f64 {
norm_float64 :: proc() -> f64 {
rn :: 3.442619855899
@(static, rodata)
@@ -115,15 +115,8 @@ norm_float64 :: proc(r: ^Rand = nil) -> f64 {
0.008624485, 0.005548995, 0.0026696292,
}
r := r
if r == nil {
// NOTE(bill, 2020-09-07): Do this so that people can
// enforce the global random state if necessary with `nil`
r = &global_rand
}
for {
j := i32(uint32(r))
j := i32(uint32())
i := j & 0x7f
x := f64(j) * f64(wn[i])
if u32(abs(j)) < kn[i] {
@@ -133,15 +126,15 @@ norm_float64 :: proc(r: ^Rand = nil) -> f64 {
if i == 0 {
for {
x = -math.ln(float64(r)) * (1.0/ rn)
y := -math.ln(float64(r))
x = -math.ln(float64()) * (1.0/ rn)
y := -math.ln(float64())
if y+y >= x*x {
break
}
}
return j > 0 ? rn + x : -rn - x
}
if fn[i]+f32(float64(r))*(fn[i-1]-fn[i]) < f32(math.exp(-0.5*x*x)) {
if fn[i]+f32(float64())*(fn[i-1]-fn[i]) < f32(math.exp(-0.5*x*x)) {
return x
}
}
+63 -252
View File
@@ -5,22 +5,22 @@ Package core:math/rand implements various random number generators
package rand
import "base:intrinsics"
import "core:crypto"
import "base:runtime"
import "core:math"
import "core:mem"
Rand :: struct {
state: u64,
inc: u64,
is_system: bool,
Default_Random_State :: runtime.Default_Random_State
default_random_generator :: runtime.default_random_generator
create :: proc(seed: u64) -> (state: Default_Random_State) {
seed := seed
runtime.default_random_generator(&state)
runtime.default_random_generator_proc(&state, .Reset, ([^]byte)(&seed)[:size_of(seed)])
return
}
@(private)
global_rand := create(u64(intrinsics.read_cycle_counter()))
/*
Sets the seed used by the global random number generator.
Reset the seed used by the context.random_generator.
Inputs:
- seed: The seed value
@@ -37,139 +37,46 @@ Example:
Possible Output:
10
*/
@(deprecated="Prefer `rand.reset`")
set_global_seed :: proc(seed: u64) {
init(&global_rand, seed)
runtime.random_generator_reset_u64(context.random_generator, seed)
}
/*
Creates a new random number generator.
Reset the seed used by the context.random_generator.
Inputs:
- seed: The seed value to create the random number generator with
Returns:
- res: The created random number generator
- seed: The seed value
Example:
import "core:math/rand"
import "core:fmt"
create_example :: proc() {
my_rand := rand.create(1)
fmt.println(rand.uint64(&my_rand))
set_global_seed_example :: proc() {
rand.set_global_seed(1)
fmt.println(rand.uint64())
}
Possible Output:
10
*/
@(require_results)
create :: proc(seed: u64) -> (res: Rand) {
r: Rand
init(&r, seed)
return r
reset :: proc(seed: u64) {
runtime.random_generator_reset_u64(context.random_generator, seed)
}
/*
Initialises a random number generator.
Inputs:
- r: The random number generator to initialise
- seed: The seed value to initialise this random number generator
Example:
import "core:math/rand"
import "core:fmt"
init_example :: proc() {
my_rand: rand.Rand
rand.init(&my_rand, 1)
fmt.println(rand.uint64(&my_rand))
}
Possible Output:
10
*/
init :: proc(r: ^Rand, seed: u64) {
r.state = 0
r.inc = (seed << 1) | 1
_random_u64(r)
r.state += seed
_random_u64(r)
}
/*
Initialises a random number generator to use the system random number generator.
The system random number generator is platform specific, and not supported
on all targets.
Inputs:
- r: The random number generator to use the system random number generator
WARNING: Panics if the system random number generator is not supported.
Support can be determined via the `core:crypto.HAS_RAND_BYTES` constant.
Example:
import "core:crypto"
import "core:math/rand"
import "core:fmt"
init_as_system_example :: proc() {
my_rand: rand.Rand
switch crypto.HAS_RAND_BYTES {
case true:
rand.init_as_system(&my_rand)
fmt.println(rand.uint64(&my_rand))
case false:
fmt.println("system random not supported!")
}
}
Possible Output:
10
*/
init_as_system :: proc(r: ^Rand) {
if !crypto.HAS_RAND_BYTES {
panic(#procedure + " is not supported on this platform yet")
}
r.state = 0
r.inc = 0
r.is_system = true
}
@(private)
_random_u64 :: proc(r: ^Rand) -> u64 {
r := r
switch {
case r == nil:
r = &global_rand
case r.is_system:
value: u64
crypto.rand_bytes((cast([^]u8)&value)[:size_of(u64)])
return value
}
old_state := r.state
r.state = old_state * 6364136223846793005 + (r.inc|1)
xor_shifted := (((old_state >> 59) + 5) ~ old_state) * 12605985483714917081
rot := (old_state >> 59)
return (xor_shifted >> rot) | (xor_shifted << ((-rot) & 63))
_random_u64 :: proc() -> (res: u64) {
ok := runtime.random_generator_read_ptr(context.random_generator, &res, size_of(res))
assert(ok, "uninitialized context.random_generator")
return
}
/*
Generates a random 32 bit value using the provided random number generator. If no generator is provided the global random number generator will be used.
Inputs:
- r: The random number generator to use, or nil for the global generator
Returns:
- val: A random unsigned 32 bit value
@@ -178,11 +85,7 @@ Example:
import "core:fmt"
uint32_example :: proc() {
// Using the global random number generator
fmt.println(rand.uint32())
// Using local random number generator
my_rand := rand.create(1)
fmt.println(rand.uint32(&my_rand))
}
Possible Output:
@@ -192,14 +95,11 @@ Possible Output:
*/
@(require_results)
uint32 :: proc(r: ^Rand = nil) -> (val: u32) { return u32(_random_u64(r)) }
uint32 :: proc() -> (val: u32) { return u32(_random_u64()) }
/*
Generates a random 64 bit value using the provided random number generator. If no generator is provided the global random number generator will be used.
Inputs:
- r: The random number generator to use, or nil for the global generator
Returns:
- val: A random unsigned 64 bit value
@@ -208,11 +108,7 @@ Example:
import "core:fmt"
uint64_example :: proc() {
// Using the global random number generator
fmt.println(rand.uint64())
// Using local random number generator
my_rand := rand.create(1)
fmt.println(rand.uint64(&my_rand))
}
Possible Output:
@@ -222,14 +118,11 @@ Possible Output:
*/
@(require_results)
uint64 :: proc(r: ^Rand = nil) -> (val: u64) { return _random_u64(r) }
uint64 :: proc() -> (val: u64) { return _random_u64() }
/*
Generates a random 128 bit value using the provided random number generator. If no generator is provided the global random number generator will be used.
Inputs:
- r: The random number generator to use, or nil for the global generator
Returns:
- val: A random unsigned 128 bit value
@@ -238,11 +131,7 @@ Example:
import "core:fmt"
uint128_example :: proc() {
// Using the global random number generator
fmt.println(rand.uint128())
// Using local random number generator
my_rand := rand.create(1)
fmt.println(rand.uint128(&my_rand))
}
Possible Output:
@@ -252,9 +141,9 @@ Possible Output:
*/
@(require_results)
uint128 :: proc(r: ^Rand = nil) -> (val: u128) {
a := u128(_random_u64(r))
b := u128(_random_u64(r))
uint128 :: proc() -> (val: u128) {
a := u128(_random_u64())
b := u128(_random_u64())
return (a<<64) | b
}
@@ -262,9 +151,6 @@ uint128 :: proc(r: ^Rand = nil) -> (val: u128) {
Generates a random 31 bit value using the provided random number generator. If no generator is provided the global random number generator will be used.
The sign bit will always be set to 0, thus all generated numbers will be positive.
Inputs:
- r: The random number generator to use, or nil for the global generator
Returns:
- val: A random 31 bit value
@@ -273,11 +159,7 @@ Example:
import "core:fmt"
int31_example :: proc() {
// Using the global random number generator
fmt.println(rand.int31())
// Using local random number generator
my_rand := rand.create(1)
fmt.println(rand.int31(&my_rand))
}
Possible Output:
@@ -286,15 +168,12 @@ Possible Output:
389
*/
@(require_results) int31 :: proc(r: ^Rand = nil) -> (val: i32) { return i32(uint32(r) << 1 >> 1) }
@(require_results) int31 :: proc() -> (val: i32) { return i32(uint32() << 1 >> 1) }
/*
Generates a random 63 bit value using the provided random number generator. If no generator is provided the global random number generator will be used.
The sign bit will always be set to 0, thus all generated numbers will be positive.
Inputs:
- r: The random number generator to use, or nil for the global generator
Returns:
- val: A random 63 bit value
@@ -303,11 +182,7 @@ Example:
import "core:fmt"
int63_example :: proc() {
// Using the global random number generator
fmt.println(rand.int63())
// Using local random number generator
my_rand := rand.create(1)
fmt.println(rand.int63(&my_rand))
}
Possible Output:
@@ -316,15 +191,12 @@ Possible Output:
389
*/
@(require_results) int63 :: proc(r: ^Rand = nil) -> (val: i64) { return i64(uint64(r) << 1 >> 1) }
@(require_results) int63 :: proc() -> (val: i64) { return i64(uint64() << 1 >> 1) }
/*
Generates a random 127 bit value using the provided random number generator. If no generator is provided the global random number generator will be used.
The sign bit will always be set to 0, thus all generated numbers will be positive.
Inputs:
- r: The random number generator to use, or nil for the global generator
Returns:
- val: A random 127 bit value
@@ -333,11 +205,7 @@ Example:
import "core:fmt"
int127_example :: proc() {
// Using the global random number generator
fmt.println(rand.int127())
// Using local random number generator
my_rand := rand.create(1)
fmt.println(rand.int127(&my_rand))
}
Possible Output:
@@ -346,14 +214,13 @@ Possible Output:
389
*/
@(require_results) int127 :: proc(r: ^Rand = nil) -> (val: i128) { return i128(uint128(r) << 1 >> 1) }
@(require_results) int127 :: proc() -> (val: i128) { return i128(uint128() << 1 >> 1) }
/*
Generates a random 31 bit value in the range `[0, n)` using the provided random number generator. If no generator is provided the global random number generator will be used.
Inputs:
- n: The upper bound of the generated number, this value is exclusive
- r: The random number generator to use, or nil for the global generator
Returns:
- val: A random 31 bit value in the range `[0, n)`
@@ -365,11 +232,7 @@ Example:
import "core:fmt"
int31_max_example :: proc() {
// Using the global random number generator
fmt.println(rand.int31_max(16))
// Using local random number generator
my_rand := rand.create(1)
fmt.println(rand.int31_max(1024, &my_rand))
}
Possible Output:
@@ -379,17 +242,17 @@ Possible Output:
*/
@(require_results)
int31_max :: proc(n: i32, r: ^Rand = nil) -> (val: i32) {
int31_max :: proc(n: i32) -> (val: i32) {
if n <= 0 {
panic("Invalid argument to int31_max")
}
if n&(n-1) == 0 {
return int31(r) & (n-1)
return int31() & (n-1)
}
max := i32((1<<31) - 1 - (1<<31)%u32(n))
v := int31(r)
v := int31()
for v > max {
v = int31(r)
v = int31()
}
return v % n
}
@@ -399,7 +262,6 @@ Generates a random 63 bit value in the range `[0, n)` using the provided random
Inputs:
- n: The upper bound of the generated number, this value is exclusive
- r: The random number generator to use, or nil for the global generator
Returns:
- val: A random 63 bit value in the range `[0, n)`
@@ -411,11 +273,7 @@ Example:
import "core:fmt"
int63_max_example :: proc() {
// Using the global random number generator
fmt.println(rand.int63_max(16))
// Using local random number generator
my_rand := rand.create(1)
fmt.println(rand.int63_max(1024, &my_rand))
}
Possible Output:
@@ -425,17 +283,17 @@ Possible Output:
*/
@(require_results)
int63_max :: proc(n: i64, r: ^Rand = nil) -> (val: i64) {
int63_max :: proc(n: i64) -> (val: i64) {
if n <= 0 {
panic("Invalid argument to int63_max")
}
if n&(n-1) == 0 {
return int63(r) & (n-1)
return int63() & (n-1)
}
max := i64((1<<63) - 1 - (1<<63)%u64(n))
v := int63(r)
v := int63()
for v > max {
v = int63(r)
v = int63()
}
return v % n
}
@@ -445,7 +303,6 @@ Generates a random 127 bit value in the range `[0, n)` using the provided random
Inputs:
- n: The upper bound of the generated number, this value is exclusive
- r: The random number generator to use, or nil for the global generator
Returns:
- val: A random 127 bit value in the range `[0, n)`
@@ -457,11 +314,7 @@ Example:
import "core:fmt"
int127_max_example :: proc() {
// Using the global random number generator
fmt.println(rand.int127_max(16))
// Using local random number generator
my_rand := rand.create(1)
fmt.println(rand.int127_max(1024, &my_rand))
}
Possible Output:
@@ -471,17 +324,17 @@ Possible Output:
*/
@(require_results)
int127_max :: proc(n: i128, r: ^Rand = nil) -> (val: i128) {
int127_max :: proc(n: i128) -> (val: i128) {
if n <= 0 {
panic("Invalid argument to int127_max")
}
if n&(n-1) == 0 {
return int127(r) & (n-1)
return int127() & (n-1)
}
max := i128((1<<127) - 1 - (1<<127)%u128(n))
v := int127(r)
v := int127()
for v > max {
v = int127(r)
v = int127()
}
return v % n
}
@@ -491,7 +344,6 @@ Generates a random integer value in the range `[0, n)` using the provided random
Inputs:
- n: The upper bound of the generated number, this value is exclusive
- r: The random number generator to use, or nil for the global generator
Returns:
- val: A random integer value in the range `[0, n)`
@@ -503,11 +355,7 @@ Example:
import "core:fmt"
int_max_example :: proc() {
// Using the global random number generator
fmt.println(rand.int_max(16))
// Using local random number generator
my_rand := rand.create(1)
fmt.println(rand.int_max(1024, &my_rand))
}
Possible Output:
@@ -517,23 +365,20 @@ Possible Output:
*/
@(require_results)
int_max :: proc(n: int, r: ^Rand = nil) -> (val: int) {
int_max :: proc(n: int) -> (val: int) {
if n <= 0 {
panic("Invalid argument to int_max")
}
when size_of(int) == 4 {
return int(int31_max(i32(n), r))
return int(int31_max(i32(n)))
} else {
return int(int63_max(i64(n), r))
return int(int63_max(i64(n)))
}
}
/*
Generates a random double floating point value in the range `[0, 1)` using the provided random number generator. If no generator is provided the global random number generator will be used.
Inputs:
- r: The random number generator to use, or nil for the global generator
Returns:
- val: A random double floating point value in the range `[0, 1)`
@@ -542,11 +387,7 @@ Example:
import "core:fmt"
float64_example :: proc() {
// Using the global random number generator
fmt.println(rand.float64())
// Using local random number generator
my_rand := rand.create(1)
fmt.println(rand.float64(&my_rand))
}
Possible Output:
@@ -555,14 +396,11 @@ Possible Output:
0.511
*/
@(require_results) float64 :: proc(r: ^Rand = nil) -> (val: f64) { return f64(int63_max(1<<53, r)) / (1 << 53) }
@(require_results) float64 :: proc() -> (val: f64) { return f64(int63_max(1<<53)) / (1 << 53) }
/*
Generates a random single floating point value in the range `[0, 1)` using the provided random number generator. If no generator is provided the global random number generator will be used.
Inputs:
- r: The random number generator to use, or nil for the global generator
Returns:
- val: A random single floating point value in the range `[0, 1)`
@@ -571,11 +409,7 @@ Example:
import "core:fmt"
float32_example :: proc() {
// Using the global random number generator
fmt.println(rand.float32())
// Using local random number generator
my_rand := rand.create(1)
fmt.println(rand.float32(&my_rand))
}
Possible Output:
@@ -584,7 +418,7 @@ Possible Output:
0.511
*/
@(require_results) float32 :: proc(r: ^Rand = nil) -> (val: f32) { return f32(int31_max(1<<24, r)) / (1 << 24) }
@(require_results) float32 :: proc() -> (val: f32) { return f32(int31_max(1<<24)) / (1 << 24) }
/*
Generates a random double floating point value in the range `[low, high)` using the provided random number generator. If no generator is provided the global random number generator will be used.
@@ -594,7 +428,6 @@ WARNING: Panics if `high < low`
Inputs:
- low: The lower bounds of the value, this value is inclusive
- high: The upper bounds of the value, this value is exclusive
- r: The random number generator to use, or nil for the global generator
Returns:
- val: A random double floating point value in the range [low, high)
@@ -604,11 +437,7 @@ Example:
import "core:fmt"
float64_range_example :: proc() {
// Using the global random number generator
fmt.println(rand.float64_range(-10, 300))
// Using local random number generator
my_rand := rand.create(1)
fmt.println(rand.float64_range(600, 900, &my_rand))
}
Possible Output:
@@ -617,9 +446,9 @@ Possible Output:
673.130
*/
@(require_results) float64_range :: proc(low, high: f64, r: ^Rand = nil) -> (val: f64) {
@(require_results) float64_range :: proc(low, high: f64) -> (val: f64) {
assert(low <= high, "low must be lower than or equal to high")
val = (high-low)*float64(r) + low
val = (high-low)*float64() + low
if val >= high {
val = max(low, high * (1 - math.F64_EPSILON))
}
@@ -632,7 +461,6 @@ Generates a random single floating point value in the range `[low, high)` using
Inputs:
- low: The lower bounds of the value, this value is inclusive
- high: The upper bounds of the value, this value is exclusive
- r: The random number generator to use, or nil for the global generator
Returns:
- val: A random single floating point value in the range [low, high)
@@ -644,11 +472,7 @@ Example:
import "core:fmt"
float32_range_example :: proc() {
// Using the global random number generator
fmt.println(rand.float32_range(-10, 300))
// Using local random number generator
my_rand := rand.create(1)
fmt.println(rand.float32_range(600, 900, &my_rand))
}
Possible Output:
@@ -657,9 +481,9 @@ Possible Output:
673.130
*/
@(require_results) float32_range :: proc(low, high: f32, r: ^Rand = nil) -> (val: f32) {
@(require_results) float32_range :: proc(low, high: f32) -> (val: f32) {
assert(low <= high, "low must be lower than or equal to high")
val = (high-low)*float32(r) + low
val = (high-low)*float32() + low
if val >= high {
val = max(low, high * (1 - math.F32_EPSILON))
}
@@ -672,7 +496,6 @@ Due to floating point precision there is no guarantee if the upper and lower bou
Inputs:
- p: The byte slice to fill
- r: The random number generator to use, or nil for the global generator
Returns:
- n: The number of bytes generated
@@ -682,7 +505,6 @@ Example:
import "core:fmt"
read_example :: proc() {
// Using the global random number generator
data: [8]byte
n := rand.read(data[:])
fmt.println(n)
@@ -696,12 +518,12 @@ Possible Output:
*/
@(require_results)
read :: proc(p: []byte, r: ^Rand = nil) -> (n: int) {
read :: proc(p: []byte) -> (n: int) {
pos := i8(0)
val := i64(0)
for n = 0; n < len(p); n += 1 {
if pos == 0 {
val = int63(r)
val = int63()
pos = 7
}
p[n] = byte(val)
@@ -718,7 +540,6 @@ Creates a slice of `int` filled with random values using the provided random num
Inputs:
- n: The size of the created slice
- r: The random number generator to use, or nil for the global generator
- allocator: (default: context.allocator)
Returns:
@@ -731,16 +552,10 @@ Example:
import "core:fmt"
perm_example :: proc() -> (err: mem.Allocator_Error) {
// Using the global random number generator and using the context allocator
data := rand.perm(4) or_return
fmt.println(data)
defer delete(data, context.allocator)
// Using local random number generator and temp allocator
my_rand := rand.create(1)
data_tmp := rand.perm(4, &my_rand, context.temp_allocator) or_return
fmt.println(data_tmp)
return
}
@@ -751,10 +566,10 @@ Possible Output:
*/
@(require_results)
perm :: proc(n: int, r: ^Rand = nil, allocator := context.allocator) -> (res: []int, err: mem.Allocator_Error) #optional_allocator_error {
perm :: proc(n: int, allocator := context.allocator) -> (res: []int, err: mem.Allocator_Error) #optional_allocator_error {
m := make([]int, n, allocator) or_return
for i := 0; i < n; i += 1 {
j := int_max(i+1, r)
j := int_max(i+1)
m[i] = m[j]
m[j] = i
}
@@ -766,14 +581,12 @@ Randomizes the ordering of elements for the provided slice. If no generator is p
Inputs:
- array: The slice to randomize
- r: The random number generator to use, or nil for the global generator
Example:
import "core:math/rand"
import "core:fmt"
shuffle_example :: proc() {
// Using the global random number generator
data: [4]int = { 1, 2, 3, 4 }
fmt.println(data) // the contents are in order
rand.shuffle(data[:])
@@ -786,14 +599,14 @@ Possible Output:
[2, 4, 3, 1]
*/
shuffle :: proc(array: $T/[]$E, r: ^Rand = nil) {
shuffle :: proc(array: $T/[]$E) {
n := i64(len(array))
if n < 2 {
return
}
for i := i64(n - 1); i > 0; i -= 1 {
j := int63_max(i + 1, r)
j := int63_max(i + 1)
array[i], array[j] = array[j], array[i]
}
}
@@ -803,7 +616,6 @@ Returns a random element from the provided slice. If no generator is provided th
Inputs:
- array: The slice to choose an element from
- r: The random number generator to use, or nil for the global generator
Returns:
- res: A random element from `array`
@@ -813,7 +625,6 @@ Example:
import "core:fmt"
choice_example :: proc() {
// Using the global random number generator
data: [4]int = { 1, 2, 3, 4 }
fmt.println(rand.choice(data[:]))
fmt.println(rand.choice(data[:]))
@@ -830,17 +641,17 @@ Possible Output:
*/
@(require_results)
choice :: proc(array: $T/[]$E, r: ^Rand = nil) -> (res: E) {
choice :: proc(array: $T/[]$E) -> (res: E) {
n := i64(len(array))
if n < 1 {
return E{}
}
return array[int63_max(n, r)]
return array[int63_max(n)]
}
@(require_results)
choice_enum :: proc($T: typeid, r: ^Rand = nil) -> T
choice_enum :: proc($T: typeid) -> T
where
intrinsics.type_is_enum(T),
size_of(T) <= 8,
@@ -848,11 +659,11 @@ choice_enum :: proc($T: typeid, r: ^Rand = nil) -> T
{
when intrinsics.type_is_unsigned(intrinsics.type_core_type(T)) &&
u64(max(T)) > u64(max(i64)) {
i := uint64(r) % u64(len(T))
i := uint64() % u64(len(T))
i += u64(min(T))
return T(i)
} else {
i := int63_max(i64(len(T)), r)
i := int63_max(i64(len(T)))
i += i64(min(T))
return T(i)
}
+2 -2
View File
@@ -180,7 +180,7 @@ binary_search_by :: proc(array: $A/[]$T, key: T, f: proc(T, T) -> Ordering) -> (
}
@(require_results)
equal :: proc(a, b: $T/[]$E) -> bool where intrinsics.type_is_comparable(E) {
equal :: proc(a, b: $T/[]$E) -> bool where intrinsics.type_is_comparable(E) #no_bounds_check {
if len(a) != len(b) {
return false
}
@@ -736,4 +736,4 @@ bitset_to_enum_slice_with_make :: proc(bs: $T, $E: typeid, allocator := context.
return bitset_to_enum_slice(buf, bs)
}
bitset_to_enum_slice :: proc{bitset_to_enum_slice_with_make, bitset_to_enum_slice_with_buffer}
bitset_to_enum_slice :: proc{bitset_to_enum_slice_with_make, bitset_to_enum_slice_with_buffer}
+1 -4
View File
@@ -476,10 +476,7 @@ select_raw :: proc "odin" (recvs: []^Raw_Chan, sends: []^Raw_Chan, send_msgs: []
return
}
r: ^rand.Rand = nil
select_idx = rand.int_max(count, r) if count > 0 else 0
select_idx = rand.int_max(count) if count > 0 else 0
sel := candidates[select_idx]
if sel.is_recv {
Regular → Executable
+72 -109
View File
@@ -64,6 +64,7 @@ foreign kernel32 {
RemoveVectoredContinueHandler :: proc(Handle: LPVOID) -> DWORD ---
RaiseException :: proc(dwExceptionCode, dwExceptionFlags, nNumberOfArguments: DWORD, lpArguments: ^ULONG_PTR) -> ! ---
SetUnhandledExceptionFilter :: proc(lpTopLevelExceptionFilter: LPTOP_LEVEL_EXCEPTION_FILTER) -> LPTOP_LEVEL_EXCEPTION_FILTER ---
CreateHardLinkW :: proc(lpSymlinkFileName: LPCWSTR,
lpTargetFileName: LPCWSTR,
@@ -464,6 +465,8 @@ foreign kernel32 {
GetHandleInformation :: proc(hObject: HANDLE, lpdwFlags: ^DWORD) -> BOOL ---
RtlCaptureStackBackTrace :: proc(FramesToSkip: ULONG, FramesToCapture: ULONG, BackTrace: [^]PVOID, BackTraceHash: PULONG) -> USHORT ---
GetSystemPowerStatus :: proc(lpSystemPowerStatus: ^SYSTEM_POWER_STATUS) -> BOOL ---
}
DEBUG_PROCESS :: 0x00000001
@@ -1009,31 +1012,9 @@ foreign kernel32 {
HandlerRoutine :: proc "system" (dwCtrlType: DWORD) -> BOOL
PHANDLER_ROUTINE :: HandlerRoutine
// NOTE(Jeroen, 2024-06-13): As Odin now supports bit_fields, we no longer need
// a helper procedure. `init_dcb_with_config` and `get_dcb_config` have been removed.
DCB_Config :: struct {
fParity: bool,
fOutxCtsFlow: bool,
fOutxDsrFlow: bool,
fDtrControl: DTR_Control,
fDsrSensitivity: bool,
fTXContinueOnXoff: bool,
fOutX: bool,
fInX: bool,
fErrorChar: bool,
fNull: bool,
fRtsControl: RTS_Control,
fAbortOnError: bool,
BaudRate: DWORD,
ByteSize: BYTE,
Parity: Parity,
StopBits: Stop_Bits,
XonChar: byte,
XoffChar: byte,
ErrorChar: byte,
EvtChar: byte,
}
DTR_Control :: enum byte {
Disable = 0,
Enable = 1,
@@ -1058,92 +1039,35 @@ Stop_Bits :: enum byte {
Two = 2,
}
// A helper procedure to set the values of a DCB structure.
init_dcb_with_config :: proc "contextless" (dcb: ^DCB, config: DCB_Config) {
out: u32
// NOTE(tetra, 2022-09-21): On both Clang 14 on Windows, and MSVC, the bits in the bitfield
// appear to be defined from LSB to MSB order.
// i.e: `fBinary` (the first bitfield in the C source) is the LSB in the `settings` u32.
out |= u32(1) << 0 // fBinary must always be true on Windows.
out |= u32(config.fParity) << 1
out |= u32(config.fOutxCtsFlow) << 2
out |= u32(config.fOutxDsrFlow) << 3
out |= u32(config.fDtrControl) << 4
out |= u32(config.fDsrSensitivity) << 6
out |= u32(config.fTXContinueOnXoff) << 7
out |= u32(config.fOutX) << 8
out |= u32(config.fInX) << 9
out |= u32(config.fErrorChar) << 10
out |= u32(config.fNull) << 11
out |= u32(config.fRtsControl) << 12
out |= u32(config.fAbortOnError) << 14
dcb.settings = out
dcb.BaudRate = config.BaudRate
dcb.ByteSize = config.ByteSize
dcb.Parity = config.Parity
dcb.StopBits = config.StopBits
dcb.XonChar = config.XonChar
dcb.XoffChar = config.XoffChar
dcb.ErrorChar = config.ErrorChar
dcb.EvtChar = config.EvtChar
dcb.DCBlength = size_of(DCB)
}
get_dcb_config :: proc "contextless" (dcb: DCB) -> (config: DCB_Config) {
config.fParity = bool((dcb.settings >> 1) & 0x01)
config.fOutxCtsFlow = bool((dcb.settings >> 2) & 0x01)
config.fOutxDsrFlow = bool((dcb.settings >> 3) & 0x01)
config.fDtrControl = DTR_Control((dcb.settings >> 4) & 0x02)
config.fDsrSensitivity = bool((dcb.settings >> 6) & 0x01)
config.fTXContinueOnXoff = bool((dcb.settings >> 7) & 0x01)
config.fOutX = bool((dcb.settings >> 8) & 0x01)
config.fInX = bool((dcb.settings >> 9) & 0x01)
config.fErrorChar = bool((dcb.settings >> 10) & 0x01)
config.fNull = bool((dcb.settings >> 11) & 0x01)
config.fRtsControl = RTS_Control((dcb.settings >> 12) & 0x02)
config.fAbortOnError = bool((dcb.settings >> 14) & 0x01)
config.BaudRate = dcb.BaudRate
config.ByteSize = dcb.ByteSize
config.Parity = dcb.Parity
config.StopBits = dcb.StopBits
config.XonChar = dcb.XonChar
config.XoffChar = dcb.XoffChar
config.ErrorChar = dcb.ErrorChar
config.EvtChar = dcb.EvtChar
return
}
// NOTE(tetra): See get_dcb_config() and init_dcb_with_config() for help with initializing this.
DCB :: struct {
DCBlength: DWORD, // NOTE(tetra): Must be set to size_of(DCB).
BaudRate: DWORD,
settings: u32, // NOTE(tetra): These are bitfields in the C struct.
wReserved: WORD,
XOnLim: WORD,
XOffLim: WORD,
ByteSize: BYTE,
Parity: Parity,
StopBits: Stop_Bits,
XonChar: byte,
XoffChar: byte,
ErrorChar: byte,
EofChar: byte,
EvtChar: byte,
DCBlength: DWORD,
BaudRate: DWORD,
using _: bit_field DWORD {
fBinary: bool | 1,
fParity: bool | 1,
fOutxCtsFlow: bool | 1,
fOutxDsrFlow: bool | 1,
fDtrControl: DTR_Control | 2,
fDsrSensitivity: bool | 1,
fTXContinueOnXoff: bool | 1,
fOutX: bool | 1,
fInX: bool | 1,
fErrorChar: bool | 1,
fNull: bool | 1,
fRtsControl: RTS_Control | 2,
fAbortOnError: bool | 1,
},
wReserved: WORD,
XOnLim: WORD,
XOffLim: WORD,
ByteSize: BYTE,
Parity: Parity,
StopBits: Stop_Bits,
XonChar: byte,
XoffChar: byte,
ErrorChar: byte,
EofChar: byte,
EvtChar: byte,
wReserved1: WORD,
}
@@ -1153,6 +1077,19 @@ foreign kernel32 {
SetCommState :: proc(handle: HANDLE, dcb: ^DCB) -> BOOL ---
}
COMMTIMEOUTS :: struct {
ReadIntervalTimeout: DWORD,
ReadTotalTimeoutMultiplier: DWORD,
ReadTotalTimeoutConstant: DWORD,
WriteTotalTimeoutMultiplier: DWORD,
WriteTotalTimeoutConstant: DWORD,
}
@(default_calling_convention="system")
foreign kernel32 {
GetCommTimeouts :: proc(handle: HANDLE, timeouts: ^COMMTIMEOUTS) -> BOOL ---
SetCommTimeouts :: proc(handle: HANDLE, timeouts: ^COMMTIMEOUTS) -> BOOL ---
}
LPFIBER_START_ROUTINE :: #type proc "system" (lpFiberParameter: LPVOID)
@@ -1210,6 +1147,30 @@ SYSTEM_LOGICAL_PROCESSOR_INFORMATION :: struct {
DummyUnion: DUMMYUNIONNAME_u,
}
SYSTEM_POWER_STATUS :: struct {
ACLineStatus: AC_Line_Status,
BatteryFlag: Battery_Flags,
BatteryLifePercent: BYTE,
SystemStatusFlag: BYTE,
BatteryLifeTime: DWORD,
BatteryFullLifeTime: DWORD,
}
AC_Line_Status :: enum BYTE {
Offline = 0,
Online = 1,
Unknown = 255,
}
Battery_Flag :: enum BYTE {
High = 0,
Low = 1,
Critical = 2,
Charging = 3,
No_Battery = 7,
}
Battery_Flags :: bit_set[Battery_Flag; BYTE]
/* Global Memory Flags */
GMEM_FIXED :: 0x0000
GMEM_MOVEABLE :: 0x0002
@@ -1228,3 +1189,5 @@ GMEM_INVALID_HANDLE :: 0x8000
GHND :: (GMEM_MOVEABLE | GMEM_ZEROINIT)
GPTR :: (GMEM_FIXED | GMEM_ZEROINIT)
LPTOP_LEVEL_EXCEPTION_FILTER :: PVECTORED_EXCEPTION_HANDLER
+9
View File
@@ -34,6 +34,7 @@ HGDIOBJ :: distinct HANDLE
HBITMAP :: distinct HANDLE
HGLOBAL :: distinct HANDLE
HHOOK :: distinct HANDLE
HWINEVENTHOOK :: distinct HANDLE
HKEY :: distinct HANDLE
HDESK :: distinct HANDLE
HFONT :: distinct HANDLE
@@ -703,6 +704,14 @@ WNDPROC :: #type proc "system" (HWND, UINT, WPARAM, LPARAM) -> LRESULT
HOOKPROC :: #type proc "system" (code: c_int, wParam: WPARAM, lParam: LPARAM) -> LRESULT
WINEVENTPROC :: #type proc "system" (
hWinEventHook: HWINEVENTHOOK,
event: DWORD,
hwnd: HWND,
idObject, idChild: LONG,
idEventThread, dwmsEventTime: DWORD,
)
CWPRETSTRUCT :: struct {
lResult: LRESULT,
lParam: LPARAM,
+21
View File
@@ -17,6 +17,18 @@ foreign user32 {
GetClassNameW :: proc(hWnd: HWND, lpClassName: LPWSTR, nMaxCount: c_int) -> c_int ---
GetParent :: proc(hWnd: HWND) -> HWND ---
IsWindowVisible :: proc(hWnd: HWND) -> BOOL ---
SetWinEventHook :: proc(
eventMin, eventMax: DWORD,
hmodWinEventProc: HMODULE,
pfnWinEvenProc: WINEVENTPROC,
idProcess, idThread: DWORD,
dwFlags: WinEventFlags,
) -> HWINEVENTHOOK ---
IsChild :: proc(hWndParent, hWnd: HWND) -> BOOL ---
RegisterClassW :: proc(lpWndClass: ^WNDCLASSW) -> ATOM ---
RegisterClassExW :: proc(^WNDCLASSEXW) -> ATOM ---
UnregisterClassW :: proc(lpClassName: LPCWSTR, hInstance: HINSTANCE) -> BOOL ---
@@ -568,3 +580,12 @@ RedrawWindowFlags :: enum UINT {
RDW_FRAME = 0x0400,
RDW_NOFRAME = 0x0800,
}
// OUTOFCONTEXT is the zero value, use {}
WinEventFlags :: bit_set[WinEventFlag; DWORD]
WinEventFlag :: enum DWORD {
SKIPOWNTHREAD = 0,
SKIPOWNPROCESS = 1,
INCONTEXT = 2,
}
+1 -1
View File
@@ -47,7 +47,7 @@ ERROR_PIPE_BUSY : DWORD : 231
E_NOTIMPL :: HRESULT(-0x7fff_bfff) // 0x8000_4001
SUCCEEDED :: #force_inline proc(#any_int result: int) -> bool { return result >= 0 }
SUCCEEDED :: #force_inline proc "contextless" (#any_int result: int) -> bool { return result >= 0 }
System_Error :: enum DWORD {
+16 -7
View File
@@ -8,13 +8,22 @@ import "core:strings"
import "core:sync/chan"
import "core:time"
Default_Test_Logger_Opts :: runtime.Logger_Options {
.Level,
.Terminal_Color,
.Short_File_Path,
.Line,
.Procedure,
.Date, .Time,
when USING_SHORT_LOGS {
Default_Test_Logger_Opts :: runtime.Logger_Options {
.Level,
.Terminal_Color,
.Short_File_Path,
.Line,
}
} else {
Default_Test_Logger_Opts :: runtime.Logger_Options {
.Level,
.Terminal_Color,
.Short_File_Path,
.Line,
.Procedure,
.Date, .Time,
}
}
Log_Message :: struct {
+31 -13
View File
@@ -41,6 +41,8 @@ PROGRESS_WIDTH : int : #config(ODIN_TEST_PROGRESS_WIDTH, 24)
SHARED_RANDOM_SEED : u64 : #config(ODIN_TEST_RANDOM_SEED, 0)
// Set the lowest log level for this test run.
LOG_LEVEL : string : #config(ODIN_TEST_LOG_LEVEL, "info")
// Show only the most necessary logging information.
USING_SHORT_LOGS : bool : #config(ODIN_TEST_SHORT_LOGS, false)
get_log_level :: #force_inline proc() -> runtime.Logger_Level {
@@ -497,6 +499,7 @@ runner :: proc(internal_tests: []Internal_Test) -> bool {
data.it = it
data.t.seed = shared_random_seed
data.t.error_count = 0
data.t._fail_now_called = false
thread.pool_add_task(&pool, task.allocator, run_test_task, data, run_index)
}
@@ -604,10 +607,10 @@ runner :: proc(internal_tests: []Internal_Test) -> bool {
})
fmt.assertf(alloc_error == nil, "Error appending to log messages: %v", alloc_error)
find_task_data: for &data in task_data_slots {
find_task_data_for_timeout: for &data in task_data_slots {
if data.it.pkg == it.pkg && data.it.name == it.name {
end_t(&data.t)
break find_task_data
break find_task_data_for_timeout
}
}
}
@@ -645,21 +648,36 @@ runner :: proc(internal_tests: []Internal_Test) -> bool {
"A signal (%v) was raised to stop test #%i %s.%s, but it was unable to be found.",
reason, test_index, it.pkg, it.name)
if test_index not_in failed_test_reason_map {
// We only write a new error message here if there wasn't one
// already, because the message we can provide based only on
// the signal won't be very useful, whereas asserts and panics
// will provide a user-written error message.
failed_test_reason_map[test_index] = fmt.aprintf("Signal caught: %v", reason, allocator = shared_log_allocator)
pkg_log.fatalf("Caught signal to stop test #%i %s.%s for: %v.", test_index, it.pkg, it.name, reason)
// The order this is handled in is a little particular.
task_data: ^Task_Data
find_task_data_for_stop_signal: for &data in task_data_slots {
if data.it.pkg == it.pkg && data.it.name == it.name {
task_data = &data
break find_task_data_for_stop_signal
}
}
when FANCY_OUTPUT {
bypass_progress_overwrite = true
signals_were_raised = true
fmt.assertf(task_data != nil, "A signal (%v) was raised to stop test #%i %s.%s, but its task data is missing.",
reason, test_index, it.pkg, it.name)
if !task_data.t._fail_now_called {
if test_index not_in failed_test_reason_map {
// We only write a new error message here if there wasn't one
// already, because the message we can provide based only on
// the signal won't be very useful, whereas asserts and panics
// will provide a user-written error message.
failed_test_reason_map[test_index] = fmt.aprintf("Signal caught: %v", reason, allocator = shared_log_allocator)
pkg_log.fatalf("Caught signal to stop test #%i %s.%s for: %v.", test_index, it.pkg, it.name, reason)
}
when FANCY_OUTPUT {
bypass_progress_overwrite = true
signals_were_raised = true
}
}
end_t(&task_data.t)
total_failure_count += 1
total_done_count += 1
}
+21 -6
View File
@@ -48,7 +48,7 @@ T :: struct {
// tests during channel transmission.
_log_allocator: runtime.Allocator,
_fail_now: proc() -> !,
_fail_now_called: bool,
}
@@ -66,15 +66,20 @@ fail :: proc(t: ^T, loc := #caller_location) {
pkg_log.error("FAIL", location=loc)
}
fail_now :: proc(t: ^T, msg := "", loc := #caller_location) {
// fail_now will cause a test to immediately fail and abort, much in the same
// way a failed assertion or panic call will stop a thread.
//
// It is for when you absolutely need a test to fail without calling any of its
// deferred statements. It will be cleaner than a regular assert or panic,
// as the test runner will know to expect the signal this procedure will raise.
fail_now :: proc(t: ^T, msg := "", loc := #caller_location) -> ! {
t._fail_now_called = true
if msg != "" {
pkg_log.error("FAIL:", msg, location=loc)
} else {
pkg_log.error("FAIL", location=loc)
}
if t._fail_now != nil {
t._fail_now()
}
runtime.trap()
}
failed :: proc(t: ^T) -> bool {
@@ -94,7 +99,17 @@ logf :: proc(t: ^T, format: string, args: ..any, loc := #caller_location) {
// cleanup registers a procedure and user_data, which will be called when the test, and all its subtests, complete.
// Cleanup procedures will be called in LIFO (last added, first called) order.
// Each procedure will use a copy of the context at the time of registering.
//
// Each procedure will use a copy of the context at the time of registering,
// and if the test failed due to a timeout, failed assertion, panic, bounds-checking error,
// memory access violation, or any other signal-based fault, this procedure will
// run with greater privilege in the test runner's main thread.
//
// That means that any cleanup procedure absolutely must not fail in the same way,
// or it will take down the entire test runner with it. This is for when you
// need something to run no matter what, if a test failed.
//
// For almost every usual case, `defer` should be preferable and sufficient.
cleanup :: proc(t: ^T, procedure: proc(rawptr), user_data: rawptr) {
append(&t.cleanups, Internal_Cleanup{procedure, user_data, context})
}