mirror of
https://github.com/Ed94/Odin.git
synced 2026-06-15 10:22:23 -07:00
Merge remote-tracking branch 'offical/master'
# Conflicts: # src/check_builtin.cpp
This commit is contained in:
+1
-1
@@ -303,7 +303,7 @@ bin/
|
||||
# - Linux/MacOS
|
||||
odin
|
||||
!odin/
|
||||
odin.dSYM
|
||||
**/*.dSYM
|
||||
*.bin
|
||||
demo.bin
|
||||
libLLVM*.so*
|
||||
|
||||
@@ -73,6 +73,8 @@ expect :: proc(val, expected_val: T) -> T ---
|
||||
|
||||
// Linux and Darwin Only
|
||||
syscall :: proc(id: uintptr, args: ..uintptr) -> uintptr ---
|
||||
// FreeBSD, NetBSD, et cetera
|
||||
syscall_bsd :: proc(id: uintptr, args: ..uintptr) -> (uintptr, bool) ---
|
||||
|
||||
|
||||
// Atomics
|
||||
|
||||
+2
-1
@@ -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,
|
||||
@@ -73,7 +73,12 @@ default_random_generator_proc :: proc(data: rawptr, mode: Random_Generator_Mode,
|
||||
_ = read_u64(r)
|
||||
}
|
||||
|
||||
r := &global_rand_seed
|
||||
r: ^Default_Random_State = ---
|
||||
if data == nil {
|
||||
r = &global_rand_seed
|
||||
} else {
|
||||
r = cast(^Default_Random_State)data
|
||||
}
|
||||
|
||||
switch mode {
|
||||
case .Read:
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
@@ -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),
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
+10
-4
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -286,6 +286,20 @@ to_string :: proc(b: Builder) -> (res: string) {
|
||||
return string(b.buf[:])
|
||||
}
|
||||
/*
|
||||
Appends a trailing null byte after the end of the current Builder byte buffer and then casts it to a cstring
|
||||
|
||||
Inputs:
|
||||
- b: A pointer to builder
|
||||
|
||||
Returns:
|
||||
- res: A cstring of the Builder's buffer
|
||||
*/
|
||||
to_cstring :: proc(b: ^Builder) -> (res: cstring) {
|
||||
append(&b.buf, 0)
|
||||
pop(&b.buf)
|
||||
return cstring(raw_data(b.buf))
|
||||
}
|
||||
/*
|
||||
Returns the length of the Builder's buffer, in bytes
|
||||
|
||||
Inputs:
|
||||
|
||||
@@ -30,8 +30,8 @@ get_last_error :: proc "contextless" () -> int {
|
||||
}
|
||||
|
||||
_futex_wait :: proc "contextless" (futex: ^Futex, expected: u32) -> bool {
|
||||
if cast(int) intrinsics.syscall(unix.SYS___futex, uintptr(futex), FUTEX_WAIT_PRIVATE, uintptr(expected), 0, 0, 0) == -1 {
|
||||
switch get_last_error() {
|
||||
if error, ok := intrinsics.syscall_bsd(unix.SYS___futex, uintptr(futex), FUTEX_WAIT_PRIVATE, uintptr(expected), 0, 0, 0); !ok {
|
||||
switch error {
|
||||
case EINTR, EAGAIN:
|
||||
return true
|
||||
case:
|
||||
@@ -45,11 +45,11 @@ _futex_wait_with_timeout :: proc "contextless" (futex: ^Futex, expected: u32, du
|
||||
if duration <= 0 {
|
||||
return false
|
||||
}
|
||||
if cast(int) intrinsics.syscall(unix.SYS___futex, uintptr(futex), FUTEX_WAIT_PRIVATE, uintptr(expected), cast(uintptr) &Time_Spec{
|
||||
if error, ok := intrinsics.syscall_bsd(unix.SYS___futex, uintptr(futex), FUTEX_WAIT_PRIVATE, uintptr(expected), cast(uintptr) &Time_Spec{
|
||||
time_sec = cast(uint)(duration / 1e9),
|
||||
time_nsec = cast(uint)(duration % 1e9),
|
||||
}, 0, 0) == -1 {
|
||||
switch get_last_error() {
|
||||
}, 0, 0); !ok {
|
||||
switch error {
|
||||
case EINTR, EAGAIN:
|
||||
return true
|
||||
case ETIMEDOUT:
|
||||
@@ -62,13 +62,13 @@ _futex_wait_with_timeout :: proc "contextless" (futex: ^Futex, expected: u32, du
|
||||
}
|
||||
|
||||
_futex_signal :: proc "contextless" (futex: ^Futex) {
|
||||
if cast(int) intrinsics.syscall(unix.SYS___futex, uintptr(futex), FUTEX_WAKE_PRIVATE, 1, 0, 0, 0) == -1 {
|
||||
if _, ok := intrinsics.syscall_bsd(unix.SYS___futex, uintptr(futex), FUTEX_WAKE_PRIVATE, 1, 0, 0, 0); !ok {
|
||||
_panic("futex_wake_single failure")
|
||||
}
|
||||
}
|
||||
|
||||
_futex_broadcast :: proc "contextless" (futex: ^Futex) {
|
||||
if cast(int) intrinsics.syscall(unix.SYS___futex, uintptr(futex), FUTEX_WAKE_PRIVATE, uintptr(max(i32)), 0, 0, 0) == -1 {
|
||||
if _, ok := intrinsics.syscall_bsd(unix.SYS___futex, uintptr(futex), FUTEX_WAKE_PRIVATE, uintptr(max(i32)), 0, 0, 0); !ok {
|
||||
_panic("_futex_wake_all failure")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -712,3 +712,7 @@ Window_setDelegate :: proc "c" (self: ^Window, delegate: ^WindowDelegate) {
|
||||
Window_backingScaleFactor :: proc "c" (self: ^Window) -> Float {
|
||||
return msgSend(Float, self, "backingScaleFactor")
|
||||
}
|
||||
@(objc_type=Window, objc_name="setWantsLayer")
|
||||
Window_setWantsLayer :: proc "c" (self: ^Window, ok: BOOL) {
|
||||
msgSend(nil, self, "setWantsLayer:", ok)
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ version_string_buf: [1024]u8
|
||||
init_os_version :: proc () {
|
||||
os_version.platform = .FreeBSD
|
||||
|
||||
kernel_version_buf: [129]u8
|
||||
kernel_version_buf: [1024]u8
|
||||
|
||||
b := strings.builder_from_bytes(version_string_buf[:])
|
||||
// Retrieve kernel info using `sysctl`, e.g. FreeBSD 13.1-RELEASE-p2 GENERIC
|
||||
|
||||
@@ -5,14 +5,15 @@ import "base:intrinsics"
|
||||
|
||||
sysctl :: proc(mib: []i32, val: ^$T) -> (ok: bool) {
|
||||
mib := mib
|
||||
result_size := i64(size_of(T))
|
||||
result_size := u64(size_of(T))
|
||||
|
||||
res := intrinsics.syscall(SYS_sysctl,
|
||||
res: uintptr
|
||||
res, ok = intrinsics.syscall_bsd(SYS_sysctl,
|
||||
uintptr(raw_data(mib)), uintptr(len(mib)),
|
||||
uintptr(val), uintptr(&result_size),
|
||||
uintptr(0), uintptr(0),
|
||||
)
|
||||
return res == 0
|
||||
return
|
||||
}
|
||||
|
||||
// See /usr/include/sys/sysctl.h for details
|
||||
|
||||
@@ -38,16 +38,30 @@ foreign kernel32 {
|
||||
lpNumberOfCharsWritten: LPDWORD,
|
||||
lpReserved: LPVOID) -> BOOL ---
|
||||
|
||||
PeekConsoleInputW :: proc(hConsoleInput: HANDLE,
|
||||
lpBuffer: ^INPUT_RECORD,
|
||||
nLength: DWORD,
|
||||
lpNumberOfEventsRead: LPDWORD) -> BOOL ---
|
||||
|
||||
ReadConsoleInputW :: proc(hConsoleInput: HANDLE,
|
||||
lpBuffer: ^INPUT_RECORD,
|
||||
nLength: DWORD,
|
||||
lpNumberOfEventsRead: LPDWORD) -> BOOL ---
|
||||
|
||||
GetConsoleMode :: proc(hConsoleHandle: HANDLE,
|
||||
lpMode: LPDWORD) -> BOOL ---
|
||||
SetConsoleMode :: proc(hConsoleHandle: HANDLE,
|
||||
dwMode: DWORD) -> BOOL ---
|
||||
SetConsoleCursorPosition :: proc(hConsoleHandle: HANDLE,
|
||||
dwCursorPosition: COORD) -> BOOL ---
|
||||
dwCursorPosition: COORD) -> BOOL ---
|
||||
SetConsoleTextAttribute :: proc(hConsoleOutput: HANDLE,
|
||||
wAttributes: WORD) -> BOOL ---
|
||||
wAttributes: WORD) -> BOOL ---
|
||||
GetConsoleCP :: proc() -> UINT ---
|
||||
SetConsoleCP :: proc(wCodePageID: UINT) -> BOOL ---
|
||||
GetConsoleOutputCP :: proc() -> UINT ---
|
||||
SetConsoleOutputCP :: proc(wCodePageID: UINT) -> BOOL ---
|
||||
|
||||
FlushConsoleInputBuffer :: proc(hConsoleInput: HANDLE) -> BOOL ---
|
||||
|
||||
GetFileInformationByHandle :: proc(hFile: HANDLE, lpFileInformation: LPBY_HANDLE_FILE_INFORMATION) -> BOOL ---
|
||||
SetHandleInformation :: proc(hObject: HANDLE,
|
||||
dwMask: DWORD,
|
||||
@@ -85,6 +99,12 @@ foreign kernel32 {
|
||||
RemoveDirectoryW :: proc(lpPathName: LPCWSTR) -> BOOL ---
|
||||
SetFileAttributesW :: proc(lpFileName: LPCWSTR, dwFileAttributes: DWORD) -> BOOL ---
|
||||
SetLastError :: proc(dwErrCode: DWORD) ---
|
||||
GetCommTimeouts :: proc(handle: HANDLE, timeouts: ^COMMTIMEOUTS) -> BOOL ---
|
||||
SetCommTimeouts :: proc(handle: HANDLE, timeouts: ^COMMTIMEOUTS) -> BOOL ---
|
||||
ClearCommError :: proc(hFile: HANDLE, lpErrors: ^Com_Error, lpStat: ^COMSTAT) -> BOOL ---
|
||||
GetCommState :: proc(handle: HANDLE, dcb: ^DCB) -> BOOL ---
|
||||
SetCommState :: proc(handle: HANDLE, dcb: ^DCB) -> BOOL ---
|
||||
GetCommPorts :: proc(lpPortNumbers: PULONG, uPortNumbersCount: ULONG, puPortNumbersFound: PULONG) -> ULONG ---
|
||||
GetCommandLineW :: proc() -> LPCWSTR ---
|
||||
GetTempPathW :: proc(nBufferLength: DWORD, lpBuffer: LPCWSTR) -> DWORD ---
|
||||
GetCurrentProcess :: proc() -> HANDLE ---
|
||||
@@ -413,6 +433,7 @@ foreign kernel32 {
|
||||
LoadLibraryW :: proc(c_str: LPCWSTR) -> HMODULE ---
|
||||
FreeLibrary :: proc(h: HMODULE) -> BOOL ---
|
||||
GetProcAddress :: proc(h: HMODULE, c_str: LPCSTR) -> rawptr ---
|
||||
LoadLibraryExW :: proc(c_str: LPCWSTR, file: HANDLE, flags: LoadLibraryEx_Flags) -> HMODULE ---
|
||||
|
||||
|
||||
GetFullPathNameW :: proc(filename: LPCWSTR, buffer_length: DWORD, buffer: LPCWSTR, file_part: ^LPCWSTR) -> DWORD ---
|
||||
@@ -1015,82 +1036,6 @@ 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.
|
||||
|
||||
DTR_Control :: enum byte {
|
||||
Disable = 0,
|
||||
Enable = 1,
|
||||
Handshake = 2,
|
||||
}
|
||||
RTS_Control :: enum byte {
|
||||
Disable = 0,
|
||||
Enable = 1,
|
||||
Handshake = 2,
|
||||
Toggle = 3,
|
||||
}
|
||||
Parity :: enum byte {
|
||||
None = 0,
|
||||
Odd = 1,
|
||||
Even = 2,
|
||||
Mark = 3,
|
||||
Space = 4,
|
||||
}
|
||||
Stop_Bits :: enum byte {
|
||||
One = 0,
|
||||
One_And_A_Half = 1,
|
||||
Two = 2,
|
||||
}
|
||||
|
||||
DCB :: struct {
|
||||
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,
|
||||
}
|
||||
|
||||
@(default_calling_convention="system")
|
||||
foreign kernel32 {
|
||||
GetCommState :: proc(handle: HANDLE, dcb: ^DCB) -> BOOL ---
|
||||
SetCommState :: proc(handle: HANDLE, dcb: ^DCB) -> BOOL ---
|
||||
}
|
||||
|
||||
COMMTIMEOUTS :: struct {
|
||||
ReadIntervalTimeout: DWORD,
|
||||
ReadTotalTimeoutMultiplier: DWORD,
|
||||
ReadTotalTimeoutConstant: DWORD,
|
||||
WriteTotalTimeoutMultiplier: DWORD,
|
||||
WriteTotalTimeoutConstant: DWORD,
|
||||
}
|
||||
|
||||
@(default_calling_convention="system")
|
||||
foreign kernel32 {
|
||||
GetCommTimeouts :: proc(handle: HANDLE, timeouts: ^COMMTIMEOUTS) -> BOOL ---
|
||||
SetCommTimeouts :: proc(handle: HANDLE, timeouts: ^COMMTIMEOUTS) -> BOOL ---
|
||||
}
|
||||
|
||||
LPFIBER_START_ROUTINE :: #type proc "system" (lpFiberParameter: LPVOID)
|
||||
|
||||
@(default_calling_convention = "system")
|
||||
@@ -1167,9 +1112,9 @@ Battery_Flag :: enum BYTE {
|
||||
Low = 1,
|
||||
Critical = 2,
|
||||
Charging = 3,
|
||||
No_Battery = 7,
|
||||
No_Battery = 7,
|
||||
}
|
||||
Battery_Flags :: bit_set[Battery_Flag; BYTE]
|
||||
Battery_Flags :: bit_set[Battery_Flag; BYTE]
|
||||
|
||||
/* Global Memory Flags */
|
||||
GMEM_FIXED :: 0x0000
|
||||
|
||||
@@ -2674,6 +2674,22 @@ OSVERSIONINFOEXW :: struct {
|
||||
wReserved: UCHAR,
|
||||
}
|
||||
|
||||
LoadLibraryEx_Flag :: enum DWORD {
|
||||
LOAD_LIBRARY_AS_DATAFILE = 1, // 1 << 1: 0x0002,
|
||||
LOAD_WITH_ALTERED_SEARCH_PATH = 3, // 1 << 3: 0x0008,
|
||||
LOAD_IGNORE_CODE_AUTHZ_LEVEL = 4, // 1 << 4: 0x0010,
|
||||
LOAD_LIBRARY_AS_IMAGE_RESOURCE = 5, // 1 << 5: 0x0020,
|
||||
LOAD_LIBRARY_AS_DATAFILE_EXCLUSIVE = 6, // 1 << 6: 0x0040,
|
||||
LOAD_LIBRARY_REQUIRE_SIGNED_TARGET = 7, // 1 << 7: 0x0080,
|
||||
LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR = 8, // 1 << 8: 0x0100,
|
||||
LOAD_LIBRARY_SEARCH_APPLICATION_DIR = 9, // 1 << 9: 0x0200,
|
||||
LOAD_LIBRARY_SEARCH_USER_DIRS = 10, // 1 << 10: 0x0400,
|
||||
LOAD_LIBRARY_SEARCH_SYSTEM32 = 11, // 1 << 11: 0x0800,
|
||||
LOAD_LIBRARY_SEARCH_DEFAULT_DIRS = 12, // 1 << 12: 0x1000,
|
||||
LOAD_LIBRARY_SAFE_CURRENT_DIRS = 13, // 1 << 13: 0x2000,
|
||||
}
|
||||
LoadLibraryEx_Flags :: distinct bit_set[LoadLibraryEx_Flag]
|
||||
|
||||
// https://docs.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-quota_limits
|
||||
// Used in LogonUserExW
|
||||
PQUOTA_LIMITS :: struct {
|
||||
@@ -3983,6 +3999,70 @@ CONSOLE_CURSOR_INFO :: struct {
|
||||
PCONSOLE_SCREEN_BUFFER_INFO :: ^CONSOLE_SCREEN_BUFFER_INFO
|
||||
PCONSOLE_CURSOR_INFO :: ^CONSOLE_CURSOR_INFO
|
||||
|
||||
Event_Type :: enum WORD {
|
||||
KEY_EVENT = 0x0001,
|
||||
MOUSE_EVENT = 0x0002,
|
||||
WINDOW_BUFFER_SIZE_EVENT = 0x0004,
|
||||
MENU_EVENT = 0x0008,
|
||||
FOCUS_EVENT = 0x0010,
|
||||
}
|
||||
|
||||
INPUT_RECORD :: struct {
|
||||
EventType: Event_Type,
|
||||
Event: struct #raw_union {
|
||||
KeyEvent: KEY_EVENT_RECORD,
|
||||
MouseEvent: MOUSE_EVENT_RECORD,
|
||||
WindowBufferSizeEvent: WINDOW_BUFFER_SIZE_RECORD,
|
||||
MenuEvent: MENU_EVENT_RECORD,
|
||||
FocusEvent: FOCUS_EVENT_RECORD,
|
||||
},
|
||||
}
|
||||
|
||||
Control_Key_State_Bits :: enum {
|
||||
RIGHT_ALT_PRESSED,
|
||||
LEFT_ALT_PRESSED,
|
||||
RIGHT_CTRL_PRESSED,
|
||||
LEFT_CTRL_PRESSED,
|
||||
SHIFT_PRESSED,
|
||||
NUMLOCK_ON,
|
||||
SCROLLLOCK_ON,
|
||||
CAPSLOCK_ON,
|
||||
ENHANCED_KEY,
|
||||
}
|
||||
Control_Key_State :: bit_set[Control_Key_State_Bits; DWORD]
|
||||
|
||||
KEY_EVENT_RECORD :: struct {
|
||||
bKeyDown: BOOL,
|
||||
wRepeatCount: WORD,
|
||||
wVirtualKeyCode: WORD,
|
||||
wVirtualScanCode: WORD,
|
||||
uChar: struct #raw_union {
|
||||
UnicodeChar: WCHAR,
|
||||
AsciiChar: CHAR,
|
||||
},
|
||||
dwControlKeyState: Control_Key_State,
|
||||
}
|
||||
|
||||
MOUSE_EVENT_RECORD :: struct {
|
||||
dwMousePosition: COORD,
|
||||
dwButtonState: DWORD,
|
||||
dwControlKeyState: DWORD,
|
||||
dwEventFlags: DWORD,
|
||||
}
|
||||
|
||||
WINDOW_BUFFER_SIZE_RECORD :: struct {
|
||||
dwSize: COORD,
|
||||
}
|
||||
|
||||
MENU_EVENT_RECORD :: struct {
|
||||
dwCommandId: UINT,
|
||||
}
|
||||
|
||||
FOCUS_EVENT_RECORD :: struct {
|
||||
bSetFocus: BOOL,
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Networking
|
||||
//
|
||||
@@ -4217,3 +4297,92 @@ SOCKADDR :: struct {
|
||||
sa_family: ADDRESS_FAMILY,
|
||||
sa_data: [14]CHAR,
|
||||
}
|
||||
|
||||
DTR_Control :: enum byte {
|
||||
Disable = 0,
|
||||
Enable = 1,
|
||||
Handshake = 2,
|
||||
}
|
||||
RTS_Control :: enum byte {
|
||||
Disable = 0,
|
||||
Enable = 1,
|
||||
Handshake = 2,
|
||||
Toggle = 3,
|
||||
}
|
||||
Parity :: enum byte {
|
||||
None = 0,
|
||||
Odd = 1,
|
||||
Even = 2,
|
||||
Mark = 3,
|
||||
Space = 4,
|
||||
}
|
||||
Stop_Bits :: enum byte {
|
||||
One = 0,
|
||||
One_And_A_Half = 1,
|
||||
Two = 2,
|
||||
}
|
||||
|
||||
DCB :: struct {
|
||||
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,
|
||||
}
|
||||
|
||||
COMMTIMEOUTS :: struct {
|
||||
ReadIntervalTimeout: DWORD,
|
||||
ReadTotalTimeoutMultiplier: DWORD,
|
||||
ReadTotalTimeoutConstant: DWORD,
|
||||
WriteTotalTimeoutMultiplier: DWORD,
|
||||
WriteTotalTimeoutConstant: DWORD,
|
||||
}
|
||||
|
||||
Com_Stat_Bits :: enum {
|
||||
fCtsHold,
|
||||
fDsrHold,
|
||||
fRlsdHold,
|
||||
fXoffHold,
|
||||
fXoffSent,
|
||||
fEof,
|
||||
fTxim,
|
||||
}
|
||||
COMSTAT :: struct {
|
||||
bits: bit_set[Com_Stat_Bits; DWORD],
|
||||
cbInQue: DWORD,
|
||||
cbOutQue: DWORD,
|
||||
}
|
||||
|
||||
Com_Error_Bits :: enum {
|
||||
RXOVER,
|
||||
OVERRUN,
|
||||
RXPARITY,
|
||||
FRAME,
|
||||
BREAK,
|
||||
}
|
||||
Com_Error :: bit_set[Com_Error_Bits; DWORD]
|
||||
|
||||
|
||||
@@ -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"
|
||||
@@ -106,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)
|
||||
|
||||
+40
-38
@@ -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
|
||||
|
||||
+118
-70
@@ -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.
|
||||
*/
|
||||
|
||||
+301
-4
@@ -5,6 +5,12 @@ REPLACEMENT_CHAR :: '\ufffd' // Represented an invalid code point
|
||||
MAX_ASCII :: '\u007f' // Maximum ASCII value
|
||||
MAX_LATIN1 :: '\u00ff' // Maximum Latin-1 value
|
||||
|
||||
ZERO_WIDTH_SPACE :: '\u200B'
|
||||
ZERO_WIDTH_NON_JOINER :: '\u200C'
|
||||
ZERO_WIDTH_JOINER :: '\u200D'
|
||||
WORD_JOINER :: '\u2060'
|
||||
|
||||
@(require_results)
|
||||
binary_search :: proc(c: i32, table: []i32, length, stride: int) -> int {
|
||||
n := length
|
||||
t := 0
|
||||
@@ -24,6 +30,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 +43,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 +56,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 +67,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 +84,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 +102,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 +123,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 +138,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 +155,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 +176,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 +184,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 +192,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 +200,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 +208,285 @@ 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)
|
||||
}
|
||||
|
||||
// Return values:
|
||||
//
|
||||
// - 2 if East_Asian_Width=F or W, or
|
||||
// - 0 if non-printable / zero-width, or
|
||||
// - 1 in all other cases.
|
||||
//
|
||||
@(require_results)
|
||||
normalized_east_asian_width :: proc(r: rune) -> int {
|
||||
// This is a different interpretation of the BOM which occurs in the middle of text.
|
||||
ZERO_WIDTH_NO_BREAK_SPACE :: '\uFEFF'
|
||||
|
||||
if is_control(r) {
|
||||
return 0
|
||||
} else if r <= 0x10FF {
|
||||
// Easy early out for low runes.
|
||||
return 1
|
||||
}
|
||||
|
||||
switch r {
|
||||
case ZERO_WIDTH_NO_BREAK_SPACE,
|
||||
ZERO_WIDTH_SPACE,
|
||||
ZERO_WIDTH_NON_JOINER,
|
||||
ZERO_WIDTH_JOINER,
|
||||
WORD_JOINER:
|
||||
return 0
|
||||
}
|
||||
|
||||
c := i32(r)
|
||||
p := binary_search(c, normalized_east_asian_width_ranges[:], len(normalized_east_asian_width_ranges)/3, 3)
|
||||
if p >= 0 && normalized_east_asian_width_ranges[p] <= c && c <= normalized_east_asian_width_ranges[p+1] {
|
||||
return cast(int)normalized_east_asian_width_ranges[p+2]
|
||||
}
|
||||
return 1
|
||||
}
|
||||
|
||||
//
|
||||
// End of Unicode 15.1.0 block.
|
||||
//
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,420 @@
|
||||
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
|
||||
normalized_east_asian_width :: unicode.normalized_east_asian_width
|
||||
|
||||
|
||||
Grapheme :: struct {
|
||||
byte_index: int,
|
||||
rune_index: int,
|
||||
width: 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.
|
||||
- width: The width of the string in number of monospace cells.
|
||||
*/
|
||||
@(require_results)
|
||||
grapheme_count :: proc(str: string) -> (graphemes, runes, width: int) {
|
||||
_, graphemes, runes, width = 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.
|
||||
- width: The width of the string in number of monospace cells.
|
||||
*/
|
||||
@(require_results)
|
||||
decode_grapheme_clusters :: proc(
|
||||
str: string,
|
||||
track_graphemes := true,
|
||||
allocator := context.allocator,
|
||||
) -> (
|
||||
graphemes: [dynamic]Grapheme,
|
||||
grapheme_count: int,
|
||||
rune_count: int,
|
||||
width: 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
|
||||
|
||||
// Additionally, this procedure now takes into account Standard Annex #11,
|
||||
// in order to estimate how visually wide the string will appear on a
|
||||
// monospaced display. This can only ever be a rough guess, as this tends
|
||||
// to be an implementation detail relating to which fonts are being used,
|
||||
// how codepoints are interpreted and drawn, if codepoint sequences are
|
||||
// interpreted correctly, and et cetera.
|
||||
//
|
||||
// For example, a program may not properly interpret an emoji modifier
|
||||
// sequence and print the component glyphs instead of one whole glyph.
|
||||
//
|
||||
// See here for more information: https://www.unicode.org/reports/tr11/
|
||||
//
|
||||
// NOTE: There is no explicit mention of what to do with zero-width spaces
|
||||
// as far as grapheme cluster segmentation goes, therefore this
|
||||
// implementation may count and return graphemes with a `width` of zero.
|
||||
//
|
||||
// Treat them as any other space.
|
||||
|
||||
Grapheme_Cluster_Sequence :: enum {
|
||||
None,
|
||||
Indic,
|
||||
Emoji,
|
||||
Regional,
|
||||
}
|
||||
|
||||
context.allocator = allocator
|
||||
|
||||
last_rune: rune
|
||||
last_rune_breaks_forward: bool
|
||||
|
||||
last_width: int
|
||||
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 grapheme_count > last_grapheme_count {
|
||||
width += normalized_east_asian_width(this_rune)
|
||||
if track_graphemes {
|
||||
append(&graphemes, Grapheme{
|
||||
byte_index,
|
||||
rune_count,
|
||||
width - last_width,
|
||||
})
|
||||
}
|
||||
last_grapheme_count = grapheme_count
|
||||
last_width = width
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
package all
|
||||
|
||||
import wgpu "vendor:wgpu"
|
||||
_ :: wgpu
|
||||
@@ -844,6 +844,8 @@ struct BuildContext {
|
||||
bool show_unused;
|
||||
bool show_unused_with_location;
|
||||
bool show_more_timings;
|
||||
bool show_defineables;
|
||||
String export_defineables_file;
|
||||
bool show_system_calls;
|
||||
bool keep_temp_files;
|
||||
bool ignore_unknown_attributes;
|
||||
|
||||
+81
-9
@@ -1756,9 +1756,21 @@ gb_internal bool check_builtin_procedure_directive(CheckerContext *c, Operand *o
|
||||
operand->mode = Addressing_Constant;
|
||||
operand->value = exact_value_bool(is_defined);
|
||||
|
||||
// If the arg is a selector expression we don't add it, `-define` only allows identifiers.
|
||||
if (arg->kind == Ast_Ident) {
|
||||
Defineable defineable = {};
|
||||
defineable.docs = nullptr;
|
||||
defineable.name = arg->Ident.token.string;
|
||||
defineable.default_value = exact_value_bool(false);
|
||||
defineable.pos = arg->Ident.token.pos;
|
||||
|
||||
MUTEX_GUARD(&c->info->defineables_mutex);
|
||||
array_add(&c->info->defineables, defineable);
|
||||
}
|
||||
|
||||
} else if (name == "config") {
|
||||
if (ce->args.count != 2) {
|
||||
error(call, "'#config' expects 2 argument, got %td", ce->args.count);
|
||||
error(call, "'#config' expects 2 arguments, got %td", ce->args.count);
|
||||
return false;
|
||||
}
|
||||
Ast *arg = unparen_expr(ce->args[0]);
|
||||
@@ -1793,8 +1805,22 @@ gb_internal bool check_builtin_procedure_directive(CheckerContext *c, Operand *o
|
||||
operand->value = found->Constant.value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Defineable defineable = {};
|
||||
defineable.docs = nullptr;
|
||||
defineable.name = name;
|
||||
defineable.default_value = def.value;
|
||||
defineable.pos = arg->Ident.token.pos;
|
||||
|
||||
if (c->decl) {
|
||||
defineable.docs = c->decl->docs;
|
||||
}
|
||||
|
||||
MUTEX_GUARD(&c->info->defineables_mutex);
|
||||
array_add(&c->info->defineables, defineable);
|
||||
|
||||
}
|
||||
|
||||
// Bodging in #region & #endregion support
|
||||
else if (name == "region") {
|
||||
operand->type = t_untyped_bool;
|
||||
@@ -1806,7 +1832,7 @@ gb_internal bool check_builtin_procedure_directive(CheckerContext *c, Operand *o
|
||||
operand->mode = Addressing_Constant;
|
||||
operand->value = exact_value_bool(true);
|
||||
}
|
||||
|
||||
|
||||
else {
|
||||
error(call, "Unknown directive call: #%.*s", LIT(name));
|
||||
}
|
||||
@@ -5103,15 +5129,9 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As
|
||||
isize max_arg_count = 32;
|
||||
|
||||
switch (build_context.metrics.os) {
|
||||
case TargetOs_windows:
|
||||
case TargetOs_freestanding:
|
||||
error(call, "'%.*s' is not supported on this platform (%.*s)", LIT(builtin_name), LIT(target_os_names[build_context.metrics.os]));
|
||||
break;
|
||||
case TargetOs_darwin:
|
||||
case TargetOs_linux:
|
||||
case TargetOs_essence:
|
||||
case TargetOs_freebsd:
|
||||
case TargetOs_openbsd:
|
||||
case TargetOs_haiku:
|
||||
switch (build_context.metrics.arch) {
|
||||
case TargetArch_i386:
|
||||
@@ -5121,6 +5141,9 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
error(call, "'%.*s' is not supported on this platform (%.*s)", LIT(builtin_name), LIT(target_os_names[build_context.metrics.os]));
|
||||
break;
|
||||
}
|
||||
|
||||
if (ce->args.count > max_arg_count) {
|
||||
@@ -5134,6 +5157,55 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
case BuiltinProc_syscall_bsd:
|
||||
{
|
||||
convert_to_typed(c, operand, t_uintptr);
|
||||
if (!is_type_uintptr(operand->type)) {
|
||||
gbString t = type_to_string(operand->type);
|
||||
error(operand->expr, "Argument 0 must be of type 'uintptr', got %s", t);
|
||||
gb_string_free(t);
|
||||
}
|
||||
for (isize i = 1; i < ce->args.count; i++) {
|
||||
Operand x = {};
|
||||
check_expr(c, &x, ce->args[i]);
|
||||
if (x.mode != Addressing_Invalid) {
|
||||
convert_to_typed(c, &x, t_uintptr);
|
||||
}
|
||||
convert_to_typed(c, &x, t_uintptr);
|
||||
if (!is_type_uintptr(x.type)) {
|
||||
gbString t = type_to_string(x.type);
|
||||
error(x.expr, "Argument %td must be of type 'uintptr', got %s", i, t);
|
||||
gb_string_free(t);
|
||||
}
|
||||
}
|
||||
|
||||
isize max_arg_count = 32;
|
||||
|
||||
switch (build_context.metrics.os) {
|
||||
case TargetOs_freebsd:
|
||||
case TargetOs_netbsd:
|
||||
case TargetOs_openbsd:
|
||||
switch (build_context.metrics.arch) {
|
||||
case TargetArch_amd64:
|
||||
case TargetArch_arm64:
|
||||
max_arg_count = 7;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
error(call, "'%.*s' is not supported on this platform (%.*s)", LIT(builtin_name), LIT(target_os_names[build_context.metrics.os]));
|
||||
break;
|
||||
}
|
||||
|
||||
if (ce->args.count > max_arg_count) {
|
||||
error(ast_end_token(call), "'%.*s' has a maximum of %td arguments on this platform (%.*s), got %td", LIT(builtin_name), max_arg_count, LIT(target_os_names[build_context.metrics.os]), ce->args.count);
|
||||
}
|
||||
|
||||
operand->mode = Addressing_Value;
|
||||
operand->type = make_optional_ok_type(t_uintptr);
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
case BuiltinProc_type_base_type:
|
||||
|
||||
+1
-1
@@ -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) {
|
||||
|
||||
+16
-1
@@ -2606,6 +2606,11 @@ gb_internal void check_unary_expr(CheckerContext *c, Operand *o, Token op, Ast *
|
||||
if (o->mode == Addressing_Constant) {
|
||||
Type *type = base_type(o->type);
|
||||
if (!is_type_constant_type(o->type)) {
|
||||
if (is_type_array_like(o->type)) {
|
||||
o->mode = Addressing_Value;
|
||||
return;
|
||||
}
|
||||
|
||||
gbString xt = type_to_string(o->type);
|
||||
gbString err_str = expr_to_string(node);
|
||||
error(op, "Invalid type, '%s', for constant unary expression '%s'", xt, err_str);
|
||||
@@ -8324,6 +8329,14 @@ gb_internal ExprKind check_basic_directive_expr(CheckerContext *c, Operand *o, A
|
||||
}
|
||||
o->type = t_untyped_string;
|
||||
o->value = exact_value_string(file);
|
||||
} else if (name == "directory") {
|
||||
String file = get_file_path_string(bd->token.pos.file_id);
|
||||
String path = dir_from_path(file);
|
||||
if (build_context.obfuscate_source_code_locations) {
|
||||
path = obfuscate_string(path, "D");
|
||||
}
|
||||
o->type = t_untyped_string;
|
||||
o->value = exact_value_string(path);
|
||||
} else if (name == "line") {
|
||||
i32 line = bd->token.pos.line;
|
||||
if (build_context.obfuscate_source_code_locations) {
|
||||
@@ -9821,7 +9834,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);
|
||||
|
||||
@@ -1302,6 +1302,7 @@ gb_internal void init_checker_info(CheckerInfo *i) {
|
||||
array_init(&i->init_procedures, a, 0, 0);
|
||||
array_init(&i->fini_procedures, a, 0, 0);
|
||||
array_init(&i->required_foreign_imports_through_force, a, 0, 0);
|
||||
array_init(&i->defineables, a);
|
||||
|
||||
map_init(&i->objc_msgSend_types);
|
||||
string_map_init(&i->load_file_cache);
|
||||
@@ -1331,6 +1332,7 @@ gb_internal void destroy_checker_info(CheckerInfo *i) {
|
||||
string_map_destroy(&i->packages);
|
||||
array_free(&i->variable_init_order);
|
||||
array_free(&i->required_foreign_imports_through_force);
|
||||
array_free(&i->defineables);
|
||||
|
||||
mpsc_destroy(&i->entity_queue);
|
||||
mpsc_destroy(&i->definition_queue);
|
||||
@@ -4110,6 +4112,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 +4157,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 +4180,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) {
|
||||
|
||||
@@ -382,6 +382,17 @@ struct GenTypesData {
|
||||
RecursiveMutex mutex;
|
||||
};
|
||||
|
||||
struct Defineable {
|
||||
String name;
|
||||
ExactValue default_value;
|
||||
TokenPos pos;
|
||||
CommentGroup *docs;
|
||||
|
||||
// These strings are only computed from previous fields when defineables are being shown or exported.
|
||||
String default_value_str;
|
||||
String pos_str;
|
||||
};
|
||||
|
||||
// CheckerInfo stores all the symbol information for a type-checked program
|
||||
struct CheckerInfo {
|
||||
Checker *checker;
|
||||
@@ -408,6 +419,9 @@ struct CheckerInfo {
|
||||
Array<Entity *> entities;
|
||||
Array<Entity *> required_foreign_imports_through_force;
|
||||
|
||||
BlockingMutex defineables_mutex;
|
||||
Array<Defineable> defineables;
|
||||
|
||||
|
||||
// Below are accessed within procedures
|
||||
RwMutex global_untyped_mutex;
|
||||
|
||||
@@ -192,6 +192,7 @@ BuiltinProc__simd_end,
|
||||
|
||||
// Platform specific intrinsics
|
||||
BuiltinProc_syscall,
|
||||
BuiltinProc_syscall_bsd,
|
||||
|
||||
BuiltinProc_x86_cpuid,
|
||||
BuiltinProc_x86_xgetbv,
|
||||
@@ -512,7 +513,8 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = {
|
||||
{STR_LIT(""), 0, false, Expr_Stmt, BuiltinProcPkg_intrinsics},
|
||||
|
||||
|
||||
{STR_LIT("syscall"), 1, true, Expr_Expr, BuiltinProcPkg_intrinsics, false, true},
|
||||
{STR_LIT("syscall"), 1, true, Expr_Expr, BuiltinProcPkg_intrinsics, false, true},
|
||||
{STR_LIT("syscall_bsd"), 1, true, Expr_Expr, BuiltinProcPkg_intrinsics, false, true},
|
||||
{STR_LIT("x86_cpuid"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics},
|
||||
{STR_LIT("x86_xgetbv"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics},
|
||||
|
||||
|
||||
+6
-2
@@ -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)
|
||||
|
||||
+134
-44
@@ -2747,26 +2747,10 @@ gb_internal lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValu
|
||||
{
|
||||
GB_ASSERT(arg_count <= 7);
|
||||
|
||||
// FreeBSD additionally clobbers r8, r9, r10, but they
|
||||
// can also be used to pass in arguments, so this needs
|
||||
// to be handled in two parts.
|
||||
bool clobber_arg_regs[7] = {
|
||||
false, false, false, false, false, false, false
|
||||
};
|
||||
if (build_context.metrics.os == TargetOs_freebsd) {
|
||||
clobber_arg_regs[4] = true; // r10
|
||||
clobber_arg_regs[5] = true; // r8
|
||||
clobber_arg_regs[6] = true; // r9
|
||||
}
|
||||
|
||||
char asm_string[] = "syscall";
|
||||
gbString constraints = gb_string_make(heap_allocator(), "={rax}");
|
||||
for (unsigned i = 0; i < arg_count; i++) {
|
||||
if (!clobber_arg_regs[i]) {
|
||||
constraints = gb_string_appendc(constraints, ",{");
|
||||
} else {
|
||||
constraints = gb_string_appendc(constraints, ",+{");
|
||||
}
|
||||
constraints = gb_string_appendc(constraints, ",{");
|
||||
static char const *regs[] = {
|
||||
"rax",
|
||||
"rdi",
|
||||
@@ -2790,36 +2774,9 @@ gb_internal lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValu
|
||||
// Some but not all system calls will additionally
|
||||
// clobber memory.
|
||||
//
|
||||
// As a fix for CVE-2019-5595, FreeBSD started
|
||||
// clobbering R8, R9, and R10, instead of restoring
|
||||
// them. Additionally unlike Linux, instead of
|
||||
// returning negative errno, positive errno is
|
||||
// returned and CF is set.
|
||||
//
|
||||
// TODO:
|
||||
// * Figure out what Darwin does.
|
||||
// * Add some extra handling to propagate CF back
|
||||
// up to the caller on FreeBSD systems so that
|
||||
// the caller knows that the return value is
|
||||
// positive errno.
|
||||
constraints = gb_string_appendc(constraints, ",~{rcx},~{r11},~{memory}");
|
||||
if (build_context.metrics.os == TargetOs_freebsd) {
|
||||
// Second half of dealing with FreeBSD's system
|
||||
// call semantics. Explicitly clobber the registers
|
||||
// that were not used to pass in arguments, and
|
||||
// then clobber RFLAGS.
|
||||
if (arg_count < 5) {
|
||||
constraints = gb_string_appendc(constraints, ",~{r10}");
|
||||
}
|
||||
if (arg_count < 6) {
|
||||
constraints = gb_string_appendc(constraints, ",~{r8}");
|
||||
}
|
||||
if (arg_count < 7) {
|
||||
constraints = gb_string_appendc(constraints, ",~{r9}");
|
||||
}
|
||||
constraints = gb_string_appendc(constraints, ",~{cc}");
|
||||
}
|
||||
|
||||
inline_asm = llvm_get_inline_asm(func_type, make_string_c(asm_string), make_string_c(constraints));
|
||||
}
|
||||
break;
|
||||
@@ -2927,6 +2884,139 @@ gb_internal lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValu
|
||||
res.type = t_uintptr;
|
||||
return res;
|
||||
}
|
||||
case BuiltinProc_syscall_bsd:
|
||||
{
|
||||
// This is a BSD-style syscall where errors are indicated by a high
|
||||
// Carry Flag and a positive return value, allowing the kernel to
|
||||
// return any value that fits into a machine word.
|
||||
//
|
||||
// This is unlike Linux, where errors are indicated by a negative
|
||||
// return value, limiting what can be expressed in one result.
|
||||
unsigned arg_count = cast(unsigned)ce->args.count;
|
||||
LLVMValueRef *args = gb_alloc_array(permanent_allocator(), LLVMValueRef, arg_count);
|
||||
for_array(i, ce->args) {
|
||||
lbValue arg = lb_build_expr(p, ce->args[i]);
|
||||
arg = lb_emit_conv(p, arg, t_uintptr);
|
||||
args[i] = arg.value;
|
||||
}
|
||||
|
||||
LLVMTypeRef llvm_uintptr = lb_type(p->module, t_uintptr);
|
||||
LLVMTypeRef *llvm_arg_types = gb_alloc_array(permanent_allocator(), LLVMTypeRef, arg_count);
|
||||
for (unsigned i = 0; i < arg_count; i++) {
|
||||
llvm_arg_types[i] = llvm_uintptr;
|
||||
}
|
||||
|
||||
LLVMTypeRef *results = gb_alloc_array(permanent_allocator(), LLVMTypeRef, 2);
|
||||
results[0] = lb_type(p->module, t_uintptr);
|
||||
results[1] = lb_type(p->module, t_bool);
|
||||
LLVMTypeRef llvm_results = LLVMStructTypeInContext(p->module->ctx, results, 2, false);
|
||||
|
||||
LLVMTypeRef func_type = LLVMFunctionType(llvm_results, llvm_arg_types, arg_count, false);
|
||||
|
||||
LLVMValueRef inline_asm = nullptr;
|
||||
|
||||
switch (build_context.metrics.arch) {
|
||||
case TargetArch_amd64:
|
||||
{
|
||||
GB_ASSERT(arg_count <= 7);
|
||||
|
||||
char asm_string[] = "syscall; setnb %cl";
|
||||
|
||||
// Using CL as an output; RCX doesn't need to get clobbered later.
|
||||
gbString constraints = gb_string_make(heap_allocator(), "={rax},={cl}");
|
||||
for (unsigned i = 0; i < arg_count; i++) {
|
||||
constraints = gb_string_appendc(constraints, ",{");
|
||||
static char const *regs[] = {
|
||||
"rax",
|
||||
"rdi",
|
||||
"rsi",
|
||||
"rdx",
|
||||
"r10",
|
||||
"r8",
|
||||
"r9",
|
||||
};
|
||||
constraints = gb_string_appendc(constraints, regs[i]);
|
||||
constraints = gb_string_appendc(constraints, "}");
|
||||
}
|
||||
|
||||
// NOTE(Feoramund): If you're experiencing instability
|
||||
// regarding syscalls during optimized builds, it is
|
||||
// possible that the ABI has changed for your platform,
|
||||
// or I've missed a register clobber.
|
||||
//
|
||||
// Documentation on this topic is sparse, but I was able to
|
||||
// determine what registers were being clobbered by adding
|
||||
// dummy values to them, setting a breakpoint after the
|
||||
// syscall, and checking the state of the registers afterwards.
|
||||
//
|
||||
// Be advised that manually stepping through a debugger may
|
||||
// cause the kernel to not return via sysret, which will
|
||||
// preserve register state that normally would've been
|
||||
// otherwise clobbered.
|
||||
//
|
||||
// It is also possible that some syscalls clobber different registers.
|
||||
|
||||
if (build_context.metrics.os == TargetOs_freebsd) {
|
||||
// As a fix for CVE-2019-5595, FreeBSD started
|
||||
// clobbering R8, R9, and R10, instead of restoring
|
||||
// them.
|
||||
//
|
||||
// More info here:
|
||||
//
|
||||
// https://www.freebsd.org/security/advisories/FreeBSD-SA-19:01.syscall.asc
|
||||
// https://github.com/freebsd/freebsd-src/blob/098dbd7ff7f3da9dda03802cdb2d8755f816eada/sys/amd64/amd64/exception.S#L605
|
||||
// https://stackoverflow.com/q/66878250
|
||||
constraints = gb_string_appendc(constraints, ",~{r8},~{r9},~{r10}");
|
||||
}
|
||||
|
||||
// Both FreeBSD and NetBSD might clobber RDX.
|
||||
//
|
||||
// For NetBSD, it was clobbered during a call to sysctl.
|
||||
//
|
||||
// For FreeBSD, it's listed as "return value 2" in their
|
||||
// AMD64 assembly, so there's no guarantee that it will persist.
|
||||
constraints = gb_string_appendc(constraints, ",~{rdx},~{r11},~{cc},~{memory}");
|
||||
|
||||
inline_asm = llvm_get_inline_asm(func_type, make_string_c(asm_string), make_string_c(constraints));
|
||||
}
|
||||
break;
|
||||
case TargetArch_arm64:
|
||||
{
|
||||
GB_ASSERT(arg_count <= 7);
|
||||
|
||||
char asm_string[] = "svc #0; cset x8, cc";
|
||||
gbString constraints = gb_string_make(heap_allocator(), "={x0},={x8}");
|
||||
for (unsigned i = 0; i < arg_count; i++) {
|
||||
constraints = gb_string_appendc(constraints, ",{");
|
||||
static char const *regs[] = {
|
||||
"x8",
|
||||
"x0",
|
||||
"x1",
|
||||
"x2",
|
||||
"x3",
|
||||
"x4",
|
||||
"x5",
|
||||
};
|
||||
constraints = gb_string_appendc(constraints, regs[i]);
|
||||
constraints = gb_string_appendc(constraints, "}");
|
||||
}
|
||||
|
||||
// FreeBSD clobbered x1 on a call to sysctl.
|
||||
constraints = gb_string_appendc(constraints, ",~{x1},~{cc},~{memory}");
|
||||
|
||||
inline_asm = llvm_get_inline_asm(func_type, make_string_c(asm_string), make_string_c(constraints));
|
||||
}
|
||||
break;
|
||||
default:
|
||||
GB_PANIC("Unsupported platform");
|
||||
}
|
||||
|
||||
lbValue res = {};
|
||||
res.value = LLVMBuildCall2(p->builder, func_type, inline_asm, args, arg_count, "");
|
||||
res.type = make_optional_ok_type(t_uintptr, true);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
case BuiltinProc_objc_send:
|
||||
return lb_handle_objc_send(p, expr);
|
||||
|
||||
+156
@@ -288,6 +288,9 @@ enum BuildFlagKind {
|
||||
BuildFlag_NoThreadedChecker,
|
||||
BuildFlag_ShowDebugMessages,
|
||||
|
||||
BuildFlag_ShowDefineables,
|
||||
BuildFlag_ExportDefineables,
|
||||
|
||||
BuildFlag_Vet,
|
||||
BuildFlag_VetShadowing,
|
||||
BuildFlag_VetUnused,
|
||||
@@ -483,6 +486,9 @@ gb_internal bool parse_build_flags(Array<String> args) {
|
||||
add_flag(&build_flags, BuildFlag_NoThreadedChecker, str_lit("no-threaded-checker"), BuildFlagParam_None, Command__does_check);
|
||||
add_flag(&build_flags, BuildFlag_ShowDebugMessages, str_lit("show-debug-messages"), BuildFlagParam_None, Command_all);
|
||||
|
||||
add_flag(&build_flags, BuildFlag_ShowDefineables, str_lit("show-defineables"), BuildFlagParam_None, Command__does_check);
|
||||
add_flag(&build_flags, BuildFlag_ExportDefineables, str_lit("export-defineables"), BuildFlagParam_String, Command__does_check);
|
||||
|
||||
add_flag(&build_flags, BuildFlag_Vet, str_lit("vet"), BuildFlagParam_None, Command__does_check);
|
||||
add_flag(&build_flags, BuildFlag_VetUnused, str_lit("vet-unused"), BuildFlagParam_None, Command__does_check);
|
||||
add_flag(&build_flags, BuildFlag_VetUnusedVariables, str_lit("vet-unused-variables"), BuildFlagParam_None, Command__does_check);
|
||||
@@ -814,6 +820,24 @@ gb_internal bool parse_build_flags(Array<String> args) {
|
||||
|
||||
break;
|
||||
}
|
||||
case BuildFlag_ShowDefineables: {
|
||||
GB_ASSERT(value.kind == ExactValue_Invalid);
|
||||
build_context.show_defineables = true;
|
||||
break;
|
||||
}
|
||||
case BuildFlag_ExportDefineables: {
|
||||
GB_ASSERT(value.kind == ExactValue_String);
|
||||
|
||||
String export_path = string_trim_whitespace(value.value_string);
|
||||
if (is_build_flag_path_valid(export_path)) {
|
||||
build_context.export_defineables_file = path_to_full_path(heap_allocator(), export_path);
|
||||
} else {
|
||||
gb_printf_err("Invalid -export-defineables path, got %.*s\n", LIT(export_path));
|
||||
bad_flags = true;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case BuildFlag_ShowSystemCalls: {
|
||||
GB_ASSERT(value.kind == ExactValue_Invalid);
|
||||
build_context.show_system_calls = true;
|
||||
@@ -1553,6 +1577,115 @@ gb_internal void timings_export_all(Timings *t, Checker *c, bool timings_are_fin
|
||||
gb_printf("Done.\n");
|
||||
}
|
||||
|
||||
gb_internal void check_defines(BuildContext *bc, Checker *c) {
|
||||
for (auto const &entry : bc->defined_values) {
|
||||
String name = make_string_c(entry.key);
|
||||
ExactValue value = entry.value;
|
||||
GB_ASSERT(value.kind != ExactValue_Invalid);
|
||||
|
||||
bool found = false;
|
||||
for_array(i, c->info.defineables) {
|
||||
Defineable *def = &c->info.defineables[i];
|
||||
if (def->name == name) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
ERROR_BLOCK();
|
||||
warning(nullptr, "given -define:%.*s is unused in the project", LIT(name));
|
||||
error_line("\tSuggestion: use the -show-defineables flag for an overview of the possible defines\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
gb_internal void temp_alloc_defineable_strings(Checker *c) {
|
||||
for_array(i, c->info.defineables) {
|
||||
Defineable *def = &c->info.defineables[i];
|
||||
def->default_value_str = make_string_c(write_exact_value_to_string(gb_string_make(temporary_allocator(), ""), def->default_value));
|
||||
def->pos_str = make_string_c(token_pos_to_string(def->pos));
|
||||
}
|
||||
}
|
||||
|
||||
gb_internal GB_COMPARE_PROC(defineables_cmp) {
|
||||
Defineable *x = (Defineable *)a;
|
||||
Defineable *y = (Defineable *)b;
|
||||
|
||||
int cmp = 0;
|
||||
|
||||
String x_file = get_file_path_string(x->pos.file_id);
|
||||
String y_file = get_file_path_string(y->pos.file_id);
|
||||
cmp = string_compare(x_file, y_file);
|
||||
if (cmp) {
|
||||
return cmp;
|
||||
}
|
||||
|
||||
return i32_cmp(x->pos.offset, y->pos.offset);
|
||||
}
|
||||
|
||||
gb_internal void sort_defineables(Checker *c) {
|
||||
gb_sort_array(c->info.defineables.data, c->info.defineables.count, defineables_cmp);
|
||||
}
|
||||
|
||||
gb_internal void export_defineables(Checker *c, String path) {
|
||||
gbFile f = {};
|
||||
gbFileError err = gb_file_open_mode(&f, gbFileMode_Write, (char *)path.text);
|
||||
if (err != gbFileError_None) {
|
||||
gb_printf_err("Failed to export defineables to: %.*s\n", LIT(path));
|
||||
gb_exit(1);
|
||||
return;
|
||||
} else {
|
||||
gb_printf("Exporting defineables to '%.*s'...\n", LIT(path));
|
||||
}
|
||||
defer (gb_file_close(&f));
|
||||
|
||||
gbString docs = gb_string_make(heap_allocator(), "");
|
||||
defer (gb_string_free(docs));
|
||||
|
||||
gb_fprintf(&f, "Defineable,Default Value,Docs,Location\n");
|
||||
for_array(i, c->info.defineables) {
|
||||
Defineable *def = &c->info.defineables[i];
|
||||
|
||||
gb_string_clear(docs);
|
||||
if (def->docs) {
|
||||
docs = gb_string_appendc(docs, "\"");
|
||||
for (Token const &token : def->docs->list) {
|
||||
for (isize i = 0; i < token.string.len; i++) {
|
||||
u8 c = token.string.text[i];
|
||||
if (c == '"') {
|
||||
docs = gb_string_appendc(docs, "\"\"");
|
||||
} else {
|
||||
docs = gb_string_append_length(docs, &c, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
docs = gb_string_appendc(docs, "\"");
|
||||
}
|
||||
|
||||
gb_fprintf(&f,"%.*s,%.*s,%s,%.*s\n", LIT(def->name), LIT(def->default_value_str), docs, LIT(def->pos_str));
|
||||
}
|
||||
}
|
||||
|
||||
gb_internal void show_defineables(Checker *c) {
|
||||
for_array(i, c->info.defineables) {
|
||||
Defineable *def = &c->info.defineables[i];
|
||||
if (has_ansi_terminal_colours()) {
|
||||
gb_printf("\x1b[0;90m");
|
||||
}
|
||||
printf("%.*s\n", LIT(def->pos_str));
|
||||
if (def->docs) {
|
||||
for (Token const &token : def->docs->list) {
|
||||
gb_printf("%.*s\n", LIT(token.string));
|
||||
}
|
||||
}
|
||||
if (has_ansi_terminal_colours()) {
|
||||
gb_printf("\x1b[0m");
|
||||
}
|
||||
gb_printf("%.*s :: %.*s\n\n", LIT(def->name), LIT(def->default_value_str));
|
||||
}
|
||||
}
|
||||
|
||||
gb_internal void show_timings(Checker *c, Timings *t) {
|
||||
Parser *p = c->parser;
|
||||
isize lines = p->total_line_count;
|
||||
@@ -1955,6 +2088,15 @@ gb_internal void print_show_help(String const arg0, String const &command) {
|
||||
print_usage_line(2, "Usage in code:");
|
||||
print_usage_line(3, "#config(SPAM, default_value)");
|
||||
print_usage_line(0, "");
|
||||
|
||||
print_usage_line(1, "-show-defineables");
|
||||
print_usage_line(2, "Shows an overview of all the #config/#defined usages in the project.");
|
||||
print_usage_line(0, "");
|
||||
|
||||
print_usage_line(1, "-export-defineables:<filename>");
|
||||
print_usage_line(2, "Exports an overview of all the #config/#defined usages in CSV format to the given file path.");
|
||||
print_usage_line(2, "Example: -export-defineables:defineables.csv");
|
||||
print_usage_line(0, "");
|
||||
}
|
||||
|
||||
if (build) {
|
||||
@@ -2953,6 +3095,7 @@ int main(int arg_count, char const **arg_ptr) {
|
||||
defer (destroy_checker(checker));
|
||||
|
||||
check_parsed_files(checker);
|
||||
check_defines(&build_context, checker);
|
||||
if (any_errors()) {
|
||||
print_all_errors();
|
||||
return 1;
|
||||
@@ -2961,6 +3104,19 @@ int main(int arg_count, char const **arg_ptr) {
|
||||
print_all_errors();
|
||||
}
|
||||
|
||||
if (build_context.show_defineables || build_context.export_defineables_file != "") {
|
||||
TEMPORARY_ALLOCATOR_GUARD();
|
||||
temp_alloc_defineable_strings(checker);
|
||||
sort_defineables(checker);
|
||||
|
||||
if (build_context.show_defineables) {
|
||||
show_defineables(checker);
|
||||
}
|
||||
|
||||
if (build_context.export_defineables_file != "") {
|
||||
export_defineables(checker, build_context.export_defineables_file);
|
||||
}
|
||||
}
|
||||
|
||||
if (build_context.command_kind == Command_strip_semicolon) {
|
||||
return strip_semicolons(parser);
|
||||
|
||||
@@ -20,9 +20,6 @@ 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.create(t.seed)
|
||||
context.random_generator = rand.default_random_generator(&r)
|
||||
|
||||
// Test insertion.
|
||||
NR_INSERTS :: 32 + 1 // Ensure at least 1 collision.
|
||||
inserted_map := make(map[int]^avl.Node(int))
|
||||
|
||||
@@ -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.create(t.seed)
|
||||
context.random_generator = rand.default_random_generator(&r)
|
||||
|
||||
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)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -53,8 +53,7 @@ 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)
|
||||
context.random_generator = rand.default_random_generator(&random_seed)
|
||||
rand.reset(t.seed)
|
||||
for len(b) > 0 {
|
||||
update_size := min(len(b), rand.int_max(8192))
|
||||
if update_size > 4096 {
|
||||
|
||||
@@ -5,7 +5,7 @@ set TEST_ARGS=-fast-tests
|
||||
set TEST_ARGS=-no-random
|
||||
set TEST_ARGS=
|
||||
set OUT_NAME=math_big_test_library.dll
|
||||
set COMMON=-build-mode:shared -show-timings -no-bounds-check -define:MATH_BIG_EXE=false -vet -strict-style -define:ODIN_TEST_FANCY=false
|
||||
set COMMON=-build-mode:shared -show-timings -no-bounds-check -define:MATH_BIG_EXE=false -vet -strict-style
|
||||
echo ---
|
||||
echo Running core:math/big tests
|
||||
echo ---
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -11,8 +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)
|
||||
context.random_generator = rand.default_random_generator(&r)
|
||||
rand.reset(t.seed)
|
||||
|
||||
vals := make([]u64, test_size)
|
||||
r_idx := make([]int, test_size) // Reverse index
|
||||
@@ -63,8 +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)
|
||||
context.random_generator = rand.default_random_generator(&r)
|
||||
rand.reset(t.seed)
|
||||
|
||||
vals := make([]u64, test_size)
|
||||
r_idx := make([]int, test_size) // Reverse index
|
||||
@@ -203,7 +201,7 @@ 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
|
||||
}
|
||||
@@ -218,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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,135 @@
|
||||
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)
|
||||
}
|
||||
|
||||
@test
|
||||
test_width :: proc(t: ^testing.T) {
|
||||
{
|
||||
str := "He\u200dllo"
|
||||
graphemes, _, width := utf8.grapheme_count(str)
|
||||
testing.expect_value(t, graphemes, 5)
|
||||
testing.expect_value(t, width, 5)
|
||||
}
|
||||
|
||||
{
|
||||
// Note that a zero-width space is still considered a grapheme as far
|
||||
// as the specification is concerned.
|
||||
str := "He\u200bllo"
|
||||
graphemes, _, width := utf8.grapheme_count(str)
|
||||
testing.expect_value(t, graphemes, 6)
|
||||
testing.expect_value(t, width, 5)
|
||||
}
|
||||
|
||||
{
|
||||
str := "\U0001F926\U0001F3FC\u200D\u2642"
|
||||
graphemes, _, width := utf8.grapheme_count(str)
|
||||
testing.expect_value(t, graphemes, 1)
|
||||
testing.expect_value(t, width, 2)
|
||||
}
|
||||
|
||||
{
|
||||
str := "H̷e̶l̵l̸o̴p̵e̷ ̸w̶o̸r̵l̶d̵!̴"
|
||||
graphemes, _, width := utf8.grapheme_count(str)
|
||||
testing.expect_value(t, graphemes, 14)
|
||||
testing.expect_value(t, width, 14)
|
||||
}
|
||||
|
||||
{
|
||||
str := "aカ.ヒフ"
|
||||
graphemes, grapheme_count, _, width := utf8.decode_grapheme_clusters(str)
|
||||
defer delete(graphemes)
|
||||
testing.expect_value(t, grapheme_count, 5)
|
||||
testing.expect_value(t, width, 8)
|
||||
if grapheme_count == 5 {
|
||||
testing.expect_value(t, graphemes[0].width, 1)
|
||||
testing.expect_value(t, graphemes[1].width, 2)
|
||||
testing.expect_value(t, graphemes[2].width, 1)
|
||||
testing.expect_value(t, graphemes[3].width, 2)
|
||||
testing.expect_value(t, graphemes[4].width, 2)
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
str := "いろはにほへ"
|
||||
graphemes, _, width := utf8.grapheme_count(str)
|
||||
testing.expect_value(t, graphemes, 6)
|
||||
testing.expect_value(t, width, 12)
|
||||
}
|
||||
|
||||
{
|
||||
str := "舍利弗,是諸法空相,不生不滅,不垢不淨,不增不減。"
|
||||
graphemes, _, width := utf8.grapheme_count(str)
|
||||
testing.expect_value(t, graphemes, 25)
|
||||
testing.expect_value(t, width, 50)
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -16,8 +16,7 @@ map_insert_random_key_value :: proc(t: ^testing.T) {
|
||||
defer delete(m)
|
||||
|
||||
unique_keys := 0
|
||||
r := rand.create(t.seed + seed_incr)
|
||||
context.random_generator = rand.default_random_generator(&r)
|
||||
rand.reset(t.seed + seed_incr)
|
||||
for _ in 0..<entries {
|
||||
k := rand.int63()
|
||||
v := rand.int63()
|
||||
@@ -37,8 +36,7 @@ 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)
|
||||
context.random_generator = rand.default_random_generator(&r)
|
||||
rand.reset(t.seed + seed_incr)
|
||||
|
||||
num_fails := 0
|
||||
for _ in 0..<entries {
|
||||
@@ -68,8 +66,7 @@ map_update_random_key_value :: proc(t: ^testing.T) {
|
||||
defer delete(m)
|
||||
|
||||
unique_keys := 0
|
||||
r := rand.create(t.seed + seed_incr)
|
||||
context.random_generator = rand.default_random_generator(&r)
|
||||
rand.reset(t.seed + seed_incr)
|
||||
|
||||
for _ in 0..<entries {
|
||||
k := rand.int63()
|
||||
@@ -92,8 +89,7 @@ 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)
|
||||
context.random_generator = rand.default_random_generator(&r)
|
||||
rand.reset(t.seed + seed_incr)
|
||||
|
||||
for _ in 0..<half_entries {
|
||||
k := rand.int63()
|
||||
@@ -103,8 +99,7 @@ map_update_random_key_value :: proc(t: ^testing.T) {
|
||||
}
|
||||
|
||||
// Reset randomizer and verify
|
||||
r = rand.create(t.seed + seed_incr)
|
||||
context.random_generator = rand.default_random_generator(&r)
|
||||
rand.reset(t.seed + seed_incr)
|
||||
|
||||
num_fails := 0
|
||||
for i in 0..<entries {
|
||||
@@ -135,8 +130,7 @@ map_delete_random_key_value :: proc(t: ^testing.T) {
|
||||
defer delete(m)
|
||||
|
||||
unique_keys := 0
|
||||
r := rand.create(t.seed + seed_incr)
|
||||
context.random_generator = rand.default_random_generator(&r)
|
||||
rand.reset(t.seed + seed_incr)
|
||||
|
||||
for _ in 0..<entries {
|
||||
k := rand.int63()
|
||||
@@ -159,8 +153,7 @@ 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)
|
||||
context.random_generator = rand.default_random_generator(&r)
|
||||
rand.reset(t.seed + seed_incr)
|
||||
|
||||
for _ in 0..<half_entries {
|
||||
k := rand.int63()
|
||||
@@ -170,8 +163,7 @@ map_delete_random_key_value :: proc(t: ^testing.T) {
|
||||
}
|
||||
|
||||
// Reset randomizer and verify
|
||||
r = rand.create(t.seed + seed_incr)
|
||||
context.random_generator = rand.default_random_generator(&r)
|
||||
rand.reset(t.seed + seed_incr)
|
||||
|
||||
num_fails := 0
|
||||
for i in 0..<entries {
|
||||
@@ -218,8 +210,7 @@ set_insert_random_key_value :: proc(t: ^testing.T) {
|
||||
defer delete(m)
|
||||
|
||||
unique_keys := 0
|
||||
r := rand.create(t.seed + seed_incr)
|
||||
context.random_generator = rand.default_random_generator(&r)
|
||||
rand.reset(t.seed + seed_incr)
|
||||
|
||||
for _ in 0..<entries {
|
||||
k := rand.int63()
|
||||
@@ -238,8 +229,7 @@ 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)
|
||||
context.random_generator = rand.default_random_generator(&r)
|
||||
rand.reset(t.seed + seed_incr)
|
||||
|
||||
num_fails := 0
|
||||
for _ in 0..<entries {
|
||||
@@ -268,8 +258,7 @@ set_delete_random_key_value :: proc(t: ^testing.T) {
|
||||
defer delete(m)
|
||||
|
||||
unique_keys := 0
|
||||
r := rand.create(t.seed + seed_incr)
|
||||
context.random_generator = rand.default_random_generator(&r)
|
||||
rand.reset(t.seed + seed_incr)
|
||||
|
||||
for _ in 0..<entries {
|
||||
k := rand.int63()
|
||||
@@ -291,8 +280,7 @@ 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)
|
||||
context.random_generator = rand.default_random_generator(&r)
|
||||
rand.reset(t.seed + seed_incr)
|
||||
|
||||
for _ in 0..<half_entries {
|
||||
k := rand.int63()
|
||||
@@ -300,8 +288,7 @@ set_delete_random_key_value :: proc(t: ^testing.T) {
|
||||
}
|
||||
|
||||
// Reset randomizer and verify
|
||||
r = rand.create(t.seed + seed_incr)
|
||||
context.random_generator = rand.default_random_generator(&r)
|
||||
rand.reset(t.seed + seed_incr)
|
||||
|
||||
num_fails := 0
|
||||
for i in 0..<entries {
|
||||
|
||||
Vendored
+24
-22
@@ -3775,39 +3775,41 @@ MESSAGE :: struct {
|
||||
|
||||
IInfoQueue_VTable :: struct {
|
||||
using iunkown_vtable: IUnknown_VTable,
|
||||
AddApplicationMessage: proc "system" (this: ^IInfoQueue, Severity: MESSAGE_SEVERITY, pDescription: cstring) -> HRESULT,
|
||||
AddMessage: proc "system" (this: ^IInfoQueue, Category: MESSAGE_CATEGORY, Severity: MESSAGE_SEVERITY, ID: MESSAGE_ID, pDescription: cstring) -> HRESULT,
|
||||
AddRetrievalFilterEntries: proc "system" (this: ^IInfoQueue, pFilter: ^INFO_QUEUE_FILTER) -> HRESULT,
|
||||
AddStorageFilterEntries: proc "system" (this: ^IInfoQueue, pFilter: ^INFO_QUEUE_FILTER) -> HRESULT,
|
||||
ClearRetrievalFilter: proc "system" (this: ^IInfoQueue),
|
||||
ClearStorageFilter: proc "system" (this: ^IInfoQueue),
|
||||
SetMessageCountLimit: proc "system" (this: ^IInfoQueue, MessageCountLimit: u64) -> HRESULT,
|
||||
ClearStoredMessages: proc "system" (this: ^IInfoQueue),
|
||||
GetBreakOnCategory: proc "system" (this: ^IInfoQueue, Category: MESSAGE_CATEGORY) -> BOOL,
|
||||
GetBreakOnID: proc "system" (this: ^IInfoQueue, ID: MESSAGE_ID) -> BOOL,
|
||||
GetBreakOnSeverity: proc "system" (this: ^IInfoQueue, Severity: MESSAGE_SEVERITY) -> BOOL,
|
||||
GetMessage: proc "system" (this: ^IInfoQueue, MessageIndex: u64, pMessage: ^MESSAGE, pMessageByteLength: ^SIZE_T) -> HRESULT,
|
||||
GetMessageCountLimit: proc "system" (this: ^IInfoQueue) -> u64,
|
||||
GetMuteDebugOutput: proc "system" (this: ^IInfoQueue) -> BOOL,
|
||||
GetNumMessagesAllowedByStorageFilter: proc "system" (this: ^IInfoQueue) -> u64,
|
||||
GetNumMessagesDeniedByStorageFilter: proc "system" (this: ^IInfoQueue) -> u64,
|
||||
GetNumMessagesDiscardedByMessageCountLimit: proc "system" (this: ^IInfoQueue) -> u64,
|
||||
GetNumStoredMessages: proc "system" (this: ^IInfoQueue) -> u64,
|
||||
GetNumStoredMessagesAllowedByRetrievalFilter: proc "system" (this: ^IInfoQueue) -> u64,
|
||||
GetRetrievalFilter: proc "system" (this: ^IInfoQueue, pFilter: ^INFO_QUEUE_FILTER, pFilterByteLength: ^SIZE_T) -> HRESULT,
|
||||
GetRetrievalFilterStackSize: proc "system" (this: ^IInfoQueue) -> u64,
|
||||
GetNumMessagesDiscardedByMessageCountLimit: proc "system" (this: ^IInfoQueue) -> u64,
|
||||
GetMessageCountLimit: proc "system" (this: ^IInfoQueue) -> u64,
|
||||
AddStorageFilterEntries: proc "system" (this: ^IInfoQueue, pFilter: ^INFO_QUEUE_FILTER) -> HRESULT,
|
||||
GetStorageFilter: proc "system" (this: ^IInfoQueue, pFilter: ^INFO_QUEUE_FILTER, pFilterByteLength: ^SIZE_T) -> HRESULT,
|
||||
GetStorageFilterStackSize: proc "system" (this: ^IInfoQueue) -> u64,
|
||||
PopRetrievalFilter: proc "system" (this: ^IInfoQueue),
|
||||
PopStorageFilter: proc "system" (this: ^IInfoQueue),
|
||||
PushCopyOfRetrievalFilter: proc "system" (this: ^IInfoQueue) -> HRESULT,
|
||||
PushCopyOfStorageFilter: proc "system" (this: ^IInfoQueue) -> HRESULT,
|
||||
PushEmptyRetrievalFilter: proc "system" (this: ^IInfoQueue) -> HRESULT,
|
||||
ClearStorageFilter: proc "system" (this: ^IInfoQueue),
|
||||
PushEmptyStorageFilter: proc "system" (this: ^IInfoQueue) -> HRESULT,
|
||||
PushCopyOfStorageFilter: proc "system" (this: ^IInfoQueue) -> HRESULT,
|
||||
PushStorageFilter: proc "system" (this: ^IInfoQueue, pFilter: ^INFO_QUEUE_FILTER) -> HRESULT,
|
||||
PopStorageFilter: proc "system" (this: ^IInfoQueue),
|
||||
GetStorageFilterStackSize: proc "system" (this: ^IInfoQueue) -> u64,
|
||||
AddRetrievalFilterEntries: proc "system" (this: ^IInfoQueue, pFilter: ^INFO_QUEUE_FILTER) -> HRESULT,
|
||||
GetRetrievalFilter: proc "system" (this: ^IInfoQueue, pFilter: ^INFO_QUEUE_FILTER, pFilterByteLength: ^SIZE_T) -> HRESULT,
|
||||
ClearRetrievalFilter: proc "system" (this: ^IInfoQueue),
|
||||
PushEmptyRetrievalFilter: proc "system" (this: ^IInfoQueue) -> HRESULT,
|
||||
PushCopyOfRetrievalFilter: proc "system" (this: ^IInfoQueue) -> HRESULT,
|
||||
PushRetrievalFilter: proc "system" (this: ^IInfoQueue, pFilter: ^INFO_QUEUE_FILTER) -> HRESULT,
|
||||
PopRetrievalFilter: proc "system" (this: ^IInfoQueue),
|
||||
GetRetrievalFilterStackSize: proc "system" (this: ^IInfoQueue) -> u64,
|
||||
AddMessage: proc "system" (this: ^IInfoQueue, Category: MESSAGE_CATEGORY, Severity: MESSAGE_SEVERITY, ID: MESSAGE_ID, pDescription: cstring) -> HRESULT,
|
||||
AddApplicationMessage: proc "system" (this: ^IInfoQueue, Severity: MESSAGE_SEVERITY, pDescription: cstring) -> HRESULT,
|
||||
SetBreakOnCategory: proc "system" (this: ^IInfoQueue, Category: MESSAGE_CATEGORY, bEnable: BOOL) -> HRESULT,
|
||||
SetBreakOnID: proc "system" (this: ^IInfoQueue, ID: MESSAGE_ID, bEnable: BOOL) -> HRESULT,
|
||||
SetBreakOnSeverity: proc "system" (this: ^IInfoQueue, Severity: MESSAGE_SEVERITY, bEnable: BOOL) -> HRESULT,
|
||||
SetMessageCountLimit: proc "system" (this: ^IInfoQueue, MessageCountLimit: u64) -> HRESULT,
|
||||
SetBreakOnID: proc "system" (this: ^IInfoQueue, ID: MESSAGE_ID, bEnable: BOOL) -> HRESULT,
|
||||
GetBreakOnCategory: proc "system" (this: ^IInfoQueue, Category: MESSAGE_CATEGORY) -> BOOL,
|
||||
GetBreakOnSeverity: proc "system" (this: ^IInfoQueue, Severity: MESSAGE_SEVERITY) -> BOOL,
|
||||
GetBreakOnID: proc "system" (this: ^IInfoQueue, ID: MESSAGE_ID) -> BOOL,
|
||||
SetMuteDebugOutput: proc "system" (this: ^IInfoQueue, bMute: BOOL),
|
||||
GetMuteDebugOutput: proc "system" (this: ^IInfoQueue) -> BOOL,
|
||||
}
|
||||
|
||||
MESSAGE_ID :: enum u32 {
|
||||
|
||||
Vendored
+14
-10
@@ -2,14 +2,18 @@
|
||||
|
||||
package glfw
|
||||
|
||||
// TODO: Native Linux
|
||||
// Display* glfwGetX11Display(void);
|
||||
// RRCrtc glfwGetX11Adapter(GLFWmonitor* monitor);
|
||||
// RROutput glfwGetX11Monitor(GLFWmonitor* monitor);
|
||||
// Window glfwGetX11Window(GLFWwindow* window);
|
||||
// void glfwSetX11SelectionString(const char* string);
|
||||
// const char* glfwGetX11SelectionString(void);
|
||||
import "vendor:x11/xlib"
|
||||
|
||||
// struct wl_display* glfwGetWaylandDisplay(void);
|
||||
// struct wl_output* glfwGetWaylandMonitor(GLFWmonitor* monitor);
|
||||
// struct wl_surface* glfwGetWaylandWindow(GLFWwindow* window);
|
||||
@(default_calling_convention="c", link_prefix="glfw")
|
||||
foreign {
|
||||
GetX11Display :: proc() -> ^xlib.Display ---
|
||||
GetX11Window :: proc(window: WindowHandle) -> xlib.Window ---
|
||||
GetX11Adapter :: proc(monitor: MonitorHandle) -> xlib.RRCrtc ---
|
||||
GetX11Monitor :: proc(monitor: MonitorHandle) -> xlib.RROutput ---
|
||||
SetX11SelectionString :: proc(string: cstring) ---
|
||||
GetX11SelectionString :: proc() -> cstring ---
|
||||
|
||||
GetWaylandDisplay :: proc() -> rawptr /* struct wl_display* */ ---
|
||||
GetWaylandWindow :: proc(window: WindowHandle) -> rawptr /* struct wl_surface* */ ---
|
||||
GetWaylandMonitor :: proc(monitor: MonitorHandle) -> rawptr /* struct wl_output* */ ---
|
||||
}
|
||||
|
||||
Vendored
+37
-7
@@ -96,6 +96,10 @@ class WasmMemoryInterface {
|
||||
};
|
||||
loadPtr(addr) { return this.loadU32(addr); }
|
||||
|
||||
loadB32(addr) {
|
||||
return this.loadU32(addr) != 0;
|
||||
}
|
||||
|
||||
loadBytes(ptr, len) {
|
||||
return new Uint8Array(this.memory.buffer, ptr, Number(len));
|
||||
}
|
||||
@@ -104,6 +108,16 @@ class WasmMemoryInterface {
|
||||
const bytes = this.loadBytes(ptr, Number(len));
|
||||
return new TextDecoder().decode(bytes);
|
||||
}
|
||||
|
||||
loadCstring(ptr) {
|
||||
const start = this.loadPtr(ptr);
|
||||
if (start == 0) {
|
||||
return null;
|
||||
}
|
||||
let len = 0;
|
||||
for (; this.mem.getUint8(start+len) != 0; len += 1) {}
|
||||
return this.loadString(start, len);
|
||||
}
|
||||
|
||||
storeU8(addr, value) { this.mem.setUint8 (addr, value); }
|
||||
storeI8(addr, value) { this.mem.setInt8 (addr, value); }
|
||||
@@ -1245,7 +1259,7 @@ class WebGLInterface {
|
||||
};
|
||||
|
||||
|
||||
function odinSetupDefaultImports(wasmMemoryInterface, consoleElement) {
|
||||
function odinSetupDefaultImports(wasmMemoryInterface, consoleElement, memory) {
|
||||
const MAX_INFO_CONSOLE_LINES = 512;
|
||||
let infoConsoleLines = new Array();
|
||||
let currentLine = {};
|
||||
@@ -1366,8 +1380,15 @@ function odinSetupDefaultImports(wasmMemoryInterface, consoleElement) {
|
||||
let event_temp_data = {};
|
||||
|
||||
let webglContext = new WebGLInterface(wasmMemoryInterface);
|
||||
|
||||
const env = {};
|
||||
|
||||
if (memory) {
|
||||
env.memory = memory;
|
||||
}
|
||||
|
||||
return {
|
||||
"env": {},
|
||||
env,
|
||||
"odin_env": {
|
||||
write: (fd, ptr, len) => {
|
||||
const str = wasmMemoryInterface.loadString(ptr, len);
|
||||
@@ -1720,13 +1741,16 @@ function odinSetupDefaultImports(wasmMemoryInterface, consoleElement) {
|
||||
* @param {string} wasmPath - Path to the WASM module to run
|
||||
* @param {?HTMLPreElement} consoleElement - Optional console/pre element to append output to, in addition to the console
|
||||
* @param {any} extraForeignImports - Imports, in addition to the default runtime to provide the module
|
||||
* @param {?WasmMemoryInterface} wasmMemoryInterface - Optional memory to use instead of the defaults
|
||||
* @param {?int} intSize - Size (in bytes) of the integer type, should be 4 on `js_wasm32` and 8 on `js_wasm64p32`
|
||||
*/
|
||||
async function runWasm(wasmPath, consoleElement, extraForeignImports, intSize = 4) {
|
||||
const wasmMemoryInterface = new WasmMemoryInterface();
|
||||
async function runWasm(wasmPath, consoleElement, extraForeignImports, wasmMemoryInterface, intSize = 4) {
|
||||
if (!wasmMemoryInterface) {
|
||||
wasmMemoryInterface = new WasmMemoryInterface();
|
||||
}
|
||||
wasmMemoryInterface.setIntSize(intSize);
|
||||
|
||||
let imports = odinSetupDefaultImports(wasmMemoryInterface, consoleElement);
|
||||
let imports = odinSetupDefaultImports(wasmMemoryInterface, consoleElement, wasmMemoryInterface.memory);
|
||||
let exports = {};
|
||||
|
||||
if (extraForeignImports !== undefined) {
|
||||
@@ -1741,11 +1765,17 @@ async function runWasm(wasmPath, consoleElement, extraForeignImports, intSize =
|
||||
const wasm = await WebAssembly.instantiate(file, imports);
|
||||
exports = wasm.instance.exports;
|
||||
wasmMemoryInterface.setExports(exports);
|
||||
wasmMemoryInterface.setMemory(exports.memory);
|
||||
|
||||
if (exports.memory) {
|
||||
if (wasmMemoryInterface.memory) {
|
||||
console.warn("WASM module exports memory, but `runWasm` was given an interface with existing memory too");
|
||||
}
|
||||
wasmMemoryInterface.setMemory(exports.memory);
|
||||
}
|
||||
|
||||
exports._start();
|
||||
|
||||
// Define a `@export step :: proc(dt: f32) -> (continue: bool) {`
|
||||
// Define a `@export step :: proc(dt: f32) -> (keep_going: bool) {`
|
||||
// in your app and it will get called every frame.
|
||||
// return `false` to stop the execution of the module.
|
||||
if (exports.step) {
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
lib/*
|
||||
!lib/.gitkeep
|
||||
example/web/triangle.wasm
|
||||
example/web/wgpu.js
|
||||
example/web/runtime.js
|
||||
example/example
|
||||
example/example.exe
|
||||
example/triangle
|
||||
example/triangle.exe
|
||||
Vendored
+48
@@ -0,0 +1,48 @@
|
||||
# WGPU
|
||||
|
||||
A cross-platform (and WASM) GPU API.
|
||||
|
||||
WASM support is achieved by providing wrappers around the browser native WebGPU API
|
||||
that are called instead of the [wgpu-native](https://github.com/gfx-rs/wgpu-native) library,
|
||||
the wgpu-native library provides support for all other targets.
|
||||
|
||||
Have a look at the `example/` directory for the rendering of a basic triangle.
|
||||
|
||||
## Getting the wgpu-native libraries
|
||||
|
||||
For native support (not the browser), some libraries are required. Fortunately this is
|
||||
extremely easy, just download them from the [releases on GitHub](https://github.com/gfx-rs/wgpu-native/releases/tag/v0.19.4.1),
|
||||
the bindings are for v0.19.4.1 at the moment.
|
||||
|
||||
These are expected in the `lib` folder under the same name as they are released (just unzipped).
|
||||
By default it will look for a static release version (`wgpu-OS-ARCH-release.a|lib`),
|
||||
you can set `-define:WGPU_DEBUG=true` for it to look for a debug version,
|
||||
and use `-define:WGPU_SHARED=true` to look for the shared libraries.
|
||||
|
||||
## WASM
|
||||
|
||||
For WASM, the module has to be built with a function table to enable callbacks.
|
||||
You can do so using `-extra-linker-flags:"--export-table"`.
|
||||
|
||||
Being able to allocate is also required (for some auxiliary APIs but also for mapping/unmapping buffers).
|
||||
|
||||
You can set the context that is used for allocations by setting the global variable `wpgu.g_context`.
|
||||
It will default to the `runtime.default_context`.
|
||||
|
||||
Again, have a look at the `example/` and how it is set up, doing the `--import-memory` and the likes
|
||||
is not strictly necessary but allows your app more memory than the minimal default.
|
||||
|
||||
The bindings work on both `-target:js_wasm32` and `-target:js_wasm64p32`.
|
||||
|
||||
## GLFW Glue
|
||||
|
||||
There is an inner package `glfwglue` that can be used to glue together WGPU and GLFW.
|
||||
It exports one procedure `GetSurface(wgpu.Instance, glfw.WindowHandle) -> glfw.Surface`.
|
||||
The procedure will call the needed target specific procedures and return a surface configured
|
||||
for the given window.
|
||||
|
||||
To support Wayland on Linux, you need to have GLFW compiled to support it, and use
|
||||
`-define:WGPU_GFLW_GLUE_SUPPORT_WAYLAND=true` to enable the package to check for Wayland.
|
||||
|
||||
Do note that wgpu does not require GLFW, you can use native windows or another windowing library too.
|
||||
For that you can take inspiration from `glfwglue` on glueing them together.
|
||||
Vendored
+17
@@ -0,0 +1,17 @@
|
||||
FILES := $(wildcard *)
|
||||
|
||||
# NOTE: changing this requires changing the same values in the `web/index.html`.
|
||||
INITIAL_MEMORY_PAGES := 2000
|
||||
MAX_MEMORY_PAGES := 65536
|
||||
|
||||
PAGE_SIZE := 65536
|
||||
INITIAL_MEMORY_BYTES := $(shell expr $(INITIAL_MEMORY_PAGES) \* $(PAGE_SIZE))
|
||||
MAX_MEMORY_BYTES := $(shell expr $(MAX_MEMORY_PAGES) \* $(PAGE_SIZE))
|
||||
|
||||
web/triangle.wasm: $(FILES) ../wgpu.js ../../wasm/js/runtime.js
|
||||
odin build . \
|
||||
-target:js_wasm32 -out:web/triangle.wasm -o:size \
|
||||
-extra-linker-flags:"--export-table --import-memory --initial-memory=$(INITIAL_MEMORY_BYTES) --max-memory=$(MAX_MEMORY_BYTES)"
|
||||
|
||||
cp ../wgpu.js web/wgpu.js
|
||||
cp ../../wasm/js/runtime.js web/runtime.js
|
||||
Vendored
+12
@@ -0,0 +1,12 @@
|
||||
REM NOTE: changing this requires changing the same values in the `web/index.html`.
|
||||
set INITIAL_MEMORY_PAGES=2000
|
||||
set MAX_MEMORY_PAGES=65536
|
||||
|
||||
set PAGE_SIZE=65536
|
||||
set /a INITIAL_MEMORY_BYTES=%INITIAL_MEMORY_PAGES% * %PAGE_SIZE%
|
||||
set /a MAX_MEMORY_BYTES=%MAX_MEMORY_PAGES% * %PAGE_SIZE%
|
||||
|
||||
call odin.exe build . -target:js_wasm32 -out:web/triangle.wasm -o:size -extra-linker-flags:"--export-table --import-memory --initial-memory=%INITIAL_MEMORY_BYTES% --max-memory=%MAX_MEMORY_BYTES%"
|
||||
|
||||
copy "..\wgpu.js" "web\wgpu.js"
|
||||
copy "..\..\wasm\js\runtime.js" "web\runtime.js"
|
||||
Vendored
+187
@@ -0,0 +1,187 @@
|
||||
package vendor_wgpu_example_triangle
|
||||
|
||||
import "base:runtime"
|
||||
|
||||
import "core:fmt"
|
||||
|
||||
import "vendor:wgpu"
|
||||
|
||||
State :: struct {
|
||||
ctx: runtime.Context,
|
||||
os: OS,
|
||||
|
||||
instance: wgpu.Instance,
|
||||
surface: wgpu.Surface,
|
||||
adapter: wgpu.Adapter,
|
||||
device: wgpu.Device,
|
||||
config: wgpu.SurfaceConfiguration,
|
||||
queue: wgpu.Queue,
|
||||
module: wgpu.ShaderModule,
|
||||
pipeline_layout: wgpu.PipelineLayout,
|
||||
pipeline: wgpu.RenderPipeline,
|
||||
}
|
||||
|
||||
@(private="file")
|
||||
state: State
|
||||
|
||||
main :: proc() {
|
||||
state.ctx = context
|
||||
|
||||
os_init(&state.os)
|
||||
|
||||
state.instance = wgpu.CreateInstance(nil)
|
||||
if state.instance == nil {
|
||||
panic("WebGPU is not supported")
|
||||
}
|
||||
state.surface = os_get_surface(&state.os, state.instance)
|
||||
|
||||
wgpu.InstanceRequestAdapter(state.instance, &{ compatibleSurface = state.surface }, on_adapter, nil)
|
||||
|
||||
on_adapter :: proc "c" (status: wgpu.RequestAdapterStatus, adapter: wgpu.Adapter, message: cstring, userdata: rawptr) {
|
||||
context = state.ctx
|
||||
if status != .Success || adapter == nil {
|
||||
fmt.panicf("request adapter failure: [%v] %s", status, message)
|
||||
}
|
||||
state.adapter = adapter
|
||||
wgpu.AdapterRequestDevice(adapter, nil, on_device)
|
||||
}
|
||||
|
||||
on_device :: proc "c" (status: wgpu.RequestDeviceStatus, device: wgpu.Device, message: cstring, userdata: rawptr) {
|
||||
context = state.ctx
|
||||
if status != .Success || device == nil {
|
||||
fmt.panicf("request device failure: [%v] %s", status, message)
|
||||
}
|
||||
state.device = device
|
||||
|
||||
width, height := os_get_render_bounds(&state.os)
|
||||
|
||||
state.config = wgpu.SurfaceConfiguration {
|
||||
device = state.device,
|
||||
usage = { .RenderAttachment },
|
||||
format = .BGRA8Unorm,
|
||||
width = width,
|
||||
height = height,
|
||||
presentMode = .Fifo,
|
||||
alphaMode = .Opaque,
|
||||
}
|
||||
wgpu.SurfaceConfigure(state.surface, &state.config)
|
||||
|
||||
state.queue = wgpu.DeviceGetQueue(state.device)
|
||||
|
||||
shader :: `
|
||||
@vertex
|
||||
fn vs_main(@builtin(vertex_index) in_vertex_index: u32) -> @builtin(position) vec4<f32> {
|
||||
let x = f32(i32(in_vertex_index) - 1);
|
||||
let y = f32(i32(in_vertex_index & 1u) * 2 - 1);
|
||||
return vec4<f32>(x, y, 0.0, 1.0);
|
||||
}
|
||||
|
||||
@fragment
|
||||
fn fs_main() -> @location(0) vec4<f32> {
|
||||
return vec4<f32>(1.0, 0.0, 0.0, 1.0);
|
||||
}`
|
||||
|
||||
state.module = wgpu.DeviceCreateShaderModule(state.device, &{
|
||||
nextInChain = &wgpu.ShaderModuleWGSLDescriptor{
|
||||
sType = .ShaderModuleWGSLDescriptor,
|
||||
code = shader,
|
||||
},
|
||||
})
|
||||
|
||||
state.pipeline_layout = wgpu.DeviceCreatePipelineLayout(state.device, &{})
|
||||
state.pipeline = wgpu.DeviceCreateRenderPipeline(state.device, &{
|
||||
layout = state.pipeline_layout,
|
||||
vertex = {
|
||||
module = state.module,
|
||||
entryPoint = "vs_main",
|
||||
},
|
||||
fragment = &{
|
||||
module = state.module,
|
||||
entryPoint = "fs_main",
|
||||
targetCount = 1,
|
||||
targets = &wgpu.ColorTargetState{
|
||||
format = .BGRA8Unorm,
|
||||
writeMask = wgpu.ColorWriteMaskFlags_All,
|
||||
},
|
||||
},
|
||||
primitive = {
|
||||
topology = .TriangleList,
|
||||
|
||||
},
|
||||
multisample = {
|
||||
count = 1,
|
||||
mask = 0xFFFFFFFF,
|
||||
},
|
||||
})
|
||||
|
||||
os_run(&state.os)
|
||||
}
|
||||
}
|
||||
|
||||
resize :: proc "c" () {
|
||||
context = state.ctx
|
||||
|
||||
state.config.width, state.config.height = os_get_render_bounds(&state.os)
|
||||
wgpu.SurfaceConfigure(state.surface, &state.config)
|
||||
}
|
||||
|
||||
frame :: proc "c" (dt: f32) {
|
||||
context = state.ctx
|
||||
|
||||
surface_texture := wgpu.SurfaceGetCurrentTexture(state.surface)
|
||||
switch surface_texture.status {
|
||||
case .Success:
|
||||
// All good, could check for `surface_texture.suboptimal` here.
|
||||
case .Timeout, .Outdated, .Lost:
|
||||
// Skip this frame, and re-configure surface.
|
||||
if surface_texture.texture != nil {
|
||||
wgpu.TextureRelease(surface_texture.texture)
|
||||
}
|
||||
resize()
|
||||
return
|
||||
case .OutOfMemory, .DeviceLost:
|
||||
// Fatal error
|
||||
fmt.panicf("[triangle] get_current_texture status=%v", surface_texture.status)
|
||||
}
|
||||
defer wgpu.TextureRelease(surface_texture.texture)
|
||||
|
||||
frame := wgpu.TextureCreateView(surface_texture.texture, nil)
|
||||
defer wgpu.TextureViewRelease(frame)
|
||||
|
||||
command_encoder := wgpu.DeviceCreateCommandEncoder(state.device, nil)
|
||||
defer wgpu.CommandEncoderRelease(command_encoder)
|
||||
|
||||
render_pass_encoder := wgpu.CommandEncoderBeginRenderPass(
|
||||
command_encoder, &{
|
||||
colorAttachmentCount = 1,
|
||||
colorAttachments = &wgpu.RenderPassColorAttachment{
|
||||
view = frame,
|
||||
loadOp = .Clear,
|
||||
storeOp = .Store,
|
||||
clearValue = { r = 0, g = 1, b = 0, a = 1 },
|
||||
},
|
||||
},
|
||||
)
|
||||
defer wgpu.RenderPassEncoderRelease(render_pass_encoder)
|
||||
|
||||
wgpu.RenderPassEncoderSetPipeline(render_pass_encoder, state.pipeline)
|
||||
wgpu.RenderPassEncoderDraw(render_pass_encoder, vertexCount=3, instanceCount=1, firstVertex=0, firstInstance=0)
|
||||
wgpu.RenderPassEncoderEnd(render_pass_encoder)
|
||||
|
||||
command_buffer := wgpu.CommandEncoderFinish(command_encoder, nil)
|
||||
defer wgpu.CommandBufferRelease(command_buffer)
|
||||
|
||||
wgpu.QueueSubmit(state.queue, { command_buffer })
|
||||
wgpu.SurfacePresent(state.surface)
|
||||
}
|
||||
|
||||
finish :: proc() {
|
||||
wgpu.RenderPipelineRelease(state.pipeline)
|
||||
wgpu.PipelineLayoutRelease(state.pipeline_layout)
|
||||
wgpu.ShaderModuleRelease(state.module)
|
||||
wgpu.QueueRelease(state.queue)
|
||||
wgpu.DeviceRelease(state.device)
|
||||
wgpu.AdapterRelease(state.adapter)
|
||||
wgpu.SurfaceRelease(state.surface)
|
||||
wgpu.InstanceRelease(state.instance)
|
||||
}
|
||||
Vendored
+55
@@ -0,0 +1,55 @@
|
||||
//+build !js
|
||||
package vendor_wgpu_example_triangle
|
||||
|
||||
import "core:time"
|
||||
|
||||
import "vendor:glfw"
|
||||
import "vendor:wgpu"
|
||||
import "vendor:wgpu/glfwglue"
|
||||
|
||||
OS :: struct {
|
||||
window: glfw.WindowHandle,
|
||||
}
|
||||
|
||||
os_init :: proc(os: ^OS) {
|
||||
if !glfw.Init() {
|
||||
panic("[glfw] init failure")
|
||||
}
|
||||
|
||||
glfw.WindowHint(glfw.CLIENT_API, glfw.NO_API)
|
||||
os.window = glfw.CreateWindow(960, 540, "WGPU Native Triangle", nil, nil)
|
||||
|
||||
glfw.SetFramebufferSizeCallback(os.window, size_callback)
|
||||
}
|
||||
|
||||
os_run :: proc(os: ^OS) {
|
||||
dt: f32
|
||||
|
||||
for !glfw.WindowShouldClose(os.window) {
|
||||
start := time.tick_now()
|
||||
|
||||
glfw.PollEvents()
|
||||
frame(dt)
|
||||
|
||||
dt = f32(time.duration_seconds(time.tick_since(start)))
|
||||
}
|
||||
|
||||
finish()
|
||||
|
||||
glfw.DestroyWindow(os.window)
|
||||
glfw.Terminate()
|
||||
}
|
||||
|
||||
os_get_render_bounds :: proc(os: ^OS) -> (width, height: u32) {
|
||||
iw, ih := glfw.GetWindowSize(os.window)
|
||||
return u32(iw), u32(ih)
|
||||
}
|
||||
|
||||
os_get_surface :: proc(os: ^OS, instance: wgpu.Instance) -> wgpu.Surface {
|
||||
return glfwglue.GetSurface(instance, os.window)
|
||||
}
|
||||
|
||||
@(private="file")
|
||||
size_callback :: proc "c" (window: glfw.WindowHandle, width, height: i32) {
|
||||
resize()
|
||||
}
|
||||
Vendored
+60
@@ -0,0 +1,60 @@
|
||||
package vendor_wgpu_example_triangle
|
||||
|
||||
import "vendor:wgpu"
|
||||
import "vendor:wasm/js"
|
||||
|
||||
OS :: struct {
|
||||
initialized: bool,
|
||||
}
|
||||
|
||||
@(private="file")
|
||||
g_os: ^OS
|
||||
|
||||
os_init :: proc(os: ^OS) {
|
||||
g_os = os
|
||||
assert(js.add_window_event_listener(.Resize, nil, size_callback))
|
||||
}
|
||||
|
||||
// NOTE: frame loop is done by the runtime.js repeatedly calling `step`.
|
||||
os_run :: proc(os: ^OS) {
|
||||
os.initialized = true
|
||||
}
|
||||
|
||||
os_get_render_bounds :: proc(os: ^OS) -> (width, height: u32) {
|
||||
rect := js.get_bounding_client_rect("body")
|
||||
return u32(rect.width), u32(rect.height)
|
||||
}
|
||||
|
||||
os_get_surface :: proc(os: ^OS, instance: wgpu.Instance) -> wgpu.Surface {
|
||||
return wgpu.InstanceCreateSurface(
|
||||
instance,
|
||||
&wgpu.SurfaceDescriptor{
|
||||
nextInChain = &wgpu.SurfaceDescriptorFromCanvasHTMLSelector{
|
||||
sType = .SurfaceDescriptorFromCanvasHTMLSelector,
|
||||
selector = "#wgpu-canvas",
|
||||
},
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@(private="file", export)
|
||||
step :: proc(dt: f32) -> bool {
|
||||
if !g_os.initialized {
|
||||
return true
|
||||
}
|
||||
|
||||
frame(dt)
|
||||
return true
|
||||
}
|
||||
|
||||
@(private="file", fini)
|
||||
os_fini :: proc() {
|
||||
js.remove_window_event_listener(.Resize, nil, size_callback)
|
||||
|
||||
finish()
|
||||
}
|
||||
|
||||
@(private="file")
|
||||
size_callback :: proc(e: js.Event) {
|
||||
resize()
|
||||
}
|
||||
Vendored
+23
@@ -0,0 +1,23 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" style="height: 100%;">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>WGPU WASM Triangle</title>
|
||||
</head>
|
||||
<body id="body" style="height: 100%; padding: 0; margin: 0; overflow: hidden;">
|
||||
<canvas id="wgpu-canvas"></canvas>
|
||||
|
||||
<script type="text/javascript" src="runtime.js"></script>
|
||||
<script type="text/javascript" src="wgpu.js"></script>
|
||||
<script type="text/javascript">
|
||||
const mem = new WebAssembly.Memory({ initial: 2000, maximum: 65536, shared: false });
|
||||
const memInterface = new odin.WasmMemoryInterface();
|
||||
memInterface.setMemory(mem);
|
||||
|
||||
const wgpuInterface = new odin.WebGPUInterface(memInterface);
|
||||
|
||||
odin.runWasm("triangle.wasm", null, { wgpu: wgpuInterface.getInterface() }, memInterface, /*intSize=8*/);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Vendored
+6
@@ -0,0 +1,6 @@
|
||||
//+build !linux
|
||||
//+build !windows
|
||||
//+build !darwin
|
||||
package wgpu_glfw_glue
|
||||
|
||||
#panic("package wgpu/glfwglue is not supported on the current target")
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
package wgpu_glfw_glue
|
||||
|
||||
import "vendor:glfw"
|
||||
import "vendor:wgpu"
|
||||
import CA "vendor:darwin/QuartzCore"
|
||||
|
||||
GetSurface :: proc(instance: wgpu.Instance, window: glfw.WindowHandle) -> wgpu.Surface {
|
||||
ns_window := glfw.GetCocoaWindow(window)
|
||||
ns_window->contentView()->setWantsLayer(true)
|
||||
metal_layer := CA.MetalLayer_layer()
|
||||
ns_window->contentView()->setLayer(metal_layer)
|
||||
return wgpu.InstanceCreateSurface(
|
||||
instance,
|
||||
&wgpu.SurfaceDescriptor{
|
||||
nextInChain = &wgpu.SurfaceDescriptorFromMetalLayer{
|
||||
chain = wgpu.ChainedStruct{
|
||||
sType = .SurfaceDescriptorFromMetalLayer,
|
||||
},
|
||||
layer = rawptr(metal_layer),
|
||||
},
|
||||
},
|
||||
)
|
||||
}
|
||||
Vendored
+43
@@ -0,0 +1,43 @@
|
||||
package wgpu_glfw_glue
|
||||
|
||||
import "vendor:glfw"
|
||||
import "vendor:wgpu"
|
||||
|
||||
// GLFW needs to be compiled with wayland support for this to work.
|
||||
SUPPORT_WAYLAND :: #config(WGPU_GFLW_GLUE_SUPPORT_WAYLAND, false)
|
||||
|
||||
GetSurface :: proc(instance: wgpu.Instance, window: glfw.WindowHandle) -> wgpu.Surface {
|
||||
when SUPPORT_WAYLAND {
|
||||
if glfw.GetPlatform() == glfw.PLATFORM_WAYLAND {
|
||||
display := glfw.GetWaylandDisplay()
|
||||
surface := glfw.GetWaylandWindow(window)
|
||||
return wgpu.InstanceCreateSurface(
|
||||
instance,
|
||||
&wgpu.SurfaceDescriptor{
|
||||
nextInChain = &wgpu.SurfaceDescriptorFromWaylandSurface{
|
||||
chain = {
|
||||
sType = .SurfaceDescriptorFromWaylandSurface,
|
||||
},
|
||||
display = display,
|
||||
surface = surface,
|
||||
},
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
display := glfw.GetX11Display()
|
||||
window := glfw.GetX11Window(window)
|
||||
return wgpu.InstanceCreateSurface(
|
||||
instance,
|
||||
&wgpu.SurfaceDescriptor{
|
||||
nextInChain = &wgpu.SurfaceDescriptorFromXlibWindow{
|
||||
chain = {
|
||||
sType = .SurfaceDescriptorFromXlibWindow,
|
||||
},
|
||||
display = display,
|
||||
window = u64(window),
|
||||
},
|
||||
},
|
||||
)
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
package wgpu_glfw_glue
|
||||
|
||||
import win "core:sys/windows"
|
||||
|
||||
import "vendor:glfw"
|
||||
import "vendor:wgpu"
|
||||
|
||||
GetSurface :: proc(instance: wgpu.Instance, window: glfw.WindowHandle) -> wgpu.Surface {
|
||||
hwnd := glfw.GetWin32Window(window)
|
||||
hinstance := win.GetModuleHandleW(nil)
|
||||
return wgpu.InstanceCreateSurface(
|
||||
instance,
|
||||
&wgpu.SurfaceDescriptor{
|
||||
nextInChain = &wgpu.SurfaceDescriptorFromWindowsHWND{
|
||||
chain = wgpu.ChainedStruct{
|
||||
sType = .SurfaceDescriptorFromWindowsHWND,
|
||||
},
|
||||
hinstance = rawptr(hinstance),
|
||||
hwnd = rawptr(hwnd),
|
||||
},
|
||||
},
|
||||
)
|
||||
}
|
||||
Vendored
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Vendored
+2916
File diff suppressed because it is too large
Load Diff
Vendored
+1636
File diff suppressed because it is too large
Load Diff
Vendored
+26
@@ -0,0 +1,26 @@
|
||||
package wgpu
|
||||
|
||||
import "base:runtime"
|
||||
|
||||
g_context: runtime.Context
|
||||
|
||||
@(private="file", init)
|
||||
wgpu_init_allocator :: proc() {
|
||||
if g_context.allocator.procedure == nil {
|
||||
g_context = runtime.default_context()
|
||||
}
|
||||
}
|
||||
|
||||
@(private="file", export)
|
||||
wgpu_alloc :: proc "contextless" (size: i32) -> [^]byte {
|
||||
context = g_context
|
||||
bytes, err := runtime.mem_alloc(int(size), 16)
|
||||
assert(err == nil, "wgpu_alloc failed")
|
||||
return raw_data(bytes)
|
||||
}
|
||||
|
||||
@(private="file", export)
|
||||
wgpu_free :: proc "contextless" (ptr: rawptr) {
|
||||
context = g_context
|
||||
assert(free(ptr) == nil, "wgpu_free failed")
|
||||
}
|
||||
Vendored
+75
@@ -0,0 +1,75 @@
|
||||
//+build !js
|
||||
package wgpu
|
||||
|
||||
BINDINGS_VERSION :: [4]u8{0, 19, 4, 1}
|
||||
BINDINGS_VERSION_STRING :: "0.19.4.1"
|
||||
|
||||
@(private="file", init)
|
||||
wgpu_native_version_check :: proc() {
|
||||
v := (transmute([4]u8)GetVersion()).wzyx
|
||||
|
||||
if v != BINDINGS_VERSION {
|
||||
buf: [1024]byte
|
||||
n := copy(buf[:], "wgpu-native version mismatch: ")
|
||||
n += copy(buf[n:], "bindings are for version ")
|
||||
n += copy(buf[n:], BINDINGS_VERSION_STRING)
|
||||
n += copy(buf[n:], ", but a different version is linked")
|
||||
panic(string(buf[:n]))
|
||||
}
|
||||
}
|
||||
|
||||
@(link_prefix="wgpu")
|
||||
foreign {
|
||||
@(link_name="wgpuGenerateReport")
|
||||
RawGenerateReport :: proc(instance: Instance, report: ^GlobalReport) ---
|
||||
@(link_name="wgpuInstanceEnumerateAdapters")
|
||||
RawInstanceEnumerateAdapters :: proc(instance: Instance, /* NULLABLE */ options: /* const */ ^InstanceEnumerateAdapterOptions, adapters: [^]Adapter) -> uint ---
|
||||
|
||||
@(link_name="wgpuQueueSubmitForIndex")
|
||||
RawQueueSubmitForIndex :: proc(queue: Queue, commandCount: uint, commands: [^]CommandBuffer) -> SubmissionIndex ---
|
||||
|
||||
// Returns true if the queue is empty, or false if there are more queue submissions still in flight.
|
||||
@(link_name="wgpuDevicePoll")
|
||||
RawDevicePoll :: proc(device: Device, wait: b32, /* NULLABLE */ wrappedSubmissionIndex: /* const */ ^WrappedSubmissionIndex) -> b32 ---
|
||||
|
||||
SetLogCallback :: proc "odin" (callback: LogCallback) ---
|
||||
|
||||
SetLogLevel :: proc(level: LogLevel) ---
|
||||
|
||||
GetVersion :: proc() -> u32 ---
|
||||
|
||||
RenderPassEncoderSetPushConstants :: proc(encoder: RenderPassEncoder, stages: ShaderStageFlags, offset: u32, sizeBytes: u32, data: cstring) ---
|
||||
|
||||
RenderPassEncoderMultiDrawIndirect :: proc(encoder: RenderPassEncoder, buffer: Buffer, offset: u64, count: u32) ---
|
||||
RenderPassEncoderMultiDrawIndexedIndirect :: proc(encoder: RenderPassEncoder, buffer: Buffer, offset: u64, count: u32) ---
|
||||
|
||||
RenderPassEncoderMultiDrawIndirectCount :: proc(encoder: RenderPassEncoder, buffer: Buffer, offset: u64, count_buffer: Buffer, count_buffer_offset: u64, max_count: u32) ---
|
||||
RenderPassEncoderMultiDrawIndexedIndirectCount :: proc(encoder: RenderPassEncoder, buffer: Buffer, offset: u64, count_buffer: Buffer, count_buffer_offset: u64, max_count: u32) ---
|
||||
|
||||
ComputePassEncoderBeginPipelineStatisticsQuery :: proc(computePassEncoder: ComputePassEncoder, querySet: QuerySet, queryIndex: u32) ---
|
||||
ComputePassEncoderEndPipelineStatisticsQuery :: proc(computePassEncoder: ComputePassEncoder) ---
|
||||
RenderPassEncoderBeginPipelineStatisticsQuery :: proc(renderPassEncoder: RenderPassEncoder, querySet: QuerySet, queryIndex: u32) ---
|
||||
RenderPassEncoderEndPipelineStatisticsQuery :: proc(renderPassEncoder: RenderPassEncoder) ---
|
||||
}
|
||||
|
||||
GenerateReport :: proc(instance: Instance) -> (report: GlobalReport) {
|
||||
RawGenerateReport(instance, &report)
|
||||
return
|
||||
}
|
||||
|
||||
InstanceEnumerateAdapters :: proc(instance: Instance, options: ^InstanceEnumerateAdapterOptions = nil, allocator := context.allocator) -> (adapters: []Adapter) {
|
||||
count := RawInstanceEnumerateAdapters(instance, options, nil)
|
||||
adapters = make([]Adapter, count, allocator)
|
||||
RawInstanceEnumerateAdapters(instance, options, raw_data(adapters))
|
||||
return
|
||||
}
|
||||
|
||||
QueueSubmitForIndex :: proc(queue: Queue, commands: []CommandBuffer) -> SubmissionIndex {
|
||||
return RawQueueSubmitForIndex(queue, len(commands), raw_data(commands))
|
||||
}
|
||||
|
||||
DevicePoll :: proc(device: Device, wait: b32) -> (wrappedSubmissionIndex: WrappedSubmissionIndex, ok: bool) {
|
||||
ok = bool(RawDevicePoll(device, wait, &wrappedSubmissionIndex))
|
||||
return
|
||||
}
|
||||
|
||||
Vendored
+212
@@ -0,0 +1,212 @@
|
||||
package wgpu
|
||||
|
||||
import "base:runtime"
|
||||
|
||||
LogLevel :: enum i32 {
|
||||
Off,
|
||||
Error,
|
||||
Warn,
|
||||
Info,
|
||||
Debug,
|
||||
Trace,
|
||||
}
|
||||
|
||||
InstanceBackend :: enum i32 {
|
||||
Vulkan,
|
||||
GL,
|
||||
Metal,
|
||||
DX12,
|
||||
DX11,
|
||||
BrowserWebGPU,
|
||||
}
|
||||
InstanceBackendFlags :: bit_set[InstanceBackend; Flags]
|
||||
InstanceBackendFlags_All :: InstanceBackendFlags{}
|
||||
InstanceBackendFlags_Primary :: InstanceBackendFlags{ .Vulkan, .Metal, .DX12, .BrowserWebGPU }
|
||||
InstanceBackendFlags_Secondary :: InstanceBackendFlags{ .GL, .DX11 }
|
||||
|
||||
InstanceFlag :: enum i32 {
|
||||
Debug,
|
||||
Validation,
|
||||
DiscardHalLabels,
|
||||
}
|
||||
InstanceFlags :: bit_set[InstanceFlag; Flags]
|
||||
InstanceFlags_Default :: InstanceFlags{}
|
||||
|
||||
Dx12Compiler :: enum i32 {
|
||||
Undefined,
|
||||
Fxc,
|
||||
Dxc,
|
||||
}
|
||||
|
||||
Gles3MinorVersion :: enum i32 {
|
||||
Automatic,
|
||||
Version0,
|
||||
Version1,
|
||||
Version2,
|
||||
}
|
||||
|
||||
PipelineStatisticName :: enum i32 {
|
||||
VertexShaderInvocations,
|
||||
ClipperInvocations,
|
||||
ClipperPrimitivesOut,
|
||||
FragmentShaderInvocations,
|
||||
ComputeShaderInvocations,
|
||||
}
|
||||
|
||||
InstanceExtras :: struct {
|
||||
using chain: ChainedStruct,
|
||||
backends: InstanceBackendFlags,
|
||||
flags: InstanceFlags,
|
||||
dx12ShaderCompiler: Dx12Compiler,
|
||||
gles3MinorVersion: Gles3MinorVersion,
|
||||
dxilPath: cstring,
|
||||
dxcPath: cstring,
|
||||
}
|
||||
|
||||
DeviceExtras :: struct {
|
||||
using chain: ChainedStruct,
|
||||
tracePath: cstring,
|
||||
}
|
||||
|
||||
NativeLimits :: struct {
|
||||
maxPushConstantSize: u32,
|
||||
maxNonSamplerBindings: u32,
|
||||
}
|
||||
|
||||
RequiredLimitsExtras :: struct {
|
||||
using chain: ChainedStruct,
|
||||
limits: NativeLimits,
|
||||
}
|
||||
|
||||
SupportedLimitsExtras :: struct {
|
||||
using chain: ChainedStruct,
|
||||
limits: NativeLimits,
|
||||
}
|
||||
|
||||
PushConstantRange :: struct {
|
||||
stages: ShaderStageFlags,
|
||||
start: u32,
|
||||
end: u32,
|
||||
}
|
||||
|
||||
PipelineLayoutExtras :: struct {
|
||||
using chain: ChainedStruct,
|
||||
pushConstantRangeCount: uint,
|
||||
pushConstantRanges: [^]PushConstantRange `fmt:"v,pushConstantRangeCount"`,
|
||||
}
|
||||
|
||||
SubmissionIndex :: distinct u64
|
||||
|
||||
WrappedSubmissionIndex :: struct {
|
||||
queue: Queue,
|
||||
submissionIndex: SubmissionIndex,
|
||||
}
|
||||
|
||||
ShaderDefine :: struct {
|
||||
name: cstring,
|
||||
value: cstring,
|
||||
}
|
||||
|
||||
ShaderModuleGLSLDescriptor :: struct {
|
||||
using chain: ChainedStruct,
|
||||
stage: ShaderStage,
|
||||
code: cstring,
|
||||
defineCount: uint,
|
||||
defines: [^]ShaderDefine `fmt:"v,defineCount"`,
|
||||
}
|
||||
|
||||
RegistryReport :: struct {
|
||||
numAllocated: uint,
|
||||
numKeptFromUser: uint,
|
||||
numReleasedFromUser: uint,
|
||||
numErrors: uint,
|
||||
elementSize: uint,
|
||||
}
|
||||
|
||||
HubReport :: struct {
|
||||
adapters: RegistryReport,
|
||||
devices: RegistryReport,
|
||||
queues: RegistryReport,
|
||||
pipelineLayouts: RegistryReport,
|
||||
shaderModules: RegistryReport,
|
||||
bindGroupLayouts: RegistryReport,
|
||||
bindGroups: RegistryReport,
|
||||
commandBuffers: RegistryReport,
|
||||
renderBundles: RegistryReport,
|
||||
renderPipelines: RegistryReport,
|
||||
computePipelines: RegistryReport,
|
||||
querySets: RegistryReport,
|
||||
buffers: RegistryReport,
|
||||
textures: RegistryReport,
|
||||
textureViews: RegistryReport,
|
||||
samplers: RegistryReport,
|
||||
}
|
||||
|
||||
GlobalReport :: struct {
|
||||
surfaces: RegistryReport,
|
||||
backendType: BackendType,
|
||||
vulkan: HubReport,
|
||||
metal: HubReport,
|
||||
dx12: HubReport,
|
||||
gl: HubReport,
|
||||
}
|
||||
|
||||
InstanceEnumerateAdapterOptions :: struct {
|
||||
nextInChain: ^ChainedStruct,
|
||||
backends: InstanceBackendFlags,
|
||||
}
|
||||
|
||||
BindGroupEntryExtras :: struct {
|
||||
using chain: ChainedStruct,
|
||||
buffers: [^]Buffer `fmt:"v,bufferCount"`,
|
||||
bufferCount: uint,
|
||||
samplers: [^]Sampler `fmt:"v,samplerCount"`,
|
||||
samplerCount: uint,
|
||||
textureViews: [^]TextureView `fmt:"v,textureViewCount"`,
|
||||
textureViewCount: uint,
|
||||
}
|
||||
|
||||
BindGroupLayoutEntryExtras :: struct {
|
||||
using chain: ChainedStruct,
|
||||
count: u32,
|
||||
}
|
||||
|
||||
QuerySetDescriptorExtras :: struct {
|
||||
using chain: ChainedStruct,
|
||||
pipelineStatistics: [^]PipelineStatisticName `fmt:"v,pipelineStatisticCount"`,
|
||||
pipelineStatisticCount: uint,
|
||||
}
|
||||
|
||||
SurfaceConfigurationExtras :: struct {
|
||||
using chain: ChainedStruct,
|
||||
desiredMaximumFrameLatency: i32,
|
||||
}
|
||||
|
||||
LogCallback :: #type proc "odin" (level: LogLevel, message: cstring)
|
||||
|
||||
// Wrappers
|
||||
|
||||
ConvertOdinToWGPULogLevel :: proc(level: runtime.Logger_Level) -> LogLevel {
|
||||
switch {
|
||||
case level < .Debug: return .Trace
|
||||
case level < .Info: return .Debug
|
||||
case level < .Warning: return .Info
|
||||
case level < .Error: return .Warn
|
||||
case: return .Error
|
||||
}
|
||||
}
|
||||
|
||||
ConvertWGPUToOdinLogLevel :: proc(level: LogLevel) -> runtime.Logger_Level {
|
||||
switch level {
|
||||
case .Off, .Trace, .Debug: return .Debug
|
||||
case .Info: return .Info
|
||||
case .Warn: return .Warning
|
||||
case .Error: return .Error
|
||||
case: return .Error
|
||||
}
|
||||
}
|
||||
|
||||
ConvertLogLevel :: proc {
|
||||
ConvertOdinToWGPULogLevel,
|
||||
ConvertWGPUToOdinLogLevel,
|
||||
}
|
||||
Vendored
+3
@@ -24,6 +24,9 @@ Cursor :: XID
|
||||
Colormap :: XID
|
||||
GContext :: XID
|
||||
|
||||
RRCrtc :: XID
|
||||
RROutput :: XID
|
||||
|
||||
KeyCode :: u8
|
||||
|
||||
/* ---- X11/Xlib.h ---------------------------------------------------------*/
|
||||
|
||||
Reference in New Issue
Block a user