Merge branch 'odin-lang:master' into badaxis/Windows-Audio&Winmm

This commit is contained in:
Vincent Billet
2025-05-26 08:16:45 +02:00
committed by GitHub
49 changed files with 978 additions and 322 deletions
@@ -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) { remove :: proc(pq: ^$Q/Priority_Queue($T), i: int) -> (value: T, ok: bool) {
n := builtin.len(pq.queue) n := builtin.len(pq.queue)
if 0 <= i && i < n { if 0 <= i && i < n {
if n != i { pq.swap(pq.queue[:], i, n-1)
pq.swap(pq.queue[:], i, n) _shift_down(pq, i, n-1)
_shift_down(pq, i, n) _shift_up(pq, i)
_shift_up(pq, i) value, ok = builtin.pop(&pq.queue), true
}
value, ok = builtin.pop_safe(&pq.queue)
} }
return return
} }
+1 -1
View File
@@ -6,7 +6,7 @@ import "core:sys/info"
// is_supported returns true iff hardware accelerated AES // is_supported returns true iff hardware accelerated AES
// is supported. // is supported.
is_supported :: proc "contextless" () -> bool { is_supported :: proc "contextless" () -> bool {
features, ok := info.cpu_features.? features, ok := info.cpu.features.?
if !ok { if !ok {
return false return false
} }
@@ -227,7 +227,7 @@ is_performant :: proc "contextless" () -> bool {
req_features :: info.CPU_Features{.V} req_features :: info.CPU_Features{.V}
} }
features, ok := info.cpu_features.? features, ok := info.cpu.features.?
if !ok { if !ok {
return false return false
} }
@@ -41,7 +41,7 @@ _VEC_TWO: simd.u64x4 : {2, 0, 2, 0}
is_performant :: proc "contextless" () -> bool { is_performant :: proc "contextless" () -> bool {
req_features :: info.CPU_Features{.avx, .avx2} req_features :: info.CPU_Features{.avx, .avx2}
features, ok := info.cpu_features.? features, ok := info.cpu.features.?
if !ok { if !ok {
return false return false
} }
+1 -1
View File
@@ -52,7 +52,7 @@ K_15 :: simd.u64x2{0xa4506ceb90befffa, 0xc67178f2bef9a3f7}
// is_hardware_accelerated_256 returns true iff hardware accelerated // is_hardware_accelerated_256 returns true iff hardware accelerated
// SHA-224/SHA-256 is supported. // SHA-224/SHA-256 is supported.
is_hardware_accelerated_256 :: proc "contextless" () -> bool { is_hardware_accelerated_256 :: proc "contextless" () -> bool {
features, ok := info.cpu_features.? features, ok := info.cpu.features.?
if !ok { if !ok {
return false return false
} }
+53 -11
View File
@@ -2,10 +2,12 @@
#+build !orca #+build !orca
package log package log
import "core:encoding/ansi" import "base:runtime"
import "core:fmt" import "core:fmt"
import "core:strings" import "core:strings"
import "core:os" import "core:os"
import "core:terminal"
import "core:terminal/ansi"
import "core:time" import "core:time"
Level_Headers := [?]string{ Level_Headers := [?]string{
@@ -37,11 +39,36 @@ File_Console_Logger_Data :: struct {
ident: string, 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 { 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 := new(File_Console_Logger_Data, allocator)
data.file_handle = h data.file_handle = h
data.ident = ident 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) { 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 := new(File_Console_Logger_Data, allocator)
data.file_handle = os.INVALID_HANDLE data.file_handle = os.INVALID_HANDLE
data.ident = ident 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) { destroy_console_logger :: proc(log: Logger, allocator := context.allocator) {
free(log.data, allocator) free(log.data, allocator)
} }
file_console_logger_proc :: proc(logger_data: rawptr, level: Level, text: string, options: Options, location := #caller_location) { @(private)
data := cast(^File_Console_Logger_Data)logger_data _file_console_logger_proc :: proc(h: os.Handle, ident: string, level: Level, text: string, options: Options, location: runtime.Source_Code_Location) {
h: os.Handle = os.stdout if level <= Level.Error else os.stderr
if data.file_handle != os.INVALID_HANDLE {
h = data.file_handle
}
backing: [1024]byte //NOTE(Hoej): 1024 might be too much for a header backing, unless somebody has really long paths. 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[:]) 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()) fmt.sbprintf(&buf, "[{}] ", os.current_thread_id())
} }
if data.ident != "" { if ident != "" {
fmt.sbprintf(&buf, "[%s] ", data.ident) fmt.sbprintf(&buf, "[%s] ", ident)
} }
//TODO(Hoej): When we have better atomics and such, make this thread-safe //TODO(Hoej): When we have better atomics and such, make this thread-safe
fmt.fprintf(h, "%s%s\n", strings.to_string(buf), text) 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) { do_level_header :: proc(opts: Options, str: ^strings.Builder, level: Level) {
RESET :: ansi.CSI + ansi.RESET + ansi.SGR RESET :: ansi.CSI + ansi.RESET + ansi.SGR
+1 -1
View File
@@ -280,7 +280,7 @@ int_atoi :: proc(res: ^Int, input: string, radix := i8(10), allocator := context
} }
pos := ch - '+' pos := ch - '+'
if RADIX_TABLE_REVERSE_SIZE <= pos { if RADIX_TABLE_REVERSE_SIZE <= u32(pos) {
break break
} }
y := RADIX_TABLE_REVERSE[pos] y := RADIX_TABLE_REVERSE[pos]
+1 -1
View File
@@ -477,7 +477,7 @@ block_mark_as_free :: proc(block: ^Block_Header) {
} }
@(private, no_sanitize_address) @(private, no_sanitize_address)
block_mark_as_used :: proc(block: ^Block_Header, ) { block_mark_as_used :: proc(block: ^Block_Header) {
next := block_next(block) next := block_next(block)
block_set_prev_used(next) block_set_prev_used(next)
block_set_used(block) block_set_used(block)
+18
View File
@@ -207,3 +207,21 @@ _get_full_path :: proc(fd: linux.Fd, allocator: runtime.Allocator) -> (fullpath:
} }
return 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)
}
+17
View File
@@ -123,3 +123,20 @@ _set_working_directory :: proc(dir: string) -> (err: Error) {
} }
return 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
}
-21
View File
@@ -4,10 +4,6 @@ package os2
// This implementation is for all systems that have POSIX-compliant filesystem paths. // 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) { _are_paths_identical :: proc(a, b: string) -> (identical: bool) {
return a == b return a == b
} }
@@ -26,23 +22,6 @@ _is_absolute_path :: proc(path: string) -> bool {
return len(path) > 0 && _is_path_separator(path[0]) 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 { _get_relative_path_handle_start :: proc(base, target: string) -> bool {
base_rooted := len(base) > 0 && _is_path_separator(base[0]) base_rooted := len(base) > 0 && _is_path_separator(base[0])
target_rooted := len(target) > 0 && _is_path_separator(target[0]) target_rooted := len(target) > 0 && _is_path_separator(target[0])
+12 -12
View File
@@ -204,21 +204,21 @@ accept_nil :: proc "contextless" (s: Fd) -> (Fd, Errno) {
accept :: proc { accept_T, accept_nil } accept :: proc { accept_T, accept_nil }
getsockname_or_peername :: proc "contextless" (s: Fd, sockaddr: ^$T, is_peer: bool) -> Errno { getsockname_or_peername :: proc "contextless" (s: Fd, sockaddr: ^$T, is_peer: bool) -> Errno {
// sockaddr must contain a valid pointer, or this will segfault because // sockaddr must contain a valid pointer, or this will segfault because
// we're telling the syscall that there's memory available to write to. // we're telling the syscall that there's memory available to write to.
addrlen: socklen_t = size_of(T) addrlen: socklen_t = size_of(T)
result, ok := intrinsics.syscall_bsd( result, ok := intrinsics.syscall_bsd(
is_peer ? SYS_getpeername : SYS_getsockname, is_peer ? SYS_getpeername : SYS_getsockname,
cast(uintptr)s, cast(uintptr)s,
cast(uintptr)sockaddr, cast(uintptr)sockaddr,
cast(uintptr)&addrlen) cast(uintptr)&addrlen)
if !ok { if !ok {
return cast(Errno)result return cast(Errno)result
} }
return nil return nil
} }
// Get name of connected peer // Get name of connected peer
+10 -6
View File
@@ -40,9 +40,13 @@ CPU_Feature :: enum u64 {
} }
CPU_Features :: distinct bit_set[CPU_Feature; u64] CPU_Features :: distinct bit_set[CPU_Feature; u64]
CPU :: struct {
cpu_features: Maybe(CPU_Features) name: Maybe(string),
cpu_name: Maybe(string) features: Maybe(CPU_Features),
physical_cores: int,
logical_cores: int,
}
cpu: CPU
@(private) @(private)
cpu_name_buf: [128]byte cpu_name_buf: [128]byte
@@ -53,7 +57,7 @@ init_cpu_name :: proc "contextless" () {
when ODIN_OS == .Darwin { when ODIN_OS == .Darwin {
if unix.sysctlbyname("machdep.cpu.brand_string", &cpu_name_buf) { 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 generic = false
} }
} }
@@ -61,10 +65,10 @@ init_cpu_name :: proc "contextless" () {
if generic { if generic {
when ODIN_ARCH == .arm64 { when ODIN_ARCH == .arm64 {
copy(cpu_name_buf[:], "ARM64") copy(cpu_name_buf[:], "ARM64")
cpu_name = string(cpu_name_buf[:len("ARM64")]) cpu.name = string(cpu_name_buf[:len("ARM64")])
} else { } else {
copy(cpu_name_buf[:], "ARM") copy(cpu_name_buf[:], "ARM")
cpu_name = string(cpu_name_buf[:len("ARM")]) cpu.name = string(cpu_name_buf[:len("ARM")])
} }
} }
} }
+1 -1
View File
@@ -5,7 +5,7 @@ import "core:sys/unix"
@(init, private) @(init, private)
init_cpu_features :: proc "contextless" () { init_cpu_features :: proc "contextless" () {
@(static) features: CPU_Features @(static) features: CPU_Features
defer cpu_features = features defer cpu.features = features
try_set :: proc "contextless" (name: cstring, feature: CPU_Feature) -> (ok: bool) { try_set :: proc "contextless" (name: cstring, feature: CPU_Feature) -> (ok: bool) {
support: b32 support: b32
+16 -12
View File
@@ -3,12 +3,6 @@ package sysinfo
import "base:intrinsics" 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 { CPU_Feature :: enum u64 {
aes, // AES hardware implementation (AES NI) aes, // AES hardware implementation (AES NI)
adx, // Multi-precision add-carry instruction extensions 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 :: distinct bit_set[CPU_Feature; u64]
CPU :: struct {
cpu_features: Maybe(CPU_Features) name: Maybe(string),
cpu_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, private)
init_cpu_features :: proc "c" () { init_cpu_features :: proc "c" () {
@@ -88,7 +86,7 @@ init_cpu_features :: proc "c" () {
when ODIN_OS == .FreeBSD || ODIN_OS == .OpenBSD || ODIN_OS == .NetBSD { when ODIN_OS == .FreeBSD || ODIN_OS == .OpenBSD || ODIN_OS == .NetBSD {
// xgetbv is an illegal instruction under FreeBSD 13, OpenBSD 7.1 and NetBSD 10 // xgetbv is an illegal instruction under FreeBSD 13, OpenBSD 7.1 and NetBSD 10
// return before probing further // return before probing further
cpu_features = set cpu.features = set
return return
} }
@@ -151,7 +149,7 @@ init_cpu_features :: proc "c" () {
try_set(&set, .rdseed, 18, ebx7) try_set(&set, .rdseed, 18, ebx7)
try_set(&set, .adx, 19, ebx7) try_set(&set, .adx, 19, ebx7)
cpu_features = set cpu.features = set
} }
@(private) @(private)
@@ -179,5 +177,11 @@ init_cpu_name :: proc "c" () {
for len(brand) > 0 && brand[len(brand) - 1] == 0 || brand[len(brand) - 1] == ' ' { for len(brand) > 0 && brand[len(brand) - 1] == 0 || brand[len(brand) - 1] == ' ' {
brand = 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
+1 -1
View File
@@ -17,7 +17,7 @@ init_cpu_features :: proc() {
if rerr != .NONE || n == 0 { return } if rerr != .NONE || n == 0 { return }
features: CPU_Features features: CPU_Features
defer cpu_features = features defer cpu.features = features
str := string(buf[:n]) str := string(buf[:n])
for line in strings.split_lines_iterator(&str) { for line in strings.split_lines_iterator(&str) {
+38
View File
@@ -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
}
}
}
}
+2 -2
View File
@@ -9,7 +9,7 @@ import "core:sys/linux"
@(init, private) @(init, private)
init_cpu_features :: proc() { init_cpu_features :: proc() {
_features: CPU_Features _features: CPU_Features
defer cpu_features = _features defer cpu.features = _features
HWCAP_Bits :: enum u64 { HWCAP_Bits :: enum u64 {
I = 'I' - 'A', I = 'I' - 'A',
@@ -109,5 +109,5 @@ init_cpu_features :: proc() {
@(init, private) @(init, private)
init_cpu_name :: proc() { init_cpu_name :: proc() {
cpu_name = "RISCV64" cpu.name = "RISCV64"
} }
+7 -3
View File
@@ -95,6 +95,10 @@ CPU_Feature :: enum u64 {
} }
CPU_Features :: distinct bit_set[CPU_Feature; u64] CPU_Features :: distinct bit_set[CPU_Feature; u64]
CPU :: struct {
cpu_features: Maybe(CPU_Features) name: Maybe(string),
cpu_name: Maybe(string) features: Maybe(CPU_Features),
physical_cores: int,
logical_cores: int,
}
cpu: CPU
+28
View File
@@ -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))
}
}
}
+8 -6
View File
@@ -26,13 +26,15 @@ Example:
import si "core:sys/info" import si "core:sys/info"
main :: proc() { main :: proc() {
fmt.printfln("Odin: %v", ODIN_VERSION) fmt.printfln("Odin: %v", ODIN_VERSION)
fmt.printfln("OS: %v", si.os_version.as_string) fmt.printfln("OS: %v", si.os_version.as_string)
fmt.printfln("OS: %#v", si.os_version) fmt.printfln("OS: %#v", si.os_version)
fmt.printfln("CPU: %v", si.cpu_name) fmt.printfln("CPU: %v", si.cpu.name)
fmt.printfln("RAM: %#.1M", si.ram.total_ram) 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.printfln("MacOS version: %v", si.macos_version)
fmt.println() fmt.println()
+1 -2
View File
@@ -857,7 +857,6 @@ MEMORY_RESOURCE_NOTIFICATION_TYPE :: enum c_int {
LowMemoryResourceNotification :: MEMORY_RESOURCE_NOTIFICATION_TYPE.LowMemoryResourceNotification LowMemoryResourceNotification :: MEMORY_RESOURCE_NOTIFICATION_TYPE.LowMemoryResourceNotification
HighMemoryResourceNotification :: MEMORY_RESOURCE_NOTIFICATION_TYPE.HighMemoryResourceNotification HighMemoryResourceNotification :: MEMORY_RESOURCE_NOTIFICATION_TYPE.HighMemoryResourceNotification
@(default_calling_convention="system") @(default_calling_convention="system")
foreign kernel32 { foreign kernel32 {
CreateMemoryResourceNotification :: proc( CreateMemoryResourceNotification :: proc(
@@ -1194,7 +1193,7 @@ DUMMYUNIONNAME_u :: struct #raw_union {
SYSTEM_LOGICAL_PROCESSOR_INFORMATION :: struct { SYSTEM_LOGICAL_PROCESSOR_INFORMATION :: struct {
ProcessorMask: ULONG_PTR, ProcessorMask: ULONG_PTR,
Relationship: LOGICAL_PROCESSOR_RELATIONSHIP, Relationship: LOGICAL_PROCESSOR_RELATIONSHIP,
DummyUnion: DUMMYUNIONNAME_u, using DummyUnion: DUMMYUNIONNAME_u,
} }
SYSTEM_POWER_STATUS :: struct { SYSTEM_POWER_STATUS :: struct {
+4
View File
@@ -0,0 +1,4 @@
/*
This package is for interacting with the command line interface of the system.
*/
package terminal
+87
View File
@@ -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()
}
+36
View File
@@ -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
+16
View File
@@ -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() { }
+60
View File
@@ -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)
}
}
+1 -1
View File
@@ -10,12 +10,12 @@ package testing
*/ */
import "base:runtime" import "base:runtime"
import "core:encoding/ansi"
import "core:fmt" import "core:fmt"
import "core:io" import "core:io"
import "core:mem" import "core:mem"
import "core:path/filepath" import "core:path/filepath"
import "core:strings" import "core:strings"
import "core:terminal/ansi"
// Definitions of colors for use in the test runner. // Definitions of colors for use in the test runner.
SGR_RESET :: ansi.CSI + ansi.RESET + ansi.SGR SGR_RESET :: ansi.CSI + ansi.RESET + ansi.SGR
+93 -69
View File
@@ -13,7 +13,6 @@ package testing
import "base:intrinsics" import "base:intrinsics"
import "base:runtime" import "base:runtime"
import "core:bytes" import "core:bytes"
import "core:encoding/ansi"
@require import "core:encoding/base64" @require import "core:encoding/base64"
@require import "core:encoding/json" @require import "core:encoding/json"
import "core:fmt" import "core:fmt"
@@ -25,6 +24,8 @@ import "core:os"
import "core:slice" import "core:slice"
@require import "core:strings" @require import "core:strings"
import "core:sync/chan" import "core:sync/chan"
import "core:terminal"
import "core:terminal/ansi"
import "core:thread" import "core:thread"
import "core:time" 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,...` // The format is: `package.test_name,test_name_only,...`
TEST_NAMES : string : #config(ODIN_TEST_NAMES, "") TEST_NAMES : string : #config(ODIN_TEST_NAMES, "")
// Show the fancy animated progress report. // 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) FANCY_OUTPUT : bool : #config(ODIN_TEST_FANCY, true)
// Copy failed tests to the clipboard when done. // Copy failed tests to the clipboard when done.
USE_CLIPBOARD : bool : #config(ODIN_TEST_CLIPBOARD, false) 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 { JSON :: struct {
total: int, total: int,
success: int, success: int,
@@ -129,11 +134,16 @@ run_test_task :: proc(task: thread.Task) {
context.assertion_failure_proc = test_assertion_failure_proc 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 = { context.logger = {
procedure = test_logger_proc, procedure = test_logger_proc,
data = &data.t, data = &data.t,
lowest_level = get_log_level(), lowest_level = get_log_level(),
options = Default_Test_Logger_Opts, options = logger_options,
} }
random_generator_state: runtime.Default_Random_State 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)) stdout := io.to_writer(os.stream_from_handle(os.stdout))
stderr := io.to_writer(os.stream_from_handle(os.stderr)) 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. // -- Prepare test data.
alloc_error: mem.Allocator_Error alloc_error: mem.Allocator_Error
@@ -268,12 +281,12 @@ runner :: proc(internal_tests: []Internal_Test) -> bool {
total_done_count := 0 total_done_count := 0
total_test_count := len(internal_tests) total_test_count := len(internal_tests)
when !FANCY_OUTPUT {
// This is strictly for updating the window title when the progress // This is strictly for updating the window title when the progress
// report is disabled. We're otherwise able to depend on the call to // report is disabled. We're otherwise able to depend on the call to
// `needs_to_redraw`. // `needs_to_redraw`.
last_done_count := -1 last_done_count := -1
}
if total_test_count == 0 { if total_test_count == 0 {
// Exit early. // 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) fmt.assertf(alloc_error == nil, "Error allocating memory for test report: %v", alloc_error)
defer destroy_report(&report) 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 = --- // We cannot make use of the ANSI save/restore cursor codes, because they
{ // work by absolute screen coordinates. This will cause unnecessary
PADDING :: PROGRESS_COLUMN_SPACING + PROGRESS_WIDTH // 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: string = ---
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.") PADDING :: PROGRESS_COLUMN_SPACING + PROGRESS_WIDTH
}
defer delete(thread_count_status_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)
task_data_slots: []Task_Data = --- task_data_slots: []Task_Data = ---
task_data_slots, alloc_error = make([]Task_Data, thread_count) 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. // digging through the source to divine everywhere it is used for that.
shared_log_allocator := context.allocator 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 = { context.logger = {
procedure = runner_logger_proc, procedure = runner_logger_proc,
data = &log_messages, data = &log_messages,
lowest_level = get_log_level(), lowest_level = get_log_level(),
options = Default_Test_Logger_Opts - {.Short_File_Path, .Line, .Procedure}, options = logger_options,
} }
run_index: int run_index: int
@@ -481,11 +499,13 @@ runner :: proc(internal_tests: []Internal_Test) -> bool {
setup_signal_handler() 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) redraw_report(stdout, report)
draw_status_bar(stdout, thread_count_status_string, total_done_count, total_test_count) 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 break main_loop
} }
when FANCY_OUTPUT {
// Because the bounds checking procs send directly to STDERR with // Because the bounds checking procs send directly to STDERR with
// no way to redirect or handle them, we need to at least try to // 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 // 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 // report. This flag may be set by the block of code below if a
// signal is raised. // signal is raised.
// //
// It'll be purely by luck if the output is interleaved properly, // It'll be purely by luck if the output is interleaved properly,
// given the nature of non-thread-safe printing. // given the nature of non-thread-safe printing.
// //
// At worst, if Odin did not print any error for this signal, we'll // 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 // just re-display the progress report. The fatal log error message
// should be enough to clue the user in that something dire has // should be enough to clue the user in that something dire has
// occurred. // occurred.
bypass_progress_overwrite := false bypass_progress_overwrite := false
}
if test_index, reason, ok := should_stop_test(); ok { if test_index, reason, ok := should_stop_test(); ok {
#no_bounds_check report.all_test_states[test_index] = .Failed #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) 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 bypass_progress_overwrite = true
signals_were_raised = true signals_were_raised = true
} }
@@ -766,7 +786,7 @@ runner :: proc(internal_tests: []Internal_Test) -> bool {
// -- Redraw. // -- Redraw.
when FANCY_OUTPUT { if should_show_animations {
if len(log_messages) == 0 && !needs_to_redraw(report) { if len(log_messages) == 0 && !needs_to_redraw(report) {
continue main_loop continue main_loop
} }
@@ -776,7 +796,9 @@ runner :: proc(internal_tests: []Internal_Test) -> bool {
} }
} else { } else {
if total_done_count != last_done_count { 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 last_done_count = total_done_count
} }
@@ -801,7 +823,7 @@ runner :: proc(internal_tests: []Internal_Test) -> bool {
clear(&log_messages) clear(&log_messages)
bytes.buffer_reset(&batch_buffer) bytes.buffer_reset(&batch_buffer)
when FANCY_OUTPUT { if should_show_animations {
redraw_report(batch_writer, report) redraw_report(batch_writer, report)
draw_status_bar(batch_writer, thread_count_status_string, total_done_count, total_test_count) draw_status_bar(batch_writer, thread_count_status_string, total_done_count, total_test_count)
fmt.wprint(stdout, bytes.buffer_to_string(&batch_buffer)) 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) 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 // One line to space out the results, since we don't have the status
// bar in plain mode. // bar in plain mode.
fmt.wprintln(batch_writer) fmt.wprintln(batch_writer)
@@ -836,24 +858,28 @@ runner :: proc(internal_tests: []Internal_Test) -> bool {
if total_done_count != total_test_count { if total_done_count != total_test_count {
not_run_count := total_test_count - total_done_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, fmt.wprintf(batch_writer,
" " + SGR_READY + "%i" + SGR_RESET + " %s left undone.", message,
not_run_count, not_run_count,
"test was" if not_run_count == 1 else "tests were") "test was" if not_run_count == 1 else "tests were")
} }
if total_success_count == total_test_count { 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, fmt.wprintfln(batch_writer,
" %s " + SGR_SUCCESS + "successful." + SGR_RESET, message,
"The test was" if total_test_count == 1 else "All tests were") "The test was" if total_test_count == 1 else "All tests were")
} else if total_failure_count > 0 { } else if total_failure_count > 0 {
if total_failure_count == total_test_count { 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, fmt.wprintfln(batch_writer,
" %s " + SGR_FAILED + "failed." + SGR_RESET, message,
"The test" if total_test_count == 1 else "All tests") "The test" if total_test_count == 1 else "All tests")
} else { } else {
message := " %i test%s failed." if global_log_colors_disabled else " " + SGR_FAILED + "%i" + SGR_RESET + " test%s failed."
fmt.wprintfln(batch_writer, fmt.wprintfln(batch_writer,
" " + SGR_FAILED + "%i" + SGR_RESET + " test%s failed.", message,
total_failure_count, total_failure_count,
"" if total_failure_count == 1 else "s") "" 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 { if signals_were_raised {
fmt.wprintln(batch_writer, ` fmt.wprintln(batch_writer, `
Signals were raised during this test run. Log messages are likely to have collided with each other. 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) fmt.assertf(err == nil, "Error writing JSON report: %v", err)
} }
when ODIN_OS == .Windows {
console_ansi_fini()
}
return total_success_count == total_test_count return total_success_count == total_test_count
} }
-36
View File
@@ -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)
}
}
+7 -5
View File
@@ -12,9 +12,9 @@ package testing
import "base:intrinsics" import "base:intrinsics"
import "core:c/libc" import "core:c/libc"
import "core:encoding/ansi"
import "core:sync"
import "core:os" import "core:os"
import "core:sync"
import "core:terminal/ansi"
@(private="file") stop_runner_flag: libc.sig_atomic_t @(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 // NOTE(Feoramund): Using these write calls in a signal handler is
// undefined behavior in C99 but possibly tolerated in POSIX 2008. // undefined behavior in C99 but possibly tolerated in POSIX 2008.
// Either way, we may as well try to salvage what we can. // Either way, we may as well try to salvage what we can.
show_cursor := ansi.CSI + ansi.DECTCEM_SHOW if !global_ansi_disabled {
libc.fwrite(raw_data(show_cursor), size_of(byte), len(show_cursor), libc.stdout) show_cursor := ansi.CSI + ansi.DECTCEM_SHOW
libc.fflush(libc.stdout) 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. // This is an attempt at being compliant by avoiding printf.
sigbuf: [8]byte sigbuf: [8]byte
-3
View File
@@ -15,8 +15,6 @@ MAX_PROGRAM_SIZE :: int(max(i16))
MAX_CLASSES :: int(max(u8)) MAX_CLASSES :: int(max(u8))
Flag :: enum 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: treat `^` and `$` as if they also match newlines.
Multiline, Multiline,
// Case Insensitive: treat `a-z` as if it was also `A-Z`. // Case Insensitive: treat `a-z` as if it was also `A-Z`.
@@ -36,7 +34,6 @@ Flags :: bit_set[Flag; u8]
@(rodata) @(rodata)
Flag_To_Letter := #sparse[Flag]u8 { Flag_To_Letter := #sparse[Flag]u8 {
.Global = 'g',
.Multiline = 'm', .Multiline = 'm',
.Case_Insensitive = 'i', .Case_Insensitive = 'i',
.Ignore_Whitespace = 'x', .Ignore_Whitespace = 'x',
+9 -5
View File
@@ -401,7 +401,7 @@ compile :: proc(tree: Node, flags: common.Flags) -> (code: Program, class_data:
pc_open := 0 pc_open := 0
add_global: if .Global in flags { optimize_opening: {
// Check if the opening to the pattern is predictable. // Check if the opening to the pattern is predictable.
// If so, use one of the optimized Wait opcodes. // If so, use one of the optimized Wait opcodes.
iter := virtual_machine.Opcode_Iterator{ code[:], 0 } 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) pc_open += size_of(Opcode)
inject_at(&code, pc_open, Opcode(code[pc + size_of(Opcode) + pc_open])) inject_at(&code, pc_open, Opcode(code[pc + size_of(Opcode) + pc_open]))
pc_open += size_of(u8) pc_open += size_of(u8)
break add_global break optimize_opening
case .Rune: case .Rune:
operand := intrinsics.unaligned_load(cast(^rune)&code[pc+1]) 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) pc_open += size_of(Opcode)
inject_raw(&code, pc_open, operand) inject_raw(&code, pc_open, operand)
pc_open += size_of(rune) pc_open += size_of(rune)
break add_global break optimize_opening
case .Rune_Class: case .Rune_Class:
inject_at(&code, pc_open, Opcode.Wait_For_Rune_Class) inject_at(&code, pc_open, Opcode.Wait_For_Rune_Class)
pc_open += size_of(Opcode) pc_open += size_of(Opcode)
inject_at(&code, pc_open, Opcode(code[pc + size_of(Opcode) + pc_open])) inject_at(&code, pc_open, Opcode(code[pc + size_of(Opcode) + pc_open]))
pc_open += size_of(u8) pc_open += size_of(u8)
break add_global break optimize_opening
case .Rune_Class_Negated: case .Rune_Class_Negated:
inject_at(&code, pc_open, Opcode.Wait_For_Rune_Class_Negated) inject_at(&code, pc_open, Opcode.Wait_For_Rune_Class_Negated)
pc_open += size_of(Opcode) pc_open += size_of(Opcode)
inject_at(&code, pc_open, Opcode(code[pc + size_of(Opcode) + pc_open])) inject_at(&code, pc_open, Opcode(code[pc + size_of(Opcode) + pc_open]))
pc_open += size_of(u8) pc_open += size_of(u8)
break add_global break optimize_opening
case .Save: case .Save:
continue continue
case .Assert_Start:
break optimize_opening
case: case:
break seek_loop break seek_loop
} }
+62 -9
View File
@@ -77,6 +77,8 @@ Match_Iterator :: struct {
vm: virtual_machine.Machine, vm: virtual_machine.Machine,
idx: int, idx: int,
temp: runtime.Allocator, temp: runtime.Allocator,
threads: int,
done: bool,
} }
/* /*
@@ -101,7 +103,6 @@ create :: proc(
permanent_allocator := context.allocator, permanent_allocator := context.allocator,
temporary_allocator := context.temp_allocator, temporary_allocator := context.temp_allocator,
) -> (result: Regular_Expression, err: Error) { ) -> (result: Regular_Expression, err: Error) {
// For the sake of speed and simplicity, we first run all the intermediate // For the sake of speed and simplicity, we first run all the intermediate
// processes such as parsing and compilation through the temporary // processes such as parsing and compilation through the temporary
// allocator. // 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: All runes after the closing delimiter will be parsed as flags:
- 'g': Global
- 'm': Multiline - 'm': Multiline
- 'i': Case_Insensitive - 'i': Case_Insensitive
- 'x': Ignore_Whitespace - 'x': Ignore_Whitespace
@@ -243,7 +243,6 @@ create_by_user :: proc(
// to `end` here. // to `end` here.
for r in pattern[start + end:] { for r in pattern[start + end:] {
switch r { switch r {
case 'g': flags += { .Global }
case 'm': flags += { .Multiline } case 'm': flags += { .Multiline }
case 'i': flags += { .Case_Insensitive } case 'i': flags += { .Case_Insensitive }
case 'x': flags += { .Ignore_Whitespace } case 'x': flags += { .Ignore_Whitespace }
@@ -282,8 +281,6 @@ create_iterator :: proc(
permanent_allocator := context.allocator, permanent_allocator := context.allocator,
temporary_allocator := context.temp_allocator, temporary_allocator := context.temp_allocator,
) -> (result: Match_Iterator, err: Error) { ) -> (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 { if .Multiline in flags {
return {}, .Unsupported_Flag return {}, .Unsupported_Flag
@@ -294,6 +291,7 @@ create_iterator :: proc(
result.temp = temporary_allocator result.temp = temporary_allocator
result.vm = virtual_machine.create(result.regex.program, str) result.vm = virtual_machine.create(result.regex.program, str)
result.vm.class_data = result.regex.class_data result.vm.class_data = result.regex.class_data
result.threads = max(1, virtual_machine.opcode_count(result.vm.code) - 1)
return 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, assert(len(it.capture.pos) >= common.MAX_CAPTURE_GROUPS,
"Pre-allocated RegEx capture `pos` must be at least 10 elements long.") "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() 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 saved: ^[2 * common.MAX_CAPTURE_GROUPS]int
{ {
context.allocator = it.temp 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) str := string(it.vm.memory)
num_groups: int num_groups: int
@@ -488,9 +527,7 @@ match_iterator :: proc(it: ^Match_Iterator) -> (result: Capture, index: int, ok:
num_groups = n num_groups = n
} }
defer if ok { defer it.idx += 1
it.idx += 1
}
if num_groups > 0 { if num_groups > 0 {
result = {it.capture.pos[:num_groups], it.capture.groups[:num_groups]} result = {it.capture.pos[:num_groups], it.capture.groups[:num_groups]}
@@ -504,8 +541,24 @@ match :: proc {
match_iterator, 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) { 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 { run :: proc(vm: ^Machine, $UNICODE_MODE: bool) -> (saved: ^[2 * common.MAX_CAPTURE_GROUPS]int, ok: bool) #no_bounds_check {
when UNICODE_MODE { 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 { } else {
if len(vm.memory) > 0 { 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 vm.next_rune_size = 1
} }
} }
+4 -2
View File
@@ -285,6 +285,7 @@ scan_number :: proc(s: ^Scanner, ch: rune, seen_dot: bool) -> (rune, rune) {
case 'o': return "octal literal" case 'o': return "octal literal"
case 'z': return "dozenal literal" case 'z': return "dozenal literal"
case 'x': return "hexadecimal literal" case 'x': return "hexadecimal literal"
case 'h': return "hexadecimal literal"
} }
return "decimal literal" return "decimal literal"
} }
@@ -360,7 +361,8 @@ scan_number :: proc(s: ^Scanner, ch: rune, seen_dot: bool) -> (rune, rune) {
base, prefix = 12, 'z' base, prefix = 12, 'z'
case 'h': case 'h':
tok = Float tok = Float
fallthrough ch = advance(s)
base, prefix = 16, 'h'
case 'x': case 'x':
ch = advance(s) ch = advance(s)
base, prefix = 16, 'x' base, prefix = 16, 'x'
@@ -447,7 +449,7 @@ scan_string :: proc(s: ^Scanner, quote: rune) -> (n: int) {
ch := advance(s) ch := advance(s)
for ch != quote { for ch != quote {
if ch == '\n' || ch < 0 { if ch == '\n' || ch < 0 {
error(s, "literal no terminated") error(s, "literal not terminated")
return return
} }
if ch == '\\' { if ch == '\\' {
+5 -2
View File
@@ -58,7 +58,6 @@ import trace "core:debug/trace"
import dynlib "core:dynlib" import dynlib "core:dynlib"
import net "core:net" import net "core:net"
import ansi "core:encoding/ansi"
import base32 "core:encoding/base32" import base32 "core:encoding/base32"
import base64 "core:encoding/base64" import base64 "core:encoding/base64"
import cbor "core:encoding/cbor" import cbor "core:encoding/cbor"
@@ -129,6 +128,9 @@ import strings "core:strings"
import sync "core:sync" import sync "core:sync"
import testing "core:testing" import testing "core:testing"
import terminal "core:terminal"
import ansi "core:terminal/ansi"
import edit "core:text/edit" import edit "core:text/edit"
import i18n "core:text/i18n" import i18n "core:text/i18n"
import match "core:text/match" import match "core:text/match"
@@ -201,7 +203,6 @@ _ :: pe
_ :: trace _ :: trace
_ :: dynlib _ :: dynlib
_ :: net _ :: net
_ :: ansi
_ :: base32 _ :: base32
_ :: base64 _ :: base64
_ :: csv _ :: csv
@@ -257,6 +258,8 @@ _ :: strconv
_ :: strings _ :: strings
_ :: sync _ :: sync
_ :: testing _ :: testing
_ :: terminal
_ :: ansi
_ :: scanner _ :: scanner
_ :: i18n _ :: i18n
_ :: match _ :: match
+32 -8
View File
@@ -441,6 +441,7 @@ struct BuildContext {
String extra_assembler_flags; String extra_assembler_flags;
String microarch; String microarch;
BuildModeKind build_mode; BuildModeKind build_mode;
bool keep_executable;
bool generate_docs; bool generate_docs;
bool custom_optimization_level; bool custom_optimization_level;
i32 optimization_level; i32 optimization_level;
@@ -2209,10 +2210,33 @@ 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] == '\\')) { while (output_name.len > 0 && (output_name[output_name.len-1] == '/' || output_name[output_name.len-1] == '\\')) {
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_directory_from_path(output_name);
output_name = remove_extension_from_path(output_name);
output_name = copy_string(ha, string_trim_whitespace(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 // Note(Dragos): This is a fix for empty filenames
// Turn the trailing folder into the file name // Turn the trailing folder into the file name
@@ -2278,9 +2302,10 @@ gb_internal bool init_build_paths(String init_filename) {
case TargetOs_windows: case TargetOs_windows:
case TargetOs_linux: case TargetOs_linux:
case TargetOs_darwin: case TargetOs_darwin:
case TargetOs_freebsd:
break; break;
default: 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; return false;
} }
} }
@@ -2288,12 +2313,10 @@ gb_internal bool init_build_paths(String init_filename) {
if (build_context.sanitizer_flags & SanitizerFlag_Memory) { if (build_context.sanitizer_flags & SanitizerFlag_Memory) {
switch (build_context.metrics.os) { switch (build_context.metrics.os) {
case TargetOs_linux: case TargetOs_linux:
case TargetOs_freebsd:
break; break;
default: default:
gb_printf_err("-sanitize:memory is only supported on linux\n"); gb_printf_err("-sanitize:memory is only supported on Linux and FreeBSD\n");
return false;
}
if (build_context.metrics.os != TargetOs_linux) {
return false; return false;
} }
} }
@@ -2302,9 +2325,10 @@ gb_internal bool init_build_paths(String init_filename) {
switch (build_context.metrics.os) { switch (build_context.metrics.os) {
case TargetOs_linux: case TargetOs_linux:
case TargetOs_darwin: case TargetOs_darwin:
case TargetOs_freebsd:
break; break;
default: 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; return false;
} }
} }
+14 -6
View File
@@ -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 && 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; String field_name = selector->Ident.token.string;
if (1 < field_name.len && field_name.len <= 4) { if (1 < field_name.len && field_name.len <= 4) {
u8 swizzles_xyzw[4] = {'x', 'y', 'z', 'w'}; 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 *original_type = operand->type;
Type *array_type = base_type(type_deref(original_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); 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; 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); 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); add_type_and_value(c, operand->expr, operand->mode, operand->type, operand->value);
return swizzle_entity; 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 (pt->kind == Type_Proc && pt->Proc.calling_convention == ProcCC_Odin) {
if ((c->scope->flags & ScopeFlag_ContextDefined) == 0) { 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(call, "'context' has not been defined within this scope, but is required for this procedure call");
error_line("\tSuggestion: 'context = runtime.default_context()'");
} }
} }
+15
View File
@@ -801,6 +801,21 @@ try_cross_linking:;
// This points the linker to where the entry point is // This points the linker to where the entry point is
link_settings = gb_string_appendc(link_settings, "-e _main "); 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) { } else if (build_context.metrics.os == TargetOs_openbsd) {
// OpenBSD ports install shared libraries in /usr/local/lib. Also, we must explicitly link libpthread. // 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 "); platform_lib_str = gb_string_appendc(platform_lib_str, "-lpthread -Wl,-L/usr/local/lib ");
+24 -4
View File
@@ -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, "optnone");
lb_add_attribute_to_proc(p->module, p->value, "noinline"); 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_begin_procedure_body(p);
lb_setup_type_info_data(main_module); 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, "optnone");
lb_add_attribute_to_proc(p->module, p->value, "noinline"); 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); lb_begin_procedure_body(p);
CheckerInfo *info = main_module->gen->info; 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.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); 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")); 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); 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")); 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); 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) { if (!build_context.extra_linker_flags.text) {
build_context.extra_linker_flags = str_lit("-fsanitize=address"); build_context.extra_linker_flags = str_lit("-fsanitize=address");
} else { } else {
build_context.extra_linker_flags = concatenate_strings(permanent_allocator(), build_context.extra_linker_flags, str_lit(" -fsanitize=address")); 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.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) { if (!build_context.extra_linker_flags.text) {
build_context.extra_linker_flags = str_lit("-fsanitize=memory"); build_context.extra_linker_flags = str_lit("-fsanitize=memory");
} else { } else {
build_context.extra_linker_flags = concatenate_strings(permanent_allocator(), build_context.extra_linker_flags, str_lit(" -fsanitize=memory")); 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.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) { if (!build_context.extra_linker_flags.text) {
build_context.extra_linker_flags = str_lit("-fsanitize=thread"); build_context.extra_linker_flags = str_lit("-fsanitize=thread");
} else { } else {
build_context.extra_linker_flags = concatenate_strings(permanent_allocator(), build_context.extra_linker_flags, str_lit(" -fsanitize=thread")); build_context.extra_linker_flags = concatenate_strings(permanent_allocator(), build_context.extra_linker_flags, str_lit(" -fsanitize=thread"));
} }
break;
} }
} }
+89 -50
View File
@@ -312,6 +312,7 @@ enum BuildFlagKind {
BuildFlag_Collection, BuildFlag_Collection,
BuildFlag_Define, BuildFlag_Define,
BuildFlag_BuildMode, BuildFlag_BuildMode,
BuildFlag_KeepExecutable,
BuildFlag_Target, BuildFlag_Target,
BuildFlag_Subtarget, BuildFlag_Subtarget,
BuildFlag_Debug, 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_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_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_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_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_Subtarget, str_lit("subtarget"), BuildFlagParam_String, Command__does_check);
add_flag(&build_flags, BuildFlag_Debug, str_lit("debug"), BuildFlagParam_None, 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; break;
} }
case BuildFlag_KeepExecutable:
build_context.keep_executable = true;
break;
case BuildFlag_Debug: case BuildFlag_Debug:
build_context.ODIN_DEBUG = true; 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>")) { if (print_flag("-build-mode:<mode>")) {
print_usage_line(2, "Sets the build mode."); print_usage_line(2, "Sets the build mode.");
print_usage_line(2, "Available options:"); print_usage_line(2, "Available options:");
print_usage_line(3, "-build-mode:exe Builds as an executable."); 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: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: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: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: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: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: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:obj Builds as an object file.");
print_usage_line(3, "-build-mode:object 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:assembly Builds as an assembly file.");
print_usage_line(3, "-build-mode:assembler 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: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-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: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, "Defines a library collection used for imports.");
print_usage_line(2, "Example: -collection:shared=dir/to/shared"); print_usage_line(2, "Example: -collection:shared=dir/to/shared");
print_usage_line(2, "Usage in Code:"); 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>")) { 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, "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, "This can be used with metaprogramming tools.");
print_usage_line(2, "Examples:"); print_usage_line(2, "Examples:");
print_usage_line(3, "-custom-attribute:my_tag"); 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,the_other_thing");
print_usage_line(3, "-custom-attribute:my_tag -custom-attribute: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, "Defines a scalar boolean, integer or string as global constant.");
print_usage_line(2, "Example: -define:SPAM=123"); print_usage_line(2, "Example: -define:SPAM=123");
print_usage_line(2, "Usage in code:"); 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 (check) {
if (print_flag("-error-pos-style:<string>")) { if (print_flag("-error-pos-style:<string>")) {
print_usage_line(2, "Available options:"); 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:unix file/path:45:3:");
print_usage_line(3, "-error-pos-style:odin 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:default (Defaults to 'odin'.)");
} }
if (print_flag("-export-defineables:<filename>")) { 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>")) { 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, "Exports dependencies to one of a few formats. Requires `-export-dependencies-file`.");
print_usage_line(2, "Available options:"); print_usage_line(2, "Available options:");
print_usage_line(3, "-export-dependencies:make Exports in Makefile format"); 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:json Exports in JSON format");
} }
if (print_flag("-export-dependencies-file:<filename>")) { 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>")) { 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, "Exports timings to one of a few formats. Requires `-show-timings` or `-show-more-timings`.");
print_usage_line(2, "Available options:"); 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: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:csv Exports compile time stats to CSV.");
} }
if (print_flag("-export-timings-file:<filename>")) { 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 (run_or_build) {
if (print_flag("-linker:<string>")) { if (print_flag("-linker:<string>")) {
print_usage_line(2, "Specify the linker to use."); 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>")) { if (print_flag("-microarch:<string>")) {
print_usage_line(2, "Specifies the specific micro-architecture for the build in a string."); print_usage_line(2, "Specifies the specific micro-architecture for the build in a string.");
print_usage_line(2, "Examples:"); print_usage_line(2, "Examples:");
print_usage_line(3, "-microarch:sandybridge"); print_usage_line(3, "-microarch:sandybridge");
print_usage_line(3, "-microarch:native"); print_usage_line(3, "-microarch:native");
print_usage_line(3, "-microarch:\"?\" for a list"); 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>")) { if (print_flag("-o:<string>")) {
print_usage_line(2, "Sets the optimization mode for compilation."); print_usage_line(2, "Sets the optimization mode for compilation.");
print_usage_line(2, "Available options:"); print_usage_line(2, "Available options:");
print_usage_line(3, "-o:none"); print_usage_line(3, "-o:none");
print_usage_line(3, "-o:minimal"); print_usage_line(3, "-o:minimal");
print_usage_line(3, "-o:size"); print_usage_line(3, "-o:size");
print_usage_line(3, "-o:speed"); print_usage_line(3, "-o:speed");
if (LB_USE_NEW_PASS_SYSTEM) { if (LB_USE_NEW_PASS_SYSTEM) {
print_usage_line(3, "-o:aggressive (use this with caution)"); 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>")) { if (print_flag("-reloc-mode:<string>")) {
print_usage_line(2, "Specifies the reloc mode."); print_usage_line(2, "Specifies the reloc mode.");
print_usage_line(2, "Available options:"); print_usage_line(2, "Available options:");
print_usage_line(3, "-reloc-mode:default"); print_usage_line(3, "-reloc-mode:default");
print_usage_line(3, "-reloc-mode:static"); print_usage_line(3, "-reloc-mode:static");
print_usage_line(3, "-reloc-mode:pic"); print_usage_line(3, "-reloc-mode:pic");
print_usage_line(3, "-reloc-mode:dynamic-no-pic"); print_usage_line(3, "-reloc-mode:dynamic-no-pic");
} }
#if defined(GB_SYSTEM_WINDOWS) #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>")) { if (print_flag("-sanitize:<string>")) {
print_usage_line(2, "Enables sanitization analysis."); print_usage_line(2, "Enables sanitization analysis.");
print_usage_line(2, "Available options:"); print_usage_line(2, "Available options:");
print_usage_line(3, "-sanitize:address"); print_usage_line(3, "-sanitize:address");
print_usage_line(3, "-sanitize:memory"); print_usage_line(3, "-sanitize:memory");
print_usage_line(3, "-sanitize:thread"); print_usage_line(3, "-sanitize:thread");
print_usage_line(2, "NOTE: This flag can be used multiple times."); 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, "[Windows only]");
print_usage_line(2, "Defines the subsystem for the application."); print_usage_line(2, "Defines the subsystem for the application.");
print_usage_line(2, "Available options:"); print_usage_line(2, "Available options:");
print_usage_line(3, "-subsystem:console"); print_usage_line(3, "-subsystem:console");
print_usage_line(3, "-subsystem:windows"); print_usage_line(3, "-subsystem:windows");
} }
#endif #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>")) { 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, "Specifies CPU features to enable on top of the enabled features implied by -microarch.");
print_usage_line(2, "Examples:"); print_usage_line(2, "Examples:");
print_usage_line(3, "-target-features:atomics"); print_usage_line(3, "-target-features:atomics");
print_usage_line(3, "-target-features:\"sse2,aes\""); print_usage_line(3, "-target-features:\"sse2,aes\"");
print_usage_line(3, "-target-features:\"?\" for a list"); 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")) { if (print_flag("-vet")) {
print_usage_line(2, "Does extra checks on the code."); print_usage_line(2, "Does extra checks on the code.");
print_usage_line(2, "Extra checks include:"); print_usage_line(2, "Extra checks include:");
print_usage_line(3, "-vet-unused"); print_usage_line(3, "-vet-unused");
print_usage_line(3, "-vet-unused-variables"); print_usage_line(3, "-vet-unused-variables");
print_usage_line(3, "-vet-unused-imports"); print_usage_line(3, "-vet-unused-imports");
print_usage_line(3, "-vet-shadowing"); print_usage_line(3, "-vet-shadowing");
print_usage_line(3, "-vet-using-stmt"); print_usage_line(3, "-vet-using-stmt");
} }
if (print_flag("-vet-cast")) { if (print_flag("-vet-cast")) {
@@ -3851,6 +3885,11 @@ end_of_code_gen:;
defer (gb_free(heap_allocator(), exe_name.text)); 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)); 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; return 0;
} }
@@ -103,9 +103,11 @@ expensive_for_backtrackers :: proc(t: ^testing.T) {
@test @test
global_capture_end_word :: proc(t: ^testing.T) { 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!` EXPR :: `Hellope World!`
rex, err := regex.create(EXPR, { .Global }) rex, err := regex.create(EXPR, { /*.Global*/ })
if !testing.expect_value(t, err, nil) { if !testing.expect_value(t, err, nil) {
return return
} }
@@ -145,7 +147,7 @@ global_capture_end_word_unicode :: proc(t: ^testing.T) {
EXPR :: `こにちは` EXPR :: `こにちは`
needle := string(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) { if !testing.expect_value(t, err, nil) {
return return
} }
@@ -185,7 +187,7 @@ global_capture_end_word_unicode :: proc(t: ^testing.T) {
alternations :: proc(t: ^testing.T) { alternations :: proc(t: ^testing.T) {
EXPR :: `a(?:bb|cc|dd|ee|ff)` 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) { if !testing.expect_value(t, err, nil) {
return return
} }
@@ -219,7 +221,7 @@ classes :: proc(t: ^testing.T) {
EXPR :: `[\w\d]+` EXPR :: `[\w\d]+`
NEEDLE :: "0123456789abcdef" NEEDLE :: "0123456789abcdef"
rex, err := regex.create(EXPR, { .Global }) rex, err := regex.create(EXPR, { /*.Global*/ })
if !testing.expect_value(t, err, nil) { if !testing.expect_value(t, err, nil) {
return return
} }
+173 -18
View File
@@ -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 :: 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) 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) 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) 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) 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) testing.expect_value(t, variant_ti, expected_ti, loc = loc)
} }
check_capture :: proc(t: ^testing.T, got, expected: regex.Capture, loc := #caller_location) { 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) 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) 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) testing.expect_value(t, len(got.groups), len(expected.groups), loc = loc) or_return
if len(got.pos) == len(expected.pos) { if len(got.pos) == len(expected.pos) {
for i in 0..<len(got.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.pos[i], expected.pos[i], loc = loc) or_return
testing.expect_value(t, got.groups[i], expected.groups[i], loc = loc) testing.expect_value(t, got.groups[i], expected.groups[i], loc = loc) or_return
} }
} }
return true
} }
@test @test
@@ -212,6 +213,12 @@ test_alternation :: proc(t: ^testing.T) {
check_expression(t, EXPR, "cc", "cc") 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
test_optional :: proc(t: ^testing.T) { test_optional :: proc(t: ^testing.T) {
EXPR :: "a?a?a?aaa" EXPR :: "a?a?a?aaa"
@@ -516,7 +523,7 @@ test_pos_index_explicitly :: proc(t: ^testing.T) {
STR :: "This is an island." STR :: "This is an island."
EXPR :: `\bis\b` EXPR :: `\bis\b`
rex, err := regex.create(EXPR, { .Global }) rex, err := regex.create(EXPR)
if !testing.expect_value(t, err, nil) { if !testing.expect_value(t, err, nil) {
return return
} }
@@ -642,9 +649,9 @@ test_unicode_explicitly :: proc(t: ^testing.T) {
} }
{ {
EXPR :: "こにちは!" EXPR :: "こにちは!"
check_expression_with_flags(t, EXPR, { .Global, .Unicode }, check_expression_with_flags(t, EXPR, { .Unicode },
"Hello こにちは!", "こにちは!") "Hello こにちは!", "こにちは!")
check_expression_with_flags(t, EXPR, { .Global, .Unicode, .No_Optimization }, check_expression_with_flags(t, EXPR, { .Unicode, .No_Optimization },
"Hello こにちは!", "こにちは!") "Hello こにちは!", "こにちは!")
} }
} }
@@ -901,12 +908,12 @@ test_everything_at_once :: proc(t: ^testing.T) {
@test @test
test_creation_from_user_string :: proc(t: ^testing.T) { test_creation_from_user_string :: proc(t: ^testing.T) {
{ {
USER_EXPR :: `/^hellope$/gmixun-` USER_EXPR :: `/^hellope$/mixun-`
STR :: "hellope" STR :: "hellope"
rex, err := regex.create_by_user(USER_EXPR) rex, err := regex.create_by_user(USER_EXPR)
defer regex.destroy(rex) defer regex.destroy(rex)
testing.expect_value(t, err, nil) 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) _, ok := regex.match(rex, STR)
testing.expectf(t, ok, "expected user-provided RegEx %v to match %q", 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{ 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 = {{2, 5}, {2, 5}}, groups = {"ab3", "ab3"}},
{pos = {{6, 9}, {6, 9}}, groups = {"ab5", "ab5"}}, {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 = {{2, 8}, {3, 4}, {6, 8}}, groups = {"foobar", "o", "ar"}},
{pos = {{9, 15}, {10, 11}, {13, 15}}, 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
test_match_iterator :: proc(t: ^testing.T) { 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) it, err := regex.create_iterator(test.haystack, test.pattern, test.flags)
defer regex.destroy(it) defer regex.destroy(it)
@@ -1128,10 +1232,61 @@ test_match_iterator :: proc(t: ^testing.T) {
for capture, idx in regex.match(&it) { for capture, idx in regex.match(&it) {
if idx >= len(test.expected) { 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 break
} }
check_capture(t, capture, test.expected[idx]) check_capture(t, capture, test.expected[idx])
} }
testing.expect_value(t, it.idx, len(test.expected)) 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))
} }
} }
+1 -1
View File
@@ -87,7 +87,7 @@ foreign lib {
RWseek :: proc(ctx: ^RWops, offset: i64, whence: c.int) -> i64 --- RWseek :: proc(ctx: ^RWops, offset: i64, whence: c.int) -> i64 ---
RWtell :: proc(ctx: ^RWops) -> i64 --- RWtell :: proc(ctx: ^RWops) -> i64 ---
RWread :: proc(ctx: ^RWops, ptr: rawptr, size: c.size_t, maxnum: c.size_t) -> c.size_t --- 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 --- RWclose :: proc(ctx: ^RWops) -> c.int ---
LoadFile_RW :: proc(src: ^RWops, datasize: ^c.size_t, freesrc: bool) -> rawptr --- LoadFile_RW :: proc(src: ^RWops, datasize: ^c.size_t, freesrc: bool) -> rawptr ---
+11 -3
View File
@@ -1,8 +1,8 @@
package sdl3 package sdl3
Mutex :: struct {} Mutex :: struct {}
RWLock :: struct {} RWLock :: struct {}
Semaphore :: struct {}
@(default_calling_convention="c", link_prefix="SDL_", require_results) @(default_calling_convention="c", link_prefix="SDL_", require_results)
foreign lib { foreign lib {
@@ -19,4 +19,12 @@ foreign lib {
TryLockRWLockForWriting :: proc(rwlock: ^RWLock) -> bool --- TryLockRWLockForWriting :: proc(rwlock: ^RWLock) -> bool ---
UnlockRWLock :: proc(rwlock: ^RWLock) --- UnlockRWLock :: proc(rwlock: ^RWLock) ---
DestroyRWLock :: 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) ---
} }
+1 -1
View File
@@ -2033,7 +2033,7 @@ class WebGPUInterface {
addressModeW: this.enumeration("AddressMode", off(4)), addressModeW: this.enumeration("AddressMode", off(4)),
magFilter: this.enumeration("FilterMode", off(4)), magFilter: this.enumeration("FilterMode", off(4)),
minFilter: 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)), lodMinClamp: this.mem.loadF32(off(4)),
lodMaxClamp: this.mem.loadF32(off(4)), lodMaxClamp: this.mem.loadF32(off(4)),
compare: this.enumeration("CompareFunction", off(4)), compare: this.enumeration("CompareFunction", off(4)),