mirror of
https://github.com/odin-lang/Odin.git
synced 2026-06-09 11:58:10 +00:00
Merge remote-tracking branch 'origin/master' into more-windows-comm
This commit is contained in:
@@ -397,11 +397,34 @@ Logger :: struct {
|
||||
options: Logger_Options,
|
||||
}
|
||||
|
||||
|
||||
Random_Generator_Mode :: enum {
|
||||
Read,
|
||||
Reset,
|
||||
Query_Info,
|
||||
}
|
||||
|
||||
Random_Generator_Query_Info_Flag :: enum u32 {
|
||||
Cryptographic,
|
||||
Uniform,
|
||||
External_Entropy,
|
||||
Resettable,
|
||||
}
|
||||
Random_Generator_Query_Info :: distinct bit_set[Random_Generator_Query_Info_Flag; u32]
|
||||
|
||||
Random_Generator_Proc :: #type proc(data: rawptr, mode: Random_Generator_Mode, p: []byte)
|
||||
|
||||
Random_Generator :: struct {
|
||||
procedure: Random_Generator_Proc,
|
||||
data: rawptr,
|
||||
}
|
||||
|
||||
Context :: struct {
|
||||
allocator: Allocator,
|
||||
temp_allocator: Allocator,
|
||||
assertion_failure_proc: Assertion_Failure_Proc,
|
||||
logger: Logger,
|
||||
random_generator: Random_Generator,
|
||||
|
||||
user_ptr: rawptr,
|
||||
user_index: int,
|
||||
@@ -708,6 +731,9 @@ __init_context :: proc "contextless" (c: ^Context) {
|
||||
|
||||
c.logger.procedure = default_logger_proc
|
||||
c.logger.data = nil
|
||||
|
||||
c.random_generator.procedure = default_random_generator_proc
|
||||
c.random_generator.data = nil
|
||||
}
|
||||
|
||||
default_assertion_failure_proc :: proc(prefix, message: string, loc: Source_Code_Location) -> ! {
|
||||
|
||||
@@ -12,7 +12,8 @@ Memory_Block :: struct {
|
||||
capacity: uint,
|
||||
}
|
||||
|
||||
// NOTE: This is for internal use, prefer `Arena` from `core:mem/virtual` if necessary
|
||||
// NOTE: This is a growing arena that is only used for the default temp allocator.
|
||||
// For your own growing arena needs, prefer `Arena` from `core:mem/virtual`.
|
||||
Arena :: struct {
|
||||
backing_allocator: Allocator,
|
||||
curr_block: ^Memory_Block,
|
||||
120
base/runtime/random_generator.odin
Normal file
120
base/runtime/random_generator.odin
Normal file
@@ -0,0 +1,120 @@
|
||||
package runtime
|
||||
|
||||
import "base:intrinsics"
|
||||
|
||||
@(require_results)
|
||||
random_generator_read_bytes :: proc(rg: Random_Generator, p: []byte) -> bool {
|
||||
if rg.procedure != nil {
|
||||
rg.procedure(rg.data, .Read, p)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
random_generator_read_ptr :: proc(rg: Random_Generator, p: rawptr, len: uint) -> bool {
|
||||
if rg.procedure != nil {
|
||||
rg.procedure(rg.data, .Read, ([^]byte)(p)[:len])
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
random_generator_query_info :: proc(rg: Random_Generator) -> (info: Random_Generator_Query_Info) {
|
||||
if rg.procedure != nil {
|
||||
rg.procedure(rg.data, .Query_Info, ([^]byte)(&info)[:size_of(info)])
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
random_generator_reset_bytes :: proc(rg: Random_Generator, p: []byte) {
|
||||
if rg.procedure != nil {
|
||||
rg.procedure(rg.data, .Reset, p)
|
||||
}
|
||||
}
|
||||
|
||||
random_generator_reset_u64 :: proc(rg: Random_Generator, p: u64) {
|
||||
if rg.procedure != nil {
|
||||
p := p
|
||||
rg.procedure(rg.data, .Reset, ([^]byte)(&p)[:size_of(p)])
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Default_Random_State :: struct {
|
||||
state: u64,
|
||||
inc: u64,
|
||||
}
|
||||
|
||||
default_random_generator_proc :: proc(data: rawptr, mode: Random_Generator_Mode, p: []byte) {
|
||||
@(require_results)
|
||||
read_u64 :: proc "contextless" (r: ^Default_Random_State) -> u64 {
|
||||
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))
|
||||
}
|
||||
|
||||
@(thread_local)
|
||||
global_rand_seed: Default_Random_State
|
||||
|
||||
init :: proc "contextless" (r: ^Default_Random_State, seed: u64) {
|
||||
seed := seed
|
||||
if seed == 0 {
|
||||
seed = u64(intrinsics.read_cycle_counter())
|
||||
}
|
||||
r.state = 0
|
||||
r.inc = (seed << 1) | 1
|
||||
_ = read_u64(r)
|
||||
r.state += seed
|
||||
_ = read_u64(r)
|
||||
}
|
||||
|
||||
r: ^Default_Random_State = ---
|
||||
if data == nil {
|
||||
r = &global_rand_seed
|
||||
} else {
|
||||
r = cast(^Default_Random_State)data
|
||||
}
|
||||
|
||||
switch mode {
|
||||
case .Read:
|
||||
if r.state == 0 && r.inc == 0 {
|
||||
init(r, 0)
|
||||
}
|
||||
|
||||
pos := i8(0)
|
||||
val := u64(0)
|
||||
for &v in p {
|
||||
if pos == 0 {
|
||||
val = read_u64(r)
|
||||
pos = 7
|
||||
}
|
||||
v = byte(val)
|
||||
val >>= 8
|
||||
pos -= 1
|
||||
}
|
||||
|
||||
case .Reset:
|
||||
seed: u64
|
||||
mem_copy_non_overlapping(&seed, raw_data(p), min(size_of(seed), len(p)))
|
||||
init(r, seed)
|
||||
|
||||
case .Query_Info:
|
||||
if len(p) != size_of(Random_Generator_Query_Info) {
|
||||
return
|
||||
}
|
||||
info := (^Random_Generator_Query_Info)(raw_data(p))
|
||||
info^ += {.Uniform, .Resettable}
|
||||
}
|
||||
}
|
||||
|
||||
default_random_generator :: proc "contextless" (state: ^Default_Random_State = nil) -> Random_Generator {
|
||||
return {
|
||||
procedure = default_random_generator_proc,
|
||||
data = state,
|
||||
}
|
||||
}
|
||||
@@ -87,7 +87,7 @@ init_cmp :: proc(
|
||||
init_ordered :: proc(
|
||||
t: ^$T/Tree($Value),
|
||||
node_allocator := context.allocator,
|
||||
) where intrinsics.type_is_ordered_numeric(Value) {
|
||||
) where intrinsics.type_is_ordered(Value) {
|
||||
init_cmp(t, slice.cmp_proc(Value), node_allocator)
|
||||
}
|
||||
|
||||
|
||||
@@ -79,7 +79,7 @@ init_cmp :: proc(t: ^$T/Tree($Key, $Value), cmp_fn: proc(a, b: Key) -> Ordering,
|
||||
|
||||
// init_ordered initializes a tree containing ordered keys, with
|
||||
// a comparison function that results in an ascending order sort.
|
||||
init_ordered :: proc(t: ^$T/Tree($Key, $Value), node_allocator := context.allocator) where intrinsics.type_is_ordered_numeric(Key) {
|
||||
init_ordered :: proc(t: ^$T/Tree($Key, $Value), node_allocator := context.allocator) where intrinsics.type_is_ordered(Key) {
|
||||
init_cmp(t, slice.cmp_proc(Key), node_allocator)
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
28
core/flags/LICENSE
Normal file
28
core/flags/LICENSE
Normal file
@@ -0,0 +1,28 @@
|
||||
BSD 3-Clause License
|
||||
|
||||
Copyright (c) 2024, Feoramund
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
38
core/flags/constants.odin
Normal file
38
core/flags/constants.odin
Normal file
@@ -0,0 +1,38 @@
|
||||
package flags
|
||||
|
||||
import "core:time"
|
||||
|
||||
// Set to true to compile with support for core named types disabled, as a
|
||||
// fallback in the event your platform does not support one of the types, or
|
||||
// you have no need for them and want a smaller binary.
|
||||
NO_CORE_NAMED_TYPES :: #config(ODIN_CORE_FLAGS_NO_CORE_NAMED_TYPES, false)
|
||||
|
||||
// Override support for parsing `time` types.
|
||||
IMPORTING_TIME :: #config(ODIN_CORE_FLAGS_USE_TIME, time.IS_SUPPORTED)
|
||||
|
||||
// Override support for parsing `net` types.
|
||||
// TODO: Update this when the BSDs are supported.
|
||||
IMPORTING_NET :: #config(ODIN_CORE_FLAGS_USE_NET, ODIN_OS == .Windows || ODIN_OS == .Linux || ODIN_OS == .Darwin)
|
||||
|
||||
TAG_ARGS :: "args"
|
||||
SUBTAG_NAME :: "name"
|
||||
SUBTAG_POS :: "pos"
|
||||
SUBTAG_REQUIRED :: "required"
|
||||
SUBTAG_HIDDEN :: "hidden"
|
||||
SUBTAG_VARIADIC :: "variadic"
|
||||
SUBTAG_FILE :: "file"
|
||||
SUBTAG_PERMS :: "perms"
|
||||
SUBTAG_INDISTINCT :: "indistinct"
|
||||
|
||||
TAG_USAGE :: "usage"
|
||||
|
||||
UNDOCUMENTED_FLAG :: "<This flag has not been documented yet.>"
|
||||
|
||||
INTERNAL_VARIADIC_FLAG :: "varg"
|
||||
|
||||
RESERVED_HELP_FLAG :: "help"
|
||||
RESERVED_HELP_FLAG_SHORT :: "h"
|
||||
|
||||
// If there are more than this number of flags in total, only the required and
|
||||
// positional flags will be shown in the one-line usage summary.
|
||||
ONE_LINE_FLAG_CUTOFF_COUNT :: 16
|
||||
181
core/flags/doc.odin
Normal file
181
core/flags/doc.odin
Normal file
@@ -0,0 +1,181 @@
|
||||
/*
|
||||
package flags implements a command-line argument parser.
|
||||
|
||||
It works by using Odin's run-time type information to determine where and how
|
||||
to store data on a struct provided by the program. Type conversion is handled
|
||||
automatically and errors are reported with useful messages.
|
||||
|
||||
|
||||
Command-Line Syntax:
|
||||
|
||||
Arguments are treated differently depending on how they're formatted.
|
||||
The format is similar to the Odin binary's way of handling compiler flags.
|
||||
|
||||
```
|
||||
type handling
|
||||
------------ ------------------------
|
||||
<positional> depends on struct layout
|
||||
-<flag> set a bool true
|
||||
-<flag:option> set flag to option
|
||||
-<flag=option> set flag to option, alternative syntax
|
||||
-<map>:<key>=<value> set map[key] to value
|
||||
```
|
||||
|
||||
|
||||
Struct Tags:
|
||||
|
||||
Users of the `core:encoding/json` package may be familiar with using tags to
|
||||
annotate struct metadata. The same technique is used here to annotate where
|
||||
arguments should go and which are required.
|
||||
|
||||
Under the `args` tag, there are the following subtags:
|
||||
|
||||
- `name=S`: set `S` as the flag's name.
|
||||
- `pos=N`: place positional argument `N` into this flag.
|
||||
- `hidden`: hide this flag from the usage documentation.
|
||||
- `required`: cause verification to fail if this argument is not set.
|
||||
- `variadic`: take all remaining arguments when set, UNIX-style only.
|
||||
- `file`: for `os.Handle` types, file open mode.
|
||||
- `perms`: for `os.Handle` types, file open permissions.
|
||||
- `indistinct`: allow the setting of distinct types by their base type.
|
||||
|
||||
`required` may be given a range specifier in the following formats:
|
||||
```
|
||||
min
|
||||
<max
|
||||
min<max
|
||||
```
|
||||
|
||||
`max` is not inclusive in this range, as noted by the less-than `<` sign, so if
|
||||
you want to require 3 and only 3 arguments in a dynamic array, you would
|
||||
specify `required=3<4`.
|
||||
|
||||
|
||||
`variadic` may be given a number (`variadic=N`) above 1 to limit how many extra
|
||||
arguments it consumes.
|
||||
|
||||
|
||||
`file` determines the file open mode for an `os.Handle`.
|
||||
It accepts a string of flags that can be mixed together:
|
||||
- r: read
|
||||
- w: write
|
||||
- c: create, create the file if it doesn't exist
|
||||
- a: append, add any new writes to the end of the file
|
||||
- t: truncate, erase the file on open
|
||||
|
||||
|
||||
`perms` determines the file open permissions for an `os.Handle`.
|
||||
|
||||
The permissions are represented by three numbers in octal format. The first
|
||||
number is the owner, the second is the group, and the third is other. Read is
|
||||
represented by 4, write by 2, and execute by 1.
|
||||
|
||||
These numbers are added together to get combined permissions. For example, 644
|
||||
represents read/write for the owner, read for the group, and read for other.
|
||||
|
||||
Note that this may only have effect on UNIX-like platforms. By default, `perms`
|
||||
is set to 444 when only reading and 644 when writing.
|
||||
|
||||
|
||||
`indistinct` tells the parser that it's okay to treat distinct types as their
|
||||
underlying base type. Normally, the parser will hand those types off to the
|
||||
custom type setter (more about that later) if one is available, if it doesn't
|
||||
know how to handle the type.
|
||||
|
||||
|
||||
Usage Tag:
|
||||
|
||||
There is also the `usage` tag, which is a plain string to be printed alongside
|
||||
the flag in the usage output. If `usage` contains a newline, it will be
|
||||
properly aligned when printed.
|
||||
|
||||
All surrounding whitespace is trimmed when formatting with multiple lines.
|
||||
|
||||
|
||||
Supported Flag Data Types:
|
||||
|
||||
- all booleans
|
||||
- all integers
|
||||
- all floats
|
||||
- all enums
|
||||
- all complex numbers
|
||||
- all quaternions
|
||||
- all bit_sets
|
||||
- `string` and `cstring`
|
||||
- `rune`
|
||||
- `os.Handle`
|
||||
- `time.Time`
|
||||
- `datetime.DateTime`
|
||||
- `net.Host_Or_Endpoint`,
|
||||
- additional custom types, see Custom Types below
|
||||
- `dynamic` arrays with element types of the above
|
||||
- `map[string]`s or `map[cstring]`s with value types of the above
|
||||
|
||||
|
||||
Validation:
|
||||
|
||||
The parser will ensure `required` arguments are set, if no errors occurred
|
||||
during parsing. This is on by default.
|
||||
|
||||
Additionally, you may call `register_flag_checker` to set your own argument
|
||||
validation procedure that will be called after the default checker.
|
||||
|
||||
|
||||
Strict:
|
||||
|
||||
The parser will return on the first error and stop parsing. This is on by
|
||||
default. Otherwise, all arguments that can be parsed, will be, and only the
|
||||
last error is returned.
|
||||
|
||||
|
||||
Error Messages:
|
||||
|
||||
All error message strings are allocated using the context's `temp_allocator`,
|
||||
so if you need them to persist, make sure to clone the underlying `message`.
|
||||
|
||||
|
||||
Help:
|
||||
|
||||
By default, `-h` and `-help` are reserved flags which raise their own error
|
||||
type when set, allowing the program to handle the request differently from
|
||||
other errors.
|
||||
|
||||
|
||||
Custom Types:
|
||||
|
||||
You may specify your own type setter for program-specific structs and other
|
||||
named types. Call `register_type_setter` with an appropriate proc before
|
||||
calling any of the parsing procs.
|
||||
|
||||
A compliant `Custom_Type_Setter` must return three values:
|
||||
- an error message if one occurred,
|
||||
- a boolean indicating if the proc handles the type, and
|
||||
- an `Allocator_Error` if any occurred.
|
||||
|
||||
If the setter does not handle the type, simply return without setting any of
|
||||
the values.
|
||||
|
||||
|
||||
UNIX-style:
|
||||
|
||||
This package also supports parsing arguments in a limited flavor of UNIX.
|
||||
Odin and UNIX style are mutually exclusive, and which one to be used is chosen
|
||||
at parse time.
|
||||
|
||||
```
|
||||
--flag
|
||||
--flag=argument
|
||||
--flag argument
|
||||
--flag argument repeating-argument
|
||||
```
|
||||
|
||||
`-flag` may also be substituted for `--flag`.
|
||||
|
||||
Do note that map flags are not currently supported in this parsing style.
|
||||
|
||||
|
||||
Example:
|
||||
|
||||
A complete example is given in the `example` subdirectory.
|
||||
*/
|
||||
package flags
|
||||
50
core/flags/errors.odin
Normal file
50
core/flags/errors.odin
Normal file
@@ -0,0 +1,50 @@
|
||||
package flags
|
||||
|
||||
import "core:os"
|
||||
|
||||
Parse_Error_Reason :: enum {
|
||||
None,
|
||||
// An extra positional argument was given, and there is no `varg` field.
|
||||
Extra_Positional,
|
||||
// The underlying type does not support the string value it is being set to.
|
||||
Bad_Value,
|
||||
// No flag was given by the user.
|
||||
No_Flag,
|
||||
// No value was given by the user.
|
||||
No_Value,
|
||||
// The flag on the struct is missing.
|
||||
Missing_Flag,
|
||||
// The type itself isn't supported.
|
||||
Unsupported_Type,
|
||||
}
|
||||
|
||||
// Raised during parsing, naturally.
|
||||
Parse_Error :: struct {
|
||||
reason: Unified_Parse_Error_Reason,
|
||||
message: string,
|
||||
}
|
||||
|
||||
// Raised during parsing.
|
||||
// Provides more granular information than what just a string could hold.
|
||||
Open_File_Error :: struct {
|
||||
filename: string,
|
||||
errno: os.Errno,
|
||||
mode: int,
|
||||
perms: int,
|
||||
}
|
||||
|
||||
// Raised during parsing.
|
||||
Help_Request :: distinct bool
|
||||
|
||||
|
||||
// Raised after parsing, during validation.
|
||||
Validation_Error :: struct {
|
||||
message: string,
|
||||
}
|
||||
|
||||
Error :: union {
|
||||
Parse_Error,
|
||||
Open_File_Error,
|
||||
Help_Request,
|
||||
Validation_Error,
|
||||
}
|
||||
9
core/flags/errors_bsd.odin
Normal file
9
core/flags/errors_bsd.odin
Normal file
@@ -0,0 +1,9 @@
|
||||
//+build freebsd, netbsd, openbsd
|
||||
package flags
|
||||
|
||||
import "base:runtime"
|
||||
|
||||
Unified_Parse_Error_Reason :: union #shared_nil {
|
||||
Parse_Error_Reason,
|
||||
runtime.Allocator_Error,
|
||||
}
|
||||
11
core/flags/errors_nonbsd.odin
Normal file
11
core/flags/errors_nonbsd.odin
Normal file
@@ -0,0 +1,11 @@
|
||||
//+build !freebsd !netbsd !openbsd
|
||||
package flags
|
||||
|
||||
import "base:runtime"
|
||||
import "core:net"
|
||||
|
||||
Unified_Parse_Error_Reason :: union #shared_nil {
|
||||
Parse_Error_Reason,
|
||||
runtime.Allocator_Error,
|
||||
net.Parse_Endpoint_Error,
|
||||
}
|
||||
132
core/flags/example/example.odin
Normal file
132
core/flags/example/example.odin
Normal file
@@ -0,0 +1,132 @@
|
||||
package core_flags_example
|
||||
|
||||
import "base:runtime"
|
||||
import "core:flags"
|
||||
import "core:fmt"
|
||||
import "core:net"
|
||||
import "core:os"
|
||||
import "core:time/datetime"
|
||||
|
||||
|
||||
Fixed_Point1_1 :: struct {
|
||||
integer: u8,
|
||||
fractional: u8,
|
||||
}
|
||||
|
||||
Optimization_Level :: enum {
|
||||
Slow,
|
||||
Fast,
|
||||
Warp_Speed,
|
||||
Ludicrous_Speed,
|
||||
}
|
||||
|
||||
// It's simple but powerful.
|
||||
my_custom_type_setter :: proc(
|
||||
data: rawptr,
|
||||
data_type: typeid,
|
||||
unparsed_value: string,
|
||||
args_tag: string,
|
||||
) -> (
|
||||
error: string,
|
||||
handled: bool,
|
||||
alloc_error: runtime.Allocator_Error,
|
||||
) {
|
||||
if data_type == Fixed_Point1_1 {
|
||||
handled = true
|
||||
ptr := cast(^Fixed_Point1_1)data
|
||||
|
||||
// precision := flags.get_subtag(args_tag, "precision")
|
||||
|
||||
if len(unparsed_value) == 3 {
|
||||
ptr.integer = unparsed_value[0] - '0'
|
||||
ptr.fractional = unparsed_value[2] - '0'
|
||||
} else {
|
||||
error = "Incorrect format. Must be in the form of `i.f`."
|
||||
}
|
||||
|
||||
// Perform sanity checking here in the type parsing phase.
|
||||
//
|
||||
// The validation phase is flag-specific.
|
||||
if !(0 <= ptr.integer && ptr.integer < 10) || !(0 <= ptr.fractional && ptr.fractional < 10) {
|
||||
error = "Incorrect format. Must be between `0.0` and `9.9`."
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
my_custom_flag_checker :: proc(
|
||||
model: rawptr,
|
||||
name: string,
|
||||
value: any,
|
||||
args_tag: string,
|
||||
) -> (error: string) {
|
||||
if name == "iterations" {
|
||||
v := value.(int)
|
||||
if !(1 <= v && v < 5) {
|
||||
error = "Iterations only supports 1 ..< 5."
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
Distinct_Int :: distinct int
|
||||
|
||||
main :: proc() {
|
||||
Options :: struct {
|
||||
|
||||
file: os.Handle `args:"pos=0,required,file=r" usage:"Input file."`,
|
||||
output: os.Handle `args:"pos=1,file=cw" usage:"Output file."`,
|
||||
|
||||
hub: net.Host_Or_Endpoint `usage:"Internet address to contact for updates."`,
|
||||
schedule: datetime.DateTime `usage:"Launch tasks at this time."`,
|
||||
|
||||
opt: Optimization_Level `usage:"Optimization level."`,
|
||||
todo: [dynamic]string `usage:"Todo items."`,
|
||||
|
||||
accuracy: Fixed_Point1_1 `args:"required" usage:"Lenience in FLOP calculations."`,
|
||||
iterations: int `usage:"Run this many times."`,
|
||||
|
||||
// Note how the parser will transform this flag's name into `special-int`.
|
||||
special_int: Distinct_Int `args:"indistinct" usage:"Able to set distinct types."`,
|
||||
|
||||
quat: quaternion256,
|
||||
|
||||
bits: bit_set[0..<8],
|
||||
|
||||
// Many different requirement styles:
|
||||
|
||||
// gadgets: [dynamic]string `args:"required=1" usage:"gadgets"`,
|
||||
// widgets: [dynamic]string `args:"required=<3" usage:"widgets"`,
|
||||
// foos: [dynamic]string `args:"required=2<4"`,
|
||||
// bars: [dynamic]string `args:"required=3<4"`,
|
||||
// bots: [dynamic]string `args:"required"`,
|
||||
|
||||
// (Maps) Only available in Odin style:
|
||||
|
||||
// assignments: map[string]u8 `args:"name=assign" usage:"Number of jobs per worker."`,
|
||||
|
||||
// (Variadic) Only available in UNIX style:
|
||||
|
||||
// bots: [dynamic]string `args:"variadic=2,required"`,
|
||||
|
||||
verbose: bool `usage:"Show verbose output."`,
|
||||
debug: bool `args:"hidden" usage:"print debug info"`,
|
||||
|
||||
varg: [dynamic]string `usage:"Any extra arguments go here."`,
|
||||
}
|
||||
|
||||
opt: Options
|
||||
style : flags.Parsing_Style = .Odin
|
||||
|
||||
flags.register_type_setter(my_custom_type_setter)
|
||||
flags.register_flag_checker(my_custom_flag_checker)
|
||||
flags.parse_or_exit(&opt, os.args, style)
|
||||
|
||||
fmt.printfln("%#v", opt)
|
||||
|
||||
if opt.output != 0 {
|
||||
os.write_string(opt.output, "Hellope!\n")
|
||||
}
|
||||
}
|
||||
262
core/flags/internal_assignment.odin
Normal file
262
core/flags/internal_assignment.odin
Normal file
@@ -0,0 +1,262 @@
|
||||
//+private
|
||||
package flags
|
||||
|
||||
import "base:intrinsics"
|
||||
@require import "base:runtime"
|
||||
import "core:container/bit_array"
|
||||
@require import "core:fmt"
|
||||
@require import "core:mem"
|
||||
import "core:reflect"
|
||||
@require import "core:strconv"
|
||||
@require import "core:strings"
|
||||
|
||||
// Push a positional argument onto a data struct, checking for specified
|
||||
// positionals first before adding it to a fallback field.
|
||||
@(optimization_mode="size")
|
||||
push_positional :: #force_no_inline proc (model: ^$T, parser: ^Parser, arg: string) -> (error: Error) {
|
||||
if bit_array.get(&parser.filled_pos, parser.filled_pos.max_index) {
|
||||
// The max index is set, which means we're out of space.
|
||||
// Add one free bit by setting the index above to false.
|
||||
bit_array.set(&parser.filled_pos, 1 + parser.filled_pos.max_index, false)
|
||||
}
|
||||
|
||||
pos: int = ---
|
||||
{
|
||||
iter := bit_array.make_iterator(&parser.filled_pos)
|
||||
ok: bool
|
||||
pos, ok = bit_array.iterate_by_unset(&iter)
|
||||
|
||||
// This may be an allocator error.
|
||||
assert(ok, "Unable to find a free spot in the positional bit_array.")
|
||||
}
|
||||
|
||||
field, index, has_pos_assigned := get_field_by_pos(model, pos)
|
||||
|
||||
if !has_pos_assigned {
|
||||
when intrinsics.type_has_field(T, INTERNAL_VARIADIC_FLAG) {
|
||||
// Add it to the fallback array.
|
||||
field = reflect.struct_field_by_name(T, INTERNAL_VARIADIC_FLAG)
|
||||
} else {
|
||||
return Parse_Error {
|
||||
.Extra_Positional,
|
||||
fmt.tprintf("Got extra positional argument `%s` with nowhere to store it.", arg),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ptr := cast(rawptr)(cast(uintptr)model + field.offset)
|
||||
args_tag, _ := reflect.struct_tag_lookup(field.tag, TAG_ARGS)
|
||||
field_name := get_field_name(field)
|
||||
error = parse_and_set_pointer_by_type(ptr, arg, field.type, args_tag)
|
||||
#partial switch &specific_error in error {
|
||||
case Parse_Error:
|
||||
specific_error.message = fmt.tprintf("Unable to set positional #%i (%s) of type %v to `%s`.%s%s",
|
||||
pos,
|
||||
field_name,
|
||||
field.type,
|
||||
arg,
|
||||
" " if len(specific_error.message) > 0 else "",
|
||||
specific_error.message)
|
||||
case nil:
|
||||
bit_array.set(&parser.filled_pos, pos)
|
||||
bit_array.set(&parser.fields_set, index)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
register_field :: proc(parser: ^Parser, field: reflect.Struct_Field, index: int) {
|
||||
if pos, ok := get_field_pos(field); ok {
|
||||
bit_array.set(&parser.filled_pos, pos)
|
||||
}
|
||||
|
||||
bit_array.set(&parser.fields_set, index)
|
||||
}
|
||||
|
||||
// Set a `-flag` argument, Odin-style.
|
||||
@(optimization_mode="size")
|
||||
set_odin_flag :: proc(model: ^$T, parser: ^Parser, name: string) -> (error: Error) {
|
||||
// We make a special case for help requests.
|
||||
switch name {
|
||||
case RESERVED_HELP_FLAG, RESERVED_HELP_FLAG_SHORT:
|
||||
return Help_Request{}
|
||||
}
|
||||
|
||||
field, index := get_field_by_name(model, name) or_return
|
||||
|
||||
#partial switch specific_type_info in field.type.variant {
|
||||
case runtime.Type_Info_Boolean:
|
||||
ptr := cast(^bool)(cast(uintptr)model + field.offset)
|
||||
ptr^ = true
|
||||
case:
|
||||
return Parse_Error {
|
||||
.Bad_Value,
|
||||
fmt.tprintf("Unable to set `%s` of type %v to true.", name, field.type),
|
||||
}
|
||||
}
|
||||
|
||||
register_field(parser, field, index)
|
||||
return
|
||||
}
|
||||
|
||||
// Set a `-flag` argument, UNIX-style.
|
||||
@(optimization_mode="size")
|
||||
set_unix_flag :: proc(model: ^$T, parser: ^Parser, name: string) -> (future_args: int, error: Error) {
|
||||
// We make a special case for help requests.
|
||||
switch name {
|
||||
case RESERVED_HELP_FLAG, RESERVED_HELP_FLAG_SHORT:
|
||||
return 0, Help_Request{}
|
||||
}
|
||||
|
||||
field, index := get_field_by_name(model, name) or_return
|
||||
|
||||
#partial switch specific_type_info in field.type.variant {
|
||||
case runtime.Type_Info_Boolean:
|
||||
ptr := cast(^bool)(cast(uintptr)model + field.offset)
|
||||
ptr^ = true
|
||||
case runtime.Type_Info_Dynamic_Array:
|
||||
future_args = 1
|
||||
if tag, ok := reflect.struct_tag_lookup(field.tag, TAG_ARGS); ok {
|
||||
if length, is_variadic := get_struct_subtag(tag, SUBTAG_VARIADIC); is_variadic {
|
||||
// Variadic arrays may specify how many arguments they consume at once.
|
||||
// Otherwise, they take everything that's left.
|
||||
if value, value_ok := strconv.parse_u64_of_base(length, 10); value_ok {
|
||||
future_args = cast(int)value
|
||||
} else {
|
||||
future_args = max(int)
|
||||
}
|
||||
}
|
||||
}
|
||||
case:
|
||||
// `--flag`, waiting on its value.
|
||||
future_args = 1
|
||||
}
|
||||
|
||||
register_field(parser, field, index)
|
||||
return
|
||||
}
|
||||
|
||||
// Set a `-flag:option` argument.
|
||||
@(optimization_mode="size")
|
||||
set_option :: proc(model: ^$T, parser: ^Parser, name, option: string) -> (error: Error) {
|
||||
field, index := get_field_by_name(model, name) or_return
|
||||
|
||||
if len(option) == 0 {
|
||||
return Parse_Error {
|
||||
.No_Value,
|
||||
fmt.tprintf("Setting `%s` to an empty value is meaningless.", name),
|
||||
}
|
||||
}
|
||||
|
||||
// Guard against incorrect syntax.
|
||||
#partial switch specific_type_info in field.type.variant {
|
||||
case runtime.Type_Info_Map:
|
||||
return Parse_Error {
|
||||
.No_Value,
|
||||
fmt.tprintf("Unable to set `%s` of type %v to `%s`. Are you missing an `=`? The correct format is `map:key=value`.", name, field.type, option),
|
||||
}
|
||||
}
|
||||
|
||||
ptr := cast(rawptr)(cast(uintptr)model + field.offset)
|
||||
args_tag := reflect.struct_tag_get(field.tag, TAG_ARGS)
|
||||
error = parse_and_set_pointer_by_type(ptr, option, field.type, args_tag)
|
||||
#partial switch &specific_error in error {
|
||||
case Parse_Error:
|
||||
specific_error.message = fmt.tprintf("Unable to set `%s` of type %v to `%s`.%s%s",
|
||||
name,
|
||||
field.type,
|
||||
option,
|
||||
" " if len(specific_error.message) > 0 else "",
|
||||
specific_error.message)
|
||||
case nil:
|
||||
register_field(parser, field, index)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Set a `-map:key=value` argument.
|
||||
@(optimization_mode="size")
|
||||
set_key_value :: proc(model: ^$T, parser: ^Parser, name, key, value: string) -> (error: Error) {
|
||||
field, index := get_field_by_name(model, name) or_return
|
||||
|
||||
#partial switch specific_type_info in field.type.variant {
|
||||
case runtime.Type_Info_Map:
|
||||
key := key
|
||||
key_ptr := cast(rawptr)&key
|
||||
key_cstr: cstring
|
||||
if reflect.is_cstring(specific_type_info.key) {
|
||||
// We clone the key here, because it's liable to be a slice of an
|
||||
// Odin string, and we need to put a NUL terminator in it.
|
||||
key_cstr = strings.clone_to_cstring(key)
|
||||
key_ptr = &key_cstr
|
||||
}
|
||||
defer if key_cstr != nil {
|
||||
delete(key_cstr)
|
||||
}
|
||||
|
||||
raw_map := (^runtime.Raw_Map)(cast(uintptr)model + field.offset)
|
||||
|
||||
hash := specific_type_info.map_info.key_hasher(key_ptr, runtime.map_seed(raw_map^))
|
||||
|
||||
backing_alloc := false
|
||||
elem_backing: []byte
|
||||
value_ptr: rawptr
|
||||
|
||||
if raw_map.allocator.procedure == nil {
|
||||
raw_map.allocator = context.allocator
|
||||
} else {
|
||||
value_ptr = runtime.__dynamic_map_get(raw_map,
|
||||
specific_type_info.map_info,
|
||||
hash,
|
||||
key_ptr,
|
||||
)
|
||||
}
|
||||
|
||||
if value_ptr == nil {
|
||||
alloc_error: runtime.Allocator_Error = ---
|
||||
elem_backing, alloc_error = mem.alloc_bytes(specific_type_info.value.size, specific_type_info.value.align)
|
||||
if elem_backing == nil {
|
||||
return Parse_Error {
|
||||
alloc_error,
|
||||
"Failed to allocate element backing for map value.",
|
||||
}
|
||||
}
|
||||
|
||||
backing_alloc = true
|
||||
value_ptr = raw_data(elem_backing)
|
||||
}
|
||||
|
||||
args_tag, _ := reflect.struct_tag_lookup(field.tag, TAG_ARGS)
|
||||
error = parse_and_set_pointer_by_type(value_ptr, value, specific_type_info.value, args_tag)
|
||||
#partial switch &specific_error in error {
|
||||
case Parse_Error:
|
||||
specific_error.message = fmt.tprintf("Unable to set `%s` of type %v with key=value: `%s`=`%s`.%s%s",
|
||||
name,
|
||||
field.type,
|
||||
key,
|
||||
value,
|
||||
" " if len(specific_error.message) > 0 else "",
|
||||
specific_error.message)
|
||||
}
|
||||
|
||||
if backing_alloc {
|
||||
runtime.__dynamic_map_set(raw_map,
|
||||
specific_type_info.map_info,
|
||||
hash,
|
||||
key_ptr,
|
||||
value_ptr,
|
||||
)
|
||||
|
||||
delete(elem_backing)
|
||||
}
|
||||
|
||||
register_field(parser, field, index)
|
||||
return
|
||||
}
|
||||
|
||||
return Parse_Error {
|
||||
.Bad_Value,
|
||||
fmt.tprintf("Unable to set `%s` of type %v with key=value: `%s`=`%s`.", name, field.type, key, value),
|
||||
}
|
||||
}
|
||||
170
core/flags/internal_parsing.odin
Normal file
170
core/flags/internal_parsing.odin
Normal file
@@ -0,0 +1,170 @@
|
||||
//+private
|
||||
package flags
|
||||
|
||||
import "core:container/bit_array"
|
||||
import "core:strconv"
|
||||
import "core:strings"
|
||||
|
||||
// Used to group state together.
|
||||
Parser :: struct {
|
||||
// `fields_set` tracks which arguments have been set.
|
||||
// It uses their struct field index.
|
||||
fields_set: bit_array.Bit_Array,
|
||||
|
||||
// `filled_pos` tracks which arguments have been filled into positional
|
||||
// spots, much like how `fmt` treats them.
|
||||
filled_pos: bit_array.Bit_Array,
|
||||
}
|
||||
|
||||
parse_one_odin_arg :: proc(model: ^$T, parser: ^Parser, arg: string) -> (error: Error) {
|
||||
arg := arg
|
||||
|
||||
if strings.has_prefix(arg, "-") {
|
||||
arg = arg[1:]
|
||||
|
||||
flag: string
|
||||
assignment_rune: rune
|
||||
find_assignment: for r, i in arg {
|
||||
switch r {
|
||||
case ':', '=':
|
||||
assignment_rune = r
|
||||
flag = arg[:i]
|
||||
arg = arg[1 + i:]
|
||||
break find_assignment
|
||||
case:
|
||||
continue find_assignment
|
||||
}
|
||||
}
|
||||
|
||||
if assignment_rune == 0 {
|
||||
if len(arg) == 0 {
|
||||
return Parse_Error {
|
||||
.No_Flag,
|
||||
"No flag was given.",
|
||||
}
|
||||
}
|
||||
|
||||
// -flag
|
||||
set_odin_flag(model, parser, arg) or_return
|
||||
|
||||
} else if assignment_rune == ':' {
|
||||
// -flag:option <OR> -map:key=value
|
||||
error = set_option(model, parser, flag, arg)
|
||||
|
||||
if error != nil {
|
||||
// -flag:option did not work, so this may be a -map:key=value set.
|
||||
find_equals: for r, i in arg {
|
||||
if r == '=' {
|
||||
key := arg[:i]
|
||||
arg = arg[1 + i:]
|
||||
error = set_key_value(model, parser, flag, key, arg)
|
||||
break find_equals
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
// -flag=option, alternative syntax
|
||||
set_option(model, parser, flag, arg) or_return
|
||||
}
|
||||
|
||||
} else {
|
||||
// positional
|
||||
error = push_positional(model, parser, arg)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
parse_one_unix_arg :: proc(model: ^$T, parser: ^Parser, arg: string) -> (
|
||||
future_args: int,
|
||||
current_flag: string,
|
||||
error: Error,
|
||||
) {
|
||||
arg := arg
|
||||
|
||||
if strings.has_prefix(arg, "-") {
|
||||
// -flag
|
||||
arg = arg[1:]
|
||||
|
||||
if strings.has_prefix(arg, "-") {
|
||||
// Allow `--` to function as `-`.
|
||||
arg = arg[1:]
|
||||
|
||||
if len(arg) == 0 {
|
||||
// `--`, and only `--`.
|
||||
// Everything from now on will be treated as an argument.
|
||||
future_args = max(int)
|
||||
current_flag = INTERNAL_VARIADIC_FLAG
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
flag: string
|
||||
find_assignment: for r, i in arg {
|
||||
if r == '=' {
|
||||
// --flag=option
|
||||
flag = arg[:i]
|
||||
arg = arg[1 + i:]
|
||||
error = set_option(model, parser, flag, arg)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// --flag option, potentially
|
||||
future_args = set_unix_flag(model, parser, arg) or_return
|
||||
current_flag = arg
|
||||
|
||||
} else {
|
||||
// positional
|
||||
error = push_positional(model, parser, arg)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Parse a number of requirements specifier.
|
||||
//
|
||||
// Examples:
|
||||
//
|
||||
// `min`
|
||||
// `<max`
|
||||
// `min<max`
|
||||
parse_requirements :: proc(str: string) -> (minimum, maximum: int, ok: bool) {
|
||||
if len(str) == 0 {
|
||||
return 1, max(int), true
|
||||
}
|
||||
|
||||
if less_than := strings.index_byte(str, '<'); less_than != -1 {
|
||||
if len(str) == 1 {
|
||||
return 0, 0, false
|
||||
}
|
||||
|
||||
#no_bounds_check left := str[:less_than]
|
||||
#no_bounds_check right := str[1 + less_than:]
|
||||
|
||||
if left_value, parse_ok := strconv.parse_u64_of_base(left, 10); parse_ok {
|
||||
minimum = cast(int)left_value
|
||||
} else if len(left) > 0 {
|
||||
return 0, 0, false
|
||||
}
|
||||
|
||||
if right_value, parse_ok := strconv.parse_u64_of_base(right, 10); parse_ok {
|
||||
maximum = cast(int)right_value
|
||||
} else if len(right) > 0 {
|
||||
return 0, 0, false
|
||||
} else {
|
||||
maximum = max(int)
|
||||
}
|
||||
} else {
|
||||
if value, parse_ok := strconv.parse_u64_of_base(str, 10); parse_ok {
|
||||
minimum = cast(int)value
|
||||
maximum = max(int)
|
||||
} else {
|
||||
return 0, 0, false
|
||||
}
|
||||
}
|
||||
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
536
core/flags/internal_rtti.odin
Normal file
536
core/flags/internal_rtti.odin
Normal file
@@ -0,0 +1,536 @@
|
||||
//+private
|
||||
package flags
|
||||
|
||||
import "base:intrinsics"
|
||||
import "base:runtime"
|
||||
import "core:fmt"
|
||||
import "core:mem"
|
||||
import "core:os"
|
||||
import "core:reflect"
|
||||
import "core:strconv"
|
||||
import "core:strings"
|
||||
@require import "core:time"
|
||||
@require import "core:time/datetime"
|
||||
import "core:unicode/utf8"
|
||||
|
||||
@(optimization_mode="size")
|
||||
parse_and_set_pointer_by_base_type :: proc(ptr: rawptr, str: string, type_info: ^runtime.Type_Info) -> bool {
|
||||
bounded_int :: proc(value, min, max: i128) -> (result: i128, ok: bool) {
|
||||
return value, min <= value && value <= max
|
||||
}
|
||||
|
||||
bounded_uint :: proc(value, max: u128) -> (result: u128, ok: bool) {
|
||||
return value, value <= max
|
||||
}
|
||||
|
||||
// NOTE(Feoramund): This procedure has been written with the goal in mind
|
||||
// of generating the least amount of assembly, given that this library is
|
||||
// likely to be called once and forgotten.
|
||||
//
|
||||
// I've rewritten the switch tables below in 3 different ways, and the
|
||||
// current one generates the least amount of code for me on Linux AMD64.
|
||||
//
|
||||
// The other two ways were:
|
||||
//
|
||||
// - the original implementation: use of parametric polymorphism which led
|
||||
// to dozens of functions generated, one for each type.
|
||||
//
|
||||
// - a `value, ok` assignment statement with the `or_return` done at the
|
||||
// end of the switch, instead of inline.
|
||||
//
|
||||
// This seems to be the smallest way for now.
|
||||
|
||||
#partial switch specific_type_info in type_info.variant {
|
||||
case runtime.Type_Info_Integer:
|
||||
if specific_type_info.signed {
|
||||
value := strconv.parse_i128(str) or_return
|
||||
switch type_info.id {
|
||||
case i8: (cast(^i8) ptr)^ = cast(i8) bounded_int(value, cast(i128)min(i8), cast(i128)max(i8) ) or_return
|
||||
case i16: (cast(^i16) ptr)^ = cast(i16) bounded_int(value, cast(i128)min(i16), cast(i128)max(i16) ) or_return
|
||||
case i32: (cast(^i32) ptr)^ = cast(i32) bounded_int(value, cast(i128)min(i32), cast(i128)max(i32) ) or_return
|
||||
case i64: (cast(^i64) ptr)^ = cast(i64) bounded_int(value, cast(i128)min(i64), cast(i128)max(i64) ) or_return
|
||||
case i128: (cast(^i128) ptr)^ = value
|
||||
|
||||
case int: (cast(^int) ptr)^ = cast(int) bounded_int(value, cast(i128)min(int), cast(i128)max(int) ) or_return
|
||||
|
||||
case i16le: (cast(^i16le) ptr)^ = cast(i16le) bounded_int(value, cast(i128)min(i16le), cast(i128)max(i16le) ) or_return
|
||||
case i32le: (cast(^i32le) ptr)^ = cast(i32le) bounded_int(value, cast(i128)min(i32le), cast(i128)max(i32le) ) or_return
|
||||
case i64le: (cast(^i64le) ptr)^ = cast(i64le) bounded_int(value, cast(i128)min(i64le), cast(i128)max(i64le) ) or_return
|
||||
case i128le: (cast(^i128le)ptr)^ = cast(i128le) bounded_int(value, cast(i128)min(i128le), cast(i128)max(i128le)) or_return
|
||||
|
||||
case i16be: (cast(^i16be) ptr)^ = cast(i16be) bounded_int(value, cast(i128)min(i16be), cast(i128)max(i16be) ) or_return
|
||||
case i32be: (cast(^i32be) ptr)^ = cast(i32be) bounded_int(value, cast(i128)min(i32be), cast(i128)max(i32be) ) or_return
|
||||
case i64be: (cast(^i64be) ptr)^ = cast(i64be) bounded_int(value, cast(i128)min(i64be), cast(i128)max(i64be) ) or_return
|
||||
case i128be: (cast(^i128be)ptr)^ = cast(i128be) bounded_int(value, cast(i128)min(i128be), cast(i128)max(i128be)) or_return
|
||||
}
|
||||
} else {
|
||||
value := strconv.parse_u128(str) or_return
|
||||
switch type_info.id {
|
||||
case u8: (cast(^u8) ptr)^ = cast(u8) bounded_uint(value, cast(u128)max(u8) ) or_return
|
||||
case u16: (cast(^u16) ptr)^ = cast(u16) bounded_uint(value, cast(u128)max(u16) ) or_return
|
||||
case u32: (cast(^u32) ptr)^ = cast(u32) bounded_uint(value, cast(u128)max(u32) ) or_return
|
||||
case u64: (cast(^u64) ptr)^ = cast(u64) bounded_uint(value, cast(u128)max(u64) ) or_return
|
||||
case u128: (cast(^u128) ptr)^ = value
|
||||
|
||||
case uint: (cast(^uint) ptr)^ = cast(uint) bounded_uint(value, cast(u128)max(uint) ) or_return
|
||||
case uintptr: (cast(^uintptr)ptr)^ = cast(uintptr) bounded_uint(value, cast(u128)max(uintptr)) or_return
|
||||
|
||||
case u16le: (cast(^u16le) ptr)^ = cast(u16le) bounded_uint(value, cast(u128)max(u16le) ) or_return
|
||||
case u32le: (cast(^u32le) ptr)^ = cast(u32le) bounded_uint(value, cast(u128)max(u32le) ) or_return
|
||||
case u64le: (cast(^u64le) ptr)^ = cast(u64le) bounded_uint(value, cast(u128)max(u64le) ) or_return
|
||||
case u128le: (cast(^u128le) ptr)^ = cast(u128le) bounded_uint(value, cast(u128)max(u128le) ) or_return
|
||||
|
||||
case u16be: (cast(^u16be) ptr)^ = cast(u16be) bounded_uint(value, cast(u128)max(u16be) ) or_return
|
||||
case u32be: (cast(^u32be) ptr)^ = cast(u32be) bounded_uint(value, cast(u128)max(u32be) ) or_return
|
||||
case u64be: (cast(^u64be) ptr)^ = cast(u64be) bounded_uint(value, cast(u128)max(u64be) ) or_return
|
||||
case u128be: (cast(^u128be) ptr)^ = cast(u128be) bounded_uint(value, cast(u128)max(u128be) ) or_return
|
||||
}
|
||||
}
|
||||
|
||||
case runtime.Type_Info_Rune:
|
||||
if utf8.rune_count_in_string(str) != 1 {
|
||||
return false
|
||||
}
|
||||
|
||||
(cast(^rune)ptr)^ = utf8.rune_at_pos(str, 0)
|
||||
|
||||
case runtime.Type_Info_Float:
|
||||
value := strconv.parse_f64(str) or_return
|
||||
switch type_info.id {
|
||||
case f16: (cast(^f16) ptr)^ = cast(f16) value
|
||||
case f32: (cast(^f32) ptr)^ = cast(f32) value
|
||||
case f64: (cast(^f64) ptr)^ = value
|
||||
|
||||
case f16le: (cast(^f16le)ptr)^ = cast(f16le) value
|
||||
case f32le: (cast(^f32le)ptr)^ = cast(f32le) value
|
||||
case f64le: (cast(^f64le)ptr)^ = cast(f64le) value
|
||||
|
||||
case f16be: (cast(^f16be)ptr)^ = cast(f16be) value
|
||||
case f32be: (cast(^f32be)ptr)^ = cast(f32be) value
|
||||
case f64be: (cast(^f64be)ptr)^ = cast(f64be) value
|
||||
}
|
||||
|
||||
case runtime.Type_Info_Complex:
|
||||
value := strconv.parse_complex128(str) or_return
|
||||
switch type_info.id {
|
||||
case complex128: (cast(^complex128)ptr)^ = value
|
||||
case complex64: (cast(^complex64) ptr)^ = cast(complex64)value
|
||||
case complex32: (cast(^complex32) ptr)^ = cast(complex32)value
|
||||
}
|
||||
|
||||
case runtime.Type_Info_Quaternion:
|
||||
value := strconv.parse_quaternion256(str) or_return
|
||||
switch type_info.id {
|
||||
case quaternion256: (cast(^quaternion256)ptr)^ = value
|
||||
case quaternion128: (cast(^quaternion128)ptr)^ = cast(quaternion128)value
|
||||
case quaternion64: (cast(^quaternion64) ptr)^ = cast(quaternion64)value
|
||||
}
|
||||
|
||||
case runtime.Type_Info_String:
|
||||
if specific_type_info.is_cstring {
|
||||
cstr_ptr := cast(^cstring)ptr
|
||||
if cstr_ptr != nil {
|
||||
// Prevent memory leaks from us setting this value multiple times.
|
||||
delete(cstr_ptr^)
|
||||
}
|
||||
cstr_ptr^ = strings.clone_to_cstring(str)
|
||||
} else {
|
||||
(cast(^string)ptr)^ = str
|
||||
}
|
||||
|
||||
case runtime.Type_Info_Boolean:
|
||||
value := strconv.parse_bool(str) or_return
|
||||
switch type_info.id {
|
||||
case bool: (cast(^bool) ptr)^ = value
|
||||
case b8: (cast(^b8) ptr)^ = cast(b8) value
|
||||
case b16: (cast(^b16) ptr)^ = cast(b16) value
|
||||
case b32: (cast(^b32) ptr)^ = cast(b32) value
|
||||
case b64: (cast(^b64) ptr)^ = cast(b64) value
|
||||
}
|
||||
|
||||
case runtime.Type_Info_Bit_Set:
|
||||
// Parse a string of 1's and 0's, from left to right,
|
||||
// least significant bit to most significant bit.
|
||||
value: u128
|
||||
|
||||
// NOTE: `upper` is inclusive, i.e: `0..=31`
|
||||
max_bit_index := cast(u128)(1 + specific_type_info.upper - specific_type_info.lower)
|
||||
bit_index : u128 = 0
|
||||
#no_bounds_check for string_index : uint = 0; string_index < len(str); string_index += 1 {
|
||||
if bit_index == max_bit_index {
|
||||
// The string's too long for this bit_set.
|
||||
return false
|
||||
}
|
||||
|
||||
switch str[string_index] {
|
||||
case '1':
|
||||
value |= 1 << bit_index
|
||||
bit_index += 1
|
||||
case '0':
|
||||
bit_index += 1
|
||||
continue
|
||||
case '_':
|
||||
continue
|
||||
case:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
if specific_type_info.underlying != nil {
|
||||
set_unbounded_integer_by_type(ptr, value, specific_type_info.underlying.id)
|
||||
} else {
|
||||
switch 8*type_info.size {
|
||||
case 8: (cast(^u8) ptr)^ = cast(u8) value
|
||||
case 16: (cast(^u16) ptr)^ = cast(u16) value
|
||||
case 32: (cast(^u32) ptr)^ = cast(u32) value
|
||||
case 64: (cast(^u64) ptr)^ = cast(u64) value
|
||||
case 128: (cast(^u128) ptr)^ = cast(u128) value
|
||||
}
|
||||
}
|
||||
|
||||
case:
|
||||
fmt.panicf("Unsupported base data type: %v", specific_type_info)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// This proc exists to make error handling easier, since everything in the base
|
||||
// type one above works on booleans. It's a simple parsing error if it's false.
|
||||
//
|
||||
// However, here we have to be more careful about how we handle errors,
|
||||
// especially with files.
|
||||
//
|
||||
// We want to provide as informative as an error as we can.
|
||||
@(optimization_mode="size", disabled=NO_CORE_NAMED_TYPES)
|
||||
parse_and_set_pointer_by_named_type :: proc(ptr: rawptr, str: string, data_type: typeid, arg_tag: string, out_error: ^Error) {
|
||||
// Core types currently supported:
|
||||
//
|
||||
// - os.Handle
|
||||
// - time.Time
|
||||
// - datetime.DateTime
|
||||
// - net.Host_Or_Endpoint
|
||||
|
||||
GENERIC_RFC_3339_ERROR :: "Invalid RFC 3339 string. Try this format: `yyyy-mm-ddThh:mm:ssZ`, for example `2024-02-29T16:30:00Z`."
|
||||
|
||||
out_error^ = nil
|
||||
|
||||
if data_type == os.Handle {
|
||||
// NOTE: `os` is hopefully available everywhere, even if it might panic on some calls.
|
||||
wants_read := false
|
||||
wants_write := false
|
||||
mode: int
|
||||
|
||||
if file, ok := get_struct_subtag(arg_tag, SUBTAG_FILE); ok {
|
||||
for i := 0; i < len(file); i += 1 {
|
||||
#no_bounds_check switch file[i] {
|
||||
case 'r': wants_read = true
|
||||
case 'w': wants_write = true
|
||||
case 'c': mode |= os.O_CREATE
|
||||
case 'a': mode |= os.O_APPEND
|
||||
case 't': mode |= os.O_TRUNC
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sane default.
|
||||
// owner/group/other: r--r--r--
|
||||
perms: int = 0o444
|
||||
|
||||
if wants_read && wants_write {
|
||||
mode |= os.O_RDWR
|
||||
perms |= 0o200
|
||||
} else if wants_write {
|
||||
mode |= os.O_WRONLY
|
||||
perms |= 0o200
|
||||
} else {
|
||||
mode |= os.O_RDONLY
|
||||
}
|
||||
|
||||
if permstr, ok := get_struct_subtag(arg_tag, SUBTAG_PERMS); ok {
|
||||
if value, parse_ok := strconv.parse_u64_of_base(permstr, 8); parse_ok {
|
||||
perms = cast(int)value
|
||||
}
|
||||
}
|
||||
|
||||
handle, errno := os.open(str, mode, perms)
|
||||
if errno != 0 {
|
||||
// NOTE(Feoramund): os.Errno is system-dependent, and there's
|
||||
// currently no good way to translate them all into strings.
|
||||
//
|
||||
// The upcoming `os2` package will hopefully solve this.
|
||||
//
|
||||
// We can at least provide the number for now, so the user can look
|
||||
// it up.
|
||||
out_error^ = Open_File_Error {
|
||||
str,
|
||||
errno,
|
||||
mode,
|
||||
perms,
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
(cast(^os.Handle)ptr)^ = handle
|
||||
return
|
||||
}
|
||||
|
||||
when IMPORTING_TIME {
|
||||
if data_type == time.Time {
|
||||
// NOTE: The leap second data is discarded.
|
||||
res, consumed := time.rfc3339_to_time_utc(str)
|
||||
if consumed == 0 {
|
||||
// The RFC 3339 parsing facilities provide no indication as to what
|
||||
// went wrong, so just treat it as a regular parsing error.
|
||||
out_error^ = Parse_Error {
|
||||
.Bad_Value,
|
||||
GENERIC_RFC_3339_ERROR,
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
(cast(^time.Time)ptr)^ = res
|
||||
return
|
||||
} else if data_type == datetime.DateTime {
|
||||
// NOTE: The UTC offset and leap second data are discarded.
|
||||
res, _, _, consumed := time.rfc3339_to_components(str)
|
||||
if consumed == 0 {
|
||||
out_error^ = Parse_Error {
|
||||
.Bad_Value,
|
||||
GENERIC_RFC_3339_ERROR,
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
(cast(^datetime.DateTime)ptr)^ = res
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
when IMPORTING_NET {
|
||||
if try_net_parse_workaround(data_type, str, ptr, out_error) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
out_error ^= Parse_Error {
|
||||
// The caller will add more details.
|
||||
.Unsupported_Type,
|
||||
"",
|
||||
}
|
||||
}
|
||||
|
||||
@(optimization_mode="size")
|
||||
set_unbounded_integer_by_type :: proc(ptr: rawptr, value: $T, data_type: typeid) where intrinsics.type_is_integer(T) {
|
||||
switch data_type {
|
||||
case i8: (cast(^i8) ptr)^ = cast(i8) value
|
||||
case i16: (cast(^i16) ptr)^ = cast(i16) value
|
||||
case i32: (cast(^i32) ptr)^ = cast(i32) value
|
||||
case i64: (cast(^i64) ptr)^ = cast(i64) value
|
||||
case i128: (cast(^i128) ptr)^ = cast(i128) value
|
||||
|
||||
case int: (cast(^int) ptr)^ = cast(int) value
|
||||
|
||||
case i16le: (cast(^i16le) ptr)^ = cast(i16le) value
|
||||
case i32le: (cast(^i32le) ptr)^ = cast(i32le) value
|
||||
case i64le: (cast(^i64le) ptr)^ = cast(i64le) value
|
||||
case i128le: (cast(^i128le) ptr)^ = cast(i128le) value
|
||||
|
||||
case i16be: (cast(^i16be) ptr)^ = cast(i16be) value
|
||||
case i32be: (cast(^i32be) ptr)^ = cast(i32be) value
|
||||
case i64be: (cast(^i64be) ptr)^ = cast(i64be) value
|
||||
case i128be: (cast(^i128be) ptr)^ = cast(i128be) value
|
||||
|
||||
case u8: (cast(^u8) ptr)^ = cast(u8) value
|
||||
case u16: (cast(^u16) ptr)^ = cast(u16) value
|
||||
case u32: (cast(^u32) ptr)^ = cast(u32) value
|
||||
case u64: (cast(^u64) ptr)^ = cast(u64) value
|
||||
case u128: (cast(^u128) ptr)^ = cast(u128) value
|
||||
|
||||
case uint: (cast(^uint) ptr)^ = cast(uint) value
|
||||
case uintptr: (cast(^uintptr)ptr)^ = cast(uintptr) value
|
||||
|
||||
case u16le: (cast(^u16le) ptr)^ = cast(u16le) value
|
||||
case u32le: (cast(^u32le) ptr)^ = cast(u32le) value
|
||||
case u64le: (cast(^u64le) ptr)^ = cast(u64le) value
|
||||
case u128le: (cast(^u128le) ptr)^ = cast(u128le) value
|
||||
|
||||
case u16be: (cast(^u16be) ptr)^ = cast(u16be) value
|
||||
case u32be: (cast(^u32be) ptr)^ = cast(u32be) value
|
||||
case u64be: (cast(^u64be) ptr)^ = cast(u64be) value
|
||||
case u128be: (cast(^u128be) ptr)^ = cast(u128be) value
|
||||
|
||||
case rune: (cast(^rune) ptr)^ = cast(rune) value
|
||||
|
||||
case:
|
||||
fmt.panicf("Unsupported integer backing type: %v", data_type)
|
||||
}
|
||||
}
|
||||
|
||||
@(optimization_mode="size")
|
||||
parse_and_set_pointer_by_type :: proc(ptr: rawptr, str: string, type_info: ^runtime.Type_Info, arg_tag: string) -> (error: Error) {
|
||||
#partial switch specific_type_info in type_info.variant {
|
||||
case runtime.Type_Info_Named:
|
||||
if global_custom_type_setter != nil {
|
||||
// The program gets to go first.
|
||||
error_message, handled, alloc_error := global_custom_type_setter(ptr, type_info.id, str, arg_tag)
|
||||
|
||||
if alloc_error != nil {
|
||||
// There was an allocation error. Bail out.
|
||||
return Parse_Error {
|
||||
alloc_error,
|
||||
"Custom type setter encountered allocation error.",
|
||||
}
|
||||
}
|
||||
|
||||
if handled {
|
||||
// The program handled the type.
|
||||
|
||||
if len(error_message) != 0 {
|
||||
// However, there was an error. Pass it along.
|
||||
error = Parse_Error {
|
||||
.Bad_Value,
|
||||
error_message,
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Might be a named enum. Need to check here first, since we handle all enums.
|
||||
if enum_type_info, is_enum := specific_type_info.base.variant.(runtime.Type_Info_Enum); is_enum {
|
||||
if value, ok := reflect.enum_from_name_any(type_info.id, str); ok {
|
||||
set_unbounded_integer_by_type(ptr, value, enum_type_info.base.id)
|
||||
} else {
|
||||
return Parse_Error {
|
||||
.Bad_Value,
|
||||
fmt.tprintf("Invalid value name. Valid names are: %s", enum_type_info.names),
|
||||
}
|
||||
}
|
||||
} else {
|
||||
parse_and_set_pointer_by_named_type(ptr, str, type_info.id, arg_tag, &error)
|
||||
|
||||
if error != nil {
|
||||
// So far, it's none of the types that we recognize.
|
||||
// Check to see if we can set it by base type, if allowed.
|
||||
if _, is_indistinct := get_struct_subtag(arg_tag, SUBTAG_INDISTINCT); is_indistinct {
|
||||
return parse_and_set_pointer_by_type(ptr, str, specific_type_info.base, arg_tag)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case runtime.Type_Info_Dynamic_Array:
|
||||
ptr := cast(^runtime.Raw_Dynamic_Array)ptr
|
||||
|
||||
// Try to convert the value first.
|
||||
elem_backing, alloc_error := mem.alloc_bytes(specific_type_info.elem.size, specific_type_info.elem.align)
|
||||
if alloc_error != nil {
|
||||
return Parse_Error {
|
||||
alloc_error,
|
||||
"Failed to allocate element backing for dynamic array.",
|
||||
}
|
||||
}
|
||||
defer delete(elem_backing)
|
||||
parse_and_set_pointer_by_type(raw_data(elem_backing), str, specific_type_info.elem, arg_tag) or_return
|
||||
|
||||
if !runtime.__dynamic_array_resize(ptr, specific_type_info.elem.size, specific_type_info.elem.align, ptr.len + 1) {
|
||||
// NOTE: This is purely an assumption that it's OOM.
|
||||
// Regardless, the resize failed.
|
||||
return Parse_Error {
|
||||
runtime.Allocator_Error.Out_Of_Memory,
|
||||
"Failed to resize dynamic array.",
|
||||
}
|
||||
}
|
||||
|
||||
subptr := cast(rawptr)(
|
||||
cast(uintptr)ptr.data +
|
||||
cast(uintptr)((ptr.len - 1) * specific_type_info.elem.size))
|
||||
mem.copy(subptr, raw_data(elem_backing), len(elem_backing))
|
||||
|
||||
case runtime.Type_Info_Enum:
|
||||
// This is a nameless enum.
|
||||
// The code here is virtually the same as above for named enums.
|
||||
if value, ok := reflect.enum_from_name_any(type_info.id, str); ok {
|
||||
set_unbounded_integer_by_type(ptr, value, specific_type_info.base.id)
|
||||
} else {
|
||||
return Parse_Error {
|
||||
.Bad_Value,
|
||||
fmt.tprintf("Invalid value name. Valid names are: %s", specific_type_info.names),
|
||||
}
|
||||
}
|
||||
|
||||
case:
|
||||
if !parse_and_set_pointer_by_base_type(ptr, str, type_info) {
|
||||
return Parse_Error {
|
||||
// The caller will add more details.
|
||||
.Bad_Value,
|
||||
"",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
get_struct_subtag :: get_subtag
|
||||
|
||||
get_field_name :: proc(field: reflect.Struct_Field) -> string {
|
||||
if args_tag, ok := reflect.struct_tag_lookup(field.tag, TAG_ARGS); ok {
|
||||
if name_subtag, name_ok := get_struct_subtag(args_tag, SUBTAG_NAME); name_ok {
|
||||
return name_subtag
|
||||
}
|
||||
}
|
||||
|
||||
name, _ := strings.replace_all(field.name, "_", "-", context.temp_allocator)
|
||||
return name
|
||||
}
|
||||
|
||||
get_field_pos :: proc(field: reflect.Struct_Field) -> (int, bool) {
|
||||
if args_tag, ok := reflect.struct_tag_lookup(field.tag, TAG_ARGS); ok {
|
||||
if pos_subtag, pos_ok := get_struct_subtag(args_tag, SUBTAG_POS); pos_ok {
|
||||
if value, parse_ok := strconv.parse_u64_of_base(pos_subtag, 10); parse_ok {
|
||||
return cast(int)value, true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0, false
|
||||
}
|
||||
|
||||
// Get a struct field by its field name or `name` subtag.
|
||||
get_field_by_name :: proc(model: ^$T, name: string) -> (result: reflect.Struct_Field, index: int, error: Error) {
|
||||
for field, i in reflect.struct_fields_zipped(T) {
|
||||
if get_field_name(field) == name {
|
||||
return field, i, nil
|
||||
}
|
||||
}
|
||||
|
||||
error = Parse_Error {
|
||||
.Missing_Flag,
|
||||
fmt.tprintf("Unable to find any flag named `%s`.", name),
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Get a struct field by its `pos` subtag.
|
||||
get_field_by_pos :: proc(model: ^$T, pos: int) -> (result: reflect.Struct_Field, index: int, ok: bool) {
|
||||
for field, i in reflect.struct_fields_zipped(T) {
|
||||
args_tag, tag_ok := reflect.struct_tag_lookup(field.tag, TAG_ARGS)
|
||||
if !tag_ok {
|
||||
continue
|
||||
}
|
||||
|
||||
pos_subtag, pos_ok := get_struct_subtag(args_tag, SUBTAG_POS)
|
||||
if !pos_ok {
|
||||
continue
|
||||
}
|
||||
|
||||
value, parse_ok := strconv.parse_u64_of_base(pos_subtag, 10)
|
||||
if parse_ok && cast(int)value == pos {
|
||||
return field, i, true
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
31
core/flags/internal_rtti_nonbsd.odin
Normal file
31
core/flags/internal_rtti_nonbsd.odin
Normal file
@@ -0,0 +1,31 @@
|
||||
//+private
|
||||
//+build !freebsd !netbsd !openbsd
|
||||
package flags
|
||||
|
||||
import "core:net"
|
||||
|
||||
// This proc exists purely as a workaround for import restrictions.
|
||||
// Returns true if caller should return early.
|
||||
try_net_parse_workaround :: #force_inline proc (
|
||||
data_type: typeid,
|
||||
str: string,
|
||||
ptr: rawptr,
|
||||
out_error: ^Error,
|
||||
) -> bool {
|
||||
if data_type == net.Host_Or_Endpoint {
|
||||
addr, net_error := net.parse_hostname_or_endpoint(str)
|
||||
if net_error != nil {
|
||||
// We pass along `net.Error` here.
|
||||
out_error^ = Parse_Error {
|
||||
net_error,
|
||||
"Invalid Host/Endpoint.",
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
(cast(^net.Host_Or_Endpoint)ptr)^ = addr
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
244
core/flags/internal_validation.odin
Normal file
244
core/flags/internal_validation.odin
Normal file
@@ -0,0 +1,244 @@
|
||||
//+private
|
||||
package flags
|
||||
|
||||
@require import "base:runtime"
|
||||
@require import "core:container/bit_array"
|
||||
@require import "core:fmt"
|
||||
@require import "core:mem"
|
||||
@require import "core:os"
|
||||
@require import "core:reflect"
|
||||
@require import "core:strconv"
|
||||
@require import "core:strings"
|
||||
|
||||
// This proc is used to assert that `T` meets the expectations of the library.
|
||||
@(optimization_mode="size", disabled=ODIN_DISABLE_ASSERT)
|
||||
validate_structure :: proc(model_type: $T, style: Parsing_Style, loc := #caller_location) {
|
||||
positionals_assigned_so_far: bit_array.Bit_Array
|
||||
defer bit_array.destroy(&positionals_assigned_so_far)
|
||||
|
||||
check_fields: for field in reflect.struct_fields_zipped(T) {
|
||||
if style == .Unix {
|
||||
#partial switch specific_type_info in field.type.variant {
|
||||
case runtime.Type_Info_Map:
|
||||
fmt.panicf("%T.%s is a map type, and these are not supported in UNIX-style parsing mode.",
|
||||
model_type, field.name, loc = loc)
|
||||
}
|
||||
}
|
||||
|
||||
name_is_safe := true
|
||||
defer {
|
||||
fmt.assertf(name_is_safe, "%T.%s is using a reserved name.",
|
||||
model_type, field.name, loc = loc)
|
||||
}
|
||||
|
||||
switch field.name {
|
||||
case RESERVED_HELP_FLAG, RESERVED_HELP_FLAG_SHORT:
|
||||
name_is_safe = false
|
||||
}
|
||||
|
||||
args_tag, ok := reflect.struct_tag_lookup(field.tag, TAG_ARGS)
|
||||
if !ok {
|
||||
// If it has no args tag, then we've checked all we need to.
|
||||
// Most of this proc is validating that the subtags are sane.
|
||||
continue
|
||||
}
|
||||
|
||||
if name, has_name := get_struct_subtag(args_tag, SUBTAG_NAME); has_name {
|
||||
fmt.assertf(len(name) > 0, "%T.%s has a zero-length `%s`.",
|
||||
model_type, field.name, SUBTAG_NAME, loc = loc)
|
||||
|
||||
fmt.assertf(strings.index(name, " ") == -1, "%T.%s has a `%s` with spaces in it.",
|
||||
model_type, field.name, SUBTAG_NAME, loc = loc)
|
||||
|
||||
switch name {
|
||||
case RESERVED_HELP_FLAG, RESERVED_HELP_FLAG_SHORT:
|
||||
name_is_safe = false
|
||||
continue check_fields
|
||||
case:
|
||||
name_is_safe = true
|
||||
}
|
||||
}
|
||||
|
||||
if pos_str, has_pos := get_struct_subtag(args_tag, SUBTAG_POS); has_pos {
|
||||
#partial switch specific_type_info in field.type.variant {
|
||||
case runtime.Type_Info_Map:
|
||||
fmt.panicf("%T.%s has `%s` defined, and this does not make sense on a map type.",
|
||||
model_type, field.name, SUBTAG_POS, loc = loc)
|
||||
}
|
||||
|
||||
pos_value, pos_ok := strconv.parse_u64_of_base(pos_str, 10)
|
||||
fmt.assertf(pos_ok, "%T.%s has `%s` defined as %q but cannot be parsed a base-10 integer >= 0.",
|
||||
model_type, field.name, SUBTAG_POS, pos_str, loc = loc)
|
||||
fmt.assertf(!bit_array.get(&positionals_assigned_so_far, pos_value), "%T.%s has `%s` set to #%i, but that position has already been assigned to another flag.",
|
||||
model_type, field.name, SUBTAG_POS, pos_value, loc = loc)
|
||||
bit_array.set(&positionals_assigned_so_far, pos_value)
|
||||
}
|
||||
|
||||
required_min, required_max: int
|
||||
if requirement, is_required := get_struct_subtag(args_tag, SUBTAG_REQUIRED); is_required {
|
||||
fmt.assertf(!reflect.is_boolean(field.type), "%T.%s is a required boolean. This is disallowed.",
|
||||
model_type, field.name, loc = loc)
|
||||
|
||||
fmt.assertf(field.name != INTERNAL_VARIADIC_FLAG, "%T.%s is defined as required. This is disallowed.",
|
||||
model_type, field.name, loc = loc)
|
||||
|
||||
if len(requirement) > 0 {
|
||||
if required_min, required_max, ok = parse_requirements(requirement); ok {
|
||||
#partial switch specific_type_info in field.type.variant {
|
||||
case runtime.Type_Info_Dynamic_Array:
|
||||
fmt.assertf(required_min != required_max, "%T.%s has `%s` defined as %q, but the minimum and maximum are the same. Increase the maximum by 1 for an exact number of arguments: (%i<%i)",
|
||||
model_type,
|
||||
field.name,
|
||||
SUBTAG_REQUIRED,
|
||||
requirement,
|
||||
required_min,
|
||||
1 + required_max,
|
||||
loc = loc)
|
||||
|
||||
fmt.assertf(required_min < required_max, "%T.%s has `%s` defined as %q, but the minimum and maximum are swapped.",
|
||||
model_type, field.name, SUBTAG_REQUIRED, requirement, loc = loc)
|
||||
|
||||
case:
|
||||
fmt.panicf("%T.%s has `%s` defined as %q, but ranges are only supported on dynamic arrays.",
|
||||
model_type, field.name, SUBTAG_REQUIRED, requirement, loc = loc)
|
||||
}
|
||||
} else {
|
||||
fmt.panicf("%T.%s has `%s` defined as %q, but it cannot be parsed as a valid range.",
|
||||
model_type, field.name, SUBTAG_REQUIRED, requirement, loc = loc)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if length, is_variadic := get_struct_subtag(args_tag, SUBTAG_VARIADIC); is_variadic {
|
||||
if value, parse_ok := strconv.parse_u64_of_base(length, 10); parse_ok {
|
||||
fmt.assertf(value > 0,
|
||||
"%T.%s has `%s` set to %i. It must be greater than zero.",
|
||||
model_type, field.name, value, SUBTAG_VARIADIC, loc = loc)
|
||||
fmt.assertf(value != 1,
|
||||
"%T.%s has `%s` set to 1. This has no effect.",
|
||||
model_type, field.name, SUBTAG_VARIADIC, loc = loc)
|
||||
}
|
||||
|
||||
#partial switch specific_type_info in field.type.variant {
|
||||
case runtime.Type_Info_Dynamic_Array:
|
||||
fmt.assertf(style != .Odin,
|
||||
"%T.%s has `%s` defined, but this only makes sense in UNIX-style parsing mode.",
|
||||
model_type, field.name, SUBTAG_VARIADIC, loc = loc)
|
||||
case:
|
||||
fmt.panicf("%T.%s has `%s` defined, but this only makes sense on dynamic arrays.",
|
||||
model_type, field.name, SUBTAG_VARIADIC, loc = loc)
|
||||
}
|
||||
}
|
||||
|
||||
allowed_to_define_file_perms: bool = ---
|
||||
#partial switch specific_type_info in field.type.variant {
|
||||
case runtime.Type_Info_Map:
|
||||
allowed_to_define_file_perms = specific_type_info.value.id == os.Handle
|
||||
case runtime.Type_Info_Dynamic_Array:
|
||||
allowed_to_define_file_perms = specific_type_info.elem.id == os.Handle
|
||||
case:
|
||||
allowed_to_define_file_perms = field.type.id == os.Handle
|
||||
}
|
||||
|
||||
if _, has_file := get_struct_subtag(args_tag, SUBTAG_FILE); has_file {
|
||||
fmt.assertf(allowed_to_define_file_perms, "%T.%s has `%s` defined, but it is not nor does it contain an `os.Handle` type.",
|
||||
model_type, field.name, SUBTAG_FILE, loc = loc)
|
||||
}
|
||||
|
||||
if _, has_perms := get_struct_subtag(args_tag, SUBTAG_PERMS); has_perms {
|
||||
fmt.assertf(allowed_to_define_file_perms, "%T.%s has `%s` defined, but it is not nor does it contain an `os.Handle` type.",
|
||||
model_type, field.name, SUBTAG_PERMS, loc = loc)
|
||||
}
|
||||
|
||||
#partial switch specific_type_info in field.type.variant {
|
||||
case runtime.Type_Info_Map:
|
||||
fmt.assertf(reflect.is_string(specific_type_info.key), "%T.%s is defined as a map[%T]. Only string types are currently supported as map keys.",
|
||||
model_type,
|
||||
field.name,
|
||||
specific_type_info.key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Validate that all the required arguments are set and that the set arguments
|
||||
// are up to the program's expectations.
|
||||
@(optimization_mode="size")
|
||||
validate_arguments :: proc(model: ^$T, parser: ^Parser) -> Error {
|
||||
check_fields: for field, index in reflect.struct_fields_zipped(T) {
|
||||
was_set := bit_array.get(&parser.fields_set, index)
|
||||
|
||||
field_name := get_field_name(field)
|
||||
args_tag := reflect.struct_tag_get(field.tag, TAG_ARGS)
|
||||
requirement, is_required := get_struct_subtag(args_tag, SUBTAG_REQUIRED)
|
||||
|
||||
required_min, required_max: int
|
||||
has_requirements: bool
|
||||
if is_required {
|
||||
required_min, required_max, has_requirements = parse_requirements(requirement)
|
||||
}
|
||||
|
||||
if has_requirements && required_min == 0 {
|
||||
// Allow `0<n` or `<n` to bypass the required condition.
|
||||
is_required = false
|
||||
}
|
||||
|
||||
if _, is_array := field.type.variant.(runtime.Type_Info_Dynamic_Array); is_array && has_requirements {
|
||||
// If it's an array, make sure it meets the required number of arguments.
|
||||
ptr := cast(^runtime.Raw_Dynamic_Array)(cast(uintptr)model + field.offset)
|
||||
if required_min == required_max - 1 && ptr.len != required_min {
|
||||
return Validation_Error {
|
||||
fmt.tprintf("The flag `%s` had %i option%s set, but it requires exactly %i.",
|
||||
field_name,
|
||||
ptr.len,
|
||||
"" if ptr.len == 1 else "s",
|
||||
required_min),
|
||||
}
|
||||
} else if required_min > ptr.len || ptr.len >= required_max {
|
||||
if required_max == max(int) {
|
||||
return Validation_Error {
|
||||
fmt.tprintf("The flag `%s` had %i option%s set, but it requires at least %i.",
|
||||
field_name,
|
||||
ptr.len,
|
||||
"" if ptr.len == 1 else "s",
|
||||
required_min),
|
||||
}
|
||||
} else {
|
||||
return Validation_Error {
|
||||
fmt.tprintf("The flag `%s` had %i option%s set, but it requires at least %i and at most %i.",
|
||||
field_name,
|
||||
ptr.len,
|
||||
"" if ptr.len == 1 else "s",
|
||||
required_min,
|
||||
required_max - 1),
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if !was_set {
|
||||
if is_required {
|
||||
return Validation_Error {
|
||||
fmt.tprintf("The required flag `%s` was not set.", field_name),
|
||||
}
|
||||
}
|
||||
|
||||
// Not set, not required; moving on.
|
||||
continue
|
||||
}
|
||||
|
||||
// All default checks have passed. The program gets a look at it now.
|
||||
|
||||
if global_custom_flag_checker != nil {
|
||||
ptr := cast(rawptr)(cast(uintptr)model + field.offset)
|
||||
error := global_custom_flag_checker(model,
|
||||
field.name,
|
||||
mem.make_any(ptr, field.type.id),
|
||||
args_tag)
|
||||
|
||||
if len(error) > 0 {
|
||||
// The program reported an error message.
|
||||
return Validation_Error { error }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
101
core/flags/parsing.odin
Normal file
101
core/flags/parsing.odin
Normal file
@@ -0,0 +1,101 @@
|
||||
package flags
|
||||
|
||||
@require import "core:container/bit_array"
|
||||
@require import "core:fmt"
|
||||
|
||||
Parsing_Style :: enum {
|
||||
// Odin-style: `-flag`, `-flag:option`, `-map:key=value`
|
||||
Odin,
|
||||
// UNIX-style: `-flag` or `--flag`, `--flag=argument`, `--flag argument repeating-argument`
|
||||
Unix,
|
||||
}
|
||||
|
||||
/*
|
||||
Parse a slice of command-line arguments into an annotated struct.
|
||||
|
||||
*Allocates Using Provided Allocator*
|
||||
|
||||
By default, this proc will only allocate memory outside of its lifetime if it
|
||||
has to append to a dynamic array, set a map value, or set a cstring.
|
||||
|
||||
The program is expected to free any allocations on `model` as a result of parsing.
|
||||
|
||||
Inputs:
|
||||
- model: A pointer to an annotated struct with flag definitions.
|
||||
- args: A slice of strings, usually `os.args[1:]`.
|
||||
- style: The argument parsing style.
|
||||
- validate_args: If `true`, will ensure that all required arguments are set if no errors occurred.
|
||||
- strict: If `true`, will return on first error. Otherwise, parsing continues.
|
||||
- allocator: (default: context.allocator)
|
||||
- loc: The caller location for debugging purposes (default: #caller_location)
|
||||
|
||||
Returns:
|
||||
- error: A union of errors; parsing, file open, a help request, or validation.
|
||||
*/
|
||||
@(optimization_mode="size")
|
||||
parse :: proc(
|
||||
model: ^$T,
|
||||
args: []string,
|
||||
style: Parsing_Style = .Odin,
|
||||
validate_args: bool = true,
|
||||
strict: bool = true,
|
||||
allocator := context.allocator,
|
||||
loc := #caller_location,
|
||||
) -> (error: Error) {
|
||||
context.allocator = allocator
|
||||
validate_structure(model^, style, loc)
|
||||
|
||||
parser: Parser
|
||||
defer {
|
||||
bit_array.destroy(&parser.filled_pos)
|
||||
bit_array.destroy(&parser.fields_set)
|
||||
}
|
||||
|
||||
switch style {
|
||||
case .Odin:
|
||||
for arg in args {
|
||||
error = parse_one_odin_arg(model, &parser, arg)
|
||||
if strict && error != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
case .Unix:
|
||||
// Support for `-flag argument (repeating-argument ...)`
|
||||
future_args: int
|
||||
current_flag: string
|
||||
|
||||
for i := 0; i < len(args); i += 1 {
|
||||
#no_bounds_check arg := args[i]
|
||||
future_args, current_flag, error = parse_one_unix_arg(model, &parser, arg)
|
||||
if strict && error != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for starting_future_args := future_args; future_args > 0; future_args -= 1 {
|
||||
i += 1
|
||||
if i == len(args) {
|
||||
if future_args == starting_future_args {
|
||||
return Parse_Error {
|
||||
.No_Value,
|
||||
fmt.tprintf("Expected a value for `%s` but none was given.", current_flag),
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
#no_bounds_check arg = args[i]
|
||||
|
||||
error = set_option(model, &parser, current_flag, arg)
|
||||
if strict && error != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if error == nil && validate_args {
|
||||
return validate_arguments(model, &parser)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
43
core/flags/rtti.odin
Normal file
43
core/flags/rtti.odin
Normal file
@@ -0,0 +1,43 @@
|
||||
package flags
|
||||
|
||||
import "base:runtime"
|
||||
|
||||
/*
|
||||
Handle setting custom data types.
|
||||
|
||||
Inputs:
|
||||
- data: A raw pointer to the field where the data will go.
|
||||
- data_type: Type information on the underlying field.
|
||||
- unparsed_value: The unparsed string that the flag is being set to.
|
||||
- args_tag: The `args` tag from the struct's field.
|
||||
|
||||
Returns:
|
||||
- error: An error message, or an empty string if no error occurred.
|
||||
- handled: A boolean indicating if the setter handles this type.
|
||||
- alloc_error: If an allocation error occurred, return it here.
|
||||
*/
|
||||
Custom_Type_Setter :: #type proc(
|
||||
data: rawptr,
|
||||
data_type: typeid,
|
||||
unparsed_value: string,
|
||||
args_tag: string,
|
||||
) -> (
|
||||
error: string,
|
||||
handled: bool,
|
||||
alloc_error: runtime.Allocator_Error,
|
||||
)
|
||||
|
||||
@(private)
|
||||
global_custom_type_setter: Custom_Type_Setter
|
||||
|
||||
/*
|
||||
Set the global custom type setter.
|
||||
|
||||
Note that only one can be active at a time.
|
||||
|
||||
Inputs:
|
||||
- setter: The type setter. Pass `nil` to disable any previously set setter.
|
||||
*/
|
||||
register_type_setter :: proc(setter: Custom_Type_Setter) {
|
||||
global_custom_type_setter = setter
|
||||
}
|
||||
293
core/flags/usage.odin
Normal file
293
core/flags/usage.odin
Normal file
@@ -0,0 +1,293 @@
|
||||
package flags
|
||||
|
||||
import "base:runtime"
|
||||
import "core:fmt"
|
||||
import "core:io"
|
||||
import "core:reflect"
|
||||
import "core:slice"
|
||||
import "core:strconv"
|
||||
import "core:strings"
|
||||
|
||||
/*
|
||||
Write out the documentation for the command-line arguments to a stream.
|
||||
|
||||
Inputs:
|
||||
- out: The stream to write to.
|
||||
- data_type: The typeid of the data structure to describe.
|
||||
- program: The name of the program, usually the first argument to `os.args`.
|
||||
- style: The argument parsing style, required to show flags in the proper style.
|
||||
*/
|
||||
@(optimization_mode="size")
|
||||
write_usage :: proc(out: io.Writer, data_type: typeid, program: string = "", style: Parsing_Style = .Odin) {
|
||||
// All flags get their tags parsed so they can be reasoned about later.
|
||||
Flag :: struct {
|
||||
name: string,
|
||||
usage: string,
|
||||
type_description: string,
|
||||
full_length: int,
|
||||
pos: int,
|
||||
required_min, required_max: int,
|
||||
is_positional: bool,
|
||||
is_required: bool,
|
||||
is_boolean: bool,
|
||||
is_variadic: bool,
|
||||
variadic_length: int,
|
||||
}
|
||||
|
||||
//
|
||||
// POSITIONAL+REQUIRED, POSITIONAL, REQUIRED, NON_REQUIRED+NON_POSITIONAL, ...
|
||||
//
|
||||
sort_flags :: proc(i, j: Flag) -> slice.Ordering {
|
||||
// `varg` goes to the end.
|
||||
if i.name == INTERNAL_VARIADIC_FLAG {
|
||||
return .Greater
|
||||
} else if j.name == INTERNAL_VARIADIC_FLAG {
|
||||
return .Less
|
||||
}
|
||||
|
||||
// Handle positionals.
|
||||
if i.is_positional {
|
||||
if j.is_positional {
|
||||
return slice.cmp(i.pos, j.pos)
|
||||
} else {
|
||||
return .Less
|
||||
}
|
||||
} else {
|
||||
if j.is_positional {
|
||||
return .Greater
|
||||
}
|
||||
}
|
||||
|
||||
// Then required flags.
|
||||
if i.is_required {
|
||||
if !j.is_required {
|
||||
return .Less
|
||||
}
|
||||
} else if j.is_required {
|
||||
return .Greater
|
||||
}
|
||||
|
||||
// Finally, sort by name.
|
||||
return slice.cmp(i.name, j.name)
|
||||
}
|
||||
|
||||
describe_array_requirements :: proc(flag: Flag) -> (spec: string) {
|
||||
if flag.is_required {
|
||||
if flag.required_min == flag.required_max - 1 {
|
||||
spec = fmt.tprintf(", exactly %i", flag.required_min)
|
||||
} else if flag.required_min > 0 && flag.required_max == max(int) {
|
||||
spec = fmt.tprintf(", at least %i", flag.required_min)
|
||||
} else if flag.required_min == 0 && flag.required_max > 1 {
|
||||
spec = fmt.tprintf(", at most %i", flag.required_max - 1)
|
||||
} else if flag.required_min > 0 && flag.required_max > 1 {
|
||||
spec = fmt.tprintf(", between %i and %i", flag.required_min, flag.required_max - 1)
|
||||
} else {
|
||||
spec = ", required"
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
builder := strings.builder_make()
|
||||
defer strings.builder_destroy(&builder)
|
||||
|
||||
flag_prefix, flag_assignment: string = ---, ---
|
||||
switch style {
|
||||
case .Odin: flag_prefix = "-"; flag_assignment = ":"
|
||||
case .Unix: flag_prefix = "--"; flag_assignment = " "
|
||||
}
|
||||
|
||||
visible_flags: [dynamic]Flag
|
||||
defer delete(visible_flags)
|
||||
|
||||
longest_flag_length: int
|
||||
|
||||
for field in reflect.struct_fields_zipped(data_type) {
|
||||
flag: Flag
|
||||
|
||||
if args_tag, ok := reflect.struct_tag_lookup(field.tag, TAG_ARGS); ok {
|
||||
if _, is_hidden := get_struct_subtag(args_tag, SUBTAG_HIDDEN); is_hidden {
|
||||
// Hidden flags stay hidden.
|
||||
continue
|
||||
}
|
||||
if pos_str, is_pos := get_struct_subtag(args_tag, SUBTAG_POS); is_pos {
|
||||
flag.is_positional = true
|
||||
if pos, parse_ok := strconv.parse_u64_of_base(pos_str, 10); parse_ok {
|
||||
flag.pos = cast(int)pos
|
||||
}
|
||||
}
|
||||
if requirement, is_required := get_struct_subtag(args_tag, SUBTAG_REQUIRED); is_required {
|
||||
flag.is_required = true
|
||||
flag.required_min, flag.required_max, _ = parse_requirements(requirement)
|
||||
}
|
||||
if length_str, is_variadic := get_struct_subtag(args_tag, SUBTAG_VARIADIC); is_variadic {
|
||||
flag.is_variadic = true
|
||||
if length, parse_ok := strconv.parse_u64_of_base(length_str, 10); parse_ok {
|
||||
flag.variadic_length = cast(int)length
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
flag.name = get_field_name(field)
|
||||
flag.is_boolean = reflect.is_boolean(field.type)
|
||||
|
||||
if usage, ok := reflect.struct_tag_lookup(field.tag, TAG_USAGE); ok {
|
||||
flag.usage = usage
|
||||
} else {
|
||||
flag.usage = UNDOCUMENTED_FLAG
|
||||
}
|
||||
|
||||
#partial switch specific_type_info in field.type.variant {
|
||||
case runtime.Type_Info_Map:
|
||||
flag.type_description = fmt.tprintf("<%v>=<%v>%s",
|
||||
specific_type_info.key.id,
|
||||
specific_type_info.value.id,
|
||||
", required" if flag.is_required else "")
|
||||
|
||||
case runtime.Type_Info_Dynamic_Array:
|
||||
requirement_spec := describe_array_requirements(flag)
|
||||
|
||||
if flag.is_variadic || flag.name == INTERNAL_VARIADIC_FLAG {
|
||||
if flag.variadic_length == 0 {
|
||||
flag.type_description = fmt.tprintf("<%v, ...>%s",
|
||||
specific_type_info.elem.id,
|
||||
requirement_spec)
|
||||
} else {
|
||||
flag.type_description = fmt.tprintf("<%v, %i at once>%s",
|
||||
specific_type_info.elem.id,
|
||||
flag.variadic_length,
|
||||
requirement_spec)
|
||||
}
|
||||
} else {
|
||||
flag.type_description = fmt.tprintf("<%v>%s", specific_type_info.elem.id,
|
||||
requirement_spec if len(requirement_spec) > 0 else ", multiple")
|
||||
}
|
||||
|
||||
case:
|
||||
if flag.is_boolean {
|
||||
/*
|
||||
if flag.is_required {
|
||||
flag.type_description = ", required"
|
||||
}
|
||||
*/
|
||||
} else {
|
||||
flag.type_description = fmt.tprintf("<%v>%s",
|
||||
field.type.id,
|
||||
", required" if flag.is_required else "")
|
||||
}
|
||||
}
|
||||
|
||||
if flag.name == INTERNAL_VARIADIC_FLAG {
|
||||
flag.full_length = len(flag.type_description)
|
||||
} else if flag.is_boolean {
|
||||
flag.full_length = len(flag_prefix) + len(flag.name) + len(flag.type_description)
|
||||
} else {
|
||||
flag.full_length = len(flag_prefix) + len(flag.name) + len(flag_assignment) + len(flag.type_description)
|
||||
}
|
||||
|
||||
longest_flag_length = max(longest_flag_length, flag.full_length)
|
||||
|
||||
append(&visible_flags, flag)
|
||||
}
|
||||
|
||||
slice.sort_by_cmp(visible_flags[:], sort_flags)
|
||||
|
||||
// All the flags have been figured out now.
|
||||
|
||||
if len(program) > 0 {
|
||||
keep_it_short := len(visible_flags) >= ONE_LINE_FLAG_CUTOFF_COUNT
|
||||
|
||||
strings.write_string(&builder, "Usage:\n\t")
|
||||
strings.write_string(&builder, program)
|
||||
|
||||
for flag in visible_flags {
|
||||
if keep_it_short && !(flag.is_required || flag.is_positional || flag.name == INTERNAL_VARIADIC_FLAG) {
|
||||
continue
|
||||
}
|
||||
|
||||
strings.write_byte(&builder, ' ')
|
||||
|
||||
if flag.name == INTERNAL_VARIADIC_FLAG {
|
||||
strings.write_string(&builder, "...")
|
||||
continue
|
||||
}
|
||||
|
||||
if !flag.is_required { strings.write_byte(&builder, '[') }
|
||||
if !flag.is_positional { strings.write_string(&builder, flag_prefix) }
|
||||
strings.write_string(&builder, flag.name)
|
||||
if !flag.is_required { strings.write_byte(&builder, ']') }
|
||||
}
|
||||
|
||||
strings.write_byte(&builder, '\n')
|
||||
}
|
||||
|
||||
if len(visible_flags) == 0 {
|
||||
// No visible flags. An unusual situation, but prevent any extra work.
|
||||
fmt.wprint(out, strings.to_string(builder))
|
||||
return
|
||||
}
|
||||
|
||||
strings.write_string(&builder, "Flags:\n")
|
||||
|
||||
// Divide the positional/required arguments and the non-required arguments.
|
||||
divider_index := -1
|
||||
for flag, i in visible_flags {
|
||||
if !flag.is_positional && !flag.is_required {
|
||||
divider_index = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if divider_index == 0 {
|
||||
divider_index = -1
|
||||
}
|
||||
|
||||
for flag, i in visible_flags {
|
||||
if i == divider_index {
|
||||
SPACING :: 2 // Number of spaces before the '|' from below.
|
||||
strings.write_byte(&builder, '\t')
|
||||
spacing := strings.repeat(" ", SPACING + longest_flag_length, context.temp_allocator)
|
||||
strings.write_string(&builder, spacing)
|
||||
strings.write_string(&builder, "|\n")
|
||||
}
|
||||
|
||||
strings.write_byte(&builder, '\t')
|
||||
|
||||
if flag.name == INTERNAL_VARIADIC_FLAG {
|
||||
strings.write_string(&builder, flag.type_description)
|
||||
} else {
|
||||
strings.write_string(&builder, flag_prefix)
|
||||
strings.write_string(&builder, flag.name)
|
||||
if !flag.is_boolean {
|
||||
strings.write_string(&builder, flag_assignment)
|
||||
}
|
||||
strings.write_string(&builder, flag.type_description)
|
||||
}
|
||||
|
||||
if strings.contains_rune(flag.usage, '\n') {
|
||||
// Multi-line usage documentation. Let's make it look nice.
|
||||
usage_builder := strings.builder_make(context.temp_allocator)
|
||||
|
||||
strings.write_byte(&usage_builder, '\n')
|
||||
iter := strings.trim_space(flag.usage)
|
||||
for line in strings.split_lines_iterator(&iter) {
|
||||
strings.write_string(&usage_builder, "\t\t")
|
||||
strings.write_string(&usage_builder, strings.trim_left_space(line))
|
||||
strings.write_byte(&usage_builder, '\n')
|
||||
}
|
||||
|
||||
strings.write_string(&builder, strings.to_string(usage_builder))
|
||||
} else {
|
||||
// Single-line usage documentation.
|
||||
spacing := strings.repeat(" ",
|
||||
(longest_flag_length) - flag.full_length,
|
||||
context.temp_allocator)
|
||||
|
||||
strings.write_string(&builder, spacing)
|
||||
strings.write_string(&builder, " | ")
|
||||
strings.write_string(&builder, flag.usage)
|
||||
strings.write_byte(&builder, '\n')
|
||||
}
|
||||
}
|
||||
|
||||
fmt.wprint(out, strings.to_string(builder))
|
||||
}
|
||||
130
core/flags/util.odin
Normal file
130
core/flags/util.odin
Normal file
@@ -0,0 +1,130 @@
|
||||
package flags
|
||||
|
||||
import "core:fmt"
|
||||
@require import "core:os"
|
||||
@require import "core:path/filepath"
|
||||
import "core:strings"
|
||||
|
||||
/*
|
||||
Parse any arguments into an annotated struct or exit if there was an error.
|
||||
|
||||
*Allocates Using Provided Allocator*
|
||||
|
||||
This is a convenience wrapper over `parse` and `print_errors`.
|
||||
|
||||
Inputs:
|
||||
- model: A pointer to an annotated struct.
|
||||
- program_args: A slice of strings, usually `os.args`.
|
||||
- style: The argument parsing style.
|
||||
- allocator: (default: context.allocator)
|
||||
- loc: The caller location for debugging purposes (default: #caller_location)
|
||||
*/
|
||||
@(optimization_mode="size")
|
||||
parse_or_exit :: proc(
|
||||
model: ^$T,
|
||||
program_args: []string,
|
||||
style: Parsing_Style = .Odin,
|
||||
allocator := context.allocator,
|
||||
loc := #caller_location,
|
||||
) {
|
||||
assert(len(program_args) > 0, "Program arguments slice is empty.", loc)
|
||||
|
||||
program := filepath.base(program_args[0])
|
||||
args: []string
|
||||
|
||||
if len(program_args) > 1 {
|
||||
args = program_args[1:]
|
||||
}
|
||||
|
||||
error := parse(model, args, style)
|
||||
if error != nil {
|
||||
stderr := os.stream_from_handle(os.stderr)
|
||||
|
||||
if len(args) == 0 {
|
||||
// No arguments entered, and there was an error; show the usage,
|
||||
// specifically on STDERR.
|
||||
write_usage(stderr, T, program, style)
|
||||
fmt.wprintln(stderr)
|
||||
}
|
||||
|
||||
print_errors(T, error, program, style)
|
||||
|
||||
_, was_help_request := error.(Help_Request)
|
||||
os.exit(0 if was_help_request else 1)
|
||||
}
|
||||
}
|
||||
/*
|
||||
Print out any errors that may have resulted from parsing.
|
||||
|
||||
All error messages print to STDERR, while usage goes to STDOUT, if requested.
|
||||
|
||||
Inputs:
|
||||
- data_type: The typeid of the data structure to describe, if usage is requested.
|
||||
- error: The error returned from `parse`.
|
||||
- style: The argument parsing style, required to show flags in the proper style, when usage is shown.
|
||||
*/
|
||||
@(optimization_mode="size")
|
||||
print_errors :: proc(data_type: typeid, error: Error, program: string, style: Parsing_Style = .Odin) {
|
||||
stderr := os.stream_from_handle(os.stderr)
|
||||
stdout := os.stream_from_handle(os.stdout)
|
||||
|
||||
switch specific_error in error {
|
||||
case Parse_Error:
|
||||
fmt.wprintfln(stderr, "[%T.%v] %s", specific_error, specific_error.reason, specific_error.message)
|
||||
case Open_File_Error:
|
||||
fmt.wprintfln(stderr, "[%T#%i] Unable to open file with perms 0o%o in mode 0x%x: %s",
|
||||
specific_error,
|
||||
specific_error.errno,
|
||||
specific_error.perms,
|
||||
specific_error.mode,
|
||||
specific_error.filename)
|
||||
case Validation_Error:
|
||||
fmt.wprintfln(stderr, "[%T] %s", specific_error, specific_error.message)
|
||||
case Help_Request:
|
||||
write_usage(stdout, data_type, program, style)
|
||||
}
|
||||
}
|
||||
/*
|
||||
Get the value for a subtag.
|
||||
|
||||
This is useful if you need to parse through the `args` tag for a struct field
|
||||
on a custom type setter or custom flag checker.
|
||||
|
||||
Example:
|
||||
|
||||
import "core:flags"
|
||||
import "core:fmt"
|
||||
|
||||
subtag_example :: proc() {
|
||||
args_tag := "precision=3,signed"
|
||||
|
||||
precision, has_precision := flags.get_subtag(args_tag, "precision")
|
||||
signed, is_signed := flags.get_subtag(args_tag, "signed")
|
||||
|
||||
fmt.printfln("precision = %q, %t", precision, has_precision)
|
||||
fmt.printfln("signed = %q, %t", signed, is_signed)
|
||||
}
|
||||
|
||||
Output:
|
||||
|
||||
precision = "3", true
|
||||
signed = "", true
|
||||
|
||||
*/
|
||||
get_subtag :: proc(tag, id: string) -> (value: string, ok: bool) {
|
||||
// This proc was initially private in `internal_rtti.odin`, but given how
|
||||
// useful it would be to custom type setters and flag checkers, it lives
|
||||
// here now.
|
||||
|
||||
tag := tag
|
||||
|
||||
for subtag in strings.split_iterator(&tag, ",") {
|
||||
if equals := strings.index_byte(subtag, '='); equals != -1 && id == subtag[:equals] {
|
||||
return subtag[1 + equals:], true
|
||||
} else if id == subtag {
|
||||
return "", true
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
37
core/flags/validation.odin
Normal file
37
core/flags/validation.odin
Normal file
@@ -0,0 +1,37 @@
|
||||
package flags
|
||||
|
||||
/*
|
||||
Check a flag after parsing, during the validation stage.
|
||||
|
||||
Inputs:
|
||||
- model: A raw pointer to the data structure provided to `parse`.
|
||||
- name: The name of the flag being checked.
|
||||
- value: An `any` type that contains the value to be checked.
|
||||
- args_tag: The `args` tag from within the struct.
|
||||
|
||||
Returns:
|
||||
- error: An error message, or an empty string if no error occurred.
|
||||
*/
|
||||
Custom_Flag_Checker :: #type proc(
|
||||
model: rawptr,
|
||||
name: string,
|
||||
value: any,
|
||||
args_tag: string,
|
||||
) -> (
|
||||
error: string,
|
||||
)
|
||||
|
||||
@(private)
|
||||
global_custom_flag_checker: Custom_Flag_Checker
|
||||
|
||||
/*
|
||||
Set the global custom flag checker.
|
||||
|
||||
Note that only one can be active at a time.
|
||||
|
||||
Inputs:
|
||||
- checker: The flag checker. Pass `nil` to disable any previously set checker.
|
||||
*/
|
||||
register_flag_checker :: proc(checker: Custom_Flag_Checker) {
|
||||
global_custom_flag_checker = checker
|
||||
}
|
||||
@@ -1973,11 +1973,13 @@ fmt_struct :: proc(fi: ^Info, v: any, the_verb: rune, info: runtime.Type_Info_St
|
||||
// fi.hash = false;
|
||||
fi.indent += 1
|
||||
|
||||
if !is_soa && hash {
|
||||
is_empty := len(info.names) == 0
|
||||
|
||||
if !is_soa && hash && !is_empty {
|
||||
io.write_byte(fi.writer, '\n', &fi.n)
|
||||
}
|
||||
defer {
|
||||
if hash {
|
||||
if !is_soa && hash && !is_empty {
|
||||
for _ in 0..<indent { io.write_byte(fi.writer, '\t', &fi.n) }
|
||||
}
|
||||
io.write_byte(fi.writer, ']' if is_soa && the_verb == 'v' else '}', &fi.n)
|
||||
@@ -2025,9 +2027,9 @@ fmt_struct :: proc(fi: ^Info, v: any, the_verb: rune, info: runtime.Type_Info_St
|
||||
}
|
||||
io.write_string(fi.writer, base_type_name, &fi.n)
|
||||
io.write_byte(fi.writer, '{', &fi.n)
|
||||
if hash { io.write_byte(fi.writer, '\n', &fi.n) }
|
||||
if hash && !is_empty { io.write_byte(fi.writer, '\n', &fi.n) }
|
||||
defer {
|
||||
if hash {
|
||||
if hash && !is_empty {
|
||||
fi.indent -= 1
|
||||
fmt_write_indent(fi)
|
||||
fi.indent += 1
|
||||
@@ -2075,6 +2077,10 @@ fmt_struct :: proc(fi: ^Info, v: any, the_verb: rune, info: runtime.Type_Info_St
|
||||
if hash { io.write_string(fi.writer, ",\n", &fi.n) }
|
||||
}
|
||||
}
|
||||
|
||||
if hash && n > 0 {
|
||||
for _ in 0..<indent { io.write_byte(fi.writer, '\t', &fi.n) }
|
||||
}
|
||||
} else {
|
||||
field_count := -1
|
||||
for name, i in info.names {
|
||||
|
||||
@@ -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, }
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)))
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -495,8 +495,10 @@ unique :: proc(s: $S/[]$T) -> S where intrinsics.type_is_comparable(T) #no_bound
|
||||
}
|
||||
i := 1
|
||||
for j in 1..<len(s) {
|
||||
if s[j] != s[j-1] && i != j {
|
||||
s[i] = s[j]
|
||||
if s[j] != s[j-1] {
|
||||
if i != j {
|
||||
s[i] = s[j]
|
||||
}
|
||||
i += 1
|
||||
}
|
||||
}
|
||||
@@ -513,8 +515,10 @@ unique_proc :: proc(s: $S/[]$T, eq: proc(T, T) -> bool) -> S #no_bounds_check {
|
||||
}
|
||||
i := 1
|
||||
for j in 1..<len(s) {
|
||||
if !eq(s[j], s[j-1]) && i != j {
|
||||
s[i] = s[j]
|
||||
if !eq(s[j], s[j-1]) {
|
||||
if i != j {
|
||||
s[i] = s[j]
|
||||
}
|
||||
i += 1
|
||||
}
|
||||
}
|
||||
@@ -736,4 +740,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}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -78,6 +78,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,
|
||||
@@ -479,6 +480,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
|
||||
@@ -1024,31 +1027,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,
|
||||
@@ -1073,92 +1054,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,
|
||||
}
|
||||
|
||||
@@ -1238,6 +1162,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
|
||||
@@ -1256,3 +1204,5 @@ GMEM_INVALID_HANDLE :: 0x8000
|
||||
|
||||
GHND :: (GMEM_MOVEABLE | GMEM_ZEROINIT)
|
||||
GPTR :: (GMEM_FIXED | GMEM_ZEROINIT)
|
||||
|
||||
LPTOP_LEVEL_EXCEPTION_FILTER :: PVECTORED_EXCEPTION_HANDLER
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -9,6 +9,7 @@ import "core:encoding/ansi"
|
||||
import "core:fmt"
|
||||
import "core:io"
|
||||
@require import pkg_log "core:log"
|
||||
import "core:math/rand"
|
||||
import "core:mem"
|
||||
import "core:os"
|
||||
import "core:slice"
|
||||
@@ -41,6 +42,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 {
|
||||
@@ -104,6 +107,13 @@ run_test_task :: proc(task: thread.Task) {
|
||||
options = Default_Test_Logger_Opts,
|
||||
}
|
||||
|
||||
random_generator_state: runtime.Default_Random_State
|
||||
context.random_generator = {
|
||||
procedure = runtime.default_random_generator_proc,
|
||||
data = &random_generator_state,
|
||||
}
|
||||
rand.reset(data.t.seed)
|
||||
|
||||
free_all(context.temp_allocator)
|
||||
|
||||
data.it.p(&data.t)
|
||||
@@ -497,6 +507,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 +615,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 +656,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
|
||||
}
|
||||
|
||||
@@ -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})
|
||||
}
|
||||
|
||||
@@ -1,31 +1,44 @@
|
||||
|
||||
/*
|
||||
The `i18n` package is flexible and easy to use.
|
||||
The `i18n` package is a flexible and easy to use way to localise applications.
|
||||
|
||||
It has one call to get a translation: `get`, which the user can alias into something like `T`.
|
||||
It has two calls to get a translation: `get()` and `get_n()`, which the user can alias into something like `T` and `Tn`
|
||||
with statements like:
|
||||
T :: i18n.get
|
||||
Tn :: i18n.get_n.
|
||||
|
||||
`get`, referred to as `T` here, has a few different signatures.
|
||||
All of them will return the key if the entry can't be found in the active translation catalog.
|
||||
`get()` is used for retrieving the translation of sentences which **never** change in form,
|
||||
like for instance "Connection established" or "All temporary files have been deleted".
|
||||
Note that the number (singular, dual, plural, whatever else) is not relevant: the sentence is fixed and it will have only one possible translation in any other language.
|
||||
|
||||
- `T(key)` returns the translation of `key`.
|
||||
- `T(key, n)` returns a pluralized translation of `key` according to value `n`.
|
||||
`get_n()` is used for retrieving the translations of sentences which change according to the number of items referenced.
|
||||
The various signatures of `get_n()` have one more parameter, `n`, which will receive that number and be used
|
||||
to select the correct form according to the pluralizer attached to the message catalogue when initially loaded;
|
||||
for instance, to summarise a rather complex matter, some languages use the singular form when referring to 0 items and some use the (only in their case) plural forms;
|
||||
also, languages may have more or less quantifier forms than a single singular form and a universal plural form:
|
||||
for instance, Chinese has just one form for any quantity, while Welsh may have up to 6 different forms for specific different quantities.
|
||||
|
||||
- `T(section, key)` returns the translation of `key` in `section`.
|
||||
- `T(section, key, n)` returns a pluralized translation of `key` in `section` according to value `n`.
|
||||
Both `get()` and `get_n()`, referred to as `T` and `Tn` here, have several different signatures.
|
||||
All of them will return the key if the entry can't be found in the active translation catalogue.
|
||||
By default lookup take place in the global `i18n.ACTIVE` catalogue for ease of use, unless a specific catalogue is supplied.
|
||||
|
||||
By default lookup take place in the global `i18n.ACTIVE` catalog for ease of use.
|
||||
If you want to override which translation to use, for example in a language preview dialog, you can use the following:
|
||||
- `T(key)` returns the translation of `key`.
|
||||
- `T(key, catalog)` returns the translation of `key` from explictly supplied catalogue.
|
||||
- `T(section, key)` returns the translation of `key` in `section`.
|
||||
- `T(section, key, catalog)` returns the translation of `key` in `section` from explictly supplied catalogue.
|
||||
|
||||
- `T(key, n, catalog)` returns the pluralized version of `key` from explictly supplied catalog.
|
||||
- `T(section, key, n, catalog)` returns the pluralized version of `key` in `section` from explictly supplied catalog.
|
||||
- `Tn(key, n)` returns the translation of `key` according to number of items `n`.
|
||||
- `Tn(key, n, catalog)` returns the translation of `key` from explictly supplied catalogue.
|
||||
- `Tn(section, key, n)` returns the translation of `key` in `section` according to number of items `n`.
|
||||
- `Tn(section, key, n, catalog)` returns the translation of `key` in `section` according to number of items `n` from explictly supplied catalogue.
|
||||
|
||||
If a catalog has translation contexts or sections, then omitting it in the above calls looks up in section "".
|
||||
|
||||
The default pluralization rule is n != 1, which is to say that passing n == 1 (or not passing n) returns the singular form.
|
||||
Passing n != 1 returns plural form 1.
|
||||
The default pluralization rule is `n != 1`, which is to say that passing `n == 1` returns the singular form (in slot 0).
|
||||
Passing `n != 1` returns the plural form in slot 1 (if any).
|
||||
|
||||
Should a language not conform to this rule, you can pass a pluralizer procedure to the catalog parser.
|
||||
This is a procedure that maps an integer to an integer, taking a value and returning which plural slot should be used.
|
||||
This is a procedure that maps an integer to an integer, taking a quantity and returning which plural slot should be used.
|
||||
|
||||
You can also assign it to a loaded catalog after parsing, of course.
|
||||
|
||||
@@ -34,24 +47,21 @@ Example:
|
||||
import "core:fmt"
|
||||
import "core:text/i18n"
|
||||
|
||||
T :: i18n.get
|
||||
T :: i18n.get
|
||||
Tn :: i18n.get_n
|
||||
|
||||
mo :: proc() {
|
||||
using fmt
|
||||
|
||||
err: i18n.Error
|
||||
|
||||
/*
|
||||
Parse MO file and set it as the active translation so we can omit `get`'s "catalog" parameter.
|
||||
*/
|
||||
// Parse MO file and set it as the active translation so we can omit `get`'s "catalog" parameter.
|
||||
i18n.ACTIVE, err = i18n.parse_mo(#load("translations/nl_NL.mo"))
|
||||
defer i18n.destroy()
|
||||
|
||||
if err != .None { return }
|
||||
|
||||
/*
|
||||
These are in the .MO catalog.
|
||||
*/
|
||||
// These are in the .MO catalog.
|
||||
println("-----")
|
||||
println(T(""))
|
||||
println("-----")
|
||||
@@ -60,13 +70,11 @@ Example:
|
||||
println(T("Hellope, World!"))
|
||||
println("-----")
|
||||
// We pass 1 into `T` to get the singular format string, then 1 again into printf.
|
||||
printf(T("There is %d leaf.\n", 1), 1)
|
||||
printf(Tn("There is %d leaf.\n", 1), 1)
|
||||
// We pass 42 into `T` to get the plural format string, then 42 again into printf.
|
||||
printf(T("There is %d leaf.\n", 42), 42)
|
||||
printf(Tn("There is %d leaf.\n", 42), 42)
|
||||
|
||||
/*
|
||||
This isn't in the translation catalog, so the key is passed back untranslated.
|
||||
*/
|
||||
// This isn't in the translation catalog, so the key is passed back untranslated.
|
||||
println("-----")
|
||||
println(T("Come visit us on Discord!"))
|
||||
}
|
||||
@@ -76,19 +84,13 @@ Example:
|
||||
|
||||
err: i18n.Error
|
||||
|
||||
/*
|
||||
Parse QT file and set it as the active translation so we can omit `get`'s "catalog" parameter.
|
||||
*/
|
||||
// Parse QT file and set it as the active translation so we can omit `get`'s "catalog" parameter.
|
||||
i18n.ACTIVE, err = i18n.parse_qt(#load("translations/nl_NL-qt-ts.ts"))
|
||||
defer i18n.destroy()
|
||||
|
||||
if err != .None {
|
||||
return
|
||||
}
|
||||
if err != .None { return }
|
||||
|
||||
/*
|
||||
These are in the .TS catalog. As you can see they have sections.
|
||||
*/
|
||||
// These are in the .TS catalog. As you can see they have sections.
|
||||
println("--- Page section ---")
|
||||
println("Page:Text for translation =", T("Page", "Text for translation"))
|
||||
println("-----")
|
||||
@@ -99,8 +101,8 @@ Example:
|
||||
println("-----")
|
||||
println("--- apple_count section ---")
|
||||
println("apple_count:%d apple(s) =")
|
||||
println("\t 1 =", T("apple_count", "%d apple(s)", 1))
|
||||
println("\t 42 =", T("apple_count", "%d apple(s)", 42))
|
||||
println("\t 1 =", Tn("apple_count", "%d apple(s)", 1))
|
||||
println("\t 42 =", Tn("apple_count", "%d apple(s)", 42))
|
||||
}
|
||||
*/
|
||||
package i18n
|
||||
|
||||
@@ -10,23 +10,13 @@ package i18n
|
||||
*/
|
||||
import "core:strings"
|
||||
|
||||
/*
|
||||
TODO:
|
||||
- Support for more translation catalog file formats.
|
||||
*/
|
||||
|
||||
/*
|
||||
Currently active catalog.
|
||||
*/
|
||||
// Currently active catalog.
|
||||
ACTIVE: ^Translation
|
||||
|
||||
// Allow between 1 and 255 plural forms. Default: 10.
|
||||
MAX_PLURALS :: min(max(#config(ODIN_i18N_MAX_PLURAL_FORMS, 10), 1), 255)
|
||||
|
||||
/*
|
||||
The main data structure. This can be generated from various different file formats, as long as we have a parser for them.
|
||||
*/
|
||||
|
||||
// The main data structure. This can be generated from various different file formats, as long as we have a parser for them.
|
||||
Section :: map[string][]string
|
||||
|
||||
Translation :: struct {
|
||||
@@ -37,34 +27,24 @@ Translation :: struct {
|
||||
}
|
||||
|
||||
Error :: enum {
|
||||
/*
|
||||
General return values.
|
||||
*/
|
||||
// General return values.
|
||||
None = 0,
|
||||
Empty_Translation_Catalog,
|
||||
Duplicate_Key,
|
||||
|
||||
/*
|
||||
Couldn't find, open or read file.
|
||||
*/
|
||||
// Couldn't find, open or read file.
|
||||
File_Error,
|
||||
|
||||
/*
|
||||
File too short.
|
||||
*/
|
||||
// File too short.
|
||||
Premature_EOF,
|
||||
|
||||
/*
|
||||
GNU Gettext *.MO file errors.
|
||||
*/
|
||||
// GNU Gettext *.MO file errors.
|
||||
MO_File_Invalid_Signature,
|
||||
MO_File_Unsupported_Version,
|
||||
MO_File_Invalid,
|
||||
MO_File_Incorrect_Plural_Count,
|
||||
|
||||
/*
|
||||
Qt Linguist *.TS file errors.
|
||||
*/
|
||||
// Qt Linguist *.TS file errors.
|
||||
TS_File_Parse_Error,
|
||||
TS_File_Expected_Context,
|
||||
TS_File_Expected_Context_Name,
|
||||
@@ -85,73 +65,142 @@ DEFAULT_PARSE_OPTIONS :: Parse_Options{
|
||||
}
|
||||
|
||||
/*
|
||||
Several ways to use:
|
||||
- get(key), which defaults to the singular form and i18n.ACTIVE catalog, or
|
||||
- get(key, number), which returns the appropriate plural from the active catalog, or
|
||||
- get(key, number, catalog) to grab text from a specific one.
|
||||
*/
|
||||
get_single_section :: proc(key: string, number := 1, catalog: ^Translation = ACTIVE) -> (value: string) {
|
||||
/*
|
||||
A lot of languages use singular for 1 item and plural for 0 or more than 1 items. This is our default pluralize rule.
|
||||
*/
|
||||
plural := 1 if number != 1 else 0
|
||||
Returns the first translation string for the passed `key`.
|
||||
It is also aliased with `get()`.
|
||||
|
||||
if catalog.pluralize != nil {
|
||||
plural = catalog.pluralize(number)
|
||||
}
|
||||
return get_by_slot(key, plural, catalog)
|
||||
Two ways to use it:
|
||||
- get(key), which defaults to the `i18n.ACTIVE` catalogue, or
|
||||
- get(key, catalog) to grab text from a specific loaded catalogue
|
||||
|
||||
Inputs:
|
||||
- key: the string to translate
|
||||
- catalog: the catalogue to use for the translation (defaults to i18n.ACTIVE)
|
||||
|
||||
Returns: the translated string, or the original `key` if no translation was found.
|
||||
*/
|
||||
get_single_section :: proc(key: string, catalog: ^Translation = ACTIVE) -> (value: string) {
|
||||
return get_by_slot(key, 0, catalog)
|
||||
}
|
||||
|
||||
/*
|
||||
Several ways to use:
|
||||
- get(section, key), which defaults to the singular form and i18n.ACTIVE catalog, or
|
||||
- get(section, key, number), which returns the appropriate plural from the active catalog, or
|
||||
- get(section, key, number, catalog) to grab text from a specific one.
|
||||
*/
|
||||
get_by_section :: proc(section, key: string, number := 1, catalog: ^Translation = ACTIVE) -> (value: string) {
|
||||
/*
|
||||
A lot of languages use singular for 1 item and plural for 0 or more than 1 items. This is our default pluralize rule.
|
||||
*/
|
||||
plural := 1 if number != 1 else 0
|
||||
Returns the first translation string for the passed `key` in a specific section or context.
|
||||
It is also aliases with `get()`.
|
||||
|
||||
if catalog.pluralize != nil {
|
||||
plural = catalog.pluralize(number)
|
||||
}
|
||||
return get_by_slot(section, key, plural, catalog)
|
||||
Two ways to use it:
|
||||
- get(section, key), which defaults to the `i18n.ACTIVE` catalogue, or
|
||||
- get(section, key, catalog) to grab text from a specific loaded catalogue
|
||||
|
||||
Inputs:
|
||||
- section: the catalogue section (sometimes also called 'context') in which to look up the translation
|
||||
- key: the string to translate
|
||||
- catalog: the catalogue to use for the translation (defaults to i18n.ACTIVE)
|
||||
|
||||
Returns: the translated string, or the original `key` if no translation was found.
|
||||
*/
|
||||
get_by_section :: proc(section, key: string, catalog: ^Translation = ACTIVE) -> (value: string) {
|
||||
return get_by_slot(section, key, 0, catalog)
|
||||
}
|
||||
|
||||
get :: proc{get_single_section, get_by_section}
|
||||
|
||||
/*
|
||||
Several ways to use:
|
||||
- get_by_slot(key), which defaults to the singular form and i18n.ACTIVE catalog, or
|
||||
- get_by_slot(key, slot), which returns the requested plural from the active catalog, or
|
||||
- get_by_slot(key, slot, catalog) to grab text from a specific one.
|
||||
Returns the translation string for the passed `key` in a specific plural form (if present in the catalogue).
|
||||
It is also aliased with `get_n()`.
|
||||
|
||||
Two ways to use it:
|
||||
- get_n(key, quantity), which returns the appropriate plural from the active catalogue, or
|
||||
- get_n(key, quantity, catalog) to grab text from a specific loaded catalogue
|
||||
|
||||
Inputs:
|
||||
- key: the string to translate
|
||||
- quantity: the quantity of item to be used to select the correct plural form
|
||||
- catalog: the catalogue to use for the translation (defaults to i18n.ACTIVE)
|
||||
|
||||
Returns: the translated string, or the original `key` if no translation was found.
|
||||
*/
|
||||
get_single_section_with_quantity :: proc(key: string, quantity: int, catalog: ^Translation = ACTIVE) -> (value: string) {
|
||||
/*
|
||||
A lot of languages use singular for 1 item and plural for 0 or more than 1 items. This is our default pluralize rule.
|
||||
*/
|
||||
slot := 1 if quantity != 1 else 0
|
||||
|
||||
if catalog.pluralize != nil {
|
||||
slot = catalog.pluralize(quantity)
|
||||
}
|
||||
return get_by_slot(key, slot, catalog)
|
||||
}
|
||||
|
||||
/*
|
||||
Returns the translation string for the passed `key` in a specific plural form (if present in the catalogue)
|
||||
in a specific section or context.
|
||||
It is also aliases with `get_n()`.
|
||||
|
||||
Two ways to use it:
|
||||
- get(section, key, quantity), which returns the appropriate plural from the active catalogue, or
|
||||
- get(section, key, quantity, catalog) to grab text from a specific loaded catalogue
|
||||
|
||||
Inputs:
|
||||
- section: the catalogue section (sometime also called 'context') from which to lookup the translation
|
||||
- key: the string to translate
|
||||
- qantity: the quantity of item to be used to select the correct plural form
|
||||
- catalog: the catalogue to use for the translation (defaults to i18n.ACTIVE)
|
||||
|
||||
Returns: the translated string, or the original `key` if no translation was found
|
||||
*/
|
||||
get_by_section_with_quantity :: proc(section, key: string, quantity: int, catalog: ^Translation = ACTIVE) -> (value: string) {
|
||||
/*
|
||||
A lot of languages use singular for 1 item and plural for 0 or more than 1 items. This is our default pluralize rule.
|
||||
*/
|
||||
slot := 1 if quantity != 1 else 0
|
||||
|
||||
if catalog.pluralize != nil {
|
||||
slot = catalog.pluralize(quantity)
|
||||
}
|
||||
return get_by_slot(section, key, slot, catalog)
|
||||
}
|
||||
get_n :: proc{get_single_section_with_quantity, get_by_section_with_quantity}
|
||||
|
||||
/*
|
||||
Two ways to use:
|
||||
- get_by_slot(key, slot), which returns the requested plural from the active catalogue, or
|
||||
- get_by_slot(key, slot, catalog) to grab text from a specific loaded catalogue.
|
||||
|
||||
If a file format parser doesn't (yet) support plural slots, each of the slots will point at the same string.
|
||||
- section: the catalogue section (sometime also called 'context') from which to lookup the translation
|
||||
|
||||
Inputs:
|
||||
- key: the string to translate.
|
||||
- slot: the translation slot to choose (slots refer to plural forms specific for each language and their meaning changes from catalogue to catalogue).
|
||||
- catalog: the catalogue to use for the translation (defaults to i18n.ACTIVE)
|
||||
|
||||
Returns: the translated string, or the original `key` if no translation was found.
|
||||
*/
|
||||
get_by_slot_single_section :: proc(key: string, slot := 0, catalog: ^Translation = ACTIVE) -> (value: string) {
|
||||
get_by_slot_single_section :: proc(key: string, slot: int, catalog: ^Translation = ACTIVE) -> (value: string) {
|
||||
return get_by_slot_by_section("", key, slot, catalog)
|
||||
}
|
||||
|
||||
/*
|
||||
Several ways to use:
|
||||
- get_by_slot(key), which defaults to the singular form and i18n.ACTIVE catalog, or
|
||||
Two ways to use:
|
||||
- get_by_slot(key, slot), which returns the requested plural from the active catalog, or
|
||||
- get_by_slot(key, slot, catalog) to grab text from a specific one.
|
||||
|
||||
If a file format parser doesn't (yet) support plural slots, each of the slots will point at the same string.
|
||||
|
||||
Inputs:
|
||||
- section: the catalogue section (sometime also called 'context') from which to lookup the translation
|
||||
- key: the string to translate.
|
||||
- slot: the translation slot to choose (slots refer to plural forms specific for each language and their meaning changes from catalogue to catalogue).
|
||||
- catalog: the catalogue to use for the translation (defaults to i18n.ACTIVE)
|
||||
|
||||
Returns: the translated string or the original `key` if no translation was found.
|
||||
*/
|
||||
get_by_slot_by_section :: proc(section, key: string, slot := 0, catalog: ^Translation = ACTIVE) -> (value: string) {
|
||||
get_by_slot_by_section :: proc(section, key: string, slot: int, catalog: ^Translation = ACTIVE) -> (value: string) {
|
||||
if catalog == nil || section not_in catalog.k_v {
|
||||
/*
|
||||
Return the key if the catalog catalog hasn't been initialized yet, or the section is not present.
|
||||
*/
|
||||
// Return the key if the catalog catalog hasn't been initialized yet, or the section is not present.
|
||||
return key
|
||||
}
|
||||
|
||||
/*
|
||||
Return the translation from the requested slot if this key is known, else return the key.
|
||||
*/
|
||||
// Return the translation from the requested slot if this key is known, else return the key.
|
||||
if translations, ok := catalog.k_v[section][key]; ok {
|
||||
plural := min(max(0, slot), len(catalog.k_v[section][key]) - 1)
|
||||
return translations[plural]
|
||||
@@ -161,7 +210,6 @@ get_by_slot_by_section :: proc(section, key: string, slot := 0, catalog: ^Transl
|
||||
get_by_slot :: proc{get_by_slot_single_section, get_by_slot_by_section}
|
||||
|
||||
/*
|
||||
Same for destroy:
|
||||
- destroy(), to clean up the currently active catalog catalog i18n.ACTIVE
|
||||
- destroy(catalog), to clean up a specific catalog.
|
||||
*/
|
||||
|
||||
@@ -5,6 +5,10 @@ REPLACEMENT_CHAR :: '\ufffd' // Represented an invalid code point
|
||||
MAX_ASCII :: '\u007f' // Maximum ASCII value
|
||||
MAX_LATIN1 :: '\u00ff' // Maximum Latin-1 value
|
||||
|
||||
ZERO_WIDTH_NON_JOINER :: '\u200C'
|
||||
ZERO_WIDTH_JOINER :: '\u200D'
|
||||
|
||||
@(require_results)
|
||||
binary_search :: proc(c: i32, table: []i32, length, stride: int) -> int {
|
||||
n := length
|
||||
t := 0
|
||||
@@ -24,6 +28,7 @@ binary_search :: proc(c: i32, table: []i32, length, stride: int) -> int {
|
||||
return -1
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
to_lower :: proc(r: rune) -> rune {
|
||||
c := i32(r)
|
||||
p := binary_search(c, to_lower_ranges[:], len(to_lower_ranges)/3, 3)
|
||||
@@ -36,6 +41,7 @@ to_lower :: proc(r: rune) -> rune {
|
||||
}
|
||||
return rune(c)
|
||||
}
|
||||
@(require_results)
|
||||
to_upper :: proc(r: rune) -> rune {
|
||||
c := i32(r)
|
||||
p := binary_search(c, to_upper_ranges[:], len(to_upper_ranges)/3, 3)
|
||||
@@ -48,6 +54,7 @@ to_upper :: proc(r: rune) -> rune {
|
||||
}
|
||||
return rune(c)
|
||||
}
|
||||
@(require_results)
|
||||
to_title :: proc(r: rune) -> rune {
|
||||
c := i32(r)
|
||||
p := binary_search(c, to_upper_singlets[:], len(to_title_singlets)/2, 2)
|
||||
@@ -58,6 +65,7 @@ to_title :: proc(r: rune) -> rune {
|
||||
}
|
||||
|
||||
|
||||
@(require_results)
|
||||
is_lower :: proc(r: rune) -> bool {
|
||||
if r <= MAX_ASCII {
|
||||
return u32(r)-'a' < 26
|
||||
@@ -74,6 +82,7 @@ is_lower :: proc(r: rune) -> bool {
|
||||
return false
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
is_upper :: proc(r: rune) -> bool {
|
||||
if r <= MAX_ASCII {
|
||||
return u32(r)-'A' < 26
|
||||
@@ -91,6 +100,7 @@ is_upper :: proc(r: rune) -> bool {
|
||||
}
|
||||
|
||||
is_alpha :: is_letter
|
||||
@(require_results)
|
||||
is_letter :: proc(r: rune) -> bool {
|
||||
if u32(r) <= MAX_LATIN1 {
|
||||
return char_properties[u8(r)]&pLmask != 0
|
||||
@@ -111,10 +121,12 @@ is_letter :: proc(r: rune) -> bool {
|
||||
return false
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
is_title :: proc(r: rune) -> bool {
|
||||
return is_upper(r) && is_lower(r)
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
is_digit :: proc(r: rune) -> bool {
|
||||
if r <= MAX_LATIN1 {
|
||||
return '0' <= r && r <= '9'
|
||||
@@ -124,6 +136,7 @@ is_digit :: proc(r: rune) -> bool {
|
||||
|
||||
|
||||
is_white_space :: is_space
|
||||
@(require_results)
|
||||
is_space :: proc(r: rune) -> bool {
|
||||
if u32(r) <= MAX_LATIN1 {
|
||||
switch r {
|
||||
@@ -140,18 +153,20 @@ is_space :: proc(r: rune) -> bool {
|
||||
return false
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
is_combining :: proc(r: rune) -> bool {
|
||||
c := i32(r)
|
||||
|
||||
return c >= 0x0300 && (c <= 0x036f ||
|
||||
(c >= 0x1ab0 && c <= 0x1aff) ||
|
||||
(c >= 0x1dc0 && c <= 0x1dff) ||
|
||||
(c >= 0x20d0 && c <= 0x20ff) ||
|
||||
(c >= 0xfe20 && c <= 0xfe2f))
|
||||
(c >= 0x1ab0 && c <= 0x1aff) ||
|
||||
(c >= 0x1dc0 && c <= 0x1dff) ||
|
||||
(c >= 0x20d0 && c <= 0x20ff) ||
|
||||
(c >= 0xfe20 && c <= 0xfe2f))
|
||||
}
|
||||
|
||||
|
||||
|
||||
@(require_results)
|
||||
is_graphic :: proc(r: rune) -> bool {
|
||||
if u32(r) <= MAX_LATIN1 {
|
||||
return char_properties[u8(r)]&pg != 0
|
||||
@@ -159,6 +174,7 @@ is_graphic :: proc(r: rune) -> bool {
|
||||
return false
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
is_print :: proc(r: rune) -> bool {
|
||||
if u32(r) <= MAX_LATIN1 {
|
||||
return char_properties[u8(r)]&pp != 0
|
||||
@@ -166,6 +182,7 @@ is_print :: proc(r: rune) -> bool {
|
||||
return false
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
is_control :: proc(r: rune) -> bool {
|
||||
if u32(r) <= MAX_LATIN1 {
|
||||
return char_properties[u8(r)]&pC != 0
|
||||
@@ -173,6 +190,7 @@ is_control :: proc(r: rune) -> bool {
|
||||
return false
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
is_number :: proc(r: rune) -> bool {
|
||||
if u32(r) <= MAX_LATIN1 {
|
||||
return char_properties[u8(r)]&pN != 0
|
||||
@@ -180,6 +198,7 @@ is_number :: proc(r: rune) -> bool {
|
||||
return false
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
is_punct :: proc(r: rune) -> bool {
|
||||
if u32(r) <= MAX_LATIN1 {
|
||||
return char_properties[u8(r)]&pP != 0
|
||||
@@ -187,9 +206,250 @@ is_punct :: proc(r: rune) -> bool {
|
||||
return false
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
is_symbol :: proc(r: rune) -> bool {
|
||||
if u32(r) <= MAX_LATIN1 {
|
||||
return char_properties[u8(r)]&pS != 0
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
//
|
||||
// The procedures below are accurate as of Unicode 15.1.0.
|
||||
//
|
||||
|
||||
// Emoji_Modifier
|
||||
@(require_results)
|
||||
is_emoji_modifier :: proc(r: rune) -> bool {
|
||||
return 0x1F3FB <= r && r <= 0x1F3FF
|
||||
}
|
||||
|
||||
// Regional_Indicator
|
||||
@(require_results)
|
||||
is_regional_indicator :: proc(r: rune) -> bool {
|
||||
return 0x1F1E6 <= r && r <= 0x1F1FF
|
||||
}
|
||||
|
||||
// General_Category=Enclosing_Mark
|
||||
@(require_results)
|
||||
is_enclosing_mark :: proc(r: rune) -> bool {
|
||||
switch r {
|
||||
case 0x0488,
|
||||
0x0489,
|
||||
0x1ABE,
|
||||
0x20DD ..= 0x20E0,
|
||||
0x20E2 ..= 0x20E4,
|
||||
0xA670 ..= 0xA672:
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// Prepended_Concatenation_Mark
|
||||
@(require_results)
|
||||
is_prepended_concatenation_mark :: proc(r: rune) -> bool {
|
||||
switch r {
|
||||
case 0x00600 ..= 0x00605,
|
||||
0x006DD,
|
||||
0x0070F,
|
||||
0x00890 ..= 0x00891,
|
||||
0x008E2,
|
||||
0x110BD,
|
||||
0x110CD:
|
||||
return true
|
||||
case:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// General_Category=Spacing_Mark
|
||||
@(require_results)
|
||||
is_spacing_mark :: proc(r: rune) -> bool {
|
||||
c := i32(r)
|
||||
p := binary_search(c, spacing_mark_ranges[:], len(spacing_mark_ranges)/2, 2)
|
||||
if p >= 0 && spacing_mark_ranges[p] <= c && c <= spacing_mark_ranges[p+1] {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// General_Category=Nonspacing_Mark
|
||||
@(require_results)
|
||||
is_nonspacing_mark :: proc(r: rune) -> bool {
|
||||
c := i32(r)
|
||||
p := binary_search(c, nonspacing_mark_ranges[:], len(nonspacing_mark_ranges)/2, 2)
|
||||
if p >= 0 && nonspacing_mark_ranges[p] <= c && c <= nonspacing_mark_ranges[p+1] {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Extended_Pictographic
|
||||
@(require_results)
|
||||
is_emoji_extended_pictographic :: proc(r: rune) -> bool {
|
||||
c := i32(r)
|
||||
p := binary_search(c, emoji_extended_pictographic_ranges[:], len(emoji_extended_pictographic_ranges)/2, 2)
|
||||
if p >= 0 && emoji_extended_pictographic_ranges[p] <= c && c <= emoji_extended_pictographic_ranges[p+1] {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Grapheme_Extend
|
||||
@(require_results)
|
||||
is_grapheme_extend :: proc(r: rune) -> bool {
|
||||
c := i32(r)
|
||||
p := binary_search(c, grapheme_extend_ranges[:], len(grapheme_extend_ranges)/2, 2)
|
||||
if p >= 0 && grapheme_extend_ranges[p] <= c && c <= grapheme_extend_ranges[p+1] {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
// Hangul_Syllable_Type=Leading_Jamo
|
||||
@(require_results)
|
||||
is_hangul_syllable_leading :: proc(r: rune) -> bool {
|
||||
return 0x1100 <= r && r <= 0x115F || 0xA960 <= r && r <= 0xA97C
|
||||
}
|
||||
|
||||
// Hangul_Syllable_Type=Vowel_Jamo
|
||||
@(require_results)
|
||||
is_hangul_syllable_vowel :: proc(r: rune) -> bool {
|
||||
return 0x1160 <= r && r <= 0x11A7 || 0xD7B0 <= r && r <= 0xD7C6
|
||||
}
|
||||
|
||||
// Hangul_Syllable_Type=Trailing_Jamo
|
||||
@(require_results)
|
||||
is_hangul_syllable_trailing :: proc(r: rune) -> bool {
|
||||
return 0x11A8 <= r && r <= 0x11FF || 0xD7CB <= r && r <= 0xD7FB
|
||||
}
|
||||
|
||||
// Hangul_Syllable_Type=LV_Syllable
|
||||
@(require_results)
|
||||
is_hangul_syllable_lv :: proc(r: rune) -> bool {
|
||||
c := i32(r)
|
||||
p := binary_search(c, hangul_syllable_lv_singlets[:], len(hangul_syllable_lv_singlets), 1)
|
||||
if p >= 0 && c == hangul_syllable_lv_singlets[p] {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Hangul_Syllable_Type=LVT_Syllable
|
||||
@(require_results)
|
||||
is_hangul_syllable_lvt :: proc(r: rune) -> bool {
|
||||
c := i32(r)
|
||||
p := binary_search(c, hangul_syllable_lvt_ranges[:], len(hangul_syllable_lvt_ranges)/2, 2)
|
||||
if p >= 0 && hangul_syllable_lvt_ranges[p] <= c && c <= hangul_syllable_lvt_ranges[p+1] {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
// Indic_Syllabic_Category=Consonant_Preceding_Repha
|
||||
@(require_results)
|
||||
is_indic_consonant_preceding_repha :: proc(r: rune) -> bool {
|
||||
switch r {
|
||||
case 0x00D4E,
|
||||
0x11941,
|
||||
0x11D46,
|
||||
0x11F02:
|
||||
return true
|
||||
case:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Indic_Syllabic_Category=Consonant_Prefixed
|
||||
@(require_results)
|
||||
is_indic_consonant_prefixed :: proc(r: rune) -> bool {
|
||||
switch r {
|
||||
case 0x111C2 ..= 0x111C3,
|
||||
0x1193F,
|
||||
0x11A3A,
|
||||
0x11A84 ..= 0x11A89:
|
||||
return true
|
||||
case:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Indic_Conjunct_Break=Linker
|
||||
@(require_results)
|
||||
is_indic_conjunct_break_linker :: proc(r: rune) -> bool {
|
||||
switch r {
|
||||
case 0x094D,
|
||||
0x09CD,
|
||||
0x0ACD,
|
||||
0x0B4D,
|
||||
0x0C4D,
|
||||
0x0D4D:
|
||||
return true
|
||||
case:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Indic_Conjunct_Break=Consonant
|
||||
@(require_results)
|
||||
is_indic_conjunct_break_consonant :: proc(r: rune) -> bool {
|
||||
c := i32(r)
|
||||
p := binary_search(c, indic_conjunct_break_consonant_ranges[:], len(indic_conjunct_break_consonant_ranges)/2, 2)
|
||||
if p >= 0 && indic_conjunct_break_consonant_ranges[p] <= c && c <= indic_conjunct_break_consonant_ranges[p+1] {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Indic_Conjunct_Break=Extend
|
||||
@(require_results)
|
||||
is_indic_conjunct_break_extend :: proc(r: rune) -> bool {
|
||||
c := i32(r)
|
||||
p := binary_search(c, indic_conjunct_break_extend_ranges[:], len(indic_conjunct_break_extend_ranges)/2, 2)
|
||||
if p >= 0 && indic_conjunct_break_extend_ranges[p] <= c && c <= indic_conjunct_break_extend_ranges[p+1] {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
For grapheme text segmentation, from Unicode TR 29 Rev 43:
|
||||
|
||||
```
|
||||
Indic_Syllabic_Category = Consonant_Preceding_Repha, or
|
||||
Indic_Syllabic_Category = Consonant_Prefixed, or
|
||||
Prepended_Concatenation_Mark = Yes
|
||||
```
|
||||
*/
|
||||
@(require_results)
|
||||
is_gcb_prepend_class :: proc(r: rune) -> bool {
|
||||
return is_indic_consonant_preceding_repha(r) || is_indic_consonant_prefixed(r) || is_prepended_concatenation_mark(r)
|
||||
}
|
||||
|
||||
/*
|
||||
For grapheme text segmentation, from Unicode TR 29 Rev 43:
|
||||
|
||||
```
|
||||
Grapheme_Extend = Yes, or
|
||||
Emoji_Modifier = Yes
|
||||
|
||||
This includes:
|
||||
General_Category = Nonspacing_Mark
|
||||
General_Category = Enclosing_Mark
|
||||
U+200C ZERO WIDTH NON-JOINER
|
||||
|
||||
plus a few General_Category = Spacing_Mark needed for canonical equivalence.
|
||||
```
|
||||
*/
|
||||
@(require_results)
|
||||
is_gcb_extend_class :: proc(r: rune) -> bool {
|
||||
return is_grapheme_extend(r) || is_emoji_modifier(r)
|
||||
}
|
||||
|
||||
//
|
||||
// End of Unicode 15.1.0 block.
|
||||
//
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
387
core/unicode/utf8/grapheme.odin
Normal file
387
core/unicode/utf8/grapheme.odin
Normal file
@@ -0,0 +1,387 @@
|
||||
package utf8
|
||||
|
||||
import "core:unicode"
|
||||
|
||||
ZERO_WIDTH_JOINER :: unicode.ZERO_WIDTH_JOINER
|
||||
is_control :: unicode.is_control
|
||||
is_hangul_syllable_leading :: unicode.is_hangul_syllable_leading
|
||||
is_hangul_syllable_vowel :: unicode.is_hangul_syllable_vowel
|
||||
is_hangul_syllable_trailing :: unicode.is_hangul_syllable_trailing
|
||||
is_hangul_syllable_lv :: unicode.is_hangul_syllable_lv
|
||||
is_hangul_syllable_lvt :: unicode.is_hangul_syllable_lvt
|
||||
is_indic_conjunct_break_extend :: unicode.is_indic_conjunct_break_extend
|
||||
is_indic_conjunct_break_linker :: unicode.is_indic_conjunct_break_linker
|
||||
is_indic_conjunct_break_consonant :: unicode.is_indic_conjunct_break_consonant
|
||||
is_gcb_extend_class :: unicode.is_gcb_extend_class
|
||||
is_spacing_mark :: unicode.is_spacing_mark
|
||||
is_gcb_prepend_class :: unicode.is_gcb_prepend_class
|
||||
is_emoji_extended_pictographic :: unicode.is_emoji_extended_pictographic
|
||||
is_regional_indicator :: unicode.is_regional_indicator
|
||||
|
||||
|
||||
Grapheme :: struct {
|
||||
byte_index: int,
|
||||
rune_index: int,
|
||||
}
|
||||
|
||||
/*
|
||||
Count the individual graphemes in a UTF-8 string.
|
||||
|
||||
Inputs:
|
||||
- str: The input string.
|
||||
|
||||
Returns:
|
||||
- graphemes: The number of graphemes in the string.
|
||||
- runes: The number of runes in the string.
|
||||
*/
|
||||
@(require_results)
|
||||
grapheme_count :: proc(str: string) -> (graphemes, runes: int) {
|
||||
_, graphemes, runes = decode_grapheme_clusters(str, false)
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
Decode the individual graphemes in a UTF-8 string.
|
||||
|
||||
*Allocates Using Provided Allocator*
|
||||
|
||||
Inputs:
|
||||
- str: The input string.
|
||||
- track_graphemes: Whether or not to allocate and return `graphemes` with extra data about each grapheme.
|
||||
- allocator: (default: context.allocator)
|
||||
|
||||
Returns:
|
||||
- graphemes: Extra data about each grapheme.
|
||||
- grapheme_count: The number of graphemes in the string.
|
||||
- rune_count: The number of runes in the string.
|
||||
*/
|
||||
@(require_results)
|
||||
decode_grapheme_clusters :: proc(
|
||||
str: string,
|
||||
track_graphemes := true,
|
||||
allocator := context.allocator,
|
||||
) -> (
|
||||
graphemes: [dynamic]Grapheme,
|
||||
grapheme_count: int,
|
||||
rune_count: int,
|
||||
) {
|
||||
// The following procedure implements text segmentation by breaking on
|
||||
// Grapheme Cluster Boundaries[1], using the values[2] and rules[3] from
|
||||
// the Unicode® Standard Annex #29, entitled:
|
||||
//
|
||||
// UNICODE TEXT SEGMENTATION
|
||||
//
|
||||
// Version: Unicode 15.1.0
|
||||
// Date: 2023-08-16
|
||||
// Revision: 43
|
||||
//
|
||||
// This procedure is conformant[4] to UAX29-C1-1, otherwise known as the
|
||||
// extended, non-legacy ruleset.
|
||||
//
|
||||
// Please see the references below for more information.
|
||||
//
|
||||
//
|
||||
// NOTE(Feoramund): This procedure has not been highly optimized.
|
||||
// A couple opportunities were taken to bypass repeated checking when a
|
||||
// rune is outside of certain codepoint ranges, but little else has been
|
||||
// done. Standard switches, conditionals, and binary search are used to
|
||||
// see if a rune fits into a certain category.
|
||||
//
|
||||
// I did find that only one prior rune of state was necessary to build an
|
||||
// algorithm that successfully passes all 4,835 test cases provided with
|
||||
// this implementation from the Unicode organization's website.
|
||||
//
|
||||
// My initial implementation tracked explicit breaks and counted them once
|
||||
// the string iteration had terminated. I've found this current
|
||||
// implementation to be far simpler and need no allocations (unless the
|
||||
// caller wants position data).
|
||||
//
|
||||
// Most rules work backwards instead of forwards which has helped keep this
|
||||
// simple, despite its length and verbosity.
|
||||
//
|
||||
//
|
||||
// The implementation has been left verbose and in the order described by
|
||||
// the specification, to enable better readability and future upkeep.
|
||||
//
|
||||
// Some possible optimizations might include:
|
||||
//
|
||||
// - saving the type of `last_rune` instead of the exact rune.
|
||||
// - reordering rules.
|
||||
// - combining tables.
|
||||
//
|
||||
//
|
||||
// [1]: https://www.unicode.org/reports/tr29/#Grapheme_Cluster_Boundaries
|
||||
// [2]: https://www.unicode.org/reports/tr29/#Default_Grapheme_Cluster_Table
|
||||
// [3]: https://www.unicode.org/reports/tr29/#Grapheme_Cluster_Boundary_Rules
|
||||
// [4]: https://www.unicode.org/reports/tr29/#Conformance
|
||||
|
||||
Grapheme_Cluster_Sequence :: enum {
|
||||
None,
|
||||
Indic,
|
||||
Emoji,
|
||||
Regional,
|
||||
}
|
||||
|
||||
context.allocator = allocator
|
||||
|
||||
last_rune: rune
|
||||
last_rune_breaks_forward: bool
|
||||
|
||||
last_grapheme_count: int
|
||||
|
||||
bypass_next_rune: bool
|
||||
|
||||
regional_indicator_counter: int
|
||||
|
||||
current_sequence: Grapheme_Cluster_Sequence
|
||||
continue_sequence: bool
|
||||
|
||||
for this_rune, byte_index in str {
|
||||
defer {
|
||||
// "Break at the start and end of text, unless the text is empty."
|
||||
//
|
||||
// GB1: sot ÷ Any
|
||||
// GB2: Any ÷ eot
|
||||
if rune_count == 0 && grapheme_count == 0 {
|
||||
grapheme_count += 1
|
||||
}
|
||||
if track_graphemes && grapheme_count > last_grapheme_count {
|
||||
append(&graphemes, Grapheme{ byte_index, rune_count })
|
||||
}
|
||||
last_grapheme_count = grapheme_count
|
||||
|
||||
last_rune = this_rune
|
||||
rune_count += 1
|
||||
|
||||
if !continue_sequence {
|
||||
current_sequence = .None
|
||||
regional_indicator_counter = 0
|
||||
}
|
||||
continue_sequence = false
|
||||
}
|
||||
|
||||
// "Do not break between a CR and LF. Otherwise, break before and after controls."
|
||||
//
|
||||
// GB3: CR × LF
|
||||
// GB4: (Control | CR | LF) ÷
|
||||
// GB5: ÷ (Control | CR | LF)
|
||||
if this_rune == '\n' && last_rune == '\r' {
|
||||
last_rune_breaks_forward = false
|
||||
bypass_next_rune = false
|
||||
continue
|
||||
}
|
||||
|
||||
if is_control(this_rune) {
|
||||
grapheme_count += 1
|
||||
last_rune_breaks_forward = true
|
||||
bypass_next_rune = true
|
||||
continue
|
||||
}
|
||||
|
||||
// (This check is for rules that work forwards, instead of backwards.)
|
||||
if bypass_next_rune {
|
||||
if last_rune_breaks_forward {
|
||||
grapheme_count += 1
|
||||
last_rune_breaks_forward = false
|
||||
}
|
||||
|
||||
bypass_next_rune = false
|
||||
continue
|
||||
}
|
||||
|
||||
// (Optimization 1: Prevent low runes from proceeding further.)
|
||||
//
|
||||
// * 0xA9 and 0xAE are in the Extended_Pictographic range,
|
||||
// which is checked later in GB11.
|
||||
if this_rune != 0xA9 && this_rune != 0xAE && this_rune <= 0x2FF {
|
||||
grapheme_count += 1
|
||||
continue
|
||||
}
|
||||
|
||||
// (Optimization 2: Check if the rune is in the Hangul space before getting specific.)
|
||||
if 0x1100 <= this_rune && this_rune <= 0xD7FB {
|
||||
// "Do not break Hangul syllable sequences."
|
||||
//
|
||||
// GB6: L × (L | V | LV | LVT)
|
||||
// GB7: (LV | V) × (V | T)
|
||||
// GB8: (LVT | T) × T
|
||||
if is_hangul_syllable_leading(this_rune) ||
|
||||
is_hangul_syllable_lv(this_rune) ||
|
||||
is_hangul_syllable_lvt(this_rune)
|
||||
{
|
||||
if !is_hangul_syllable_leading(last_rune) {
|
||||
grapheme_count += 1
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if is_hangul_syllable_vowel(this_rune) {
|
||||
if is_hangul_syllable_leading(last_rune) ||
|
||||
is_hangul_syllable_vowel(last_rune) ||
|
||||
is_hangul_syllable_lv(last_rune)
|
||||
{
|
||||
continue
|
||||
}
|
||||
grapheme_count += 1
|
||||
continue
|
||||
}
|
||||
|
||||
if is_hangul_syllable_trailing(this_rune) {
|
||||
if is_hangul_syllable_trailing(last_rune) ||
|
||||
is_hangul_syllable_lvt(last_rune) ||
|
||||
is_hangul_syllable_lv(last_rune) ||
|
||||
is_hangul_syllable_vowel(last_rune)
|
||||
{
|
||||
continue
|
||||
}
|
||||
grapheme_count += 1
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// "Do not break before extending characters or ZWJ."
|
||||
//
|
||||
// GB9: × (Extend | ZWJ)
|
||||
if this_rune == ZERO_WIDTH_JOINER {
|
||||
continue_sequence = true
|
||||
continue
|
||||
}
|
||||
|
||||
if is_gcb_extend_class(this_rune) {
|
||||
// (Support for GB9c.)
|
||||
if current_sequence == .Indic {
|
||||
if is_indic_conjunct_break_extend(this_rune) && (
|
||||
is_indic_conjunct_break_linker(last_rune) ||
|
||||
is_indic_conjunct_break_consonant(last_rune) )
|
||||
{
|
||||
continue_sequence = true
|
||||
continue
|
||||
}
|
||||
|
||||
if is_indic_conjunct_break_linker(this_rune) && (
|
||||
is_indic_conjunct_break_linker(last_rune) ||
|
||||
is_indic_conjunct_break_extend(last_rune) ||
|
||||
is_indic_conjunct_break_consonant(last_rune) )
|
||||
{
|
||||
continue_sequence = true
|
||||
continue
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
// (Support for GB11.)
|
||||
if current_sequence == .Emoji && (
|
||||
is_gcb_extend_class(last_rune) ||
|
||||
is_emoji_extended_pictographic(last_rune) )
|
||||
{
|
||||
continue_sequence = true
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
// _The GB9a and GB9b rules only apply to extended grapheme clusters:_
|
||||
// "Do not break before SpacingMarks, or after Prepend characters."
|
||||
//
|
||||
// GB9a: × SpacingMark
|
||||
// GB9b: Prepend ×
|
||||
if is_spacing_mark(this_rune) {
|
||||
continue
|
||||
}
|
||||
|
||||
if is_gcb_prepend_class(this_rune) {
|
||||
grapheme_count += 1
|
||||
bypass_next_rune = true
|
||||
continue
|
||||
}
|
||||
|
||||
// _The GB9c rule only applies to extended grapheme clusters:_
|
||||
// "Do not break within certain combinations with Indic_Conjunct_Break (InCB)=Linker."
|
||||
//
|
||||
// GB9c: \p{InCB=Consonant} [ \p{InCB=Extend} \p{InCB=Linker} ]* \p{InCB=Linker} [ \p{InCB=Extend} \p{InCB=Linker} ]* × \p{InCB=Consonant}
|
||||
if is_indic_conjunct_break_consonant(this_rune) {
|
||||
if current_sequence == .Indic {
|
||||
if last_rune == ZERO_WIDTH_JOINER ||
|
||||
is_indic_conjunct_break_linker(last_rune)
|
||||
{
|
||||
continue_sequence = true
|
||||
} else {
|
||||
grapheme_count += 1
|
||||
}
|
||||
} else {
|
||||
grapheme_count += 1
|
||||
current_sequence = .Indic
|
||||
continue_sequence = true
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if is_indic_conjunct_break_extend(this_rune) {
|
||||
if current_sequence == .Indic {
|
||||
if is_indic_conjunct_break_consonant(last_rune) ||
|
||||
is_indic_conjunct_break_linker(last_rune)
|
||||
{
|
||||
continue_sequence = true
|
||||
} else {
|
||||
grapheme_count += 1
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if is_indic_conjunct_break_linker(this_rune) {
|
||||
if current_sequence == .Indic {
|
||||
if is_indic_conjunct_break_extend(last_rune) ||
|
||||
is_indic_conjunct_break_linker(last_rune)
|
||||
{
|
||||
continue_sequence = true
|
||||
} else {
|
||||
grapheme_count += 1
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
//
|
||||
// (Curiously, there is no GB10.)
|
||||
//
|
||||
|
||||
// "Do not break within emoji modifier sequences or emoji zwj sequences."
|
||||
//
|
||||
// GB11: \p{Extended_Pictographic} Extend* ZWJ × \p{Extended_Pictographic}
|
||||
if is_emoji_extended_pictographic(this_rune) {
|
||||
if current_sequence != .Emoji || last_rune != ZERO_WIDTH_JOINER {
|
||||
grapheme_count += 1
|
||||
}
|
||||
current_sequence = .Emoji
|
||||
continue_sequence = true
|
||||
continue
|
||||
}
|
||||
|
||||
// "Do not break within emoji flag sequences.
|
||||
// That is, do not break between regional indicator (RI) symbols
|
||||
// if there is an odd number of RI characters before the break point."
|
||||
//
|
||||
// GB12: sot (RI RI)* RI × RI
|
||||
// GB13: [^RI] (RI RI)* RI × RI
|
||||
if is_regional_indicator(this_rune) {
|
||||
if regional_indicator_counter & 1 == 0 {
|
||||
grapheme_count += 1
|
||||
}
|
||||
|
||||
current_sequence = .Regional
|
||||
continue_sequence = true
|
||||
regional_indicator_counter += 1
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
// "Otherwise, break everywhere."
|
||||
//
|
||||
// GB999: Any ÷ Any
|
||||
grapheme_count += 1
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
@@ -121,6 +121,7 @@ import edit "core:text/edit"
|
||||
import thread "core:thread"
|
||||
import time "core:time"
|
||||
import datetime "core:time/datetime"
|
||||
import flags "core:flags"
|
||||
|
||||
import sysinfo "core:sys/info"
|
||||
|
||||
@@ -233,6 +234,7 @@ _ :: edit
|
||||
_ :: thread
|
||||
_ :: time
|
||||
_ :: datetime
|
||||
_ :: flags
|
||||
_ :: sysinfo
|
||||
_ :: unicode
|
||||
_ :: utf8
|
||||
|
||||
@@ -1077,7 +1077,7 @@ gb_internal void check_proc_decl(CheckerContext *ctx, Entity *e, DeclInfo *d) {
|
||||
}
|
||||
|
||||
|
||||
if (e->pkg != nullptr && e->token.string == "main") {
|
||||
if (e->pkg != nullptr && e->token.string == "main" && !build_context.no_entry_point) {
|
||||
if (e->pkg->kind != Package_Runtime) {
|
||||
if (pt->param_count != 0 ||
|
||||
pt->result_count != 0) {
|
||||
|
||||
@@ -9819,7 +9819,9 @@ gb_internal ExprKind check_compound_literal(CheckerContext *c, Operand *o, Ast *
|
||||
if (tav.mode != Addressing_Constant) {
|
||||
continue;
|
||||
}
|
||||
GB_ASSERT(tav.value.kind == ExactValue_Integer);
|
||||
if (tav.value.kind != ExactValue_Integer) {
|
||||
continue;
|
||||
}
|
||||
i64 v = big_int_to_i64(&tav.value.value_integer);
|
||||
i64 lower = bt->BitSet.lower;
|
||||
u64 index = cast(u64)(v-lower);
|
||||
|
||||
@@ -4110,6 +4110,7 @@ gb_internal void check_collect_value_decl(CheckerContext *c, Ast *decl) {
|
||||
bool is_test = false;
|
||||
bool is_init = false;
|
||||
bool is_fini = false;
|
||||
bool is_priv = false;
|
||||
|
||||
for_array(i, vd->attributes) {
|
||||
Ast *attr = vd->attributes[i];
|
||||
@@ -4154,6 +4155,8 @@ gb_internal void check_collect_value_decl(CheckerContext *c, Ast *decl) {
|
||||
}
|
||||
if (!success) {
|
||||
error(value, "'%.*s' expects no parameter, or a string literal containing \"file\" or \"package\"", LIT(name));
|
||||
} else {
|
||||
is_priv = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -4175,6 +4178,11 @@ gb_internal void check_collect_value_decl(CheckerContext *c, Ast *decl) {
|
||||
}
|
||||
}
|
||||
|
||||
if (is_priv && is_test) {
|
||||
error(decl, "Attribute 'private' is not allowed on a test case");
|
||||
return;
|
||||
}
|
||||
|
||||
if (entity_visibility_kind == EntityVisiblity_Public &&
|
||||
(c->scope->flags&ScopeFlag_File) &&
|
||||
c->scope->file) {
|
||||
|
||||
@@ -265,16 +265,20 @@ gb_internal i32 linker_stage(LinkerData *gen) {
|
||||
if (!build_context.use_lld) { // msvc
|
||||
String res_path = {};
|
||||
defer (gb_free(heap_allocator(), res_path.text));
|
||||
|
||||
// TODO(Jeroen): Add ability to reuse .res file instead of recompiling, if `-resource:file.res` is given.
|
||||
if (build_context.has_resource) {
|
||||
String temp_res_path = path_to_string(heap_allocator(), build_context.build_paths[BuildPath_RES]);
|
||||
res_path = concatenate3_strings(heap_allocator(), str_lit("\""), temp_res_path, str_lit("\""));
|
||||
gb_free(heap_allocator(), temp_res_path.text);
|
||||
|
||||
String rc_path = path_to_string(heap_allocator(), build_context.build_paths[BuildPath_RC]);
|
||||
String temp_rc_path = path_to_string(heap_allocator(), build_context.build_paths[BuildPath_RC]);
|
||||
String rc_path = concatenate3_strings(heap_allocator(), str_lit("\""), temp_rc_path, str_lit("\""));
|
||||
gb_free(heap_allocator(), temp_rc_path.text);
|
||||
defer (gb_free(heap_allocator(), rc_path.text));
|
||||
|
||||
result = system_exec_command_line_app("msvc-link",
|
||||
"\"%.*src.exe\" /nologo /fo \"%.*s\" \"%.*s\"",
|
||||
"\"%.*src.exe\" /nologo /fo %.*s %.*s",
|
||||
LIT(windows_sdk_bin_path),
|
||||
LIT(res_path),
|
||||
LIT(rc_path)
|
||||
|
||||
@@ -2934,7 +2934,8 @@ int main(int arg_count, char const **arg_ptr) {
|
||||
// TODO(jeroen): Remove the `init_filename` param.
|
||||
// Let's put that on `build_context.build_paths[0]` instead.
|
||||
if (parse_packages(parser, init_filename) != ParseFile_None) {
|
||||
return 1;
|
||||
GB_ASSERT_MSG(any_errors(), "parse_packages failed but no error was reported.");
|
||||
// We depend on the next conditional block to return 1, after printing errors.
|
||||
}
|
||||
|
||||
if (any_errors()) {
|
||||
|
||||
@@ -20,15 +20,12 @@ test_avl :: proc(t: ^testing.T) {
|
||||
iter := avl.iterator(&tree, avl.Direction.Forward)
|
||||
testing.expect(t, avl.iterator_get(&iter) == nil, "empty/iterator: first node should be nil")
|
||||
|
||||
r: rand.Rand
|
||||
rand.init(&r, t.seed)
|
||||
|
||||
// Test insertion.
|
||||
NR_INSERTS :: 32 + 1 // Ensure at least 1 collision.
|
||||
inserted_map := make(map[int]^avl.Node(int))
|
||||
defer delete(inserted_map)
|
||||
for i := 0; i < NR_INSERTS; i += 1 {
|
||||
v := int(rand.uint32(&r) & 0x1f)
|
||||
v := int(rand.uint32() & 0x1f)
|
||||
existing_node, in_map := inserted_map[v]
|
||||
|
||||
n, ok, _ := avl.find_or_insert(&tree, v)
|
||||
@@ -78,7 +75,7 @@ test_avl :: proc(t: ^testing.T) {
|
||||
testing.expect(t, visited == nrEntries, "iterator/backward: visited")
|
||||
|
||||
// Test removal.
|
||||
rand.shuffle(inserted_values[:], &r)
|
||||
rand.shuffle(inserted_values[:])
|
||||
for v, i in inserted_values {
|
||||
node := avl.find(&tree, v)
|
||||
testing.expect(t, node != nil, "remove: find (pre)")
|
||||
|
||||
@@ -14,9 +14,6 @@ test_rbtree_integer :: proc(t: ^testing.T, $Key: typeid, $Value: typeid) {
|
||||
defer mem.tracking_allocator_destroy(&track)
|
||||
context.allocator = mem.tracking_allocator(&track)
|
||||
|
||||
r: rand.Rand
|
||||
rand.init(&r, t.seed)
|
||||
|
||||
log.infof("Testing Red-Black Tree($Key=%v,$Value=%v) using random seed %v.", type_info_of(Key), type_info_of(Value), t.seed)
|
||||
tree: rb.Tree(Key, Value)
|
||||
rb.init(&tree)
|
||||
@@ -35,9 +32,9 @@ test_rbtree_integer :: proc(t: ^testing.T, $Key: typeid, $Value: typeid) {
|
||||
max_key := min(Key)
|
||||
|
||||
for i := 0; i < NR_INSERTS; i += 1 {
|
||||
k := Key(rand.uint32(&r)) & 0x1f
|
||||
k := Key(rand.uint32()) & 0x1f
|
||||
min_key = min(min_key, k); max_key = max(max_key, k)
|
||||
v := Value(rand.uint32(&r))
|
||||
v := Value(rand.uint32())
|
||||
|
||||
existing_node, in_map := inserted_map[k]
|
||||
n, inserted, _ := rb.find_or_insert(&tree, k, v)
|
||||
@@ -92,7 +89,7 @@ test_rbtree_integer :: proc(t: ^testing.T, $Key: typeid, $Value: typeid) {
|
||||
testing.expect(t, visited == entry_count, "iterator/backward: visited")
|
||||
|
||||
// Test removal (and on_remove callback)
|
||||
rand.shuffle(inserted_keys[:], &r)
|
||||
rand.shuffle(inserted_keys[:])
|
||||
callback_count := entry_count
|
||||
tree.user_data = &callback_count
|
||||
tree.on_remove = proc(key: Key, value: Value, user_data: rawptr) {
|
||||
|
||||
1393
tests/core/flags/test_core_flags.odin
Normal file
1393
tests/core/flags/test_core_flags.odin
Normal file
File diff suppressed because it is too large
Load Diff
@@ -53,9 +53,9 @@ test_xxhash_zero_streamed_random_updates :: proc(t: ^testing.T) {
|
||||
testing.expect(t, xxh3_128_err == nil, "Problem initializing XXH3_128 state")
|
||||
|
||||
// XXH3_128_update
|
||||
random_seed := rand.create(t.seed)
|
||||
rand.reset(t.seed)
|
||||
for len(b) > 0 {
|
||||
update_size := min(len(b), rand.int_max(8192, &random_seed))
|
||||
update_size := min(len(b), rand.int_max(8192))
|
||||
if update_size > 4096 {
|
||||
update_size %= 73
|
||||
}
|
||||
|
||||
35
tests/core/math/rand/test_core_math_rand.odin
Normal file
35
tests/core/math/rand/test_core_math_rand.odin
Normal file
@@ -0,0 +1,35 @@
|
||||
package test_core_math_rand
|
||||
|
||||
import "core:math/rand"
|
||||
import "core:testing"
|
||||
|
||||
@test
|
||||
test_default_rand_determinism :: proc(t: ^testing.T) {
|
||||
rand.reset(13)
|
||||
first_value := rand.int127()
|
||||
rand.reset(13)
|
||||
second_value := rand.int127()
|
||||
|
||||
testing.expect(t, first_value == second_value, "Context default random number generator is non-deterministic.")
|
||||
}
|
||||
|
||||
@test
|
||||
test_default_rand_determinism_user_set :: proc(t: ^testing.T) {
|
||||
rng_state_1 := rand.create(13)
|
||||
rng_state_2 := rand.create(13)
|
||||
|
||||
rng_1 := rand.default_random_generator(&rng_state_1)
|
||||
rng_2 := rand.default_random_generator(&rng_state_2)
|
||||
|
||||
first_value, second_value: i128
|
||||
{
|
||||
context.random_generator = rng_1
|
||||
first_value = rand.int127()
|
||||
}
|
||||
{
|
||||
context.random_generator = rng_2
|
||||
second_value = rand.int127()
|
||||
}
|
||||
|
||||
testing.expect(t, first_value == second_value, "User-set default random number generator is non-deterministic.")
|
||||
}
|
||||
@@ -19,11 +19,13 @@ download_assets :: proc() {
|
||||
@(require) import "encoding/json"
|
||||
@(require) import "encoding/varint"
|
||||
@(require) import "encoding/xml"
|
||||
@(require) import "flags"
|
||||
@(require) import "fmt"
|
||||
@(require) import "math"
|
||||
@(require) import "math/big"
|
||||
@(require) import "math/linalg/glsl"
|
||||
@(require) import "math/noise"
|
||||
@(require) import "math/rand"
|
||||
@(require) import "mem"
|
||||
@(require) import "net"
|
||||
@(require) import "odin"
|
||||
@@ -37,3 +39,4 @@ download_assets :: proc() {
|
||||
@(require) import "text/match"
|
||||
@(require) import "thread"
|
||||
@(require) import "time"
|
||||
@(require) import "unicode"
|
||||
|
||||
@@ -3,6 +3,7 @@ package test_core_slice
|
||||
import "core:slice"
|
||||
import "core:testing"
|
||||
import "core:math/rand"
|
||||
import "core:log"
|
||||
|
||||
@test
|
||||
test_sort_with_indices :: proc(t: ^testing.T) {
|
||||
@@ -10,7 +11,7 @@ test_sort_with_indices :: proc(t: ^testing.T) {
|
||||
test_sizes :: []int{7, 13, 347, 1031, 10111, 100003}
|
||||
|
||||
for test_size in test_sizes {
|
||||
r := rand.create(t.seed)
|
||||
rand.reset(t.seed)
|
||||
|
||||
vals := make([]u64, test_size)
|
||||
r_idx := make([]int, test_size) // Reverse index
|
||||
@@ -21,7 +22,7 @@ test_sort_with_indices :: proc(t: ^testing.T) {
|
||||
|
||||
// Set up test values
|
||||
for _, i in vals {
|
||||
vals[i] = rand.uint64(&r)
|
||||
vals[i] = rand.uint64()
|
||||
}
|
||||
|
||||
// Sort
|
||||
@@ -29,7 +30,7 @@ test_sort_with_indices :: proc(t: ^testing.T) {
|
||||
defer delete(f_idx)
|
||||
|
||||
// Verify sorted test values
|
||||
rand.init(&r, t.seed)
|
||||
rand.reset(t.seed)
|
||||
|
||||
for v, i in f_idx {
|
||||
r_idx[v] = i
|
||||
@@ -45,7 +46,7 @@ test_sort_with_indices :: proc(t: ^testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
idx_pass := vals[r_idx[i]] == rand.uint64(&r)
|
||||
idx_pass := vals[r_idx[i]] == rand.uint64()
|
||||
testing.expect(t, idx_pass, "Expected index to have been sorted")
|
||||
if !idx_pass {
|
||||
break
|
||||
@@ -61,7 +62,7 @@ test_sort_by_indices :: proc(t: ^testing.T) {
|
||||
test_sizes :: []int{7, 13, 347, 1031, 10111, 100003}
|
||||
|
||||
for test_size in test_sizes {
|
||||
r := rand.create(t.seed)
|
||||
rand.reset(t.seed)
|
||||
|
||||
vals := make([]u64, test_size)
|
||||
r_idx := make([]int, test_size) // Reverse index
|
||||
@@ -72,7 +73,7 @@ test_sort_by_indices :: proc(t: ^testing.T) {
|
||||
|
||||
// Set up test values
|
||||
for _, i in vals {
|
||||
vals[i] = rand.uint64(&r)
|
||||
vals[i] = rand.uint64()
|
||||
}
|
||||
|
||||
// Sort
|
||||
@@ -80,7 +81,7 @@ test_sort_by_indices :: proc(t: ^testing.T) {
|
||||
defer delete(f_idx)
|
||||
|
||||
// Verify sorted test values
|
||||
rand.init(&r, t.seed)
|
||||
rand.reset(t.seed)
|
||||
|
||||
{
|
||||
indices := make([]int, test_size)
|
||||
@@ -200,12 +201,12 @@ test_permutation_iterator :: proc(t: ^testing.T) {
|
||||
permutations_counted: int
|
||||
for slice.permute(&iter) {
|
||||
n := 0
|
||||
for item, index in s {
|
||||
for item in s {
|
||||
n *= 10
|
||||
n += item
|
||||
}
|
||||
if n in seen {
|
||||
testing.fail_now(t, "Permutation iterator made a duplicate permutation.")
|
||||
log.error("Permutation iterator made a duplicate permutation.")
|
||||
return
|
||||
}
|
||||
seen[n] = true
|
||||
@@ -215,3 +216,63 @@ test_permutation_iterator :: proc(t: ^testing.T) {
|
||||
testing.expect_value(t, len(seen), FAC_5)
|
||||
testing.expect_value(t, permutations_counted, FAC_5)
|
||||
}
|
||||
|
||||
// Test inputs from #3276 and #3769
|
||||
UNIQUE_TEST_VECTORS :: [][2][]int{
|
||||
{{2,2,2}, {2}},
|
||||
{{1,1,1,2,2,3,3,3,3}, {1,2,3}},
|
||||
{{1,2,4,4,5}, {1,2,4,5}},
|
||||
}
|
||||
|
||||
@test
|
||||
test_unique :: proc(t: ^testing.T) {
|
||||
for v in UNIQUE_TEST_VECTORS {
|
||||
assorted := v[0]
|
||||
expected := v[1]
|
||||
|
||||
uniq := slice.unique(assorted)
|
||||
testing.expectf(t, slice.equal(uniq, expected), "Expected slice.uniq(%v) == %v, got %v", v[0], v[1], uniq)
|
||||
}
|
||||
|
||||
for v in UNIQUE_TEST_VECTORS {
|
||||
assorted := v[0]
|
||||
expected := v[1]
|
||||
|
||||
uniq := slice.unique_proc(assorted, proc(a, b: int) -> bool {
|
||||
return a == b
|
||||
})
|
||||
testing.expectf(t, slice.equal(uniq, expected), "Expected slice.unique_proc(%v, ...) == %v, got %v", v[0], v[1], uniq)
|
||||
}
|
||||
|
||||
r := rand.create(t.seed)
|
||||
context.random_generator = rand.default_random_generator(&r)
|
||||
|
||||
// 10_000 random tests
|
||||
for _ in 0..<10_000 {
|
||||
assorted: [dynamic]i64
|
||||
expected: [dynamic]i64
|
||||
|
||||
// Prime with 1 value
|
||||
old := rand.int63()
|
||||
append(&assorted, old)
|
||||
append(&expected, old)
|
||||
|
||||
// Add 99 additional random values
|
||||
for _ in 1..<100 {
|
||||
new := rand.int63()
|
||||
append(&assorted, new)
|
||||
if old != new {
|
||||
append(&expected, new)
|
||||
}
|
||||
old = new
|
||||
}
|
||||
|
||||
original := slice.clone(assorted[:])
|
||||
uniq := slice.unique(assorted[:])
|
||||
testing.expectf(t, slice.equal(uniq, expected[:]), "Expected slice.uniq(%v) == %v, got %v", original, expected, uniq)
|
||||
|
||||
delete(assorted)
|
||||
delete(original)
|
||||
delete(expected)
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import "core:testing"
|
||||
import "core:text/i18n"
|
||||
|
||||
T :: i18n.get
|
||||
Tn :: i18n.get_n
|
||||
|
||||
Test :: struct {
|
||||
section: string,
|
||||
@@ -47,7 +48,8 @@ test_custom_pluralizer :: proc(t: ^testing.T) {
|
||||
{"", "Message1/plural", "This is message 1", 1},
|
||||
{"", "Message1/plural", "This is message 1 - plural A", 1_000_000},
|
||||
{"", "Message1/plural", "This is message 1 - plural B", 42},
|
||||
// This isn't in the catalog, so should ruturn the key.
|
||||
|
||||
// This isn't in the catalog, so should return the key.
|
||||
{"", "Come visit us on Discord!", "Come visit us on Discord!", 1},
|
||||
},
|
||||
})
|
||||
@@ -61,11 +63,11 @@ test_mixed_context :: proc(t: ^testing.T) {
|
||||
plural = nil,
|
||||
tests = {
|
||||
// These are in the catalog.
|
||||
{"", "Message1", "This is message 1 without Context", 1},
|
||||
{"Context", "Message1", "This is message 1 with Context", 1},
|
||||
{"", "Message1", "This is message 1 without Context",-1},
|
||||
{"Context", "Message1", "This is message 1 with Context", -1},
|
||||
|
||||
// This isn't in the catalog, so should ruturn the key.
|
||||
{"", "Come visit us on Discord!", "Come visit us on Discord!", 1},
|
||||
{"", "Come visit us on Discord!", "Come visit us on Discord!", -1},
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -90,15 +92,15 @@ test_nl_mo :: proc(t: ^testing.T) {
|
||||
plural = nil, // Default pluralizer
|
||||
tests = {
|
||||
// These are in the catalog.
|
||||
{"", "There are 69,105 leaves here.", "Er zijn hier 69.105 bladeren.", 1},
|
||||
{"", "Hellope, World!", "Hallo, Wereld!", 1},
|
||||
{"", "There are 69,105 leaves here.", "Er zijn hier 69.105 bladeren.", -1},
|
||||
{"", "Hellope, World!", "Hallo, Wereld!", -1},
|
||||
{"", "There is %d leaf.\n", "Er is %d blad.\n", 1},
|
||||
{"", "There are %d leaves.\n", "Er is %d blad.\n", 1},
|
||||
{"", "There is %d leaf.\n", "Er zijn %d bladeren.\n", 42},
|
||||
{"", "There are %d leaves.\n", "Er zijn %d bladeren.\n", 42},
|
||||
|
||||
// This isn't in the catalog, so should ruturn the key.
|
||||
{"", "Come visit us on Discord!", "Come visit us on Discord!", 1},
|
||||
{"", "Come visit us on Discord!", "Come visit us on Discord!", -1},
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -111,15 +113,15 @@ test_qt_linguist :: proc(t: ^testing.T) {
|
||||
plural = nil, // Default pluralizer
|
||||
tests = {
|
||||
// These are in the catalog.
|
||||
{"Page", "Text for translation", "Tekst om te vertalen", 1},
|
||||
{"Page", "Also text to translate", "Ook tekst om te vertalen", 1},
|
||||
{"installscript", "99 bottles of beer on the wall", "99 flessen bier op de muur", 1},
|
||||
{"Page", "Text for translation", "Tekst om te vertalen", -1},
|
||||
{"Page", "Also text to translate", "Ook tekst om te vertalen", -1},
|
||||
{"installscript", "99 bottles of beer on the wall", "99 flessen bier op de muur", -1},
|
||||
{"apple_count", "%d apple(s)", "%d appel", 1},
|
||||
{"apple_count", "%d apple(s)", "%d appels", 42},
|
||||
|
||||
// These aren't in the catalog, so should ruturn the key.
|
||||
{"", "Come visit us on Discord!", "Come visit us on Discord!", 1},
|
||||
{"Fake_Section", "Come visit us on Discord!", "Come visit us on Discord!", 1},
|
||||
{"", "Come visit us on Discord!", "Come visit us on Discord!", -1},
|
||||
{"Fake_Section", "Come visit us on Discord!", "Come visit us on Discord!", -1},
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -133,16 +135,16 @@ test_qt_linguist_merge_sections :: proc(t: ^testing.T) {
|
||||
options = {merge_sections = true},
|
||||
tests = {
|
||||
// All of them are now in section "", lookup with original section should return the key.
|
||||
{"", "Text for translation", "Tekst om te vertalen", 1},
|
||||
{"", "Also text to translate", "Ook tekst om te vertalen", 1},
|
||||
{"", "99 bottles of beer on the wall", "99 flessen bier op de muur", 1},
|
||||
{"", "Text for translation", "Tekst om te vertalen", -1},
|
||||
{"", "Also text to translate", "Ook tekst om te vertalen", -1},
|
||||
{"", "99 bottles of beer on the wall", "99 flessen bier op de muur", -1},
|
||||
{"", "%d apple(s)", "%d appel", 1},
|
||||
{"", "%d apple(s)", "%d appels", 42},
|
||||
|
||||
// All of them are now in section "", lookup with original section should return the key.
|
||||
{"Page", "Text for translation", "Text for translation", 1},
|
||||
{"Page", "Also text to translate", "Also text to translate", 1},
|
||||
{"installscript", "99 bottles of beer on the wall", "99 bottles of beer on the wall", 1},
|
||||
{"Page", "Text for translation", "Text for translation", -1},
|
||||
{"Page", "Also text to translate", "Also text to translate", -1},
|
||||
{"installscript", "99 bottles of beer on the wall", "99 bottles of beer on the wall", -1},
|
||||
{"apple_count", "%d apple(s)", "%d apple(s)", 1},
|
||||
{"apple_count", "%d apple(s)", "%d apple(s)", 42},
|
||||
},
|
||||
@@ -175,7 +177,7 @@ test :: proc(t: ^testing.T, suite: Test_Suite, loc := #caller_location) {
|
||||
|
||||
if err == .None {
|
||||
for test in suite.tests {
|
||||
val := T(test.section, test.key, test.n, cat)
|
||||
val := test.n > -1 ? Tn(test.section, test.key, test.n, cat): T(test.section, test.key, cat)
|
||||
testing.expectf(t, val == test.val, "Expected key `%v` from section `%v`'s form for value `%v` to equal `%v`, got `%v`", test.key, test.section, test.n, test.val, val, loc=loc)
|
||||
}
|
||||
}
|
||||
|
||||
73
tests/core/unicode/test_core_unicode.odin
Normal file
73
tests/core/unicode/test_core_unicode.odin
Normal file
@@ -0,0 +1,73 @@
|
||||
package test_core_unicode
|
||||
|
||||
import "core:log"
|
||||
import "core:testing"
|
||||
import "core:unicode/utf8"
|
||||
|
||||
Test_Case :: struct {
|
||||
str: string,
|
||||
expected_clusters: int,
|
||||
}
|
||||
|
||||
run_test_cases :: proc(t: ^testing.T, test_cases: []Test_Case, loc := #caller_location) {
|
||||
failed := 0
|
||||
for c, i in test_cases {
|
||||
log.debugf("(#% 4i) %q ...", i, c.str)
|
||||
result, _ := utf8.grapheme_count(c.str)
|
||||
if !testing.expectf(t, result == c.expected_clusters,
|
||||
"(#% 4i) graphemes: %i != %i, %q %s", i, result, c.expected_clusters, c.str, c.str,
|
||||
loc = loc)
|
||||
{
|
||||
failed += 1
|
||||
}
|
||||
}
|
||||
|
||||
log.logf(.Error if failed > 0 else .Info, "% 4i/% 4i test cases failed.", failed, len(test_cases), location = loc)
|
||||
}
|
||||
|
||||
@test
|
||||
test_official_gcb_cases :: proc(t: ^testing.T) {
|
||||
run_test_cases(t, official_grapheme_break_test_cases)
|
||||
}
|
||||
|
||||
@test
|
||||
test_official_emoji_cases :: proc(t: ^testing.T) {
|
||||
run_test_cases(t, official_emoji_test_cases)
|
||||
}
|
||||
|
||||
@test
|
||||
test_grapheme_byte_index_segmentation :: proc(t: ^testing.T) {
|
||||
SAMPLE_1 :: "\U0001F600"
|
||||
SAMPLE_2 :: "\U0001F3F4\U000E0067\U000E0062\U000E0065\U000E006E\U000E0067\U000E007F"
|
||||
SAMPLE_3 :: "\U0001F468\U0001F3FB\u200D\U0001F9B0"
|
||||
|
||||
str := SAMPLE_1 + SAMPLE_2 + SAMPLE_3 + SAMPLE_2 + SAMPLE_1
|
||||
|
||||
graphemes, _, _ := utf8.decode_grapheme_clusters(str)
|
||||
defer delete(graphemes)
|
||||
|
||||
defer if testing.failed(t) {
|
||||
log.infof("%#v\n%q\n%v", graphemes, str, transmute([]u8)str)
|
||||
}
|
||||
if !testing.expect_value(t, len(graphemes), 5) {
|
||||
return
|
||||
}
|
||||
|
||||
testing.expect_value(t, graphemes[0].rune_index, 0)
|
||||
testing.expect_value(t, graphemes[1].rune_index, 1)
|
||||
testing.expect_value(t, graphemes[2].rune_index, 8)
|
||||
testing.expect_value(t, graphemes[3].rune_index, 12)
|
||||
testing.expect_value(t, graphemes[4].rune_index, 19)
|
||||
|
||||
grapheme_1 := str[graphemes[0].byte_index:graphemes[1].byte_index]
|
||||
grapheme_2 := str[graphemes[1].byte_index:graphemes[2].byte_index]
|
||||
grapheme_3 := str[graphemes[2].byte_index:graphemes[3].byte_index]
|
||||
grapheme_4 := str[graphemes[3].byte_index:graphemes[4].byte_index]
|
||||
grapheme_5 := str[graphemes[4].byte_index:]
|
||||
|
||||
testing.expectf(t, grapheme_1 == SAMPLE_1, "expected %q, got %q", SAMPLE_1, grapheme_1)
|
||||
testing.expectf(t, grapheme_2 == SAMPLE_2, "expected %q, got %q", SAMPLE_2, grapheme_2)
|
||||
testing.expectf(t, grapheme_3 == SAMPLE_3, "expected %q, got %q", SAMPLE_3, grapheme_3)
|
||||
testing.expectf(t, grapheme_4 == SAMPLE_2, "expected %q, got %q", SAMPLE_2, grapheme_2)
|
||||
testing.expectf(t, grapheme_5 == SAMPLE_1, "expected %q, got %q", SAMPLE_1, grapheme_1)
|
||||
}
|
||||
4912
tests/core/unicode/test_core_unicode_data.odin
Normal file
4912
tests/core/unicode/test_core_unicode_data.odin
Normal file
File diff suppressed because it is too large
Load Diff
@@ -16,10 +16,10 @@ map_insert_random_key_value :: proc(t: ^testing.T) {
|
||||
defer delete(m)
|
||||
|
||||
unique_keys := 0
|
||||
r := rand.create(t.seed + seed_incr)
|
||||
rand.reset(t.seed + seed_incr)
|
||||
for _ in 0..<entries {
|
||||
k := rand.int63(&r)
|
||||
v := rand.int63(&r)
|
||||
k := rand.int63()
|
||||
v := rand.int63()
|
||||
|
||||
if k not_in m {
|
||||
unique_keys += 1
|
||||
@@ -36,12 +36,12 @@ map_insert_random_key_value :: proc(t: ^testing.T) {
|
||||
testing.expectf(t, len(m) == unique_keys, "Expected len(map) to equal %v, got %v", unique_keys, len(m))
|
||||
|
||||
// Reset randomizer and verify
|
||||
r = rand.create(t.seed + seed_incr)
|
||||
rand.reset(t.seed + seed_incr)
|
||||
|
||||
num_fails := 0
|
||||
for _ in 0..<entries {
|
||||
k := rand.int63(&r)
|
||||
v := rand.int63(&r)
|
||||
k := rand.int63()
|
||||
v := rand.int63()
|
||||
|
||||
cond := m[k] == v
|
||||
if !cond {
|
||||
@@ -66,10 +66,11 @@ map_update_random_key_value :: proc(t: ^testing.T) {
|
||||
defer delete(m)
|
||||
|
||||
unique_keys := 0
|
||||
r := rand.create(t.seed + seed_incr)
|
||||
rand.reset(t.seed + seed_incr)
|
||||
|
||||
for _ in 0..<entries {
|
||||
k := rand.int63(&r)
|
||||
v := rand.int63(&r)
|
||||
k := rand.int63()
|
||||
v := rand.int63()
|
||||
|
||||
if k not_in m {
|
||||
unique_keys += 1
|
||||
@@ -88,21 +89,22 @@ map_update_random_key_value :: proc(t: ^testing.T) {
|
||||
half_entries := entries / 2
|
||||
|
||||
// Reset randomizer and update half the entries
|
||||
r = rand.create(t.seed + seed_incr)
|
||||
rand.reset(t.seed + seed_incr)
|
||||
|
||||
for _ in 0..<half_entries {
|
||||
k := rand.int63(&r)
|
||||
v := rand.int63(&r)
|
||||
k := rand.int63()
|
||||
v := rand.int63()
|
||||
|
||||
m[k] = v + 42
|
||||
}
|
||||
|
||||
// Reset randomizer and verify
|
||||
r = rand.create(t.seed + seed_incr)
|
||||
rand.reset(t.seed + seed_incr)
|
||||
|
||||
num_fails := 0
|
||||
for i in 0..<entries {
|
||||
k := rand.int63(&r)
|
||||
v := rand.int63(&r)
|
||||
k := rand.int63()
|
||||
v := rand.int63()
|
||||
|
||||
diff := i64(42) if i < half_entries else i64(0)
|
||||
cond := m[k] == (v + diff)
|
||||
@@ -128,10 +130,11 @@ map_delete_random_key_value :: proc(t: ^testing.T) {
|
||||
defer delete(m)
|
||||
|
||||
unique_keys := 0
|
||||
r := rand.create(t.seed + seed_incr)
|
||||
rand.reset(t.seed + seed_incr)
|
||||
|
||||
for _ in 0..<entries {
|
||||
k := rand.int63(&r)
|
||||
v := rand.int63(&r)
|
||||
k := rand.int63()
|
||||
v := rand.int63()
|
||||
|
||||
if k not_in m {
|
||||
unique_keys += 1
|
||||
@@ -150,21 +153,22 @@ map_delete_random_key_value :: proc(t: ^testing.T) {
|
||||
half_entries := entries / 2
|
||||
|
||||
// Reset randomizer and delete half the entries
|
||||
r = rand.create(t.seed + seed_incr)
|
||||
rand.reset(t.seed + seed_incr)
|
||||
|
||||
for _ in 0..<half_entries {
|
||||
k := rand.int63(&r)
|
||||
_ = rand.int63(&r)
|
||||
k := rand.int63()
|
||||
_ = rand.int63()
|
||||
|
||||
delete_key(&m, k)
|
||||
}
|
||||
|
||||
// Reset randomizer and verify
|
||||
r = rand.create(t.seed + seed_incr)
|
||||
rand.reset(t.seed + seed_incr)
|
||||
|
||||
num_fails := 0
|
||||
for i in 0..<entries {
|
||||
k := rand.int63(&r)
|
||||
v := rand.int63(&r)
|
||||
k := rand.int63()
|
||||
v := rand.int63()
|
||||
|
||||
if i < half_entries {
|
||||
if k in m {
|
||||
@@ -206,9 +210,10 @@ set_insert_random_key_value :: proc(t: ^testing.T) {
|
||||
defer delete(m)
|
||||
|
||||
unique_keys := 0
|
||||
r := rand.create(t.seed + seed_incr)
|
||||
rand.reset(t.seed + seed_incr)
|
||||
|
||||
for _ in 0..<entries {
|
||||
k := rand.int63(&r)
|
||||
k := rand.int63()
|
||||
if k not_in m {
|
||||
unique_keys += 1
|
||||
}
|
||||
@@ -224,11 +229,11 @@ set_insert_random_key_value :: proc(t: ^testing.T) {
|
||||
testing.expectf(t, len(m) == unique_keys, "Expected len(map) to equal %v, got %v", unique_keys, len(m))
|
||||
|
||||
// Reset randomizer and verify
|
||||
r = rand.create(t.seed + seed_incr)
|
||||
rand.reset(t.seed + seed_incr)
|
||||
|
||||
num_fails := 0
|
||||
for _ in 0..<entries {
|
||||
k := rand.int63(&r)
|
||||
k := rand.int63()
|
||||
|
||||
cond := k in m
|
||||
if !cond {
|
||||
@@ -253,9 +258,10 @@ set_delete_random_key_value :: proc(t: ^testing.T) {
|
||||
defer delete(m)
|
||||
|
||||
unique_keys := 0
|
||||
r := rand.create(t.seed + seed_incr)
|
||||
rand.reset(t.seed + seed_incr)
|
||||
|
||||
for _ in 0..<entries {
|
||||
k := rand.int63(&r)
|
||||
k := rand.int63()
|
||||
|
||||
if k not_in m {
|
||||
unique_keys += 1
|
||||
@@ -274,18 +280,19 @@ set_delete_random_key_value :: proc(t: ^testing.T) {
|
||||
half_entries := entries / 2
|
||||
|
||||
// Reset randomizer and delete half the entries
|
||||
r = rand.create(t.seed + seed_incr)
|
||||
rand.reset(t.seed + seed_incr)
|
||||
|
||||
for _ in 0..<half_entries {
|
||||
k := rand.int63(&r)
|
||||
k := rand.int63()
|
||||
delete_key(&m, k)
|
||||
}
|
||||
|
||||
// Reset randomizer and verify
|
||||
r = rand.create(t.seed + seed_incr)
|
||||
rand.reset(t.seed + seed_incr)
|
||||
|
||||
num_fails := 0
|
||||
for i in 0..<entries {
|
||||
k := rand.int63(&r)
|
||||
k := rand.int63()
|
||||
|
||||
if i < half_entries {
|
||||
if k in m {
|
||||
|
||||
Reference in New Issue
Block a user