mirror of
https://github.com/Ed94/Odin.git
synced 2026-06-18 11:52:22 -07:00
Merge branch 'odin-lang:master' into badaxis/Windows-Audio&Winmm
This commit is contained in:
@@ -133,12 +133,10 @@ pop_safe :: proc(pq: ^$Q/Priority_Queue($T), loc := #caller_location) -> (value:
|
||||
remove :: proc(pq: ^$Q/Priority_Queue($T), i: int) -> (value: T, ok: bool) {
|
||||
n := builtin.len(pq.queue)
|
||||
if 0 <= i && i < n {
|
||||
if n != i {
|
||||
pq.swap(pq.queue[:], i, n)
|
||||
_shift_down(pq, i, n)
|
||||
_shift_up(pq, i)
|
||||
}
|
||||
value, ok = builtin.pop_safe(&pq.queue)
|
||||
pq.swap(pq.queue[:], i, n-1)
|
||||
_shift_down(pq, i, n-1)
|
||||
_shift_up(pq, i)
|
||||
value, ok = builtin.pop(&pq.queue), true
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import "core:sys/info"
|
||||
// is_supported returns true iff hardware accelerated AES
|
||||
// is supported.
|
||||
is_supported :: proc "contextless" () -> bool {
|
||||
features, ok := info.cpu_features.?
|
||||
features, ok := info.cpu.features.?
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -227,7 +227,7 @@ is_performant :: proc "contextless" () -> bool {
|
||||
req_features :: info.CPU_Features{.V}
|
||||
}
|
||||
|
||||
features, ok := info.cpu_features.?
|
||||
features, ok := info.cpu.features.?
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ _VEC_TWO: simd.u64x4 : {2, 0, 2, 0}
|
||||
is_performant :: proc "contextless" () -> bool {
|
||||
req_features :: info.CPU_Features{.avx, .avx2}
|
||||
|
||||
features, ok := info.cpu_features.?
|
||||
features, ok := info.cpu.features.?
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ K_15 :: simd.u64x2{0xa4506ceb90befffa, 0xc67178f2bef9a3f7}
|
||||
// is_hardware_accelerated_256 returns true iff hardware accelerated
|
||||
// SHA-224/SHA-256 is supported.
|
||||
is_hardware_accelerated_256 :: proc "contextless" () -> bool {
|
||||
features, ok := info.cpu_features.?
|
||||
features, ok := info.cpu.features.?
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -2,10 +2,12 @@
|
||||
#+build !orca
|
||||
package log
|
||||
|
||||
import "core:encoding/ansi"
|
||||
import "base:runtime"
|
||||
import "core:fmt"
|
||||
import "core:strings"
|
||||
import "core:os"
|
||||
import "core:terminal"
|
||||
import "core:terminal/ansi"
|
||||
import "core:time"
|
||||
|
||||
Level_Headers := [?]string{
|
||||
@@ -37,11 +39,36 @@ File_Console_Logger_Data :: struct {
|
||||
ident: string,
|
||||
}
|
||||
|
||||
@(private) global_subtract_stdout_options: Options
|
||||
@(private) global_subtract_stderr_options: Options
|
||||
|
||||
@(init, private)
|
||||
init_standard_stream_status :: proc() {
|
||||
// NOTE(Feoramund): While it is technically possible for these streams to
|
||||
// be redirected during the runtime of the program, the cost of checking on
|
||||
// every single log message is not worth it to support such an
|
||||
// uncommonly-used feature.
|
||||
if terminal.color_enabled {
|
||||
// This is done this way because it's possible that only one of these
|
||||
// streams could be redirected to a file.
|
||||
if !terminal.is_terminal(os.stdout) {
|
||||
global_subtract_stdout_options = {.Terminal_Color}
|
||||
}
|
||||
if !terminal.is_terminal(os.stderr) {
|
||||
global_subtract_stderr_options = {.Terminal_Color}
|
||||
}
|
||||
} else {
|
||||
// Override any terminal coloring.
|
||||
global_subtract_stdout_options = {.Terminal_Color}
|
||||
global_subtract_stderr_options = {.Terminal_Color}
|
||||
}
|
||||
}
|
||||
|
||||
create_file_logger :: proc(h: os.Handle, lowest := Level.Debug, opt := Default_File_Logger_Opts, ident := "", allocator := context.allocator) -> Logger {
|
||||
data := new(File_Console_Logger_Data, allocator)
|
||||
data.file_handle = h
|
||||
data.ident = ident
|
||||
return Logger{file_console_logger_proc, data, lowest, opt}
|
||||
return Logger{file_logger_proc, data, lowest, opt}
|
||||
}
|
||||
|
||||
destroy_file_logger :: proc(log: Logger, allocator := context.allocator) {
|
||||
@@ -56,19 +83,15 @@ create_console_logger :: proc(lowest := Level.Debug, opt := Default_Console_Logg
|
||||
data := new(File_Console_Logger_Data, allocator)
|
||||
data.file_handle = os.INVALID_HANDLE
|
||||
data.ident = ident
|
||||
return Logger{file_console_logger_proc, data, lowest, opt}
|
||||
return Logger{console_logger_proc, data, lowest, opt}
|
||||
}
|
||||
|
||||
destroy_console_logger :: proc(log: Logger, allocator := context.allocator) {
|
||||
free(log.data, allocator)
|
||||
}
|
||||
|
||||
file_console_logger_proc :: proc(logger_data: rawptr, level: Level, text: string, options: Options, location := #caller_location) {
|
||||
data := cast(^File_Console_Logger_Data)logger_data
|
||||
h: os.Handle = os.stdout if level <= Level.Error else os.stderr
|
||||
if data.file_handle != os.INVALID_HANDLE {
|
||||
h = data.file_handle
|
||||
}
|
||||
@(private)
|
||||
_file_console_logger_proc :: proc(h: os.Handle, ident: string, level: Level, text: string, options: Options, location: runtime.Source_Code_Location) {
|
||||
backing: [1024]byte //NOTE(Hoej): 1024 might be too much for a header backing, unless somebody has really long paths.
|
||||
buf := strings.builder_from_bytes(backing[:])
|
||||
|
||||
@@ -86,13 +109,32 @@ file_console_logger_proc :: proc(logger_data: rawptr, level: Level, text: string
|
||||
fmt.sbprintf(&buf, "[{}] ", os.current_thread_id())
|
||||
}
|
||||
|
||||
if data.ident != "" {
|
||||
fmt.sbprintf(&buf, "[%s] ", data.ident)
|
||||
if ident != "" {
|
||||
fmt.sbprintf(&buf, "[%s] ", ident)
|
||||
}
|
||||
//TODO(Hoej): When we have better atomics and such, make this thread-safe
|
||||
fmt.fprintf(h, "%s%s\n", strings.to_string(buf), text)
|
||||
}
|
||||
|
||||
file_logger_proc :: proc(logger_data: rawptr, level: Level, text: string, options: Options, location := #caller_location) {
|
||||
data := cast(^File_Console_Logger_Data)logger_data
|
||||
_file_console_logger_proc(data.file_handle, data.ident, level, text, options, location)
|
||||
}
|
||||
|
||||
console_logger_proc :: proc(logger_data: rawptr, level: Level, text: string, options: Options, location := #caller_location) {
|
||||
options := options
|
||||
data := cast(^File_Console_Logger_Data)logger_data
|
||||
h: os.Handle = ---
|
||||
if level < Level.Error {
|
||||
h = os.stdout
|
||||
options -= global_subtract_stdout_options
|
||||
} else {
|
||||
h = os.stderr
|
||||
options -= global_subtract_stderr_options
|
||||
}
|
||||
_file_console_logger_proc(h, data.ident, level, text, options, location)
|
||||
}
|
||||
|
||||
do_level_header :: proc(opts: Options, str: ^strings.Builder, level: Level) {
|
||||
|
||||
RESET :: ansi.CSI + ansi.RESET + ansi.SGR
|
||||
|
||||
@@ -280,7 +280,7 @@ int_atoi :: proc(res: ^Int, input: string, radix := i8(10), allocator := context
|
||||
}
|
||||
|
||||
pos := ch - '+'
|
||||
if RADIX_TABLE_REVERSE_SIZE <= pos {
|
||||
if RADIX_TABLE_REVERSE_SIZE <= u32(pos) {
|
||||
break
|
||||
}
|
||||
y := RADIX_TABLE_REVERSE[pos]
|
||||
|
||||
@@ -477,7 +477,7 @@ block_mark_as_free :: proc(block: ^Block_Header) {
|
||||
}
|
||||
|
||||
@(private, no_sanitize_address)
|
||||
block_mark_as_used :: proc(block: ^Block_Header, ) {
|
||||
block_mark_as_used :: proc(block: ^Block_Header) {
|
||||
next := block_next(block)
|
||||
block_set_prev_used(next)
|
||||
block_set_used(block)
|
||||
|
||||
@@ -207,3 +207,21 @@ _get_full_path :: proc(fd: linux.Fd, allocator: runtime.Allocator) -> (fullpath:
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
_get_absolute_path :: proc(path: string, allocator: runtime.Allocator) -> (absolute_path: string, err: Error) {
|
||||
rel := path
|
||||
if rel == "" {
|
||||
rel = "."
|
||||
}
|
||||
|
||||
temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
|
||||
|
||||
fd, errno := linux.open(clone_to_cstring(path, temp_allocator) or_return, {})
|
||||
if errno != nil {
|
||||
err = _get_platform_error(errno)
|
||||
return
|
||||
}
|
||||
defer linux.close(fd)
|
||||
|
||||
return _get_full_path(fd, allocator)
|
||||
}
|
||||
|
||||
@@ -123,3 +123,20 @@ _set_working_directory :: proc(dir: string) -> (err: Error) {
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
_get_absolute_path :: proc(path: string, allocator: runtime.Allocator) -> (absolute_path: string, err: Error) {
|
||||
rel := path
|
||||
if rel == "" {
|
||||
rel = "."
|
||||
}
|
||||
temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
|
||||
rel_cstr := clone_to_cstring(rel, temp_allocator) or_return
|
||||
path_ptr := posix.realpath(rel_cstr, nil)
|
||||
if path_ptr == nil {
|
||||
return "", Platform_Error(posix.errno())
|
||||
}
|
||||
defer posix.free(path_ptr)
|
||||
|
||||
path_str := clone_string(string(path_ptr), allocator) or_return
|
||||
return path_str, nil
|
||||
}
|
||||
|
||||
@@ -4,10 +4,6 @@ package os2
|
||||
|
||||
// This implementation is for all systems that have POSIX-compliant filesystem paths.
|
||||
|
||||
import "base:runtime"
|
||||
import "core:strings"
|
||||
import "core:sys/posix"
|
||||
|
||||
_are_paths_identical :: proc(a, b: string) -> (identical: bool) {
|
||||
return a == b
|
||||
}
|
||||
@@ -26,23 +22,6 @@ _is_absolute_path :: proc(path: string) -> bool {
|
||||
return len(path) > 0 && _is_path_separator(path[0])
|
||||
}
|
||||
|
||||
_get_absolute_path :: proc(path: string, allocator: runtime.Allocator) -> (absolute_path: string, err: Error) {
|
||||
rel := path
|
||||
if rel == "" {
|
||||
rel = "."
|
||||
}
|
||||
temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
|
||||
rel_cstr := strings.clone_to_cstring(rel, temp_allocator)
|
||||
path_ptr := posix.realpath(rel_cstr, nil)
|
||||
if path_ptr == nil {
|
||||
return "", Platform_Error(posix.errno())
|
||||
}
|
||||
defer posix.free(path_ptr)
|
||||
|
||||
path_str := strings.clone(string(path_ptr), allocator)
|
||||
return path_str, nil
|
||||
}
|
||||
|
||||
_get_relative_path_handle_start :: proc(base, target: string) -> bool {
|
||||
base_rooted := len(base) > 0 && _is_path_separator(base[0])
|
||||
target_rooted := len(target) > 0 && _is_path_separator(target[0])
|
||||
|
||||
@@ -204,21 +204,21 @@ accept_nil :: proc "contextless" (s: Fd) -> (Fd, Errno) {
|
||||
accept :: proc { accept_T, accept_nil }
|
||||
|
||||
getsockname_or_peername :: proc "contextless" (s: Fd, sockaddr: ^$T, is_peer: bool) -> Errno {
|
||||
// sockaddr must contain a valid pointer, or this will segfault because
|
||||
// we're telling the syscall that there's memory available to write to.
|
||||
addrlen: socklen_t = size_of(T)
|
||||
// sockaddr must contain a valid pointer, or this will segfault because
|
||||
// we're telling the syscall that there's memory available to write to.
|
||||
addrlen: socklen_t = size_of(T)
|
||||
|
||||
result, ok := intrinsics.syscall_bsd(
|
||||
is_peer ? SYS_getpeername : SYS_getsockname,
|
||||
cast(uintptr)s,
|
||||
cast(uintptr)sockaddr,
|
||||
cast(uintptr)&addrlen)
|
||||
result, ok := intrinsics.syscall_bsd(
|
||||
is_peer ? SYS_getpeername : SYS_getsockname,
|
||||
cast(uintptr)s,
|
||||
cast(uintptr)sockaddr,
|
||||
cast(uintptr)&addrlen)
|
||||
|
||||
if !ok {
|
||||
return cast(Errno)result
|
||||
}
|
||||
if !ok {
|
||||
return cast(Errno)result
|
||||
}
|
||||
|
||||
return nil
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get name of connected peer
|
||||
|
||||
@@ -40,9 +40,13 @@ CPU_Feature :: enum u64 {
|
||||
}
|
||||
|
||||
CPU_Features :: distinct bit_set[CPU_Feature; u64]
|
||||
|
||||
cpu_features: Maybe(CPU_Features)
|
||||
cpu_name: Maybe(string)
|
||||
CPU :: struct {
|
||||
name: Maybe(string),
|
||||
features: Maybe(CPU_Features),
|
||||
physical_cores: int,
|
||||
logical_cores: int,
|
||||
}
|
||||
cpu: CPU
|
||||
|
||||
@(private)
|
||||
cpu_name_buf: [128]byte
|
||||
@@ -53,7 +57,7 @@ init_cpu_name :: proc "contextless" () {
|
||||
|
||||
when ODIN_OS == .Darwin {
|
||||
if unix.sysctlbyname("machdep.cpu.brand_string", &cpu_name_buf) {
|
||||
cpu_name = string(cstring(rawptr(&cpu_name_buf)))
|
||||
cpu.name = string(cstring(rawptr(&cpu_name_buf)))
|
||||
generic = false
|
||||
}
|
||||
}
|
||||
@@ -61,10 +65,10 @@ init_cpu_name :: proc "contextless" () {
|
||||
if generic {
|
||||
when ODIN_ARCH == .arm64 {
|
||||
copy(cpu_name_buf[:], "ARM64")
|
||||
cpu_name = string(cpu_name_buf[:len("ARM64")])
|
||||
cpu.name = string(cpu_name_buf[:len("ARM64")])
|
||||
} else {
|
||||
copy(cpu_name_buf[:], "ARM")
|
||||
cpu_name = string(cpu_name_buf[:len("ARM")])
|
||||
cpu.name = string(cpu_name_buf[:len("ARM")])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import "core:sys/unix"
|
||||
@(init, private)
|
||||
init_cpu_features :: proc "contextless" () {
|
||||
@(static) features: CPU_Features
|
||||
defer cpu_features = features
|
||||
defer cpu.features = features
|
||||
|
||||
try_set :: proc "contextless" (name: cstring, feature: CPU_Feature) -> (ok: bool) {
|
||||
support: b32
|
||||
|
||||
@@ -3,12 +3,6 @@ package sysinfo
|
||||
|
||||
import "base:intrinsics"
|
||||
|
||||
// cpuid :: proc(ax, cx: u32) -> (eax, ebc, ecx, edx: u32) ---
|
||||
cpuid :: intrinsics.x86_cpuid
|
||||
|
||||
// xgetbv :: proc(cx: u32) -> (eax, edx: u32) ---
|
||||
xgetbv :: intrinsics.x86_xgetbv
|
||||
|
||||
CPU_Feature :: enum u64 {
|
||||
aes, // AES hardware implementation (AES NI)
|
||||
adx, // Multi-precision add-carry instruction extensions
|
||||
@@ -49,9 +43,13 @@ CPU_Feature :: enum u64 {
|
||||
}
|
||||
|
||||
CPU_Features :: distinct bit_set[CPU_Feature; u64]
|
||||
|
||||
cpu_features: Maybe(CPU_Features)
|
||||
cpu_name: Maybe(string)
|
||||
CPU :: struct {
|
||||
name: Maybe(string),
|
||||
features: Maybe(CPU_Features),
|
||||
physical_cores: int, // Initialized by cpu_<os>.odin
|
||||
logical_cores: int, // Initialized by cpu_<os>.odin
|
||||
}
|
||||
cpu: CPU
|
||||
|
||||
@(init, private)
|
||||
init_cpu_features :: proc "c" () {
|
||||
@@ -88,7 +86,7 @@ init_cpu_features :: proc "c" () {
|
||||
when ODIN_OS == .FreeBSD || ODIN_OS == .OpenBSD || ODIN_OS == .NetBSD {
|
||||
// xgetbv is an illegal instruction under FreeBSD 13, OpenBSD 7.1 and NetBSD 10
|
||||
// return before probing further
|
||||
cpu_features = set
|
||||
cpu.features = set
|
||||
return
|
||||
}
|
||||
|
||||
@@ -151,7 +149,7 @@ init_cpu_features :: proc "c" () {
|
||||
try_set(&set, .rdseed, 18, ebx7)
|
||||
try_set(&set, .adx, 19, ebx7)
|
||||
|
||||
cpu_features = set
|
||||
cpu.features = set
|
||||
}
|
||||
|
||||
@(private)
|
||||
@@ -179,5 +177,11 @@ init_cpu_name :: proc "c" () {
|
||||
for len(brand) > 0 && brand[len(brand) - 1] == 0 || brand[len(brand) - 1] == ' ' {
|
||||
brand = brand[:len(brand) - 1]
|
||||
}
|
||||
cpu_name = brand
|
||||
cpu.name = brand
|
||||
}
|
||||
|
||||
// cpuid :: proc(ax, cx: u32) -> (eax, ebc, ecx, edx: u32) ---
|
||||
cpuid :: intrinsics.x86_cpuid
|
||||
|
||||
// xgetbv :: proc(cx: u32) -> (eax, edx: u32) ---
|
||||
xgetbv :: intrinsics.x86_xgetbv
|
||||
@@ -17,7 +17,7 @@ init_cpu_features :: proc() {
|
||||
if rerr != .NONE || n == 0 { return }
|
||||
|
||||
features: CPU_Features
|
||||
defer cpu_features = features
|
||||
defer cpu.features = features
|
||||
|
||||
str := string(buf[:n])
|
||||
for line in strings.split_lines_iterator(&str) {
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
#+build i386, amd64
|
||||
#+build linux
|
||||
package sysinfo
|
||||
|
||||
import "core:sys/linux"
|
||||
import "core:strings"
|
||||
import "core:strconv"
|
||||
|
||||
@(init, private)
|
||||
init_cpu_core_count :: proc() {
|
||||
fd, err := linux.open("/proc/cpuinfo", {})
|
||||
if err != .NONE { return }
|
||||
defer linux.close(fd)
|
||||
|
||||
// This is probably enough right?
|
||||
buf: [4096]byte
|
||||
n, rerr := linux.read(fd, buf[:])
|
||||
if rerr != .NONE || n == 0 { return }
|
||||
|
||||
str := string(buf[:n])
|
||||
for line in strings.split_lines_iterator(&str) {
|
||||
key, _, value := strings.partition(line, ":")
|
||||
key = strings.trim_space(key)
|
||||
value = strings.trim_space(value)
|
||||
|
||||
if key == "cpu cores" {
|
||||
if num_physical_cores, ok := strconv.parse_int(value); ok {
|
||||
cpu.physical_cores = num_physical_cores
|
||||
}
|
||||
}
|
||||
|
||||
if key == "siblings" {
|
||||
if num_logical_cores, ok := strconv.parse_int(value); ok {
|
||||
cpu.logical_cores = num_logical_cores
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,7 @@ import "core:sys/linux"
|
||||
@(init, private)
|
||||
init_cpu_features :: proc() {
|
||||
_features: CPU_Features
|
||||
defer cpu_features = _features
|
||||
defer cpu.features = _features
|
||||
|
||||
HWCAP_Bits :: enum u64 {
|
||||
I = 'I' - 'A',
|
||||
@@ -109,5 +109,5 @@ init_cpu_features :: proc() {
|
||||
|
||||
@(init, private)
|
||||
init_cpu_name :: proc() {
|
||||
cpu_name = "RISCV64"
|
||||
cpu.name = "RISCV64"
|
||||
}
|
||||
|
||||
@@ -95,6 +95,10 @@ CPU_Feature :: enum u64 {
|
||||
}
|
||||
|
||||
CPU_Features :: distinct bit_set[CPU_Feature; u64]
|
||||
|
||||
cpu_features: Maybe(CPU_Features)
|
||||
cpu_name: Maybe(string)
|
||||
CPU :: struct {
|
||||
name: Maybe(string),
|
||||
features: Maybe(CPU_Features),
|
||||
physical_cores: int,
|
||||
logical_cores: int,
|
||||
}
|
||||
cpu: CPU
|
||||
@@ -0,0 +1,28 @@
|
||||
package sysinfo
|
||||
|
||||
import sys "core:sys/windows"
|
||||
import "base:intrinsics"
|
||||
|
||||
@(init, private)
|
||||
init_cpu_core_count :: proc() {
|
||||
infos: []sys.SYSTEM_LOGICAL_PROCESSOR_INFORMATION
|
||||
defer delete(infos)
|
||||
|
||||
returned_length: sys.DWORD
|
||||
// Query for the required buffer size.
|
||||
if ok := sys.GetLogicalProcessorInformation(raw_data(infos), &returned_length); !ok {
|
||||
infos = make([]sys.SYSTEM_LOGICAL_PROCESSOR_INFORMATION, returned_length / size_of(sys.SYSTEM_LOGICAL_PROCESSOR_INFORMATION))
|
||||
}
|
||||
|
||||
// If it still doesn't work, return
|
||||
if ok := sys.GetLogicalProcessorInformation(raw_data(infos), &returned_length); !ok {
|
||||
return
|
||||
}
|
||||
|
||||
for info in infos {
|
||||
#partial switch info.Relationship {
|
||||
case .RelationProcessorCore: cpu.physical_cores += 1
|
||||
case .RelationNumaNode: cpu.logical_cores += int(intrinsics.count_ones(info.ProcessorMask))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,13 +26,15 @@ Example:
|
||||
import si "core:sys/info"
|
||||
|
||||
main :: proc() {
|
||||
fmt.printfln("Odin: %v", ODIN_VERSION)
|
||||
fmt.printfln("OS: %v", si.os_version.as_string)
|
||||
fmt.printfln("OS: %#v", si.os_version)
|
||||
fmt.printfln("CPU: %v", si.cpu_name)
|
||||
fmt.printfln("RAM: %#.1M", si.ram.total_ram)
|
||||
fmt.printfln("Odin: %v", ODIN_VERSION)
|
||||
fmt.printfln("OS: %v", si.os_version.as_string)
|
||||
fmt.printfln("OS: %#v", si.os_version)
|
||||
fmt.printfln("CPU: %v", si.cpu.name)
|
||||
fmt.printfln("CPU: %v", si.cpu.name)
|
||||
fmt.printfln("CPU cores: %vc/%vt", si.cpu.physical_cores, si.cpu.logical_cores)
|
||||
fmt.printfln("RAM: %#.1M", si.ram.total_ram)
|
||||
|
||||
// fmt.printfln("Features: %v", si.cpu_features)
|
||||
// fmt.printfln("Features: %v", si.cpu.features)
|
||||
// fmt.printfln("MacOS version: %v", si.macos_version)
|
||||
|
||||
fmt.println()
|
||||
|
||||
@@ -857,7 +857,6 @@ MEMORY_RESOURCE_NOTIFICATION_TYPE :: enum c_int {
|
||||
LowMemoryResourceNotification :: MEMORY_RESOURCE_NOTIFICATION_TYPE.LowMemoryResourceNotification
|
||||
HighMemoryResourceNotification :: MEMORY_RESOURCE_NOTIFICATION_TYPE.HighMemoryResourceNotification
|
||||
|
||||
|
||||
@(default_calling_convention="system")
|
||||
foreign kernel32 {
|
||||
CreateMemoryResourceNotification :: proc(
|
||||
@@ -1194,7 +1193,7 @@ DUMMYUNIONNAME_u :: struct #raw_union {
|
||||
SYSTEM_LOGICAL_PROCESSOR_INFORMATION :: struct {
|
||||
ProcessorMask: ULONG_PTR,
|
||||
Relationship: LOGICAL_PROCESSOR_RELATIONSHIP,
|
||||
DummyUnion: DUMMYUNIONNAME_u,
|
||||
using DummyUnion: DUMMYUNIONNAME_u,
|
||||
}
|
||||
|
||||
SYSTEM_POWER_STATUS :: struct {
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
/*
|
||||
This package is for interacting with the command line interface of the system.
|
||||
*/
|
||||
package terminal
|
||||
@@ -0,0 +1,87 @@
|
||||
#+private
|
||||
package terminal
|
||||
|
||||
import "core:os"
|
||||
import "core:strings"
|
||||
|
||||
// Reference documentation:
|
||||
//
|
||||
// - [[ https://no-color.org/ ]]
|
||||
// - [[ https://github.com/termstandard/colors ]]
|
||||
// - [[ https://invisible-island.net/ncurses/terminfo.src.html ]]
|
||||
|
||||
get_no_color :: proc() -> bool {
|
||||
if no_color, ok := os.lookup_env("NO_COLOR"); ok {
|
||||
defer delete(no_color)
|
||||
return no_color != ""
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
get_environment_color :: proc() -> Color_Depth {
|
||||
// `COLORTERM` is non-standard but widespread and unambiguous.
|
||||
if colorterm, ok := os.lookup_env("COLORTERM"); ok {
|
||||
defer delete(colorterm)
|
||||
// These are the only values that are typically advertised that have
|
||||
// anything to do with color depth.
|
||||
if colorterm == "truecolor" || colorterm == "24bit" {
|
||||
return .True_Color
|
||||
}
|
||||
}
|
||||
|
||||
if term, ok := os.lookup_env("TERM"); ok {
|
||||
defer delete(term)
|
||||
if strings.contains(term, "-truecolor") {
|
||||
return .True_Color
|
||||
}
|
||||
if strings.contains(term, "-256color") {
|
||||
return .Eight_Bit
|
||||
}
|
||||
if strings.contains(term, "-16color") {
|
||||
return .Four_Bit
|
||||
}
|
||||
|
||||
// The `terminfo` database, which is stored in binary on *nix
|
||||
// platforms, has an undocumented format that is not guaranteed to be
|
||||
// portable, so beyond this point, we can only make safe assumptions.
|
||||
//
|
||||
// This section should only be necessary for terminals that do not
|
||||
// define any of the previous environment values.
|
||||
//
|
||||
// Only a small sampling of some common values are checked here.
|
||||
switch term {
|
||||
case "ansi": fallthrough
|
||||
case "konsole": fallthrough
|
||||
case "putty": fallthrough
|
||||
case "rxvt": fallthrough
|
||||
case "rxvt-color": fallthrough
|
||||
case "screen": fallthrough
|
||||
case "st": fallthrough
|
||||
case "tmux": fallthrough
|
||||
case "vte": fallthrough
|
||||
case "xterm": fallthrough
|
||||
case "xterm-color":
|
||||
return .Three_Bit
|
||||
}
|
||||
}
|
||||
|
||||
return .None
|
||||
}
|
||||
|
||||
@(init)
|
||||
init_terminal :: proc() {
|
||||
_init_terminal()
|
||||
|
||||
// We respect `NO_COLOR` specifically as a color-disabler but not as a
|
||||
// blanket ban on any terminal manipulation codes, hence why this comes
|
||||
// after `_init_terminal` which will allow Windows to enable Virtual
|
||||
// Terminal Processing for non-color control sequences.
|
||||
if !get_no_color() {
|
||||
color_enabled = color_depth > .None
|
||||
}
|
||||
}
|
||||
|
||||
@(fini)
|
||||
fini_terminal :: proc() {
|
||||
_fini_terminal()
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package terminal
|
||||
|
||||
import "core:os"
|
||||
|
||||
/*
|
||||
This describes the range of colors that a terminal is capable of supporting.
|
||||
*/
|
||||
Color_Depth :: enum {
|
||||
None, // No color support
|
||||
Three_Bit, // 8 colors
|
||||
Four_Bit, // 16 colors
|
||||
Eight_Bit, // 256 colors
|
||||
True_Color, // 24-bit true color
|
||||
}
|
||||
|
||||
/*
|
||||
Returns true if the file `handle` is attached to a terminal.
|
||||
|
||||
This is normally true for `os.stdout` and `os.stderr` unless they are
|
||||
redirected to a file.
|
||||
*/
|
||||
@(require_results)
|
||||
is_terminal :: proc(handle: os.Handle) -> bool {
|
||||
return _is_terminal(handle)
|
||||
}
|
||||
|
||||
/*
|
||||
This is true if the terminal is accepting any form of colored text output.
|
||||
*/
|
||||
color_enabled: bool
|
||||
|
||||
/*
|
||||
This value reports the color depth support as reported by the terminal at the
|
||||
start of the program.
|
||||
*/
|
||||
color_depth: Color_Depth
|
||||
@@ -0,0 +1,16 @@
|
||||
#+private
|
||||
#+build linux, darwin, netbsd, openbsd, freebsd, haiku
|
||||
package terminal
|
||||
|
||||
import "core:os"
|
||||
import "core:sys/posix"
|
||||
|
||||
_is_terminal :: proc(handle: os.Handle) -> bool {
|
||||
return bool(posix.isatty(posix.FD(handle)))
|
||||
}
|
||||
|
||||
_init_terminal :: proc() {
|
||||
color_depth = get_environment_color()
|
||||
}
|
||||
|
||||
_fini_terminal :: proc() { }
|
||||
@@ -0,0 +1,60 @@
|
||||
#+private
|
||||
package terminal
|
||||
|
||||
import "core:os"
|
||||
import "core:sys/windows"
|
||||
|
||||
_is_terminal :: proc(handle: os.Handle) -> bool {
|
||||
is_tty := windows.GetFileType(windows.HANDLE(handle)) == windows.FILE_TYPE_CHAR
|
||||
return is_tty
|
||||
}
|
||||
|
||||
old_modes: [2]struct{
|
||||
handle: windows.DWORD,
|
||||
mode: windows.DWORD,
|
||||
} = {
|
||||
{windows.STD_OUTPUT_HANDLE, 0},
|
||||
{windows.STD_ERROR_HANDLE, 0},
|
||||
}
|
||||
|
||||
@(init)
|
||||
_init_terminal :: proc() {
|
||||
vtp_enabled: bool
|
||||
|
||||
for &v in old_modes {
|
||||
handle := windows.GetStdHandle(v.handle)
|
||||
if handle == windows.INVALID_HANDLE || handle == nil {
|
||||
return
|
||||
}
|
||||
if windows.GetConsoleMode(handle, &v.mode) {
|
||||
windows.SetConsoleMode(handle, v.mode | windows.ENABLE_PROCESSED_OUTPUT | windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING)
|
||||
|
||||
new_mode: windows.DWORD
|
||||
windows.GetConsoleMode(handle, &new_mode)
|
||||
|
||||
if new_mode & (windows.ENABLE_PROCESSED_OUTPUT | windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING) != 0 {
|
||||
vtp_enabled = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if vtp_enabled {
|
||||
// This color depth is available on Windows 10 since build 10586.
|
||||
color_depth = .Four_Bit
|
||||
} else {
|
||||
// The user may be on a non-default terminal emulator.
|
||||
color_depth = get_environment_color()
|
||||
}
|
||||
}
|
||||
|
||||
@(fini)
|
||||
_fini_terminal :: proc() {
|
||||
for v in old_modes {
|
||||
handle := windows.GetStdHandle(v.handle)
|
||||
if handle == windows.INVALID_HANDLE || handle == nil {
|
||||
return
|
||||
}
|
||||
|
||||
windows.SetConsoleMode(handle, v.mode)
|
||||
}
|
||||
}
|
||||
@@ -10,12 +10,12 @@ package testing
|
||||
*/
|
||||
|
||||
import "base:runtime"
|
||||
import "core:encoding/ansi"
|
||||
import "core:fmt"
|
||||
import "core:io"
|
||||
import "core:mem"
|
||||
import "core:path/filepath"
|
||||
import "core:strings"
|
||||
import "core:terminal/ansi"
|
||||
|
||||
// Definitions of colors for use in the test runner.
|
||||
SGR_RESET :: ansi.CSI + ansi.RESET + ansi.SGR
|
||||
|
||||
+93
-69
@@ -13,7 +13,6 @@ package testing
|
||||
import "base:intrinsics"
|
||||
import "base:runtime"
|
||||
import "core:bytes"
|
||||
import "core:encoding/ansi"
|
||||
@require import "core:encoding/base64"
|
||||
@require import "core:encoding/json"
|
||||
import "core:fmt"
|
||||
@@ -25,6 +24,8 @@ import "core:os"
|
||||
import "core:slice"
|
||||
@require import "core:strings"
|
||||
import "core:sync/chan"
|
||||
import "core:terminal"
|
||||
import "core:terminal/ansi"
|
||||
import "core:thread"
|
||||
import "core:time"
|
||||
|
||||
@@ -44,6 +45,7 @@ PER_THREAD_MEMORY : int : #config(ODIN_TEST_THREAD_MEMORY, mem.ROLLBACK_S
|
||||
// The format is: `package.test_name,test_name_only,...`
|
||||
TEST_NAMES : string : #config(ODIN_TEST_NAMES, "")
|
||||
// Show the fancy animated progress report.
|
||||
// This requires terminal color support, as well as STDOUT to not be redirected to a file.
|
||||
FANCY_OUTPUT : bool : #config(ODIN_TEST_FANCY, true)
|
||||
// Copy failed tests to the clipboard when done.
|
||||
USE_CLIPBOARD : bool : #config(ODIN_TEST_CLIPBOARD, false)
|
||||
@@ -70,6 +72,9 @@ get_log_level :: #force_inline proc() -> runtime.Logger_Level {
|
||||
}
|
||||
}
|
||||
|
||||
@(private) global_log_colors_disabled: bool
|
||||
@(private) global_ansi_disabled: bool
|
||||
|
||||
JSON :: struct {
|
||||
total: int,
|
||||
success: int,
|
||||
@@ -129,11 +134,16 @@ run_test_task :: proc(task: thread.Task) {
|
||||
|
||||
context.assertion_failure_proc = test_assertion_failure_proc
|
||||
|
||||
logger_options := Default_Test_Logger_Opts
|
||||
if global_log_colors_disabled {
|
||||
logger_options -= {.Terminal_Color}
|
||||
}
|
||||
|
||||
context.logger = {
|
||||
procedure = test_logger_proc,
|
||||
data = &data.t,
|
||||
lowest_level = get_log_level(),
|
||||
options = Default_Test_Logger_Opts,
|
||||
options = logger_options,
|
||||
}
|
||||
|
||||
random_generator_state: runtime.Default_Random_State
|
||||
@@ -204,13 +214,16 @@ runner :: proc(internal_tests: []Internal_Test) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
when ODIN_OS == .Windows {
|
||||
console_ansi_init()
|
||||
}
|
||||
|
||||
stdout := io.to_writer(os.stream_from_handle(os.stdout))
|
||||
stderr := io.to_writer(os.stream_from_handle(os.stderr))
|
||||
|
||||
// The animations are only ever shown through STDOUT;
|
||||
// STDERR is used exclusively for logging regardless of error level.
|
||||
global_log_colors_disabled = !terminal.color_enabled || !terminal.is_terminal(os.stderr)
|
||||
global_ansi_disabled = !terminal.is_terminal(os.stdout)
|
||||
|
||||
should_show_animations := FANCY_OUTPUT && terminal.color_enabled && !global_ansi_disabled
|
||||
|
||||
// -- Prepare test data.
|
||||
|
||||
alloc_error: mem.Allocator_Error
|
||||
@@ -268,12 +281,12 @@ runner :: proc(internal_tests: []Internal_Test) -> bool {
|
||||
total_done_count := 0
|
||||
total_test_count := len(internal_tests)
|
||||
|
||||
when !FANCY_OUTPUT {
|
||||
// This is strictly for updating the window title when the progress
|
||||
// report is disabled. We're otherwise able to depend on the call to
|
||||
// `needs_to_redraw`.
|
||||
last_done_count := -1
|
||||
}
|
||||
|
||||
// This is strictly for updating the window title when the progress
|
||||
// report is disabled. We're otherwise able to depend on the call to
|
||||
// `needs_to_redraw`.
|
||||
last_done_count := -1
|
||||
|
||||
|
||||
if total_test_count == 0 {
|
||||
// Exit early.
|
||||
@@ -342,31 +355,31 @@ runner :: proc(internal_tests: []Internal_Test) -> bool {
|
||||
fmt.assertf(alloc_error == nil, "Error allocating memory for test report: %v", alloc_error)
|
||||
defer destroy_report(&report)
|
||||
|
||||
when FANCY_OUTPUT {
|
||||
// We cannot make use of the ANSI save/restore cursor codes, because they
|
||||
// work by absolute screen coordinates. This will cause unnecessary
|
||||
// scrollback if we print at the bottom of someone's terminal.
|
||||
ansi_redraw_string := fmt.aprintf(
|
||||
// ANSI for "go up N lines then erase the screen from the cursor forward."
|
||||
ansi.CSI + "%i" + ansi.CPL + ansi.CSI + ansi.ED +
|
||||
// We'll combine this with the window title format string, since it
|
||||
// can be printed at the same time.
|
||||
"%s",
|
||||
// 1 extra line for the status bar.
|
||||
1 + len(report.packages), OSC_WINDOW_TITLE)
|
||||
assert(len(ansi_redraw_string) > 0, "Error allocating ANSI redraw string.")
|
||||
defer delete(ansi_redraw_string)
|
||||
|
||||
thread_count_status_string: string = ---
|
||||
{
|
||||
PADDING :: PROGRESS_COLUMN_SPACING + PROGRESS_WIDTH
|
||||
// We cannot make use of the ANSI save/restore cursor codes, because they
|
||||
// work by absolute screen coordinates. This will cause unnecessary
|
||||
// scrollback if we print at the bottom of someone's terminal.
|
||||
ansi_redraw_string := fmt.aprintf(
|
||||
// ANSI for "go up N lines then erase the screen from the cursor forward."
|
||||
ansi.CSI + "%i" + ansi.CPL + ansi.CSI + ansi.ED +
|
||||
// We'll combine this with the window title format string, since it
|
||||
// can be printed at the same time.
|
||||
"%s",
|
||||
// 1 extra line for the status bar.
|
||||
1 + len(report.packages), OSC_WINDOW_TITLE)
|
||||
assert(len(ansi_redraw_string) > 0, "Error allocating ANSI redraw string.")
|
||||
defer delete(ansi_redraw_string)
|
||||
|
||||
unpadded := fmt.tprintf("%i thread%s", thread_count, "" if thread_count == 1 else "s")
|
||||
thread_count_status_string = fmt.aprintf("%- *[1]s", unpadded, report.pkg_column_len + PADDING)
|
||||
assert(len(thread_count_status_string) > 0, "Error allocating thread count status string.")
|
||||
}
|
||||
defer delete(thread_count_status_string)
|
||||
thread_count_status_string: string = ---
|
||||
{
|
||||
PADDING :: PROGRESS_COLUMN_SPACING + PROGRESS_WIDTH
|
||||
|
||||
unpadded := fmt.tprintf("%i thread%s", thread_count, "" if thread_count == 1 else "s")
|
||||
thread_count_status_string = fmt.aprintf("%- *[1]s", unpadded, report.pkg_column_len + PADDING)
|
||||
assert(len(thread_count_status_string) > 0, "Error allocating thread count status string.")
|
||||
}
|
||||
defer delete(thread_count_status_string)
|
||||
|
||||
|
||||
task_data_slots: []Task_Data = ---
|
||||
task_data_slots, alloc_error = make([]Task_Data, thread_count)
|
||||
@@ -442,11 +455,16 @@ runner :: proc(internal_tests: []Internal_Test) -> bool {
|
||||
// digging through the source to divine everywhere it is used for that.
|
||||
shared_log_allocator := context.allocator
|
||||
|
||||
logger_options := Default_Test_Logger_Opts - {.Short_File_Path, .Line, .Procedure}
|
||||
if global_log_colors_disabled {
|
||||
logger_options -= {.Terminal_Color}
|
||||
}
|
||||
|
||||
context.logger = {
|
||||
procedure = runner_logger_proc,
|
||||
data = &log_messages,
|
||||
lowest_level = get_log_level(),
|
||||
options = Default_Test_Logger_Opts - {.Short_File_Path, .Line, .Procedure},
|
||||
options = logger_options,
|
||||
}
|
||||
|
||||
run_index: int
|
||||
@@ -481,11 +499,13 @@ runner :: proc(internal_tests: []Internal_Test) -> bool {
|
||||
|
||||
setup_signal_handler()
|
||||
|
||||
fmt.wprint(stdout, ansi.CSI + ansi.DECTCEM_HIDE)
|
||||
if !global_ansi_disabled {
|
||||
fmt.wprint(stdout, ansi.CSI + ansi.DECTCEM_HIDE)
|
||||
}
|
||||
|
||||
when FANCY_OUTPUT {
|
||||
signals_were_raised := false
|
||||
signals_were_raised := false
|
||||
|
||||
if should_show_animations {
|
||||
redraw_report(stdout, report)
|
||||
draw_status_bar(stdout, thread_count_status_string, total_done_count, total_test_count)
|
||||
}
|
||||
@@ -703,22 +723,22 @@ runner :: proc(internal_tests: []Internal_Test) -> bool {
|
||||
break main_loop
|
||||
}
|
||||
|
||||
when FANCY_OUTPUT {
|
||||
// Because the bounds checking procs send directly to STDERR with
|
||||
// no way to redirect or handle them, we need to at least try to
|
||||
// let the user see those messages when using the animated progress
|
||||
// report. This flag may be set by the block of code below if a
|
||||
// signal is raised.
|
||||
//
|
||||
// It'll be purely by luck if the output is interleaved properly,
|
||||
// given the nature of non-thread-safe printing.
|
||||
//
|
||||
// At worst, if Odin did not print any error for this signal, we'll
|
||||
// just re-display the progress report. The fatal log error message
|
||||
// should be enough to clue the user in that something dire has
|
||||
// occurred.
|
||||
bypass_progress_overwrite := false
|
||||
}
|
||||
|
||||
// Because the bounds checking procs send directly to STDERR with
|
||||
// no way to redirect or handle them, we need to at least try to
|
||||
// let the user see those messages when using the animated progress
|
||||
// report. This flag may be set by the block of code below if a
|
||||
// signal is raised.
|
||||
//
|
||||
// It'll be purely by luck if the output is interleaved properly,
|
||||
// given the nature of non-thread-safe printing.
|
||||
//
|
||||
// At worst, if Odin did not print any error for this signal, we'll
|
||||
// just re-display the progress report. The fatal log error message
|
||||
// should be enough to clue the user in that something dire has
|
||||
// occurred.
|
||||
bypass_progress_overwrite := false
|
||||
|
||||
|
||||
if test_index, reason, ok := should_stop_test(); ok {
|
||||
#no_bounds_check report.all_test_states[test_index] = .Failed
|
||||
@@ -752,7 +772,7 @@ runner :: proc(internal_tests: []Internal_Test) -> bool {
|
||||
log.fatalf("Caught signal to stop test #%i %s.%s for: %v.", test_index, it.pkg, it.name, reason)
|
||||
}
|
||||
|
||||
when FANCY_OUTPUT {
|
||||
if should_show_animations {
|
||||
bypass_progress_overwrite = true
|
||||
signals_were_raised = true
|
||||
}
|
||||
@@ -766,7 +786,7 @@ runner :: proc(internal_tests: []Internal_Test) -> bool {
|
||||
|
||||
// -- Redraw.
|
||||
|
||||
when FANCY_OUTPUT {
|
||||
if should_show_animations {
|
||||
if len(log_messages) == 0 && !needs_to_redraw(report) {
|
||||
continue main_loop
|
||||
}
|
||||
@@ -776,7 +796,9 @@ runner :: proc(internal_tests: []Internal_Test) -> bool {
|
||||
}
|
||||
} else {
|
||||
if total_done_count != last_done_count {
|
||||
fmt.wprintf(stdout, OSC_WINDOW_TITLE, total_done_count, total_test_count)
|
||||
if !global_ansi_disabled {
|
||||
fmt.wprintf(stdout, OSC_WINDOW_TITLE, total_done_count, total_test_count)
|
||||
}
|
||||
last_done_count = total_done_count
|
||||
}
|
||||
|
||||
@@ -801,7 +823,7 @@ runner :: proc(internal_tests: []Internal_Test) -> bool {
|
||||
clear(&log_messages)
|
||||
bytes.buffer_reset(&batch_buffer)
|
||||
|
||||
when FANCY_OUTPUT {
|
||||
if should_show_animations {
|
||||
redraw_report(batch_writer, report)
|
||||
draw_status_bar(batch_writer, thread_count_status_string, total_done_count, total_test_count)
|
||||
fmt.wprint(stdout, bytes.buffer_to_string(&batch_buffer))
|
||||
@@ -822,7 +844,7 @@ runner :: proc(internal_tests: []Internal_Test) -> bool {
|
||||
|
||||
finished_in := time.since(start_time)
|
||||
|
||||
when !FANCY_OUTPUT {
|
||||
if !should_show_animations || !terminal.is_terminal(os.stderr) {
|
||||
// One line to space out the results, since we don't have the status
|
||||
// bar in plain mode.
|
||||
fmt.wprintln(batch_writer)
|
||||
@@ -836,24 +858,28 @@ runner :: proc(internal_tests: []Internal_Test) -> bool {
|
||||
|
||||
if total_done_count != total_test_count {
|
||||
not_run_count := total_test_count - total_done_count
|
||||
message := " %i %s left undone." if global_log_colors_disabled else " " + SGR_READY + "%i" + SGR_RESET + " %s left undone."
|
||||
fmt.wprintf(batch_writer,
|
||||
" " + SGR_READY + "%i" + SGR_RESET + " %s left undone.",
|
||||
message,
|
||||
not_run_count,
|
||||
"test was" if not_run_count == 1 else "tests were")
|
||||
}
|
||||
|
||||
if total_success_count == total_test_count {
|
||||
message := " %s successful." if global_log_colors_disabled else " %s " + SGR_SUCCESS + "successful." + SGR_RESET
|
||||
fmt.wprintfln(batch_writer,
|
||||
" %s " + SGR_SUCCESS + "successful." + SGR_RESET,
|
||||
message,
|
||||
"The test was" if total_test_count == 1 else "All tests were")
|
||||
} else if total_failure_count > 0 {
|
||||
if total_failure_count == total_test_count {
|
||||
message := " %s failed." if global_log_colors_disabled else " %s " + SGR_FAILED + "failed." + SGR_RESET
|
||||
fmt.wprintfln(batch_writer,
|
||||
" %s " + SGR_FAILED + "failed." + SGR_RESET,
|
||||
message,
|
||||
"The test" if total_test_count == 1 else "All tests")
|
||||
} else {
|
||||
message := " %i test%s failed." if global_log_colors_disabled else " " + SGR_FAILED + "%i" + SGR_RESET + " test%s failed."
|
||||
fmt.wprintfln(batch_writer,
|
||||
" " + SGR_FAILED + "%i" + SGR_RESET + " test%s failed.",
|
||||
message,
|
||||
total_failure_count,
|
||||
"" if total_failure_count == 1 else "s")
|
||||
}
|
||||
@@ -907,9 +933,11 @@ runner :: proc(internal_tests: []Internal_Test) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
fmt.wprint(stdout, ansi.CSI + ansi.DECTCEM_SHOW)
|
||||
if !global_ansi_disabled {
|
||||
fmt.wprint(stdout, ansi.CSI + ansi.DECTCEM_SHOW)
|
||||
}
|
||||
|
||||
when FANCY_OUTPUT {
|
||||
if should_show_animations {
|
||||
if signals_were_raised {
|
||||
fmt.wprintln(batch_writer, `
|
||||
Signals were raised during this test run. Log messages are likely to have collided with each other.
|
||||
@@ -949,9 +977,5 @@ To partly mitigate this, redirect STDERR to a file or use the -define:ODIN_TEST_
|
||||
fmt.assertf(err == nil, "Error writing JSON report: %v", err)
|
||||
}
|
||||
|
||||
when ODIN_OS == .Windows {
|
||||
console_ansi_fini()
|
||||
}
|
||||
|
||||
return total_success_count == total_test_count
|
||||
}
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
#+private
|
||||
package testing
|
||||
|
||||
import win32 "core:sys/windows"
|
||||
|
||||
old_stdout_mode: u32
|
||||
old_stderr_mode: u32
|
||||
|
||||
console_ansi_init :: proc() {
|
||||
stdout := win32.GetStdHandle(win32.STD_OUTPUT_HANDLE)
|
||||
if stdout != win32.INVALID_HANDLE && stdout != nil {
|
||||
if win32.GetConsoleMode(stdout, &old_stdout_mode) {
|
||||
win32.SetConsoleMode(stdout, old_stdout_mode | win32.ENABLE_VIRTUAL_TERMINAL_PROCESSING)
|
||||
}
|
||||
}
|
||||
|
||||
stderr := win32.GetStdHandle(win32.STD_ERROR_HANDLE)
|
||||
if stderr != win32.INVALID_HANDLE && stderr != nil {
|
||||
if win32.GetConsoleMode(stderr, &old_stderr_mode) {
|
||||
win32.SetConsoleMode(stderr, old_stderr_mode | win32.ENABLE_VIRTUAL_TERMINAL_PROCESSING)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Restore the cursor on exit
|
||||
console_ansi_fini :: proc() {
|
||||
stdout := win32.GetStdHandle(win32.STD_OUTPUT_HANDLE)
|
||||
if stdout != win32.INVALID_HANDLE && stdout != nil {
|
||||
win32.SetConsoleMode(stdout, old_stdout_mode)
|
||||
}
|
||||
|
||||
stderr := win32.GetStdHandle(win32.STD_ERROR_HANDLE)
|
||||
if stderr != win32.INVALID_HANDLE && stderr != nil {
|
||||
win32.SetConsoleMode(stderr, old_stderr_mode)
|
||||
}
|
||||
}
|
||||
@@ -12,9 +12,9 @@ package testing
|
||||
|
||||
import "base:intrinsics"
|
||||
import "core:c/libc"
|
||||
import "core:encoding/ansi"
|
||||
import "core:sync"
|
||||
import "core:os"
|
||||
import "core:sync"
|
||||
import "core:terminal/ansi"
|
||||
|
||||
@(private="file") stop_runner_flag: libc.sig_atomic_t
|
||||
|
||||
@@ -63,9 +63,11 @@ stop_test_callback :: proc "c" (sig: libc.int) {
|
||||
// NOTE(Feoramund): Using these write calls in a signal handler is
|
||||
// undefined behavior in C99 but possibly tolerated in POSIX 2008.
|
||||
// Either way, we may as well try to salvage what we can.
|
||||
show_cursor := ansi.CSI + ansi.DECTCEM_SHOW
|
||||
libc.fwrite(raw_data(show_cursor), size_of(byte), len(show_cursor), libc.stdout)
|
||||
libc.fflush(libc.stdout)
|
||||
if !global_ansi_disabled {
|
||||
show_cursor := ansi.CSI + ansi.DECTCEM_SHOW
|
||||
libc.fwrite(raw_data(show_cursor), size_of(byte), len(show_cursor), libc.stdout)
|
||||
libc.fflush(libc.stdout)
|
||||
}
|
||||
|
||||
// This is an attempt at being compliant by avoiding printf.
|
||||
sigbuf: [8]byte
|
||||
|
||||
@@ -15,8 +15,6 @@ MAX_PROGRAM_SIZE :: int(max(i16))
|
||||
MAX_CLASSES :: int(max(u8))
|
||||
|
||||
Flag :: enum u8 {
|
||||
// Global: try to match the pattern anywhere in the string.
|
||||
Global,
|
||||
// Multiline: treat `^` and `$` as if they also match newlines.
|
||||
Multiline,
|
||||
// Case Insensitive: treat `a-z` as if it was also `A-Z`.
|
||||
@@ -36,7 +34,6 @@ Flags :: bit_set[Flag; u8]
|
||||
|
||||
@(rodata)
|
||||
Flag_To_Letter := #sparse[Flag]u8 {
|
||||
.Global = 'g',
|
||||
.Multiline = 'm',
|
||||
.Case_Insensitive = 'i',
|
||||
.Ignore_Whitespace = 'x',
|
||||
|
||||
@@ -401,7 +401,7 @@ compile :: proc(tree: Node, flags: common.Flags) -> (code: Program, class_data:
|
||||
|
||||
pc_open := 0
|
||||
|
||||
add_global: if .Global in flags {
|
||||
optimize_opening: {
|
||||
// Check if the opening to the pattern is predictable.
|
||||
// If so, use one of the optimized Wait opcodes.
|
||||
iter := virtual_machine.Opcode_Iterator{ code[:], 0 }
|
||||
@@ -412,7 +412,7 @@ compile :: proc(tree: Node, flags: common.Flags) -> (code: Program, class_data:
|
||||
pc_open += size_of(Opcode)
|
||||
inject_at(&code, pc_open, Opcode(code[pc + size_of(Opcode) + pc_open]))
|
||||
pc_open += size_of(u8)
|
||||
break add_global
|
||||
break optimize_opening
|
||||
|
||||
case .Rune:
|
||||
operand := intrinsics.unaligned_load(cast(^rune)&code[pc+1])
|
||||
@@ -420,24 +420,28 @@ compile :: proc(tree: Node, flags: common.Flags) -> (code: Program, class_data:
|
||||
pc_open += size_of(Opcode)
|
||||
inject_raw(&code, pc_open, operand)
|
||||
pc_open += size_of(rune)
|
||||
break add_global
|
||||
break optimize_opening
|
||||
|
||||
case .Rune_Class:
|
||||
inject_at(&code, pc_open, Opcode.Wait_For_Rune_Class)
|
||||
pc_open += size_of(Opcode)
|
||||
inject_at(&code, pc_open, Opcode(code[pc + size_of(Opcode) + pc_open]))
|
||||
pc_open += size_of(u8)
|
||||
break add_global
|
||||
break optimize_opening
|
||||
|
||||
case .Rune_Class_Negated:
|
||||
inject_at(&code, pc_open, Opcode.Wait_For_Rune_Class_Negated)
|
||||
pc_open += size_of(Opcode)
|
||||
inject_at(&code, pc_open, Opcode(code[pc + size_of(Opcode) + pc_open]))
|
||||
pc_open += size_of(u8)
|
||||
break add_global
|
||||
break optimize_opening
|
||||
|
||||
case .Save:
|
||||
continue
|
||||
|
||||
case .Assert_Start:
|
||||
break optimize_opening
|
||||
|
||||
case:
|
||||
break seek_loop
|
||||
}
|
||||
|
||||
@@ -77,6 +77,8 @@ Match_Iterator :: struct {
|
||||
vm: virtual_machine.Machine,
|
||||
idx: int,
|
||||
temp: runtime.Allocator,
|
||||
threads: int,
|
||||
done: bool,
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -101,7 +103,6 @@ create :: proc(
|
||||
permanent_allocator := context.allocator,
|
||||
temporary_allocator := context.temp_allocator,
|
||||
) -> (result: Regular_Expression, err: Error) {
|
||||
|
||||
// For the sake of speed and simplicity, we first run all the intermediate
|
||||
// processes such as parsing and compilation through the temporary
|
||||
// allocator.
|
||||
@@ -166,7 +167,6 @@ to escape the delimiter if found in the middle of the string.
|
||||
|
||||
All runes after the closing delimiter will be parsed as flags:
|
||||
|
||||
- 'g': Global
|
||||
- 'm': Multiline
|
||||
- 'i': Case_Insensitive
|
||||
- 'x': Ignore_Whitespace
|
||||
@@ -243,7 +243,6 @@ create_by_user :: proc(
|
||||
// to `end` here.
|
||||
for r in pattern[start + end:] {
|
||||
switch r {
|
||||
case 'g': flags += { .Global }
|
||||
case 'm': flags += { .Multiline }
|
||||
case 'i': flags += { .Case_Insensitive }
|
||||
case 'x': flags += { .Ignore_Whitespace }
|
||||
@@ -282,8 +281,6 @@ create_iterator :: proc(
|
||||
permanent_allocator := context.allocator,
|
||||
temporary_allocator := context.temp_allocator,
|
||||
) -> (result: Match_Iterator, err: Error) {
|
||||
flags := flags
|
||||
flags += {.Global} // We're iterating over a string, so the next match could start anywhere
|
||||
|
||||
if .Multiline in flags {
|
||||
return {}, .Unsupported_Flag
|
||||
@@ -294,6 +291,7 @@ create_iterator :: proc(
|
||||
result.temp = temporary_allocator
|
||||
result.vm = virtual_machine.create(result.regex.program, str)
|
||||
result.vm.class_data = result.regex.class_data
|
||||
result.threads = max(1, virtual_machine.opcode_count(result.vm.code) - 1)
|
||||
|
||||
return
|
||||
}
|
||||
@@ -457,8 +455,27 @@ match_iterator :: proc(it: ^Match_Iterator) -> (result: Capture, index: int, ok:
|
||||
assert(len(it.capture.pos) >= common.MAX_CAPTURE_GROUPS,
|
||||
"Pre-allocated RegEx capture `pos` must be at least 10 elements long.")
|
||||
|
||||
// Guard against situations in which the iterator should finish.
|
||||
if it.done {
|
||||
return
|
||||
}
|
||||
|
||||
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||
|
||||
if it.idx > 0 {
|
||||
// Reset the state needed to `virtual_machine.run` again.
|
||||
it.vm.top_thread = 0
|
||||
it.vm.current_rune = rune(0)
|
||||
it.vm.current_rune_size = 0
|
||||
for i in 0..<it.threads {
|
||||
it.vm.threads[i] = {}
|
||||
it.vm.next_threads[i] = {}
|
||||
}
|
||||
}
|
||||
|
||||
// Take note of where the string pointer is before we start.
|
||||
sp_before := it.vm.string_pointer
|
||||
|
||||
saved: ^[2 * common.MAX_CAPTURE_GROUPS]int
|
||||
{
|
||||
context.allocator = it.temp
|
||||
@@ -469,6 +486,28 @@ match_iterator :: proc(it: ^Match_Iterator) -> (result: Capture, index: int, ok:
|
||||
}
|
||||
}
|
||||
|
||||
if !ok {
|
||||
// Match failed, bail out.
|
||||
return
|
||||
}
|
||||
|
||||
if it.vm.string_pointer == sp_before {
|
||||
// The string pointer did not move, but there was a match.
|
||||
//
|
||||
// At this point, the pattern supplied to the iterator will infinitely
|
||||
// loop if we do not intervene.
|
||||
it.done = true
|
||||
}
|
||||
if it.vm.string_pointer == len(it.vm.memory) {
|
||||
// The VM hit the end of the string.
|
||||
//
|
||||
// We do not check at the start, because a match of pattern `$`
|
||||
// against string "" is valid and must return a match.
|
||||
//
|
||||
// This check prevents a double-match of `$` against a non-empty string.
|
||||
it.done = true
|
||||
}
|
||||
|
||||
str := string(it.vm.memory)
|
||||
num_groups: int
|
||||
|
||||
@@ -488,9 +527,7 @@ match_iterator :: proc(it: ^Match_Iterator) -> (result: Capture, index: int, ok:
|
||||
num_groups = n
|
||||
}
|
||||
|
||||
defer if ok {
|
||||
it.idx += 1
|
||||
}
|
||||
defer it.idx += 1
|
||||
|
||||
if num_groups > 0 {
|
||||
result = {it.capture.pos[:num_groups], it.capture.groups[:num_groups]}
|
||||
@@ -504,8 +541,24 @@ match :: proc {
|
||||
match_iterator,
|
||||
}
|
||||
|
||||
/*
|
||||
Reset an iterator, allowing it to be run again as if new.
|
||||
|
||||
Inputs:
|
||||
- it: The iterator to reset.
|
||||
*/
|
||||
reset :: proc(it: ^Match_Iterator) {
|
||||
it.idx = 0
|
||||
it.done = false
|
||||
it.idx = 0
|
||||
it.vm.string_pointer = 0
|
||||
|
||||
it.vm.top_thread = 0
|
||||
it.vm.current_rune = rune(0)
|
||||
it.vm.current_rune_size = 0
|
||||
for i in 0..<it.threads {
|
||||
it.vm.threads[i] = {}
|
||||
it.vm.next_threads[i] = {}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -329,10 +329,10 @@ add_thread :: proc(vm: ^Machine, saved: ^[2 * common.MAX_CAPTURE_GROUPS]int, pc:
|
||||
|
||||
run :: proc(vm: ^Machine, $UNICODE_MODE: bool) -> (saved: ^[2 * common.MAX_CAPTURE_GROUPS]int, ok: bool) #no_bounds_check {
|
||||
when UNICODE_MODE {
|
||||
vm.next_rune, vm.next_rune_size = utf8.decode_rune_in_string(vm.memory)
|
||||
vm.next_rune, vm.next_rune_size = utf8.decode_rune_in_string(vm.memory[vm.string_pointer:])
|
||||
} else {
|
||||
if len(vm.memory) > 0 {
|
||||
vm.next_rune = cast(rune)vm.memory[0]
|
||||
vm.next_rune = cast(rune)vm.memory[vm.string_pointer]
|
||||
vm.next_rune_size = 1
|
||||
}
|
||||
}
|
||||
@@ -652,4 +652,4 @@ destroy :: proc(vm: Machine, allocator := context.allocator) {
|
||||
delete(vm.busy_map)
|
||||
free(vm.threads)
|
||||
free(vm.next_threads)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -285,6 +285,7 @@ scan_number :: proc(s: ^Scanner, ch: rune, seen_dot: bool) -> (rune, rune) {
|
||||
case 'o': return "octal literal"
|
||||
case 'z': return "dozenal literal"
|
||||
case 'x': return "hexadecimal literal"
|
||||
case 'h': return "hexadecimal literal"
|
||||
}
|
||||
return "decimal literal"
|
||||
}
|
||||
@@ -360,7 +361,8 @@ scan_number :: proc(s: ^Scanner, ch: rune, seen_dot: bool) -> (rune, rune) {
|
||||
base, prefix = 12, 'z'
|
||||
case 'h':
|
||||
tok = Float
|
||||
fallthrough
|
||||
ch = advance(s)
|
||||
base, prefix = 16, 'h'
|
||||
case 'x':
|
||||
ch = advance(s)
|
||||
base, prefix = 16, 'x'
|
||||
@@ -447,7 +449,7 @@ scan_string :: proc(s: ^Scanner, quote: rune) -> (n: int) {
|
||||
ch := advance(s)
|
||||
for ch != quote {
|
||||
if ch == '\n' || ch < 0 {
|
||||
error(s, "literal no terminated")
|
||||
error(s, "literal not terminated")
|
||||
return
|
||||
}
|
||||
if ch == '\\' {
|
||||
|
||||
@@ -58,7 +58,6 @@ import trace "core:debug/trace"
|
||||
import dynlib "core:dynlib"
|
||||
import net "core:net"
|
||||
|
||||
import ansi "core:encoding/ansi"
|
||||
import base32 "core:encoding/base32"
|
||||
import base64 "core:encoding/base64"
|
||||
import cbor "core:encoding/cbor"
|
||||
@@ -129,6 +128,9 @@ import strings "core:strings"
|
||||
import sync "core:sync"
|
||||
import testing "core:testing"
|
||||
|
||||
import terminal "core:terminal"
|
||||
import ansi "core:terminal/ansi"
|
||||
|
||||
import edit "core:text/edit"
|
||||
import i18n "core:text/i18n"
|
||||
import match "core:text/match"
|
||||
@@ -201,7 +203,6 @@ _ :: pe
|
||||
_ :: trace
|
||||
_ :: dynlib
|
||||
_ :: net
|
||||
_ :: ansi
|
||||
_ :: base32
|
||||
_ :: base64
|
||||
_ :: csv
|
||||
@@ -257,6 +258,8 @@ _ :: strconv
|
||||
_ :: strings
|
||||
_ :: sync
|
||||
_ :: testing
|
||||
_ :: terminal
|
||||
_ :: ansi
|
||||
_ :: scanner
|
||||
_ :: i18n
|
||||
_ :: match
|
||||
|
||||
+33
-9
@@ -441,6 +441,7 @@ struct BuildContext {
|
||||
String extra_assembler_flags;
|
||||
String microarch;
|
||||
BuildModeKind build_mode;
|
||||
bool keep_executable;
|
||||
bool generate_docs;
|
||||
bool custom_optimization_level;
|
||||
i32 optimization_level;
|
||||
@@ -2209,11 +2210,34 @@ gb_internal bool init_build_paths(String init_filename) {
|
||||
while (output_name.len > 0 && (output_name[output_name.len-1] == '/' || output_name[output_name.len-1] == '\\')) {
|
||||
output_name.len -= 1;
|
||||
}
|
||||
// Only trim the extension if it's an Odin source file.
|
||||
// This lets people build folders with extensions or files beginning with dots.
|
||||
if (path_extension(output_name) == ".odin" && !path_is_directory(output_name)) {
|
||||
output_name = remove_extension_from_path(output_name);
|
||||
}
|
||||
output_name = remove_directory_from_path(output_name);
|
||||
output_name = remove_extension_from_path(output_name);
|
||||
output_name = copy_string(ha, string_trim_whitespace(output_name));
|
||||
output_path = path_from_string(ha, output_name);
|
||||
|
||||
// This is `path_from_string` without the extension trimming.
|
||||
Path res = {};
|
||||
if (output_name.len > 0) {
|
||||
String fullpath = path_to_full_path(ha, output_name);
|
||||
defer (gb_free(ha, fullpath.text));
|
||||
|
||||
res.basename = directory_from_path(fullpath);
|
||||
res.basename = copy_string(ha, res.basename);
|
||||
|
||||
if (path_is_directory(fullpath)) {
|
||||
if (res.basename.len > 0 && res.basename.text[res.basename.len - 1] == '/') {
|
||||
res.basename.len--;
|
||||
}
|
||||
} else {
|
||||
isize name_start = (res.basename.len > 0) ? res.basename.len + 1 : res.basename.len;
|
||||
res.name = substring(fullpath, name_start, fullpath.len);
|
||||
res.name = copy_string(ha, res.name);
|
||||
}
|
||||
}
|
||||
output_path = res;
|
||||
|
||||
// Note(Dragos): This is a fix for empty filenames
|
||||
// Turn the trailing folder into the file name
|
||||
if (output_path.name.len == 0) {
|
||||
@@ -2278,9 +2302,10 @@ gb_internal bool init_build_paths(String init_filename) {
|
||||
case TargetOs_windows:
|
||||
case TargetOs_linux:
|
||||
case TargetOs_darwin:
|
||||
case TargetOs_freebsd:
|
||||
break;
|
||||
default:
|
||||
gb_printf_err("-sanitize:address is only supported on windows, linux, and darwin\n");
|
||||
gb_printf_err("-sanitize:address is only supported on Windows, Linux, Darwin, and FreeBSD\n");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -2288,12 +2313,10 @@ gb_internal bool init_build_paths(String init_filename) {
|
||||
if (build_context.sanitizer_flags & SanitizerFlag_Memory) {
|
||||
switch (build_context.metrics.os) {
|
||||
case TargetOs_linux:
|
||||
case TargetOs_freebsd:
|
||||
break;
|
||||
default:
|
||||
gb_printf_err("-sanitize:memory is only supported on linux\n");
|
||||
return false;
|
||||
}
|
||||
if (build_context.metrics.os != TargetOs_linux) {
|
||||
gb_printf_err("-sanitize:memory is only supported on Linux and FreeBSD\n");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -2302,9 +2325,10 @@ gb_internal bool init_build_paths(String init_filename) {
|
||||
switch (build_context.metrics.os) {
|
||||
case TargetOs_linux:
|
||||
case TargetOs_darwin:
|
||||
case TargetOs_freebsd:
|
||||
break;
|
||||
default:
|
||||
gb_printf_err("-sanitize:thread is only supported on linux and darwin\n");
|
||||
gb_printf_err("-sanitize:thread is only supported on Linux, Darwin, and FreeBSD\n");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
+14
-6
@@ -5461,8 +5461,18 @@ gb_internal Entity *check_selector(CheckerContext *c, Operand *operand, Ast *nod
|
||||
}
|
||||
}
|
||||
|
||||
if (operand->type && is_type_simd_vector(type_deref(operand->type))) {
|
||||
String field_name = selector->Ident.token.string;
|
||||
if (field_name.len == 1) {
|
||||
error(op_expr, "Extracting an element from a #simd array using .%.*s syntax is disallowed, prefer `simd.extract`", LIT(field_name));
|
||||
} else {
|
||||
error(op_expr, "Extracting elements from a #simd array using .%.*s syntax is disallowed, prefer `swizzle`", LIT(field_name));
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (entity == nullptr && selector->kind == Ast_Ident && operand->type != nullptr &&
|
||||
(is_type_array(type_deref(operand->type)) || is_type_simd_vector(type_deref(operand->type)))) {
|
||||
(is_type_array(type_deref(operand->type)))) {
|
||||
String field_name = selector->Ident.token.string;
|
||||
if (1 < field_name.len && field_name.len <= 4) {
|
||||
u8 swizzles_xyzw[4] = {'x', 'y', 'z', 'w'};
|
||||
@@ -5517,7 +5527,7 @@ gb_internal Entity *check_selector(CheckerContext *c, Operand *operand, Ast *nod
|
||||
|
||||
Type *original_type = operand->type;
|
||||
Type *array_type = base_type(type_deref(original_type));
|
||||
GB_ASSERT(array_type->kind == Type_Array || array_type->kind == Type_SimdVector);
|
||||
GB_ASSERT(array_type->kind == Type_Array);
|
||||
|
||||
i64 array_count = get_array_type_count(array_type);
|
||||
|
||||
@@ -5558,10 +5568,6 @@ gb_internal Entity *check_selector(CheckerContext *c, Operand *operand, Ast *nod
|
||||
break;
|
||||
}
|
||||
|
||||
if (array_type->kind == Type_SimdVector) {
|
||||
operand->mode = Addressing_Value;
|
||||
}
|
||||
|
||||
Entity *swizzle_entity = alloc_entity_variable(nullptr, make_token_ident(field_name), operand->type, EntityState_Resolved);
|
||||
add_type_and_value(c, operand->expr, operand->mode, operand->type, operand->value);
|
||||
return swizzle_entity;
|
||||
@@ -8070,7 +8076,9 @@ gb_internal ExprKind check_call_expr(CheckerContext *c, Operand *operand, Ast *c
|
||||
|
||||
if (pt->kind == Type_Proc && pt->Proc.calling_convention == ProcCC_Odin) {
|
||||
if ((c->scope->flags & ScopeFlag_ContextDefined) == 0) {
|
||||
ERROR_BLOCK();
|
||||
error(call, "'context' has not been defined within this scope, but is required for this procedure call");
|
||||
error_line("\tSuggestion: 'context = runtime.default_context()'");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -801,6 +801,21 @@ try_cross_linking:;
|
||||
// This points the linker to where the entry point is
|
||||
link_settings = gb_string_appendc(link_settings, "-e _main ");
|
||||
}
|
||||
} else if (build_context.metrics.os == TargetOs_freebsd) {
|
||||
if (build_context.sanitizer_flags & (SanitizerFlag_Address | SanitizerFlag_Memory)) {
|
||||
// It's imperative that `pthread` is linked before `libc`,
|
||||
// otherwise ASan/MSan will be unable to call `pthread_key_create`
|
||||
// because FreeBSD's `libthr` implementation of `pthread`
|
||||
// needs to replace the relevant stubs first.
|
||||
//
|
||||
// (Presumably TSan implements its own `pthread` interface,
|
||||
// which is why it isn't required.)
|
||||
//
|
||||
// See: https://reviews.llvm.org/D39254
|
||||
platform_lib_str = gb_string_appendc(platform_lib_str, "-lpthread ");
|
||||
}
|
||||
// FreeBSD pkg installs third-party shared libraries in /usr/local/lib.
|
||||
platform_lib_str = gb_string_appendc(platform_lib_str, "-Wl,-L/usr/local/lib ");
|
||||
} else if (build_context.metrics.os == TargetOs_openbsd) {
|
||||
// OpenBSD ports install shared libraries in /usr/local/lib. Also, we must explicitly link libpthread.
|
||||
platform_lib_str = gb_string_appendc(platform_lib_str, "-lpthread -Wl,-L/usr/local/lib ");
|
||||
|
||||
+24
-4
@@ -1907,6 +1907,10 @@ gb_internal lbProcedure *lb_create_startup_runtime(lbModule *main_module, lbProc
|
||||
lb_add_attribute_to_proc(p->module, p->value, "optnone");
|
||||
lb_add_attribute_to_proc(p->module, p->value, "noinline");
|
||||
|
||||
// Make sure shared libraries call their own runtime startup on Linux.
|
||||
LLVMSetVisibility(p->value, LLVMHiddenVisibility);
|
||||
LLVMSetLinkage(p->value, LLVMWeakAnyLinkage);
|
||||
|
||||
lb_begin_procedure_body(p);
|
||||
|
||||
lb_setup_type_info_data(main_module);
|
||||
@@ -2016,6 +2020,10 @@ gb_internal lbProcedure *lb_create_cleanup_runtime(lbModule *main_module) { // C
|
||||
lb_add_attribute_to_proc(p->module, p->value, "optnone");
|
||||
lb_add_attribute_to_proc(p->module, p->value, "noinline");
|
||||
|
||||
// Make sure shared libraries call their own runtime cleanup on Linux.
|
||||
LLVMSetVisibility(p->value, LLVMHiddenVisibility);
|
||||
LLVMSetLinkage(p->value, LLVMWeakAnyLinkage);
|
||||
|
||||
lb_begin_procedure_body(p);
|
||||
|
||||
CheckerInfo *info = main_module->gen->info;
|
||||
@@ -3417,36 +3425,48 @@ gb_internal bool lb_generate_code(lbGenerator *gen) {
|
||||
|
||||
|
||||
if (build_context.sanitizer_flags & SanitizerFlag_Address) {
|
||||
if (build_context.metrics.os == TargetOs_windows) {
|
||||
switch (build_context.metrics.os) {
|
||||
case TargetOs_windows: {
|
||||
auto paths = array_make<String>(heap_allocator(), 0, 1);
|
||||
String path = concatenate_strings(permanent_allocator(), build_context.ODIN_ROOT, str_lit("\\bin\\llvm\\windows\\clang_rt.asan-x86_64.lib"));
|
||||
array_add(&paths, path);
|
||||
Entity *lib = alloc_entity_library_name(nullptr, make_token_ident("asan_lib"), nullptr, slice_from_array(paths), str_lit("asan_lib"));
|
||||
array_add(&gen->foreign_libraries, lib);
|
||||
} else if (build_context.metrics.os == TargetOs_darwin || build_context.metrics.os == TargetOs_linux) {
|
||||
} break;
|
||||
case TargetOs_darwin:
|
||||
case TargetOs_linux:
|
||||
case TargetOs_freebsd:
|
||||
if (!build_context.extra_linker_flags.text) {
|
||||
build_context.extra_linker_flags = str_lit("-fsanitize=address");
|
||||
} else {
|
||||
build_context.extra_linker_flags = concatenate_strings(permanent_allocator(), build_context.extra_linker_flags, str_lit(" -fsanitize=address"));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (build_context.sanitizer_flags & SanitizerFlag_Memory) {
|
||||
if (build_context.metrics.os == TargetOs_darwin || build_context.metrics.os == TargetOs_linux) {
|
||||
switch (build_context.metrics.os) {
|
||||
case TargetOs_linux:
|
||||
case TargetOs_freebsd:
|
||||
if (!build_context.extra_linker_flags.text) {
|
||||
build_context.extra_linker_flags = str_lit("-fsanitize=memory");
|
||||
} else {
|
||||
build_context.extra_linker_flags = concatenate_strings(permanent_allocator(), build_context.extra_linker_flags, str_lit(" -fsanitize=memory"));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (build_context.sanitizer_flags & SanitizerFlag_Thread) {
|
||||
if (build_context.metrics.os == TargetOs_darwin || build_context.metrics.os == TargetOs_linux) {
|
||||
switch (build_context.metrics.os) {
|
||||
case TargetOs_darwin:
|
||||
case TargetOs_linux:
|
||||
case TargetOs_freebsd:
|
||||
if (!build_context.extra_linker_flags.text) {
|
||||
build_context.extra_linker_flags = str_lit("-fsanitize=thread");
|
||||
} else {
|
||||
build_context.extra_linker_flags = concatenate_strings(permanent_allocator(), build_context.extra_linker_flags, str_lit(" -fsanitize=thread"));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+89
-50
@@ -312,6 +312,7 @@ enum BuildFlagKind {
|
||||
BuildFlag_Collection,
|
||||
BuildFlag_Define,
|
||||
BuildFlag_BuildMode,
|
||||
BuildFlag_KeepExecutable,
|
||||
BuildFlag_Target,
|
||||
BuildFlag_Subtarget,
|
||||
BuildFlag_Debug,
|
||||
@@ -531,6 +532,7 @@ gb_internal bool parse_build_flags(Array<String> args) {
|
||||
add_flag(&build_flags, BuildFlag_Collection, str_lit("collection"), BuildFlagParam_String, Command__does_check);
|
||||
add_flag(&build_flags, BuildFlag_Define, str_lit("define"), BuildFlagParam_String, Command__does_check, true);
|
||||
add_flag(&build_flags, BuildFlag_BuildMode, str_lit("build-mode"), BuildFlagParam_String, Command__does_build); // Commands_build is not used to allow for a better error message
|
||||
add_flag(&build_flags, BuildFlag_KeepExecutable, str_lit("keep-executable"), BuildFlagParam_None, Command__does_build | Command_test);
|
||||
add_flag(&build_flags, BuildFlag_Target, str_lit("target"), BuildFlagParam_String, Command__does_check);
|
||||
add_flag(&build_flags, BuildFlag_Subtarget, str_lit("subtarget"), BuildFlagParam_String, Command__does_check);
|
||||
add_flag(&build_flags, BuildFlag_Debug, str_lit("debug"), BuildFlagParam_None, Command__does_check);
|
||||
@@ -1193,6 +1195,9 @@ gb_internal bool parse_build_flags(Array<String> args) {
|
||||
|
||||
break;
|
||||
}
|
||||
case BuildFlag_KeepExecutable:
|
||||
build_context.keep_executable = true;
|
||||
break;
|
||||
|
||||
case BuildFlag_Debug:
|
||||
build_context.ODIN_DEBUG = true;
|
||||
@@ -2364,20 +2369,26 @@ gb_internal int print_show_help(String const arg0, String command, String option
|
||||
if (print_flag("-build-mode:<mode>")) {
|
||||
print_usage_line(2, "Sets the build mode.");
|
||||
print_usage_line(2, "Available options:");
|
||||
print_usage_line(3, "-build-mode:exe Builds as an executable.");
|
||||
print_usage_line(3, "-build-mode:test Builds as an executable that executes tests.");
|
||||
print_usage_line(3, "-build-mode:dll Builds as a dynamically linked library.");
|
||||
print_usage_line(3, "-build-mode:shared Builds as a dynamically linked library.");
|
||||
print_usage_line(3, "-build-mode:dynamic Builds as a dynamically linked library.");
|
||||
print_usage_line(3, "-build-mode:lib Builds as a statically linked library.");
|
||||
print_usage_line(3, "-build-mode:static Builds as a statically linked library.");
|
||||
print_usage_line(3, "-build-mode:obj Builds as an object file.");
|
||||
print_usage_line(3, "-build-mode:object Builds as an object file.");
|
||||
print_usage_line(3, "-build-mode:assembly Builds as an assembly file.");
|
||||
print_usage_line(3, "-build-mode:assembler Builds as an assembly file.");
|
||||
print_usage_line(3, "-build-mode:asm Builds as an assembly file.");
|
||||
print_usage_line(3, "-build-mode:llvm-ir Builds as an LLVM IR file.");
|
||||
print_usage_line(3, "-build-mode:llvm Builds as an LLVM IR file.");
|
||||
print_usage_line(3, "-build-mode:exe Builds as an executable.");
|
||||
print_usage_line(3, "-build-mode:test Builds as an executable that executes tests.");
|
||||
print_usage_line(3, "-build-mode:dll Builds as a dynamically linked library.");
|
||||
print_usage_line(3, "-build-mode:shared Builds as a dynamically linked library.");
|
||||
print_usage_line(3, "-build-mode:dynamic Builds as a dynamically linked library.");
|
||||
print_usage_line(3, "-build-mode:lib Builds as a statically linked library.");
|
||||
print_usage_line(3, "-build-mode:static Builds as a statically linked library.");
|
||||
print_usage_line(3, "-build-mode:obj Builds as an object file.");
|
||||
print_usage_line(3, "-build-mode:object Builds as an object file.");
|
||||
print_usage_line(3, "-build-mode:assembly Builds as an assembly file.");
|
||||
print_usage_line(3, "-build-mode:assembler Builds as an assembly file.");
|
||||
print_usage_line(3, "-build-mode:asm Builds as an assembly file.");
|
||||
print_usage_line(3, "-build-mode:llvm-ir Builds as an LLVM IR file.");
|
||||
print_usage_line(3, "-build-mode:llvm Builds as an LLVM IR file.");
|
||||
}
|
||||
}
|
||||
|
||||
if (test_only) {
|
||||
if (print_flag("-build-only")) {
|
||||
print_usage_line(2, "Only builds the test executable; does not automatically run it afterwards.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2386,16 +2397,16 @@ gb_internal int print_show_help(String const arg0, String command, String option
|
||||
print_usage_line(2, "Defines a library collection used for imports.");
|
||||
print_usage_line(2, "Example: -collection:shared=dir/to/shared");
|
||||
print_usage_line(2, "Usage in Code:");
|
||||
print_usage_line(3, "import \"shared:foo\"");
|
||||
print_usage_line(3, "import \"shared:foo\"");
|
||||
}
|
||||
|
||||
if (print_flag("-custom-attribute:<string>")) {
|
||||
print_usage_line(2, "Add a custom attribute which will be ignored if it is unknown.");
|
||||
print_usage_line(2, "This can be used with metaprogramming tools.");
|
||||
print_usage_line(2, "Examples:");
|
||||
print_usage_line(3, "-custom-attribute:my_tag");
|
||||
print_usage_line(3, "-custom-attribute:my_tag,the_other_thing");
|
||||
print_usage_line(3, "-custom-attribute:my_tag -custom-attribute:the_other_thing");
|
||||
print_usage_line(3, "-custom-attribute:my_tag");
|
||||
print_usage_line(3, "-custom-attribute:my_tag,the_other_thing");
|
||||
print_usage_line(3, "-custom-attribute:my_tag -custom-attribute:the_other_thing");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2418,7 +2429,7 @@ gb_internal int print_show_help(String const arg0, String command, String option
|
||||
print_usage_line(2, "Defines a scalar boolean, integer or string as global constant.");
|
||||
print_usage_line(2, "Example: -define:SPAM=123");
|
||||
print_usage_line(2, "Usage in code:");
|
||||
print_usage_line(3, "#config(SPAM, default_value)");
|
||||
print_usage_line(3, "#config(SPAM, default_value)");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2453,9 +2464,9 @@ gb_internal int print_show_help(String const arg0, String command, String option
|
||||
if (check) {
|
||||
if (print_flag("-error-pos-style:<string>")) {
|
||||
print_usage_line(2, "Available options:");
|
||||
print_usage_line(3, "-error-pos-style:unix file/path:45:3:");
|
||||
print_usage_line(3, "-error-pos-style:odin file/path(45:3)");
|
||||
print_usage_line(3, "-error-pos-style:default (Defaults to 'odin'.)");
|
||||
print_usage_line(3, "-error-pos-style:unix file/path:45:3:");
|
||||
print_usage_line(3, "-error-pos-style:odin file/path(45:3)");
|
||||
print_usage_line(3, "-error-pos-style:default (Defaults to 'odin'.)");
|
||||
}
|
||||
|
||||
if (print_flag("-export-defineables:<filename>")) {
|
||||
@@ -2466,8 +2477,8 @@ gb_internal int print_show_help(String const arg0, String command, String option
|
||||
if (print_flag("-export-dependencies:<format>")) {
|
||||
print_usage_line(2, "Exports dependencies to one of a few formats. Requires `-export-dependencies-file`.");
|
||||
print_usage_line(2, "Available options:");
|
||||
print_usage_line(3, "-export-dependencies:make Exports in Makefile format");
|
||||
print_usage_line(3, "-export-dependencies:json Exports in JSON format");
|
||||
print_usage_line(3, "-export-dependencies:make Exports in Makefile format");
|
||||
print_usage_line(3, "-export-dependencies:json Exports in JSON format");
|
||||
}
|
||||
|
||||
if (print_flag("-export-dependencies-file:<filename>")) {
|
||||
@@ -2478,8 +2489,8 @@ gb_internal int print_show_help(String const arg0, String command, String option
|
||||
if (print_flag("-export-timings:<format>")) {
|
||||
print_usage_line(2, "Exports timings to one of a few formats. Requires `-show-timings` or `-show-more-timings`.");
|
||||
print_usage_line(2, "Available options:");
|
||||
print_usage_line(3, "-export-timings:json Exports compile time stats to JSON.");
|
||||
print_usage_line(3, "-export-timings:csv Exports compile time stats to CSV.");
|
||||
print_usage_line(3, "-export-timings:json Exports compile time stats to JSON.");
|
||||
print_usage_line(3, "-export-timings:csv Exports compile time stats to CSV.");
|
||||
}
|
||||
|
||||
if (print_flag("-export-timings-file:<filename>")) {
|
||||
@@ -2543,6 +2554,14 @@ gb_internal int print_show_help(String const arg0, String command, String option
|
||||
}
|
||||
}
|
||||
|
||||
if (test_only || run_or_build) {
|
||||
if (print_flag("-keep-executable")) {
|
||||
print_usage_line(2, "Keep the executable generated by `odin test` or `odin run` after running it. We clean it up by default.");
|
||||
print_usage_line(2, "If you build your program or test using `odin build`, the compiler does not automatically execute");
|
||||
print_usage_line(2, "the resulting program, and this option is not applicable.");
|
||||
}
|
||||
}
|
||||
|
||||
if (run_or_build) {
|
||||
if (print_flag("-linker:<string>")) {
|
||||
print_usage_line(2, "Specify the linker to use.");
|
||||
@@ -2569,9 +2588,9 @@ gb_internal int print_show_help(String const arg0, String command, String option
|
||||
if (print_flag("-microarch:<string>")) {
|
||||
print_usage_line(2, "Specifies the specific micro-architecture for the build in a string.");
|
||||
print_usage_line(2, "Examples:");
|
||||
print_usage_line(3, "-microarch:sandybridge");
|
||||
print_usage_line(3, "-microarch:native");
|
||||
print_usage_line(3, "-microarch:\"?\" for a list");
|
||||
print_usage_line(3, "-microarch:sandybridge");
|
||||
print_usage_line(3, "-microarch:native");
|
||||
print_usage_line(3, "-microarch:\"?\" for a list");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2628,10 +2647,10 @@ gb_internal int print_show_help(String const arg0, String command, String option
|
||||
if (print_flag("-o:<string>")) {
|
||||
print_usage_line(2, "Sets the optimization mode for compilation.");
|
||||
print_usage_line(2, "Available options:");
|
||||
print_usage_line(3, "-o:none");
|
||||
print_usage_line(3, "-o:minimal");
|
||||
print_usage_line(3, "-o:size");
|
||||
print_usage_line(3, "-o:speed");
|
||||
print_usage_line(3, "-o:none");
|
||||
print_usage_line(3, "-o:minimal");
|
||||
print_usage_line(3, "-o:size");
|
||||
print_usage_line(3, "-o:speed");
|
||||
if (LB_USE_NEW_PASS_SYSTEM) {
|
||||
print_usage_line(3, "-o:aggressive (use this with caution)");
|
||||
}
|
||||
@@ -2682,10 +2701,10 @@ gb_internal int print_show_help(String const arg0, String command, String option
|
||||
if (print_flag("-reloc-mode:<string>")) {
|
||||
print_usage_line(2, "Specifies the reloc mode.");
|
||||
print_usage_line(2, "Available options:");
|
||||
print_usage_line(3, "-reloc-mode:default");
|
||||
print_usage_line(3, "-reloc-mode:static");
|
||||
print_usage_line(3, "-reloc-mode:pic");
|
||||
print_usage_line(3, "-reloc-mode:dynamic-no-pic");
|
||||
print_usage_line(3, "-reloc-mode:default");
|
||||
print_usage_line(3, "-reloc-mode:static");
|
||||
print_usage_line(3, "-reloc-mode:pic");
|
||||
print_usage_line(3, "-reloc-mode:dynamic-no-pic");
|
||||
}
|
||||
|
||||
#if defined(GB_SYSTEM_WINDOWS)
|
||||
@@ -2700,9 +2719,9 @@ gb_internal int print_show_help(String const arg0, String command, String option
|
||||
if (print_flag("-sanitize:<string>")) {
|
||||
print_usage_line(2, "Enables sanitization analysis.");
|
||||
print_usage_line(2, "Available options:");
|
||||
print_usage_line(3, "-sanitize:address");
|
||||
print_usage_line(3, "-sanitize:memory");
|
||||
print_usage_line(3, "-sanitize:thread");
|
||||
print_usage_line(3, "-sanitize:address");
|
||||
print_usage_line(3, "-sanitize:memory");
|
||||
print_usage_line(3, "-sanitize:thread");
|
||||
print_usage_line(2, "NOTE: This flag can be used multiple times.");
|
||||
}
|
||||
}
|
||||
@@ -2763,17 +2782,32 @@ gb_internal int print_show_help(String const arg0, String command, String option
|
||||
print_usage_line(2, "[Windows only]");
|
||||
print_usage_line(2, "Defines the subsystem for the application.");
|
||||
print_usage_line(2, "Available options:");
|
||||
print_usage_line(3, "-subsystem:console");
|
||||
print_usage_line(3, "-subsystem:windows");
|
||||
print_usage_line(3, "-subsystem:console");
|
||||
print_usage_line(3, "-subsystem:windows");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
if (build) {
|
||||
if (print_flag("-subtarget:<subtarget>")) {
|
||||
print_usage_line(2, "[Darwin and Linux only]");
|
||||
print_usage_line(2, "Available subtargets:");
|
||||
String prefix = str_lit("-subtarget:");
|
||||
for (u32 i = 1; i < Subtarget_COUNT; i++) {
|
||||
String name = subtarget_strings[i];
|
||||
String help_string = concatenate_strings(temporary_allocator(), prefix, name);
|
||||
print_usage_line(3, (const char *)help_string.text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (run_or_build) {
|
||||
if (print_flag("-target-features:<string>")) {
|
||||
print_usage_line(2, "Specifies CPU features to enable on top of the enabled features implied by -microarch.");
|
||||
print_usage_line(2, "Examples:");
|
||||
print_usage_line(3, "-target-features:atomics");
|
||||
print_usage_line(3, "-target-features:\"sse2,aes\"");
|
||||
print_usage_line(3, "-target-features:\"?\" for a list");
|
||||
print_usage_line(3, "-target-features:atomics");
|
||||
print_usage_line(3, "-target-features:\"sse2,aes\"");
|
||||
print_usage_line(3, "-target-features:\"?\" for a list");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2810,11 +2844,11 @@ gb_internal int print_show_help(String const arg0, String command, String option
|
||||
if (print_flag("-vet")) {
|
||||
print_usage_line(2, "Does extra checks on the code.");
|
||||
print_usage_line(2, "Extra checks include:");
|
||||
print_usage_line(3, "-vet-unused");
|
||||
print_usage_line(3, "-vet-unused-variables");
|
||||
print_usage_line(3, "-vet-unused-imports");
|
||||
print_usage_line(3, "-vet-shadowing");
|
||||
print_usage_line(3, "-vet-using-stmt");
|
||||
print_usage_line(3, "-vet-unused");
|
||||
print_usage_line(3, "-vet-unused-variables");
|
||||
print_usage_line(3, "-vet-unused-imports");
|
||||
print_usage_line(3, "-vet-shadowing");
|
||||
print_usage_line(3, "-vet-using-stmt");
|
||||
}
|
||||
|
||||
if (print_flag("-vet-cast")) {
|
||||
@@ -3851,6 +3885,11 @@ end_of_code_gen:;
|
||||
defer (gb_free(heap_allocator(), exe_name.text));
|
||||
|
||||
system_must_exec_command_line_app("odin run", "\"%.*s\" %.*s", LIT(exe_name), LIT(run_args_string));
|
||||
|
||||
if (!build_context.keep_executable) {
|
||||
char const *filename = cast(char const *)exe_name.text;
|
||||
gb_file_remove(filename);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -103,9 +103,11 @@ expensive_for_backtrackers :: proc(t: ^testing.T) {
|
||||
|
||||
@test
|
||||
global_capture_end_word :: proc(t: ^testing.T) {
|
||||
// NOTE: The previous behavior of `.Global`, which was to automatically
|
||||
// insert `.*?` at the start of the pattern, is now default.
|
||||
EXPR :: `Hellope World!`
|
||||
|
||||
rex, err := regex.create(EXPR, { .Global })
|
||||
rex, err := regex.create(EXPR, { /*.Global*/ })
|
||||
if !testing.expect_value(t, err, nil) {
|
||||
return
|
||||
}
|
||||
@@ -145,7 +147,7 @@ global_capture_end_word_unicode :: proc(t: ^testing.T) {
|
||||
EXPR :: `こにちは`
|
||||
needle := string(EXPR)
|
||||
|
||||
rex, err := regex.create(EXPR, { .Global, .Unicode })
|
||||
rex, err := regex.create(EXPR, { /*.Global,*/ .Unicode })
|
||||
if !testing.expect_value(t, err, nil) {
|
||||
return
|
||||
}
|
||||
@@ -185,7 +187,7 @@ global_capture_end_word_unicode :: proc(t: ^testing.T) {
|
||||
alternations :: proc(t: ^testing.T) {
|
||||
EXPR :: `a(?:bb|cc|dd|ee|ff)`
|
||||
|
||||
rex, err := regex.create(EXPR, { .No_Capture, .Global })
|
||||
rex, err := regex.create(EXPR, { .No_Capture, /*.Global*/ })
|
||||
if !testing.expect_value(t, err, nil) {
|
||||
return
|
||||
}
|
||||
@@ -219,7 +221,7 @@ classes :: proc(t: ^testing.T) {
|
||||
EXPR :: `[\w\d]+`
|
||||
NEEDLE :: "0123456789abcdef"
|
||||
|
||||
rex, err := regex.create(EXPR, { .Global })
|
||||
rex, err := regex.create(EXPR, { /*.Global*/ })
|
||||
if !testing.expect_value(t, err, nil) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -51,13 +51,13 @@ check_expression_with_flags :: proc(t: ^testing.T, pattern: string, flags: regex
|
||||
}
|
||||
|
||||
check_expression :: proc(t: ^testing.T, pattern, haystack: string, needles: ..string, extra_flags := regex.Flags{}, loc := #caller_location) {
|
||||
check_expression_with_flags(t, pattern, { .Global } + extra_flags,
|
||||
check_expression_with_flags(t, pattern, extra_flags,
|
||||
haystack, ..needles, loc = loc)
|
||||
check_expression_with_flags(t, pattern, { .Global, .No_Optimization } + extra_flags,
|
||||
check_expression_with_flags(t, pattern, { .No_Optimization } + extra_flags,
|
||||
haystack, ..needles, loc = loc)
|
||||
check_expression_with_flags(t, pattern, { .Global, .Unicode } + extra_flags,
|
||||
check_expression_with_flags(t, pattern, { .Unicode } + extra_flags,
|
||||
haystack, ..needles, loc = loc)
|
||||
check_expression_with_flags(t, pattern, { .Global, .Unicode, .No_Optimization } + extra_flags,
|
||||
check_expression_with_flags(t, pattern, { .Unicode, .No_Optimization } + extra_flags,
|
||||
haystack, ..needles, loc = loc)
|
||||
}
|
||||
|
||||
@@ -72,17 +72,18 @@ expect_error :: proc(t: ^testing.T, pattern: string, expected_error: typeid, fla
|
||||
testing.expect_value(t, variant_ti, expected_ti, loc = loc)
|
||||
}
|
||||
|
||||
check_capture :: proc(t: ^testing.T, got, expected: regex.Capture, loc := #caller_location) {
|
||||
testing.expect_value(t, len(got.pos), len(got.groups), loc = loc)
|
||||
testing.expect_value(t, len(got.pos), len(expected.pos), loc = loc)
|
||||
testing.expect_value(t, len(got.groups), len(expected.groups), loc = loc)
|
||||
check_capture :: proc(t: ^testing.T, got, expected: regex.Capture, loc := #caller_location) -> (ok: bool) {
|
||||
testing.expect_value(t, len(got.pos), len(got.groups), loc = loc) or_return
|
||||
testing.expect_value(t, len(got.pos), len(expected.pos), loc = loc) or_return
|
||||
testing.expect_value(t, len(got.groups), len(expected.groups), loc = loc) or_return
|
||||
|
||||
if len(got.pos) == len(expected.pos) {
|
||||
for i in 0..<len(got.pos) {
|
||||
testing.expect_value(t, got.pos[i], expected.pos[i], loc = loc)
|
||||
testing.expect_value(t, got.groups[i], expected.groups[i], loc = loc)
|
||||
testing.expect_value(t, got.pos[i], expected.pos[i], loc = loc) or_return
|
||||
testing.expect_value(t, got.groups[i], expected.groups[i], loc = loc) or_return
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@test
|
||||
@@ -212,6 +213,12 @@ test_alternation :: proc(t: ^testing.T) {
|
||||
check_expression(t, EXPR, "cc", "cc")
|
||||
}
|
||||
|
||||
@test
|
||||
test_alternation_order :: proc(t: ^testing.T) {
|
||||
check_expression(t, "a|ab", "ab", "a")
|
||||
check_expression(t, "ab|a", "ab", "ab")
|
||||
}
|
||||
|
||||
@test
|
||||
test_optional :: proc(t: ^testing.T) {
|
||||
EXPR :: "a?a?a?aaa"
|
||||
@@ -516,7 +523,7 @@ test_pos_index_explicitly :: proc(t: ^testing.T) {
|
||||
STR :: "This is an island."
|
||||
EXPR :: `\bis\b`
|
||||
|
||||
rex, err := regex.create(EXPR, { .Global })
|
||||
rex, err := regex.create(EXPR)
|
||||
if !testing.expect_value(t, err, nil) {
|
||||
return
|
||||
}
|
||||
@@ -642,9 +649,9 @@ test_unicode_explicitly :: proc(t: ^testing.T) {
|
||||
}
|
||||
{
|
||||
EXPR :: "こにちは!"
|
||||
check_expression_with_flags(t, EXPR, { .Global, .Unicode },
|
||||
check_expression_with_flags(t, EXPR, { .Unicode },
|
||||
"Hello こにちは!", "こにちは!")
|
||||
check_expression_with_flags(t, EXPR, { .Global, .Unicode, .No_Optimization },
|
||||
check_expression_with_flags(t, EXPR, { .Unicode, .No_Optimization },
|
||||
"Hello こにちは!", "こにちは!")
|
||||
}
|
||||
}
|
||||
@@ -901,12 +908,12 @@ test_everything_at_once :: proc(t: ^testing.T) {
|
||||
@test
|
||||
test_creation_from_user_string :: proc(t: ^testing.T) {
|
||||
{
|
||||
USER_EXPR :: `/^hellope$/gmixun-`
|
||||
USER_EXPR :: `/^hellope$/mixun-`
|
||||
STR :: "hellope"
|
||||
rex, err := regex.create_by_user(USER_EXPR)
|
||||
defer regex.destroy(rex)
|
||||
testing.expect_value(t, err, nil)
|
||||
testing.expect_value(t, rex.flags, regex.Flags{ .Global, .Multiline, .Case_Insensitive, .Ignore_Whitespace, .Unicode, .No_Capture, .No_Optimization })
|
||||
testing.expect_value(t, rex.flags, regex.Flags{ .Multiline, .Case_Insensitive, .Ignore_Whitespace, .Unicode, .No_Capture, .No_Optimization })
|
||||
|
||||
_, ok := regex.match(rex, STR)
|
||||
testing.expectf(t, ok, "expected user-provided RegEx %v to match %q", rex, STR)
|
||||
@@ -1102,24 +1109,121 @@ Iterator_Test :: struct {
|
||||
|
||||
iterator_vectors := []Iterator_Test{
|
||||
{
|
||||
`xxab32ab52xx`, `(ab\d{1})`, {}, // {.Global} implicitly added by the iterator
|
||||
`xxab32ab52xx`, `(ab\d{1})`, {},
|
||||
{
|
||||
{pos = {{2, 5}, {2, 5}}, groups = {"ab3", "ab3"}},
|
||||
{pos = {{6, 9}, {6, 9}}, groups = {"ab5", "ab5"}},
|
||||
},
|
||||
},
|
||||
{
|
||||
`xxfoobarxfoobarxx`, `f(o)ob(ar)`, {.Global},
|
||||
`xxfoobarxfoobarxx`, `f(o)ob(ar)`, {},
|
||||
{
|
||||
{pos = {{2, 8}, {3, 4}, {6, 8}}, groups = {"foobar", "o", "ar"}},
|
||||
{pos = {{9, 15}, {10, 11}, {13, 15}}, groups = {"foobar", "o", "ar"}},
|
||||
},
|
||||
},
|
||||
{
|
||||
`aaa`, `a`, {},
|
||||
{
|
||||
{pos = {{0, 1}}, groups = {"a"}},
|
||||
{pos = {{1, 2}}, groups = {"a"}},
|
||||
{pos = {{2, 3}}, groups = {"a"}},
|
||||
},
|
||||
},
|
||||
{
|
||||
`aaa`, `\w`, {},
|
||||
{
|
||||
{pos = {{0, 1}}, groups = {"a"}},
|
||||
{pos = {{1, 2}}, groups = {"a"}},
|
||||
{pos = {{2, 3}}, groups = {"a"}},
|
||||
},
|
||||
},
|
||||
{
|
||||
`aかか`, `.`, {.Unicode},
|
||||
{
|
||||
{pos = {{0, 1}}, groups = {"a"}},
|
||||
{pos = {{1, 4}}, groups = {"か"}},
|
||||
{pos = {{4, 7}}, groups = {"か"}},
|
||||
},
|
||||
},
|
||||
{
|
||||
`ゆめ.`, `.`, {.Unicode},
|
||||
{
|
||||
{pos = {{0, 3}}, groups = {"ゆ"}},
|
||||
{pos = {{3, 6}}, groups = {"め"}},
|
||||
{pos = {{6, 7}}, groups = {"."}},
|
||||
},
|
||||
},
|
||||
// This pattern is not anchored, so this is valid per the new behavior.
|
||||
{
|
||||
`ababa`, `(?:a|ab)`, {},
|
||||
{
|
||||
{pos = {{0, 1}}, groups = {"a"}},
|
||||
{pos = {{2, 3}}, groups = {"a"}},
|
||||
{pos = {{4, 5}}, groups = {"a"}},
|
||||
},
|
||||
},
|
||||
// Ensure alternation follows expected ordering of left-higher priority.
|
||||
{
|
||||
`ababa`, `(?:ab|a)`, {},
|
||||
{
|
||||
{pos = {{0, 2}}, groups = {"ab"}},
|
||||
{pos = {{2, 4}}, groups = {"ab"}},
|
||||
{pos = {{4, 5}}, groups = {"a"}},
|
||||
},
|
||||
},
|
||||
// This one is anchored, so only one match.
|
||||
//
|
||||
// This test ensures the behavior of `Assert_Start` is checking against the
|
||||
// start of the string and we haven't just slid the memory string itself.
|
||||
{
|
||||
`ababa`, `^(?:a|b)`, {},
|
||||
{
|
||||
{pos = {{0, 1}}, groups = {"a"}},
|
||||
},
|
||||
},
|
||||
{
|
||||
`ababa`, `a$`, {},
|
||||
{
|
||||
{pos = {{4, 5}}, groups = {"a"}},
|
||||
},
|
||||
},
|
||||
// A blank pattern on a blank string is valid and must not loop infinitely.
|
||||
{
|
||||
``, ``, {},
|
||||
{
|
||||
{pos = {{0, 0}}, groups = {""}},
|
||||
},
|
||||
},
|
||||
// These too are valid.
|
||||
//
|
||||
// The iterator must bail out when it encounters any situation which would
|
||||
// loop infinitely, but only after giving at least one valid match if one
|
||||
// exists, as this would accord with attempting to singularly match the
|
||||
// pattern against the string and provide a positive result.
|
||||
{
|
||||
`a`, ``, {},
|
||||
{
|
||||
{pos = {{0, 0}}, groups = {""}},
|
||||
},
|
||||
},
|
||||
{
|
||||
``, `$`, {},
|
||||
{
|
||||
{pos = {{0, 0}}, groups = {""}},
|
||||
},
|
||||
},
|
||||
{
|
||||
`aaa`, `$`, {},
|
||||
{
|
||||
{pos = {{3, 3}}, groups = {""}},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@test
|
||||
test_match_iterator :: proc(t: ^testing.T) {
|
||||
for test in iterator_vectors {
|
||||
vector: for test in iterator_vectors {
|
||||
it, err := regex.create_iterator(test.haystack, test.pattern, test.flags)
|
||||
defer regex.destroy(it)
|
||||
|
||||
@@ -1128,10 +1232,61 @@ test_match_iterator :: proc(t: ^testing.T) {
|
||||
|
||||
for capture, idx in regex.match(&it) {
|
||||
if idx >= len(test.expected) {
|
||||
log.errorf("got more than expected number of captures for matching string %q against pattern %q\n\tidx %i = %v", test.haystack, test.pattern, idx, capture)
|
||||
continue vector
|
||||
}
|
||||
if !check_capture(t, capture, test.expected[idx]) {
|
||||
log.errorf("capture check failed on string %q against pattern %q", test.haystack, test.pattern)
|
||||
}
|
||||
}
|
||||
testing.expectf(t, it.idx == len(test.expected), "expected idx to be %i, got %i on string %q against pattern %q", len(test.expected), it.idx, test.haystack, test.pattern)
|
||||
}
|
||||
}
|
||||
|
||||
@test
|
||||
test_iterator_reset :: proc(t: ^testing.T) {
|
||||
test := Iterator_Test{
|
||||
`aaa`, `a`, {},
|
||||
{
|
||||
{pos = {{0, 1}}, groups = {"a"}},
|
||||
{pos = {{1, 2}}, groups = {"a"}},
|
||||
{pos = {{2, 3}}, groups = {"a"}},
|
||||
},
|
||||
}
|
||||
|
||||
out: {
|
||||
it, err := regex.create_iterator(`aaa`, `a`)
|
||||
defer regex.destroy(it)
|
||||
|
||||
testing.expect_value(t, err, nil)
|
||||
(err == nil) or_break out
|
||||
|
||||
for capture, idx in regex.match(&it) {
|
||||
if idx >= len(test.expected) {
|
||||
log.errorf("got more than expected number of captures for matching string %q against pattern %q\n\tidx %i = %v", test.haystack, test.pattern, idx, capture)
|
||||
break
|
||||
}
|
||||
check_capture(t, capture, test.expected[idx])
|
||||
}
|
||||
testing.expect_value(t, it.idx, len(test.expected))
|
||||
|
||||
// Do it again.
|
||||
iterations := 0
|
||||
regex.reset(&it)
|
||||
|
||||
// Mind that this loop can do nothing if it wasn't reset but leave us
|
||||
// with the expected `idx` state.
|
||||
//
|
||||
// That's why we count iterations this time around.
|
||||
for capture, idx in regex.match(&it) {
|
||||
iterations += 1
|
||||
if idx >= len(test.expected) {
|
||||
log.errorf("got more than expected number of captures for matching string %q against pattern %q\n\tidx %i = %v", test.haystack, test.pattern, idx, capture)
|
||||
break
|
||||
}
|
||||
check_capture(t, capture, test.expected[idx])
|
||||
}
|
||||
testing.expect_value(t, it.idx, len(test.expected))
|
||||
testing.expect_value(t, iterations, len(test.expected))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+1
-1
@@ -87,7 +87,7 @@ foreign lib {
|
||||
RWseek :: proc(ctx: ^RWops, offset: i64, whence: c.int) -> i64 ---
|
||||
RWtell :: proc(ctx: ^RWops) -> i64 ---
|
||||
RWread :: proc(ctx: ^RWops, ptr: rawptr, size: c.size_t, maxnum: c.size_t) -> c.size_t ---
|
||||
RWwrite :: proc(ctx: ^RWops, size: c.size_t, num: c.size_t) -> c.size_t ---
|
||||
RWwrite :: proc(ctx: ^RWops, ptr: rawptr, size: c.size_t, num: c.size_t) -> c.size_t ---
|
||||
RWclose :: proc(ctx: ^RWops) -> c.int ---
|
||||
|
||||
LoadFile_RW :: proc(src: ^RWops, datasize: ^c.size_t, freesrc: bool) -> rawptr ---
|
||||
|
||||
Vendored
+12
-4
@@ -1,8 +1,8 @@
|
||||
package sdl3
|
||||
|
||||
Mutex :: struct {}
|
||||
RWLock :: struct {}
|
||||
|
||||
Mutex :: struct {}
|
||||
RWLock :: struct {}
|
||||
Semaphore :: struct {}
|
||||
|
||||
@(default_calling_convention="c", link_prefix="SDL_", require_results)
|
||||
foreign lib {
|
||||
@@ -19,4 +19,12 @@ foreign lib {
|
||||
TryLockRWLockForWriting :: proc(rwlock: ^RWLock) -> bool ---
|
||||
UnlockRWLock :: proc(rwlock: ^RWLock) ---
|
||||
DestroyRWLock :: proc(rwlock: ^RWLock) ---
|
||||
}
|
||||
|
||||
CreateSemaphore :: proc(initial_value: Uint32) -> ^Semaphore ---
|
||||
DestroySemaphore :: proc(sem: ^Semaphore) ---
|
||||
GetSemaphoreValue :: proc(sem: ^Semaphore) -> Uint32 ---
|
||||
SignalSemaphore :: proc(sem: ^Semaphore) ---
|
||||
TryWaitSemaphore :: proc(sem: ^Semaphore) -> bool ---
|
||||
WaitSemaphore :: proc(sem: ^Semaphore) ---
|
||||
WaitSemaphoreTimeout :: proc(sem: ^Semaphore, timeout_ms: Sint32) ---
|
||||
}
|
||||
|
||||
Vendored
+1
-1
@@ -2033,7 +2033,7 @@ class WebGPUInterface {
|
||||
addressModeW: this.enumeration("AddressMode", off(4)),
|
||||
magFilter: this.enumeration("FilterMode", off(4)),
|
||||
minFilter: this.enumeration("FilterMode", off(4)),
|
||||
mipMapFilter: this.enumeration("MipmapFilterMode", off(4)),
|
||||
mipmapFilter: this.enumeration("MipmapFilterMode", off(4)),
|
||||
lodMinClamp: this.mem.loadF32(off(4)),
|
||||
lodMaxClamp: this.mem.loadF32(off(4)),
|
||||
compare: this.enumeration("CompareFunction", off(4)),
|
||||
|
||||
Reference in New Issue
Block a user