mirror of
https://github.com/Ed94/Odin.git
synced 2026-07-05 11:11:37 -07:00
Compare commits
179 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| adcaace03c | |||
| f205df1996 | |||
| c59ad24856 | |||
| 2b9b0ac62e | |||
| 67e6f57192 | |||
| 24ddb8506f | |||
| 6ff0cc0b40 | |||
| 7620fe1ac6 | |||
| 22e0f5ecd0 | |||
| fce2042375 | |||
| 57594153a1 | |||
| ff93ea5bf1 | |||
| 4a54676f31 | |||
| 0d900521bc | |||
| bd7ffcc048 | |||
| 2f771bee7b | |||
| ae5214c1f2 | |||
| 84d8798ad3 | |||
| ab7e1e01de | |||
| bbafc3fbd6 | |||
| 194fa7cd98 | |||
| 4c12addcaf | |||
| 0bd27381c3 | |||
| 6dce07790a | |||
| 203ae32b79 | |||
| 3c493194c9 | |||
| 692764aad3 | |||
| 937e5de1d8 | |||
| 7de67f8c1b | |||
| f5d66bcb6f | |||
| bf82c40964 | |||
| aa6299f114 | |||
| 7ffca8ed58 | |||
| 030405dbb6 | |||
| 8862f9118b | |||
| e2e98672bd | |||
| 51f295cacc | |||
| 0c50ac3396 | |||
| 2da81a4a26 | |||
| b6d4853a33 | |||
| 020b147222 | |||
| 34b037f19b | |||
| 88ee5d1a6d | |||
| 0892d84c17 | |||
| 2501d50f9c | |||
| 1e4a4181e2 | |||
| 3e1daa002c | |||
| 4c13dee18f | |||
| 95497626e3 | |||
| 9ada48054f | |||
| 99d6c58971 | |||
| b974b3ccfd | |||
| 75cf45f0be | |||
| d337a11e83 | |||
| a86386d882 | |||
| bbf40bf318 | |||
| b054585066 | |||
| 90c44c34a9 | |||
| e449cc9e2d | |||
| 909ed93cd3 | |||
| 9c97b11ab9 | |||
| 5ae44b25da | |||
| b2ecb37b35 | |||
| 144d034475 | |||
| 50d8dc91cf | |||
| e58915e12f | |||
| 7f8c2a44a4 | |||
| d986eee36b | |||
| b3e712e0b8 | |||
| 05434daa69 | |||
| 2c4a478987 | |||
| a80ca23937 | |||
| ba02ef8f25 | |||
| ef3d8bdc42 | |||
| 951511704d | |||
| 23aae6ab0f | |||
| 3748e117a9 | |||
| 33798b8b80 | |||
| 2e85083d0a | |||
| 23b8a9033a | |||
| 313b6874b1 | |||
| 6004412365 | |||
| adac039a2b | |||
| adcc865c70 | |||
| fe533fb809 | |||
| fa62963da7 | |||
| 1f5bb99548 | |||
| f1cd56c28a | |||
| 852c8b533c | |||
| 582a72574e | |||
| b249ddde48 | |||
| b020ba2b5f | |||
| 03c6862d51 | |||
| b7f953b2ee | |||
| 0b064765c9 | |||
| eb3ddce706 | |||
| 5fdc9fa3b6 | |||
| bfb231fb8a | |||
| 74fb74d9cb | |||
| 97d7e295dd | |||
| 0727e91aeb | |||
| 8dc70f797c | |||
| 2cf8a9da6f | |||
| c1c7128634 | |||
| 0e9ef50e63 | |||
| e05944601a | |||
| 49cf0125a9 | |||
| 0602a16ad6 | |||
| 09a0dad115 | |||
| 243a3f5006 | |||
| 33ca85bd4e | |||
| ca15eb26f0 | |||
| 28eebc14d0 | |||
| 4210aa9ab9 | |||
| 5bbdbadc25 | |||
| 00f24a3249 | |||
| d8a798372b | |||
| 8d5c865814 | |||
| 5134d6bc63 | |||
| ede57720fd | |||
| 830d2007a6 | |||
| 5f3b6c9722 | |||
| 93f7d3bfb9 | |||
| f0ef10aa57 | |||
| bf91fcc6f7 | |||
| 2d894a0164 | |||
| 731b9c902f | |||
| ac0f3c8433 | |||
| 56bfbbf501 | |||
| 63b5d472fa | |||
| 233e3c76fd | |||
| 2334dadb6a | |||
| 6f4f2754d6 | |||
| 30ced04137 | |||
| c39bd7e089 | |||
| 3470d986f0 | |||
| 7c0257fcda | |||
| 9af6d6c9c6 | |||
| 1ecab2fcbc | |||
| a262c0bbf3 | |||
| 7f3f164736 | |||
| 085db569f1 | |||
| 133af6f826 | |||
| 1c2301e2f1 | |||
| ef999f660b | |||
| fad330acd1 | |||
| 8f1af2630d | |||
| eea92a3371 | |||
| ff275df5ea | |||
| 6e56e5457d | |||
| afaa5f2deb | |||
| 0674b1b6ee | |||
| 1ef8602f19 | |||
| ee597fc9b8 | |||
| e254581a1b | |||
| 38ea140b3f | |||
| d939d6079a | |||
| 6e9475d61d | |||
| f6134422e6 | |||
| 5c05038af0 | |||
| 5da5ebff13 | |||
| 798932523e | |||
| 5267a864db | |||
| f02334237a | |||
| d5ea492ef5 | |||
| 96ac405952 | |||
| 38d58e818c | |||
| 090723179b | |||
| 5b55fbff23 | |||
| 64f200dc74 | |||
| c02ff3af27 | |||
| 13c6352b8e | |||
| 707c2b3d7a | |||
| 14eed79a21 | |||
| 2ca30e3acd | |||
| caf9716bf1 | |||
| d569daae33 | |||
| 28f7f57247 | |||
| 34cb558279 |
@@ -163,6 +163,13 @@ jobs:
|
||||
cd tests\internal
|
||||
call build.bat
|
||||
timeout-minutes: 10
|
||||
- name: Odin documentation tests
|
||||
shell: cmd
|
||||
run: |
|
||||
call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Auxiliary\Build\vcvars64.bat
|
||||
cd tests\documentation
|
||||
call build.bat
|
||||
timeout-minutes: 10
|
||||
- name: core:math/big tests
|
||||
shell: cmd
|
||||
run: |
|
||||
|
||||
@@ -94,7 +94,15 @@ cap :: proc(array: Array_Type) -> int ---
|
||||
|
||||
size_of :: proc($T: typeid) -> int ---
|
||||
align_of :: proc($T: typeid) -> int ---
|
||||
offset_of :: proc($T: typeid) -> uintptr ---
|
||||
|
||||
// e.g. offset_of(t.f), where t is an instance of the type T
|
||||
offset_of_selector :: proc(selector: $T) -> uintptr ---
|
||||
// e.g. offset_of(T, f), where T can be the type instead of a variable
|
||||
offset_of_member :: proc($T: typeid, member: $M) -> uintptr ---
|
||||
offset_of :: proc{offset_of_selector, offset_of_member}
|
||||
// e.g. offset_of(T, "f"), where T can be the type instead of a variable
|
||||
offset_of_by_string :: proc($T: typeid, member: string) -> uintptr ---
|
||||
|
||||
type_of :: proc(x: expr) -> type ---
|
||||
type_info_of :: proc($T: typeid) -> ^runtime.Type_Info ---
|
||||
typeid_of :: proc($T: typeid) -> typeid ---
|
||||
|
||||
@@ -44,7 +44,7 @@ when ODIN_OS == .Windows {
|
||||
@(link_name="_Cnd_destroy") cnd_destroy :: proc(cond: ^cnd_t) ---
|
||||
@(link_name="_Cnd_init") cnd_init :: proc(cond: ^cnd_t) -> int ---
|
||||
@(link_name="_Cnd_signal") cnd_signal :: proc(cond: ^cnd_t) -> int ---
|
||||
@(link_name="_Cnd_timedwait") cnd_timedwait :: proc(cond: ^cnd_t, ts: ^timespec) -> int ---
|
||||
@(link_name="_Cnd_timedwait") cnd_timedwait :: proc(cond: ^cnd_t, mtx: ^mtx_t, ts: ^timespec) -> int ---
|
||||
@(link_name="_Cnd_wait") cnd_wait :: proc(cond: ^cnd_t, mtx: ^mtx_t) -> int ---
|
||||
|
||||
// 7.26.4 Mutex functions
|
||||
|
||||
@@ -11,6 +11,8 @@ package util
|
||||
*/
|
||||
|
||||
import "core:mem"
|
||||
// Keep vet happy
|
||||
_ :: mem
|
||||
|
||||
// @note(bp): this can replace the other two
|
||||
cast_slice :: #force_inline proc "contextless" ($D: typeid/[]$DE, src: $S/[]$SE) -> D {
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
//+build js
|
||||
//+private
|
||||
package dynlib
|
||||
|
||||
_load_library :: proc(path: string, global_symbols := false) -> (Library, bool) {
|
||||
return
|
||||
}
|
||||
|
||||
_unload_library :: proc(library: Library) -> bool {
|
||||
return
|
||||
}
|
||||
|
||||
_symbol_address :: proc(library: Library, symbol: string) -> (ptr: rawptr, found: bool) {
|
||||
return
|
||||
}
|
||||
@@ -87,7 +87,8 @@ Error :: enum {
|
||||
|
||||
|
||||
|
||||
destroy_value :: proc(value: Value) {
|
||||
destroy_value :: proc(value: Value, allocator := context.allocator) {
|
||||
context.allocator = allocator
|
||||
#partial switch v in value {
|
||||
case Object:
|
||||
for key, elem in v {
|
||||
@@ -103,5 +104,4 @@ destroy_value :: proc(value: Value) {
|
||||
case String:
|
||||
delete(v)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+1
-1
@@ -68,7 +68,7 @@ A period with no following number specifies a precision of 0.
|
||||
Examples:
|
||||
%f default width, default precision
|
||||
%8f width 8, default precision
|
||||
%.3f default width, precision 2
|
||||
%.2f default width, precision 2
|
||||
%8.3f width 8, precision 3
|
||||
%8.f width 8, precision 0
|
||||
|
||||
|
||||
+560
-98
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,48 @@
|
||||
package image
|
||||
|
||||
import "core:os"
|
||||
import "core:mem"
|
||||
import "core:bytes"
|
||||
|
||||
Loader_Proc :: #type proc(data: []byte, options: Options, allocator: mem.Allocator) -> (img: ^Image, err: Error)
|
||||
Destroy_Proc :: #type proc(img: ^Image)
|
||||
|
||||
@(private)
|
||||
_internal_loaders: [Which_File_Type]Loader_Proc
|
||||
_internal_destroyers: [Which_File_Type]Destroy_Proc
|
||||
|
||||
register :: proc(kind: Which_File_Type, loader: Loader_Proc, destroyer: Destroy_Proc) {
|
||||
assert(loader != nil)
|
||||
assert(destroyer != nil)
|
||||
assert(_internal_loaders[kind] == nil)
|
||||
_internal_loaders[kind] = loader
|
||||
|
||||
assert(_internal_destroyers[kind] == nil)
|
||||
_internal_destroyers[kind] = destroyer
|
||||
}
|
||||
|
||||
load_from_bytes :: proc(data: []byte, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) {
|
||||
loader := _internal_loaders[which(data)]
|
||||
if loader == nil {
|
||||
return nil, .Unsupported_Format
|
||||
}
|
||||
return loader(data, options, allocator)
|
||||
}
|
||||
|
||||
|
||||
destroy :: proc(img: ^Image, allocator := context.allocator) {
|
||||
if img == nil {
|
||||
return
|
||||
}
|
||||
context.allocator = allocator
|
||||
destroyer := _internal_destroyers[img.which]
|
||||
if destroyer != nil {
|
||||
destroyer(img)
|
||||
} else {
|
||||
assert(img.metadata == nil)
|
||||
bytes.buffer_destroy(&img.pixels)
|
||||
free(img)
|
||||
}
|
||||
}
|
||||
|
||||
Which_File_Type :: enum {
|
||||
Unknown,
|
||||
@@ -28,11 +70,6 @@ Which_File_Type :: enum {
|
||||
XBM, // X BitMap
|
||||
}
|
||||
|
||||
which :: proc{
|
||||
which_bytes,
|
||||
which_file,
|
||||
}
|
||||
|
||||
which_bytes :: proc(data: []byte) -> Which_File_Type {
|
||||
test_tga :: proc(s: string) -> bool {
|
||||
get8 :: #force_inline proc(s: ^string) -> u8 {
|
||||
@@ -164,16 +201,3 @@ which_bytes :: proc(data: []byte) -> Which_File_Type {
|
||||
}
|
||||
return .Unknown
|
||||
}
|
||||
|
||||
|
||||
which_file :: proc(path: string) -> Which_File_Type {
|
||||
f, err := os.open(path)
|
||||
if err != 0 {
|
||||
return .Unknown
|
||||
}
|
||||
header: [128]byte
|
||||
os.read(f, header[:])
|
||||
file_type := which_bytes(header[:])
|
||||
os.close(f)
|
||||
return file_type
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
//+build js
|
||||
package image
|
||||
|
||||
load :: proc{
|
||||
load_from_bytes,
|
||||
}
|
||||
|
||||
which :: proc{
|
||||
which_bytes,
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
package image
|
||||
|
||||
import "core:mem"
|
||||
import "core:os"
|
||||
import "core:bytes"
|
||||
|
||||
Loader_Proc :: #type proc(data: []byte, options: Options, allocator: mem.Allocator) -> (img: ^Image, err: Error)
|
||||
Destroy_Proc :: #type proc(img: ^Image)
|
||||
|
||||
@(private)
|
||||
_internal_loaders: [Which_File_Type]Loader_Proc
|
||||
_internal_destroyers: [Which_File_Type]Destroy_Proc
|
||||
|
||||
register :: proc(kind: Which_File_Type, loader: Loader_Proc, destroyer: Destroy_Proc) {
|
||||
assert(loader != nil)
|
||||
assert(destroyer != nil)
|
||||
assert(_internal_loaders[kind] == nil)
|
||||
_internal_loaders[kind] = loader
|
||||
|
||||
assert(_internal_destroyers[kind] == nil)
|
||||
_internal_destroyers[kind] = destroyer
|
||||
}
|
||||
|
||||
load :: proc{
|
||||
load_from_bytes,
|
||||
load_from_file,
|
||||
}
|
||||
|
||||
load_from_bytes :: proc(data: []byte, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) {
|
||||
loader := _internal_loaders[which(data)]
|
||||
if loader == nil {
|
||||
return nil, .Unsupported_Format
|
||||
}
|
||||
return loader(data, options, allocator)
|
||||
}
|
||||
|
||||
|
||||
load_from_file :: proc(filename: string, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) {
|
||||
data, ok := os.read_entire_file(filename, allocator)
|
||||
defer delete(data, allocator)
|
||||
if ok {
|
||||
return load_from_bytes(data, options, allocator)
|
||||
} else {
|
||||
return nil, .Unable_To_Read_File
|
||||
}
|
||||
}
|
||||
|
||||
destroy :: proc(img: ^Image, allocator := context.allocator) {
|
||||
if img == nil {
|
||||
return
|
||||
}
|
||||
context.allocator = allocator
|
||||
destroyer := _internal_destroyers[img.which]
|
||||
if destroyer != nil {
|
||||
destroyer(img)
|
||||
} else {
|
||||
assert(img.metadata == nil)
|
||||
bytes.buffer_destroy(&img.pixels)
|
||||
free(img)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
//+build !js
|
||||
package image
|
||||
|
||||
import "core:os"
|
||||
|
||||
load :: proc{
|
||||
load_from_bytes,
|
||||
load_from_file,
|
||||
}
|
||||
|
||||
|
||||
load_from_file :: proc(filename: string, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) {
|
||||
data, ok := os.read_entire_file(filename, allocator)
|
||||
defer delete(data, allocator)
|
||||
if ok {
|
||||
return load_from_bytes(data, options, allocator)
|
||||
} else {
|
||||
return nil, .Unable_To_Read_File
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
which :: proc{
|
||||
which_bytes,
|
||||
which_file,
|
||||
}
|
||||
|
||||
which_file :: proc(path: string) -> Which_File_Type {
|
||||
f, err := os.open(path)
|
||||
if err != 0 {
|
||||
return .Unknown
|
||||
}
|
||||
header: [128]byte
|
||||
os.read(f, header[:])
|
||||
file_type := which_bytes(header[:])
|
||||
os.close(f)
|
||||
return file_type
|
||||
}
|
||||
@@ -4,7 +4,6 @@ import "core:bytes"
|
||||
import "core:fmt"
|
||||
import "core:image"
|
||||
import "core:mem"
|
||||
import "core:os"
|
||||
import "core:strconv"
|
||||
import "core:strings"
|
||||
import "core:unicode"
|
||||
@@ -27,23 +26,6 @@ PFM :: Formats{.Pf, .PF}
|
||||
ASCII :: Formats{.P1, .P2, .P3}
|
||||
BINARY :: Formats{.P4, .P5, .P6} + PAM + PFM
|
||||
|
||||
load :: proc {
|
||||
load_from_file,
|
||||
load_from_bytes,
|
||||
}
|
||||
|
||||
load_from_file :: proc(filename: string, allocator := context.allocator) -> (img: ^Image, err: Error) {
|
||||
context.allocator = allocator
|
||||
|
||||
data, ok := os.read_entire_file(filename); defer delete(data)
|
||||
if !ok {
|
||||
err = .Unable_To_Read_File
|
||||
return
|
||||
}
|
||||
|
||||
return load_from_bytes(data)
|
||||
}
|
||||
|
||||
load_from_bytes :: proc(data: []byte, allocator := context.allocator) -> (img: ^Image, err: Error) {
|
||||
context.allocator = allocator
|
||||
|
||||
@@ -67,24 +49,6 @@ load_from_bytes :: proc(data: []byte, allocator := context.allocator) -> (img: ^
|
||||
return img, nil
|
||||
}
|
||||
|
||||
save :: proc {
|
||||
save_to_file,
|
||||
save_to_buffer,
|
||||
}
|
||||
|
||||
save_to_file :: proc(filename: string, img: ^Image, custom_info: Info = {}, allocator := context.allocator) -> (err: Error) {
|
||||
context.allocator = allocator
|
||||
|
||||
data: []byte; defer delete(data)
|
||||
data = save_to_buffer(img, custom_info) or_return
|
||||
|
||||
if ok := os.write_entire_file(filename, data); !ok {
|
||||
return .Unable_To_Write_File
|
||||
}
|
||||
|
||||
return Format_Error.None
|
||||
}
|
||||
|
||||
save_to_buffer :: proc(img: ^Image, custom_info: Info = {}, allocator := context.allocator) -> (buffer: []byte, err: Error) {
|
||||
context.allocator = allocator
|
||||
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
//+build js
|
||||
package netpbm
|
||||
|
||||
load :: proc {
|
||||
load_from_bytes,
|
||||
}
|
||||
|
||||
save :: proc {
|
||||
save_to_buffer,
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
//+build !js
|
||||
package netpbm
|
||||
|
||||
import "core:os"
|
||||
|
||||
load :: proc {
|
||||
load_from_file,
|
||||
load_from_bytes,
|
||||
}
|
||||
|
||||
|
||||
load_from_file :: proc(filename: string, allocator := context.allocator) -> (img: ^Image, err: Error) {
|
||||
context.allocator = allocator
|
||||
|
||||
data, ok := os.read_entire_file(filename); defer delete(data)
|
||||
if !ok {
|
||||
err = .Unable_To_Read_File
|
||||
return
|
||||
}
|
||||
|
||||
return load_from_bytes(data)
|
||||
}
|
||||
|
||||
|
||||
save :: proc {
|
||||
save_to_file,
|
||||
save_to_buffer,
|
||||
}
|
||||
|
||||
save_to_file :: proc(filename: string, img: ^Image, custom_info: Info = {}, allocator := context.allocator) -> (err: Error) {
|
||||
context.allocator = allocator
|
||||
|
||||
data: []byte; defer delete(data)
|
||||
data = save_to_buffer(img, custom_info) or_return
|
||||
|
||||
if ok := os.write_entire_file(filename, data); !ok {
|
||||
return .Unable_To_Write_File
|
||||
}
|
||||
|
||||
return Format_Error.None
|
||||
}
|
||||
@@ -17,7 +17,6 @@ import "core:compress"
|
||||
import "core:compress/zlib"
|
||||
import "core:image"
|
||||
|
||||
import "core:os"
|
||||
import "core:hash"
|
||||
import "core:bytes"
|
||||
import "core:io"
|
||||
@@ -336,19 +335,6 @@ load_from_bytes :: proc(data: []byte, options := Options{}, allocator := context
|
||||
return img, err
|
||||
}
|
||||
|
||||
load_from_file :: proc(filename: string, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) {
|
||||
context.allocator = allocator
|
||||
|
||||
data, ok := os.read_entire_file(filename)
|
||||
defer delete(data)
|
||||
|
||||
if ok {
|
||||
return load_from_bytes(data, options)
|
||||
} else {
|
||||
return nil, .Unable_To_Read_File
|
||||
}
|
||||
}
|
||||
|
||||
load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) {
|
||||
context.allocator = allocator
|
||||
options := options
|
||||
@@ -1641,8 +1627,6 @@ defilter :: proc(img: ^Image, filter_bytes: ^bytes.Buffer, header: ^image.PNG_IH
|
||||
return nil
|
||||
}
|
||||
|
||||
load :: proc{load_from_file, load_from_bytes, load_from_context}
|
||||
|
||||
|
||||
@(init, private)
|
||||
_register :: proc() {
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
//+build js
|
||||
package png
|
||||
|
||||
load :: proc{load_from_bytes, load_from_context}
|
||||
@@ -0,0 +1,19 @@
|
||||
//+build !js
|
||||
package png
|
||||
|
||||
import "core:os"
|
||||
|
||||
load :: proc{load_from_file, load_from_bytes, load_from_context}
|
||||
|
||||
load_from_file :: proc(filename: string, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) {
|
||||
context.allocator = allocator
|
||||
|
||||
data, ok := os.read_entire_file(filename)
|
||||
defer delete(data)
|
||||
|
||||
if ok {
|
||||
return load_from_bytes(data, options)
|
||||
} else {
|
||||
return nil, .Unable_To_Read_File
|
||||
}
|
||||
}
|
||||
+1
-31
@@ -15,7 +15,6 @@ package qoi
|
||||
import "core:image"
|
||||
import "core:compress"
|
||||
import "core:bytes"
|
||||
import "core:os"
|
||||
|
||||
Error :: image.Error
|
||||
Image :: image.Image
|
||||
@@ -24,7 +23,7 @@ Options :: image.Options
|
||||
RGB_Pixel :: image.RGB_Pixel
|
||||
RGBA_Pixel :: image.RGBA_Pixel
|
||||
|
||||
save_to_memory :: proc(output: ^bytes.Buffer, img: ^Image, options := Options{}, allocator := context.allocator) -> (err: Error) {
|
||||
save_to_buffer :: proc(output: ^bytes.Buffer, img: ^Image, options := Options{}, allocator := context.allocator) -> (err: Error) {
|
||||
context.allocator = allocator
|
||||
|
||||
if img == nil {
|
||||
@@ -166,20 +165,6 @@ save_to_memory :: proc(output: ^bytes.Buffer, img: ^Image, options := Options{}
|
||||
return nil
|
||||
}
|
||||
|
||||
save_to_file :: proc(output: string, img: ^Image, options := Options{}, allocator := context.allocator) -> (err: Error) {
|
||||
context.allocator = allocator
|
||||
|
||||
out := &bytes.Buffer{}
|
||||
defer bytes.buffer_destroy(out)
|
||||
|
||||
save_to_memory(out, img, options) or_return
|
||||
write_ok := os.write_entire_file(output, out.buf[:])
|
||||
|
||||
return nil if write_ok else .Unable_To_Write_File
|
||||
}
|
||||
|
||||
save :: proc{save_to_memory, save_to_file}
|
||||
|
||||
load_from_bytes :: proc(data: []byte, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) {
|
||||
ctx := &compress.Context_Memory_Input{
|
||||
input_data = data,
|
||||
@@ -189,19 +174,6 @@ load_from_bytes :: proc(data: []byte, options := Options{}, allocator := context
|
||||
return img, err
|
||||
}
|
||||
|
||||
load_from_file :: proc(filename: string, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) {
|
||||
context.allocator = allocator
|
||||
|
||||
data, ok := os.read_entire_file(filename)
|
||||
defer delete(data)
|
||||
|
||||
if ok {
|
||||
return load_from_bytes(data, options)
|
||||
} else {
|
||||
return nil, .Unable_To_Read_File
|
||||
}
|
||||
}
|
||||
|
||||
@(optimization_mode="speed")
|
||||
load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) {
|
||||
context.allocator = allocator
|
||||
@@ -359,8 +331,6 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a
|
||||
return
|
||||
}
|
||||
|
||||
load :: proc{load_from_file, load_from_bytes, load_from_context}
|
||||
|
||||
/*
|
||||
Cleanup of image-specific data.
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
//+build js
|
||||
package qoi
|
||||
|
||||
save :: proc{save_to_buffer}
|
||||
|
||||
load :: proc{load_from_bytes, load_from_context}
|
||||
@@ -0,0 +1,37 @@
|
||||
//+build !js
|
||||
package qoi
|
||||
|
||||
import "core:os"
|
||||
import "core:bytes"
|
||||
|
||||
save :: proc{save_to_buffer, save_to_file}
|
||||
|
||||
|
||||
save_to_file :: proc(output: string, img: ^Image, options := Options{}, allocator := context.allocator) -> (err: Error) {
|
||||
context.allocator = allocator
|
||||
|
||||
out := &bytes.Buffer{}
|
||||
defer bytes.buffer_destroy(out)
|
||||
|
||||
save_to_buffer(out, img, options) or_return
|
||||
write_ok := os.write_entire_file(output, out.buf[:])
|
||||
|
||||
return nil if write_ok else .Unable_To_Write_File
|
||||
}
|
||||
|
||||
|
||||
load :: proc{load_from_file, load_from_bytes, load_from_context}
|
||||
|
||||
|
||||
load_from_file :: proc(filename: string, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) {
|
||||
context.allocator = allocator
|
||||
|
||||
data, ok := os.read_entire_file(filename)
|
||||
defer delete(data)
|
||||
|
||||
if ok {
|
||||
return load_from_bytes(data, options)
|
||||
} else {
|
||||
return nil, .Unable_To_Read_File
|
||||
}
|
||||
}
|
||||
+1
-30
@@ -14,7 +14,6 @@ package tga
|
||||
import "core:mem"
|
||||
import "core:image"
|
||||
import "core:bytes"
|
||||
import "core:os"
|
||||
import "core:compress"
|
||||
import "core:strings"
|
||||
|
||||
@@ -28,7 +27,7 @@ GA_Pixel :: image.GA_Pixel
|
||||
RGB_Pixel :: image.RGB_Pixel
|
||||
RGBA_Pixel :: image.RGBA_Pixel
|
||||
|
||||
save_to_memory :: proc(output: ^bytes.Buffer, img: ^Image, options := Options{}, allocator := context.allocator) -> (err: Error) {
|
||||
save_to_buffer :: proc(output: ^bytes.Buffer, img: ^Image, options := Options{}, allocator := context.allocator) -> (err: Error) {
|
||||
context.allocator = allocator
|
||||
|
||||
if img == nil {
|
||||
@@ -92,20 +91,6 @@ save_to_memory :: proc(output: ^bytes.Buffer, img: ^Image, options := Options{}
|
||||
return nil
|
||||
}
|
||||
|
||||
save_to_file :: proc(output: string, img: ^Image, options := Options{}, allocator := context.allocator) -> (err: Error) {
|
||||
context.allocator = allocator
|
||||
|
||||
out := &bytes.Buffer{}
|
||||
defer bytes.buffer_destroy(out)
|
||||
|
||||
save_to_memory(out, img, options) or_return
|
||||
write_ok := os.write_entire_file(output, out.buf[:])
|
||||
|
||||
return nil if write_ok else .Unable_To_Write_File
|
||||
}
|
||||
|
||||
save :: proc{save_to_memory, save_to_file}
|
||||
|
||||
load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) {
|
||||
context.allocator = allocator
|
||||
options := options
|
||||
@@ -398,20 +383,6 @@ load_from_bytes :: proc(data: []byte, options := Options{}, allocator := context
|
||||
return img, err
|
||||
}
|
||||
|
||||
load_from_file :: proc(filename: string, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) {
|
||||
context.allocator = allocator
|
||||
|
||||
data, ok := os.read_entire_file(filename)
|
||||
defer delete(data)
|
||||
|
||||
if ok {
|
||||
return load_from_bytes(data, options)
|
||||
} else {
|
||||
return nil, .Unable_To_Read_File
|
||||
}
|
||||
}
|
||||
|
||||
load :: proc{load_from_file, load_from_bytes, load_from_context}
|
||||
|
||||
destroy :: proc(img: ^Image) {
|
||||
if img == nil || img.width == 0 || img.height == 0 {
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
//+build js
|
||||
package tga
|
||||
|
||||
save :: proc{save_to_buffer}
|
||||
load :: proc{load_from_bytes, load_from_context}
|
||||
@@ -0,0 +1,34 @@
|
||||
//+build !js
|
||||
package tga
|
||||
|
||||
import "core:os"
|
||||
import "core:bytes"
|
||||
|
||||
save :: proc{save_to_buffer, save_to_file}
|
||||
|
||||
save_to_file :: proc(output: string, img: ^Image, options := Options{}, allocator := context.allocator) -> (err: Error) {
|
||||
context.allocator = allocator
|
||||
|
||||
out := &bytes.Buffer{}
|
||||
defer bytes.buffer_destroy(out)
|
||||
|
||||
save_to_buffer(out, img, options) or_return
|
||||
write_ok := os.write_entire_file(output, out.buf[:])
|
||||
|
||||
return nil if write_ok else .Unable_To_Write_File
|
||||
}
|
||||
|
||||
load :: proc{load_from_file, load_from_bytes, load_from_context}
|
||||
|
||||
load_from_file :: proc(filename: string, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) {
|
||||
context.allocator = allocator
|
||||
|
||||
data, ok := os.read_entire_file(filename)
|
||||
defer delete(data)
|
||||
|
||||
if ok {
|
||||
return load_from_bytes(data, options)
|
||||
} else {
|
||||
return nil, .Unable_To_Read_File
|
||||
}
|
||||
}
|
||||
@@ -429,11 +429,11 @@ reflect :: proc(I, N: $T) -> (out: T) where IS_ARRAY(T), IS_FLOAT(ELEM_TYPE(T))
|
||||
b := N * (2 * dot(N, I))
|
||||
return I - b
|
||||
}
|
||||
refract :: proc(I, N: $T) -> (out: T) where IS_ARRAY(T), IS_FLOAT(ELEM_TYPE(T)) {
|
||||
dv := dot(N, I)
|
||||
k := 1 - eta*eta - (1 - dv*dv)
|
||||
refract :: proc(I, Normal: $V/[$N]$E, eta: E) -> (out: V) where IS_ARRAY(V), IS_FLOAT(ELEM_TYPE(V)) {
|
||||
dv := dot(Normal, I)
|
||||
k := 1 - eta*eta * (1 - dv*dv)
|
||||
a := I * eta
|
||||
b := N * eta*dv*math.sqrt(k)
|
||||
b := Normal * (eta*dv+math.sqrt(k))
|
||||
return (a - b) * E(int(k >= 0))
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,744 @@
|
||||
// +build windows, linux, darwin
|
||||
package net
|
||||
|
||||
/*
|
||||
Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures.
|
||||
For other protocols and their features, see subdirectories of this package.
|
||||
*/
|
||||
|
||||
/*
|
||||
Copyright 2022 Tetralux <tetraluxonpc@gmail.com>
|
||||
Copyright 2022 Colin Davidson <colrdavidson@gmail.com>
|
||||
Copyright 2022 Jeroen van Rijn <nom@duclavier.com>.
|
||||
Made available under Odin's BSD-3 license.
|
||||
|
||||
List of contributors:
|
||||
Tetralux: Initial implementation
|
||||
Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver
|
||||
Jeroen van Rijn: Cross platform unification, code style, documentation
|
||||
*/
|
||||
|
||||
import "core:strconv"
|
||||
import "core:strings"
|
||||
import "core:fmt"
|
||||
|
||||
/*
|
||||
Expects an IPv4 address with no leading or trailing whitespace:
|
||||
- a.b.c.d
|
||||
- a.b.c.d:port
|
||||
- [a.b.c.d]:port
|
||||
|
||||
If the IP address is bracketed, the port must be present and valid (though it will be ignored):
|
||||
- [a.b.c.d] will be treated as a parsing failure.
|
||||
|
||||
The port, if present, is required to be a base 10 number in the range 0-65535, inclusive.
|
||||
|
||||
If `allow_non_decimal` is false, `aton` is told each component must be decimal and max 255.
|
||||
*/
|
||||
parse_ip4_address :: proc(address_and_maybe_port: string, allow_non_decimal := false) -> (addr: IP4_Address, ok: bool) {
|
||||
res := aton(address_and_maybe_port, .IP4, !allow_non_decimal) or_return
|
||||
return res.?
|
||||
}
|
||||
|
||||
/*
|
||||
Parses an IP address in "non-decimal" `inet_aton` form.
|
||||
|
||||
e.g."00377.0x0ff.65534" = 255.255.255.254
|
||||
00377 = 255 in octal
|
||||
0x0ff = 255 in hexadecimal
|
||||
This leaves 16 bits worth of address
|
||||
.65534 then accounts for the last two digits
|
||||
|
||||
For the address part the allowed forms are:
|
||||
a.b.c.d - where each part represents a byte
|
||||
a.b.c - where `a` & `b` represent a byte and `c` a u16
|
||||
a.b - where `a` represents a byte and `b` supplies the trailing 24 bits
|
||||
a - where `a` gives the entire 32-bit value
|
||||
|
||||
The port, if present, is required to be a base 10 number in the range 0-65535, inclusive.
|
||||
*/
|
||||
aton :: proc(address_and_maybe_port: string, family: Address_Family, allow_decimal_only := false) -> (addr: Address, ok: bool) {
|
||||
switch family {
|
||||
case .IP4:
|
||||
// There is no valid address shorter than `0.0.0.0`.
|
||||
if len(address_and_maybe_port) < 7 {
|
||||
return {}, false
|
||||
}
|
||||
|
||||
address, _ := split_port(address_and_maybe_port) or_return // This call doesn't allocate
|
||||
|
||||
buf: [4]u64 = {}
|
||||
i := 0
|
||||
|
||||
max_value := u64(max(u32))
|
||||
bases := DEFAULT_DIGIT_BASES
|
||||
|
||||
if allow_decimal_only {
|
||||
max_value = 255
|
||||
bases = {.Dec}
|
||||
}
|
||||
|
||||
for len(address) > 0 {
|
||||
if i == 4 {
|
||||
return {}, false
|
||||
}
|
||||
|
||||
// Decimal-only addresses may not have a leading zero.
|
||||
if allow_decimal_only && len(address) > 1 && address[0] == '0' && address[1] != '.' {
|
||||
return
|
||||
}
|
||||
|
||||
number, consumed, number_ok := parse_ip_component(address, max_value, bases)
|
||||
if !number_ok || consumed == 0 {
|
||||
return {}, false
|
||||
}
|
||||
|
||||
buf[i] = number
|
||||
|
||||
address = address[consumed:]
|
||||
|
||||
if len(address) > 0 && address[0] == '.' {
|
||||
address = address[1:]
|
||||
}
|
||||
i += 1
|
||||
}
|
||||
|
||||
// Distribute parts.
|
||||
switch i {
|
||||
case 1:
|
||||
buf[1] = buf[0] & 0xffffff
|
||||
buf[0] >>= 24
|
||||
fallthrough
|
||||
case 2:
|
||||
buf[2] = buf[1] & 0xffff
|
||||
buf[1] >>= 16
|
||||
fallthrough
|
||||
case 3:
|
||||
buf[3] = buf[2] & 0xff
|
||||
buf[2] >>= 8
|
||||
}
|
||||
|
||||
a: [4]u8 = ---
|
||||
for v, i in buf {
|
||||
if v > 255 { return {}, false }
|
||||
a[i] = u8(v)
|
||||
}
|
||||
return IP4_Address(a), true
|
||||
|
||||
case .IP6:
|
||||
return parse_ip6_address(address_and_maybe_port)
|
||||
|
||||
case:
|
||||
return nil, false
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
The minimum length of a valid IPv6 address string is 2, e.g. `::`
|
||||
|
||||
The maximum length of a valid IPv6 address string is 45, when it embeds an IPv4,
|
||||
e.g. `0000:0000:0000:0000:0000:ffff:255.255.255.255`
|
||||
|
||||
An IPv6 address must contain at least 3 pieces, e.g. `::`,
|
||||
and at most 9 (using `::` for a trailing or leading 0)
|
||||
*/
|
||||
IPv6_MIN_STRING_LENGTH :: 2
|
||||
IPv6_MAX_STRING_LENGTH :: 45
|
||||
IPv6_MIN_COLONS :: 2
|
||||
IPv6_PIECE_COUNT :: 8
|
||||
|
||||
parse_ip6_address :: proc(address_and_maybe_port: string) -> (addr: IP6_Address, ok: bool) {
|
||||
// If we have an IPv6 address of the form [IP]:Port, first get us just the IP.
|
||||
address, _ := split_port(address_and_maybe_port) or_return
|
||||
|
||||
// Early bailouts based on length and number of pieces.
|
||||
if len(address) < IPv6_MIN_STRING_LENGTH || len(address) > IPv6_MAX_STRING_LENGTH { return }
|
||||
|
||||
/*
|
||||
Do a pre-pass on the string that checks how many `:` and `.` we have,
|
||||
if they're in the right order, and if the things between them are digits as expected.
|
||||
|
||||
It's not strictly necessary considering we could use `strings.split`,
|
||||
but this way we can avoid using an allocator and return earlier on bogus input. Win-win.
|
||||
*/
|
||||
colon_count := 0
|
||||
dot_count := 0
|
||||
|
||||
pieces_temp: [IPv6_PIECE_COUNT + 1]string
|
||||
|
||||
piece_start := 0
|
||||
piece_end := 0
|
||||
|
||||
for ch, i in address {
|
||||
switch ch {
|
||||
case '0'..='9', 'a'..='f', 'A'..='F':
|
||||
piece_end += 1
|
||||
|
||||
case ':':
|
||||
// If we see a `:` after a `.`, it means an IPv4 part was sandwiched between IPv6, instead of it being the tail: invalid.
|
||||
if dot_count > 0 { return }
|
||||
|
||||
pieces_temp[colon_count] = address[piece_start:piece_end]
|
||||
|
||||
colon_count += 1
|
||||
if colon_count > IPv6_PIECE_COUNT { return }
|
||||
|
||||
// If there's anything left, put it in the next piece.
|
||||
piece_start = i + 1
|
||||
piece_end = piece_start
|
||||
|
||||
case '.':
|
||||
// IPv4 address is treated as one piece. No need to update `piece_*`.
|
||||
dot_count += 1
|
||||
|
||||
case: // Invalid character, return early
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if colon_count < IPv6_MIN_COLONS { return }
|
||||
|
||||
// Assign the last piece string.
|
||||
pieces_temp[colon_count] = address[piece_start:]
|
||||
|
||||
// `pieces` now holds the same output as it would if had used `strings.split`.
|
||||
pieces := pieces_temp[:colon_count + 1]
|
||||
|
||||
// Check if we have what looks like an embedded IPv4 address.
|
||||
ipv4: IP4_Address
|
||||
have_ipv4: bool
|
||||
|
||||
if dot_count > 0 {
|
||||
/*
|
||||
If we have an IPv4 address accounting for the last 32 bits,
|
||||
this means we can have at most 6 IPv6 pieces, like so: `x:x:X:x:x:x:d.d.d.d`
|
||||
|
||||
Or, put differently: 6 pieces IPv6 (5 colons), a colon, 1 piece IPv4 (3 dots),
|
||||
for a total of 6 colons and 3 dots.
|
||||
*/
|
||||
if dot_count != 3 || colon_count > 6 { return }
|
||||
|
||||
/*
|
||||
Try to parse IPv4 address.
|
||||
If successful, we have our least significant 32 bits.
|
||||
If not, it invalidates the whole address and we can bail.
|
||||
*/
|
||||
ipv4, have_ipv4 = parse_ip4_address(pieces_temp[colon_count])
|
||||
if !have_ipv4 { return }
|
||||
}
|
||||
|
||||
// Check for `::` being used more than once, and save the skip.
|
||||
zero_skip := -1
|
||||
for i in 1..<colon_count {
|
||||
if pieces[i] == "" {
|
||||
// Return if skip has already been set.
|
||||
if zero_skip != -1 { return }
|
||||
zero_skip = i
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Now check if we have the necessary number pieces, accounting for any `::`,
|
||||
and how many were skipped by it if applicable.
|
||||
*/
|
||||
before_skip := 0
|
||||
after_skip := 0
|
||||
num_skipped := 0
|
||||
|
||||
if zero_skip != -1 {
|
||||
before_skip = zero_skip
|
||||
after_skip = colon_count - zero_skip
|
||||
|
||||
// An IPv4 "piece" accounts for 2 IPv6 pieces we haven't added to the pieces slice, so add 1.
|
||||
if have_ipv4 {
|
||||
after_skip += 1
|
||||
}
|
||||
|
||||
// Adjust for leading `::`.
|
||||
if pieces[0] == "" {
|
||||
before_skip -= 1
|
||||
// Leading `:` can only be part of `::`.
|
||||
if before_skip > 0 { return }
|
||||
}
|
||||
|
||||
// Adjust for trailing `::`.
|
||||
if pieces[colon_count] == "" {
|
||||
after_skip -= 1
|
||||
// Trailing `:` can only be part of `::`.
|
||||
if after_skip > 0 { return }
|
||||
}
|
||||
|
||||
/*
|
||||
Calculate how many zero pieces we skipped.
|
||||
It should be at least one, considering we encountered a `::`.
|
||||
*/
|
||||
num_skipped = IPv6_PIECE_COUNT - before_skip - after_skip
|
||||
if num_skipped < 1 { return }
|
||||
|
||||
} else {
|
||||
/*
|
||||
No zero skip means everything is part of "before the skip".
|
||||
An IPv4 "piece" accounts for 2 IPv6 pieces we haven't added to the pieces slice, so add 1.
|
||||
*/
|
||||
piece_count := colon_count + 1
|
||||
if have_ipv4 {
|
||||
piece_count += 1
|
||||
}
|
||||
|
||||
// Do we have the complete set?
|
||||
if piece_count != IPv6_PIECE_COUNT { return }
|
||||
|
||||
// Validate leading and trailing empty parts, as they can only be part of a `::`.
|
||||
if pieces[0] == "" || pieces[colon_count] == "" { return }
|
||||
|
||||
|
||||
before_skip = piece_count
|
||||
after_skip = 0
|
||||
num_skipped = 0
|
||||
}
|
||||
|
||||
// Now try to parse the pieces into a 8 16-bit pieces.
|
||||
piece_values: [IPv6_PIECE_COUNT]u16be
|
||||
|
||||
idx := 0
|
||||
val_idx := 0
|
||||
|
||||
for _ in 0..<before_skip {
|
||||
/*
|
||||
An empty piece is the default zero. Otherwise, try to parse as an IPv6 hex piece.
|
||||
If we have an IPv4 address, stop on the penultimate index.
|
||||
*/
|
||||
if have_ipv4 && val_idx == 6 {
|
||||
break
|
||||
}
|
||||
|
||||
piece := pieces[idx]
|
||||
|
||||
// An IPv6 piece can at most contain 4 hex digits.
|
||||
if len(piece) > 4 { return }
|
||||
|
||||
if piece != "" {
|
||||
val, _ := parse_ip_component(piece, 65535, {.IPv6}) or_return
|
||||
piece_values[val_idx] = u16be(val)
|
||||
}
|
||||
|
||||
idx += 1
|
||||
val_idx += 1
|
||||
}
|
||||
|
||||
if before_skip == 0 {
|
||||
idx += 1
|
||||
}
|
||||
|
||||
if num_skipped > 0 {
|
||||
idx += 1
|
||||
val_idx += num_skipped
|
||||
}
|
||||
|
||||
if after_skip > 0 {
|
||||
for _ in 0..<after_skip {
|
||||
/*
|
||||
An empty piece is the default zero. Otherwise, try to parse as an IPv6 hex piece.
|
||||
If we have an IPv4 address, stop on the penultimate index.
|
||||
*/
|
||||
if have_ipv4 && val_idx == 6 {
|
||||
break
|
||||
}
|
||||
|
||||
piece := pieces[idx]
|
||||
|
||||
// An IPv6 piece can contain at most 4 hex digits.
|
||||
if len(piece) > 4 { return }
|
||||
|
||||
if piece != "" {
|
||||
val, _ := parse_ip_component(piece, 65535, {.IPv6}) or_return
|
||||
piece_values[val_idx] = u16be(val)
|
||||
}
|
||||
|
||||
idx += 1
|
||||
val_idx += 1
|
||||
}
|
||||
}
|
||||
|
||||
// Distribute IPv4 address into last two pieces, if applicable.
|
||||
if have_ipv4 {
|
||||
val := u16(ipv4[0]) << 8
|
||||
val |= u16(ipv4[1])
|
||||
piece_values[6] = u16be(val)
|
||||
|
||||
val = u16(ipv4[2]) << 8
|
||||
val |= u16(ipv4[3])
|
||||
piece_values[7] = u16be(val)
|
||||
}
|
||||
return transmute(IP6_Address)piece_values, true
|
||||
}
|
||||
|
||||
/*
|
||||
Try parsing as an IPv6 address.
|
||||
If it's determined not to be, try as an IPv4 address, optionally in non-decimal format.
|
||||
*/
|
||||
parse_address :: proc(address_and_maybe_port: string, non_decimal_address := false) -> Address {
|
||||
if addr6, ok6 := parse_ip6_address(address_and_maybe_port); ok6 {
|
||||
return addr6
|
||||
}
|
||||
if addr4, ok4 := parse_ip4_address(address_and_maybe_port, non_decimal_address); ok4 {
|
||||
return addr4
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
parse_endpoint :: proc(endpoint_str: string) -> (ep: Endpoint, ok: bool) {
|
||||
if addr_str, port, split_ok := split_port(endpoint_str); split_ok {
|
||||
if addr := parse_address(addr_str); addr != nil {
|
||||
return Endpoint { address = addr, port = port }, true
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
Host :: struct {
|
||||
hostname: string,
|
||||
port: int,
|
||||
}
|
||||
Host_Or_Endpoint :: union {
|
||||
Host,
|
||||
Endpoint,
|
||||
}
|
||||
|
||||
// Takes a string consisting of a hostname or IP address, and an optional port,
|
||||
// and return the component parts in a useful form.
|
||||
parse_hostname_or_endpoint :: proc(endpoint_str: string) -> (target: Host_Or_Endpoint, err: Parse_Endpoint_Error) {
|
||||
host, port, port_ok := split_port(endpoint_str)
|
||||
if !port_ok {
|
||||
return nil, .Bad_Port
|
||||
}
|
||||
if addr := parse_address(host); addr != nil {
|
||||
return Endpoint{addr, port}, .None
|
||||
}
|
||||
if !validate_hostname(host) {
|
||||
return nil, .Bad_Hostname
|
||||
}
|
||||
return Host{host, port}, .None
|
||||
}
|
||||
|
||||
|
||||
// Takes an endpoint string and returns its parts.
|
||||
// Returns ok=false if port is not a number.
|
||||
split_port :: proc(endpoint_str: string) -> (addr_or_host: string, port: int, ok: bool) {
|
||||
// IP6 [addr_or_host]:port
|
||||
if i := strings.last_index(endpoint_str, "]:"); i >= 0 {
|
||||
addr_or_host = endpoint_str[1:i]
|
||||
port, ok = strconv.parse_int(endpoint_str[i+2:], 10)
|
||||
|
||||
if port > 65535 {
|
||||
ok = false
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if n := strings.count(endpoint_str, ":"); n == 1 {
|
||||
// IP4 addr_or_host:port
|
||||
i := strings.last_index(endpoint_str, ":")
|
||||
assert(i != -1)
|
||||
|
||||
addr_or_host = endpoint_str[:i]
|
||||
port, ok = strconv.parse_int(endpoint_str[i+1:], 10)
|
||||
|
||||
if port > 65535 {
|
||||
ok = false
|
||||
}
|
||||
return
|
||||
} else if n > 1 {
|
||||
// IP6 address without port
|
||||
}
|
||||
|
||||
// No port
|
||||
addr_or_host = endpoint_str
|
||||
port = 0
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
|
||||
// Joins an address or hostname with a port.
|
||||
join_port :: proc(address_or_host: string, port: int, allocator := context.allocator) -> string {
|
||||
addr_or_host, _, ok := split_port(address_or_host)
|
||||
if !ok do return addr_or_host
|
||||
|
||||
b := strings.builder_make(allocator)
|
||||
|
||||
addr := parse_address(addr_or_host)
|
||||
if addr == nil {
|
||||
// hostname
|
||||
fmt.sbprintf(&b, "%v:%v", addr_or_host, port)
|
||||
} else {
|
||||
switch in addr {
|
||||
case IP4_Address:
|
||||
fmt.sbprintf(&b, "%v:%v", address_to_string(addr), port)
|
||||
case IP6_Address:
|
||||
fmt.sbprintf(&b, "[%v]:%v", address_to_string(addr), port)
|
||||
}
|
||||
}
|
||||
return strings.to_string(b)
|
||||
}
|
||||
|
||||
|
||||
|
||||
// TODO(tetra): Do we need this?
|
||||
map_to_ip6 :: proc(addr: Address) -> Address {
|
||||
if addr6, ok := addr.(IP6_Address); ok {
|
||||
return addr6
|
||||
}
|
||||
addr4 := addr.(IP4_Address)
|
||||
addr4_u16 := transmute([2]u16be) addr4
|
||||
addr6: IP6_Address
|
||||
addr6[4] = 0xffff
|
||||
copy(addr6[5:], addr4_u16[:])
|
||||
return addr6
|
||||
}
|
||||
|
||||
/*
|
||||
Returns a temporarily-allocated string representation of the address.
|
||||
|
||||
See RFC 5952 section 4 for IPv6 representation recommendations.
|
||||
*/
|
||||
address_to_string :: proc(addr: Address, allocator := context.temp_allocator) -> string {
|
||||
b := strings.builder_make(allocator)
|
||||
switch v in addr {
|
||||
case IP4_Address:
|
||||
fmt.sbprintf(&b, "%v.%v.%v.%v", v[0], v[1], v[2], v[3])
|
||||
case IP6_Address:
|
||||
// First find the longest run of zeroes.
|
||||
Zero_Run :: struct {
|
||||
start: int,
|
||||
end: int,
|
||||
}
|
||||
|
||||
/*
|
||||
We're dealing with 0-based indices, appropriately enough for runs of zeroes.
|
||||
Still, it means we need to initialize runs with some value outside of the possible range.
|
||||
*/
|
||||
run := Zero_Run{-1, -1}
|
||||
best := Zero_Run{-1, -1}
|
||||
|
||||
addr := transmute([8]u16be)v
|
||||
|
||||
last := u16be(1)
|
||||
for val, i in addr {
|
||||
/*
|
||||
If we encounter adjacent zeroes, then start a new run if not already in one.
|
||||
Also remember the rightmost index regardless, because it'll be the new
|
||||
frontier of both new and existing runs.
|
||||
*/
|
||||
if last == 0 && val == 0 {
|
||||
run.end = i
|
||||
if run.start == -1 {
|
||||
run.start = i - 1
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
If we're in a run check if its length is better than the best recorded so far.
|
||||
If so, update the best run's start and end.
|
||||
*/
|
||||
if run.start != -1 {
|
||||
length_to_beat := best.end - best.start
|
||||
length := run.end - run.start
|
||||
|
||||
if length > length_to_beat {
|
||||
best = run
|
||||
}
|
||||
}
|
||||
|
||||
// If we were in a run, this is where we reset it.
|
||||
if val != 0 {
|
||||
run = {-1, -1}
|
||||
}
|
||||
|
||||
last = val
|
||||
}
|
||||
|
||||
for val, i in addr {
|
||||
if best.start == i || best.end == i {
|
||||
// For the left and right side of the best zero run, print a `:`.
|
||||
fmt.sbprint(&b, ":")
|
||||
} else if i < best.start {
|
||||
/*
|
||||
If we haven't made it to the best run yet, print the digit.
|
||||
Make sure we only print a `:` after the digit if it's not
|
||||
immediately followed by the run's own leftmost `:`.
|
||||
*/
|
||||
fmt.sbprintf(&b, "%x", val)
|
||||
if i < best.start - 1 {
|
||||
fmt.sbprintf(&b, ":")
|
||||
}
|
||||
} else if i > best.end {
|
||||
/*
|
||||
If there are any digits after the zero run, print them.
|
||||
But don't print the `:` at the end of the IP number.
|
||||
*/
|
||||
fmt.sbprintf(&b, "%x", val)
|
||||
if i != 7 {
|
||||
fmt.sbprintf(&b, ":")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return strings.to_string(b)
|
||||
}
|
||||
|
||||
// Returns a temporarily-allocated string representation of the endpoint.
|
||||
// If there's a port, uses the `[address]:port` format.
|
||||
endpoint_to_string :: proc(ep: Endpoint, allocator := context.temp_allocator) -> string {
|
||||
if ep.port == 0 {
|
||||
return address_to_string(ep.address, allocator)
|
||||
} else {
|
||||
s := address_to_string(ep.address, context.temp_allocator)
|
||||
b := strings.builder_make(allocator)
|
||||
switch a in ep.address {
|
||||
case IP4_Address: fmt.sbprintf(&b, "%v:%v", s, ep.port)
|
||||
case IP6_Address: fmt.sbprintf(&b, "[%v]:%v", s, ep.port)
|
||||
}
|
||||
return strings.to_string(b)
|
||||
}
|
||||
}
|
||||
|
||||
to_string :: proc{address_to_string, endpoint_to_string}
|
||||
|
||||
|
||||
family_from_address :: proc(addr: Address) -> Address_Family {
|
||||
switch in addr {
|
||||
case IP4_Address: return .IP4
|
||||
case IP6_Address: return .IP6
|
||||
case:
|
||||
unreachable()
|
||||
}
|
||||
}
|
||||
family_from_endpoint :: proc(ep: Endpoint) -> Address_Family {
|
||||
return family_from_address(ep.address)
|
||||
}
|
||||
|
||||
|
||||
Digit_Parse_Base :: enum u8 {
|
||||
Dec = 0, // No prefix
|
||||
Oct = 1, // Leading zero
|
||||
Hex = 2, // 0x prefix
|
||||
IPv6 = 3, // Unprefixed IPv6 piece hex. Can't be used with other bases.
|
||||
}
|
||||
Digit_Parse_Bases :: bit_set[Digit_Parse_Base; u8]
|
||||
DEFAULT_DIGIT_BASES :: Digit_Parse_Bases{.Dec, .Oct, .Hex}
|
||||
|
||||
/*
|
||||
Parses a single unsigned number in requested `bases` from `input`.
|
||||
`max_value` represents the maximum allowed value for this number.
|
||||
|
||||
Returns the `value`, the `bytes_consumed` so far, and `ok` to signal success or failure.
|
||||
|
||||
An out-of-range or invalid number will return the accumulated value so far (which can be out of range),
|
||||
the number of bytes consumed leading up the error, and `ok = false`.
|
||||
|
||||
When `.` or `:` are encountered, they'll be considered valid separators and will stop parsing,
|
||||
returning the valid number leading up to it.
|
||||
|
||||
Other non-digit characters are treated as an error.
|
||||
|
||||
Octal numbers are expected to have a leading zero, with no 'o' format specifier.
|
||||
Hexadecimal numbers are expected to be preceded by '0x' or '0X'.
|
||||
Numbers will otherwise be considered to be in base 10.
|
||||
*/
|
||||
parse_ip_component :: proc(input: string, max_value := u64(max(u32)), bases := DEFAULT_DIGIT_BASES) -> (value: u64, bytes_consumed: int, ok: bool) {
|
||||
// Default to base 10
|
||||
base := u64(10)
|
||||
input := input
|
||||
|
||||
/*
|
||||
We keep track of the number of prefix bytes and digit bytes separately.
|
||||
This way if a prefix is consumed and we encounter a separator or the end of the string,
|
||||
the number is only considered valid if at least 1 digit byte has been consumed and the value is within range.
|
||||
*/
|
||||
prefix_bytes := 0
|
||||
digit_bytes := 0
|
||||
|
||||
/*
|
||||
IPv6 hex bytes are unprefixed and can't be disambiguated from octal or hex unless the digit is out of range.
|
||||
If we got the `.IPv6` option, skip prefix scanning and other flags aren't also used.
|
||||
*/
|
||||
if .IPv6 in bases {
|
||||
if bases != {.IPv6} { return } // Must be used on its own.
|
||||
base = 16
|
||||
} else {
|
||||
// Scan for and consume prefix, if applicable.
|
||||
if len(input) >= 2 && input[0] == '0' {
|
||||
if .Hex in bases && (input[1] == 'x' || input[1] == 'X') {
|
||||
base = 16
|
||||
input = input[2:]
|
||||
prefix_bytes = 2
|
||||
}
|
||||
if prefix_bytes == 0 && .Oct in bases {
|
||||
base = 8
|
||||
input = input[1:]
|
||||
prefix_bytes = 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
parse_loop: for ch in input {
|
||||
switch ch {
|
||||
case '0'..='7':
|
||||
digit_bytes += 1
|
||||
value = value * base + u64(ch - '0')
|
||||
|
||||
case '8'..='9':
|
||||
digit_bytes += 1
|
||||
|
||||
if base == 8 {
|
||||
// Out of range for octal numbers.
|
||||
return value, digit_bytes + prefix_bytes, false
|
||||
}
|
||||
value = value * base + u64(ch - '0')
|
||||
|
||||
case 'a'..='f':
|
||||
digit_bytes += 1
|
||||
|
||||
if base == 8 || base == 10 {
|
||||
// Out of range for octal and decimal numbers.
|
||||
return value, digit_bytes + prefix_bytes, false
|
||||
}
|
||||
value = value * base + (u64(ch - 'a') + 10)
|
||||
|
||||
case 'A'..='F':
|
||||
digit_bytes += 1
|
||||
|
||||
if base == 8 || base == 10 {
|
||||
// Out of range for octal and decimal numbers.
|
||||
return value, digit_bytes + prefix_bytes, false
|
||||
}
|
||||
value = value * base + (u64(ch - 'A') + 10)
|
||||
|
||||
case '.', ':':
|
||||
/*
|
||||
Number separator. Return early.
|
||||
We don't need to check if the number is in range.
|
||||
We do that each time through the loop.
|
||||
*/
|
||||
break parse_loop
|
||||
|
||||
case:
|
||||
// Invalid character encountered.
|
||||
return value, digit_bytes + prefix_bytes, false
|
||||
}
|
||||
|
||||
if value > max_value {
|
||||
// Out-of-range number.
|
||||
return value, digit_bytes + prefix_bytes, false
|
||||
}
|
||||
}
|
||||
|
||||
// If we consumed at least 1 digit byte, `value` *should* continue a valid number in an appropriate base in the allowable range.
|
||||
return value, digit_bytes + prefix_bytes, digit_bytes >= 1
|
||||
}
|
||||
|
||||
// Returns an address for each interface that can be bound to.
|
||||
get_network_interfaces :: proc() -> []Address {
|
||||
// TODO: Implement using `enumerate_interfaces` and returning only the addresses of active interfaces.
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,416 @@
|
||||
// +build windows, linux, darwin
|
||||
package net
|
||||
|
||||
/*
|
||||
Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures.
|
||||
For other protocols and their features, see subdirectories of this package.
|
||||
|
||||
This file collects structs, enums and settings applicable to the entire package in one handy place.
|
||||
Platform-specific ones can be found in their respective `*_windows.odin` and similar files.
|
||||
*/
|
||||
|
||||
/*
|
||||
Copyright 2022 Tetralux <tetraluxonpc@gmail.com>
|
||||
Copyright 2022 Colin Davidson <colrdavidson@gmail.com>
|
||||
Copyright 2022 Jeroen van Rijn <nom@duclavier.com>.
|
||||
Made available under Odin's BSD-3 license.
|
||||
|
||||
List of contributors:
|
||||
Tetralux: Initial implementation
|
||||
Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver
|
||||
Jeroen van Rijn: Cross platform unification, code style, documentation
|
||||
*/
|
||||
|
||||
import "core:runtime"
|
||||
|
||||
/*
|
||||
TUNEABLES - See also top of `dns.odin` for DNS configuration.
|
||||
|
||||
Determines the default value for whether dial_tcp() and accept_tcp() will set TCP_NODELAY on the new
|
||||
socket, and the client socket, respectively.
|
||||
This can also be set on a per-socket basis using the 'options' optional parameter to those procedures.
|
||||
|
||||
When TCP_NODELAY is set, data will be sent out to the peer as quickly as possible, rather than being
|
||||
coalesced into fewer network packets.
|
||||
|
||||
This makes the networking layer more eagerly send data when you ask it to,
|
||||
which can reduce latency by up to 200ms.
|
||||
|
||||
This does mean that a lot of small writes will negatively effect throughput however,
|
||||
since the Nagle algorithm will be disabled, and each write becomes one
|
||||
IP packet. This will increase traffic by a factor of 40, with IP and TCP
|
||||
headers for each payload.
|
||||
|
||||
However, you can avoid this by buffering things up yourself if you wish to send a lot of
|
||||
short data chunks, when TCP_NODELAY is enabled on that socket.
|
||||
*/
|
||||
|
||||
ODIN_NET_TCP_NODELAY_DEFAULT :: #config(ODIN_NET_TCP_NODELAY_DEFAULT, true)
|
||||
|
||||
// COMMON DEFINITIONS
|
||||
Maybe :: runtime.Maybe
|
||||
|
||||
Network_Error :: union #shared_nil {
|
||||
General_Error,
|
||||
Platform_Error,
|
||||
Create_Socket_Error,
|
||||
Dial_Error,
|
||||
Listen_Error,
|
||||
Accept_Error,
|
||||
Bind_Error,
|
||||
TCP_Send_Error,
|
||||
UDP_Send_Error,
|
||||
TCP_Recv_Error,
|
||||
UDP_Recv_Error,
|
||||
Shutdown_Error,
|
||||
Socket_Option_Error,
|
||||
Set_Blocking_Error,
|
||||
Parse_Endpoint_Error,
|
||||
Resolve_Error,
|
||||
DNS_Error,
|
||||
}
|
||||
|
||||
General_Error :: enum u32 {
|
||||
None = 0,
|
||||
Unable_To_Enumerate_Network_Interfaces = 1,
|
||||
}
|
||||
|
||||
// `Platform_Error` is used to wrap errors returned by the different platforms that don't fit a common error.
|
||||
Platform_Error :: enum u32 {}
|
||||
|
||||
Parse_Endpoint_Error :: enum {
|
||||
None = 0,
|
||||
Bad_Port = 1,
|
||||
Bad_Address,
|
||||
Bad_Hostname,
|
||||
}
|
||||
|
||||
Resolve_Error :: enum u32 {
|
||||
None = 0,
|
||||
Unable_To_Resolve = 1,
|
||||
}
|
||||
|
||||
DNS_Error :: enum u32 {
|
||||
Invalid_Hostname_Error = 1,
|
||||
Invalid_Hosts_Config_Error,
|
||||
Invalid_Resolv_Config_Error,
|
||||
Connection_Error,
|
||||
Server_Error,
|
||||
System_Error,
|
||||
}
|
||||
|
||||
// SOCKET OPTIONS & DEFINITIONS
|
||||
TCP_Options :: struct {
|
||||
no_delay: bool,
|
||||
}
|
||||
|
||||
default_tcp_options := TCP_Options {
|
||||
no_delay = ODIN_NET_TCP_NODELAY_DEFAULT,
|
||||
}
|
||||
|
||||
/*
|
||||
To allow freely using `Socket` in your own data structures in a cross-platform manner,
|
||||
we treat it as a handle large enough to accomodate OS-specific notions of socket handles.
|
||||
|
||||
The platform code will perform the cast so you don't have to.
|
||||
*/
|
||||
Socket :: distinct i64
|
||||
|
||||
TCP_Socket :: distinct Socket
|
||||
UDP_Socket :: distinct Socket
|
||||
|
||||
Socket_Protocol :: enum {
|
||||
TCP,
|
||||
UDP,
|
||||
}
|
||||
|
||||
Any_Socket :: union {
|
||||
TCP_Socket,
|
||||
UDP_Socket,
|
||||
}
|
||||
|
||||
/*
|
||||
ADDRESS DEFINITIONS
|
||||
*/
|
||||
|
||||
IP4_Address :: distinct [4]u8
|
||||
IP6_Address :: distinct [8]u16be
|
||||
Address :: union {IP4_Address, IP6_Address}
|
||||
|
||||
IP4_Loopback := IP4_Address{127, 0, 0, 1}
|
||||
IP6_Loopback := IP6_Address{0, 0, 0, 0, 0, 0, 0, 1}
|
||||
|
||||
IP4_Any := IP4_Address{}
|
||||
IP6_Any := IP6_Address{}
|
||||
|
||||
Endpoint :: struct {
|
||||
address: Address,
|
||||
port: int,
|
||||
}
|
||||
|
||||
Address_Family :: enum {
|
||||
IP4,
|
||||
IP6,
|
||||
}
|
||||
|
||||
Netmask :: distinct Address
|
||||
|
||||
/*
|
||||
INTERFACE / LINK STATE
|
||||
*/
|
||||
Network_Interface :: struct {
|
||||
adapter_name: string, // On Windows this is a GUID that we could parse back into its u128 for more compact storage.
|
||||
friendly_name: string,
|
||||
description: string,
|
||||
dns_suffix: string,
|
||||
|
||||
physical_address: string, // MAC address, etc.
|
||||
mtu: u32,
|
||||
|
||||
unicast: [dynamic]Lease,
|
||||
multicast: [dynamic]Address,
|
||||
anycast: [dynamic]Address,
|
||||
|
||||
gateways: [dynamic]Address,
|
||||
dhcp_v4: Address,
|
||||
dhcp_v6: Address,
|
||||
|
||||
tunnel_type: Tunnel_Type,
|
||||
|
||||
link: struct {
|
||||
state: Link_State,
|
||||
transmit_speed: u64,
|
||||
receive_speed: u64,
|
||||
},
|
||||
}
|
||||
|
||||
// Empty bit set is unknown state.
|
||||
Link_States :: enum u32 {
|
||||
Up = 1,
|
||||
Down = 2,
|
||||
Testing = 3,
|
||||
Dormant = 4,
|
||||
Not_Present = 5,
|
||||
Lower_Layer_Down = 6,
|
||||
Loopback = 7,
|
||||
}
|
||||
Link_State :: bit_set[Link_States; u32]
|
||||
|
||||
Lease :: struct {
|
||||
address: Address,
|
||||
netmask: Netmask,
|
||||
lifetime: struct {
|
||||
valid: u32,
|
||||
preferred: u32,
|
||||
lease: u32,
|
||||
},
|
||||
origin: struct {
|
||||
prefix: Prefix_Origin,
|
||||
suffix: Suffix_Origin,
|
||||
},
|
||||
address_duplication: Address_Duplication,
|
||||
}
|
||||
|
||||
Tunnel_Type :: enum i32 {
|
||||
None = 0,
|
||||
Other = 1,
|
||||
Direct = 2,
|
||||
IPv4_To_IPv6 = 11,
|
||||
ISA_TAP = 13,
|
||||
Teredo = 14,
|
||||
IP_HTTPS = 15,
|
||||
}
|
||||
|
||||
Prefix_Origin :: enum i32 {
|
||||
Other = 0,
|
||||
Manual = 1,
|
||||
Well_Known = 2,
|
||||
DHCP = 3,
|
||||
Router_Advertisement = 4,
|
||||
Unchanged = 16,
|
||||
}
|
||||
|
||||
Suffix_Origin :: enum i32 {
|
||||
Other = 0,
|
||||
Manual = 1,
|
||||
Well_Known = 2,
|
||||
DHCP = 3,
|
||||
Link_Layer_Address = 4,
|
||||
Random = 5,
|
||||
Unchanged = 16,
|
||||
}
|
||||
|
||||
Address_Duplication :: enum i32 {
|
||||
Invalid = 0,
|
||||
Tentative = 1,
|
||||
Duplicate = 2,
|
||||
Deprecated = 3,
|
||||
Preferred = 4,
|
||||
}
|
||||
|
||||
// DNS DEFINITIONS
|
||||
DNS_Configuration :: struct {
|
||||
// Configuration files.
|
||||
resolv_conf: string,
|
||||
hosts_file: string,
|
||||
|
||||
// TODO: Allow loading these up with `reload_configuration()` call or the like,
|
||||
// so we don't have to do it each call.
|
||||
name_servers: []Endpoint,
|
||||
hosts_file_entries: []DNS_Record,
|
||||
}
|
||||
|
||||
DNS_Record_Type :: enum u16 {
|
||||
DNS_TYPE_A = 0x1, // IP4 address.
|
||||
DNS_TYPE_NS = 0x2, // IP6 address.
|
||||
DNS_TYPE_CNAME = 0x5, // Another host name.
|
||||
DNS_TYPE_MX = 0xf, // Arbitrary binary data or text.
|
||||
DNS_TYPE_AAAA = 0x1c, // Address of a name (DNS) server.
|
||||
DNS_TYPE_TEXT = 0x10, // Address and preference priority of a mail exchange server.
|
||||
DNS_TYPE_SRV = 0x21, // Address, port, priority, and weight of a host that provides a particular service.
|
||||
|
||||
IP4 = DNS_TYPE_A,
|
||||
IP6 = DNS_TYPE_AAAA,
|
||||
CNAME = DNS_TYPE_CNAME,
|
||||
TXT = DNS_TYPE_TEXT,
|
||||
NS = DNS_TYPE_NS,
|
||||
MX = DNS_TYPE_MX,
|
||||
SRV = DNS_TYPE_SRV,
|
||||
}
|
||||
|
||||
// Base DNS Record. All DNS responses will carry a hostname and TTL (time to live) field.
|
||||
DNS_Record_Base :: struct {
|
||||
record_name: string,
|
||||
ttl_seconds: u32, // The time in seconds that this service will take to update, after the record is updated.
|
||||
}
|
||||
|
||||
// An IP4 address that the domain name maps to. There can be any number of these.
|
||||
DNS_Record_IP4 :: struct {
|
||||
using base: DNS_Record_Base,
|
||||
address: IP4_Address,
|
||||
}
|
||||
|
||||
// An IPv6 address that the domain name maps to. There can be any number of these.
|
||||
DNS_Record_IP6 :: struct {
|
||||
using base: DNS_Record_Base,
|
||||
address: IP6_Address,
|
||||
}
|
||||
|
||||
/*
|
||||
Another domain name that the domain name maps to.
|
||||
Domains can be pointed to another domain instead of directly to an IP address.
|
||||
`get_dns_records` will recursively follow these if you request this type of record.
|
||||
*/
|
||||
DNS_Record_CNAME :: struct {
|
||||
using base: DNS_Record_Base,
|
||||
host_name: string,
|
||||
}
|
||||
|
||||
/*
|
||||
Arbitrary string data that is associated with the domain name.
|
||||
Commonly of the form `key=value` to be parsed, though there is no specific format for them.
|
||||
These can be used for any purpose.
|
||||
*/
|
||||
DNS_Record_TXT :: struct {
|
||||
using base: DNS_Record_Base,
|
||||
value: string,
|
||||
}
|
||||
|
||||
/*
|
||||
Domain names of other DNS servers that are associated with the domain name.
|
||||
TODO(tetra): Expand on what these records are used for, and when you should use pay attention to these.
|
||||
*/
|
||||
DNS_Record_NS :: struct {
|
||||
using base: DNS_Record_Base,
|
||||
host_name: string,
|
||||
}
|
||||
|
||||
// Domain names for email servers that are associated with the domain name.
|
||||
// These records also have values which ranks them in the order they should be preferred. Lower is more-preferred.
|
||||
DNS_Record_MX :: struct {
|
||||
using base: DNS_Record_Base,
|
||||
host_name: string,
|
||||
preference: int,
|
||||
}
|
||||
|
||||
/*
|
||||
An endpoint for a service that is available through the domain name.
|
||||
This is the way to discover the services that a domain name provides.
|
||||
|
||||
Clients MUST attempt to contact the host with the lowest priority that they can reach.
|
||||
If two hosts have the same priority, they should be contacted in the order according to their weight.
|
||||
Hosts with larger weights should have a proportionally higher chance of being contacted by clients.
|
||||
A weight of zero indicates a very low weight, or, when there is no choice (to reduce visual noise).
|
||||
|
||||
The host may be "." to indicate that it is "decidedly not available" on this domain.
|
||||
*/
|
||||
DNS_Record_SRV :: struct {
|
||||
// base contains the full name of this record.
|
||||
// e.g: _sip._tls.example.com
|
||||
using base: DNS_Record_Base,
|
||||
|
||||
// The hostname or address where this service can be found.
|
||||
target: string,
|
||||
// The port on which this service can be found.
|
||||
port: int,
|
||||
|
||||
service_name: string, // NOTE(tetra): These are substrings of 'record_name'
|
||||
protocol_name: string, // NOTE(tetra): These are substrings of 'record_name'
|
||||
|
||||
// Lower is higher priority
|
||||
priority: int,
|
||||
// Relative weight of this host compared to other of same priority; the chance of using this host should be proporitional to this weight.
|
||||
// The number of seconds that it will take to update the record.
|
||||
weight: int,
|
||||
}
|
||||
|
||||
DNS_Record :: union {
|
||||
DNS_Record_IP4,
|
||||
DNS_Record_IP6,
|
||||
DNS_Record_CNAME,
|
||||
DNS_Record_TXT,
|
||||
DNS_Record_NS,
|
||||
DNS_Record_MX,
|
||||
DNS_Record_SRV,
|
||||
}
|
||||
|
||||
DNS_Response_Code :: enum u16be {
|
||||
No_Error,
|
||||
Format_Error,
|
||||
Server_Failure,
|
||||
Name_Error,
|
||||
Not_Implemented,
|
||||
Refused,
|
||||
}
|
||||
|
||||
DNS_Query :: enum u16be {
|
||||
Host_Address = 1,
|
||||
Authoritative_Name_Server = 2,
|
||||
Mail_Destination = 3,
|
||||
Mail_Forwarder = 4,
|
||||
CNAME = 5,
|
||||
All = 255,
|
||||
}
|
||||
|
||||
DNS_Header :: struct {
|
||||
id: u16be,
|
||||
is_response: bool,
|
||||
opcode: u16be,
|
||||
is_authoritative: bool,
|
||||
is_truncated: bool,
|
||||
is_recursion_desired: bool,
|
||||
is_recursion_available: bool,
|
||||
response_code: DNS_Response_Code,
|
||||
}
|
||||
|
||||
DNS_Record_Header :: struct #packed {
|
||||
type: u16be,
|
||||
class: u16be,
|
||||
ttl: u32be,
|
||||
length: u16be,
|
||||
}
|
||||
|
||||
DNS_Host_Entry :: struct {
|
||||
name: string,
|
||||
addr: Address,
|
||||
}
|
||||
@@ -0,0 +1,863 @@
|
||||
// +build windows, linux, darwin
|
||||
package net
|
||||
|
||||
/*
|
||||
Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures.
|
||||
For other protocols and their features, see subdirectories of this package.
|
||||
*/
|
||||
|
||||
/*
|
||||
Copyright 2022 Tetralux <tetraluxonpc@gmail.com>
|
||||
Copyright 2022 Colin Davidson <colrdavidson@gmail.com>
|
||||
Copyright 2022 Jeroen van Rijn <nom@duclavier.com>.
|
||||
Made available under Odin's BSD-3 license.
|
||||
|
||||
List of contributors:
|
||||
Tetralux: Initial implementation
|
||||
Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver
|
||||
Jeroen van Rijn: Cross platform unification, code style, documentation
|
||||
*/
|
||||
|
||||
import "core:mem"
|
||||
import "core:strings"
|
||||
import "core:time"
|
||||
import "core:os"
|
||||
|
||||
/*
|
||||
Default configuration for DNS resolution.
|
||||
*/
|
||||
when ODIN_OS == .Windows {
|
||||
DEFAULT_DNS_CONFIGURATION :: DNS_Configuration{
|
||||
resolv_conf = "",
|
||||
hosts_file = "%WINDIR%\\system32\\drivers\\etc\\hosts",
|
||||
}
|
||||
} else when ODIN_OS == .Linux || ODIN_OS == .Darwin || ODIN_OS == .OpenBSD {
|
||||
DEFAULT_DNS_CONFIGURATION :: DNS_Configuration{
|
||||
resolv_conf = "/etc/resolv.conf",
|
||||
hosts_file = "/etc/hosts",
|
||||
}
|
||||
} else {
|
||||
#panic("Please add a configuration for this OS.")
|
||||
}
|
||||
|
||||
@(init)
|
||||
init_dns_configuration :: proc() {
|
||||
/*
|
||||
Resolve %ENVIRONMENT% placeholders in their paths.
|
||||
*/
|
||||
dns_configuration.resolv_conf, _ = replace_environment_path(dns_configuration.resolv_conf)
|
||||
dns_configuration.hosts_file, _ = replace_environment_path(dns_configuration.hosts_file)
|
||||
}
|
||||
|
||||
destroy_dns_configuration :: proc() {
|
||||
delete(dns_configuration.resolv_conf)
|
||||
delete(dns_configuration.hosts_file)
|
||||
}
|
||||
|
||||
dns_configuration := DEFAULT_DNS_CONFIGURATION
|
||||
|
||||
// Always allocates for consistency.
|
||||
replace_environment_path :: proc(path: string, allocator := context.allocator) -> (res: string, ok: bool) {
|
||||
// Nothing to replace. Return a clone of the original.
|
||||
if strings.count(path, "%") != 2 {
|
||||
return strings.clone(path, allocator), true
|
||||
}
|
||||
|
||||
left := strings.index(path, "%") + 1
|
||||
assert(left > 0 && left <= len(path)) // should be covered by there being two %
|
||||
|
||||
right := strings.index(path[left:], "%") + 1
|
||||
assert(right > 0 && right <= len(path)) // should be covered by there being two %
|
||||
|
||||
env_key := path[left: right]
|
||||
env_val := os.get_env(env_key, allocator)
|
||||
defer delete(env_val)
|
||||
|
||||
res, _ = strings.replace(path, path[left - 1: right + 1], env_val, 1, allocator)
|
||||
return res, true
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
Resolves a hostname to exactly one IP4 and IP6 endpoint.
|
||||
It's then up to you which one you use.
|
||||
Note that which address you use to open a socket, determines the type of the socket you get.
|
||||
|
||||
Returns `ok=false` if the host name could not be resolved to any endpoints.
|
||||
|
||||
Returned endpoints have the same port as provided in the string, or 0 if absent.
|
||||
If you want to use a specific port, just modify the field after the call to this procedure.
|
||||
|
||||
If the hostname part of the endpoint is actually a string representation of an IP address, DNS resolution will be skipped.
|
||||
This allows you to pass both strings like "example.com:9000" and "1.2.3.4:9000" to this function end reliably get
|
||||
back an endpoint in both cases.
|
||||
*/
|
||||
resolve :: proc(hostname_and_maybe_port: string) -> (ep4, ep6: Endpoint, err: Network_Error) {
|
||||
target := parse_hostname_or_endpoint(hostname_and_maybe_port) or_return
|
||||
switch t in target {
|
||||
case Endpoint:
|
||||
// NOTE(tetra): The hostname was actually an IP address; nothing to resolve, so just return it.
|
||||
switch in t.address {
|
||||
case IP4_Address: ep4 = t
|
||||
case IP6_Address: ep6 = t
|
||||
case: unreachable()
|
||||
}
|
||||
return
|
||||
|
||||
case Host:
|
||||
err4, err6: Network_Error = ---, ---
|
||||
ep4, err4 = resolve_ip4(t.hostname)
|
||||
ep6, err6 = resolve_ip6(t.hostname)
|
||||
if err4 != nil && err6 != nil {
|
||||
err = err4
|
||||
}
|
||||
return
|
||||
}
|
||||
unreachable()
|
||||
}
|
||||
|
||||
resolve_ip4 :: proc(hostname_and_maybe_port: string) -> (ep4: Endpoint, err: Network_Error) {
|
||||
target := parse_hostname_or_endpoint(hostname_and_maybe_port) or_return
|
||||
switch t in target {
|
||||
case Endpoint:
|
||||
// NOTE(tetra): The hostname was actually an IP address; nothing to resolve, so just return it.
|
||||
switch in t.address {
|
||||
case IP4_Address:
|
||||
return t, nil
|
||||
case IP6_Address:
|
||||
err = .Unable_To_Resolve
|
||||
return
|
||||
}
|
||||
case Host:
|
||||
recs, _ := get_dns_records_from_os(t.hostname, .IP4, context.temp_allocator)
|
||||
if len(recs) == 0 {
|
||||
err = .Unable_To_Resolve
|
||||
return
|
||||
}
|
||||
ep4 = {
|
||||
address = recs[0].(DNS_Record_IP4).address,
|
||||
port = t.port,
|
||||
}
|
||||
return
|
||||
}
|
||||
unreachable()
|
||||
}
|
||||
|
||||
resolve_ip6 :: proc(hostname_and_maybe_port: string) -> (ep6: Endpoint, err: Network_Error) {
|
||||
target := parse_hostname_or_endpoint(hostname_and_maybe_port) or_return
|
||||
switch t in target {
|
||||
case Endpoint:
|
||||
// NOTE(tetra): The hostname was actually an IP address; nothing to resolve, so just return it.
|
||||
switch in t.address {
|
||||
case IP4_Address:
|
||||
err = .Unable_To_Resolve
|
||||
return
|
||||
case IP6_Address:
|
||||
return t, nil
|
||||
}
|
||||
case Host:
|
||||
recs, _ := get_dns_records_from_os(t.hostname, .IP6, context.temp_allocator)
|
||||
if len(recs) == 0 {
|
||||
err = .Unable_To_Resolve
|
||||
return
|
||||
}
|
||||
ep6 = {
|
||||
address = recs[0].(DNS_Record_IP6).address,
|
||||
port = t.port,
|
||||
}
|
||||
return
|
||||
}
|
||||
unreachable()
|
||||
}
|
||||
|
||||
/*
|
||||
Performs a recursive DNS query for records of a particular type for the hostname using the OS.
|
||||
|
||||
NOTE: This procedure instructs the DNS resolver to recursively perform CNAME requests on our behalf,
|
||||
meaning that DNS queries for a hostname will resolve through CNAME records until an
|
||||
IP address is reached.
|
||||
|
||||
IMPORTANT: This procedure allocates memory for each record returned; deleting just the returned slice is not enough!
|
||||
See `destroy_records`.
|
||||
*/
|
||||
get_dns_records_from_os :: proc(hostname: string, type: DNS_Record_Type, allocator := context.allocator) -> (records: []DNS_Record, err: DNS_Error) {
|
||||
return _get_dns_records_os(hostname, type, allocator)
|
||||
}
|
||||
|
||||
/*
|
||||
A generic DNS client usable on any platform.
|
||||
Performs a recursive DNS query for records of a particular type for the hostname.
|
||||
|
||||
NOTE: This procedure instructs the DNS resolver to recursively perform CNAME requests on our behalf,
|
||||
meaning that DNS queries for a hostname will resolve through CNAME records until an
|
||||
IP address is reached.
|
||||
|
||||
IMPORTANT: This procedure allocates memory for each record returned; deleting just the returned slice is not enough!
|
||||
See `destroy_records`.
|
||||
*/
|
||||
get_dns_records_from_nameservers :: proc(hostname: string, type: DNS_Record_Type, name_servers: []Endpoint, host_overrides: []DNS_Record, allocator := context.allocator) -> (records: []DNS_Record, err: DNS_Error) {
|
||||
context.allocator = allocator
|
||||
|
||||
if type != .SRV {
|
||||
// NOTE(tetra): 'hostname' can contain underscores when querying SRV records
|
||||
ok := validate_hostname(hostname)
|
||||
if !ok {
|
||||
return nil, .Invalid_Hostname_Error
|
||||
}
|
||||
}
|
||||
|
||||
hdr := DNS_Header{
|
||||
id = 0,
|
||||
is_response = false,
|
||||
opcode = 0,
|
||||
is_authoritative = false,
|
||||
is_truncated = false,
|
||||
is_recursion_desired = true,
|
||||
is_recursion_available = false,
|
||||
response_code = DNS_Response_Code.No_Error,
|
||||
}
|
||||
|
||||
id, bits := pack_dns_header(hdr)
|
||||
dns_hdr := [6]u16be{}
|
||||
dns_hdr[0] = id
|
||||
dns_hdr[1] = bits
|
||||
dns_hdr[2] = 1
|
||||
|
||||
dns_query := [2]u16be{ u16be(type), 1 }
|
||||
|
||||
output := [(size_of(u16be) * 6) + NAME_MAX + (size_of(u16be) * 2)]u8{}
|
||||
b := strings.builder_from_slice(output[:])
|
||||
|
||||
strings.write_bytes(&b, mem.slice_data_cast([]u8, dns_hdr[:]))
|
||||
ok := encode_hostname(&b, hostname)
|
||||
if !ok {
|
||||
return nil, .Invalid_Hostname_Error
|
||||
}
|
||||
strings.write_bytes(&b, mem.slice_data_cast([]u8, dns_query[:]))
|
||||
|
||||
dns_packet := output[:strings.builder_len(b)]
|
||||
|
||||
dns_response_buf := [4096]u8{}
|
||||
dns_response: []u8
|
||||
for name_server in name_servers {
|
||||
conn, sock_err := make_unbound_udp_socket(family_from_endpoint(name_server))
|
||||
if sock_err != nil {
|
||||
return nil, .Connection_Error
|
||||
}
|
||||
defer close(conn)
|
||||
|
||||
_, send_err := send(conn, dns_packet[:], name_server)
|
||||
if send_err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
set_err := set_option(conn, .Receive_Timeout, time.Second * 1)
|
||||
if set_err != nil {
|
||||
return nil, .Connection_Error
|
||||
}
|
||||
|
||||
recv_sz, _, recv_err := recv_udp(conn, dns_response_buf[:])
|
||||
if recv_err == UDP_Recv_Error.Timeout {
|
||||
continue
|
||||
} else if recv_err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if recv_sz == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
dns_response = dns_response_buf[:recv_sz]
|
||||
|
||||
rsp, _ok := parse_response(dns_response, type)
|
||||
if !_ok {
|
||||
return nil, .Server_Error
|
||||
}
|
||||
|
||||
if len(rsp) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
return rsp[:], nil
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// `records` slice is also destroyed.
|
||||
destroy_dns_records :: proc(records: []DNS_Record, allocator := context.allocator) {
|
||||
context.allocator = allocator
|
||||
|
||||
for rec in records {
|
||||
switch r in rec {
|
||||
case DNS_Record_IP4:
|
||||
delete(r.base.record_name)
|
||||
|
||||
case DNS_Record_IP6:
|
||||
delete(r.base.record_name)
|
||||
|
||||
case DNS_Record_CNAME:
|
||||
delete(r.base.record_name)
|
||||
delete(r.host_name)
|
||||
|
||||
case DNS_Record_TXT:
|
||||
delete(r.base.record_name)
|
||||
delete(r.value)
|
||||
|
||||
case DNS_Record_NS:
|
||||
delete(r.base.record_name)
|
||||
delete(r.host_name)
|
||||
|
||||
case DNS_Record_MX:
|
||||
delete(r.base.record_name)
|
||||
delete(r.host_name)
|
||||
|
||||
case DNS_Record_SRV:
|
||||
delete(r.record_name)
|
||||
delete(r.target)
|
||||
}
|
||||
}
|
||||
|
||||
delete(records, allocator)
|
||||
}
|
||||
|
||||
/*
|
||||
TODO(cloin): Does the DNS Resolver need to recursively hop through CNAMEs to get the IP
|
||||
or is that what recursion desired does? Do we need to handle recursion unavailable?
|
||||
How do we deal with is_authoritative / is_truncated?
|
||||
*/
|
||||
|
||||
NAME_MAX :: 255
|
||||
LABEL_MAX :: 63
|
||||
|
||||
pack_dns_header :: proc(hdr: DNS_Header) -> (id: u16be, bits: u16be) {
|
||||
id = hdr.id
|
||||
bits = hdr.opcode << 1 | u16be(hdr.response_code)
|
||||
if hdr.is_response {
|
||||
bits |= 1 << 15
|
||||
}
|
||||
if hdr.is_authoritative {
|
||||
bits |= 1 << 10
|
||||
}
|
||||
if hdr.is_truncated {
|
||||
bits |= 1 << 9
|
||||
}
|
||||
if hdr.is_recursion_desired {
|
||||
bits |= 1 << 8
|
||||
}
|
||||
if hdr.is_recursion_available {
|
||||
bits |= 1 << 7
|
||||
}
|
||||
|
||||
return id, bits
|
||||
}
|
||||
|
||||
unpack_dns_header :: proc(id: u16be, bits: u16be) -> (hdr: DNS_Header) {
|
||||
hdr.id = id
|
||||
hdr.is_response = (bits & (1 << 15)) != 0
|
||||
hdr.opcode = (bits >> 11) & 0xF
|
||||
hdr.is_authoritative = (bits & (1 << 10)) != 0
|
||||
hdr.is_truncated = (bits & (1 << 9)) != 0
|
||||
hdr.is_recursion_desired = (bits & (1 << 8)) != 0
|
||||
hdr.is_recursion_available = (bits & (1 << 7)) != 0
|
||||
hdr.response_code = DNS_Response_Code(bits & 0xF)
|
||||
|
||||
return hdr
|
||||
}
|
||||
|
||||
load_resolv_conf :: proc(resolv_conf_path: string, allocator := context.allocator) -> (name_servers: []Endpoint, ok: bool) {
|
||||
context.allocator = allocator
|
||||
|
||||
res := os.read_entire_file_from_filename(resolv_conf_path) or_return
|
||||
defer delete(res)
|
||||
resolv_str := string(res)
|
||||
|
||||
_name_servers := make([dynamic]Endpoint, 0, allocator)
|
||||
for line in strings.split_lines_iterator(&resolv_str) {
|
||||
if len(line) == 0 || line[0] == '#' {
|
||||
continue
|
||||
}
|
||||
|
||||
id_str := "nameserver"
|
||||
if strings.compare(line[:len(id_str)], id_str) != 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
server_ip_str := strings.trim_left_space(line[len(id_str):])
|
||||
if len(server_ip_str) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
addr := parse_address(server_ip_str)
|
||||
endpoint := Endpoint{
|
||||
addr,
|
||||
53,
|
||||
}
|
||||
append(&_name_servers, endpoint)
|
||||
}
|
||||
|
||||
return _name_servers[:], true
|
||||
}
|
||||
|
||||
load_hosts :: proc(hosts_file_path: string, allocator := context.allocator) -> (hosts: []DNS_Host_Entry, ok: bool) {
|
||||
context.allocator = allocator
|
||||
|
||||
res := os.read_entire_file_from_filename(hosts_file_path, allocator) or_return
|
||||
defer delete(res)
|
||||
|
||||
_hosts := make([dynamic]DNS_Host_Entry, 0, allocator)
|
||||
hosts_str := string(res)
|
||||
for line in strings.split_lines_iterator(&hosts_str) {
|
||||
if len(line) == 0 || line[0] == '#' {
|
||||
continue
|
||||
}
|
||||
|
||||
splits := strings.fields(line)
|
||||
defer delete(splits)
|
||||
|
||||
ip_str := splits[0]
|
||||
addr := parse_address(ip_str)
|
||||
if addr == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
for hostname in splits[1:] {
|
||||
if len(hostname) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
append(&_hosts, DNS_Host_Entry{hostname, addr})
|
||||
}
|
||||
}
|
||||
|
||||
return _hosts[:], true
|
||||
}
|
||||
|
||||
// www.google.com -> 3www6google3com0
|
||||
encode_hostname :: proc(b: ^strings.Builder, hostname: string) -> (ok: bool) {
|
||||
_hostname := hostname
|
||||
for section in strings.split_iterator(&_hostname, ".") {
|
||||
if len(section) > LABEL_MAX {
|
||||
return
|
||||
}
|
||||
|
||||
strings.write_byte(b, u8(len(section)))
|
||||
strings.write_string(b, section)
|
||||
}
|
||||
strings.write_byte(b, 0)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
skip_hostname :: proc(packet: []u8, start_idx: int) -> (encode_size: int, ok: bool) {
|
||||
out_size := 0
|
||||
|
||||
cur_idx := start_idx
|
||||
iteration_max := 0
|
||||
top: for cur_idx < len(packet) {
|
||||
if packet[cur_idx] == 0 {
|
||||
out_size += 1
|
||||
break
|
||||
}
|
||||
|
||||
if iteration_max > 255 {
|
||||
return
|
||||
}
|
||||
|
||||
if packet[cur_idx] > 63 && packet[cur_idx] != 0xC0 {
|
||||
return
|
||||
}
|
||||
|
||||
switch packet[cur_idx] {
|
||||
case 0xC0:
|
||||
out_size += 2
|
||||
break top
|
||||
case:
|
||||
label_size := int(packet[cur_idx]) + 1
|
||||
idx2 := cur_idx + label_size
|
||||
|
||||
if idx2 < cur_idx + 1 || idx2 > len(packet) {
|
||||
return
|
||||
}
|
||||
|
||||
out_size += label_size
|
||||
cur_idx = idx2
|
||||
}
|
||||
|
||||
iteration_max += 1
|
||||
}
|
||||
|
||||
if start_idx + out_size > len(packet) {
|
||||
return
|
||||
}
|
||||
|
||||
return out_size, true
|
||||
}
|
||||
|
||||
decode_hostname :: proc(packet: []u8, start_idx: int, allocator := context.allocator) -> (hostname: string, encode_size: int, ok: bool) {
|
||||
output := [NAME_MAX]u8{}
|
||||
b := strings.builder_from_slice(output[:])
|
||||
|
||||
// If you're on level 0, update out_bytes, everything through a pointer
|
||||
// doesn't count towards this hostname's packet length
|
||||
|
||||
// Evaluate tokens to generate the hostname
|
||||
out_size := 0
|
||||
level := 0
|
||||
print_size := 0
|
||||
cur_idx := start_idx
|
||||
iteration_max := 0
|
||||
labels_added := 0
|
||||
for cur_idx < len(packet) {
|
||||
if packet[cur_idx] == 0 {
|
||||
|
||||
if (level == 0) {
|
||||
out_size += 1
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
if iteration_max > 255 {
|
||||
return
|
||||
}
|
||||
|
||||
if packet[cur_idx] > 63 && packet[cur_idx] != 0xC0 {
|
||||
return
|
||||
}
|
||||
|
||||
switch packet[cur_idx] {
|
||||
|
||||
// This is a offset to more data in the packet, jump to it
|
||||
case 0xC0:
|
||||
pkt := packet[cur_idx:cur_idx+2]
|
||||
val := (^u16be)(raw_data(pkt))^
|
||||
offset := int(val & 0x3FFF)
|
||||
if offset > len(packet) {
|
||||
return
|
||||
}
|
||||
|
||||
cur_idx = offset
|
||||
|
||||
if (level == 0) {
|
||||
out_size += 2
|
||||
level += 1
|
||||
}
|
||||
|
||||
// This is a label, insert it into the hostname
|
||||
case:
|
||||
label_size := int(packet[cur_idx])
|
||||
idx2 := cur_idx + label_size + 1
|
||||
if idx2 < cur_idx + 1 || idx2 > len(packet) {
|
||||
return
|
||||
}
|
||||
|
||||
if print_size + label_size + 1 > NAME_MAX {
|
||||
return
|
||||
}
|
||||
|
||||
if labels_added > 0 {
|
||||
strings.write_byte(&b, '.')
|
||||
}
|
||||
strings.write_bytes(&b, packet[cur_idx+1:idx2])
|
||||
print_size += label_size + 1
|
||||
labels_added += 1
|
||||
|
||||
cur_idx = idx2
|
||||
|
||||
if (level == 0) {
|
||||
out_size += label_size + 1
|
||||
}
|
||||
}
|
||||
|
||||
iteration_max += 1
|
||||
}
|
||||
|
||||
if start_idx + out_size > len(packet) {
|
||||
return
|
||||
}
|
||||
|
||||
return strings.clone(strings.to_string(b), allocator), out_size, true
|
||||
}
|
||||
|
||||
// Uses RFC 952 & RFC 1123
|
||||
validate_hostname :: proc(hostname: string) -> (ok: bool) {
|
||||
if len(hostname) > 255 || len(hostname) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
if hostname[0] == '-' {
|
||||
return
|
||||
}
|
||||
|
||||
_hostname := hostname
|
||||
for label in strings.split_iterator(&_hostname, ".") {
|
||||
if len(label) > 63 || len(label) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
for ch in label {
|
||||
switch ch {
|
||||
case:
|
||||
return
|
||||
case 'a'..='z', 'A'..='Z', '0'..='9', '-':
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
parse_record :: proc(packet: []u8, cur_off: ^int, filter: DNS_Record_Type = nil) -> (record: DNS_Record, ok: bool) {
|
||||
record_buf := packet[cur_off^:]
|
||||
|
||||
srv_record_name, hn_sz := decode_hostname(packet, cur_off^, context.temp_allocator) or_return
|
||||
// TODO(tetra): Not sure what we should call this.
|
||||
// Is it really only used in SRVs?
|
||||
// Maybe some refactoring is required?
|
||||
|
||||
ahdr_sz := size_of(DNS_Record_Header)
|
||||
if len(record_buf) - hn_sz < ahdr_sz {
|
||||
return
|
||||
}
|
||||
|
||||
record_hdr_bytes := record_buf[hn_sz:hn_sz+ahdr_sz]
|
||||
record_hdr := cast(^DNS_Record_Header)raw_data(record_hdr_bytes)
|
||||
|
||||
data_sz := record_hdr.length
|
||||
data_off := cur_off^ + int(hn_sz) + int(ahdr_sz)
|
||||
data := packet[data_off:data_off+int(data_sz)]
|
||||
cur_off^ += int(hn_sz) + int(ahdr_sz) + int(data_sz)
|
||||
|
||||
if u16be(filter) != record_hdr.type {
|
||||
return nil, true
|
||||
}
|
||||
|
||||
_record: DNS_Record
|
||||
#partial switch DNS_Record_Type(record_hdr.type) {
|
||||
case .IP4:
|
||||
if len(data) != 4 {
|
||||
return
|
||||
}
|
||||
|
||||
addr := (^IP4_Address)(raw_data(data))^
|
||||
|
||||
_record = DNS_Record_IP4{
|
||||
base = DNS_Record_Base{
|
||||
record_name = strings.clone(srv_record_name),
|
||||
ttl_seconds = u32(record_hdr.ttl),
|
||||
},
|
||||
address = addr,
|
||||
}
|
||||
|
||||
case .IP6:
|
||||
if len(data) != 16 {
|
||||
return
|
||||
}
|
||||
|
||||
addr := (^IP6_Address)(raw_data(data))^
|
||||
|
||||
_record = DNS_Record_IP6{
|
||||
base = DNS_Record_Base{
|
||||
record_name = strings.clone(srv_record_name),
|
||||
ttl_seconds = u32(record_hdr.ttl),
|
||||
},
|
||||
address = addr,
|
||||
}
|
||||
|
||||
case .CNAME:
|
||||
hostname, _ := decode_hostname(packet, data_off) or_return
|
||||
|
||||
_record = DNS_Record_CNAME{
|
||||
base = DNS_Record_Base{
|
||||
record_name = strings.clone(srv_record_name),
|
||||
ttl_seconds = u32(record_hdr.ttl),
|
||||
},
|
||||
host_name = hostname,
|
||||
}
|
||||
|
||||
case .TXT:
|
||||
_record = DNS_Record_TXT{
|
||||
base = DNS_Record_Base{
|
||||
record_name = strings.clone(srv_record_name),
|
||||
ttl_seconds = u32(record_hdr.ttl),
|
||||
},
|
||||
value = strings.clone(string(data)),
|
||||
}
|
||||
|
||||
case .NS:
|
||||
name, _ := decode_hostname(packet, data_off) or_return
|
||||
|
||||
_record = DNS_Record_NS{
|
||||
base = DNS_Record_Base{
|
||||
record_name = strings.clone(srv_record_name),
|
||||
ttl_seconds = u32(record_hdr.ttl),
|
||||
},
|
||||
host_name = name,
|
||||
}
|
||||
|
||||
case .SRV:
|
||||
if len(data) <= 6 {
|
||||
return
|
||||
}
|
||||
|
||||
_data := mem.slice_data_cast([]u16be, data)
|
||||
|
||||
priority, weight, port := _data[0], _data[1], _data[2]
|
||||
target, _ := decode_hostname(packet, data_off + (size_of(u16be) * 3)) or_return
|
||||
|
||||
// NOTE(tetra): Srv record name should be of the form '_servicename._protocol.hostname'
|
||||
// The record name is the name of the record.
|
||||
// Not to be confused with the _target_ of the record, which is--in combination with the port--what we're looking up
|
||||
// by making this request in the first place.
|
||||
|
||||
// NOTE(Jeroen): Service Name and Protocol Name can probably just be string slices into the record name.
|
||||
// It's already cloned, after all. I wouldn't put them on the temp allocator like this.
|
||||
|
||||
parts := strings.split_n(srv_record_name, ".", 3, context.temp_allocator)
|
||||
if len(parts) != 3 {
|
||||
return
|
||||
}
|
||||
service_name, protocol_name := parts[0], parts[1]
|
||||
|
||||
_record = DNS_Record_SRV{
|
||||
base = DNS_Record_Base{
|
||||
record_name = strings.clone(srv_record_name),
|
||||
ttl_seconds = u32(record_hdr.ttl),
|
||||
},
|
||||
target = target,
|
||||
service_name = service_name,
|
||||
protocol_name = protocol_name,
|
||||
priority = int(priority),
|
||||
weight = int(weight),
|
||||
port = int(port),
|
||||
}
|
||||
|
||||
case .MX:
|
||||
if len(data) <= 2 {
|
||||
return
|
||||
}
|
||||
|
||||
preference: u16be = mem.slice_data_cast([]u16be, data)[0]
|
||||
hostname, _ := decode_hostname(packet, data_off + size_of(u16be)) or_return
|
||||
|
||||
_record = DNS_Record_MX{
|
||||
base = DNS_Record_Base{
|
||||
record_name = strings.clone(srv_record_name),
|
||||
ttl_seconds = u32(record_hdr.ttl),
|
||||
},
|
||||
host_name = hostname,
|
||||
preference = int(preference),
|
||||
}
|
||||
|
||||
case:
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
return _record, true
|
||||
}
|
||||
|
||||
/*
|
||||
DNS Query Response Format:
|
||||
- DNS_Header (packed)
|
||||
- Query Count
|
||||
- Answer Count
|
||||
- Authority Count
|
||||
- Additional Count
|
||||
- Query[]
|
||||
- Hostname -- encoded
|
||||
- Type
|
||||
- Class
|
||||
- Answer[]
|
||||
- DNS Record Data
|
||||
- Authority[]
|
||||
- DNS Record Data
|
||||
- Additional[]
|
||||
- DNS Record Data
|
||||
|
||||
DNS Record Data:
|
||||
- DNS_Record_Header
|
||||
- Data[]
|
||||
*/
|
||||
|
||||
parse_response :: proc(response: []u8, filter: DNS_Record_Type = nil, allocator := context.allocator) -> (records: []DNS_Record, ok: bool) {
|
||||
context.allocator = allocator
|
||||
|
||||
HEADER_SIZE_BYTES :: 12
|
||||
if len(response) < HEADER_SIZE_BYTES {
|
||||
return
|
||||
}
|
||||
|
||||
_records := make([dynamic]DNS_Record, 0)
|
||||
|
||||
dns_hdr_chunks := mem.slice_data_cast([]u16be, response[:HEADER_SIZE_BYTES])
|
||||
hdr := unpack_dns_header(dns_hdr_chunks[0], dns_hdr_chunks[1])
|
||||
if !hdr.is_response {
|
||||
return
|
||||
}
|
||||
|
||||
question_count := int(dns_hdr_chunks[2])
|
||||
if question_count != 1 {
|
||||
return
|
||||
}
|
||||
answer_count := int(dns_hdr_chunks[3])
|
||||
authority_count := int(dns_hdr_chunks[4])
|
||||
additional_count := int(dns_hdr_chunks[5])
|
||||
|
||||
cur_idx := HEADER_SIZE_BYTES
|
||||
|
||||
for _ in 0..<question_count {
|
||||
if cur_idx == len(response) {
|
||||
continue
|
||||
}
|
||||
|
||||
dq_sz :: 4
|
||||
hn_sz := skip_hostname(response, cur_idx) or_return
|
||||
dns_query := mem.slice_data_cast([]u16be, response[cur_idx+hn_sz:cur_idx+hn_sz+dq_sz])
|
||||
|
||||
cur_idx += hn_sz + dq_sz
|
||||
}
|
||||
|
||||
for _ in 0..<answer_count {
|
||||
if cur_idx == len(response) {
|
||||
continue
|
||||
}
|
||||
|
||||
rec := parse_record(response, &cur_idx, filter) or_return
|
||||
if rec == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
append(&_records, rec)
|
||||
}
|
||||
|
||||
for _ in 0..<authority_count {
|
||||
if cur_idx == len(response) {
|
||||
continue
|
||||
}
|
||||
|
||||
rec := parse_record(response, &cur_idx, filter) or_return
|
||||
if rec == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
append(&_records, rec)
|
||||
}
|
||||
|
||||
for _ in 0..<additional_count {
|
||||
if cur_idx == len(response) {
|
||||
continue
|
||||
}
|
||||
|
||||
rec := parse_record(response, &cur_idx, filter) or_return
|
||||
if rec == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
append(&_records, rec)
|
||||
}
|
||||
|
||||
return _records[:], true
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
//+build linux, darwin
|
||||
package net
|
||||
/*
|
||||
Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures.
|
||||
For other protocols and their features, see subdirectories of this package.
|
||||
*/
|
||||
|
||||
/*
|
||||
Copyright 2022 Tetralux <tetraluxonpc@gmail.com>
|
||||
Copyright 2022 Colin Davidson <colrdavidson@gmail.com>
|
||||
Copyright 2022 Jeroen van Rijn <nom@duclavier.com>.
|
||||
Made available under Odin's BSD-3 license.
|
||||
|
||||
List of contributors:
|
||||
Tetralux: Initial implementation
|
||||
Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver
|
||||
Jeroen van Rijn: Cross platform unification, code style, documentation
|
||||
*/
|
||||
import "core:strings"
|
||||
|
||||
@(private)
|
||||
_get_dns_records_os :: proc(hostname: string, type: DNS_Record_Type, allocator := context.allocator) -> (records: []DNS_Record, err: DNS_Error) {
|
||||
context.allocator = allocator
|
||||
|
||||
if type != .SRV {
|
||||
// NOTE(tetra): 'hostname' can contain underscores when querying SRV records
|
||||
ok := validate_hostname(hostname)
|
||||
if !ok {
|
||||
return nil, .Invalid_Hostname_Error
|
||||
}
|
||||
}
|
||||
|
||||
name_servers, resolve_ok := load_resolv_conf(dns_configuration.resolv_conf)
|
||||
defer delete(name_servers)
|
||||
if !resolve_ok {
|
||||
return nil, .Invalid_Resolv_Config_Error
|
||||
}
|
||||
if len(name_servers) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
hosts, hosts_ok := load_hosts(dns_configuration.hosts_file)
|
||||
defer delete(hosts)
|
||||
if !hosts_ok {
|
||||
return nil, .Invalid_Hosts_Config_Error
|
||||
}
|
||||
if len(hosts) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
host_overrides := make([dynamic]DNS_Record)
|
||||
for host in hosts {
|
||||
if strings.compare(host.name, hostname) != 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
if type == .IP4 && family_from_address(host.addr) == .IP4 {
|
||||
record := DNS_Record_IP4{
|
||||
base = {
|
||||
record_name = strings.clone(hostname),
|
||||
ttl_seconds = 0,
|
||||
},
|
||||
address = host.addr.(IP4_Address),
|
||||
}
|
||||
append(&host_overrides, record)
|
||||
} else if type == .IP6 && family_from_address(host.addr) == .IP6 {
|
||||
record := DNS_Record_IP6{
|
||||
base = {
|
||||
record_name = strings.clone(hostname),
|
||||
ttl_seconds = 0,
|
||||
},
|
||||
address = host.addr.(IP6_Address),
|
||||
}
|
||||
append(&host_overrides, record)
|
||||
}
|
||||
}
|
||||
|
||||
if len(host_overrides) > 0 {
|
||||
return host_overrides[:], nil
|
||||
}
|
||||
|
||||
return get_dns_records_from_nameservers(hostname, type, name_servers, host_overrides[:])
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
//+build windows
|
||||
package net
|
||||
|
||||
/*
|
||||
Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures.
|
||||
For other protocols and their features, see subdirectories of this package.
|
||||
*/
|
||||
|
||||
/*
|
||||
Copyright 2022 Tetralux <tetraluxonpc@gmail.com>
|
||||
Copyright 2022 Colin Davidson <colrdavidson@gmail.com>
|
||||
Copyright 2022 Jeroen van Rijn <nom@duclavier.com>.
|
||||
Made available under Odin's BSD-3 license.
|
||||
|
||||
List of contributors:
|
||||
Tetralux: Initial implementation
|
||||
Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver
|
||||
Jeroen van Rijn: Cross platform unification, code style, documentation
|
||||
*/
|
||||
|
||||
import "core:strings"
|
||||
import "core:mem"
|
||||
|
||||
import win "core:sys/windows"
|
||||
|
||||
@(private)
|
||||
_get_dns_records_os :: proc(hostname: string, type: DNS_Record_Type, allocator := context.allocator) -> (records: []DNS_Record, err: DNS_Error) {
|
||||
context.allocator = allocator
|
||||
|
||||
host_cstr := strings.clone_to_cstring(hostname, context.temp_allocator)
|
||||
rec: ^win.DNS_RECORD
|
||||
res := win.DnsQuery_UTF8(host_cstr, u16(type), 0, nil, &rec, nil)
|
||||
|
||||
switch u32(res) {
|
||||
case 0:
|
||||
// okay
|
||||
case win.ERROR_INVALID_NAME:
|
||||
return nil, .Invalid_Hostname_Error
|
||||
case win.DNS_INFO_NO_RECORDS:
|
||||
return
|
||||
case:
|
||||
return nil, .System_Error
|
||||
}
|
||||
defer win.DnsRecordListFree(rec, 1) // 1 means that we're freeing a list... because the proc name isn't enough.
|
||||
|
||||
count := 0
|
||||
for r := rec; r != nil; r = r.pNext {
|
||||
if r.wType != u16(type) do continue // NOTE(tetra): Should never happen, but...
|
||||
count += 1
|
||||
}
|
||||
|
||||
recs := make([dynamic]DNS_Record, 0, count)
|
||||
if recs == nil do return nil, .System_Error // return no results if OOM.
|
||||
|
||||
for r := rec; r != nil; r = r.pNext {
|
||||
if r.wType != u16(type) do continue // NOTE(tetra): Should never happen, but...
|
||||
|
||||
base_record := DNS_Record_Base{
|
||||
record_name = strings.clone(string(r.pName)),
|
||||
ttl_seconds = r.dwTtl,
|
||||
}
|
||||
|
||||
switch DNS_Record_Type(r.wType) {
|
||||
case .IP4:
|
||||
addr := IP4_Address(transmute([4]u8)r.Data.A)
|
||||
record := DNS_Record_IP4{
|
||||
base = base_record,
|
||||
address = addr,
|
||||
}
|
||||
append(&recs, record)
|
||||
|
||||
case .IP6:
|
||||
addr := IP6_Address(transmute([8]u16be) r.Data.AAAA)
|
||||
record := DNS_Record_IP6{
|
||||
base = base_record,
|
||||
address = addr,
|
||||
}
|
||||
append(&recs, record)
|
||||
|
||||
case .CNAME:
|
||||
|
||||
hostname := strings.clone(string(r.Data.CNAME))
|
||||
record := DNS_Record_CNAME{
|
||||
base = base_record,
|
||||
host_name = hostname,
|
||||
}
|
||||
append(&recs, record)
|
||||
|
||||
case .TXT:
|
||||
n := r.Data.TXT.dwStringCount
|
||||
ptr := &r.Data.TXT.pStringArray
|
||||
c_strs := mem.slice_ptr(ptr, int(n))
|
||||
|
||||
for cstr in c_strs {
|
||||
record := DNS_Record_TXT{
|
||||
base = base_record,
|
||||
value = strings.clone(string(cstr)),
|
||||
}
|
||||
append(&recs, record)
|
||||
}
|
||||
|
||||
case .NS:
|
||||
hostname := strings.clone(string(r.Data.NS))
|
||||
record := DNS_Record_NS{
|
||||
base = base_record,
|
||||
host_name = hostname,
|
||||
}
|
||||
append(&recs, record)
|
||||
|
||||
case .MX:
|
||||
/*
|
||||
TODO(tetra): Order by preference priority? (Prefer hosts with lower preference values.)
|
||||
Or maybe not because you're supposed to just use the first one that works
|
||||
and which order they're in changes every few calls.
|
||||
*/
|
||||
|
||||
record := DNS_Record_MX{
|
||||
base = base_record,
|
||||
host_name = strings.clone(string(r.Data.MX.pNameExchange)),
|
||||
preference = int(r.Data.MX.wPreference),
|
||||
}
|
||||
append(&recs, record)
|
||||
|
||||
case .SRV:
|
||||
target := strings.clone(string(r.Data.SRV.pNameTarget)) // The target hostname/address that the service can be found on
|
||||
priority := int(r.Data.SRV.wPriority)
|
||||
weight := int(r.Data.SRV.wWeight)
|
||||
port := int(r.Data.SRV.wPort)
|
||||
|
||||
// NOTE(tetra): Srv record name should be of the form '_servicename._protocol.hostname'
|
||||
// The record name is the name of the record.
|
||||
// Not to be confused with the _target_ of the record, which is--in combination with the port--what we're looking up
|
||||
// by making this request in the first place.
|
||||
|
||||
// NOTE(Jeroen): Service Name and Protocol Name can probably just be string slices into the record name.
|
||||
// It's already cloned, after all. I wouldn't put them on the temp allocator like this.
|
||||
|
||||
parts := strings.split_n(base_record.record_name, ".", 3, context.temp_allocator)
|
||||
if len(parts) != 3 {
|
||||
continue
|
||||
}
|
||||
service_name, protocol_name := parts[0], parts[1]
|
||||
|
||||
append(&recs, DNS_Record_SRV {
|
||||
base = base_record,
|
||||
target = target,
|
||||
port = port,
|
||||
service_name = service_name,
|
||||
protocol_name = protocol_name,
|
||||
priority = priority,
|
||||
weight = weight,
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
records = recs[:]
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
Copyright 2022 Tetralux <tetraluxonpc@gmail.com>
|
||||
Copyright 2022 Colin Davidson <colrdavidson@gmail.com>
|
||||
Copyright 2022 Jeroen van Rijn <nom@duclavier.com>.
|
||||
Made available under Odin's BSD-3 license.
|
||||
|
||||
List of contributors:
|
||||
Tetralux: Initial implementation
|
||||
Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver
|
||||
Jeroen van Rijn: Cross platform unification, code style, documentation
|
||||
*/
|
||||
|
||||
/*
|
||||
Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures.
|
||||
For other protocols and their features, see subdirectories of this package.
|
||||
|
||||
Features:
|
||||
- Supports Windows, Linux and OSX.
|
||||
- Opening and closing of TCP and UDP sockets.
|
||||
- Sending to and receiving from these sockets.
|
||||
- DNS name lookup, using either the OS or our own resolver.
|
||||
|
||||
Planned:
|
||||
- Nonblocking IO
|
||||
- `Connection` struct
|
||||
A "fat socket" struct that remembers how you opened it, etc, instead of just being a handle.
|
||||
- IP Range structs, CIDR/class ranges, netmask calculator and associated helper procedures.
|
||||
- Use `context.temp_allocator` instead of stack-based arenas?
|
||||
And check it's the default temp allocator or can give us 4 MiB worth of memory
|
||||
without punting to the main allocator by comparing their addresses in an @(init) procedure.
|
||||
Panic if this assumption is not met.
|
||||
|
||||
- Document assumptions about libc usage (or avoidance thereof) for each platform.
|
||||
|
||||
Assumptions:
|
||||
- For performance reasons this package relies on the `context.temp_allocator` in some places.
|
||||
|
||||
You can replace the default `context.temp_allocator` with your own as long as it meets
|
||||
this requirement: A minimum of 4 MiB of scratch space that's expected not to be freed.
|
||||
|
||||
If this expectation is not met, the package's @(init) procedure will attempt to detect
|
||||
this and panic to avoid temp allocations prematurely overwriting data and garbling results,
|
||||
or worse. This means that should you replace the temp allocator with an insufficient one,
|
||||
we'll do our best to loudly complain the first time you try it.
|
||||
*/
|
||||
package net
|
||||
@@ -0,0 +1,206 @@
|
||||
package net
|
||||
// +build darwin
|
||||
|
||||
/*
|
||||
Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures.
|
||||
For other protocols and their features, see subdirectories of this package.
|
||||
*/
|
||||
|
||||
/*
|
||||
Copyright 2022 Tetralux <tetraluxonpc@gmail.com>
|
||||
Copyright 2022 Colin Davidson <colrdavidson@gmail.com>
|
||||
Copyright 2022 Jeroen van Rijn <nom@duclavier.com>.
|
||||
Made available under Odin's BSD-3 license.
|
||||
|
||||
List of contributors:
|
||||
Tetralux: Initial implementation
|
||||
Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver
|
||||
Jeroen van Rijn: Cross platform unification, code style, documentation
|
||||
*/
|
||||
|
||||
import "core:c"
|
||||
import "core:os"
|
||||
|
||||
Create_Socket_Error :: enum c.int {
|
||||
None = 0,
|
||||
Family_Not_Supported_For_This_Socket = c.int(os.EAFNOSUPPORT),
|
||||
No_Socket_Descriptors_Available = c.int(os.EMFILE),
|
||||
No_Buffer_Space_Available = c.int(os.ENOBUFS),
|
||||
No_Memory_Available_Available = c.int(os.ENOMEM),
|
||||
Protocol_Unsupported_By_System = c.int(os.EPROTONOSUPPORT),
|
||||
Wrong_Protocol_For_Socket = c.int(os.EPROTONOSUPPORT),
|
||||
Family_And_Socket_Type_Mismatch = c.int(os.EPROTONOSUPPORT),
|
||||
}
|
||||
|
||||
Dial_Error :: enum c.int {
|
||||
None = 0,
|
||||
Port_Required = -1,
|
||||
|
||||
Address_In_Use = c.int(os.EADDRINUSE),
|
||||
In_Progress = c.int(os.EINPROGRESS),
|
||||
Cannot_Use_Any_Address = c.int(os.EADDRNOTAVAIL),
|
||||
Wrong_Family_For_Socket = c.int(os.EAFNOSUPPORT),
|
||||
Refused = c.int(os.ECONNREFUSED),
|
||||
Is_Listening_Socket = c.int(os.EACCES),
|
||||
Already_Connected = c.int(os.EISCONN),
|
||||
Network_Unreachable = c.int(os.ENETUNREACH), // Device is offline
|
||||
Host_Unreachable = c.int(os.EHOSTUNREACH), // Remote host cannot be reached
|
||||
No_Buffer_Space_Available = c.int(os.ENOBUFS),
|
||||
Not_Socket = c.int(os.ENOTSOCK),
|
||||
Timeout = c.int(os.ETIMEDOUT),
|
||||
|
||||
// TODO: we may need special handling for this; maybe make a socket a struct with metadata?
|
||||
Would_Block = c.int(os.EWOULDBLOCK),
|
||||
}
|
||||
|
||||
Bind_Error :: enum c.int {
|
||||
None = 0,
|
||||
Address_In_Use = c.int(os.EADDRINUSE), // Another application is currently bound to this endpoint.
|
||||
Given_Nonlocal_Address = c.int(os.EADDRNOTAVAIL), // The address is not a local address on this machine.
|
||||
Broadcast_Disabled = c.int(os.EACCES), // To bind a UDP socket to the broadcast address, the appropriate socket option must be set.
|
||||
Address_Family_Mismatch = c.int(os.EFAULT), // The address family of the address does not match that of the socket.
|
||||
Already_Bound = c.int(os.EINVAL), // The socket is already bound to an address.
|
||||
No_Ports_Available = c.int(os.ENOBUFS), // There are not enough ephemeral ports available.
|
||||
}
|
||||
|
||||
Listen_Error :: enum c.int {
|
||||
None = 0,
|
||||
Address_In_Use = c.int(os.EADDRINUSE),
|
||||
Already_Connected = c.int(os.EISCONN),
|
||||
No_Socket_Descriptors_Available = c.int(os.EMFILE),
|
||||
No_Buffer_Space_Available = c.int(os.ENOBUFS),
|
||||
Nonlocal_Address = c.int(os.EADDRNOTAVAIL),
|
||||
Not_Socket = c.int(os.ENOTSOCK),
|
||||
Listening_Not_Supported_For_This_Socket = c.int(os.EOPNOTSUPP),
|
||||
}
|
||||
|
||||
Accept_Error :: enum c.int {
|
||||
None = 0,
|
||||
// TODO(tetra): Is this error actually possible here? Or is like Linux, in which case we can remove it.
|
||||
Reset = c.int(os.ECONNRESET),
|
||||
Not_Listening = c.int(os.EINVAL),
|
||||
No_Socket_Descriptors_Available_For_Client_Socket = c.int(os.EMFILE),
|
||||
No_Buffer_Space_Available = c.int(os.ENOBUFS),
|
||||
Not_Socket = c.int(os.ENOTSOCK),
|
||||
Not_Connection_Oriented_Socket = c.int(os.EOPNOTSUPP),
|
||||
|
||||
// TODO: we may need special handling for this; maybe make a socket a struct with metadata?
|
||||
Would_Block = c.int(os.EWOULDBLOCK),
|
||||
}
|
||||
|
||||
TCP_Recv_Error :: enum c.int {
|
||||
None = 0,
|
||||
Shutdown = c.int(os.ESHUTDOWN),
|
||||
Not_Connected = c.int(os.ENOTCONN),
|
||||
|
||||
// TODO(tetra): Is this error actually possible here?
|
||||
Connection_Broken = c.int(os.ENETRESET),
|
||||
Not_Socket = c.int(os.ENOTSOCK),
|
||||
Aborted = c.int(os.ECONNABORTED),
|
||||
|
||||
// TODO(tetra): Determine when this is different from the syscall returning n=0 and maybe normalize them?
|
||||
Connection_Closed = c.int(os.ECONNRESET),
|
||||
Offline = c.int(os.ENETDOWN),
|
||||
Host_Unreachable = c.int(os.EHOSTUNREACH),
|
||||
Interrupted = c.int(os.EINTR),
|
||||
|
||||
// NOTE: No, really. Presumably this means something different for nonblocking sockets...
|
||||
Timeout = c.int(os.EWOULDBLOCK),
|
||||
}
|
||||
|
||||
UDP_Recv_Error :: enum c.int {
|
||||
None = 0,
|
||||
Truncated = c.int(os.EMSGSIZE), // The buffer is too small to fit the entire message, and the message was truncated.
|
||||
Not_Socket = c.int(os.ENOTSOCK), // The so-called socket is not an open socket.
|
||||
Not_Descriptor = c.int(os.EBADF), // The so-called socket is, in fact, not even a valid descriptor.
|
||||
Bad_Buffer = c.int(os.EFAULT), // The buffer did not point to a valid location in memory.
|
||||
Interrupted = c.int(os.EINTR), // A signal occurred before any data was transmitted. See signal(7).
|
||||
|
||||
// The send timeout duration passed before all data was sent. See Socket_Option.Send_Timeout.
|
||||
// NOTE: No, really. Presumably this means something different for nonblocking sockets...
|
||||
Timeout = c.int(os.EWOULDBLOCK),
|
||||
Socket_Not_Bound = c.int(os.EINVAL), // The socket must be bound for this operation, but isn't.
|
||||
}
|
||||
|
||||
// TODO
|
||||
TCP_Send_Error :: enum c.int {
|
||||
None = 0,
|
||||
|
||||
// TODO: merge with other errors?
|
||||
Aborted = c.int(os.ECONNABORTED),
|
||||
Connection_Closed = c.int(os.ECONNRESET),
|
||||
Not_Connected = c.int(os.ENOTCONN),
|
||||
Shutdown = c.int(os.ESHUTDOWN),
|
||||
|
||||
// The send queue was full.
|
||||
// This is usually a transient issue.
|
||||
//
|
||||
// This also shouldn't normally happen on Linux, as data is dropped if it
|
||||
// doesn't fit in the send queue.
|
||||
No_Buffer_Space_Available = c.int(os.ENOBUFS),
|
||||
Offline = c.int(os.ENETDOWN),
|
||||
Host_Unreachable = c.int(os.EHOSTUNREACH),
|
||||
Interrupted = c.int(os.EINTR), // A signal occurred before any data was transmitted. See signal(7).
|
||||
|
||||
// NOTE: No, really. Presumably this means something different for nonblocking sockets...
|
||||
// The send timeout duration passed before all data was sent. See Socket_Option.Send_Timeout.
|
||||
Timeout = c.int(os.EWOULDBLOCK),
|
||||
Not_Socket = c.int(os.ENOTSOCK), // The so-called socket is not an open socket.
|
||||
}
|
||||
|
||||
// TODO
|
||||
UDP_Send_Error :: enum c.int {
|
||||
None = 0,
|
||||
Truncated = c.int(os.EMSGSIZE), // The message is too big. No data was sent.
|
||||
|
||||
// TODO: not sure what the exact circumstances for this is yet
|
||||
Network_Unreachable = c.int(os.ENETUNREACH),
|
||||
No_Outbound_Ports_Available = c.int(os.EAGAIN), // There are no more emphemeral outbound ports available to bind the socket to, in order to send.
|
||||
|
||||
// The send timeout duration passed before all data was sent. See Socket_Option.Send_Timeout.
|
||||
// NOTE: No, really. Presumably this means something different for nonblocking sockets...
|
||||
Timeout = c.int(os.EWOULDBLOCK),
|
||||
Not_Socket = c.int(os.ENOTSOCK), // The so-called socket is not an open socket.
|
||||
Not_Descriptor = c.int(os.EBADF), // The so-called socket is, in fact, not even a valid descriptor.
|
||||
Bad_Buffer = c.int(os.EFAULT), // The buffer did not point to a valid location in memory.
|
||||
Interrupted = c.int(os.EINTR), // A signal occurred before any data was transmitted. See signal(7).
|
||||
|
||||
// The send queue was full.
|
||||
// This is usually a transient issue.
|
||||
//
|
||||
// This also shouldn't normally happen on Linux, as data is dropped if it
|
||||
// doesn't fit in the send queue.
|
||||
No_Buffer_Space_Available = c.int(os.ENOBUFS),
|
||||
No_Memory_Available = c.int(os.ENOMEM), // No memory was available to properly manage the send queue.
|
||||
}
|
||||
|
||||
Shutdown_Manner :: enum c.int {
|
||||
Receive = c.int(os.SHUT_RD),
|
||||
Send = c.int(os.SHUT_WR),
|
||||
Both = c.int(os.SHUT_RDWR),
|
||||
}
|
||||
|
||||
Shutdown_Error :: enum c.int {
|
||||
None = 0,
|
||||
Aborted = c.int(os.ECONNABORTED),
|
||||
Reset = c.int(os.ECONNRESET),
|
||||
Offline = c.int(os.ENETDOWN),
|
||||
Not_Connected = c.int(os.ENOTCONN),
|
||||
Not_Socket = c.int(os.ENOTSOCK),
|
||||
Invalid_Manner = c.int(os.EINVAL),
|
||||
}
|
||||
|
||||
Socket_Option_Error :: enum c.int {
|
||||
None = 0,
|
||||
Offline = c.int(os.ENETDOWN),
|
||||
Timeout_When_Keepalive_Set = c.int(os.ENETRESET),
|
||||
Invalid_Option_For_Socket = c.int(os.ENOPROTOOPT),
|
||||
Reset_When_Keepalive_Set = c.int(os.ENOTCONN),
|
||||
Not_Socket = c.int(os.ENOTSOCK),
|
||||
}
|
||||
|
||||
Set_Blocking_Error :: enum c.int {
|
||||
None = 0,
|
||||
|
||||
// TODO: Add errors for `set_blocking`
|
||||
}
|
||||
@@ -0,0 +1,201 @@
|
||||
package net
|
||||
// +build linux
|
||||
|
||||
/*
|
||||
Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures.
|
||||
For other protocols and their features, see subdirectories of this package.
|
||||
*/
|
||||
|
||||
/*
|
||||
Copyright 2022 Tetralux <tetraluxonpc@gmail.com>
|
||||
Copyright 2022 Colin Davidson <colrdavidson@gmail.com>
|
||||
Copyright 2022 Jeroen van Rijn <nom@duclavier.com>.
|
||||
Made available under Odin's BSD-3 license.
|
||||
|
||||
List of contributors:
|
||||
Tetralux: Initial implementation
|
||||
Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver
|
||||
Jeroen van Rijn: Cross platform unification, code style, documentation
|
||||
*/
|
||||
|
||||
import "core:c"
|
||||
import "core:os"
|
||||
|
||||
Create_Socket_Error :: enum c.int {
|
||||
None = 0,
|
||||
Family_Not_Supported_For_This_Socket = c.int(os.EAFNOSUPPORT),
|
||||
No_Socket_Descriptors_Available = c.int(os.EMFILE),
|
||||
No_Buffer_Space_Available = c.int(os.ENOBUFS),
|
||||
No_Memory_Available_Available = c.int(os.ENOMEM),
|
||||
Protocol_Unsupported_By_System = c.int(os.EPROTONOSUPPORT),
|
||||
Wrong_Protocol_For_Socket = c.int(os.EPROTONOSUPPORT),
|
||||
Family_And_Socket_Type_Mismatch = c.int(os.EPROTONOSUPPORT),
|
||||
}
|
||||
|
||||
Dial_Error :: enum c.int {
|
||||
None = 0,
|
||||
Port_Required = -1,
|
||||
|
||||
Address_In_Use = c.int(os.EADDRINUSE),
|
||||
In_Progress = c.int(os.EINPROGRESS),
|
||||
Cannot_Use_Any_Address = c.int(os.EADDRNOTAVAIL),
|
||||
Wrong_Family_For_Socket = c.int(os.EAFNOSUPPORT),
|
||||
Refused = c.int(os.ECONNREFUSED),
|
||||
Is_Listening_Socket = c.int(os.EACCES),
|
||||
Already_Connected = c.int(os.EISCONN),
|
||||
Network_Unreachable = c.int(os.ENETUNREACH), // Device is offline
|
||||
Host_Unreachable = c.int(os.EHOSTUNREACH), // Remote host cannot be reached
|
||||
No_Buffer_Space_Available = c.int(os.ENOBUFS),
|
||||
Not_Socket = c.int(os.ENOTSOCK),
|
||||
Timeout = c.int(os.ETIMEDOUT),
|
||||
|
||||
// TODO: we may need special handling for this; maybe make a socket a struct with metadata?
|
||||
Would_Block = c.int(os.EWOULDBLOCK),
|
||||
}
|
||||
|
||||
Bind_Error :: enum c.int {
|
||||
None = 0,
|
||||
Address_In_Use = c.int(os.EADDRINUSE), // Another application is currently bound to this endpoint.
|
||||
Given_Nonlocal_Address = c.int(os.EADDRNOTAVAIL), // The address is not a local address on this machine.
|
||||
Broadcast_Disabled = c.int(os.EACCES), // To bind a UDP socket to the broadcast address, the appropriate socket option must be set.
|
||||
Address_Family_Mismatch = c.int(os.EFAULT), // The address family of the address does not match that of the socket.
|
||||
Already_Bound = c.int(os.EINVAL), // The socket is already bound to an address.
|
||||
No_Ports_Available = c.int(os.ENOBUFS), // There are not enough ephemeral ports available.
|
||||
}
|
||||
|
||||
Listen_Error :: enum c.int {
|
||||
None = 0,
|
||||
Address_In_Use = c.int(os.EADDRINUSE),
|
||||
Already_Connected = c.int(os.EISCONN),
|
||||
No_Socket_Descriptors_Available = c.int(os.EMFILE),
|
||||
No_Buffer_Space_Available = c.int(os.ENOBUFS),
|
||||
Nonlocal_Address = c.int(os.EADDRNOTAVAIL),
|
||||
Not_Socket = c.int(os.ENOTSOCK),
|
||||
Listening_Not_Supported_For_This_Socket = c.int(os.EOPNOTSUPP),
|
||||
}
|
||||
|
||||
Accept_Error :: enum c.int {
|
||||
None = 0,
|
||||
Not_Listening = c.int(os.EINVAL),
|
||||
No_Socket_Descriptors_Available_For_Client_Socket = c.int(os.EMFILE),
|
||||
No_Buffer_Space_Available = c.int(os.ENOBUFS),
|
||||
Not_Socket = c.int(os.ENOTSOCK),
|
||||
Not_Connection_Oriented_Socket = c.int(os.EOPNOTSUPP),
|
||||
|
||||
// TODO: we may need special handling for this; maybe make a socket a struct with metadata?
|
||||
Would_Block = c.int(os.EWOULDBLOCK),
|
||||
}
|
||||
|
||||
TCP_Recv_Error :: enum c.int {
|
||||
None = 0,
|
||||
Shutdown = c.int(os.ESHUTDOWN),
|
||||
Not_Connected = c.int(os.ENOTCONN),
|
||||
Connection_Broken = c.int(os.ENETRESET),
|
||||
Not_Socket = c.int(os.ENOTSOCK),
|
||||
Aborted = c.int(os.ECONNABORTED),
|
||||
|
||||
// TODO(tetra): Determine when this is different from the syscall returning n=0 and maybe normalize them?
|
||||
Connection_Closed = c.int(os.ECONNRESET),
|
||||
Offline = c.int(os.ENETDOWN),
|
||||
Host_Unreachable = c.int(os.EHOSTUNREACH),
|
||||
Interrupted = c.int(os.EINTR),
|
||||
Timeout = c.int(os.EWOULDBLOCK), // NOTE: No, really. Presumably this means something different for nonblocking sockets...
|
||||
}
|
||||
|
||||
UDP_Recv_Error :: enum c.int {
|
||||
None = 0,
|
||||
|
||||
// The buffer is too small to fit the entire message, and the message was truncated.
|
||||
// When this happens, the rest of message is lost.
|
||||
Buffer_Too_Small = c.int(os.EMSGSIZE),
|
||||
Not_Socket = c.int(os.ENOTSOCK), // The so-called socket is not an open socket.
|
||||
Not_Descriptor = c.int(os.EBADF), // The so-called socket is, in fact, not even a valid descriptor.
|
||||
Bad_Buffer = c.int(os.EFAULT), // The buffer did not point to a valid location in memory.
|
||||
Interrupted = c.int(os.EINTR), // A signal occurred before any data was transmitted. See signal(7).
|
||||
|
||||
// The send timeout duration passed before all data was received. See Socket_Option.Receive_Timeout.
|
||||
// NOTE: No, really. Presumably this means something different for nonblocking sockets...
|
||||
Timeout = c.int(os.EWOULDBLOCK),
|
||||
Socket_Not_Bound = c.int(os.EINVAL), // The socket must be bound for this operation, but isn't.
|
||||
}
|
||||
|
||||
// TODO
|
||||
TCP_Send_Error :: enum c.int {
|
||||
None = 0,
|
||||
// TODO(tetra): merge with other errors?
|
||||
Aborted = c.int(os.ECONNABORTED),
|
||||
Connection_Closed = c.int(os.ECONNRESET),
|
||||
Not_Connected = c.int(os.ENOTCONN),
|
||||
Shutdown = c.int(os.ESHUTDOWN),
|
||||
|
||||
// The send queue was full.
|
||||
// This is usually a transient issue.
|
||||
//
|
||||
// This also shouldn't normally happen on Linux, as data is dropped if it
|
||||
// doesn't fit in the send queue.
|
||||
No_Buffer_Space_Available = c.int(os.ENOBUFS),
|
||||
Offline = c.int(os.ENETDOWN),
|
||||
Host_Unreachable = c.int(os.EHOSTUNREACH), // A signal occurred before any data was transmitted. See signal(7).
|
||||
Interrupted = c.int(os.EINTR), // The send timeout duration passed before all data was sent. See Socket_Option.Send_Timeout.
|
||||
Timeout = c.int(os.EWOULDBLOCK), // NOTE: No, really. Presumably this means something different for nonblocking sockets...
|
||||
Not_Socket = c.int(os.ENOTSOCK), // The so-called socket is not an open socket.
|
||||
}
|
||||
|
||||
// TODO
|
||||
UDP_Send_Error :: enum c.int {
|
||||
None = 0,
|
||||
Message_Too_Long = c.int(os.EMSGSIZE), // The message is too big. No data was sent.
|
||||
|
||||
// TODO: not sure what the exact circumstances for this is yet
|
||||
Network_Unreachable = c.int(os.ENETUNREACH),
|
||||
No_Outbound_Ports_Available = c.int(os.EAGAIN), // There are no more emphemeral outbound ports available to bind the socket to, in order to send.
|
||||
|
||||
// The send timeout duration passed before all data was sent. See Socket_Option.Send_Timeout.
|
||||
// NOTE: No, really. Presumably this means something different for nonblocking sockets...
|
||||
Timeout = c.int(os.EWOULDBLOCK),
|
||||
Not_Socket = c.int(os.ENOTSOCK), // The so-called socket is not an open socket.
|
||||
Not_Descriptor = c.int(os.EBADF), // The so-called socket is, in fact, not even a valid descriptor.
|
||||
Bad_Buffer = c.int(os.EFAULT), // The buffer did not point to a valid location in memory.
|
||||
Interrupted = c.int(os.EINTR), // A signal occurred before any data was transmitted. See signal(7).
|
||||
|
||||
// The send queue was full.
|
||||
// This is usually a transient issue.
|
||||
//
|
||||
// This also shouldn't normally happen on Linux, as data is dropped if it
|
||||
// doesn't fit in the send queue.
|
||||
No_Buffer_Space_Available = c.int(os.ENOBUFS),
|
||||
No_Memory_Available = c.int(os.ENOMEM), // No memory was available to properly manage the send queue.
|
||||
}
|
||||
|
||||
Shutdown_Manner :: enum c.int {
|
||||
Receive = c.int(os.SHUT_RD),
|
||||
Send = c.int(os.SHUT_WR),
|
||||
Both = c.int(os.SHUT_RDWR),
|
||||
}
|
||||
|
||||
Shutdown_Error :: enum c.int {
|
||||
None = 0,
|
||||
Aborted = c.int(os.ECONNABORTED),
|
||||
Reset = c.int(os.ECONNRESET),
|
||||
Offline = c.int(os.ENETDOWN),
|
||||
Not_Connected = c.int(os.ENOTCONN),
|
||||
Not_Socket = c.int(os.ENOTSOCK),
|
||||
Invalid_Manner = c.int(os.EINVAL),
|
||||
}
|
||||
|
||||
Socket_Option_Error :: enum c.int {
|
||||
None = 0,
|
||||
Offline = c.int(os.ENETDOWN),
|
||||
Timeout_When_Keepalive_Set = c.int(os.ENETRESET),
|
||||
Invalid_Option_For_Socket = c.int(os.ENOPROTOOPT),
|
||||
Reset_When_Keepalive_Set = c.int(os.ENOTCONN),
|
||||
Not_Socket = c.int(os.ENOTSOCK),
|
||||
}
|
||||
|
||||
Set_Blocking_Error :: enum c.int {
|
||||
None = 0,
|
||||
|
||||
// TODO: add errors occuring on followig calls:
|
||||
// flags, _ := os.fcntl(sd, os.F_GETFL, 0)
|
||||
// os.fcntl(sd, os.F_SETFL, flags | int(os.O_NONBLOCK))
|
||||
}
|
||||
@@ -0,0 +1,273 @@
|
||||
package net
|
||||
// +build windows
|
||||
|
||||
/*
|
||||
Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures.
|
||||
For other protocols and their features, see subdirectories of this package.
|
||||
*/
|
||||
|
||||
/*
|
||||
Copyright 2022 Tetralux <tetraluxonpc@gmail.com>
|
||||
Copyright 2022 Colin Davidson <colrdavidson@gmail.com>
|
||||
Copyright 2022 Jeroen van Rijn <nom@duclavier.com>.
|
||||
Made available under Odin's BSD-3 license.
|
||||
|
||||
List of contributors:
|
||||
Tetralux: Initial implementation
|
||||
Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver
|
||||
Jeroen van Rijn: Cross platform unification, code style, documentation
|
||||
*/
|
||||
|
||||
import "core:c"
|
||||
import win "core:sys/windows"
|
||||
|
||||
Create_Socket_Error :: enum c.int {
|
||||
None = 0,
|
||||
Network_Subsystem_Failure = win.WSAENETDOWN,
|
||||
Family_Not_Supported_For_This_Socket = win.WSAEAFNOSUPPORT,
|
||||
No_Socket_Descriptors_Available = win.WSAEMFILE,
|
||||
No_Buffer_Space_Available = win.WSAENOBUFS,
|
||||
Protocol_Unsupported_By_System = win.WSAEPROTONOSUPPORT,
|
||||
Wrong_Protocol_For_Socket = win.WSAEPROTOTYPE,
|
||||
Family_And_Socket_Type_Mismatch = win.WSAESOCKTNOSUPPORT,
|
||||
}
|
||||
|
||||
Dial_Error :: enum c.int {
|
||||
None = 0,
|
||||
Port_Required = -1,
|
||||
Address_In_Use = win.WSAEADDRINUSE,
|
||||
In_Progress = win.WSAEALREADY,
|
||||
Cannot_Use_Any_Address = win.WSAEADDRNOTAVAIL,
|
||||
Wrong_Family_For_Socket = win.WSAEAFNOSUPPORT,
|
||||
Refused = win.WSAECONNREFUSED,
|
||||
Is_Listening_Socket = win.WSAEINVAL,
|
||||
Already_Connected = win.WSAEISCONN,
|
||||
Network_Unreachable = win.WSAENETUNREACH, // Device is offline
|
||||
Host_Unreachable = win.WSAEHOSTUNREACH, // Remote host cannot be reached
|
||||
No_Buffer_Space_Available = win.WSAENOBUFS,
|
||||
Not_Socket = win.WSAENOTSOCK,
|
||||
Timeout = win.WSAETIMEDOUT,
|
||||
Would_Block = win.WSAEWOULDBLOCK, // TODO: we may need special handling for this; maybe make a socket a struct with metadata?
|
||||
}
|
||||
|
||||
Bind_Error :: enum c.int {
|
||||
None = 0,
|
||||
Address_In_Use = win.WSAEADDRINUSE, // Another application is currently bound to this endpoint.
|
||||
Given_Nonlocal_Address = win.WSAEADDRNOTAVAIL, // The address is not a local address on this machine.
|
||||
Broadcast_Disabled = win.WSAEACCES, // To bind a UDP socket to the broadcast address, the appropriate socket option must be set.
|
||||
Address_Family_Mismatch = win.WSAEFAULT, // The address family of the address does not match that of the socket.
|
||||
Already_Bound = win.WSAEINVAL, // The socket is already bound to an address.
|
||||
No_Ports_Available = win.WSAENOBUFS, // There are not enough ephemeral ports available.
|
||||
}
|
||||
|
||||
Listen_Error :: enum c.int {
|
||||
None = 0,
|
||||
Address_In_Use = win.WSAEADDRINUSE,
|
||||
Already_Connected = win.WSAEISCONN,
|
||||
No_Socket_Descriptors_Available = win.WSAEMFILE,
|
||||
No_Buffer_Space_Available = win.WSAENOBUFS,
|
||||
Nonlocal_Address = win.WSAEADDRNOTAVAIL,
|
||||
Not_Socket = win.WSAENOTSOCK,
|
||||
Listening_Not_Supported_For_This_Socket = win.WSAEOPNOTSUPP,
|
||||
}
|
||||
|
||||
Accept_Error :: enum c.int {
|
||||
None = 0,
|
||||
Not_Listening = win.WSAEINVAL,
|
||||
No_Socket_Descriptors_Available_For_Client_Socket = win.WSAEMFILE,
|
||||
No_Buffer_Space_Available = win.WSAENOBUFS,
|
||||
Not_Socket = win.WSAENOTSOCK,
|
||||
Not_Connection_Oriented_Socket = win.WSAEOPNOTSUPP,
|
||||
|
||||
// TODO: we may need special handling for this; maybe make a socket a struct with metadata?
|
||||
Would_Block = win.WSAEWOULDBLOCK,
|
||||
}
|
||||
|
||||
TCP_Recv_Error :: enum c.int {
|
||||
None = 0,
|
||||
Network_Subsystem_Failure = win.WSAENETDOWN,
|
||||
Not_Connected = win.WSAENOTCONN,
|
||||
Bad_Buffer = win.WSAEFAULT,
|
||||
Keepalive_Failure = win.WSAENETRESET,
|
||||
Not_Socket = win.WSAENOTSOCK,
|
||||
Shutdown = win.WSAESHUTDOWN,
|
||||
Would_Block = win.WSAEWOULDBLOCK,
|
||||
|
||||
// TODO: not functionally different from Reset; merge?
|
||||
Aborted = win.WSAECONNABORTED,
|
||||
Timeout = win.WSAETIMEDOUT,
|
||||
|
||||
// TODO(tetra): Determine when this is different from the syscall returning n=0 and maybe normalize them?
|
||||
Connection_Closed = win.WSAECONNRESET,
|
||||
|
||||
// TODO: verify can actually happen
|
||||
Host_Unreachable = win.WSAEHOSTUNREACH,
|
||||
}
|
||||
|
||||
UDP_Recv_Error :: enum c.int {
|
||||
None = 0,
|
||||
Network_Subsystem_Failure = win.WSAENETDOWN,
|
||||
|
||||
// TODO: not functionally different from Reset; merge?
|
||||
// UDP packets are limited in size, and the length of the incoming message exceeded it.
|
||||
Aborted = win.WSAECONNABORTED,
|
||||
Truncated = win.WSAEMSGSIZE,
|
||||
Remote_Not_Listening = win.WSAECONNRESET, // The machine at the remote endpoint doesn't have the given port open to receiving UDP data.
|
||||
Shutdown = win.WSAESHUTDOWN,
|
||||
Broadcast_Disabled = win.WSAEACCES, // A broadcast address was specified, but the .Broadcast socket option isn't set.
|
||||
Bad_Buffer = win.WSAEFAULT,
|
||||
No_Buffer_Space_Available = win.WSAENOBUFS,
|
||||
Not_Socket = win.WSAENOTSOCK, // The socket is not valid socket handle.
|
||||
Would_Block = win.WSAEWOULDBLOCK,
|
||||
Host_Unreachable = win.WSAEHOSTUNREACH, // The remote host cannot be reached from this host at this time.
|
||||
Offline = win.WSAENETUNREACH, // The network cannot be reached from this host at this time.
|
||||
Timeout = win.WSAETIMEDOUT,
|
||||
|
||||
// TODO: can this actually happen? The socket isn't bound; an unknown flag specified; or MSG_OOB specified with SO_OOBINLINE enabled.
|
||||
Incorrectly_Configured = win.WSAEINVAL,
|
||||
TTL_Expired = win.WSAENETRESET, // The message took more hops than was allowed (the Time To Live) to reach the remote endpoint.
|
||||
}
|
||||
|
||||
// TODO: consider merging some errors to make handling them easier
|
||||
// TODO: verify once more what errors to actually expose
|
||||
TCP_Send_Error :: enum c.int {
|
||||
None = 0,
|
||||
|
||||
// TODO: not functionally different from Reset; merge?
|
||||
Aborted = win.WSAECONNABORTED,
|
||||
Not_Connected = win.WSAENOTCONN,
|
||||
Shutdown = win.WSAESHUTDOWN,
|
||||
Connection_Closed = win.WSAECONNRESET,
|
||||
No_Buffer_Space_Available = win.WSAENOBUFS,
|
||||
Network_Subsystem_Failure = win.WSAENETDOWN,
|
||||
Host_Unreachable = win.WSAEHOSTUNREACH,
|
||||
|
||||
// TODO: verify possible, as not mentioned in docs
|
||||
Offline = win.WSAENETUNREACH,
|
||||
Timeout = win.WSAETIMEDOUT,
|
||||
|
||||
// A broadcast address was specified, but the .Broadcast socket option isn't set.
|
||||
Broadcast_Disabled = win.WSAEACCES,
|
||||
Bad_Buffer = win.WSAEFAULT,
|
||||
|
||||
// Connection is broken due to keepalive activity detecting a failure during the operation.
|
||||
Keepalive_Failure = win.WSAENETRESET, // TODO: not functionally different from Reset; merge?
|
||||
Not_Socket = win.WSAENOTSOCK, // The so-called socket is not an open socket.
|
||||
}
|
||||
|
||||
UDP_Send_Error :: enum c.int {
|
||||
None = 0,
|
||||
Network_Subsystem_Failure = win.WSAENETDOWN,
|
||||
|
||||
// TODO: not functionally different from Reset; merge?
|
||||
Aborted = win.WSAECONNABORTED, // UDP packets are limited in size, and len(buf) exceeded it.
|
||||
Message_Too_Long = win.WSAEMSGSIZE, // The machine at the remote endpoint doesn't have the given port open to receiving UDP data.
|
||||
Remote_Not_Listening = win.WSAECONNRESET,
|
||||
Shutdown = win.WSAESHUTDOWN, // A broadcast address was specified, but the .Broadcast socket option isn't set.
|
||||
Broadcast_Disabled = win.WSAEACCES,
|
||||
Bad_Buffer = win.WSAEFAULT, // Connection is broken due to keepalive activity detecting a failure during the operation.
|
||||
|
||||
// TODO: not functionally different from Reset; merge?
|
||||
Keepalive_Failure = win.WSAENETRESET,
|
||||
No_Buffer_Space_Available = win.WSAENOBUFS,
|
||||
Not_Socket = win.WSAENOTSOCK, // The socket is not valid socket handle.
|
||||
|
||||
// This socket is unidirectional and cannot be used to send any data.
|
||||
// TODO: verify possible; decide whether to keep if not
|
||||
Receive_Only = win.WSAEOPNOTSUPP,
|
||||
Would_Block = win.WSAEWOULDBLOCK,
|
||||
Host_Unreachable = win.WSAEHOSTUNREACH, // The remote host cannot be reached from this host at this time.
|
||||
Cannot_Use_Any_Address = win.WSAEADDRNOTAVAIL, // Attempt to send to the Any address.
|
||||
Family_Not_Supported_For_This_Socket = win.WSAEAFNOSUPPORT, // The address is of an incorrect address family for this socket.
|
||||
Offline = win.WSAENETUNREACH, // The network cannot be reached from this host at this time.
|
||||
Timeout = win.WSAETIMEDOUT,
|
||||
}
|
||||
|
||||
Shutdown_Manner :: enum c.int {
|
||||
Receive = win.SD_RECEIVE,
|
||||
Send = win.SD_SEND,
|
||||
Both = win.SD_BOTH,
|
||||
}
|
||||
|
||||
Shutdown_Error :: enum c.int {
|
||||
None = 0,
|
||||
Aborted = win.WSAECONNABORTED,
|
||||
Reset = win.WSAECONNRESET,
|
||||
Offline = win.WSAENETDOWN,
|
||||
Not_Connected = win.WSAENOTCONN,
|
||||
Not_Socket = win.WSAENOTSOCK,
|
||||
Invalid_Manner = win.WSAEINVAL,
|
||||
}
|
||||
|
||||
Socket_Option :: enum c.int {
|
||||
// bool: Whether the address that this socket is bound to can be reused by other sockets.
|
||||
// This allows you to bypass the cooldown period if a program dies while the socket is bound.
|
||||
Reuse_Address = win.SO_REUSEADDR,
|
||||
|
||||
// bool: Whether other programs will be inhibited from binding the same endpoint as this socket.
|
||||
Exclusive_Addr_Use = win.SO_EXCLUSIVEADDRUSE,
|
||||
|
||||
// bool: When true, keepalive packets will be automatically be sent for this connection. TODO: verify this understanding
|
||||
Keep_Alive = win.SO_KEEPALIVE,
|
||||
|
||||
// bool: When true, client connections will immediately be sent a TCP/IP RST response, rather than being accepted.
|
||||
Conditional_Accept = win.SO_CONDITIONAL_ACCEPT,
|
||||
|
||||
// bool: If true, when the socket is closed, but data is still waiting to be sent, discard that data.
|
||||
Dont_Linger = win.SO_DONTLINGER,
|
||||
|
||||
// bool: When true, 'out-of-band' data sent over the socket will be read by a normal net.recv() call, the same as normal 'in-band' data.
|
||||
Out_Of_Bounds_Data_Inline = win.SO_OOBINLINE,
|
||||
|
||||
// bool: When true, disables send-coalescing, therefore reducing latency.
|
||||
TCP_Nodelay = win.TCP_NODELAY,
|
||||
|
||||
// win.LINGER: Customizes how long (if at all) the socket will remain open when there
|
||||
// is some remaining data waiting to be sent, and net.close() is called.
|
||||
Linger = win.SO_LINGER,
|
||||
|
||||
// win.DWORD: The size, in bytes, of the OS-managed receive-buffer for this socket.
|
||||
Receive_Buffer_Size = win.SO_RCVBUF,
|
||||
|
||||
// win.DWORD: The size, in bytes, of the OS-managed send-buffer for this socket.
|
||||
Send_Buffer_Size = win.SO_SNDBUF,
|
||||
|
||||
// win.DWORD: For blocking sockets, the time in milliseconds to wait for incoming data to be received, before giving up and returning .Timeout.
|
||||
// For non-blocking sockets, ignored.
|
||||
// Use a value of zero to potentially wait forever.
|
||||
Receive_Timeout = win.SO_RCVTIMEO,
|
||||
|
||||
// win.DWORD: For blocking sockets, the time in milliseconds to wait for outgoing data to be sent, before giving up and returning .Timeout.
|
||||
// For non-blocking sockets, ignored.
|
||||
// Use a value of zero to potentially wait forever.
|
||||
Send_Timeout = win.SO_SNDTIMEO,
|
||||
|
||||
// bool: Allow sending to, receiving from, and binding to, a broadcast address.
|
||||
Broadcast = win.SO_BROADCAST,
|
||||
}
|
||||
|
||||
Socket_Option_Error :: enum c.int {
|
||||
None = 0,
|
||||
Linger_Only_Supports_Whole_Seconds = 1,
|
||||
|
||||
// The given value is too big or small to be given to the OS.
|
||||
Value_Out_Of_Range,
|
||||
|
||||
Network_Subsystem_Failure = win.WSAENETDOWN,
|
||||
Timeout_When_Keepalive_Set = win.WSAENETRESET,
|
||||
Invalid_Option_For_Socket = win.WSAENOPROTOOPT,
|
||||
Reset_When_Keepalive_Set = win.WSAENOTCONN,
|
||||
Not_Socket = win.WSAENOTSOCK,
|
||||
}
|
||||
|
||||
Set_Blocking_Error :: enum c.int {
|
||||
None = 0,
|
||||
|
||||
Network_Subsystem_Failure = win.WSAENETDOWN,
|
||||
Blocking_Call_In_Progress = win.WSAEINPROGRESS,
|
||||
Not_Socket = win.WSAENOTSOCK,
|
||||
|
||||
// TODO: are those errors possible?
|
||||
Network_Subsystem_Not_Initialized = win.WSAENOTINITIALISED,
|
||||
Invalid_Argument_Pointer = win.WSAEFAULT,
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
// +build windows, linux, darwin
|
||||
package net
|
||||
|
||||
/*
|
||||
Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures.
|
||||
For other protocols and their features, see subdirectories of this package.
|
||||
*/
|
||||
|
||||
/*
|
||||
Copyright 2022 Tetralux <tetraluxonpc@gmail.com>
|
||||
Copyright 2022 Colin Davidson <colrdavidson@gmail.com>
|
||||
Copyright 2022 Jeroen van Rijn <nom@duclavier.com>.
|
||||
Made available under Odin's BSD-3 license.
|
||||
|
||||
List of contributors:
|
||||
Tetralux: Initial implementation
|
||||
Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver
|
||||
Jeroen van Rijn: Cross platform unification, code style, documentation
|
||||
*/
|
||||
|
||||
import "core:strings"
|
||||
|
||||
MAX_INTERFACE_ENUMERATION_TRIES :: 3
|
||||
|
||||
/*
|
||||
`enumerate_interfaces` retrieves a list of network interfaces with their associated properties.
|
||||
*/
|
||||
enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: []Network_Interface, err: Network_Error) {
|
||||
return _enumerate_interfaces(allocator)
|
||||
}
|
||||
|
||||
/*
|
||||
`destroy_interfaces` cleans up a list of network interfaces retrieved by e.g. `enumerate_interfaces`.
|
||||
*/
|
||||
destroy_interfaces :: proc(interfaces: []Network_Interface, allocator := context.allocator) {
|
||||
context.allocator = allocator
|
||||
|
||||
for i in interfaces {
|
||||
delete(i.adapter_name)
|
||||
delete(i.friendly_name)
|
||||
delete(i.description)
|
||||
delete(i.dns_suffix)
|
||||
|
||||
delete(i.physical_address)
|
||||
|
||||
delete(i.unicast)
|
||||
delete(i.multicast)
|
||||
delete(i.anycast)
|
||||
delete(i.gateways)
|
||||
}
|
||||
delete(interfaces, allocator)
|
||||
}
|
||||
|
||||
/*
|
||||
Turns a slice of bytes (from e.g. `get_adapters_addresses`) into a "XX:XX:XX:..." string.
|
||||
*/
|
||||
physical_address_to_string :: proc(phy_addr: []u8, allocator := context.allocator) -> (phy_string: string) {
|
||||
context.allocator = allocator
|
||||
|
||||
MAC_HEX := "0123456789ABCDEF"
|
||||
|
||||
if len(phy_addr) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
buf: strings.Builder
|
||||
|
||||
for b, i in phy_addr {
|
||||
if i > 0 {
|
||||
strings.write_rune(&buf, ':')
|
||||
}
|
||||
|
||||
hi := rune(MAC_HEX[b >> 4])
|
||||
lo := rune(MAC_HEX[b & 15])
|
||||
strings.write_rune(&buf, hi)
|
||||
strings.write_rune(&buf, lo)
|
||||
}
|
||||
return strings.to_string(buf)
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package net
|
||||
//+build darwin
|
||||
|
||||
/*
|
||||
Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures.
|
||||
For other protocols and their features, see subdirectories of this package.
|
||||
*/
|
||||
|
||||
/*
|
||||
Copyright 2022 Tetralux <tetraluxonpc@gmail.com>
|
||||
Copyright 2022 Colin Davidson <colrdavidson@gmail.com>
|
||||
Copyright 2022 Jeroen van Rijn <nom@duclavier.com>.
|
||||
Made available under Odin's BSD-3 license.
|
||||
|
||||
List of contributors:
|
||||
Tetralux: Initial implementation
|
||||
Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver
|
||||
Jeroen van Rijn: Cross platform unification, code style, documentation
|
||||
|
||||
*/
|
||||
|
||||
@(private)
|
||||
_enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: []Network_Interface, err: Network_Error) {
|
||||
context.allocator = allocator
|
||||
|
||||
|
||||
// TODO: Implement. Can probably use the (current) Linux implementation,
|
||||
// which will itself be switched over to talking to the kernel via NETLINK protocol
|
||||
// once we have raw sockets.
|
||||
|
||||
unimplemented()
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
package net
|
||||
//+build linux
|
||||
|
||||
/*
|
||||
Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures.
|
||||
For other protocols and their features, see subdirectories of this package.
|
||||
*/
|
||||
|
||||
/*
|
||||
Copyright 2022 Tetralux <tetraluxonpc@gmail.com>
|
||||
Copyright 2022 Colin Davidson <colrdavidson@gmail.com>
|
||||
Copyright 2022 Jeroen van Rijn <nom@duclavier.com>.
|
||||
Made available under Odin's BSD-3 license.
|
||||
|
||||
List of contributors:
|
||||
Tetralux: Initial implementation
|
||||
Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver
|
||||
Jeroen van Rijn: Cross platform unification, code style, documentation
|
||||
|
||||
This file uses `getifaddrs` libc call to enumerate interfaces.
|
||||
TODO: When we have raw sockets, split off into its own file for Linux so we can use the NETLINK protocol and bypass libc.
|
||||
*/
|
||||
|
||||
import "core:os"
|
||||
import "core:strings"
|
||||
|
||||
@(private)
|
||||
_enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: []Network_Interface, err: Network_Error) {
|
||||
context.allocator = allocator
|
||||
|
||||
head: ^os.ifaddrs
|
||||
|
||||
if res := os._getifaddrs(&head); res < 0 {
|
||||
return {}, .Unable_To_Enumerate_Network_Interfaces
|
||||
}
|
||||
|
||||
/*
|
||||
Unlike Windows, *nix regrettably doesn't return all it knows about an interface in one big struct.
|
||||
We're going to have to iterate over a list and coalesce information as we go.
|
||||
*/
|
||||
ifaces: map[string]^Network_Interface
|
||||
defer delete(ifaces)
|
||||
|
||||
for ifaddr := head; ifaddr != nil; ifaddr = ifaddr.next {
|
||||
adapter_name := string(ifaddr.name)
|
||||
|
||||
/*
|
||||
Check if we have seen this interface name before so we can reuse the `Network_Interface`.
|
||||
Else, create a new one.
|
||||
*/
|
||||
if adapter_name not_in ifaces {
|
||||
ifaces[adapter_name] = new(Network_Interface)
|
||||
ifaces[adapter_name].adapter_name = strings.clone(adapter_name)
|
||||
}
|
||||
iface := ifaces[adapter_name]
|
||||
|
||||
address: Address
|
||||
netmask: Netmask
|
||||
|
||||
if ifaddr.address != nil {
|
||||
switch int(ifaddr.address.sa_family) {
|
||||
case os.AF_INET, os.AF_INET6:
|
||||
address = _sockaddr_basic_to_endpoint(ifaddr.address).address
|
||||
|
||||
case os.AF_PACKET:
|
||||
/*
|
||||
For some obscure reason the 64-bit `getifaddrs` call returns a pointer to a
|
||||
32-bit `RTNL_LINK_STATS` structure, which of course means that tx/rx byte count
|
||||
is truncated beyond usefulness.
|
||||
|
||||
We're not going to retrieve stats now. Instead this serves as a reminder to use
|
||||
the NETLINK protocol for this purpose.
|
||||
|
||||
But in case you were curious:
|
||||
stats := transmute(^os.rtnl_link_stats)ifaddr.data
|
||||
fmt.println(stats)
|
||||
*/
|
||||
case:
|
||||
}
|
||||
}
|
||||
|
||||
if ifaddr.netmask != nil {
|
||||
switch int(ifaddr.netmask.sa_family) {
|
||||
case os.AF_INET, os.AF_INET6:
|
||||
netmask = Netmask(_sockaddr_basic_to_endpoint(ifaddr.netmask).address)
|
||||
case:
|
||||
}
|
||||
}
|
||||
|
||||
if ifaddr.broadcast_or_dest != nil && .BROADCAST in ifaddr.flags {
|
||||
switch int(ifaddr.broadcast_or_dest.sa_family) {
|
||||
case os.AF_INET, os.AF_INET6:
|
||||
broadcast := _sockaddr_basic_to_endpoint(ifaddr.broadcast_or_dest).address
|
||||
append(&iface.multicast, broadcast)
|
||||
case:
|
||||
}
|
||||
}
|
||||
|
||||
if address != nil {
|
||||
lease := Lease{
|
||||
address = address,
|
||||
netmask = netmask,
|
||||
}
|
||||
append(&iface.unicast, lease)
|
||||
}
|
||||
|
||||
/*
|
||||
TODO: Refine this based on the type of adapter.
|
||||
*/
|
||||
state := Link_State{}
|
||||
|
||||
if .UP in ifaddr.flags {
|
||||
state |= {.Up}
|
||||
}
|
||||
|
||||
if .DORMANT in ifaddr.flags {
|
||||
state |= {.Dormant}
|
||||
}
|
||||
|
||||
if .LOOPBACK in ifaddr.flags {
|
||||
state |= {.Loopback}
|
||||
}
|
||||
iface.link.state = state
|
||||
}
|
||||
|
||||
/*
|
||||
Free the OS structures.
|
||||
*/
|
||||
os._freeifaddrs(head)
|
||||
|
||||
/*
|
||||
Turn the map into a slice to return.
|
||||
*/
|
||||
_interfaces := make([dynamic]Network_Interface, 0, allocator)
|
||||
for _, iface in ifaces {
|
||||
append(&_interfaces, iface^)
|
||||
free(iface)
|
||||
}
|
||||
return _interfaces[:], {}
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
package net
|
||||
//+build windows
|
||||
|
||||
/*
|
||||
Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures.
|
||||
For other protocols and their features, see subdirectories of this package.
|
||||
*/
|
||||
|
||||
/*
|
||||
Copyright 2022 Tetralux <tetraluxonpc@gmail.com>
|
||||
Copyright 2022 Colin Davidson <colrdavidson@gmail.com>
|
||||
Copyright 2022 Jeroen van Rijn <nom@duclavier.com>.
|
||||
Made available under Odin's BSD-3 license.
|
||||
|
||||
List of contributors:
|
||||
Tetralux: Initial implementation
|
||||
Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver
|
||||
Jeroen van Rijn: Cross platform unification, code style, documentation
|
||||
*/
|
||||
|
||||
import sys "core:sys/windows"
|
||||
import strings "core:strings"
|
||||
|
||||
_enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: []Network_Interface, err: Network_Error) {
|
||||
context.allocator = allocator
|
||||
|
||||
buf: []u8
|
||||
defer delete(buf)
|
||||
|
||||
buf_size: u32
|
||||
res: u32
|
||||
|
||||
gaa: for _ in 1..=MAX_INTERFACE_ENUMERATION_TRIES {
|
||||
res = sys.get_adapters_addresses(
|
||||
.Unspecified, // Return both IPv4 and IPv6 adapters.
|
||||
sys.GAA_Flags{
|
||||
.Include_Prefix, // (XP SP1+) Return a list of IP address prefixes on this adapter. When this flag is set, IP address prefixes are returned for both IPv6 and IPv4 addresses.
|
||||
.Include_Gateways, // (Vista+) Return the addresses of default gateways.
|
||||
.Include_Tunnel_Binding_Order, // (Vista+) Return the adapter addresses sorted in tunnel binding order.
|
||||
},
|
||||
nil, // Reserved
|
||||
(^sys.IP_Adapter_Addresses)(raw_data(buf)),
|
||||
&buf_size,
|
||||
)
|
||||
|
||||
switch res {
|
||||
case 111: // ERROR_BUFFER_OVERFLOW:
|
||||
delete(buf)
|
||||
buf = make([]u8, buf_size)
|
||||
case 0:
|
||||
break gaa
|
||||
case:
|
||||
return {}, Platform_Error(res)
|
||||
}
|
||||
}
|
||||
|
||||
if res != 0 {
|
||||
return {}, .Unable_To_Enumerate_Network_Interfaces
|
||||
}
|
||||
|
||||
_interfaces := make([dynamic]Network_Interface, 0, allocator)
|
||||
for adapter := (^sys.IP_Adapter_Addresses)(raw_data(buf)); adapter != nil; adapter = adapter.Next {
|
||||
friendly_name, err1 := sys.wstring_to_utf8(sys.wstring(adapter.FriendlyName), 256, allocator)
|
||||
if err1 != nil { return {}, Platform_Error(err1) }
|
||||
|
||||
description, err2 := sys.wstring_to_utf8(sys.wstring(adapter.Description), 256, allocator)
|
||||
if err2 != nil { return {}, Platform_Error(err2) }
|
||||
|
||||
dns_suffix, err3 := sys.wstring_to_utf8(sys.wstring(adapter.DnsSuffix), 256, allocator)
|
||||
if err3 != nil { return {}, Platform_Error(err3) }
|
||||
|
||||
interface := Network_Interface{
|
||||
adapter_name = strings.clone(string(adapter.AdapterName)),
|
||||
friendly_name = friendly_name,
|
||||
description = description,
|
||||
dns_suffix = dns_suffix,
|
||||
|
||||
mtu = adapter.MTU,
|
||||
|
||||
link = {
|
||||
transmit_speed = adapter.TransmitLinkSpeed,
|
||||
receive_speed = adapter.ReceiveLinkSpeed,
|
||||
},
|
||||
}
|
||||
|
||||
if adapter.PhysicalAddressLength > 0 && adapter.PhysicalAddressLength <= len(adapter.PhysicalAddress) {
|
||||
interface.physical_address = physical_address_to_string(adapter.PhysicalAddress[:adapter.PhysicalAddressLength])
|
||||
}
|
||||
|
||||
for u_addr := (^sys.IP_ADAPTER_UNICAST_ADDRESS_LH)(adapter.FirstUnicastAddress); u_addr != nil; u_addr = u_addr.Next {
|
||||
win_addr := parse_socket_address(u_addr.Address)
|
||||
|
||||
lease := Lease{
|
||||
address = win_addr.address,
|
||||
origin = {
|
||||
prefix = Prefix_Origin(u_addr.PrefixOrigin),
|
||||
suffix = Suffix_Origin(u_addr.SuffixOrigin),
|
||||
},
|
||||
lifetime = {
|
||||
valid = u_addr.ValidLifetime,
|
||||
preferred = u_addr.PreferredLifetime,
|
||||
lease = u_addr.LeaseLifetime,
|
||||
},
|
||||
address_duplication = Address_Duplication(u_addr.DadState),
|
||||
}
|
||||
append(&interface.unicast, lease)
|
||||
}
|
||||
|
||||
for a_addr := (^sys.IP_ADAPTER_ANYCAST_ADDRESS_XP)(adapter.FirstAnycastAddress); a_addr != nil; a_addr = a_addr.Next {
|
||||
addr := parse_socket_address(a_addr.Address)
|
||||
append(&interface.anycast, addr.address)
|
||||
}
|
||||
|
||||
for m_addr := (^sys.IP_ADAPTER_MULTICAST_ADDRESS_XP)(adapter.FirstMulticastAddress); m_addr != nil; m_addr = m_addr.Next {
|
||||
addr := parse_socket_address(m_addr.Address)
|
||||
append(&interface.multicast, addr.address)
|
||||
}
|
||||
|
||||
for g_addr := (^sys.IP_ADAPTER_GATEWAY_ADDRESS_LH)(adapter.FirstGatewayAddress); g_addr != nil; g_addr = g_addr.Next {
|
||||
addr := parse_socket_address(g_addr.Address)
|
||||
append(&interface.gateways, addr.address)
|
||||
}
|
||||
|
||||
interface.dhcp_v4 = parse_socket_address(adapter.Dhcpv4Server).address
|
||||
interface.dhcp_v6 = parse_socket_address(adapter.Dhcpv6Server).address
|
||||
|
||||
switch adapter.OperStatus {
|
||||
case .Up: interface.link.state = {.Up}
|
||||
case .Down: interface.link.state = {.Down}
|
||||
case .Testing: interface.link.state = {.Testing}
|
||||
case .Dormant: interface.link.state = {.Dormant}
|
||||
case .NotPresent: interface.link.state = {.Not_Present}
|
||||
case .LowerLayerDown: interface.link.state = {.Lower_Layer_Down}
|
||||
case .Unknown: fallthrough
|
||||
case: interface.link.state = {}
|
||||
}
|
||||
|
||||
interface.tunnel_type = Tunnel_Type(adapter.TunnelType)
|
||||
|
||||
append(&_interfaces, interface)
|
||||
}
|
||||
|
||||
return _interfaces[:], {}
|
||||
}
|
||||
|
||||
/*
|
||||
Interpret SOCKET_ADDRESS as an Address
|
||||
*/
|
||||
parse_socket_address :: proc(addr_in: sys.SOCKET_ADDRESS) -> (addr: Endpoint) {
|
||||
if addr_in.lpSockaddr == nil {
|
||||
return // Empty or invalid address type
|
||||
}
|
||||
|
||||
sock := addr_in.lpSockaddr^
|
||||
|
||||
switch sock.sa_family {
|
||||
case u16(sys.AF_INET):
|
||||
win_addr := cast(^sys.sockaddr_in)addr_in.lpSockaddr
|
||||
port := int(win_addr.sin_port)
|
||||
return Endpoint {
|
||||
address = IP4_Address(transmute([4]byte)win_addr.sin_addr),
|
||||
port = port,
|
||||
}
|
||||
|
||||
case u16(sys.AF_INET6):
|
||||
win_addr := cast(^sys.sockaddr_in6)addr_in.lpSockaddr
|
||||
port := int(win_addr.sin6_port)
|
||||
return Endpoint {
|
||||
address = IP6_Address(transmute([8]u16be)win_addr.sin6_addr),
|
||||
port = port,
|
||||
}
|
||||
|
||||
|
||||
case: return // Empty or invalid address type
|
||||
}
|
||||
unreachable()
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
// +build windows, linux, darwin
|
||||
package net
|
||||
|
||||
/*
|
||||
Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures.
|
||||
For other protocols and their features, see subdirectories of this package.
|
||||
*/
|
||||
|
||||
/*
|
||||
Copyright 2022-2023 Tetralux <tetraluxonpc@gmail.com>
|
||||
Copyright 2022-2023 Colin Davidson <colrdavidson@gmail.com>
|
||||
Copyright 2022-2023 Jeroen van Rijn <nom@duclavier.com>.
|
||||
Made available under Odin's BSD-3 license.
|
||||
|
||||
List of contributors:
|
||||
Tetralux: Initial implementation
|
||||
Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver
|
||||
Jeroen van Rijn: Cross platform unification, code style, documentation
|
||||
*/
|
||||
|
||||
any_socket_to_socket :: proc(socket: Any_Socket) -> Socket {
|
||||
switch s in socket {
|
||||
case TCP_Socket: return Socket(s)
|
||||
case UDP_Socket: return Socket(s)
|
||||
case:
|
||||
// TODO(tetra): Bluetooth, Raw
|
||||
return Socket({})
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Expects both hostname and port to be present in the `hostname_and_port` parameter, either as:
|
||||
`a.host.name:9999`, or as `1.2.3.4:9999`, or IP6 equivalent.
|
||||
|
||||
Calls `parse_hostname_or_endpoint` and `resolve`, then `dial_tcp_from_endpoint`.
|
||||
*/
|
||||
dial_tcp_from_hostname_and_port_string :: proc(hostname_and_port: string, options := default_tcp_options) -> (socket: TCP_Socket, err: Network_Error) {
|
||||
target := parse_hostname_or_endpoint(hostname_and_port) or_return
|
||||
switch t in target {
|
||||
case Endpoint:
|
||||
return dial_tcp_from_endpoint(t, options)
|
||||
case Host:
|
||||
if t.port == 0 {
|
||||
return 0, .Port_Required
|
||||
}
|
||||
ep4, ep6 := resolve(t.hostname) or_return
|
||||
ep := ep4 if ep4.address != nil else ep6 // NOTE(tetra): We don't know what family the server uses, so we just default to IP4.
|
||||
ep.port = t.port
|
||||
return dial_tcp_from_endpoint(ep, options)
|
||||
}
|
||||
unreachable()
|
||||
}
|
||||
|
||||
/*
|
||||
Expects the `hostname` as a string and `port` as a `int`.
|
||||
`parse_hostname_or_endpoint` is called and the `hostname` will be resolved into an IP.
|
||||
|
||||
If a `hostname` of form `a.host.name:9999` is given, the port will be ignored in favor of the explicit `port` param.
|
||||
*/
|
||||
dial_tcp_from_hostname_with_port_override :: proc(hostname: string, port: int, options := default_tcp_options) -> (socket: TCP_Socket, err: Network_Error) {
|
||||
target := parse_hostname_or_endpoint(hostname) or_return
|
||||
switch t in target {
|
||||
case Endpoint:
|
||||
return dial_tcp_from_endpoint({t.address, port}, options)
|
||||
case Host:
|
||||
if port == 0 {
|
||||
return 0, .Port_Required
|
||||
}
|
||||
ep4, ep6 := resolve(t.hostname) or_return
|
||||
ep := ep4 if ep4.address != nil else ep6 // NOTE(tetra): We don't know what family the server uses, so we just default to IP4.
|
||||
ep.port = port
|
||||
return dial_tcp_from_endpoint(ep, options)
|
||||
}
|
||||
unreachable()
|
||||
}
|
||||
|
||||
// Dial from an Address
|
||||
dial_tcp_from_address_and_port :: proc(address: Address, port: int, options := default_tcp_options) -> (socket: TCP_Socket, err: Network_Error) {
|
||||
return dial_tcp_from_endpoint({address, port}, options)
|
||||
}
|
||||
|
||||
dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_options) -> (socket: TCP_Socket, err: Network_Error) {
|
||||
return _dial_tcp_from_endpoint(endpoint, options)
|
||||
}
|
||||
|
||||
dial_tcp :: proc{
|
||||
dial_tcp_from_endpoint,
|
||||
dial_tcp_from_address_and_port,
|
||||
dial_tcp_from_hostname_and_port_string,
|
||||
dial_tcp_from_hostname_with_port_override,
|
||||
}
|
||||
|
||||
create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (socket: Any_Socket, err: Network_Error) {
|
||||
return _create_socket(family, protocol)
|
||||
}
|
||||
|
||||
bind :: proc(socket: Any_Socket, ep: Endpoint) -> (err: Network_Error) {
|
||||
return _bind(socket, ep)
|
||||
}
|
||||
|
||||
/*
|
||||
This type of socket becomes bound when you try to send data.
|
||||
It is likely what you want if you want to send data unsolicited.
|
||||
|
||||
This is like a client TCP socket, except that it can send data to any remote endpoint without needing to establish a connection first.
|
||||
*/
|
||||
make_unbound_udp_socket :: proc(family: Address_Family) -> (socket: UDP_Socket, err: Network_Error) {
|
||||
sock := create_socket(family, .UDP) or_return
|
||||
socket = sock.(UDP_Socket)
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
This type of socket is bound immediately, which enables it to receive data on the port.
|
||||
Since it's UDP, it's also able to send data without receiving any first.
|
||||
|
||||
This is like a listening TCP socket, except that data packets can be sent and received without needing to establish a connection first.
|
||||
The `bound_address` is the address of the network interface that you want to use, or a loopback address if you don't care which to use.
|
||||
*/
|
||||
make_bound_udp_socket :: proc(bound_address: Address, port: int) -> (socket: UDP_Socket, err: Network_Error) {
|
||||
if bound_address == nil {
|
||||
return {}, .Bad_Address
|
||||
}
|
||||
socket = make_unbound_udp_socket(family_from_address(bound_address)) or_return
|
||||
bind(socket, {bound_address, port}) or_return
|
||||
return
|
||||
}
|
||||
|
||||
listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (socket: TCP_Socket, err: Network_Error) {
|
||||
assert(backlog > 0 && backlog < int(max(i32)))
|
||||
|
||||
return _listen_tcp(interface_endpoint, backlog)
|
||||
}
|
||||
|
||||
accept_tcp :: proc(socket: TCP_Socket, options := default_tcp_options) -> (client: TCP_Socket, source: Endpoint, err: Network_Error) {
|
||||
return _accept_tcp(socket, options)
|
||||
}
|
||||
|
||||
close :: proc(socket: Any_Socket) {
|
||||
_close(socket)
|
||||
}
|
||||
|
||||
recv_tcp :: proc(socket: TCP_Socket, buf: []byte) -> (bytes_read: int, err: Network_Error) {
|
||||
return _recv_tcp(socket, buf)
|
||||
}
|
||||
|
||||
recv_udp :: proc(socket: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_endpoint: Endpoint, err: Network_Error) {
|
||||
return _recv_udp(socket, buf)
|
||||
}
|
||||
|
||||
recv :: proc{recv_tcp, recv_udp}
|
||||
|
||||
/*
|
||||
Repeatedly sends data until the entire buffer is sent.
|
||||
If a send fails before all data is sent, returns the amount sent up to that point.
|
||||
*/
|
||||
send_tcp :: proc(socket: TCP_Socket, buf: []byte) -> (bytes_written: int, err: Network_Error) {
|
||||
return _send_tcp(socket, buf)
|
||||
}
|
||||
|
||||
/*
|
||||
Sends a single UDP datagram packet.
|
||||
|
||||
Datagrams are limited in size; attempting to send more than this limit at once will result in a Message_Too_Long error.
|
||||
UDP packets are not guarenteed to be received in order.
|
||||
*/
|
||||
send_udp :: proc(socket: UDP_Socket, buf: []byte, to: Endpoint) -> (bytes_written: int, err: Network_Error) {
|
||||
return _send_udp(socket, buf, to)
|
||||
}
|
||||
|
||||
send :: proc{send_tcp, send_udp}
|
||||
|
||||
shutdown :: proc(socket: Any_Socket, manner: Shutdown_Manner) -> (err: Network_Error) {
|
||||
return _shutdown(socket, manner)
|
||||
}
|
||||
|
||||
set_option :: proc(socket: Any_Socket, option: Socket_Option, value: any, loc := #caller_location) -> Network_Error {
|
||||
return _set_option(socket, option, value, loc)
|
||||
}
|
||||
|
||||
set_blocking :: proc(socket: Any_Socket, should_block: bool) -> (err: Network_Error) {
|
||||
return _set_blocking(socket, should_block)
|
||||
}
|
||||
@@ -0,0 +1,354 @@
|
||||
package net
|
||||
// +build darwin
|
||||
|
||||
/*
|
||||
Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures.
|
||||
For other protocols and their features, see subdirectories of this package.
|
||||
*/
|
||||
|
||||
/*
|
||||
Copyright 2022 Tetralux <tetraluxonpc@gmail.com>
|
||||
Copyright 2022 Colin Davidson <colrdavidson@gmail.com>
|
||||
Copyright 2022 Jeroen van Rijn <nom@duclavier.com>.
|
||||
Made available under Odin's BSD-3 license.
|
||||
|
||||
List of contributors:
|
||||
Tetralux: Initial implementation
|
||||
Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver
|
||||
Jeroen van Rijn: Cross platform unification, code style, documentation
|
||||
*/
|
||||
|
||||
import "core:c"
|
||||
import "core:os"
|
||||
import "core:time"
|
||||
|
||||
Socket_Option :: enum c.int {
|
||||
Reuse_Address = c.int(os.SO_REUSEADDR),
|
||||
Keep_Alive = c.int(os.SO_KEEPALIVE),
|
||||
Out_Of_Bounds_Data_Inline = c.int(os.SO_OOBINLINE),
|
||||
TCP_Nodelay = c.int(os.TCP_NODELAY),
|
||||
Linger = c.int(os.SO_LINGER),
|
||||
Receive_Buffer_Size = c.int(os.SO_RCVBUF),
|
||||
Send_Buffer_Size = c.int(os.SO_SNDBUF),
|
||||
Receive_Timeout = c.int(os.SO_RCVTIMEO),
|
||||
Send_Timeout = c.int(os.SO_SNDTIMEO),
|
||||
}
|
||||
|
||||
@(private)
|
||||
_create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (socket: Any_Socket, err: Network_Error) {
|
||||
c_type, c_protocol, c_family: int
|
||||
|
||||
switch family {
|
||||
case .IP4: c_family = os.AF_INET
|
||||
case .IP6: c_family = os.AF_INET6
|
||||
case:
|
||||
unreachable()
|
||||
}
|
||||
|
||||
switch protocol {
|
||||
case .TCP: c_type = os.SOCK_STREAM; c_protocol = os.IPPROTO_TCP
|
||||
case .UDP: c_type = os.SOCK_DGRAM; c_protocol = os.IPPROTO_UDP
|
||||
case:
|
||||
unreachable()
|
||||
}
|
||||
|
||||
sock, ok := os.socket(c_family, c_type, c_protocol)
|
||||
if ok != os.ERROR_NONE {
|
||||
err = Create_Socket_Error(ok)
|
||||
return
|
||||
}
|
||||
|
||||
switch protocol {
|
||||
case .TCP: return TCP_Socket(sock), nil
|
||||
case .UDP: return UDP_Socket(sock), nil
|
||||
case:
|
||||
unreachable()
|
||||
}
|
||||
}
|
||||
|
||||
@(private)
|
||||
_dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_options) -> (skt: TCP_Socket, err: Network_Error) {
|
||||
if endpoint.port == 0 {
|
||||
return 0, .Port_Required
|
||||
}
|
||||
|
||||
family := family_from_endpoint(endpoint)
|
||||
sock := create_socket(family, .TCP) or_return
|
||||
skt = sock.(TCP_Socket)
|
||||
|
||||
// NOTE(tetra): This is so that if we crash while the socket is open, we can
|
||||
// bypass the cooldown period, and allow the next run of the program to
|
||||
// use the same address immediately.
|
||||
_ = set_option(skt, .Reuse_Address, true)
|
||||
|
||||
sockaddr := _endpoint_to_sockaddr(endpoint)
|
||||
res := os.connect(os.Socket(skt), (^os.SOCKADDR)(&sockaddr), i32(sockaddr.len))
|
||||
if res != os.ERROR_NONE {
|
||||
err = Dial_Error(res)
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@(private)
|
||||
_bind :: proc(skt: Any_Socket, ep: Endpoint) -> (err: Network_Error) {
|
||||
sockaddr := _endpoint_to_sockaddr(ep)
|
||||
s := any_socket_to_socket(skt)
|
||||
res := os.bind(os.Socket(s), (^os.SOCKADDR)(&sockaddr), i32(sockaddr.len))
|
||||
if res != os.ERROR_NONE {
|
||||
err = Bind_Error(res)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@(private)
|
||||
_listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (skt: TCP_Socket, err: Network_Error) {
|
||||
assert(backlog > 0 && i32(backlog) < max(i32))
|
||||
|
||||
family := family_from_endpoint(interface_endpoint)
|
||||
sock := create_socket(family, .TCP) or_return
|
||||
skt = sock.(TCP_Socket)
|
||||
|
||||
// NOTE(tetra): This is so that if we crash while the socket is open, we can
|
||||
// bypass the cooldown period, and allow the next run of the program to
|
||||
// use the same address immediately.
|
||||
//
|
||||
// TODO(tetra, 2022-02-15): Confirm that this doesn't mean other processes can hijack the address!
|
||||
set_option(sock, .Reuse_Address, true) or_return
|
||||
|
||||
bind(sock, interface_endpoint) or_return
|
||||
|
||||
res := os.listen(os.Socket(skt), backlog)
|
||||
if res != os.ERROR_NONE {
|
||||
err = Listen_Error(res)
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@(private)
|
||||
_accept_tcp :: proc(sock: TCP_Socket, options := default_tcp_options) -> (client: TCP_Socket, source: Endpoint, err: Network_Error) {
|
||||
sockaddr: os.SOCKADDR_STORAGE_LH
|
||||
sockaddrlen := c.int(size_of(sockaddr))
|
||||
|
||||
client_sock, ok := os.accept(os.Socket(sock), cast(^os.SOCKADDR) &sockaddr, &sockaddrlen)
|
||||
if ok != os.ERROR_NONE {
|
||||
err = Accept_Error(ok)
|
||||
return
|
||||
}
|
||||
client = TCP_Socket(client_sock)
|
||||
source = _sockaddr_to_endpoint(&sockaddr)
|
||||
return
|
||||
}
|
||||
|
||||
@(private)
|
||||
_close :: proc(skt: Any_Socket) {
|
||||
s := any_socket_to_socket(skt)
|
||||
os.close(os.Handle(os.Socket(s)))
|
||||
}
|
||||
|
||||
@(private)
|
||||
_recv_tcp :: proc(skt: TCP_Socket, buf: []byte) -> (bytes_read: int, err: Network_Error) {
|
||||
if len(buf) <= 0 {
|
||||
return
|
||||
}
|
||||
res, ok := os.recv(os.Socket(skt), buf, 0)
|
||||
if ok != os.ERROR_NONE {
|
||||
err = TCP_Recv_Error(ok)
|
||||
return
|
||||
}
|
||||
return int(res), nil
|
||||
}
|
||||
|
||||
@(private)
|
||||
_recv_udp :: proc(skt: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_endpoint: Endpoint, err: Network_Error) {
|
||||
if len(buf) <= 0 {
|
||||
return
|
||||
}
|
||||
|
||||
from: os.SOCKADDR_STORAGE_LH
|
||||
fromsize := c.int(size_of(from))
|
||||
res, ok := os.recvfrom(os.Socket(skt), buf, 0, cast(^os.SOCKADDR) &from, &fromsize)
|
||||
if ok != os.ERROR_NONE {
|
||||
err = UDP_Recv_Error(ok)
|
||||
return
|
||||
}
|
||||
|
||||
bytes_read = int(res)
|
||||
remote_endpoint = _sockaddr_to_endpoint(&from)
|
||||
return
|
||||
}
|
||||
|
||||
@(private)
|
||||
_send_tcp :: proc(skt: TCP_Socket, buf: []byte) -> (bytes_written: int, err: Network_Error) {
|
||||
for bytes_written < len(buf) {
|
||||
limit := min(int(max(i32)), len(buf) - bytes_written)
|
||||
remaining := buf[bytes_written:][:limit]
|
||||
res, ok := os.send(os.Socket(skt), remaining, 0)
|
||||
if ok != os.ERROR_NONE {
|
||||
err = TCP_Send_Error(ok)
|
||||
return
|
||||
}
|
||||
bytes_written += int(res)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@(private)
|
||||
_send_udp :: proc(skt: UDP_Socket, buf: []byte, to: Endpoint) -> (bytes_written: int, err: Network_Error) {
|
||||
toaddr := _endpoint_to_sockaddr(to)
|
||||
for bytes_written < len(buf) {
|
||||
limit := min(1<<31, len(buf) - bytes_written)
|
||||
remaining := buf[bytes_written:][:limit]
|
||||
res, ok := os.sendto(os.Socket(skt), remaining, 0, cast(^os.SOCKADDR)&toaddr, i32(toaddr.len))
|
||||
if ok != os.ERROR_NONE {
|
||||
err = UDP_Send_Error(ok)
|
||||
return
|
||||
}
|
||||
bytes_written += int(res)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@(private)
|
||||
_shutdown :: proc(skt: Any_Socket, manner: Shutdown_Manner) -> (err: Network_Error) {
|
||||
s := any_socket_to_socket(skt)
|
||||
res := os.shutdown(os.Socket(s), int(manner))
|
||||
if res != os.ERROR_NONE {
|
||||
return Shutdown_Error(res)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@(private)
|
||||
_set_option :: proc(s: Any_Socket, option: Socket_Option, value: any, loc := #caller_location) -> Network_Error {
|
||||
level := os.SOL_SOCKET if option != .TCP_Nodelay else os.IPPROTO_TCP
|
||||
|
||||
// NOTE(tetra, 2022-02-15): On Linux, you cannot merely give a single byte for a bool;
|
||||
// it _has_ to be a b32.
|
||||
// I haven't tested if you can give more than that.
|
||||
bool_value: b32
|
||||
int_value: i32
|
||||
timeval_value: os.Timeval
|
||||
|
||||
ptr: rawptr
|
||||
len: os.socklen_t
|
||||
|
||||
switch option {
|
||||
case
|
||||
.Reuse_Address,
|
||||
.Keep_Alive,
|
||||
.Out_Of_Bounds_Data_Inline,
|
||||
.TCP_Nodelay:
|
||||
// TODO: verify whether these are options or not on Linux
|
||||
// .Broadcast,
|
||||
// .Conditional_Accept,
|
||||
// .Dont_Linger:
|
||||
switch x in value {
|
||||
case bool, b8:
|
||||
x2 := x
|
||||
bool_value = b32((^bool)(&x2)^)
|
||||
case b16:
|
||||
bool_value = b32(x)
|
||||
case b32:
|
||||
bool_value = b32(x)
|
||||
case b64:
|
||||
bool_value = b32(x)
|
||||
case:
|
||||
panic("set_option() value must be a boolean here", loc)
|
||||
}
|
||||
ptr = &bool_value
|
||||
len = size_of(bool_value)
|
||||
case
|
||||
.Linger,
|
||||
.Send_Timeout,
|
||||
.Receive_Timeout:
|
||||
t, ok := value.(time.Duration)
|
||||
if !ok do panic("set_option() value must be a time.Duration here", loc)
|
||||
|
||||
nanos := time.duration_nanoseconds(t)
|
||||
timeval_value.nanoseconds = int(nanos % 1e9)
|
||||
timeval_value.seconds = (nanos - i64(timeval_value.nanoseconds)) / 1e9
|
||||
|
||||
ptr = &timeval_value
|
||||
len = size_of(timeval_value)
|
||||
case
|
||||
.Receive_Buffer_Size,
|
||||
.Send_Buffer_Size:
|
||||
// TODO: check for out of range values and return .Value_Out_Of_Range?
|
||||
switch i in value {
|
||||
case i8, u8: i2 := i; int_value = os.socklen_t((^u8)(&i2)^)
|
||||
case i16, u16: i2 := i; int_value = os.socklen_t((^u16)(&i2)^)
|
||||
case i32, u32: i2 := i; int_value = os.socklen_t((^u32)(&i2)^)
|
||||
case i64, u64: i2 := i; int_value = os.socklen_t((^u64)(&i2)^)
|
||||
case i128, u128: i2 := i; int_value = os.socklen_t((^u128)(&i2)^)
|
||||
case int, uint: i2 := i; int_value = os.socklen_t((^uint)(&i2)^)
|
||||
case:
|
||||
panic("set_option() value must be an integer here", loc)
|
||||
}
|
||||
ptr = &int_value
|
||||
len = size_of(int_value)
|
||||
}
|
||||
|
||||
skt := any_socket_to_socket(s)
|
||||
res := os.setsockopt(os.Socket(skt), int(level), int(option), ptr, len)
|
||||
if res != os.ERROR_NONE {
|
||||
return Socket_Option_Error(res)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@(private)
|
||||
_set_blocking :: proc(socket: Any_Socket, should_block: bool) -> (err: Network_Error) {
|
||||
// TODO: Implement
|
||||
unimplemented()
|
||||
}
|
||||
|
||||
@private
|
||||
_endpoint_to_sockaddr :: proc(ep: Endpoint) -> (sockaddr: os.SOCKADDR_STORAGE_LH) {
|
||||
switch a in ep.address {
|
||||
case IP4_Address:
|
||||
(^os.sockaddr_in)(&sockaddr)^ = os.sockaddr_in {
|
||||
sin_port = u16be(ep.port),
|
||||
sin_addr = transmute(os.in_addr) a,
|
||||
sin_family = u8(os.AF_INET),
|
||||
sin_len = size_of(os.sockaddr_in),
|
||||
}
|
||||
return
|
||||
case IP6_Address:
|
||||
(^os.sockaddr_in6)(&sockaddr)^ = os.sockaddr_in6 {
|
||||
sin6_port = u16be(ep.port),
|
||||
sin6_addr = transmute(os.in6_addr) a,
|
||||
sin6_family = u8(os.AF_INET6),
|
||||
sin6_len = size_of(os.sockaddr_in6),
|
||||
}
|
||||
return
|
||||
}
|
||||
unreachable()
|
||||
}
|
||||
|
||||
@private
|
||||
_sockaddr_to_endpoint :: proc(native_addr: ^os.SOCKADDR_STORAGE_LH) -> (ep: Endpoint) {
|
||||
switch native_addr.family {
|
||||
case u8(os.AF_INET):
|
||||
addr := cast(^os.sockaddr_in) native_addr
|
||||
port := int(addr.sin_port)
|
||||
ep = Endpoint {
|
||||
address = IP4_Address(transmute([4]byte) addr.sin_addr),
|
||||
port = port,
|
||||
}
|
||||
case u8(os.AF_INET6):
|
||||
addr := cast(^os.sockaddr_in6) native_addr
|
||||
port := int(addr.sin6_port)
|
||||
ep = Endpoint {
|
||||
address = IP6_Address(transmute([8]u16be) addr.sin6_addr),
|
||||
port = port,
|
||||
}
|
||||
case:
|
||||
panic("native_addr is neither IP4 or IP6 address")
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,407 @@
|
||||
package net
|
||||
// +build linux
|
||||
|
||||
/*
|
||||
Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures.
|
||||
For other protocols and their features, see subdirectories of this package.
|
||||
*/
|
||||
|
||||
/*
|
||||
Copyright 2022 Tetralux <tetraluxonpc@gmail.com>
|
||||
Copyright 2022 Colin Davidson <colrdavidson@gmail.com>
|
||||
Copyright 2022 Jeroen van Rijn <nom@duclavier.com>.
|
||||
Made available under Odin's BSD-3 license.
|
||||
|
||||
List of contributors:
|
||||
Tetralux: Initial implementation
|
||||
Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver
|
||||
Jeroen van Rijn: Cross platform unification, code style, documentation
|
||||
*/
|
||||
|
||||
import "core:c"
|
||||
import "core:os"
|
||||
import "core:time"
|
||||
|
||||
Socket_Option :: enum c.int {
|
||||
Reuse_Address = c.int(os.SO_REUSEADDR),
|
||||
Keep_Alive = c.int(os.SO_KEEPALIVE),
|
||||
Out_Of_Bounds_Data_Inline = c.int(os.SO_OOBINLINE),
|
||||
TCP_Nodelay = c.int(os.TCP_NODELAY),
|
||||
Linger = c.int(os.SO_LINGER),
|
||||
Receive_Buffer_Size = c.int(os.SO_RCVBUF),
|
||||
Send_Buffer_Size = c.int(os.SO_SNDBUF),
|
||||
Receive_Timeout = c.int(os.SO_RCVTIMEO_NEW),
|
||||
Send_Timeout = c.int(os.SO_SNDTIMEO_NEW),
|
||||
}
|
||||
|
||||
@(private)
|
||||
_create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (socket: Any_Socket, err: Network_Error) {
|
||||
c_type, c_protocol, c_family: int
|
||||
|
||||
switch family {
|
||||
case .IP4: c_family = os.AF_INET
|
||||
case .IP6: c_family = os.AF_INET6
|
||||
case:
|
||||
unreachable()
|
||||
}
|
||||
|
||||
switch protocol {
|
||||
case .TCP: c_type = os.SOCK_STREAM; c_protocol = os.IPPROTO_TCP
|
||||
case .UDP: c_type = os.SOCK_DGRAM; c_protocol = os.IPPROTO_UDP
|
||||
case:
|
||||
unreachable()
|
||||
}
|
||||
|
||||
sock, ok := os.socket(c_family, c_type, c_protocol)
|
||||
if ok != os.ERROR_NONE {
|
||||
err = Create_Socket_Error(ok)
|
||||
return
|
||||
}
|
||||
|
||||
switch protocol {
|
||||
case .TCP: return TCP_Socket(sock), nil
|
||||
case .UDP: return UDP_Socket(sock), nil
|
||||
case:
|
||||
unreachable()
|
||||
}
|
||||
}
|
||||
|
||||
@(private)
|
||||
_dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_options) -> (skt: TCP_Socket, err: Network_Error) {
|
||||
if endpoint.port == 0 {
|
||||
return 0, .Port_Required
|
||||
}
|
||||
|
||||
family := family_from_endpoint(endpoint)
|
||||
sock := create_socket(family, .TCP) or_return
|
||||
skt = sock.(TCP_Socket)
|
||||
|
||||
// NOTE(tetra): This is so that if we crash while the socket is open, we can
|
||||
// bypass the cooldown period, and allow the next run of the program to
|
||||
// use the same address immediately.
|
||||
_ = set_option(skt, .Reuse_Address, true)
|
||||
|
||||
sockaddr := _endpoint_to_sockaddr(endpoint)
|
||||
res := os.connect(os.Socket(skt), (^os.SOCKADDR)(&sockaddr), size_of(sockaddr))
|
||||
if res != os.ERROR_NONE {
|
||||
err = Dial_Error(res)
|
||||
return
|
||||
}
|
||||
|
||||
if options.no_delay {
|
||||
_ = _set_option(sock, .TCP_Nodelay, true) // NOTE(tetra): Not vital to succeed; error ignored
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@(private)
|
||||
_bind :: proc(skt: Any_Socket, ep: Endpoint) -> (err: Network_Error) {
|
||||
sockaddr := _endpoint_to_sockaddr(ep)
|
||||
s := any_socket_to_socket(skt)
|
||||
res := os.bind(os.Socket(s), (^os.SOCKADDR)(&sockaddr), size_of(sockaddr))
|
||||
if res != os.ERROR_NONE {
|
||||
err = Bind_Error(res)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@(private)
|
||||
_listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (skt: TCP_Socket, err: Network_Error) {
|
||||
assert(backlog > 0 && i32(backlog) < max(i32))
|
||||
|
||||
family := family_from_endpoint(interface_endpoint)
|
||||
sock := create_socket(family, .TCP) or_return
|
||||
skt = sock.(TCP_Socket)
|
||||
|
||||
// NOTE(tetra): This is so that if we crash while the socket is open, we can
|
||||
// bypass the cooldown period, and allow the next run of the program to
|
||||
// use the same address immediately.
|
||||
//
|
||||
// TODO(tetra, 2022-02-15): Confirm that this doesn't mean other processes can hijack the address!
|
||||
set_option(sock, .Reuse_Address, true) or_return
|
||||
|
||||
bind(sock, interface_endpoint) or_return
|
||||
|
||||
res := os.listen(os.Socket(skt), backlog)
|
||||
if res != os.ERROR_NONE {
|
||||
err = Listen_Error(res)
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@(private)
|
||||
_accept_tcp :: proc(sock: TCP_Socket, options := default_tcp_options) -> (client: TCP_Socket, source: Endpoint, err: Network_Error) {
|
||||
sockaddr: os.SOCKADDR_STORAGE_LH
|
||||
sockaddrlen := c.int(size_of(sockaddr))
|
||||
|
||||
client_sock, ok := os.accept(os.Socket(sock), cast(^os.SOCKADDR) &sockaddr, &sockaddrlen)
|
||||
if ok != os.ERROR_NONE {
|
||||
err = Accept_Error(ok)
|
||||
return
|
||||
}
|
||||
client = TCP_Socket(client_sock)
|
||||
source = _sockaddr_storage_to_endpoint(&sockaddr)
|
||||
if options.no_delay {
|
||||
_ = _set_option(client, .TCP_Nodelay, true) // NOTE(tetra): Not vital to succeed; error ignored
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@(private)
|
||||
_close :: proc(skt: Any_Socket) {
|
||||
s := any_socket_to_socket(skt)
|
||||
os.close(os.Handle(os.Socket(s)))
|
||||
}
|
||||
|
||||
@(private)
|
||||
_recv_tcp :: proc(skt: TCP_Socket, buf: []byte) -> (bytes_read: int, err: Network_Error) {
|
||||
if len(buf) <= 0 {
|
||||
return
|
||||
}
|
||||
res, ok := os.recv(os.Socket(skt), buf, 0)
|
||||
if ok != os.ERROR_NONE {
|
||||
err = TCP_Recv_Error(ok)
|
||||
return
|
||||
}
|
||||
return int(res), nil
|
||||
}
|
||||
|
||||
@(private)
|
||||
_recv_udp :: proc(skt: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_endpoint: Endpoint, err: Network_Error) {
|
||||
if len(buf) <= 0 {
|
||||
return
|
||||
}
|
||||
|
||||
from: os.SOCKADDR_STORAGE_LH = ---
|
||||
fromsize := c.int(size_of(from))
|
||||
|
||||
// NOTE(tetra): On Linux, if the buffer is too small to fit the entire datagram payload, the rest is silently discarded,
|
||||
// and no error is returned.
|
||||
// However, if you pass MSG_TRUNC here, 'res' will be the size of the incoming message, rather than how much was read.
|
||||
// We can use this fact to detect this condition and return .Buffer_Too_Small.
|
||||
res, ok := os.recvfrom(os.Socket(skt), buf, os.MSG_TRUNC, cast(^os.SOCKADDR) &from, &fromsize)
|
||||
if ok != os.ERROR_NONE {
|
||||
err = UDP_Recv_Error(ok)
|
||||
return
|
||||
}
|
||||
|
||||
bytes_read = int(res)
|
||||
remote_endpoint = _sockaddr_storage_to_endpoint(&from)
|
||||
|
||||
if bytes_read > len(buf) {
|
||||
// NOTE(tetra): The buffer has been filled, with a partial message.
|
||||
bytes_read = len(buf)
|
||||
err = .Buffer_Too_Small
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@(private)
|
||||
_send_tcp :: proc(skt: TCP_Socket, buf: []byte) -> (bytes_written: int, err: Network_Error) {
|
||||
for bytes_written < len(buf) {
|
||||
limit := min(int(max(i32)), len(buf) - bytes_written)
|
||||
remaining := buf[bytes_written:][:limit]
|
||||
res, ok := os.send(os.Socket(skt), remaining, 0)
|
||||
if ok != os.ERROR_NONE {
|
||||
err = TCP_Send_Error(ok)
|
||||
return
|
||||
}
|
||||
bytes_written += int(res)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@(private)
|
||||
_send_udp :: proc(skt: UDP_Socket, buf: []byte, to: Endpoint) -> (bytes_written: int, err: Network_Error) {
|
||||
toaddr := _endpoint_to_sockaddr(to)
|
||||
res, os_err := os.sendto(os.Socket(skt), buf, 0, cast(^os.SOCKADDR) &toaddr, size_of(toaddr))
|
||||
if os_err != os.ERROR_NONE {
|
||||
err = UDP_Send_Error(os_err)
|
||||
return
|
||||
}
|
||||
bytes_written = int(res)
|
||||
return
|
||||
}
|
||||
|
||||
@(private)
|
||||
_shutdown :: proc(skt: Any_Socket, manner: Shutdown_Manner) -> (err: Network_Error) {
|
||||
s := any_socket_to_socket(skt)
|
||||
res := os.shutdown(os.Socket(s), int(manner))
|
||||
if res != os.ERROR_NONE {
|
||||
return Shutdown_Error(res)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@(private)
|
||||
_set_option :: proc(s: Any_Socket, option: Socket_Option, value: any, loc := #caller_location) -> Network_Error {
|
||||
level := os.SOL_SOCKET if option != .TCP_Nodelay else os.IPPROTO_TCP
|
||||
|
||||
// NOTE(tetra, 2022-02-15): On Linux, you cannot merely give a single byte for a bool;
|
||||
// it _has_ to be a b32.
|
||||
// I haven't tested if you can give more than that.
|
||||
bool_value: b32
|
||||
int_value: i32
|
||||
timeval_value: os.Timeval
|
||||
|
||||
ptr: rawptr
|
||||
len: os.socklen_t
|
||||
|
||||
switch option {
|
||||
case
|
||||
.Reuse_Address,
|
||||
.Keep_Alive,
|
||||
.Out_Of_Bounds_Data_Inline,
|
||||
.TCP_Nodelay:
|
||||
// TODO: verify whether these are options or not on Linux
|
||||
// .Broadcast,
|
||||
// .Conditional_Accept,
|
||||
// .Dont_Linger:
|
||||
switch x in value {
|
||||
case bool, b8:
|
||||
x2 := x
|
||||
bool_value = b32((^bool)(&x2)^)
|
||||
case b16:
|
||||
bool_value = b32(x)
|
||||
case b32:
|
||||
bool_value = b32(x)
|
||||
case b64:
|
||||
bool_value = b32(x)
|
||||
case:
|
||||
panic("set_option() value must be a boolean here", loc)
|
||||
}
|
||||
ptr = &bool_value
|
||||
len = size_of(bool_value)
|
||||
case
|
||||
.Linger,
|
||||
.Send_Timeout,
|
||||
.Receive_Timeout:
|
||||
t, ok := value.(time.Duration)
|
||||
if !ok do panic("set_option() value must be a time.Duration here", loc)
|
||||
|
||||
nanos := time.duration_nanoseconds(t)
|
||||
timeval_value.nanoseconds = int(nanos % 1e9)
|
||||
timeval_value.seconds = (nanos - i64(timeval_value.nanoseconds)) / 1e9
|
||||
|
||||
ptr = &timeval_value
|
||||
len = size_of(timeval_value)
|
||||
case
|
||||
.Receive_Buffer_Size,
|
||||
.Send_Buffer_Size:
|
||||
// TODO: check for out of range values and return .Value_Out_Of_Range?
|
||||
switch i in value {
|
||||
case i8, u8: i2 := i; int_value = os.socklen_t((^u8)(&i2)^)
|
||||
case i16, u16: i2 := i; int_value = os.socklen_t((^u16)(&i2)^)
|
||||
case i32, u32: i2 := i; int_value = os.socklen_t((^u32)(&i2)^)
|
||||
case i64, u64: i2 := i; int_value = os.socklen_t((^u64)(&i2)^)
|
||||
case i128, u128: i2 := i; int_value = os.socklen_t((^u128)(&i2)^)
|
||||
case int, uint: i2 := i; int_value = os.socklen_t((^uint)(&i2)^)
|
||||
case:
|
||||
panic("set_option() value must be an integer here", loc)
|
||||
}
|
||||
ptr = &int_value
|
||||
len = size_of(int_value)
|
||||
}
|
||||
|
||||
skt := any_socket_to_socket(s)
|
||||
res := os.setsockopt(os.Socket(skt), int(level), int(option), ptr, len)
|
||||
if res != os.ERROR_NONE {
|
||||
return Socket_Option_Error(res)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@(private)
|
||||
_set_blocking :: proc(socket: Any_Socket, should_block: bool) -> (err: Network_Error) {
|
||||
socket := any_socket_to_socket(socket)
|
||||
|
||||
flags, getfl_err := os.fcntl(int(socket), os.F_GETFL, 0)
|
||||
if getfl_err != os.ERROR_NONE {
|
||||
return Set_Blocking_Error(getfl_err)
|
||||
}
|
||||
|
||||
if should_block {
|
||||
flags &= ~int(os.O_NONBLOCK)
|
||||
} else {
|
||||
flags |= int(os.O_NONBLOCK)
|
||||
}
|
||||
|
||||
_, setfl_err := os.fcntl(int(socket), os.F_SETFL, flags)
|
||||
if setfl_err != os.ERROR_NONE {
|
||||
return Set_Blocking_Error(setfl_err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@(private)
|
||||
_endpoint_to_sockaddr :: proc(ep: Endpoint) -> (sockaddr: os.SOCKADDR_STORAGE_LH) {
|
||||
switch a in ep.address {
|
||||
case IP4_Address:
|
||||
(^os.sockaddr_in)(&sockaddr)^ = os.sockaddr_in {
|
||||
sin_port = u16be(ep.port),
|
||||
sin_addr = transmute(os.in_addr) a,
|
||||
sin_family = u16(os.AF_INET),
|
||||
}
|
||||
return
|
||||
case IP6_Address:
|
||||
(^os.sockaddr_in6)(&sockaddr)^ = os.sockaddr_in6 {
|
||||
sin6_port = u16be(ep.port),
|
||||
sin6_addr = transmute(os.in6_addr) a,
|
||||
sin6_family = u16(os.AF_INET6),
|
||||
}
|
||||
return
|
||||
}
|
||||
unreachable()
|
||||
}
|
||||
|
||||
@(private)
|
||||
_sockaddr_storage_to_endpoint :: proc(native_addr: ^os.SOCKADDR_STORAGE_LH) -> (ep: Endpoint) {
|
||||
switch native_addr.ss_family {
|
||||
case u16(os.AF_INET):
|
||||
addr := cast(^os.sockaddr_in) native_addr
|
||||
port := int(addr.sin_port)
|
||||
ep = Endpoint {
|
||||
address = IP4_Address(transmute([4]byte) addr.sin_addr),
|
||||
port = port,
|
||||
}
|
||||
case u16(os.AF_INET6):
|
||||
addr := cast(^os.sockaddr_in6) native_addr
|
||||
port := int(addr.sin6_port)
|
||||
ep = Endpoint {
|
||||
address = IP6_Address(transmute([8]u16be) addr.sin6_addr),
|
||||
port = port,
|
||||
}
|
||||
case:
|
||||
panic("native_addr is neither IP4 or IP6 address")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@(private)
|
||||
_sockaddr_basic_to_endpoint :: proc(native_addr: ^os.SOCKADDR) -> (ep: Endpoint) {
|
||||
switch native_addr.sa_family {
|
||||
case u16(os.AF_INET):
|
||||
addr := cast(^os.sockaddr_in) native_addr
|
||||
port := int(addr.sin_port)
|
||||
ep = Endpoint {
|
||||
address = IP4_Address(transmute([4]byte) addr.sin_addr),
|
||||
port = port,
|
||||
}
|
||||
case u16(os.AF_INET6):
|
||||
addr := cast(^os.sockaddr_in6) native_addr
|
||||
port := int(addr.sin6_port)
|
||||
ep = Endpoint {
|
||||
address = IP6_Address(transmute([8]u16be) addr.sin6_addr),
|
||||
port = port,
|
||||
}
|
||||
case:
|
||||
panic("native_addr is neither IP4 or IP6 address")
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,367 @@
|
||||
package net
|
||||
// +build windows
|
||||
|
||||
/*
|
||||
Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures.
|
||||
For other protocols and their features, see subdirectories of this package.
|
||||
*/
|
||||
|
||||
/*
|
||||
Copyright 2022 Tetralux <tetraluxonpc@gmail.com>
|
||||
Copyright 2022 Colin Davidson <colrdavidson@gmail.com>
|
||||
Copyright 2022 Jeroen van Rijn <nom@duclavier.com>.
|
||||
Made available under Odin's BSD-3 license.
|
||||
|
||||
List of contributors:
|
||||
Tetralux: Initial implementation
|
||||
Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver
|
||||
Jeroen van Rijn: Cross platform unification, code style, documentation
|
||||
*/
|
||||
|
||||
import "core:c"
|
||||
import win "core:sys/windows"
|
||||
import "core:time"
|
||||
|
||||
@(init, private)
|
||||
ensure_winsock_initialized :: proc() {
|
||||
win.ensure_winsock_initialized()
|
||||
}
|
||||
|
||||
@(private)
|
||||
_create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (socket: Any_Socket, err: Network_Error) {
|
||||
c_type, c_protocol, c_family: c.int
|
||||
|
||||
switch family {
|
||||
case .IP4: c_family = win.AF_INET
|
||||
case .IP6: c_family = win.AF_INET6
|
||||
case:
|
||||
unreachable()
|
||||
}
|
||||
|
||||
switch protocol {
|
||||
case .TCP: c_type = win.SOCK_STREAM; c_protocol = win.IPPROTO_TCP
|
||||
case .UDP: c_type = win.SOCK_DGRAM; c_protocol = win.IPPROTO_UDP
|
||||
case:
|
||||
unreachable()
|
||||
}
|
||||
|
||||
sock := win.socket(c_family, c_type, c_protocol)
|
||||
if sock == win.INVALID_SOCKET {
|
||||
err = Create_Socket_Error(win.WSAGetLastError())
|
||||
return
|
||||
}
|
||||
|
||||
switch protocol {
|
||||
case .TCP: return TCP_Socket(sock), nil
|
||||
case .UDP: return UDP_Socket(sock), nil
|
||||
case:
|
||||
unreachable()
|
||||
}
|
||||
}
|
||||
|
||||
@(private)
|
||||
_dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_options) -> (socket: TCP_Socket, err: Network_Error) {
|
||||
if endpoint.port == 0 {
|
||||
err = .Port_Required
|
||||
return
|
||||
}
|
||||
|
||||
family := family_from_endpoint(endpoint)
|
||||
sock := create_socket(family, .TCP) or_return
|
||||
socket = sock.(TCP_Socket)
|
||||
|
||||
// NOTE(tetra): This is so that if we crash while the socket is open, we can
|
||||
// bypass the cooldown period, and allow the next run of the program to
|
||||
// use the same address immediately.
|
||||
_ = set_option(socket, .Reuse_Address, true)
|
||||
|
||||
sockaddr := _endpoint_to_sockaddr(endpoint)
|
||||
res := win.connect(win.SOCKET(socket), &sockaddr, size_of(sockaddr))
|
||||
if res < 0 {
|
||||
err = Dial_Error(win.WSAGetLastError())
|
||||
return
|
||||
}
|
||||
|
||||
if options.no_delay {
|
||||
_ = set_option(sock, .TCP_Nodelay, true) // NOTE(tetra): Not vital to succeed; error ignored
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@(private)
|
||||
_bind :: proc(socket: Any_Socket, ep: Endpoint) -> (err: Network_Error) {
|
||||
sockaddr := _endpoint_to_sockaddr(ep)
|
||||
sock := any_socket_to_socket(socket)
|
||||
res := win.bind(win.SOCKET(sock), &sockaddr, size_of(sockaddr))
|
||||
if res < 0 {
|
||||
err = Bind_Error(win.WSAGetLastError())
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@(private)
|
||||
_listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (socket: TCP_Socket, err: Network_Error) {
|
||||
family := family_from_endpoint(interface_endpoint)
|
||||
sock := create_socket(family, .TCP) or_return
|
||||
socket = sock.(TCP_Socket)
|
||||
|
||||
// NOTE(tetra): While I'm not 100% clear on it, my understanding is that this will
|
||||
// prevent hijacking of the server's endpoint by other applications.
|
||||
set_option(socket, .Exclusive_Addr_Use, true) or_return
|
||||
|
||||
bind(sock, interface_endpoint) or_return
|
||||
|
||||
if res := win.listen(win.SOCKET(socket), i32(backlog)); res == win.SOCKET_ERROR {
|
||||
err = Listen_Error(win.WSAGetLastError())
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@(private)
|
||||
_accept_tcp :: proc(sock: TCP_Socket, options := default_tcp_options) -> (client: TCP_Socket, source: Endpoint, err: Network_Error) {
|
||||
for {
|
||||
sockaddr: win.SOCKADDR_STORAGE_LH
|
||||
sockaddrlen := c.int(size_of(sockaddr))
|
||||
client_sock := win.accept(win.SOCKET(sock), &sockaddr, &sockaddrlen)
|
||||
if int(client_sock) == win.SOCKET_ERROR {
|
||||
e := win.WSAGetLastError()
|
||||
if e == win.WSAECONNRESET {
|
||||
// NOTE(tetra): Reset just means that a client that connection immediately lost the connection.
|
||||
// There's no need to concern the user with this, so we handle it for them.
|
||||
// On Linux, this error isn't possible in the first place according the man pages, so we also
|
||||
// can do this to match the behaviour.
|
||||
continue
|
||||
}
|
||||
err = Accept_Error(e)
|
||||
return
|
||||
}
|
||||
client = TCP_Socket(client_sock)
|
||||
source = _sockaddr_to_endpoint(&sockaddr)
|
||||
if options.no_delay {
|
||||
_ = set_option(client, .TCP_Nodelay, true) // NOTE(tetra): Not vital to succeed; error ignored
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@(private)
|
||||
_close :: proc(socket: Any_Socket) {
|
||||
if s := any_socket_to_socket(socket); s != {} {
|
||||
win.closesocket(win.SOCKET(s))
|
||||
}
|
||||
}
|
||||
|
||||
@(private)
|
||||
_recv_tcp :: proc(socket: TCP_Socket, buf: []byte) -> (bytes_read: int, err: Network_Error) {
|
||||
if len(buf) <= 0 {
|
||||
return
|
||||
}
|
||||
res := win.recv(win.SOCKET(socket), raw_data(buf), c.int(len(buf)), 0)
|
||||
if res < 0 {
|
||||
err = TCP_Recv_Error(win.WSAGetLastError())
|
||||
return
|
||||
}
|
||||
return int(res), nil
|
||||
}
|
||||
|
||||
@(private)
|
||||
_recv_udp :: proc(socket: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_endpoint: Endpoint, err: Network_Error) {
|
||||
if len(buf) <= 0 {
|
||||
return
|
||||
}
|
||||
|
||||
from: win.SOCKADDR_STORAGE_LH
|
||||
fromsize := c.int(size_of(from))
|
||||
res := win.recvfrom(win.SOCKET(socket), raw_data(buf), c.int(len(buf)), 0, &from, &fromsize)
|
||||
if res < 0 {
|
||||
err = UDP_Recv_Error(win.WSAGetLastError())
|
||||
return
|
||||
}
|
||||
|
||||
bytes_read = int(res)
|
||||
remote_endpoint = _sockaddr_to_endpoint(&from)
|
||||
return
|
||||
}
|
||||
|
||||
@(private)
|
||||
_send_tcp :: proc(socket: TCP_Socket, buf: []byte) -> (bytes_written: int, err: Network_Error) {
|
||||
for bytes_written < len(buf) {
|
||||
limit := min(int(max(i32)), len(buf) - bytes_written)
|
||||
remaining := buf[bytes_written:]
|
||||
res := win.send(win.SOCKET(socket), raw_data(remaining), c.int(limit), 0)
|
||||
if res < 0 {
|
||||
err = TCP_Send_Error(win.WSAGetLastError())
|
||||
return
|
||||
}
|
||||
bytes_written += int(res)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@(private)
|
||||
_send_udp :: proc(socket: UDP_Socket, buf: []byte, to: Endpoint) -> (bytes_written: int, err: Network_Error) {
|
||||
if len(buf) > int(max(c.int)) {
|
||||
// NOTE(tetra): If we don't guard this, we'll return (0, nil) instead, which is misleading.
|
||||
err = .Message_Too_Long
|
||||
return
|
||||
}
|
||||
toaddr := _endpoint_to_sockaddr(to)
|
||||
res := win.sendto(win.SOCKET(socket), raw_data(buf), c.int(len(buf)), 0, &toaddr, size_of(toaddr))
|
||||
if res < 0 {
|
||||
err = UDP_Send_Error(win.WSAGetLastError())
|
||||
return
|
||||
}
|
||||
bytes_written = int(res)
|
||||
return
|
||||
}
|
||||
|
||||
@(private)
|
||||
_shutdown :: proc(socket: Any_Socket, manner: Shutdown_Manner) -> (err: Network_Error) {
|
||||
s := any_socket_to_socket(socket)
|
||||
res := win.shutdown(win.SOCKET(s), c.int(manner))
|
||||
if res < 0 {
|
||||
return Shutdown_Error(win.WSAGetLastError())
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@(private)
|
||||
_set_option :: proc(s: Any_Socket, option: Socket_Option, value: any, loc := #caller_location) -> Network_Error {
|
||||
level := win.SOL_SOCKET if option != .TCP_Nodelay else win.IPPROTO_TCP
|
||||
|
||||
bool_value: b32
|
||||
int_value: i32
|
||||
linger_value: win.LINGER
|
||||
|
||||
ptr: rawptr
|
||||
len: c.int
|
||||
|
||||
switch option {
|
||||
case
|
||||
.Reuse_Address,
|
||||
.Exclusive_Addr_Use,
|
||||
.Keep_Alive,
|
||||
.Out_Of_Bounds_Data_Inline,
|
||||
.TCP_Nodelay,
|
||||
.Broadcast,
|
||||
.Conditional_Accept,
|
||||
.Dont_Linger:
|
||||
switch x in value {
|
||||
case bool, b8:
|
||||
x2 := x
|
||||
bool_value = b32((^bool)(&x2)^)
|
||||
case b16:
|
||||
bool_value = b32(x)
|
||||
case b32:
|
||||
bool_value = b32(x)
|
||||
case b64:
|
||||
bool_value = b32(x)
|
||||
case:
|
||||
panic("set_option() value must be a boolean here", loc)
|
||||
}
|
||||
ptr = &bool_value
|
||||
len = size_of(bool_value)
|
||||
case .Linger:
|
||||
t, ok := value.(time.Duration)
|
||||
if !ok do panic("set_option() value must be a time.Duration here", loc)
|
||||
|
||||
num_secs := i64(time.duration_seconds(t))
|
||||
if time.Duration(num_secs * 1e9) != t do return .Linger_Only_Supports_Whole_Seconds
|
||||
if num_secs > i64(max(u16)) do return .Value_Out_Of_Range
|
||||
linger_value.l_onoff = 1
|
||||
linger_value.l_linger = c.ushort(num_secs)
|
||||
|
||||
ptr = &linger_value
|
||||
len = size_of(linger_value)
|
||||
case
|
||||
.Receive_Timeout,
|
||||
.Send_Timeout:
|
||||
t, ok := value.(time.Duration)
|
||||
if !ok do panic("set_option() value must be a time.Duration here", loc)
|
||||
|
||||
int_value = i32(time.duration_milliseconds(t))
|
||||
ptr = &int_value
|
||||
len = size_of(int_value)
|
||||
|
||||
case
|
||||
.Receive_Buffer_Size,
|
||||
.Send_Buffer_Size:
|
||||
switch i in value {
|
||||
case i8, u8: i2 := i; int_value = c.int((^u8)(&i2)^)
|
||||
case i16, u16: i2 := i; int_value = c.int((^u16)(&i2)^)
|
||||
case i32, u32: i2 := i; int_value = c.int((^u32)(&i2)^)
|
||||
case i64, u64: i2 := i; int_value = c.int((^u64)(&i2)^)
|
||||
case i128, u128: i2 := i; int_value = c.int((^u128)(&i2)^)
|
||||
case int, uint: i2 := i; int_value = c.int((^uint)(&i2)^)
|
||||
case:
|
||||
panic("set_option() value must be an integer here", loc)
|
||||
}
|
||||
ptr = &int_value
|
||||
len = size_of(int_value)
|
||||
}
|
||||
|
||||
socket := any_socket_to_socket(s)
|
||||
res := win.setsockopt(win.SOCKET(socket), c.int(level), c.int(option), ptr, len)
|
||||
if res < 0 {
|
||||
return Socket_Option_Error(win.WSAGetLastError())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@(private)
|
||||
_set_blocking :: proc(socket: Any_Socket, should_block: bool) -> (err: Network_Error) {
|
||||
socket := any_socket_to_socket(socket)
|
||||
arg: win.DWORD = 0 if should_block else 1
|
||||
res := win.ioctlsocket(win.SOCKET(socket), transmute(win.c_long)win.FIONBIO, &arg)
|
||||
if res == win.SOCKET_ERROR {
|
||||
return Set_Blocking_Error(win.WSAGetLastError())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@(private)
|
||||
_endpoint_to_sockaddr :: proc(ep: Endpoint) -> (sockaddr: win.SOCKADDR_STORAGE_LH) {
|
||||
switch a in ep.address {
|
||||
case IP4_Address:
|
||||
(^win.sockaddr_in)(&sockaddr)^ = win.sockaddr_in {
|
||||
sin_port = u16be(win.USHORT(ep.port)),
|
||||
sin_addr = transmute(win.in_addr) a,
|
||||
sin_family = u16(win.AF_INET),
|
||||
}
|
||||
return
|
||||
case IP6_Address:
|
||||
(^win.sockaddr_in6)(&sockaddr)^ = win.sockaddr_in6 {
|
||||
sin6_port = u16be(win.USHORT(ep.port)),
|
||||
sin6_addr = transmute(win.in6_addr) a,
|
||||
sin6_family = u16(win.AF_INET6),
|
||||
}
|
||||
return
|
||||
}
|
||||
unreachable()
|
||||
}
|
||||
|
||||
@(private)
|
||||
_sockaddr_to_endpoint :: proc(native_addr: ^win.SOCKADDR_STORAGE_LH) -> (ep: Endpoint) {
|
||||
switch native_addr.ss_family {
|
||||
case u16(win.AF_INET):
|
||||
addr := cast(^win.sockaddr_in) native_addr
|
||||
port := int(addr.sin_port)
|
||||
ep = Endpoint {
|
||||
address = IP4_Address(transmute([4]byte) addr.sin_addr),
|
||||
port = port,
|
||||
}
|
||||
case u16(win.AF_INET6):
|
||||
addr := cast(^win.sockaddr_in6) native_addr
|
||||
port := int(addr.sin6_port)
|
||||
ep = Endpoint {
|
||||
address = IP6_Address(transmute([8]u16be) addr.sin6_addr),
|
||||
port = port,
|
||||
}
|
||||
case:
|
||||
panic("native_addr is neither IP4 or IP6 address")
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,235 @@
|
||||
package net
|
||||
/*
|
||||
Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures.
|
||||
For other protocols and their features, see subdirectories of this package.
|
||||
*/
|
||||
|
||||
/*
|
||||
Copyright 2022 Tetralux <tetraluxonpc@gmail.com>
|
||||
Copyright 2022 Colin Davidson <colrdavidson@gmail.com>
|
||||
Copyright 2022 Jeroen van Rijn <nom@duclavier.com>.
|
||||
Made available under Odin's BSD-3 license.
|
||||
|
||||
List of contributors:
|
||||
Tetralux: Initial implementation
|
||||
Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver
|
||||
Jeroen van Rijn: Cross platform unification, code style, documentation
|
||||
*/
|
||||
|
||||
import "core:strings"
|
||||
import "core:strconv"
|
||||
import "core:unicode/utf8"
|
||||
import "core:mem"
|
||||
|
||||
split_url :: proc(url: string, allocator := context.allocator) -> (scheme, host, path: string, queries: map[string]string) {
|
||||
s := url
|
||||
|
||||
i := strings.last_index(s, "://")
|
||||
if i >= 0 {
|
||||
scheme = s[:i]
|
||||
s = s[i+3:]
|
||||
}
|
||||
|
||||
i = strings.index(s, "?")
|
||||
if i != -1 {
|
||||
query_str := s[i+1:]
|
||||
s = s[:i]
|
||||
if query_str != "" {
|
||||
queries_parts := strings.split(query_str, "&")
|
||||
queries = make(map[string]string, len(queries_parts), allocator)
|
||||
for q in queries_parts {
|
||||
parts := strings.split(q, "=")
|
||||
switch len(parts) {
|
||||
case 1: queries[parts[0]] = "" // NOTE(tetra): Query not set to anything, was but present.
|
||||
case 2: queries[parts[0]] = parts[1] // NOTE(tetra): Query set to something.
|
||||
case: break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
i = strings.index(s, "/")
|
||||
if i == -1 {
|
||||
host = s
|
||||
path = "/"
|
||||
} else {
|
||||
host = s[:i]
|
||||
path = s[i:]
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
join_url :: proc(scheme, host, path: string, queries: map[string]string, allocator := context.allocator) -> string {
|
||||
using strings
|
||||
|
||||
b := builder_make(allocator)
|
||||
builder_grow(&b, len(scheme) + 3 + len(host) + 1 + len(path))
|
||||
|
||||
write_string(&b, scheme)
|
||||
write_string(&b, "://")
|
||||
write_string(&b, trim_space(host))
|
||||
|
||||
if path != "" {
|
||||
if path[0] != '/' do write_string(&b, "/")
|
||||
write_string(&b, trim_space(path))
|
||||
}
|
||||
|
||||
|
||||
if len(queries) > 0 do write_string(&b, "?")
|
||||
for query_name, query_value in queries {
|
||||
write_string(&b, query_name)
|
||||
if query_value != "" {
|
||||
write_string(&b, "=")
|
||||
write_string(&b, query_value)
|
||||
}
|
||||
}
|
||||
|
||||
return to_string(b)
|
||||
}
|
||||
|
||||
percent_encode :: proc(s: string, allocator := context.allocator) -> string {
|
||||
using strings
|
||||
|
||||
b := builder_make(allocator)
|
||||
builder_grow(&b, len(s) + 16) // NOTE(tetra): A reasonable number to allow for the number of things we need to escape.
|
||||
|
||||
for ch in s {
|
||||
switch ch {
|
||||
case 'A'..='Z', 'a'..='z', '0'..='9', '-', '_', '.', '~':
|
||||
write_rune(&b, ch)
|
||||
case:
|
||||
bytes, n := utf8.encode_rune(ch)
|
||||
for byte in bytes[:n] {
|
||||
buf: [2]u8 = ---
|
||||
t := strconv.append_int(buf[:], i64(byte), 16)
|
||||
write_rune(&b, '%')
|
||||
write_string(&b, t)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return to_string(b)
|
||||
}
|
||||
|
||||
percent_decode :: proc(encoded_string: string, allocator := context.allocator) -> (decoded_string: string, ok: bool) {
|
||||
using strings
|
||||
|
||||
b := builder_make(allocator)
|
||||
builder_grow(&b, len(encoded_string))
|
||||
defer if !ok do builder_destroy(&b)
|
||||
|
||||
stack_buf: [4]u8
|
||||
pending := mem.buffer_from_slice(stack_buf[:])
|
||||
s := encoded_string
|
||||
|
||||
for len(s) > 0 {
|
||||
i := index_rune(s, '%')
|
||||
if i == -1 {
|
||||
write_string(&b, s) // no '%'s; the string is already decoded
|
||||
break
|
||||
}
|
||||
|
||||
write_string(&b, s[:i])
|
||||
s = s[i:]
|
||||
|
||||
if len(s) == 0 do return // percent without anything after it
|
||||
s = s[1:]
|
||||
|
||||
if s[0] == '%' {
|
||||
write_rune(&b, '%')
|
||||
s = s[1:]
|
||||
continue
|
||||
}
|
||||
|
||||
if len(s) < 2 do return // percent without encoded value
|
||||
|
||||
n: int
|
||||
n, _ = strconv.parse_int(s[:2], 16)
|
||||
switch n {
|
||||
case 0x20: write_rune(&b, ' ')
|
||||
case 0x21: write_rune(&b, '!')
|
||||
case 0x23: write_rune(&b, '#')
|
||||
case 0x24: write_rune(&b, '$')
|
||||
case 0x25: write_rune(&b, '%')
|
||||
case 0x26: write_rune(&b, '&')
|
||||
case 0x27: write_rune(&b, '\'')
|
||||
case 0x28: write_rune(&b, '(')
|
||||
case 0x29: write_rune(&b, ')')
|
||||
case 0x2A: write_rune(&b, '*')
|
||||
case 0x2B: write_rune(&b, '+')
|
||||
case 0x2C: write_rune(&b, ',')
|
||||
case 0x2F: write_rune(&b, '/')
|
||||
case 0x3A: write_rune(&b, ':')
|
||||
case 0x3B: write_rune(&b, ';')
|
||||
case 0x3D: write_rune(&b, '=')
|
||||
case 0x3F: write_rune(&b, '?')
|
||||
case 0x40: write_rune(&b, '@')
|
||||
case 0x5B: write_rune(&b, '[')
|
||||
case 0x5D: write_rune(&b, ']')
|
||||
case:
|
||||
// utf-8 bytes
|
||||
// TODO(tetra): Audit this - 4 bytes???
|
||||
append(&pending, s[0])
|
||||
append(&pending, s[1])
|
||||
if len(pending) == 4 {
|
||||
r, _ := utf8.decode_rune(pending[:])
|
||||
write_rune(&b, r)
|
||||
clear(&pending)
|
||||
}
|
||||
}
|
||||
s = s[2:]
|
||||
}
|
||||
|
||||
ok = true
|
||||
decoded_string = to_string(b)
|
||||
return
|
||||
}
|
||||
|
||||
//
|
||||
// TODO: encoding/base64 is broken...
|
||||
//
|
||||
|
||||
// // TODO(tetra): The whole "table" stuff in encoding/base64 is too impenetrable for me to
|
||||
// // make a table for this ... sigh - so this'll do for now.
|
||||
/*
|
||||
base64url_encode :: proc(data: []byte, allocator := context.allocator) -> string {
|
||||
out := transmute([]byte) base64.encode(data, base64.ENC_TABLE, allocator);
|
||||
for b, i in out {
|
||||
switch b {
|
||||
case '+': out[i] = '-';
|
||||
case '/': out[i] = '_';
|
||||
}
|
||||
}
|
||||
i := len(out)-1;
|
||||
for ; i >= 0; i -= 1 {
|
||||
if out[i] != '=' do break;
|
||||
}
|
||||
return string(out[:i+1]);
|
||||
}
|
||||
|
||||
base64url_decode :: proc(s: string, allocator := context.allocator) -> []byte {
|
||||
size := len(s);
|
||||
padding := 0;
|
||||
for size % 4 != 0 {
|
||||
size += 1; // TODO: SPEED
|
||||
padding += 1;
|
||||
}
|
||||
|
||||
temp := make([]byte, size, context.temp_allocator);
|
||||
copy(temp, transmute([]byte) s);
|
||||
|
||||
for b, i in temp {
|
||||
switch b {
|
||||
case '-': temp[i] = '+';
|
||||
case '_': temp[i] = '/';
|
||||
}
|
||||
}
|
||||
|
||||
for in 0..padding-1 {
|
||||
temp[len(temp)-1] = '=';
|
||||
}
|
||||
|
||||
return base64.decode(string(temp), base64.DEC_TABLE, allocator);
|
||||
}
|
||||
*/
|
||||
@@ -1425,7 +1425,7 @@ parse_stmt :: proc(p: ^Parser) -> ^ast.Stmt {
|
||||
return es
|
||||
|
||||
case "force_inline", "force_no_inline":
|
||||
expr := parse_inlining_operand(p, true, tok)
|
||||
expr := parse_inlining_operand(p, true, tag)
|
||||
es := ast.new(ast.Expr_Stmt, expr.pos, expr.end)
|
||||
es.expr = expr
|
||||
return es
|
||||
|
||||
@@ -211,7 +211,7 @@ _getwd :: proc(allocator: runtime.Allocator) -> (string, Error) {
|
||||
#no_bounds_check res := unix.sys_getcwd(&buf[0], uint(len(buf)))
|
||||
|
||||
if res >= 0 {
|
||||
return strings.string_from_nul_terminated_ptr(&buf[0], len(buf)), nil
|
||||
return strings.string_from_null_terminated_ptr(&buf[0], len(buf)), nil
|
||||
}
|
||||
if res != -ERANGE {
|
||||
return "", _get_platform_error(res)
|
||||
|
||||
@@ -67,6 +67,7 @@ ENOPROTOOPT: Errno : 42 /* Protocol not available */
|
||||
EPROTONOSUPPORT: Errno : 43 /* Protocol not supported */
|
||||
ESOCKTNOSUPPORT: Errno : 44 /* Socket type not supported */
|
||||
ENOTSUP: Errno : 45 /* Operation not supported */
|
||||
EOPNOTSUPP:: ENOTSUP
|
||||
EPFNOSUPPORT: Errno : 46 /* Protocol family not supported */
|
||||
EAFNOSUPPORT: Errno : 47 /* Address family not supported by protocol family */
|
||||
EADDRINUSE: Errno : 48 /* Address already in use */
|
||||
@@ -179,6 +180,93 @@ RTLD_NODELETE :: 0x80
|
||||
RTLD_NOLOAD :: 0x10
|
||||
RTLD_FIRST :: 0x100
|
||||
|
||||
SOL_SOCKET :: 0xFFFF
|
||||
|
||||
SOCK_STREAM :: 1
|
||||
SOCK_DGRAM :: 2
|
||||
SOCK_RAW :: 3
|
||||
SOCK_RDM :: 4
|
||||
SOCK_SEQPACKET :: 5
|
||||
|
||||
SO_DEBUG :: 0x0001
|
||||
SO_ACCEPTCONN :: 0x0002
|
||||
SO_REUSEADDR :: 0x0004
|
||||
SO_KEEPALIVE :: 0x0008
|
||||
SO_DONTROUTE :: 0x0010
|
||||
SO_BROADCAST :: 0x0020
|
||||
SO_USELOOPBACK :: 0x0040
|
||||
SO_LINGER :: 0x0080
|
||||
SO_OOBINLINE :: 0x0100
|
||||
SO_REUSEPORT :: 0x0200
|
||||
SO_TIMESTAMP :: 0x0400
|
||||
|
||||
SO_DONTTRUNC :: 0x2000
|
||||
SO_WANTMORE :: 0x4000
|
||||
SO_WANTOOBFLAG :: 0x8000
|
||||
SO_SNDBUF :: 0x1001
|
||||
SO_RCVBUF :: 0x1002
|
||||
SO_SNDLOWAT :: 0x1003
|
||||
SO_RCVLOWAT :: 0x1004
|
||||
SO_SNDTIMEO :: 0x1005
|
||||
SO_RCVTIMEO :: 0x1006
|
||||
SO_ERROR :: 0x1007
|
||||
SO_TYPE :: 0x1008
|
||||
SO_PRIVSTATE :: 0x1009
|
||||
SO_NREAD :: 0x1020
|
||||
SO_NKE :: 0x1021
|
||||
|
||||
AF_UNSPEC :: 0
|
||||
AF_LOCAL :: 1
|
||||
AF_UNIX :: AF_LOCAL
|
||||
AF_INET :: 2
|
||||
AF_IMPLINK :: 3
|
||||
AF_PUP :: 4
|
||||
AF_CHAOS :: 5
|
||||
AF_NS :: 6
|
||||
AF_ISO :: 7
|
||||
AF_OSI :: AF_ISO
|
||||
AF_ECMA :: 8
|
||||
AF_DATAKIT :: 9
|
||||
AF_CCITT :: 10
|
||||
AF_SNA :: 11
|
||||
AF_DECnet :: 12
|
||||
AF_DLI :: 13
|
||||
AF_LAT :: 14
|
||||
AF_HYLINK :: 15
|
||||
AF_APPLETALK :: 16
|
||||
AF_ROUTE :: 17
|
||||
AF_LINK :: 18
|
||||
pseudo_AF_XTP :: 19
|
||||
AF_COIP :: 20
|
||||
AF_CNT :: 21
|
||||
pseudo_AF_RTIP :: 22
|
||||
AF_IPX :: 23
|
||||
AF_SIP :: 24
|
||||
pseudo_AF_PIP :: 25
|
||||
pseudo_AF_BLUE :: 26
|
||||
AF_NDRV :: 27
|
||||
AF_ISDN :: 28
|
||||
AF_E164 :: AF_ISDN
|
||||
pseudo_AF_KEY :: 29
|
||||
AF_INET6 :: 30
|
||||
AF_NATM :: 31
|
||||
AF_SYSTEM :: 32
|
||||
AF_NETBIOS :: 33
|
||||
AF_PPP :: 34
|
||||
|
||||
TCP_NODELAY :: 0x01
|
||||
TCP_MAXSEG :: 0x02
|
||||
TCP_NOPUSH :: 0x04
|
||||
TCP_NOOPT :: 0x08
|
||||
|
||||
IPPROTO_ICMP :: 1
|
||||
IPPROTO_TCP :: 6
|
||||
IPPROTO_UDP :: 17
|
||||
|
||||
SHUT_RD :: 0
|
||||
SHUT_WR :: 1
|
||||
SHUT_RDWR :: 2
|
||||
|
||||
|
||||
// "Argv" arguments converted to Odin strings
|
||||
args := _alloc_command_line_arguments()
|
||||
@@ -224,6 +312,58 @@ Dirent :: struct {
|
||||
|
||||
Dir :: distinct rawptr // DIR*
|
||||
|
||||
SOCKADDR :: struct #packed {
|
||||
len: c.char,
|
||||
family: c.char,
|
||||
sa_data: [14]c.char,
|
||||
}
|
||||
|
||||
SOCKADDR_STORAGE_LH :: struct #packed {
|
||||
len: c.char,
|
||||
family: c.char,
|
||||
__ss_pad1: [6]c.char,
|
||||
__ss_align: i64,
|
||||
__ss_pad2: [112]c.char,
|
||||
}
|
||||
|
||||
sockaddr_in :: struct #packed {
|
||||
sin_len: c.char,
|
||||
sin_family: c.char,
|
||||
sin_port: u16be,
|
||||
sin_addr: in_addr,
|
||||
sin_zero: [8]c.char,
|
||||
}
|
||||
|
||||
sockaddr_in6 :: struct #packed {
|
||||
sin6_len: c.char,
|
||||
sin6_family: c.char,
|
||||
sin6_port: u16be,
|
||||
sin6_flowinfo: c.uint,
|
||||
sin6_addr: in6_addr,
|
||||
sin6_scope_id: c.uint,
|
||||
}
|
||||
|
||||
in_addr :: struct #packed {
|
||||
s_addr: u32,
|
||||
}
|
||||
|
||||
in6_addr :: struct #packed {
|
||||
s6_addr: [16]u8,
|
||||
}
|
||||
|
||||
Timeval :: struct {
|
||||
seconds: i64,
|
||||
nanoseconds: int,
|
||||
}
|
||||
|
||||
Linger :: struct {
|
||||
onoff: int,
|
||||
linger: int,
|
||||
}
|
||||
|
||||
Socket :: distinct int
|
||||
socklen_t :: c.int
|
||||
|
||||
// File type
|
||||
S_IFMT :: 0o170000 // Type of file mask
|
||||
S_IFIFO :: 0o010000 // Named pipe (fifo)
|
||||
@@ -318,6 +458,18 @@ foreign libc {
|
||||
@(link_name="strerror") _darwin_string_error :: proc(num : c.int) -> cstring ---
|
||||
@(link_name="sysctlbyname") _sysctlbyname :: proc(path: cstring, oldp: rawptr, oldlenp: rawptr, newp: rawptr, newlen: int) -> c.int ---
|
||||
|
||||
@(link_name="socket") _unix_socket :: proc(domain: int, type: int, protocol: int) -> int ---
|
||||
@(link_name="listen") _unix_listen :: proc(socket: int, backlog: int) -> int ---
|
||||
@(link_name="accept") _unix_accept :: proc(socket: int, addr: rawptr, addr_len: rawptr) -> int ---
|
||||
@(link_name="connect") _unix_connect :: proc(socket: int, addr: rawptr, addr_len: socklen_t) -> int ---
|
||||
@(link_name="bind") _unix_bind :: proc(socket: int, addr: rawptr, addr_len: socklen_t) -> int ---
|
||||
@(link_name="setsockopt") _unix_setsockopt :: proc(socket: int, level: int, opt_name: int, opt_val: rawptr, opt_len: socklen_t) -> int ---
|
||||
@(link_name="recvfrom") _unix_recvfrom :: proc(socket: int, buffer: rawptr, buffer_len: c.size_t, flags: int, addr: rawptr, addr_len: ^socklen_t) -> c.ssize_t ---
|
||||
@(link_name="recv") _unix_recv :: proc(socket: int, buffer: rawptr, buffer_len: c.size_t, flags: int) -> c.ssize_t ---
|
||||
@(link_name="sendto") _unix_sendto :: proc(socket: int, buffer: rawptr, buffer_len: c.size_t, flags: int, addr: rawptr, addr_len: socklen_t) -> c.ssize_t ---
|
||||
@(link_name="send") _unix_send :: proc(socket: int, buffer: rawptr, buffer_len: c.size_t, flags: int) -> c.ssize_t ---
|
||||
@(link_name="shutdown") _unix_shutdown :: proc(socket: int, how: int) -> int ---
|
||||
|
||||
@(link_name="exit") _unix_exit :: proc(status: c.int) -> ! ---
|
||||
}
|
||||
|
||||
@@ -815,3 +967,91 @@ _alloc_command_line_arguments :: proc() -> []string {
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
socket :: proc(domain: int, type: int, protocol: int) -> (Socket, Errno) {
|
||||
result := _unix_socket(domain, type, protocol)
|
||||
if result < 0 {
|
||||
return 0, Errno(get_last_error())
|
||||
}
|
||||
return Socket(result), ERROR_NONE
|
||||
}
|
||||
|
||||
connect :: proc(sd: Socket, addr: ^SOCKADDR, len: socklen_t) -> (Errno) {
|
||||
result := _unix_connect(int(sd), addr, len)
|
||||
if result < 0 {
|
||||
return Errno(get_last_error())
|
||||
}
|
||||
return ERROR_NONE
|
||||
}
|
||||
|
||||
bind :: proc(sd: Socket, addr: ^SOCKADDR, len: socklen_t) -> (Errno) {
|
||||
result := _unix_bind(int(sd), addr, len)
|
||||
if result < 0 {
|
||||
return Errno(get_last_error())
|
||||
}
|
||||
return ERROR_NONE
|
||||
}
|
||||
|
||||
accept :: proc(sd: Socket, addr: ^SOCKADDR, len: rawptr) -> (Socket, Errno) {
|
||||
result := _unix_accept(int(sd), rawptr(addr), len)
|
||||
if result < 0 {
|
||||
return 0, Errno(get_last_error())
|
||||
}
|
||||
return Socket(result), ERROR_NONE
|
||||
}
|
||||
|
||||
listen :: proc(sd: Socket, backlog: int) -> (Errno) {
|
||||
result := _unix_listen(int(sd), backlog)
|
||||
if result < 0 {
|
||||
return Errno(get_last_error())
|
||||
}
|
||||
return ERROR_NONE
|
||||
}
|
||||
|
||||
setsockopt :: proc(sd: Socket, level: int, optname: int, optval: rawptr, optlen: socklen_t) -> (Errno) {
|
||||
result := _unix_setsockopt(int(sd), level, optname, optval, optlen)
|
||||
if result < 0 {
|
||||
return Errno(get_last_error())
|
||||
}
|
||||
return ERROR_NONE
|
||||
}
|
||||
|
||||
recvfrom :: proc(sd: Socket, data: []byte, flags: int, addr: ^SOCKADDR, addr_size: ^socklen_t) -> (u32, Errno) {
|
||||
result := _unix_recvfrom(int(sd), raw_data(data), len(data), flags, addr, addr_size)
|
||||
if result < 0 {
|
||||
return 0, Errno(get_last_error())
|
||||
}
|
||||
return u32(result), ERROR_NONE
|
||||
}
|
||||
|
||||
recv :: proc(sd: Socket, data: []byte, flags: int) -> (u32, Errno) {
|
||||
result := _unix_recv(int(sd), raw_data(data), len(data), flags)
|
||||
if result < 0 {
|
||||
return 0, Errno(get_last_error())
|
||||
}
|
||||
return u32(result), ERROR_NONE
|
||||
}
|
||||
|
||||
sendto :: proc(sd: Socket, data: []u8, flags: int, addr: ^SOCKADDR, addrlen: socklen_t) -> (u32, Errno) {
|
||||
result := _unix_sendto(int(sd), raw_data(data), len(data), flags, addr, addrlen)
|
||||
if result < 0 {
|
||||
return 0, Errno(get_last_error())
|
||||
}
|
||||
return u32(result), ERROR_NONE
|
||||
}
|
||||
|
||||
send :: proc(sd: Socket, data: []byte, flags: int) -> (u32, Errno) {
|
||||
result := _unix_send(int(sd), raw_data(data), len(data), 0)
|
||||
if result < 0 {
|
||||
return 0, Errno(get_last_error())
|
||||
}
|
||||
return u32(result), ERROR_NONE
|
||||
}
|
||||
|
||||
shutdown :: proc(sd: Socket, how: int) -> (Errno) {
|
||||
result := _unix_shutdown(int(sd), how)
|
||||
if result < 0 {
|
||||
return Errno(get_last_error())
|
||||
}
|
||||
return ERROR_NONE
|
||||
}
|
||||
|
||||
+275
-2
@@ -1,4 +1,277 @@
|
||||
//+js
|
||||
//+build js
|
||||
package os
|
||||
|
||||
#panic("package os does not support a js target")
|
||||
import "core:intrinsics"
|
||||
import "core:runtime"
|
||||
import "core:unicode/utf16"
|
||||
|
||||
is_path_separator :: proc(c: byte) -> bool {
|
||||
return c == '/' || c == '\\'
|
||||
}
|
||||
|
||||
open :: proc(path: string, mode: int = O_RDONLY, perm: int = 0) -> (Handle, Errno) {
|
||||
unimplemented("core:os procedure not supported on JS target")
|
||||
}
|
||||
|
||||
close :: proc(fd: Handle) -> Errno {
|
||||
unimplemented("core:os procedure not supported on JS target")
|
||||
}
|
||||
|
||||
flush :: proc(fd: Handle) -> (err: Errno) {
|
||||
unimplemented("core:os procedure not supported on JS target")
|
||||
}
|
||||
|
||||
|
||||
|
||||
write :: proc(fd: Handle, data: []byte) -> (int, Errno) {
|
||||
unimplemented("core:os procedure not supported on JS target")
|
||||
}
|
||||
|
||||
@(private="file")
|
||||
read_console :: proc(handle: Handle, b: []byte) -> (n: int, err: Errno) {
|
||||
unimplemented("core:os procedure not supported on JS target")
|
||||
}
|
||||
|
||||
read :: proc(fd: Handle, data: []byte) -> (int, Errno) {
|
||||
unimplemented("core:os procedure not supported on JS target")
|
||||
}
|
||||
|
||||
seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Errno) {
|
||||
unimplemented("core:os procedure not supported on JS target")
|
||||
}
|
||||
|
||||
file_size :: proc(fd: Handle) -> (i64, Errno) {
|
||||
unimplemented("core:os procedure not supported on JS target")
|
||||
}
|
||||
|
||||
|
||||
@(private)
|
||||
MAX_RW :: 1<<30
|
||||
|
||||
@(private)
|
||||
pread :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Errno) {
|
||||
unimplemented("core:os procedure not supported on JS target")
|
||||
}
|
||||
@(private)
|
||||
pwrite :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Errno) {
|
||||
unimplemented("core:os procedure not supported on JS target")
|
||||
}
|
||||
|
||||
read_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Errno) {
|
||||
unimplemented("core:os procedure not supported on JS target")
|
||||
}
|
||||
write_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Errno) {
|
||||
unimplemented("core:os procedure not supported on JS target")
|
||||
}
|
||||
|
||||
|
||||
|
||||
// NOTE(bill): Uses startup to initialize it
|
||||
//stdin := get_std_handle(uint(win32.STD_INPUT_HANDLE))
|
||||
//stdout := get_std_handle(uint(win32.STD_OUTPUT_HANDLE))
|
||||
//stderr := get_std_handle(uint(win32.STD_ERROR_HANDLE))
|
||||
|
||||
|
||||
get_std_handle :: proc "contextless" (h: uint) -> Handle {
|
||||
context = runtime.default_context()
|
||||
unimplemented("core:os procedure not supported on JS target")
|
||||
}
|
||||
|
||||
|
||||
exists :: proc(path: string) -> bool {
|
||||
unimplemented("core:os procedure not supported on JS target")
|
||||
}
|
||||
|
||||
is_file :: proc(path: string) -> bool {
|
||||
unimplemented("core:os procedure not supported on JS target")
|
||||
}
|
||||
|
||||
is_dir :: proc(path: string) -> bool {
|
||||
unimplemented("core:os procedure not supported on JS target")
|
||||
}
|
||||
|
||||
// NOTE(tetra): GetCurrentDirectory is not thread safe with SetCurrentDirectory and GetFullPathName
|
||||
//@private cwd_lock := win32.SRWLOCK{} // zero is initialized
|
||||
|
||||
get_current_directory :: proc(allocator := context.allocator) -> string {
|
||||
unimplemented("core:os procedure not supported on JS target")
|
||||
}
|
||||
|
||||
set_current_directory :: proc(path: string) -> (err: Errno) {
|
||||
unimplemented("core:os procedure not supported on JS target")
|
||||
}
|
||||
|
||||
|
||||
|
||||
change_directory :: proc(path: string) -> (err: Errno) {
|
||||
unimplemented("core:os procedure not supported on JS target")
|
||||
}
|
||||
|
||||
make_directory :: proc(path: string, mode: u32 = 0) -> (err: Errno) {
|
||||
unimplemented("core:os procedure not supported on JS target")
|
||||
}
|
||||
|
||||
|
||||
remove_directory :: proc(path: string) -> (err: Errno) {
|
||||
unimplemented("core:os procedure not supported on JS target")
|
||||
}
|
||||
|
||||
|
||||
|
||||
@(private)
|
||||
is_abs :: proc(path: string) -> bool {
|
||||
unimplemented("core:os procedure not supported on JS target")
|
||||
}
|
||||
|
||||
@(private)
|
||||
fix_long_path :: proc(path: string) -> string {
|
||||
unimplemented("core:os procedure not supported on JS target")
|
||||
}
|
||||
|
||||
|
||||
link :: proc(old_name, new_name: string) -> (err: Errno) {
|
||||
unimplemented("core:os procedure not supported on JS target")
|
||||
}
|
||||
|
||||
unlink :: proc(path: string) -> (err: Errno) {
|
||||
unimplemented("core:os procedure not supported on JS target")
|
||||
}
|
||||
|
||||
|
||||
|
||||
rename :: proc(old_path, new_path: string) -> (err: Errno) {
|
||||
unimplemented("core:os procedure not supported on JS target")
|
||||
}
|
||||
|
||||
|
||||
ftruncate :: proc(fd: Handle, length: i64) -> (err: Errno) {
|
||||
unimplemented("core:os procedure not supported on JS target")
|
||||
}
|
||||
|
||||
truncate :: proc(path: string, length: i64) -> (err: Errno) {
|
||||
unimplemented("core:os procedure not supported on JS target")
|
||||
}
|
||||
|
||||
|
||||
remove :: proc(name: string) -> Errno {
|
||||
unimplemented("core:os procedure not supported on JS target")
|
||||
}
|
||||
|
||||
|
||||
pipe :: proc() -> (r, w: Handle, err: Errno) {
|
||||
unimplemented("core:os procedure not supported on JS target")
|
||||
}
|
||||
|
||||
read_dir :: proc(fd: Handle, n: int, allocator := context.allocator) -> (fi: []File_Info, err: Errno) {
|
||||
unimplemented("core:os procedure not supported on JS target")
|
||||
}
|
||||
|
||||
Handle :: distinct uintptr
|
||||
File_Time :: distinct u64
|
||||
Errno :: distinct int
|
||||
|
||||
|
||||
INVALID_HANDLE :: ~Handle(0)
|
||||
|
||||
|
||||
|
||||
O_RDONLY :: 0x00000
|
||||
O_WRONLY :: 0x00001
|
||||
O_RDWR :: 0x00002
|
||||
O_CREATE :: 0x00040
|
||||
O_EXCL :: 0x00080
|
||||
O_NOCTTY :: 0x00100
|
||||
O_TRUNC :: 0x00200
|
||||
O_NONBLOCK :: 0x00800
|
||||
O_APPEND :: 0x00400
|
||||
O_SYNC :: 0x01000
|
||||
O_ASYNC :: 0x02000
|
||||
O_CLOEXEC :: 0x80000
|
||||
|
||||
|
||||
ERROR_NONE: Errno : 0
|
||||
ERROR_FILE_NOT_FOUND: Errno : 2
|
||||
ERROR_PATH_NOT_FOUND: Errno : 3
|
||||
ERROR_ACCESS_DENIED: Errno : 5
|
||||
ERROR_INVALID_HANDLE: Errno : 6
|
||||
ERROR_NOT_ENOUGH_MEMORY: Errno : 8
|
||||
ERROR_NO_MORE_FILES: Errno : 18
|
||||
ERROR_HANDLE_EOF: Errno : 38
|
||||
ERROR_NETNAME_DELETED: Errno : 64
|
||||
ERROR_FILE_EXISTS: Errno : 80
|
||||
ERROR_INVALID_PARAMETER: Errno : 87
|
||||
ERROR_BROKEN_PIPE: Errno : 109
|
||||
ERROR_BUFFER_OVERFLOW: Errno : 111
|
||||
ERROR_INSUFFICIENT_BUFFER: Errno : 122
|
||||
ERROR_MOD_NOT_FOUND: Errno : 126
|
||||
ERROR_PROC_NOT_FOUND: Errno : 127
|
||||
ERROR_DIR_NOT_EMPTY: Errno : 145
|
||||
ERROR_ALREADY_EXISTS: Errno : 183
|
||||
ERROR_ENVVAR_NOT_FOUND: Errno : 203
|
||||
ERROR_MORE_DATA: Errno : 234
|
||||
ERROR_OPERATION_ABORTED: Errno : 995
|
||||
ERROR_IO_PENDING: Errno : 997
|
||||
ERROR_NOT_FOUND: Errno : 1168
|
||||
ERROR_PRIVILEGE_NOT_HELD: Errno : 1314
|
||||
WSAEACCES: Errno : 10013
|
||||
WSAECONNRESET: Errno : 10054
|
||||
|
||||
// Windows reserves errors >= 1<<29 for application use
|
||||
ERROR_FILE_IS_PIPE: Errno : 1<<29 + 0
|
||||
ERROR_FILE_IS_NOT_DIR: Errno : 1<<29 + 1
|
||||
ERROR_NEGATIVE_OFFSET: Errno : 1<<29 + 2
|
||||
|
||||
// "Argv" arguments converted to Odin strings
|
||||
args := _alloc_command_line_arguments()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
last_write_time :: proc(fd: Handle) -> (File_Time, Errno) {
|
||||
unimplemented("core:os procedure not supported on JS target")
|
||||
}
|
||||
|
||||
last_write_time_by_name :: proc(name: string) -> (File_Time, Errno) {
|
||||
unimplemented("core:os procedure not supported on JS target")
|
||||
}
|
||||
|
||||
|
||||
|
||||
heap_alloc :: proc(size: int, zero_memory := true) -> rawptr {
|
||||
unimplemented("core:os procedure not supported on JS target")
|
||||
}
|
||||
heap_resize :: proc(ptr: rawptr, new_size: int) -> rawptr {
|
||||
unimplemented("core:os procedure not supported on JS target")
|
||||
}
|
||||
heap_free :: proc(ptr: rawptr) {
|
||||
unimplemented("core:os procedure not supported on JS target")
|
||||
}
|
||||
|
||||
get_page_size :: proc() -> int {
|
||||
unimplemented("core:os procedure not supported on JS target")
|
||||
}
|
||||
|
||||
@(private)
|
||||
_processor_core_count :: proc() -> int {
|
||||
unimplemented("core:os procedure not supported on JS target")
|
||||
}
|
||||
|
||||
exit :: proc "contextless" (code: int) -> ! {
|
||||
context = runtime.default_context()
|
||||
unimplemented("core:os procedure not supported on JS target")
|
||||
}
|
||||
|
||||
|
||||
|
||||
current_thread_id :: proc "contextless" () -> int {
|
||||
context = runtime.default_context()
|
||||
unimplemented("core:os procedure not supported on JS target")
|
||||
}
|
||||
|
||||
|
||||
|
||||
_alloc_command_line_arguments :: proc() -> []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
+265
-1
@@ -14,6 +14,7 @@ Handle :: distinct i32
|
||||
Pid :: distinct i32
|
||||
File_Time :: distinct u64
|
||||
Errno :: distinct i32
|
||||
Socket :: distinct int
|
||||
|
||||
INVALID_HANDLE :: ~Handle(0)
|
||||
|
||||
@@ -171,6 +172,64 @@ SEEK_DATA :: 3
|
||||
SEEK_HOLE :: 4
|
||||
SEEK_MAX :: SEEK_HOLE
|
||||
|
||||
|
||||
AF_UNSPEC: int : 0
|
||||
AF_UNIX: int : 1
|
||||
AF_LOCAL: int : AF_UNIX
|
||||
AF_INET: int : 2
|
||||
AF_INET6: int : 10
|
||||
AF_PACKET: int : 17
|
||||
AF_BLUETOOTH: int : 31
|
||||
|
||||
SOCK_STREAM: int : 1
|
||||
SOCK_DGRAM: int : 2
|
||||
SOCK_RAW: int : 3
|
||||
SOCK_RDM: int : 4
|
||||
SOCK_SEQPACKET: int : 5
|
||||
SOCK_PACKET: int : 10
|
||||
|
||||
INADDR_ANY: c.ulong : 0
|
||||
INADDR_BROADCAST: c.ulong : 0xffffffff
|
||||
INADDR_NONE: c.ulong : 0xffffffff
|
||||
INADDR_DUMMY: c.ulong : 0xc0000008
|
||||
|
||||
IPPROTO_IP: int : 0
|
||||
IPPROTO_ICMP: int : 1
|
||||
IPPROTO_TCP: int : 6
|
||||
IPPROTO_UDP: int : 17
|
||||
IPPROTO_IPV6: int : 41
|
||||
IPPROTO_ETHERNET: int : 143
|
||||
IPPROTO_RAW: int : 255
|
||||
|
||||
SHUT_RD: int : 0
|
||||
SHUT_WR: int : 1
|
||||
SHUT_RDWR: int : 2
|
||||
|
||||
|
||||
SOL_SOCKET: int : 1
|
||||
SO_DEBUG: int : 1
|
||||
SO_REUSEADDR: int : 2
|
||||
SO_DONTROUTE: int : 5
|
||||
SO_BROADCAST: int : 6
|
||||
SO_SNDBUF: int : 7
|
||||
SO_RCVBUF: int : 8
|
||||
SO_KEEPALIVE: int : 9
|
||||
SO_OOBINLINE: int : 10
|
||||
SO_LINGER: int : 13
|
||||
SO_REUSEPORT: int : 15
|
||||
SO_RCVTIMEO_NEW: int : 66
|
||||
SO_SNDTIMEO_NEW: int : 67
|
||||
|
||||
TCP_NODELAY: int : 1
|
||||
TCP_CORK: int : 3
|
||||
|
||||
MSG_TRUNC : int : 0x20
|
||||
|
||||
// TODO: add remaining fcntl commands
|
||||
// reference: https://github.com/torvalds/linux/blob/master/include/uapi/asm-generic/fcntl.h
|
||||
F_GETFL: int : 3 /* Get file flags */
|
||||
F_SETFL: int : 4 /* Set file flags */
|
||||
|
||||
// NOTE(zangent): These are OS specific!
|
||||
// Do not mix these up!
|
||||
RTLD_LAZY :: 0x001
|
||||
@@ -178,6 +237,13 @@ RTLD_NOW :: 0x002
|
||||
RTLD_BINDING_MASK :: 0x3
|
||||
RTLD_GLOBAL :: 0x100
|
||||
|
||||
socklen_t :: c.int
|
||||
|
||||
Timeval :: struct {
|
||||
seconds: i64,
|
||||
nanoseconds: int,
|
||||
}
|
||||
|
||||
// "Argv" arguments converted to Odin strings
|
||||
args := _alloc_command_line_arguments()
|
||||
|
||||
@@ -217,6 +283,102 @@ Dirent :: struct {
|
||||
name: [256]byte,
|
||||
}
|
||||
|
||||
ADDRESS_FAMILY :: u16
|
||||
SOCKADDR :: struct #packed {
|
||||
sa_family: ADDRESS_FAMILY,
|
||||
sa_data: [14]c.char,
|
||||
}
|
||||
|
||||
SOCKADDR_STORAGE_LH :: struct #packed {
|
||||
ss_family: ADDRESS_FAMILY,
|
||||
__ss_pad1: [6]c.char,
|
||||
__ss_align: i64,
|
||||
__ss_pad2: [112]c.char,
|
||||
}
|
||||
|
||||
sockaddr_in :: struct #packed {
|
||||
sin_family: ADDRESS_FAMILY,
|
||||
sin_port: u16be,
|
||||
sin_addr: in_addr,
|
||||
sin_zero: [8]c.char,
|
||||
}
|
||||
|
||||
sockaddr_in6 :: struct #packed {
|
||||
sin6_family: ADDRESS_FAMILY,
|
||||
sin6_port: u16be,
|
||||
sin6_flowinfo: c.ulong,
|
||||
sin6_addr: in6_addr,
|
||||
sin6_scope_id: c.ulong,
|
||||
}
|
||||
|
||||
in_addr :: struct #packed {
|
||||
s_addr: u32,
|
||||
}
|
||||
|
||||
in6_addr :: struct #packed {
|
||||
s6_addr: [16]u8,
|
||||
}
|
||||
|
||||
rtnl_link_stats :: struct #packed {
|
||||
rx_packets: u32,
|
||||
tx_packets: u32,
|
||||
rx_bytes: u32,
|
||||
tx_bytes: u32,
|
||||
rx_errors: u32,
|
||||
tx_errors: u32,
|
||||
rx_dropped: u32,
|
||||
tx_dropped: u32,
|
||||
multicast: u32,
|
||||
collisions: u32,
|
||||
rx_length_errors: u32,
|
||||
rx_over_errors: u32,
|
||||
rx_crc_errors: u32,
|
||||
rx_frame_errors: u32,
|
||||
rx_fifo_errors: u32,
|
||||
rx_missed_errors: u32,
|
||||
tx_aborted_errors: u32,
|
||||
tx_carrier_errors: u32,
|
||||
tx_fifo_errors: u32,
|
||||
tx_heartbeat_errors: u32,
|
||||
tx_window_errors: u32,
|
||||
rx_compressed: u32,
|
||||
tx_compressed: u32,
|
||||
rx_nohandler: u32,
|
||||
}
|
||||
|
||||
SIOCGIFFLAG :: enum c.int {
|
||||
UP = 0, /* Interface is up. */
|
||||
BROADCAST = 1, /* Broadcast address valid. */
|
||||
DEBUG = 2, /* Turn on debugging. */
|
||||
LOOPBACK = 3, /* Is a loopback net. */
|
||||
POINT_TO_POINT = 4, /* Interface is point-to-point link. */
|
||||
NO_TRAILERS = 5, /* Avoid use of trailers. */
|
||||
RUNNING = 6, /* Resources allocated. */
|
||||
NOARP = 7, /* No address resolution protocol. */
|
||||
PROMISC = 8, /* Receive all packets. */
|
||||
ALL_MULTI = 9, /* Receive all multicast packets. Unimplemented. */
|
||||
MASTER = 10, /* Master of a load balancer. */
|
||||
SLAVE = 11, /* Slave of a load balancer. */
|
||||
MULTICAST = 12, /* Supports multicast. */
|
||||
PORTSEL = 13, /* Can set media type. */
|
||||
AUTOMEDIA = 14, /* Auto media select active. */
|
||||
DYNAMIC = 15, /* Dialup device with changing addresses. */
|
||||
LOWER_UP = 16,
|
||||
DORMANT = 17,
|
||||
ECHO = 18,
|
||||
}
|
||||
SIOCGIFFLAGS :: bit_set[SIOCGIFFLAG; c.int]
|
||||
|
||||
ifaddrs :: struct {
|
||||
next: ^ifaddrs,
|
||||
name: cstring,
|
||||
flags: SIOCGIFFLAGS,
|
||||
address: ^SOCKADDR,
|
||||
netmask: ^SOCKADDR,
|
||||
broadcast_or_dest: ^SOCKADDR, // Broadcast or Point-to-Point address
|
||||
data: rawptr, // Address-specific data.
|
||||
}
|
||||
|
||||
Dir :: distinct rawptr // DIR*
|
||||
|
||||
// File type
|
||||
@@ -297,6 +459,9 @@ foreign dl {
|
||||
@(link_name="dlsym") _unix_dlsym :: proc(handle: rawptr, symbol: cstring) -> rawptr ---
|
||||
@(link_name="dlclose") _unix_dlclose :: proc(handle: rawptr) -> c.int ---
|
||||
@(link_name="dlerror") _unix_dlerror :: proc() -> cstring ---
|
||||
|
||||
@(link_name="getifaddrs") _getifaddrs :: proc(ifap: ^^ifaddrs) -> (c.int) ---
|
||||
@(link_name="freeifaddrs") _freeifaddrs :: proc(ifa: ^ifaddrs) ---
|
||||
}
|
||||
|
||||
is_path_separator :: proc(r: rune) -> bool {
|
||||
@@ -748,7 +913,7 @@ get_current_directory :: proc() -> string {
|
||||
#no_bounds_check res := unix.sys_getcwd(&buf[0], uint(len(buf)))
|
||||
|
||||
if res >= 0 {
|
||||
return strings.string_from_nul_terminated_ptr(&buf[0], len(buf))
|
||||
return strings.string_from_null_terminated_ptr(&buf[0], len(buf))
|
||||
}
|
||||
if _get_errno(res) != ERANGE {
|
||||
delete(buf)
|
||||
@@ -823,3 +988,102 @@ _alloc_command_line_arguments :: proc() -> []string {
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
socket :: proc(domain: int, type: int, protocol: int) -> (Socket, Errno) {
|
||||
result := unix.sys_socket(domain, type, protocol)
|
||||
if result < 0 {
|
||||
return 0, _get_errno(result)
|
||||
}
|
||||
return Socket(result), ERROR_NONE
|
||||
}
|
||||
|
||||
bind :: proc(sd: Socket, addr: ^SOCKADDR, len: socklen_t) -> (Errno) {
|
||||
result := unix.sys_bind(int(sd), addr, len)
|
||||
if result < 0 {
|
||||
return _get_errno(result)
|
||||
}
|
||||
return ERROR_NONE
|
||||
}
|
||||
|
||||
|
||||
connect :: proc(sd: Socket, addr: ^SOCKADDR, len: socklen_t) -> (Errno) {
|
||||
result := unix.sys_connect(int(sd), addr, len)
|
||||
if result < 0 {
|
||||
return _get_errno(result)
|
||||
}
|
||||
return ERROR_NONE
|
||||
}
|
||||
|
||||
accept :: proc(sd: Socket, addr: ^SOCKADDR, len: rawptr) -> (Socket, Errno) {
|
||||
result := unix.sys_accept(int(sd), rawptr(addr), len)
|
||||
if result < 0 {
|
||||
return 0, _get_errno(result)
|
||||
}
|
||||
return Socket(result), ERROR_NONE
|
||||
}
|
||||
|
||||
listen :: proc(sd: Socket, backlog: int) -> (Errno) {
|
||||
result := unix.sys_listen(int(sd), backlog)
|
||||
if result < 0 {
|
||||
return _get_errno(result)
|
||||
}
|
||||
return ERROR_NONE
|
||||
}
|
||||
|
||||
setsockopt :: proc(sd: Socket, level: int, optname: int, optval: rawptr, optlen: socklen_t) -> (Errno) {
|
||||
result := unix.sys_setsockopt(int(sd), level, optname, optval, optlen)
|
||||
if result < 0 {
|
||||
return _get_errno(result)
|
||||
}
|
||||
return ERROR_NONE
|
||||
}
|
||||
|
||||
|
||||
recvfrom :: proc(sd: Socket, data: []byte, flags: int, addr: ^SOCKADDR, addr_size: ^socklen_t) -> (u32, Errno) {
|
||||
result := unix.sys_recvfrom(int(sd), raw_data(data), len(data), flags, addr, uintptr(addr_size))
|
||||
if result < 0 {
|
||||
return 0, _get_errno(int(result))
|
||||
}
|
||||
return u32(result), ERROR_NONE
|
||||
}
|
||||
|
||||
recv :: proc(sd: Socket, data: []byte, flags: int) -> (u32, Errno) {
|
||||
result := unix.sys_recvfrom(int(sd), raw_data(data), len(data), flags, nil, 0)
|
||||
if result < 0 {
|
||||
return 0, _get_errno(int(result))
|
||||
}
|
||||
return u32(result), ERROR_NONE
|
||||
}
|
||||
|
||||
|
||||
sendto :: proc(sd: Socket, data: []u8, flags: int, addr: ^SOCKADDR, addrlen: socklen_t) -> (u32, Errno) {
|
||||
result := unix.sys_sendto(int(sd), raw_data(data), len(data), flags, addr, addrlen)
|
||||
if result < 0 {
|
||||
return 0, _get_errno(int(result))
|
||||
}
|
||||
return u32(result), ERROR_NONE
|
||||
}
|
||||
|
||||
send :: proc(sd: Socket, data: []byte, flags: int) -> (u32, Errno) {
|
||||
result := unix.sys_sendto(int(sd), raw_data(data), len(data), 0, nil, 0)
|
||||
if result < 0 {
|
||||
return 0, _get_errno(int(result))
|
||||
}
|
||||
return u32(result), ERROR_NONE
|
||||
}
|
||||
|
||||
shutdown :: proc(sd: Socket, how: int) -> (Errno) {
|
||||
result := unix.sys_shutdown(int(sd), how)
|
||||
if result < 0 {
|
||||
return _get_errno(result)
|
||||
}
|
||||
return ERROR_NONE
|
||||
}
|
||||
|
||||
fcntl :: proc(fd: int, cmd: int, arg: int) -> (int, Errno) {
|
||||
result := unix.sys_fcntl(fd, cmd, arg)
|
||||
if result < 0 {
|
||||
return 0, _get_errno(result)
|
||||
}
|
||||
return result, ERROR_NONE
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
import "core:prof/spall"
|
||||
|
||||
spall_ctx: spall.Context
|
||||
spall_buffer: spall.Buffer
|
||||
|
||||
foo :: proc() {
|
||||
spall.SCOPED_EVENT(&spall_ctx, &spall_buffer, #procedure)
|
||||
}
|
||||
|
||||
main :: proc() {
|
||||
spall_ctx = spall.context_create("trace_test.spall")
|
||||
defer spall.context_destroy(&spall_ctx)
|
||||
|
||||
buffer_backing := make([]u8, spall.BUFFER_DEFAULT_SIZE)
|
||||
spall_buffer = spall.buffer_create(buffer_backing)
|
||||
defer spall.buffer_destroy(&spall_ctx, &spall_buffer)
|
||||
|
||||
spall.SCOPED_EVENT(&spall_ctx, &spall_buffer, #procedure)
|
||||
|
||||
for i := 0; i < 9001; i += 1 {
|
||||
foo()
|
||||
}
|
||||
}
|
||||
*/
|
||||
package spall
|
||||
@@ -1,4 +1,4 @@
|
||||
package prof_spall
|
||||
package spall
|
||||
|
||||
import "core:os"
|
||||
import "core:time"
|
||||
@@ -95,7 +95,7 @@ context_destroy :: proc(ctx: ^Context) {
|
||||
}
|
||||
|
||||
buffer_create :: proc(data: []byte, tid: u32 = 0, pid: u32 = 0) -> (buffer: Buffer, ok: bool) #optional_ok {
|
||||
assert(len(data) > 0)
|
||||
assert(len(data) >= 1024)
|
||||
buffer.data = data
|
||||
buffer.tid = tid
|
||||
buffer.pid = pid
|
||||
@@ -105,8 +105,13 @@ buffer_create :: proc(data: []byte, tid: u32 = 0, pid: u32 = 0) -> (buffer: Buff
|
||||
}
|
||||
|
||||
buffer_flush :: proc(ctx: ^Context, buffer: ^Buffer) {
|
||||
start := _trace_now(ctx)
|
||||
os.write(ctx.fd, buffer.data[:buffer.head])
|
||||
buffer.head = 0
|
||||
end := _trace_now(ctx)
|
||||
|
||||
buffer.head += _build_begin(buffer.data[buffer.head:], "Spall Trace Buffer Flush", "", start, buffer.tid, buffer.pid)
|
||||
buffer.head += _build_end(buffer.data[buffer.head:], end, buffer.tid, buffer.pid)
|
||||
}
|
||||
|
||||
buffer_destroy :: proc(ctx: ^Context, buffer: ^Buffer) {
|
||||
@@ -171,10 +176,11 @@ _build_begin :: proc "contextless" (buffer: []u8, name: string, args: string, ts
|
||||
mem.copy(raw_data(buffer[size_of(Begin_Event):]), raw_data(name), name_len)
|
||||
mem.copy(raw_data(buffer[size_of(Begin_Event)+name_len:]), raw_data(args), args_len)
|
||||
ok = true
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
_build_end :: proc(buffer: []u8, ts: f64, tid: u32, pid: u32) -> (event_size: int, ok: bool) #optional_ok {
|
||||
_build_end :: proc "contextless" (buffer: []u8, ts: f64, tid: u32, pid: u32) -> (event_size: int, ok: bool) #optional_ok {
|
||||
ev := (^End_Event)(raw_data(buffer))
|
||||
event_size = size_of(End_Event)
|
||||
if event_size > len(buffer) {
|
||||
@@ -186,6 +192,7 @@ _build_end :: proc(buffer: []u8, ts: f64, tid: u32, pid: u32) -> (event_size: in
|
||||
ev.tid = u32le(tid)
|
||||
ev.ts = f64le(ts)
|
||||
ok = true
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -449,7 +449,14 @@ struct_field_value_by_name :: proc(a: any, field: string, allow_using := false)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@(require_results)
|
||||
struct_field_value :: proc(a: any, field: Struct_Field) -> any {
|
||||
if a == nil { return nil }
|
||||
return any {
|
||||
rawptr(uintptr(a.data) + field.offset),
|
||||
field.type.id,
|
||||
}
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
struct_field_names :: proc(T: typeid) -> []string {
|
||||
|
||||
@@ -621,7 +621,9 @@ __init_context :: proc "contextless" (c: ^Context) {
|
||||
c.allocator.data = nil
|
||||
|
||||
c.temp_allocator.procedure = default_temp_allocator_proc
|
||||
c.temp_allocator.data = &global_default_temp_allocator_data
|
||||
when !NO_DEFAULT_TEMP_ALLOCATOR {
|
||||
c.temp_allocator.data = &global_default_temp_allocator_data
|
||||
}
|
||||
|
||||
when !ODIN_DISABLE_ASSERT {
|
||||
c.assertion_failure_proc = default_assertion_failure_proc
|
||||
|
||||
@@ -15,11 +15,15 @@ container_of :: #force_inline proc "contextless" (ptr: $P/^$Field_Type, $T: type
|
||||
}
|
||||
|
||||
|
||||
@thread_local global_default_temp_allocator_data: Default_Temp_Allocator
|
||||
when !NO_DEFAULT_TEMP_ALLOCATOR {
|
||||
@thread_local global_default_temp_allocator_data: Default_Temp_Allocator
|
||||
}
|
||||
|
||||
@builtin
|
||||
@(builtin, disabled=NO_DEFAULT_TEMP_ALLOCATOR)
|
||||
init_global_temporary_allocator :: proc(size: int, backup_allocator := context.allocator) {
|
||||
default_temp_allocator_init(&global_default_temp_allocator_data, size, backup_allocator)
|
||||
when !NO_DEFAULT_TEMP_ALLOCATOR {
|
||||
default_temp_allocator_init(&global_default_temp_allocator_data, size, backup_allocator)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package runtime
|
||||
|
||||
DEFAULT_TEMP_ALLOCATOR_BACKING_SIZE: int : #config(DEFAULT_TEMP_ALLOCATOR_BACKING_SIZE, 4 * Megabyte)
|
||||
NO_DEFAULT_TEMP_ALLOCATOR: bool : ODIN_OS == .Freestanding || ODIN_OS == .JS || ODIN_DEFAULT_TO_NIL_ALLOCATOR
|
||||
|
||||
|
||||
when ODIN_OS == .Freestanding || ODIN_OS == .JS || ODIN_DEFAULT_TO_NIL_ALLOCATOR {
|
||||
when NO_DEFAULT_TEMP_ALLOCATOR {
|
||||
Default_Temp_Allocator :: struct {}
|
||||
|
||||
default_temp_allocator_init :: proc(s: ^Default_Temp_Allocator, size: int, backing_allocator := context.allocator) {}
|
||||
@@ -54,6 +54,11 @@ when ODIN_OS == .Freestanding || ODIN_OS == .JS || ODIN_DEFAULT_TO_NIL_ALLOCATOR
|
||||
default_temp_allocator_temp_end :: proc(temp: Arena_Temp, loc := #caller_location) {
|
||||
arena_temp_end(temp, loc)
|
||||
}
|
||||
|
||||
@(fini, private)
|
||||
_destroy_temp_allocator_fini :: proc() {
|
||||
default_temp_allocator_destroy(&global_default_temp_allocator_data)
|
||||
}
|
||||
}
|
||||
|
||||
@(deferred_out=default_temp_allocator_temp_end)
|
||||
@@ -72,8 +77,3 @@ default_temp_allocator :: proc(allocator: ^Default_Temp_Allocator) -> Allocator
|
||||
data = allocator,
|
||||
}
|
||||
}
|
||||
|
||||
@(fini, private)
|
||||
_destroy_temp_allocator_fini :: proc() {
|
||||
default_temp_allocator_destroy(&global_default_temp_allocator_data)
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ _INTEGER_DIGITS :: "0123456789abcdefghijklmnopqrstuvwxyz"
|
||||
_INTEGER_DIGITS_VAR := _INTEGER_DIGITS
|
||||
|
||||
when !ODIN_DISALLOW_RTTI {
|
||||
print_any_single :: proc(arg: any) {
|
||||
print_any_single :: proc "contextless" (arg: any) {
|
||||
x := arg
|
||||
if loc, ok := x.(Source_Code_Location); ok {
|
||||
print_caller_location(loc)
|
||||
@@ -49,6 +49,12 @@ when !ODIN_DISALLOW_RTTI {
|
||||
case uint: print_uint(v)
|
||||
case uintptr: print_uintptr(v)
|
||||
|
||||
case bool: print_string("true" if v else "false")
|
||||
case b8: print_string("true" if v else "false")
|
||||
case b16: print_string("true" if v else "false")
|
||||
case b32: print_string("true" if v else "false")
|
||||
case b64: print_string("true" if v else "false")
|
||||
|
||||
case:
|
||||
ti := type_info_of(x.id)
|
||||
#partial switch v in ti.variant {
|
||||
@@ -60,7 +66,7 @@ when !ODIN_DISALLOW_RTTI {
|
||||
print_string("<invalid-value>")
|
||||
}
|
||||
}
|
||||
println_any :: proc(args: ..any) {
|
||||
println_any :: proc "contextless" (args: ..any) {
|
||||
loop: for arg, i in args {
|
||||
if i != 0 {
|
||||
print_string(" ")
|
||||
|
||||
@@ -181,7 +181,7 @@ reverse_sort :: proc(data: $T/[]$E) where ORD(E) {
|
||||
}
|
||||
|
||||
|
||||
reverse_sort_by :: proc(data: $T/[]$E, less: proc(i, j: E) -> bool) where ORD(E) {
|
||||
reverse_sort_by :: proc(data: $T/[]$E, less: proc(i, j: E) -> bool) {
|
||||
context._internal = rawptr(less)
|
||||
sort_by(data, proc(i, j: E) -> bool {
|
||||
k := (proc(i, j: E) -> bool)(context._internal)
|
||||
@@ -189,7 +189,7 @@ reverse_sort_by :: proc(data: $T/[]$E, less: proc(i, j: E) -> bool) where ORD(E)
|
||||
})
|
||||
}
|
||||
|
||||
reverse_sort_by_cmp :: proc(data: $T/[]$E, cmp: proc(i, j: E) -> Ordering) where ORD(E) {
|
||||
reverse_sort_by_cmp :: proc(data: $T/[]$E, cmp: proc(i, j: E) -> Ordering) {
|
||||
context._internal = rawptr(cmp)
|
||||
sort_by_cmp(data, proc(i, j: E) -> Ordering {
|
||||
k := (proc(i, j: E) -> Ordering)(context._internal)
|
||||
|
||||
+44
-15
@@ -556,19 +556,51 @@ parse_f32 :: proc(s: string, n: ^int = nil) -> (value: f32, ok: bool) {
|
||||
return f32(v), ok
|
||||
}
|
||||
|
||||
|
||||
parse_f64 :: proc(str: string, n: ^int = nil) -> (value: f64, ok: bool) {
|
||||
nr: int
|
||||
value, nr, ok = parse_f64_prefix(str)
|
||||
if ok && len(str) != nr {
|
||||
ok = false
|
||||
}
|
||||
if n != nil { n^ = nr }
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
// Parses a 32-bit floating point number from a string.
|
||||
//
|
||||
// Returns ok=false if a base 10 float could not be found,
|
||||
// or if the input string contained more than just the number.
|
||||
//
|
||||
// ```
|
||||
// n, _, ok := strconv.parse_f32("12.34eee");
|
||||
// assert(n == 12.34 && ok);
|
||||
//
|
||||
// n, _, ok = strconv.parse_f32("12.34");
|
||||
// assert(n == 12.34 && ok);
|
||||
// ```
|
||||
parse_f32_prefix :: proc(str: string) -> (value: f32, nr: int, ok: bool) {
|
||||
f: f64
|
||||
f, nr, ok = parse_f64_prefix(str)
|
||||
value = f32(f)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
// Parses a 64-bit floating point number from a string.
|
||||
//
|
||||
// Returns ok=false if a base 10 float could not be found,
|
||||
// or if the input string contained more than just the number.
|
||||
//
|
||||
// ```
|
||||
// n, ok := strconv.parse_f32("12.34eee");
|
||||
// n, _, ok := strconv.parse_f32("12.34eee");
|
||||
// assert(n == 12.34 && ok);
|
||||
//
|
||||
// n, ok = strconv.parse_f32("12.34");
|
||||
// n, _, ok = strconv.parse_f32("12.34");
|
||||
// assert(n == 12.34 && ok);
|
||||
// ```
|
||||
parse_f64 :: proc(str: string, n: ^int = nil) -> (value: f64, ok: bool) {
|
||||
parse_f64_prefix :: proc(str: string) -> (value: f64, nr: int, ok: bool) {
|
||||
common_prefix_len_ignore_case :: proc "contextless" (s, prefix: string) -> int {
|
||||
n := len(prefix)
|
||||
if n > len(s) {
|
||||
@@ -678,8 +710,8 @@ parse_f64 :: proc(str: string, n: ^int = nil) -> (value: f64, ok: bool) {
|
||||
saw_digits = true
|
||||
nd += 1
|
||||
if nd_mant < MAX_MANT_DIGITS {
|
||||
MAX_MANT_DIGITS *= 16
|
||||
MAX_MANT_DIGITS += int(lower(c) - 'a' + 10)
|
||||
mantissa *= 16
|
||||
mantissa += u64(lower(c) - 'a' + 10)
|
||||
nd_mant += 1
|
||||
} else {
|
||||
trunc = true
|
||||
@@ -729,12 +761,11 @@ parse_f64 :: proc(str: string, n: ^int = nil) -> (value: f64, ok: bool) {
|
||||
if mantissa != 0 {
|
||||
exp = decimal_point - nd_mant
|
||||
}
|
||||
// TODO(bill): check underscore correctness
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
|
||||
parse_hex :: proc(s: string, mantissa: u64, exp: int, neg, trunc: bool) -> (f64, bool) {
|
||||
parse_hex :: proc "contextless" (s: string, mantissa: u64, exp: int, neg, trunc: bool) -> (f64, bool) {
|
||||
info := &_f64_info
|
||||
|
||||
mantissa, exp := mantissa, exp
|
||||
@@ -751,7 +782,7 @@ parse_f64 :: proc(str: string, n: ^int = nil) -> (value: f64, ok: bool) {
|
||||
mantissa |= 1
|
||||
}
|
||||
|
||||
for mantissa >> (info.mantbits+2) == 0 {
|
||||
for mantissa != 0 && mantissa >> (info.mantbits+2) == 0 {
|
||||
mantissa = mantissa>>1 | mantissa&1
|
||||
exp += 1
|
||||
}
|
||||
@@ -795,9 +826,6 @@ parse_f64 :: proc(str: string, n: ^int = nil) -> (value: f64, ok: bool) {
|
||||
}
|
||||
|
||||
|
||||
nr: int
|
||||
defer if n != nil { n^ = nr }
|
||||
|
||||
if value, nr, ok = check_special(str); ok {
|
||||
return
|
||||
}
|
||||
@@ -808,7 +836,8 @@ parse_f64 :: proc(str: string, n: ^int = nil) -> (value: f64, ok: bool) {
|
||||
mantissa, exp, neg, trunc, hex, nr = parse_components(str) or_return
|
||||
|
||||
if hex {
|
||||
return parse_hex(str, mantissa, exp, neg, trunc)
|
||||
value, ok = parse_hex(str, mantissa, exp, neg, trunc)
|
||||
return
|
||||
}
|
||||
|
||||
trunc_block: if !trunc {
|
||||
@@ -827,7 +856,7 @@ parse_f64 :: proc(str: string, n: ^int = nil) -> (value: f64, ok: bool) {
|
||||
}
|
||||
switch {
|
||||
case exp == 0:
|
||||
return f, true
|
||||
return f, nr, true
|
||||
case exp > 0 && exp <= 15+22:
|
||||
if exp > 22 {
|
||||
f *= pow10[exp-22]
|
||||
@@ -836,9 +865,9 @@ parse_f64 :: proc(str: string, n: ^int = nil) -> (value: f64, ok: bool) {
|
||||
if f > 1e15 || f < 1e-15 {
|
||||
break trunc_block
|
||||
}
|
||||
return f * pow10[exp], true
|
||||
return f * pow10[exp], nr, true
|
||||
case -22 <= exp && exp < 0:
|
||||
return f / pow10[-exp], true
|
||||
return f / pow10[-exp], nr, true
|
||||
}
|
||||
}
|
||||
d: decimal.Decimal
|
||||
|
||||
@@ -3,9 +3,22 @@ package strings
|
||||
|
||||
import "core:unicode/utf8"
|
||||
|
||||
/*
|
||||
Ascii_Set is designed to store ASCII characters efficiently as a bit-array
|
||||
Each bit in the array corresponds to a specific ASCII character, where the value of the bit (0 or 1)
|
||||
indicates if the character is present in the set or not.
|
||||
*/
|
||||
Ascii_Set :: distinct [8]u32
|
||||
/*
|
||||
Creates an Ascii_Set with unique characters from the input string.
|
||||
|
||||
// create an ascii set of all unique characters in the string
|
||||
Inputs:
|
||||
- chars: A string containing characters to include in the Ascii_Set.
|
||||
|
||||
Returns:
|
||||
- as: An Ascii_Set with unique characters from the input string.
|
||||
- ok: false if any character in the input string is not a valid ASCII character.
|
||||
*/
|
||||
ascii_set_make :: proc(chars: string) -> (as: Ascii_Set, ok: bool) #no_bounds_check {
|
||||
for i in 0..<len(chars) {
|
||||
c := chars[i]
|
||||
@@ -17,8 +30,16 @@ ascii_set_make :: proc(chars: string) -> (as: Ascii_Set, ok: bool) #no_bounds_ch
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
/*
|
||||
Determines if a given char is contained within an Ascii_Set.
|
||||
|
||||
// returns true when the `c` byte is contained in the `as` ascii set
|
||||
Inputs:
|
||||
- as: The Ascii_Set to search.
|
||||
- c: The char to check for in the Ascii_Set.
|
||||
|
||||
Returns:
|
||||
A boolean indicating if the byte is contained in the Ascii_Set (true) or not (false).
|
||||
*/
|
||||
ascii_set_contains :: proc(as: Ascii_Set, c: byte) -> bool #no_bounds_check {
|
||||
return as[c>>5] & (1<<(c&31)) != 0
|
||||
}
|
||||
}
|
||||
|
||||
+517
-102
@@ -4,70 +4,135 @@ import "core:runtime"
|
||||
import "core:unicode/utf8"
|
||||
import "core:strconv"
|
||||
import "core:io"
|
||||
|
||||
Builder_Flush_Proc :: #type proc(b: ^Builder) -> (do_reset: bool)
|
||||
|
||||
/*
|
||||
dynamic byte buffer / string builder with helper procedures
|
||||
the dynamic array is wrapped inside the struct to be more opaque
|
||||
you can use `fmt.sbprint*` procedures with a `^strings.Builder` directly
|
||||
Type definition for a procedure that flushes a Builder
|
||||
|
||||
Inputs:
|
||||
- b: A pointer to the Builder
|
||||
|
||||
Returns:
|
||||
A boolean indicating whether the Builder should be reset
|
||||
*/
|
||||
Builder_Flush_Proc :: #type proc(b: ^Builder) -> (do_reset: bool)
|
||||
/*
|
||||
A dynamic byte buffer / string builder with helper procedures
|
||||
The dynamic array is wrapped inside the struct to be more opaque
|
||||
You can use `fmt.sbprint*` procedures with a `^strings.Builder` directly
|
||||
*/
|
||||
Builder :: struct {
|
||||
buf: [dynamic]byte,
|
||||
}
|
||||
/*
|
||||
Produces a Builder with a default length of 0 and cap of 16
|
||||
|
||||
// return a builder, default length 0 / cap 16 are done through make
|
||||
*Allocates Using Provided Allocator*
|
||||
|
||||
Inputs:
|
||||
- allocator: (default is context.allocator)
|
||||
|
||||
Returns:
|
||||
A new Builder
|
||||
*/
|
||||
builder_make_none :: proc(allocator := context.allocator) -> Builder {
|
||||
return Builder{buf=make([dynamic]byte, allocator)}
|
||||
}
|
||||
/*
|
||||
Produces a Builder with a specified length and cap of max(16,len) byte buffer
|
||||
|
||||
// return a builder, with a set length `len` and cap 16 byte buffer
|
||||
*Allocates Using Provided Allocator*
|
||||
|
||||
Inputs:
|
||||
- len: The desired length of the Builder's buffer
|
||||
- allocator: (default is context.allocator)
|
||||
|
||||
Returns:
|
||||
A new Builder
|
||||
*/
|
||||
builder_make_len :: proc(len: int, allocator := context.allocator) -> Builder {
|
||||
return Builder{buf=make([dynamic]byte, len, allocator)}
|
||||
}
|
||||
/*
|
||||
Produces a Builder with a specified length and cap
|
||||
|
||||
// return a builder, with a set length `len` byte buffer and a custom `cap`
|
||||
*Allocates Using Provided Allocator*
|
||||
|
||||
Inputs:
|
||||
- len: The desired length of the Builder's buffer
|
||||
- cap: The desired capacity of the Builder's buffer, cap is max(cap, len)
|
||||
- allocator: (default is context.allocator)
|
||||
|
||||
Returns:
|
||||
A new Builder
|
||||
*/
|
||||
builder_make_len_cap :: proc(len, cap: int, allocator := context.allocator) -> Builder {
|
||||
return Builder{buf=make([dynamic]byte, len, cap, allocator)}
|
||||
}
|
||||
|
||||
// overload simple `builder_make_*` with or without len / cap parameters
|
||||
builder_make :: proc{
|
||||
builder_make_none,
|
||||
builder_make_len,
|
||||
builder_make_len_cap,
|
||||
}
|
||||
/*
|
||||
Initializes a Builder with a length of 0 and cap of 16
|
||||
It replaces the existing `buf`
|
||||
|
||||
// initialize a builder, default length 0 / cap 16 are done through make
|
||||
// replaces the existing `buf`
|
||||
*Allocates Using Provided Allocator*
|
||||
|
||||
Inputs:
|
||||
- b: A pointer to the Builder
|
||||
- allocator: (default is context.allocator)
|
||||
|
||||
Returns:
|
||||
initialized ^Builder
|
||||
*/
|
||||
builder_init_none :: proc(b: ^Builder, allocator := context.allocator) -> ^Builder {
|
||||
b.buf = make([dynamic]byte, allocator)
|
||||
return b
|
||||
}
|
||||
/*
|
||||
Initializes a Builder with a specified length and cap, which is max(len,16)
|
||||
It replaces the existing `buf`
|
||||
|
||||
// initialize a builder, with a set length `len` and cap 16 byte buffer
|
||||
// replaces the existing `buf`
|
||||
*Allocates Using Provided Allocator*
|
||||
|
||||
Inputs:
|
||||
- b: A pointer to the Builder
|
||||
- len: The desired length of the Builder's buffer
|
||||
- allocator: (default is context.allocator)
|
||||
|
||||
Returns:
|
||||
Initialized ^Builder
|
||||
*/
|
||||
builder_init_len :: proc(b: ^Builder, len: int, allocator := context.allocator) -> ^Builder {
|
||||
b.buf = make([dynamic]byte, len, allocator)
|
||||
return b
|
||||
}
|
||||
/*
|
||||
Initializes a Builder with a specified length and cap
|
||||
It replaces the existing `buf`
|
||||
|
||||
// initialize a builder, with a set length `len` byte buffer and a custom `cap`
|
||||
// replaces the existing `buf`
|
||||
Inputs:
|
||||
- b: A pointer to the Builder
|
||||
- len: The desired length of the Builder's buffer
|
||||
- cap: The desired capacity of the Builder's buffer, actual max(len,cap)
|
||||
- allocator: (default is context.allocator)
|
||||
|
||||
Returns:
|
||||
A pointer to the initialized Builder
|
||||
*/
|
||||
builder_init_len_cap :: proc(b: ^Builder, len, cap: int, allocator := context.allocator) -> ^Builder {
|
||||
b.buf = make([dynamic]byte, len, cap, allocator)
|
||||
return b
|
||||
}
|
||||
|
||||
// overload simple `builder_init_*` with or without len / ap parameters
|
||||
// Overload simple `builder_init_*` with or without len / ap parameters
|
||||
builder_init :: proc{
|
||||
builder_init_none,
|
||||
builder_init_len,
|
||||
builder_init_len_cap,
|
||||
}
|
||||
|
||||
@(private)
|
||||
_builder_stream_vtable := io.Stream_VTable{
|
||||
_builder_stream_vtable_obj := io.Stream_VTable{
|
||||
impl_write = proc(s: io.Stream, p: []byte) -> (n: int, err: io.Error) {
|
||||
b := (^Builder)(s.stream_data)
|
||||
n = write_bytes(b, p)
|
||||
@@ -90,46 +155,95 @@ _builder_stream_vtable := io.Stream_VTable{
|
||||
},
|
||||
impl_destroy = proc(s: io.Stream) -> io.Error {
|
||||
b := (^Builder)(s.stream_data)
|
||||
delete(b.buf)
|
||||
builder_destroy(b)
|
||||
return .None
|
||||
},
|
||||
}
|
||||
// NOTE(dweiler): Work around a miscompilation bug on Linux still.
|
||||
@(private)
|
||||
_builder_stream_vtable := &_builder_stream_vtable_obj
|
||||
/*
|
||||
Returns an io.Stream from a Builder
|
||||
|
||||
// return an `io.Stream` from a builder
|
||||
Inputs:
|
||||
- b: A pointer to the Builder
|
||||
|
||||
Returns:
|
||||
An io.Stream
|
||||
*/
|
||||
to_stream :: proc(b: ^Builder) -> io.Stream {
|
||||
return io.Stream{stream_vtable=&_builder_stream_vtable, stream_data=b}
|
||||
return io.Stream{stream_vtable=_builder_stream_vtable, stream_data=b}
|
||||
}
|
||||
/*
|
||||
Returns an io.Writer from a Builder
|
||||
|
||||
// return an `io.Writer` from a builder
|
||||
Inputs:
|
||||
- b: A pointer to the Builder
|
||||
|
||||
Returns:
|
||||
An io.Writer
|
||||
*/
|
||||
to_writer :: proc(b: ^Builder) -> io.Writer {
|
||||
return io.to_writer(to_stream(b))
|
||||
}
|
||||
/*
|
||||
Deletes the Builder byte buffer content
|
||||
|
||||
// delete and clear the builder byte buffer content
|
||||
Inputs:
|
||||
- b: A pointer to the Builder
|
||||
*/
|
||||
builder_destroy :: proc(b: ^Builder) {
|
||||
delete(b.buf)
|
||||
clear(&b.buf)
|
||||
b.buf = nil
|
||||
}
|
||||
/*
|
||||
Reserves the Builder byte buffer to a specific capacity, when it's higher than before
|
||||
|
||||
// reserve the builfer byte buffer to a specific cap, when it's higher than before
|
||||
Inputs:
|
||||
- b: A pointer to the Builder
|
||||
- cap: The desired capacity for the Builder's buffer
|
||||
*/
|
||||
builder_grow :: proc(b: ^Builder, cap: int) {
|
||||
reserve(&b.buf, cap)
|
||||
}
|
||||
/*
|
||||
Clears the Builder byte buffer content (sets len to zero)
|
||||
|
||||
// clear the builder byte buffer content
|
||||
Inputs:
|
||||
- b: A pointer to the Builder
|
||||
*/
|
||||
builder_reset :: proc(b: ^Builder) {
|
||||
clear(&b.buf)
|
||||
}
|
||||
|
||||
/*
|
||||
create an empty builder with the same slice length as its cap
|
||||
uses the `mem.nil_allocator` to avoid allocation and keep a fixed length
|
||||
used in `fmt.bprint*`
|
||||
|
||||
bytes: [8]byte // <-- gets filled
|
||||
builder := strings.builder_from_bytes(bytes[:])
|
||||
strings.write_byte(&builder, 'a') -> "a"
|
||||
strings.write_byte(&builder, 'b') -> "ab"
|
||||
Creates a Builder from a slice of bytes with the same slice length as its capacity. Used in fmt.bprint*
|
||||
|
||||
*Uses Nil Allocator - Does NOT allocate*
|
||||
|
||||
Inputs:
|
||||
- backing: A slice of bytes to be used as the backing buffer
|
||||
|
||||
Returns:
|
||||
A new Builder
|
||||
|
||||
Example:
|
||||
|
||||
import "core:fmt"
|
||||
import "core:strings"
|
||||
builder_from_bytes_example :: proc() {
|
||||
bytes: [8]byte // <-- gets filled
|
||||
builder := strings.builder_from_bytes(bytes[:])
|
||||
strings.write_byte(&builder, 'a')
|
||||
fmt.println(strings.to_string(builder)) // -> "a"
|
||||
strings.write_byte(&builder, 'b')
|
||||
fmt.println(strings.to_string(builder)) // -> "ab"
|
||||
}
|
||||
|
||||
Output:
|
||||
|
||||
a
|
||||
ab
|
||||
|
||||
*/
|
||||
builder_from_bytes :: proc(backing: []byte) -> Builder {
|
||||
s := transmute(runtime.Raw_Slice)backing
|
||||
@@ -143,36 +257,84 @@ builder_from_bytes :: proc(backing: []byte) -> Builder {
|
||||
buf = transmute([dynamic]byte)d,
|
||||
}
|
||||
}
|
||||
// Alias to `builder_from_bytes`
|
||||
builder_from_slice :: builder_from_bytes
|
||||
/*
|
||||
Casts the Builder byte buffer to a string and returns it
|
||||
|
||||
// cast the builder byte buffer to a string and return it
|
||||
Inputs:
|
||||
- b: A Builder
|
||||
|
||||
Returns:
|
||||
The contents of the Builder's buffer, as a string
|
||||
*/
|
||||
to_string :: proc(b: Builder) -> string {
|
||||
return string(b.buf[:])
|
||||
}
|
||||
/*
|
||||
Returns the length of the Builder's buffer, in bytes
|
||||
|
||||
// return the length of the builder byte buffer
|
||||
Inputs:
|
||||
- b: A Builder
|
||||
|
||||
Returns:
|
||||
The length of the Builder's buffer
|
||||
*/
|
||||
builder_len :: proc(b: Builder) -> int {
|
||||
return len(b.buf)
|
||||
}
|
||||
/*
|
||||
Returns the capacity of the Builder's buffer, in bytes
|
||||
|
||||
// return the cap of the builder byte buffer
|
||||
Inputs:
|
||||
- b: A Builder
|
||||
|
||||
Returns:
|
||||
The capacity of the Builder's buffer
|
||||
*/
|
||||
builder_cap :: proc(b: Builder) -> int {
|
||||
return cap(b.buf)
|
||||
}
|
||||
/*
|
||||
The free space left in the Builder's buffer, in bytes
|
||||
|
||||
// returns the space left in the builder byte buffer to use up
|
||||
Inputs:
|
||||
- b: A Builder
|
||||
|
||||
Returns:
|
||||
The available space left in the Builder's buffer
|
||||
*/
|
||||
builder_space :: proc(b: Builder) -> int {
|
||||
return cap(b.buf) - len(b.buf)
|
||||
}
|
||||
|
||||
/*
|
||||
appends a byte to the builder, returns the append diff
|
||||
Appends a byte to the Builder and returns the number of bytes appended
|
||||
|
||||
Inputs:
|
||||
- b: A pointer to the Builder
|
||||
- x: The byte to be appended
|
||||
|
||||
Returns:
|
||||
The number of bytes appended
|
||||
|
||||
NOTE: The backing dynamic array may be fixed in capacity or fail to resize, `n` states the number actually written.
|
||||
|
||||
Example:
|
||||
|
||||
import "core:fmt"
|
||||
import "core:strings"
|
||||
|
||||
write_byte_example :: proc() {
|
||||
builder := strings.builder_make()
|
||||
strings.write_byte(&builder, 'a') // 1
|
||||
strings.write_byte(&builder, 'b') // 1
|
||||
fmt.println(strings.to_string(builder)) // -> ab
|
||||
}
|
||||
|
||||
Output:
|
||||
|
||||
ab
|
||||
|
||||
builder := strings.builder_make()
|
||||
strings.write_byte(&builder, 'a') // 1
|
||||
strings.write_byte(&builder, 'b') // 1
|
||||
strings.write_byte(&builder, 'c') // 1
|
||||
fmt.println(strings.to_string(builder)) // -> abc
|
||||
*/
|
||||
write_byte :: proc(b: ^Builder, x: byte) -> (n: int) {
|
||||
n0 := len(b.buf)
|
||||
@@ -180,14 +342,29 @@ write_byte :: proc(b: ^Builder, x: byte) -> (n: int) {
|
||||
n1 := len(b.buf)
|
||||
return n1-n0
|
||||
}
|
||||
|
||||
/*
|
||||
appends a slice of bytes to the builder, returns the append diff
|
||||
Appends a slice of bytes to the Builder and returns the number of bytes appended
|
||||
|
||||
builder := strings.builder_make()
|
||||
bytes := [?]byte { 'a', 'b', 'c' }
|
||||
strings.write_bytes(&builder, bytes[:]) // 3
|
||||
fmt.println(strings.to_string(builder)) // -> abc
|
||||
Inputs:
|
||||
- b: A pointer to the Builder
|
||||
- x: The slice of bytes to be appended
|
||||
|
||||
Example:
|
||||
|
||||
import "core:fmt"
|
||||
import "core:strings"
|
||||
|
||||
write_bytes_example :: proc() {
|
||||
builder := strings.builder_make()
|
||||
bytes := [?]byte { 'a', 'b', 'c' }
|
||||
strings.write_bytes(&builder, bytes[:]) // 3
|
||||
fmt.println(strings.to_string(builder)) // -> abc
|
||||
}
|
||||
|
||||
NOTE: The backing dynamic array may be fixed in capacity or fail to resize, `n` states the number actually written.
|
||||
|
||||
Returns:
|
||||
The number of bytes appended
|
||||
*/
|
||||
write_bytes :: proc(b: ^Builder, x: []byte) -> (n: int) {
|
||||
n0 := len(b.buf)
|
||||
@@ -195,42 +372,99 @@ write_bytes :: proc(b: ^Builder, x: []byte) -> (n: int) {
|
||||
n1 := len(b.buf)
|
||||
return n1-n0
|
||||
}
|
||||
|
||||
/*
|
||||
appends a single rune into the builder, returns written rune size and an `io.Error`
|
||||
Appends a single rune to the Builder and returns the number of bytes written and an `io.Error`
|
||||
|
||||
Inputs:
|
||||
- b: A pointer to the Builder
|
||||
- r: The rune to be appended
|
||||
|
||||
Returns:
|
||||
The number of bytes written and an io.Error (if any)
|
||||
|
||||
NOTE: The backing dynamic array may be fixed in capacity or fail to resize, `n` states the number actually written.
|
||||
|
||||
Example:
|
||||
|
||||
import "core:fmt"
|
||||
import "core:strings"
|
||||
|
||||
write_rune_example :: proc() {
|
||||
builder := strings.builder_make()
|
||||
strings.write_rune(&builder, 'ä') // 2 None
|
||||
strings.write_rune(&builder, 'b') // 1 None
|
||||
fmt.println(strings.to_string(builder)) // -> äb
|
||||
}
|
||||
|
||||
Output:
|
||||
|
||||
äb
|
||||
|
||||
builder := strings.builder_make()
|
||||
strings.write_rune(&builder, 'ä') // 2 None
|
||||
strings.write_rune(&builder, 'b') // 1 None
|
||||
strings.write_rune(&builder, 'c') // 1 None
|
||||
fmt.println(strings.to_string(builder)) // -> äbc
|
||||
*/
|
||||
write_rune :: proc(b: ^Builder, r: rune) -> (int, io.Error) {
|
||||
return io.write_rune(to_writer(b), r)
|
||||
}
|
||||
|
||||
/*
|
||||
appends a quoted rune into the builder, returns written size
|
||||
Appends a quoted rune to the Builder and returns the number of bytes written
|
||||
|
||||
Inputs:
|
||||
- b: A pointer to the Builder
|
||||
- r: The rune to be appended
|
||||
|
||||
Returns:
|
||||
The number of bytes written
|
||||
|
||||
NOTE: The backing dynamic array may be fixed in capacity or fail to resize, `n` states the number actually written.
|
||||
|
||||
Example:
|
||||
|
||||
import "core:fmt"
|
||||
import "core:strings"
|
||||
|
||||
write_quoted_rune_example :: proc() {
|
||||
builder := strings.builder_make()
|
||||
strings.write_string(&builder, "abc") // 3
|
||||
strings.write_quoted_rune(&builder, 'ä') // 4
|
||||
strings.write_string(&builder, "abc") // 3
|
||||
fmt.println(strings.to_string(builder)) // -> abc'ä'abc
|
||||
}
|
||||
|
||||
Output:
|
||||
|
||||
abc'ä'abc
|
||||
|
||||
builder := strings.builder_make()
|
||||
strings.write_string(&builder, "abc") // 3
|
||||
strings.write_quoted_rune(&builder, 'ä') // 4
|
||||
strings.write_string(&builder, "abc") // 3
|
||||
fmt.println(strings.to_string(builder)) // -> abc'ä'abc
|
||||
*/
|
||||
write_quoted_rune :: proc(b: ^Builder, r: rune) -> (n: int) {
|
||||
return io.write_quoted_rune(to_writer(b), r)
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
appends a string to the builder, return the written byte size
|
||||
|
||||
builder := strings.builder_make()
|
||||
strings.write_string(&builder, "a") // 1
|
||||
strings.write_string(&builder, "bc") // 2
|
||||
strings.write_string(&builder, "xyz") // 3
|
||||
fmt.println(strings.to_string(builder)) // -> abcxyz
|
||||
Appends a string to the Builder and returns the number of bytes written
|
||||
|
||||
Inputs:
|
||||
- b: A pointer to the Builder
|
||||
- s: The string to be appended
|
||||
|
||||
Returns:
|
||||
The number of bytes written
|
||||
|
||||
NOTE: The backing dynamic array may be fixed in capacity or fail to resize, `n` states the number actually written.
|
||||
|
||||
Example:
|
||||
|
||||
import "core:fmt"
|
||||
import "core:strings"
|
||||
|
||||
write_string_example :: proc() {
|
||||
builder := strings.builder_make()
|
||||
strings.write_string(&builder, "a") // 1
|
||||
strings.write_string(&builder, "bc") // 2
|
||||
fmt.println(strings.to_string(builder)) // -> abc
|
||||
}
|
||||
|
||||
Output:
|
||||
|
||||
abc
|
||||
|
||||
*/
|
||||
write_string :: proc(b: ^Builder, s: string) -> (n: int) {
|
||||
n0 := len(b.buf)
|
||||
@@ -238,10 +472,15 @@ write_string :: proc(b: ^Builder, s: string) -> (n: int) {
|
||||
n1 := len(b.buf)
|
||||
return n1-n0
|
||||
}
|
||||
/*
|
||||
Pops and returns the last byte in the Builder or 0 when the Builder is empty
|
||||
|
||||
Inputs:
|
||||
- b: A pointer to the Builder
|
||||
|
||||
// pops and returns the last byte in the builder
|
||||
// returns 0 when the builder is empty
|
||||
Returns:
|
||||
The last byte in the Builder or 0 if empty
|
||||
*/
|
||||
pop_byte :: proc(b: ^Builder) -> (r: byte) {
|
||||
if len(b.buf) == 0 {
|
||||
return 0
|
||||
@@ -252,9 +491,15 @@ pop_byte :: proc(b: ^Builder) -> (r: byte) {
|
||||
d.len = max(d.len-1, 0)
|
||||
return
|
||||
}
|
||||
/*
|
||||
Pops the last rune in the Builder and returns the popped rune and its rune width or (0, 0) if empty
|
||||
|
||||
// pops the last rune in the builder and returns the popped rune and its rune width
|
||||
// returns 0, 0 when the builder is empty
|
||||
Inputs:
|
||||
- b: A pointer to the Builder
|
||||
|
||||
Returns:
|
||||
The popped rune and its rune width or (0, 0) if empty
|
||||
*/
|
||||
pop_rune :: proc(b: ^Builder) -> (r: rune, width: int) {
|
||||
if len(b.buf) == 0 {
|
||||
return 0, 0
|
||||
@@ -265,41 +510,116 @@ pop_rune :: proc(b: ^Builder) -> (r: rune, width: int) {
|
||||
d.len = max(d.len-width, 0)
|
||||
return
|
||||
}
|
||||
|
||||
@(private)
|
||||
DIGITS_LOWER := "0123456789abcdefx"
|
||||
|
||||
/*
|
||||
append a quoted string into the builder, return the written byte size
|
||||
Inputs:
|
||||
- b: A pointer to the Builder
|
||||
- str: The string to be quoted and appended
|
||||
- quote: The optional quote character (default is double quotes)
|
||||
|
||||
Returns:
|
||||
The number of bytes written
|
||||
|
||||
NOTE: The backing dynamic array may be fixed in capacity or fail to resize, `n` states the number actually written.
|
||||
|
||||
Example:
|
||||
|
||||
import "core:fmt"
|
||||
import "core:strings"
|
||||
|
||||
write_quoted_string_example :: proc() {
|
||||
builder := strings.builder_make()
|
||||
strings.write_quoted_string(&builder, "a") // 3
|
||||
strings.write_quoted_string(&builder, "bc", '\'') // 4
|
||||
strings.write_quoted_string(&builder, "xyz") // 5
|
||||
fmt.println(strings.to_string(builder))
|
||||
}
|
||||
|
||||
Output:
|
||||
|
||||
"a"'bc'"xyz"
|
||||
|
||||
builder := strings.builder_make()
|
||||
strings.write_quoted_string(&builder, "a") // 3
|
||||
strings.write_quoted_string(&builder, "bc", '\'') // 4
|
||||
strings.write_quoted_string(&builder, "xyz") // 5
|
||||
fmt.println(strings.to_string(builder)) // -> "a"'bc'xyz"
|
||||
*/
|
||||
write_quoted_string :: proc(b: ^Builder, str: string, quote: byte = '"') -> (n: int) {
|
||||
n, _ = io.write_quoted_string(to_writer(b), str, quote)
|
||||
return
|
||||
}
|
||||
/*
|
||||
Appends a rune to the Builder and returns the number of bytes written
|
||||
|
||||
Inputs:
|
||||
- b: A pointer to the Builder
|
||||
- r: The rune to be appended
|
||||
- write_quote: Optional boolean flag to wrap in single-quotes (') (default is true)
|
||||
|
||||
// appends a rune to the builder, optional `write_quote` boolean tag, returns the written rune size
|
||||
Returns:
|
||||
The number of bytes written
|
||||
|
||||
NOTE: The backing dynamic array may be fixed in capacity or fail to resize, `n` states the number actually written.
|
||||
|
||||
Example:
|
||||
|
||||
import "core:fmt"
|
||||
import "core:strings"
|
||||
|
||||
write_encoded_rune_example :: proc() {
|
||||
builder := strings.builder_make()
|
||||
strings.write_encoded_rune(&builder, 'a', false) // 1
|
||||
strings.write_encoded_rune(&builder, '\"', true) // 3
|
||||
strings.write_encoded_rune(&builder, 'x', false) // 1
|
||||
fmt.println(strings.to_string(builder))
|
||||
}
|
||||
|
||||
Output:
|
||||
|
||||
a'"'x
|
||||
|
||||
*/
|
||||
write_encoded_rune :: proc(b: ^Builder, r: rune, write_quote := true) -> (n: int) {
|
||||
n, _ = io.write_encoded_rune(to_writer(b), r, write_quote)
|
||||
return
|
||||
|
||||
}
|
||||
/*
|
||||
Appends an escaped rune to the Builder and returns the number of bytes written
|
||||
|
||||
// appends a rune to the builder, fully written out in case of escaped runes e.g. '\a' will be written as such
|
||||
// when `r` and `quote` match and `quote` is `\\` - they will be written as two slashes
|
||||
// `html_safe` flag in case the runes '<', '>', '&' should be encoded as digits e.g. `\u0026`
|
||||
Inputs:
|
||||
- b: A pointer to the Builder
|
||||
- r: The rune to be appended
|
||||
- quote: The quote character
|
||||
- html_safe: Optional boolean flag to encode '<', '>', '&' as digits (default is false)
|
||||
|
||||
**Usage**
|
||||
- '\a' will be written as such
|
||||
- `r` and `quote` match and `quote` is `\\` - they will be written as two slashes
|
||||
- `html_safe` flag in case the runes '<', '>', '&' should be encoded as digits e.g. `\u0026`
|
||||
|
||||
NOTE: The backing dynamic array may be fixed in capacity or fail to resize, `n` states the number actually written.
|
||||
|
||||
Returns:
|
||||
The number of bytes written
|
||||
*/
|
||||
write_escaped_rune :: proc(b: ^Builder, r: rune, quote: byte, html_safe := false) -> (n: int) {
|
||||
n, _ = io.write_escaped_rune(to_writer(b), r, quote, html_safe)
|
||||
return
|
||||
}
|
||||
/*
|
||||
Writes a f64 value to the Builder and returns the number of characters written
|
||||
|
||||
// writes a f64 value into the builder, returns the written amount of characters
|
||||
Inputs:
|
||||
- b: A pointer to the Builder
|
||||
- f: The f64 value to be appended
|
||||
- fmt: The format byte
|
||||
- prec: The precision
|
||||
- bit_size: The bit size
|
||||
- always_signed: Optional boolean flag to always include the sign (default is false)
|
||||
|
||||
NOTE: The backing dynamic array may be fixed in capacity or fail to resize, `n` states the number actually written.
|
||||
|
||||
Returns:
|
||||
The number of characters written
|
||||
*/
|
||||
write_float :: proc(b: ^Builder, f: f64, fmt: byte, prec, bit_size: int, always_signed := false) -> (n: int) {
|
||||
buf: [384]byte
|
||||
s := strconv.append_float(buf[:], f, fmt, prec, bit_size)
|
||||
@@ -310,8 +630,20 @@ write_float :: proc(b: ^Builder, f: f64, fmt: byte, prec, bit_size: int, always_
|
||||
}
|
||||
return write_string(b, s)
|
||||
}
|
||||
/*
|
||||
Writes a f16 value to the Builder and returns the number of characters written
|
||||
|
||||
// writes a f16 value into the builder, returns the written amount of characters
|
||||
Inputs:
|
||||
- b: A pointer to the Builder
|
||||
- f: The f16 value to be appended
|
||||
- fmt: The format byte
|
||||
- always_signed: Optional boolean flag to always include the sign
|
||||
|
||||
NOTE: The backing dynamic array may be fixed in capacity or fail to resize, `n` states the number actually written.
|
||||
|
||||
Returns:
|
||||
The number of characters written
|
||||
*/
|
||||
write_f16 :: proc(b: ^Builder, f: f16, fmt: byte, always_signed := false) -> (n: int) {
|
||||
buf: [384]byte
|
||||
s := strconv.append_float(buf[:], f64(f), fmt, 2*size_of(f), 8*size_of(f))
|
||||
@@ -320,8 +652,38 @@ write_f16 :: proc(b: ^Builder, f: f16, fmt: byte, always_signed := false) -> (n:
|
||||
}
|
||||
return write_string(b, s)
|
||||
}
|
||||
/*
|
||||
Writes a f32 value to the Builder and returns the number of characters written
|
||||
|
||||
// writes a f32 value into the builder, returns the written amount of characters
|
||||
Inputs:
|
||||
- b: A pointer to the Builder
|
||||
- f: The f32 value to be appended
|
||||
- fmt: The format byte
|
||||
- always_signed: Optional boolean flag to always include the sign
|
||||
|
||||
Returns:
|
||||
The number of characters written
|
||||
|
||||
NOTE: The backing dynamic array may be fixed in capacity or fail to resize, `n` states the number actually written.
|
||||
|
||||
Example:
|
||||
|
||||
import "core:fmt"
|
||||
import "core:strings"
|
||||
|
||||
write_f32_example :: proc() {
|
||||
builder := strings.builder_make()
|
||||
strings.write_f32(&builder, 3.14159, 'f') // 6
|
||||
strings.write_string(&builder, " - ") // 3
|
||||
strings.write_f32(&builder, -0.123, 'e') // 8
|
||||
fmt.println(strings.to_string(builder)) // -> 3.14159012 - -1.23000003e-01
|
||||
}
|
||||
|
||||
Output:
|
||||
|
||||
3.14159012 - -1.23000003e-01
|
||||
|
||||
*/
|
||||
write_f32 :: proc(b: ^Builder, f: f32, fmt: byte, always_signed := false) -> (n: int) {
|
||||
buf: [384]byte
|
||||
s := strconv.append_float(buf[:], f64(f), fmt, 2*size_of(f), 8*size_of(f))
|
||||
@@ -330,8 +692,20 @@ write_f32 :: proc(b: ^Builder, f: f32, fmt: byte, always_signed := false) -> (n:
|
||||
}
|
||||
return write_string(b, s)
|
||||
}
|
||||
/*
|
||||
Writes a f32 value to the Builder and returns the number of characters written
|
||||
|
||||
// writes a f64 value into the builder, returns the written amount of characters
|
||||
Inputs:
|
||||
- b: A pointer to the Builder
|
||||
- f: The f32 value to be appended
|
||||
- fmt: The format byte
|
||||
- always_signed: Optional boolean flag to always include the sign
|
||||
|
||||
NOTE: The backing dynamic array may be fixed in capacity or fail to resize, `n` states the number actually written.
|
||||
|
||||
Returns:
|
||||
The number of characters written
|
||||
*/
|
||||
write_f64 :: proc(b: ^Builder, f: f64, fmt: byte, always_signed := false) -> (n: int) {
|
||||
buf: [384]byte
|
||||
s := strconv.append_float(buf[:], f64(f), fmt, 2*size_of(f), 8*size_of(f))
|
||||
@@ -340,30 +714,71 @@ write_f64 :: proc(b: ^Builder, f: f64, fmt: byte, always_signed := false) -> (n:
|
||||
}
|
||||
return write_string(b, s)
|
||||
}
|
||||
/*
|
||||
Writes a u64 value to the Builder and returns the number of characters written
|
||||
|
||||
Inputs:
|
||||
- b: A pointer to the Builder
|
||||
- i: The u64 value to be appended
|
||||
- base: The optional base for the numeric representation
|
||||
|
||||
NOTE: The backing dynamic array may be fixed in capacity or fail to resize, `n` states the number actually written.
|
||||
|
||||
// writes a u64 value `i` in `base` = 10 into the builder, returns the written amount of characters
|
||||
Returns:
|
||||
The number of characters written
|
||||
*/
|
||||
write_u64 :: proc(b: ^Builder, i: u64, base: int = 10) -> (n: int) {
|
||||
buf: [32]byte
|
||||
s := strconv.append_bits(buf[:], i, base, false, 64, strconv.digits, nil)
|
||||
return write_string(b, s)
|
||||
}
|
||||
/*
|
||||
Writes a i64 value to the Builder and returns the number of characters written
|
||||
|
||||
// writes a i64 value `i` in `base` = 10 into the builder, returns the written amount of characters
|
||||
Inputs:
|
||||
- b: A pointer to the Builder
|
||||
- i: The i64 value to be appended
|
||||
- base: The optional base for the numeric representation
|
||||
|
||||
NOTE: The backing dynamic array may be fixed in capacity or fail to resize, `n` states the number actually written.
|
||||
|
||||
Returns:
|
||||
The number of characters written
|
||||
*/
|
||||
write_i64 :: proc(b: ^Builder, i: i64, base: int = 10) -> (n: int) {
|
||||
buf: [32]byte
|
||||
s := strconv.append_bits(buf[:], u64(i), base, true, 64, strconv.digits, nil)
|
||||
return write_string(b, s)
|
||||
}
|
||||
/*
|
||||
Writes a uint value to the Builder and returns the number of characters written
|
||||
|
||||
// writes a uint value `i` in `base` = 10 into the builder, returns the written amount of characters
|
||||
Inputs:
|
||||
- b: A pointer to the Builder
|
||||
- i: The uint value to be appended
|
||||
- base: The optional base for the numeric representation
|
||||
|
||||
NOTE: The backing dynamic array may be fixed in capacity or fail to resize, `n` states the number actually written.
|
||||
|
||||
Returns:
|
||||
The number of characters written
|
||||
*/
|
||||
write_uint :: proc(b: ^Builder, i: uint, base: int = 10) -> (n: int) {
|
||||
return write_u64(b, u64(i), base)
|
||||
}
|
||||
/*
|
||||
Writes a int value to the Builder and returns the number of characters written
|
||||
|
||||
// writes a int value `i` in `base` = 10 into the builder, returns the written amount of characters
|
||||
Inputs:
|
||||
- b: A pointer to the Builder
|
||||
- i: The int value to be appended
|
||||
- base: The optional base for the numeric representation
|
||||
|
||||
NOTE: The backing dynamic array may be fixed in capacity or fail to resize, `n` states the number actually written.
|
||||
|
||||
Returns:
|
||||
The number of characters written
|
||||
*/
|
||||
write_int :: proc(b: ^Builder, i: int, base: int = 10) -> (n: int) {
|
||||
return write_i64(b, i64(i), base)
|
||||
}
|
||||
|
||||
|
||||
+322
-75
@@ -4,6 +4,21 @@ import "core:io"
|
||||
import "core:unicode"
|
||||
import "core:unicode/utf8"
|
||||
|
||||
/*
|
||||
Converts invalid UTF-8 sequences in the input string `s` to the `replacement` string.
|
||||
|
||||
*Allocates Using Provided Allocator*
|
||||
|
||||
Inputs:
|
||||
- s: Input string that may contain invalid UTF-8 sequences.
|
||||
- replacement: String to replace invalid UTF-8 sequences with.
|
||||
- allocator: (default: context.allocator).
|
||||
|
||||
WARNING: Allocation does not occur when len(s) == 0
|
||||
|
||||
Returns:
|
||||
A valid UTF-8 string with invalid sequences replaced by `replacement`.
|
||||
*/
|
||||
to_valid_utf8 :: proc(s, replacement: string, allocator := context.allocator) -> string {
|
||||
if len(s) == 0 {
|
||||
return ""
|
||||
@@ -33,7 +48,7 @@ to_valid_utf8 :: proc(s, replacement: string, allocator := context.allocator) ->
|
||||
|
||||
invalid := false
|
||||
|
||||
for i := 0; i < len(s); /**/ {
|
||||
for i := 0; i < len(s); /**/{
|
||||
c := s[i]
|
||||
if c < utf8.RUNE_SELF {
|
||||
i += 1
|
||||
@@ -57,13 +72,31 @@ to_valid_utf8 :: proc(s, replacement: string, allocator := context.allocator) ->
|
||||
}
|
||||
return to_string(b)
|
||||
}
|
||||
|
||||
/*
|
||||
returns the input string `s` with all runes set to lowered case
|
||||
always allocates using the `allocator`
|
||||
Converts the input string `s` to all lowercase characters.
|
||||
|
||||
*Allocates Using Provided Allocator*
|
||||
|
||||
Inputs:
|
||||
- s: Input string to be converted.
|
||||
- allocator: (default: context.allocator).
|
||||
|
||||
Returns:
|
||||
A new string with all characters converted to lowercase.
|
||||
|
||||
Example:
|
||||
|
||||
import "core:fmt"
|
||||
import "core:strings"
|
||||
|
||||
to_lower_example :: proc() {
|
||||
fmt.println(strings.to_lower("TeST"))
|
||||
}
|
||||
|
||||
Output:
|
||||
|
||||
test
|
||||
|
||||
strings.to_lower("test") -> test
|
||||
strings.to_lower("Test") -> test
|
||||
*/
|
||||
to_lower :: proc(s: string, allocator := context.allocator) -> string {
|
||||
b: Builder
|
||||
@@ -73,13 +106,31 @@ to_lower :: proc(s: string, allocator := context.allocator) -> string {
|
||||
}
|
||||
return to_string(b)
|
||||
}
|
||||
|
||||
/*
|
||||
returns the input string `s` with all runes set to upper case
|
||||
always allocates using the `allocator`
|
||||
Converts the input string `s` to all uppercase characters.
|
||||
|
||||
*Allocates Using Provided Allocator*
|
||||
|
||||
Inputs:
|
||||
- s: Input string to be converted.
|
||||
- allocator: (default: context.allocator).
|
||||
|
||||
Returns:
|
||||
A new string with all characters converted to uppercase.
|
||||
|
||||
Example:
|
||||
|
||||
import "core:fmt"
|
||||
import "core:strings"
|
||||
|
||||
to_upper_example :: proc() {
|
||||
fmt.println(strings.to_upper("Test"))
|
||||
}
|
||||
|
||||
Output:
|
||||
|
||||
TEST
|
||||
|
||||
strings.to_lower("test") -> TEST
|
||||
strings.to_lower("Test") -> TEST
|
||||
*/
|
||||
to_upper :: proc(s: string, allocator := context.allocator) -> string {
|
||||
b: Builder
|
||||
@@ -89,21 +140,38 @@ to_upper :: proc(s: string, allocator := context.allocator) -> string {
|
||||
}
|
||||
return to_string(b)
|
||||
}
|
||||
/*
|
||||
Checks if the rune `r` is a delimiter (' ', '-', or '_').
|
||||
|
||||
// returns true when the `c` rune is a space, '-' or '_'
|
||||
// useful when treating strings like words in a text editor or html paths
|
||||
is_delimiter :: proc(c: rune) -> bool {
|
||||
return c == '-' || c == '_' || is_space(c)
|
||||
Inputs:
|
||||
- r: Rune to check for delimiter status.
|
||||
|
||||
Returns:
|
||||
True if `r` is a delimiter, false otherwise.
|
||||
*/
|
||||
is_delimiter :: proc(r: rune) -> bool {
|
||||
return r == '-' || r == '_' || is_space(r)
|
||||
}
|
||||
/*
|
||||
Checks if the rune `r` is a non-alphanumeric or space character.
|
||||
|
||||
// returns true when the `r` rune is a non alpha or `unicode.is_space` rune
|
||||
Inputs:
|
||||
- r: Rune to check for separator status.
|
||||
|
||||
Returns:
|
||||
True if `r` is a non-alpha or `unicode.is_space` rune.
|
||||
*/
|
||||
is_separator :: proc(r: rune) -> bool {
|
||||
if r <= 0x7f {
|
||||
switch r {
|
||||
case '0'..='9': return false
|
||||
case 'a'..='z': return false
|
||||
case 'A'..='Z': return false
|
||||
case '_': return false
|
||||
case '0' ..= '9':
|
||||
return false
|
||||
case 'a' ..= 'z':
|
||||
return false
|
||||
case 'A' ..= 'Z':
|
||||
return false
|
||||
case '_':
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
@@ -115,12 +183,46 @@ is_separator :: proc(r: rune) -> bool {
|
||||
|
||||
return unicode.is_space(r)
|
||||
}
|
||||
|
||||
/*
|
||||
iterator that loops through the string and calls the callback with the `prev`, `curr` and `next` rune
|
||||
on empty string `s` the callback gets called once with empty runes
|
||||
Iterates over a string, calling a callback for each rune with the previous, current, and next runes as arguments.
|
||||
|
||||
Inputs:
|
||||
- w: An io.Writer to be used by the callback for writing output.
|
||||
- s: The input string to be iterated over.
|
||||
- callback: A procedure to be called for each rune in the string, with arguments (w: io.Writer, prev, curr, next: rune).
|
||||
The callback can utilize the provided io.Writer to write output during the iteration.
|
||||
|
||||
Example:
|
||||
|
||||
import "core:fmt"
|
||||
import "core:strings"
|
||||
import "core:io"
|
||||
|
||||
string_case_iterator_example :: proc() {
|
||||
my_callback :: proc(w: io.Writer, prev, curr, next: rune) {
|
||||
fmt.println("my_callback", curr) // <-- Custom logic here
|
||||
}
|
||||
s := "hello"
|
||||
b: strings.Builder
|
||||
strings.builder_init_len(&b, len(s))
|
||||
w := strings.to_writer(&b)
|
||||
strings.string_case_iterator(w, s, my_callback)
|
||||
}
|
||||
|
||||
Output:
|
||||
|
||||
my_callback h
|
||||
my_callback e
|
||||
my_callback l
|
||||
my_callback l
|
||||
my_callback o
|
||||
|
||||
*/
|
||||
string_case_iterator :: proc(w: io.Writer, s: string, callback: proc(w: io.Writer, prev, curr, next: rune)) {
|
||||
string_case_iterator :: proc(
|
||||
w: io.Writer,
|
||||
s: string,
|
||||
callback: proc(w: io.Writer, prev, curr, next: rune),
|
||||
) {
|
||||
prev, curr: rune
|
||||
for next in s {
|
||||
if curr == 0 {
|
||||
@@ -139,10 +241,20 @@ string_case_iterator :: proc(w: io.Writer, s: string, callback: proc(w: io.Write
|
||||
callback(w, prev, curr, 0)
|
||||
}
|
||||
}
|
||||
|
||||
// Alias to `to_camel_case`
|
||||
to_lower_camel_case :: to_camel_case
|
||||
/*
|
||||
Converts the input string `s` to "lowerCamelCase".
|
||||
|
||||
// converts the `s` string to "lowerCamelCase"
|
||||
*Allocates Using Provided Allocator*
|
||||
|
||||
Inputs:
|
||||
- s: Input string to be converted.
|
||||
- allocator: (default: context.allocator).
|
||||
|
||||
Returns:
|
||||
A "lowerCamelCase" formatted string.
|
||||
*/
|
||||
to_camel_case :: proc(s: string, allocator := context.allocator) -> string {
|
||||
s := s
|
||||
s = trim_space(s)
|
||||
@@ -164,10 +276,20 @@ to_camel_case :: proc(s: string, allocator := context.allocator) -> string {
|
||||
|
||||
return to_string(b)
|
||||
}
|
||||
|
||||
// Alias to `to_pascal_case`
|
||||
to_upper_camel_case :: to_pascal_case
|
||||
/*
|
||||
Converts the input string `s` to "UpperCamelCase" (PascalCase).
|
||||
|
||||
// converts the `s` string to "PascalCase"
|
||||
*Allocates Using Provided Allocator*
|
||||
|
||||
Inputs:
|
||||
- s: Input string to be converted.
|
||||
- allocator: (default: context.allocator).
|
||||
|
||||
Returns:
|
||||
A "PascalCase" formatted string.
|
||||
*/
|
||||
to_pascal_case :: proc(s: string, allocator := context.allocator) -> string {
|
||||
s := s
|
||||
s = trim_space(s)
|
||||
@@ -189,17 +311,44 @@ to_pascal_case :: proc(s: string, allocator := context.allocator) -> string {
|
||||
|
||||
return to_string(b)
|
||||
}
|
||||
/*
|
||||
Returns a string converted to a delimiter-separated case with configurable casing
|
||||
|
||||
/*
|
||||
returns the `s` string to words seperated by the given `delimiter` rune
|
||||
all runes will be upper or lowercased based on the `all_uppercase` bool
|
||||
*Allocates Using Provided Allocator*
|
||||
|
||||
Inputs:
|
||||
- s: The input string to be converted
|
||||
- delimiter: The rune to be used as the delimiter between words
|
||||
- all_upper_case: A boolean indicating if the output should be all uppercased (true) or lowercased (false)
|
||||
- allocator: (default: context.allocator).
|
||||
|
||||
Returns:
|
||||
The converted string
|
||||
|
||||
Example:
|
||||
|
||||
import "core:fmt"
|
||||
import "core:strings"
|
||||
|
||||
to_delimiter_case_example :: proc() {
|
||||
fmt.println(strings.to_delimiter_case("Hello World", '_', false))
|
||||
fmt.println(strings.to_delimiter_case("Hello World", ' ', true))
|
||||
fmt.println(strings.to_delimiter_case("aBC", '_', false))
|
||||
}
|
||||
|
||||
Output:
|
||||
|
||||
hello_world
|
||||
HELLO WORLD
|
||||
a_bc
|
||||
|
||||
strings.to_delimiter_case("Hello World", '_', false) -> hello_world
|
||||
strings.to_delimiter_case("Hello World", ' ', true) -> HELLO WORLD
|
||||
strings.to_delimiter_case("Hello World", ' ', true) -> HELLO WORLD
|
||||
strings.to_delimiter_case("aBC", '_', false) -> a_b_c
|
||||
*/
|
||||
to_delimiter_case :: proc(s: string, delimiter: rune, all_upper_case: bool, allocator := context.allocator) -> string {
|
||||
to_delimiter_case :: proc(
|
||||
s: string,
|
||||
delimiter: rune,
|
||||
all_upper_case: bool,
|
||||
allocator := context.allocator,
|
||||
) -> string {
|
||||
s := s
|
||||
s = trim_space(s)
|
||||
b: Builder
|
||||
@@ -237,73 +386,171 @@ to_delimiter_case :: proc(s: string, delimiter: rune, all_upper_case: bool, allo
|
||||
|
||||
return to_string(b)
|
||||
}
|
||||
/*
|
||||
Converts a string to "snake_case" with all runes lowercased
|
||||
|
||||
*Allocates Using Provided Allocator*
|
||||
|
||||
Inputs:
|
||||
- s: The input string to be converted
|
||||
- allocator: (default: context.allocator).
|
||||
|
||||
Returns:
|
||||
The converted string
|
||||
|
||||
Example:
|
||||
|
||||
import "core:fmt"
|
||||
import "core:strings"
|
||||
|
||||
to_snake_case_example :: proc() {
|
||||
fmt.println(strings.to_snake_case("HelloWorld"))
|
||||
fmt.println(strings.to_snake_case("Hello World"))
|
||||
}
|
||||
|
||||
Output:
|
||||
|
||||
hello_world
|
||||
hello_world
|
||||
|
||||
/*
|
||||
converts the `s` string to "snake_case" with all runes lowercased
|
||||
|
||||
strings.to_snake_case("HelloWorld") -> hello_world
|
||||
strings.to_snake_case("Hello World") -> hello_world
|
||||
*/
|
||||
to_snake_case :: proc(s: string, allocator := context.allocator) -> string {
|
||||
return to_delimiter_case(s, '_', false, allocator)
|
||||
}
|
||||
|
||||
// Alias for `to_upper_snake_case`
|
||||
to_screaming_snake_case :: to_upper_snake_case
|
||||
/*
|
||||
Converts a string to "SNAKE_CASE" with all runes uppercased
|
||||
|
||||
// converts the `s` string to "SNAKE_CASE" with all runes uppercased
|
||||
*Allocates Using Provided Allocator*
|
||||
|
||||
Inputs:
|
||||
- s: The input string to be converted
|
||||
- allocator: (default: context.allocator).
|
||||
|
||||
Returns:
|
||||
The converted string
|
||||
|
||||
Example:
|
||||
|
||||
import "core:fmt"
|
||||
import "core:strings"
|
||||
|
||||
to_upper_snake_case_example :: proc() {
|
||||
fmt.println(strings.to_upper_snake_case("HelloWorld"))
|
||||
}
|
||||
|
||||
Output:
|
||||
|
||||
HELLO_WORLD
|
||||
|
||||
*/
|
||||
to_upper_snake_case :: proc(s: string, allocator := context.allocator) -> string {
|
||||
return to_delimiter_case(s, '_', true, allocator)
|
||||
}
|
||||
/*
|
||||
Converts a string to "kebab-case" with all runes lowercased
|
||||
|
||||
// converts the `s` string to "kebab-case" with all runes lowercased
|
||||
*Allocates Using Provided Allocator*
|
||||
|
||||
Inputs:
|
||||
- s: The input string to be converted
|
||||
- allocator: (default: context.allocator).
|
||||
|
||||
Returns:
|
||||
The converted string
|
||||
|
||||
Example:
|
||||
|
||||
import "core:fmt"
|
||||
import "core:strings"
|
||||
|
||||
to_kebab_case_example :: proc() {
|
||||
fmt.println(strings.to_kebab_case("HelloWorld"))
|
||||
}
|
||||
|
||||
Output:
|
||||
|
||||
hello-world
|
||||
|
||||
*/
|
||||
to_kebab_case :: proc(s: string, allocator := context.allocator) -> string {
|
||||
return to_delimiter_case(s, '-', false, allocator)
|
||||
}
|
||||
/*
|
||||
Converts a string to "KEBAB-CASE" with all runes uppercased
|
||||
|
||||
// converts the `s` string to "KEBAB-CASE" with all runes uppercased
|
||||
*Allocates Using Provided Allocator*
|
||||
|
||||
Inputs:
|
||||
- s: The input string to be converted
|
||||
- allocator: (default: context.allocator).
|
||||
|
||||
Returns:
|
||||
The converted string
|
||||
|
||||
Example:
|
||||
|
||||
import "core:fmt"
|
||||
import "core:strings"
|
||||
|
||||
to_upper_kebab_case_example :: proc() {
|
||||
fmt.println(strings.to_upper_kebab_case("HelloWorld"))
|
||||
}
|
||||
|
||||
Output:
|
||||
|
||||
HELLO-WORLD
|
||||
|
||||
*/
|
||||
to_upper_kebab_case :: proc(s: string, allocator := context.allocator) -> string {
|
||||
return to_delimiter_case(s, '-', true, allocator)
|
||||
}
|
||||
/*
|
||||
Converts a string to "Ada_Case"
|
||||
|
||||
// converts the `s` string to "Ada_case"
|
||||
*Allocates Using Provided Allocator*
|
||||
|
||||
Inputs:
|
||||
- s: The input string to be converted
|
||||
- allocator: (default: context.allocator).
|
||||
|
||||
Returns:
|
||||
The converted string
|
||||
|
||||
Example:
|
||||
|
||||
import "core:fmt"
|
||||
import "core:strings"
|
||||
|
||||
to_ada_case_example :: proc() {
|
||||
fmt.println(strings.to_ada_case("HelloWorld"))
|
||||
}
|
||||
|
||||
Output:
|
||||
|
||||
Hello_World
|
||||
|
||||
*/
|
||||
to_ada_case :: proc(s: string, allocator := context.allocator) -> string {
|
||||
delimiter :: '_'
|
||||
|
||||
s := s
|
||||
s = trim_space(s)
|
||||
b: Builder
|
||||
builder_init(&b, 0, len(s), allocator)
|
||||
w := to_writer(&b)
|
||||
|
||||
prev, curr: rune
|
||||
|
||||
for next in s {
|
||||
if is_delimiter(curr) {
|
||||
if !is_delimiter(prev) {
|
||||
io.write_rune(w, delimiter)
|
||||
string_case_iterator(w, s, proc(w: io.Writer, prev, curr, next: rune) {
|
||||
if !is_delimiter(curr) {
|
||||
if is_delimiter(prev) || prev == 0 || (unicode.is_lower(prev) && unicode.is_upper(curr)) {
|
||||
if prev != 0 {
|
||||
io.write_rune(w, '_')
|
||||
}
|
||||
io.write_rune(w, unicode.to_upper(curr))
|
||||
} else {
|
||||
io.write_rune(w, unicode.to_lower(curr))
|
||||
}
|
||||
} else if unicode.is_upper(curr) {
|
||||
if unicode.is_lower(prev) || (unicode.is_upper(prev) && unicode.is_lower(next)) {
|
||||
io.write_rune(w, delimiter)
|
||||
}
|
||||
io.write_rune(w, unicode.to_upper(curr))
|
||||
} else if curr != 0 {
|
||||
io.write_rune(w, unicode.to_lower(curr))
|
||||
}
|
||||
|
||||
prev = curr
|
||||
curr = next
|
||||
}
|
||||
|
||||
if len(s) > 0 {
|
||||
if unicode.is_upper(curr) && unicode.is_lower(prev) && prev != 0 {
|
||||
io.write_rune(w, delimiter)
|
||||
io.write_rune(w, unicode.to_upper(curr))
|
||||
} else {
|
||||
io.write_rune(w, unicode.to_lower(curr))
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return to_string(b)
|
||||
}
|
||||
|
||||
|
||||
+61
-11
@@ -2,49 +2,99 @@ package strings
|
||||
|
||||
import "core:runtime"
|
||||
|
||||
// custom string entry struct
|
||||
// Custom string entry struct
|
||||
Intern_Entry :: struct {
|
||||
len: int,
|
||||
str: [1]byte, // string is allocated inline with the entry to keep allocations simple
|
||||
}
|
||||
/*
|
||||
Intern is a more memory efficient string map
|
||||
|
||||
// "intern" is a more memory efficient string map
|
||||
// `allocator` is used to allocate the actual `Intern_Entry` strings
|
||||
Uses Specified Allocator for `Intern_Entry` strings
|
||||
|
||||
Fields:
|
||||
- allocator: The allocator used for the Intern_Entry strings
|
||||
- entries: A map of strings to interned string entries
|
||||
*/
|
||||
Intern :: struct {
|
||||
allocator: runtime.Allocator,
|
||||
entries: map[string]^Intern_Entry,
|
||||
}
|
||||
/*
|
||||
Initializes the entries map and sets the allocator for the string entries
|
||||
|
||||
// initialize the entries map and set the allocator for the string entries
|
||||
*Allocates Using Provided Allocators*
|
||||
|
||||
Inputs:
|
||||
- m: A pointer to the Intern struct to be initialized
|
||||
- allocator: The allocator for the Intern_Entry strings (Default: context.allocator)
|
||||
- map_allocator: The allocator for the map of entries (Default: context.allocator)
|
||||
*/
|
||||
intern_init :: proc(m: ^Intern, allocator := context.allocator, map_allocator := context.allocator) {
|
||||
m.allocator = allocator
|
||||
m.entries = make(map[string]^Intern_Entry, 16, map_allocator)
|
||||
}
|
||||
/*
|
||||
Frees the map and all its content allocated using the `.allocator`.
|
||||
|
||||
// free the map and all its content allocated using the `.allocator`
|
||||
Inputs:
|
||||
- m: A pointer to the Intern struct to be destroyed
|
||||
*/
|
||||
intern_destroy :: proc(m: ^Intern) {
|
||||
for _, value in m.entries {
|
||||
free(value, m.allocator)
|
||||
}
|
||||
delete(m.entries)
|
||||
}
|
||||
/*
|
||||
Returns an interned copy of the given text, adding it to the map if not already present.
|
||||
|
||||
// returns the `text` string from the intern map - gets set if it didnt exist yet
|
||||
// the returned string lives as long as the map entry lives
|
||||
*Allocate using the Intern's Allocator (First time string is seen only)*
|
||||
|
||||
Inputs:
|
||||
- m: A pointer to the Intern struct
|
||||
- text: The string to be interned
|
||||
|
||||
NOTE: The returned string lives as long as the map entry lives.
|
||||
|
||||
Returns:
|
||||
The interned string and an allocator error if any
|
||||
*/
|
||||
intern_get :: proc(m: ^Intern, text: string) -> (str: string, err: runtime.Allocator_Error) {
|
||||
entry := _intern_get_entry(m, text) or_return
|
||||
#no_bounds_check return string(entry.str[:entry.len]), nil
|
||||
}
|
||||
/*
|
||||
Returns an interned copy of the given text as a cstring, adding it to the map if not already present.
|
||||
|
||||
// returns the `text` cstring from the intern map - gets set if it didnt exist yet
|
||||
// the returned cstring lives as long as the map entry lives
|
||||
*Allocate using the Intern's Allocator (First time string is seen only)*
|
||||
|
||||
Inputs:
|
||||
- m: A pointer to the Intern struct
|
||||
- text: The string to be interned
|
||||
|
||||
NOTE: The returned cstring lives as long as the map entry lives
|
||||
|
||||
Returns:
|
||||
The interned cstring and an allocator error if any
|
||||
*/
|
||||
intern_get_cstring :: proc(m: ^Intern, text: string) -> (str: cstring, err: runtime.Allocator_Error) {
|
||||
entry := _intern_get_entry(m, text) or_return
|
||||
return cstring(&entry.str[0]), nil
|
||||
}
|
||||
/*
|
||||
Internal function to lookup whether the text string exists in the map, returns the entry
|
||||
Sets and allocates the entry if it wasn't set yet
|
||||
|
||||
// looks up wether the `text` string exists in the map, returns the entry
|
||||
// sets & allocates the entry if it wasnt set yet
|
||||
*Allocate using the Intern's Allocator (First time string is seen only)*
|
||||
|
||||
Inputs:
|
||||
- m: A pointer to the Intern struct
|
||||
- text: The string to be looked up or interned
|
||||
|
||||
Returns:
|
||||
The new or existing interned entry and an allocator error if any
|
||||
*/
|
||||
_intern_get_entry :: proc(m: ^Intern, text: string) -> (new_entry: ^Intern_Entry, err: runtime.Allocator_Error) #no_bounds_check {
|
||||
if prev, ok := m.entries[text]; ok {
|
||||
return prev, nil
|
||||
|
||||
+139
-22
@@ -4,59 +4,109 @@ import "core:io"
|
||||
import "core:unicode/utf8"
|
||||
|
||||
/*
|
||||
io stream data for a string reader that can read based on bytes or runes
|
||||
implements the vtable when using the io.Reader variants
|
||||
"read" calls advance the current reading offset `i`
|
||||
io stream data for a string reader that can read based on bytes or runes
|
||||
implements the vtable when using the `io.Reader` variants
|
||||
"read" calls advance the current reading offset `i`
|
||||
*/
|
||||
Reader :: struct {
|
||||
s: string, // read-only buffer
|
||||
i: i64, // current reading index
|
||||
prev_rune: int, // previous reading index of rune or < 0
|
||||
}
|
||||
/*
|
||||
Initializes a string Reader with the provided string
|
||||
|
||||
// init the reader to the string `s`
|
||||
Inputs:
|
||||
- r: A pointer to a Reader struct
|
||||
- s: The input string to be read
|
||||
*/
|
||||
reader_init :: proc(r: ^Reader, s: string) {
|
||||
r.s = s
|
||||
r.i = 0
|
||||
r.prev_rune = -1
|
||||
}
|
||||
/*
|
||||
Converts a Reader into an `io.Stream`
|
||||
|
||||
// returns a stream from the reader data
|
||||
Inputs:
|
||||
- r: A pointer to a Reader struct
|
||||
|
||||
Returns:
|
||||
An io.Stream for the given Reader
|
||||
*/
|
||||
reader_to_stream :: proc(r: ^Reader) -> (s: io.Stream) {
|
||||
s.stream_data = r
|
||||
s.stream_vtable = &_reader_vtable
|
||||
return
|
||||
}
|
||||
/*
|
||||
Initializes a string Reader and returns an `io.Reader` for the given string
|
||||
|
||||
// init a reader to the string `s` and return an io.Reader
|
||||
Inputs:
|
||||
- r: A pointer to a Reader struct
|
||||
- s: The input string to be read
|
||||
|
||||
Returns:
|
||||
An io.Reader for the given string
|
||||
*/
|
||||
to_reader :: proc(r: ^Reader, s: string) -> io.Reader {
|
||||
reader_init(r, s)
|
||||
rr, _ := io.to_reader(reader_to_stream(r))
|
||||
return rr
|
||||
}
|
||||
/*
|
||||
Initializes a string Reader and returns an `io.Reader_At` for the given string
|
||||
|
||||
// init a reader to the string `s` and return an io.Reader_At
|
||||
Inputs:
|
||||
- r: A pointer to a Reader struct
|
||||
- s: The input string to be read
|
||||
|
||||
Returns:
|
||||
An `io.Reader_At` for the given string
|
||||
*/
|
||||
to_reader_at :: proc(r: ^Reader, s: string) -> io.Reader_At {
|
||||
reader_init(r, s)
|
||||
rr, _ := io.to_reader_at(reader_to_stream(r))
|
||||
return rr
|
||||
}
|
||||
/*
|
||||
Returns the remaining length of the Reader
|
||||
|
||||
// remaining length of the reader
|
||||
Inputs:
|
||||
- r: A pointer to a Reader struct
|
||||
|
||||
Returns:
|
||||
The remaining length of the Reader
|
||||
*/
|
||||
reader_length :: proc(r: ^Reader) -> int {
|
||||
if r.i >= i64(len(r.s)) {
|
||||
return 0
|
||||
}
|
||||
return int(i64(len(r.s)) - r.i)
|
||||
}
|
||||
/*
|
||||
Returns the length of the string stored in the Reader
|
||||
|
||||
// returns the string length stored by the reader
|
||||
Inputs:
|
||||
- r: A pointer to a Reader struct
|
||||
|
||||
Returns:
|
||||
The length of the string stored in the Reader
|
||||
*/
|
||||
reader_size :: proc(r: ^Reader) -> i64 {
|
||||
return i64(len(r.s))
|
||||
}
|
||||
/*
|
||||
Reads len(p) bytes from the Reader's string and copies into the provided slice.
|
||||
|
||||
// reads len(p) bytes into the slice from the string in the reader
|
||||
// returns `n` amount of read bytes and an io.Error
|
||||
Inputs:
|
||||
- r: A pointer to a Reader struct
|
||||
- p: A byte slice to copy data into
|
||||
|
||||
Returns:
|
||||
- n: The number of bytes read
|
||||
- err: An `io.Error` if an error occurs while reading, including `.EOF`, otherwise `nil` denotes success.
|
||||
*/
|
||||
reader_read :: proc(r: ^Reader, p: []byte) -> (n: int, err: io.Error) {
|
||||
if r.i >= i64(len(r.s)) {
|
||||
return 0, .EOF
|
||||
@@ -66,9 +116,18 @@ reader_read :: proc(r: ^Reader, p: []byte) -> (n: int, err: io.Error) {
|
||||
r.i += i64(n)
|
||||
return
|
||||
}
|
||||
/*
|
||||
Reads len(p) bytes from the Reader's string and copies into the provided slice, at the specified offset from the current index.
|
||||
|
||||
// reads len(p) bytes into the slice from the string in the reader at an offset
|
||||
// returns `n` amount of read bytes and an io.Error
|
||||
Inputs:
|
||||
- r: A pointer to a Reader struct
|
||||
- p: A byte slice to copy data into
|
||||
- off: The offset from which to read
|
||||
|
||||
Returns:
|
||||
- n: The number of bytes read
|
||||
- err: An `io.Error` if an error occurs while reading, including `.EOF`, otherwise `nil` denotes success.
|
||||
*/
|
||||
reader_read_at :: proc(r: ^Reader, p: []byte, off: i64) -> (n: int, err: io.Error) {
|
||||
if off < 0 {
|
||||
return 0, .Invalid_Offset
|
||||
@@ -82,8 +141,16 @@ reader_read_at :: proc(r: ^Reader, p: []byte, off: i64) -> (n: int, err: io.Erro
|
||||
}
|
||||
return
|
||||
}
|
||||
/*
|
||||
Reads and returns a single byte from the Reader's string
|
||||
|
||||
// reads and returns a single byte - error when out of bounds
|
||||
Inputs:
|
||||
- r: A pointer to a Reader struct
|
||||
|
||||
Returns:
|
||||
- The byte read from the Reader
|
||||
- err: An `io.Error` if an error occurs while reading, including `.EOF`, otherwise `nil` denotes success.
|
||||
*/
|
||||
reader_read_byte :: proc(r: ^Reader) -> (byte, io.Error) {
|
||||
r.prev_rune = -1
|
||||
if r.i >= i64(len(r.s)) {
|
||||
@@ -93,8 +160,15 @@ reader_read_byte :: proc(r: ^Reader) -> (byte, io.Error) {
|
||||
r.i += 1
|
||||
return b, nil
|
||||
}
|
||||
/*
|
||||
Decrements the Reader's index (i) by 1
|
||||
|
||||
// decreases the reader offset - error when below 0
|
||||
Inputs:
|
||||
- r: A pointer to a Reader struct
|
||||
|
||||
Returns:
|
||||
An `io.Error` if `r.i <= 0` (`.Invalid_Unread`), otherwise `nil` denotes success.
|
||||
*/
|
||||
reader_unread_byte :: proc(r: ^Reader) -> io.Error {
|
||||
if r.i <= 0 {
|
||||
return .Invalid_Unread
|
||||
@@ -103,9 +177,18 @@ reader_unread_byte :: proc(r: ^Reader) -> io.Error {
|
||||
r.i -= 1
|
||||
return nil
|
||||
}
|
||||
/*
|
||||
Reads and returns a single rune and its `size` from the Reader's string
|
||||
|
||||
// reads and returns a single rune and the rune size - error when out bounds
|
||||
reader_read_rune :: proc(r: ^Reader) -> (ch: rune, size: int, err: io.Error) {
|
||||
Inputs:
|
||||
- r: A pointer to a Reader struct
|
||||
|
||||
Returns:
|
||||
- rr: The rune read from the Reader
|
||||
- size: The size of the rune in bytes
|
||||
- err: An `io.Error` if an error occurs while reading
|
||||
*/
|
||||
reader_read_rune :: proc(r: ^Reader) -> (rr: rune, size: int, err: io.Error) {
|
||||
if r.i >= i64(len(r.s)) {
|
||||
r.prev_rune = -1
|
||||
return 0, 0, .EOF
|
||||
@@ -115,13 +198,21 @@ reader_read_rune :: proc(r: ^Reader) -> (ch: rune, size: int, err: io.Error) {
|
||||
r.i += 1
|
||||
return rune(c), 1, nil
|
||||
}
|
||||
ch, size = utf8.decode_rune_in_string(r.s[r.i:])
|
||||
rr, size = utf8.decode_rune_in_string(r.s[r.i:])
|
||||
r.i += i64(size)
|
||||
return
|
||||
}
|
||||
/*
|
||||
Decrements the Reader's index (i) by the size of the last read rune
|
||||
|
||||
// decreases the reader offset by the last rune
|
||||
// can only be used once and after a valid read_rune call
|
||||
Inputs:
|
||||
- r: A pointer to a Reader struct
|
||||
|
||||
WARNING: May only be used once and after a valid `read_rune` call
|
||||
|
||||
Returns:
|
||||
An `io.Error` if an error occurs while unreading (`.Invalid_Unread`), else `nil` denotes success.
|
||||
*/
|
||||
reader_unread_rune :: proc(r: ^Reader) -> io.Error {
|
||||
if r.i <= 0 {
|
||||
return .Invalid_Unread
|
||||
@@ -133,8 +224,18 @@ reader_unread_rune :: proc(r: ^Reader) -> io.Error {
|
||||
r.prev_rune = -1
|
||||
return nil
|
||||
}
|
||||
/*
|
||||
Seeks the Reader's index to a new position
|
||||
|
||||
// seeks the reader offset to a wanted offset
|
||||
Inputs:
|
||||
- r: A pointer to a Reader struct
|
||||
- offset: The new offset position
|
||||
- whence: The reference point for the new position (`.Start`, `.Current`, or `.End`)
|
||||
|
||||
Returns:
|
||||
- The absolute offset after seeking
|
||||
- err: An `io.Error` if an error occurs while seeking (`.Invalid_Whence`, `.Invalid_Offset`)
|
||||
*/
|
||||
reader_seek :: proc(r: ^Reader, offset: i64, whence: io.Seek_From) -> (i64, io.Error) {
|
||||
r.prev_rune = -1
|
||||
abs: i64
|
||||
@@ -155,8 +256,19 @@ reader_seek :: proc(r: ^Reader, offset: i64, whence: io.Seek_From) -> (i64, io.E
|
||||
r.i = abs
|
||||
return abs, nil
|
||||
}
|
||||
/*
|
||||
Writes the remaining content of the Reader's string into the provided `io.Writer`
|
||||
|
||||
// writes the string content left to read into the io.Writer `w`
|
||||
Inputs:
|
||||
- r: A pointer to a Reader struct
|
||||
- w: The io.Writer to write the remaining content into
|
||||
|
||||
WARNING: Panics if writer writes more bytes than remainig length of string.
|
||||
|
||||
Returns:
|
||||
- n: The number of bytes written
|
||||
- err: An io.Error if an error occurs while writing (`.Short_Write`)
|
||||
*/
|
||||
reader_write_to :: proc(r: ^Reader, w: io.Writer) -> (n: i64, err: io.Error) {
|
||||
r.prev_rune = -1
|
||||
if r.i >= i64(len(r.s)) {
|
||||
@@ -175,7 +287,12 @@ reader_write_to :: proc(r: ^Reader, w: io.Writer) -> (n: i64, err: io.Error) {
|
||||
}
|
||||
return
|
||||
}
|
||||
/*
|
||||
VTable containing implementations for various `io.Stream` methods
|
||||
|
||||
This VTable is used by the Reader struct to provide its functionality
|
||||
as an `io.Stream`.
|
||||
*/
|
||||
@(private)
|
||||
_reader_vtable := io.Stream_VTable{
|
||||
impl_size = proc(s: io.Stream) -> i64 {
|
||||
|
||||
+1823
-505
File diff suppressed because it is too large
Load Diff
@@ -223,11 +223,10 @@ init_os_version :: proc () {
|
||||
|
||||
// Grab Windows DisplayVersion (like 20H02)
|
||||
format_display_version :: proc (b: ^strings.Builder) -> (version: string) {
|
||||
dv, ok := read_reg(
|
||||
dv, ok := read_reg_string(
|
||||
sys.HKEY_LOCAL_MACHINE,
|
||||
"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion",
|
||||
"DisplayVersion",
|
||||
string,
|
||||
)
|
||||
defer delete(dv) // It'll be interned into `version_string_buf`
|
||||
|
||||
@@ -243,11 +242,10 @@ init_os_version :: proc () {
|
||||
|
||||
// Grab build number and UBR
|
||||
format_build_number :: proc (b: ^strings.Builder, major_build: int) -> (ubr: int) {
|
||||
res, ok := read_reg(
|
||||
res, ok := read_reg_i32(
|
||||
sys.HKEY_LOCAL_MACHINE,
|
||||
"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion",
|
||||
"UBR",
|
||||
i32,
|
||||
)
|
||||
|
||||
if ok {
|
||||
@@ -289,17 +287,17 @@ init_gpu_info :: proc() {
|
||||
for {
|
||||
key := fmt.tprintf("%v\\%04d", GPU_INFO_BASE, gpu_index)
|
||||
|
||||
if vendor, ok := read_reg(sys.HKEY_LOCAL_MACHINE, key, "ProviderName", string); ok {
|
||||
if vendor, ok := read_reg_string(sys.HKEY_LOCAL_MACHINE, key, "ProviderName"); ok {
|
||||
append(&gpu_list, GPU{vendor_name = vendor})
|
||||
} else {
|
||||
break
|
||||
}
|
||||
|
||||
if desc, ok := read_reg(sys.HKEY_LOCAL_MACHINE, key, "DriverDesc", string); ok {
|
||||
if desc, ok := read_reg_string(sys.HKEY_LOCAL_MACHINE, key, "DriverDesc"); ok {
|
||||
gpu_list[gpu_index].model_name = desc
|
||||
}
|
||||
|
||||
if vram, ok := read_reg(sys.HKEY_LOCAL_MACHINE, key, "HardwareInformation.qwMemorySize", i64); ok {
|
||||
if vram, ok := read_reg_i64(sys.HKEY_LOCAL_MACHINE, key, "HardwareInformation.qwMemorySize"); ok {
|
||||
gpu_list[gpu_index].total_ram = int(vram)
|
||||
}
|
||||
gpu_index += 1
|
||||
@@ -308,71 +306,93 @@ init_gpu_info :: proc() {
|
||||
}
|
||||
|
||||
@(private)
|
||||
read_reg :: proc(hkey: sys.HKEY, subkey, val: string, $T: typeid) -> (res: T, ok: bool) {
|
||||
BUF_SIZE :: 1024
|
||||
|
||||
read_reg_string :: proc(hkey: sys.HKEY, subkey, val: string) -> (res: string, ok: bool) {
|
||||
if len(subkey) == 0 || len(val) == 0 {
|
||||
return {}, false
|
||||
return
|
||||
}
|
||||
|
||||
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||
|
||||
BUF_SIZE :: 1024
|
||||
key_name_wide := make([]u16, BUF_SIZE, context.temp_allocator)
|
||||
val_name_wide := make([]u16, BUF_SIZE, context.temp_allocator)
|
||||
|
||||
utf16.encode_string(key_name_wide, subkey)
|
||||
utf16.encode_string(val_name_wide, val)
|
||||
|
||||
when T == string {
|
||||
result_wide := make([]u16, BUF_SIZE, context.temp_allocator)
|
||||
result_size := sys.DWORD(BUF_SIZE * size_of(u16))
|
||||
result_wide := make([]u16, BUF_SIZE, context.temp_allocator)
|
||||
result_size := sys.DWORD(BUF_SIZE * size_of(u16))
|
||||
|
||||
status := sys.RegGetValueW(
|
||||
hkey,
|
||||
&key_name_wide[0],
|
||||
&val_name_wide[0],
|
||||
sys.RRF_RT_REG_SZ,
|
||||
nil,
|
||||
raw_data(result_wide[:]),
|
||||
&result_size,
|
||||
)
|
||||
if status != 0 {
|
||||
// Couldn't retrieve string
|
||||
return
|
||||
}
|
||||
|
||||
// Result string will be allocated for the caller.
|
||||
result_utf8 := make([]u8, BUF_SIZE * 4, context.temp_allocator)
|
||||
utf16.decode_to_utf8(result_utf8, result_wide[:result_size])
|
||||
return strings.clone_from_cstring(cstring(raw_data(result_utf8))), true
|
||||
|
||||
} else when T == i32 {
|
||||
result_size := sys.DWORD(size_of(i32))
|
||||
status := sys.RegGetValueW(
|
||||
hkey,
|
||||
&key_name_wide[0],
|
||||
&val_name_wide[0],
|
||||
sys.RRF_RT_REG_DWORD,
|
||||
nil,
|
||||
&res,
|
||||
&result_size,
|
||||
)
|
||||
return res, status == 0
|
||||
|
||||
} else when T == i64 {
|
||||
result_size := sys.DWORD(size_of(i64))
|
||||
status := sys.RegGetValueW(
|
||||
hkey,
|
||||
&key_name_wide[0],
|
||||
&val_name_wide[0],
|
||||
sys.RRF_RT_REG_QWORD,
|
||||
nil,
|
||||
&res,
|
||||
&result_size,
|
||||
)
|
||||
return res, status == 0
|
||||
} else {
|
||||
#assert(false, "Unhandled type for read_reg")
|
||||
status := sys.RegGetValueW(
|
||||
hkey,
|
||||
&key_name_wide[0],
|
||||
&val_name_wide[0],
|
||||
sys.RRF_RT_REG_SZ,
|
||||
nil,
|
||||
raw_data(result_wide[:]),
|
||||
&result_size,
|
||||
)
|
||||
if status != 0 {
|
||||
// Couldn't retrieve string
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Result string will be allocated for the caller.
|
||||
result_utf8 := make([]u8, BUF_SIZE * 4, context.temp_allocator)
|
||||
utf16.decode_to_utf8(result_utf8, result_wide[:result_size])
|
||||
return strings.clone_from_cstring(cstring(raw_data(result_utf8))), true
|
||||
}
|
||||
@(private)
|
||||
read_reg_i32 :: proc(hkey: sys.HKEY, subkey, val: string) -> (res: i32, ok: bool) {
|
||||
if len(subkey) == 0 || len(val) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||
|
||||
BUF_SIZE :: 1024
|
||||
key_name_wide := make([]u16, BUF_SIZE, context.temp_allocator)
|
||||
val_name_wide := make([]u16, BUF_SIZE, context.temp_allocator)
|
||||
|
||||
utf16.encode_string(key_name_wide, subkey)
|
||||
utf16.encode_string(val_name_wide, val)
|
||||
|
||||
result_size := sys.DWORD(size_of(i32))
|
||||
status := sys.RegGetValueW(
|
||||
hkey,
|
||||
&key_name_wide[0],
|
||||
&val_name_wide[0],
|
||||
sys.RRF_RT_REG_DWORD,
|
||||
nil,
|
||||
&res,
|
||||
&result_size,
|
||||
)
|
||||
return res, status == 0
|
||||
}
|
||||
@(private)
|
||||
read_reg_i64 :: proc(hkey: sys.HKEY, subkey, val: string) -> (res: i64, ok: bool) {
|
||||
if len(subkey) == 0 || len(val) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||
|
||||
BUF_SIZE :: 1024
|
||||
key_name_wide := make([]u16, BUF_SIZE, context.temp_allocator)
|
||||
val_name_wide := make([]u16, BUF_SIZE, context.temp_allocator)
|
||||
|
||||
utf16.encode_string(key_name_wide, subkey)
|
||||
utf16.encode_string(val_name_wide, val)
|
||||
|
||||
result_size := sys.DWORD(size_of(i64))
|
||||
status := sys.RegGetValueW(
|
||||
hkey,
|
||||
&key_name_wide[0],
|
||||
&val_name_wide[0],
|
||||
sys.RRF_RT_REG_QWORD,
|
||||
nil,
|
||||
&res,
|
||||
&result_size,
|
||||
)
|
||||
return res, status == 0
|
||||
}
|
||||
|
||||
@@ -1518,6 +1518,7 @@ when ODIN_ARCH == .amd64 {
|
||||
#panic("Unsupported architecture")
|
||||
}
|
||||
|
||||
|
||||
// syscall related constants
|
||||
AT_FDCWD :: ~uintptr(99)
|
||||
AT_REMOVEDIR :: uintptr(0x200)
|
||||
@@ -2008,6 +2009,42 @@ sys_utimensat :: proc "contextless" (dfd: int, path: cstring, times: rawptr, fla
|
||||
return int(intrinsics.syscall(SYS_utimensat, uintptr(dfd), uintptr(rawptr(path)), uintptr(times), uintptr(flags)))
|
||||
}
|
||||
|
||||
sys_socket :: proc "contextless" (domain: int, type: int, protocol: int) -> int {
|
||||
return int(intrinsics.syscall(SYS_socket, uintptr(domain), uintptr(type), uintptr(protocol)))
|
||||
}
|
||||
|
||||
sys_connect :: proc "contextless" (sd: int, addr: rawptr, len: i32) -> int {
|
||||
return int(intrinsics.syscall(SYS_connect, uintptr(sd), uintptr(addr), uintptr(len)))
|
||||
}
|
||||
|
||||
sys_accept :: proc "contextless" (sd: int, addr: rawptr, len: rawptr) -> int {
|
||||
return int(intrinsics.syscall(SYS_accept4, uintptr(sd), uintptr(addr), uintptr(len), uintptr(0)))
|
||||
}
|
||||
|
||||
sys_listen :: proc "contextless" (sd: int, backlog: int) -> int {
|
||||
return int(intrinsics.syscall(SYS_listen, uintptr(sd), uintptr(backlog)))
|
||||
}
|
||||
|
||||
sys_bind :: proc "contextless" (sd: int, addr: rawptr, len: i32) -> int {
|
||||
return int(intrinsics.syscall(SYS_bind, uintptr(sd), uintptr(addr), uintptr(len)))
|
||||
}
|
||||
|
||||
sys_setsockopt :: proc "contextless" (sd: int, level: int, optname: int, optval: rawptr, optlen: i32) -> int {
|
||||
return int(intrinsics.syscall(SYS_setsockopt, uintptr(sd), uintptr(level), uintptr(optname), uintptr(optval), uintptr(optlen)))
|
||||
}
|
||||
|
||||
sys_recvfrom :: proc "contextless" (sd: int, buf: rawptr, len: uint, flags: int, addr: rawptr, alen: uintptr) -> i64 {
|
||||
return i64(intrinsics.syscall(SYS_recvfrom, uintptr(sd), uintptr(buf), uintptr(len), uintptr(flags), uintptr(addr), uintptr(alen)))
|
||||
}
|
||||
|
||||
sys_sendto :: proc "contextless" (sd: int, buf: rawptr, len: uint, flags: int, addr: rawptr, alen: i32) -> i64 {
|
||||
return i64(intrinsics.syscall(SYS_sendto, uintptr(sd), uintptr(buf), uintptr(len), uintptr(flags), uintptr(addr), uintptr(alen)))
|
||||
}
|
||||
|
||||
sys_shutdown :: proc "contextless" (sd: int, how: int) -> int {
|
||||
return int(intrinsics.syscall(SYS_shutdown, uintptr(sd), uintptr(how)))
|
||||
}
|
||||
|
||||
sys_perf_event_open :: proc "contextless" (event_attr: rawptr, pid: i32, cpu: i32, group_fd: i32, flags: u32) -> int {
|
||||
return int(intrinsics.syscall(SYS_perf_event_open, uintptr(event_attr), uintptr(pid), uintptr(cpu), uintptr(group_fd), uintptr(flags)))
|
||||
}
|
||||
@@ -2016,6 +2053,10 @@ sys_personality :: proc(persona: u64) -> int {
|
||||
return int(intrinsics.syscall(SYS_personality, uintptr(persona)))
|
||||
}
|
||||
|
||||
sys_fcntl :: proc "contextless" (fd: int, cmd: int, arg: int) -> int {
|
||||
return int(intrinsics.syscall(SYS_fcntl, uintptr(fd), uintptr(cmd), uintptr(arg)))
|
||||
}
|
||||
|
||||
get_errno :: proc "contextless" (res: int) -> i32 {
|
||||
if res < 0 && res > -4096 {
|
||||
return i32(-res)
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
// +build windows
|
||||
package sys_windows
|
||||
|
||||
foreign import "system:Dnsapi.lib"
|
||||
|
||||
@(default_calling_convention="std")
|
||||
foreign Dnsapi {
|
||||
DnsQuery_UTF8 :: proc(name: cstring, type: u16, options: DWORD, extra: PVOID, results: ^^DNS_RECORD, reserved: PVOID) -> DNS_STATUS ---
|
||||
DnsRecordListFree :: proc(list: ^DNS_RECORD, options: DWORD) ---
|
||||
}
|
||||
@@ -0,0 +1,234 @@
|
||||
// +build windows
|
||||
package sys_windows
|
||||
|
||||
foreign import "system:iphlpapi.lib"
|
||||
|
||||
Address_Family :: enum u32 {
|
||||
Unspecified = 0, // Return both IPv4 and IPv6 addresses associated with adapters with them enabled.
|
||||
IPv4 = 2, // Return only IPv4 addresses associated with adapters with it enabled.
|
||||
IPv6 = 23, // Return only IPv6 addresses associated with adapters with it enabled.
|
||||
}
|
||||
|
||||
GAA_Flag :: enum u32 {
|
||||
Skip_Unicast = 0, // Do not return unicast addresses.
|
||||
Skip_Anycast = 1, // Do not return IPv6 anycast addresses.
|
||||
Skip_Multicast = 2, // Do not return multicast addresses.
|
||||
Skip_DNS_Server = 3, // Do not return addresses of DNS servers.
|
||||
Include_Prefix = 4, // (XP SP1+) Return a list of IP address prefixes on this adapter. When this flag is set, IP address prefixes are returned for both IPv6 and IPv4 addresses.
|
||||
Skip_Friendly_Name = 5, // Do not return the adapter friendly name.
|
||||
Include_WINS_info = 6, // (Vista+) Return addresses of Windows Internet Name Service (WINS) servers.
|
||||
Include_Gateways = 7, // (Vista+) Return the addresses of default gateways.
|
||||
Include_All_Interfaces = 8, // (Vista+) Return addresses for all NDIS interfaces.
|
||||
Include_All_Compartments = 9, // (Reserved, Unsupported) Return addresses in all routing compartments.
|
||||
Include_Tunnel_Binding_Order = 10, // (Vista+) Return the adapter addresses sorted in tunnel binding order.
|
||||
}
|
||||
GAA_Flags :: bit_set[GAA_Flag; u32]
|
||||
|
||||
IP_Adapter_Addresses :: struct {
|
||||
Raw: struct #raw_union {
|
||||
Alignment: u64,
|
||||
Anonymous: struct {
|
||||
Length: u32,
|
||||
IfIndex: u32,
|
||||
},
|
||||
},
|
||||
Next: ^IP_Adapter_Addresses,
|
||||
AdapterName: cstring,
|
||||
FirstUnicastAddress: ^IP_ADAPTER_UNICAST_ADDRESS_LH,
|
||||
FirstAnycastAddress: ^IP_ADAPTER_ANYCAST_ADDRESS_XP,
|
||||
FirstMulticastAddress: ^IP_ADAPTER_MULTICAST_ADDRESS_XP,
|
||||
FirstDnsServerAddress: ^IP_ADAPTER_DNS_SERVER_ADDRESS_XP,
|
||||
DnsSuffix: ^u16,
|
||||
Description: ^u16,
|
||||
FriendlyName: ^u16,
|
||||
PhysicalAddress: [8]u8,
|
||||
PhysicalAddressLength: u32,
|
||||
Anonymous2: struct #raw_union {
|
||||
Flags: u32,
|
||||
Anonymous: struct {
|
||||
_bitfield: u32,
|
||||
},
|
||||
},
|
||||
MTU: u32,
|
||||
IfType: u32,
|
||||
OperStatus: IF_OPER_STATUS,
|
||||
Ipv6IfIndex: u32,
|
||||
ZoneIndices: [16]u32,
|
||||
FirstPrefix: rawptr, // ^IP_ADAPTER_PREFIX_XP,
|
||||
TransmitLinkSpeed: u64,
|
||||
ReceiveLinkSpeed: u64,
|
||||
FirstWinsServerAddress: rawptr, // ^IP_ADAPTER_WINS_SERVER_ADDRESS_LH,
|
||||
FirstGatewayAddress: ^IP_ADAPTER_GATEWAY_ADDRESS_LH,
|
||||
Ipv4Metric: u32,
|
||||
Ipv6Metric: u32,
|
||||
Luid: NET_LUID_LH,
|
||||
Dhcpv4Server: SOCKET_ADDRESS,
|
||||
CompartmentId: u32,
|
||||
NetworkGuid: GUID,
|
||||
ConnectionType: NET_IF_CONNECTION_TYPE,
|
||||
TunnelType: TUNNEL_TYPE,
|
||||
Dhcpv6Server: SOCKET_ADDRESS,
|
||||
Dhcpv6ClientDuid: [130]u8,
|
||||
Dhcpv6ClientDuidLength: u32,
|
||||
Dhcpv6Iaid: u32,
|
||||
FirstDnsSuffix: rawptr, // ^IP_ADAPTER_DNS_SUFFIX,
|
||||
}
|
||||
|
||||
IP_ADAPTER_UNICAST_ADDRESS_LH :: struct {
|
||||
Anonymous: struct #raw_union {
|
||||
Alignment: u64,
|
||||
Anonymous: struct {
|
||||
Length: u32,
|
||||
Flags: u32,
|
||||
},
|
||||
},
|
||||
Next: ^IP_ADAPTER_UNICAST_ADDRESS_LH,
|
||||
Address: SOCKET_ADDRESS,
|
||||
PrefixOrigin: NL_PREFIX_ORIGIN,
|
||||
SuffixOrigin: NL_SUFFIX_ORIGIN,
|
||||
DadState: NL_DAD_STATE,
|
||||
ValidLifetime: u32,
|
||||
PreferredLifetime: u32,
|
||||
LeaseLifetime: u32,
|
||||
OnLinkPrefixLength: u8,
|
||||
}
|
||||
|
||||
IP_ADAPTER_ANYCAST_ADDRESS_XP :: struct {
|
||||
Anonymous: struct #raw_union {
|
||||
Alignment: u64,
|
||||
Anonymous: struct {
|
||||
Length: u32,
|
||||
Flags: u32,
|
||||
},
|
||||
},
|
||||
Next: ^IP_ADAPTER_ANYCAST_ADDRESS_XP,
|
||||
Address: SOCKET_ADDRESS,
|
||||
}
|
||||
|
||||
IP_ADAPTER_MULTICAST_ADDRESS_XP :: struct {
|
||||
Anonymous: struct #raw_union {
|
||||
Alignment: u64,
|
||||
Anonymous: struct {
|
||||
Length: u32,
|
||||
Flags: u32,
|
||||
},
|
||||
},
|
||||
Next: ^IP_ADAPTER_MULTICAST_ADDRESS_XP,
|
||||
Address: SOCKET_ADDRESS,
|
||||
}
|
||||
|
||||
IP_ADAPTER_GATEWAY_ADDRESS_LH :: struct {
|
||||
Anonymous: struct #raw_union {
|
||||
Alignment: u64,
|
||||
Anonymous: struct {
|
||||
Length: u32,
|
||||
Reserved: u32,
|
||||
},
|
||||
},
|
||||
Next: ^IP_ADAPTER_GATEWAY_ADDRESS_LH,
|
||||
Address: SOCKET_ADDRESS,
|
||||
}
|
||||
|
||||
IP_ADAPTER_DNS_SERVER_ADDRESS_XP :: struct {
|
||||
Anonymous: struct #raw_union {
|
||||
Alignment: u64,
|
||||
Anonymous: struct {
|
||||
Length: u32,
|
||||
Reserved: u32,
|
||||
},
|
||||
},
|
||||
Next: ^IP_ADAPTER_DNS_SERVER_ADDRESS_XP,
|
||||
Address: SOCKET_ADDRESS,
|
||||
}
|
||||
|
||||
IF_OPER_STATUS :: enum i32 {
|
||||
Up = 1,
|
||||
Down = 2,
|
||||
Testing = 3,
|
||||
Unknown = 4,
|
||||
Dormant = 5,
|
||||
NotPresent = 6,
|
||||
LowerLayerDown = 7,
|
||||
}
|
||||
|
||||
NET_LUID_LH :: struct #raw_union {
|
||||
Value: u64,
|
||||
Info: struct {
|
||||
_bitfield: u64,
|
||||
},
|
||||
}
|
||||
|
||||
SOCKET_ADDRESS :: struct {
|
||||
lpSockaddr: ^SOCKADDR,
|
||||
iSockaddrLength: i32,
|
||||
}
|
||||
|
||||
NET_IF_CONNECTION_TYPE :: enum i32 {
|
||||
NET_IF_CONNECTION_DEDICATED = 1,
|
||||
NET_IF_CONNECTION_PASSIVE = 2,
|
||||
NET_IF_CONNECTION_DEMAND = 3,
|
||||
NET_IF_CONNECTION_MAXIMUM = 4,
|
||||
}
|
||||
|
||||
TUNNEL_TYPE :: enum i32 {
|
||||
TUNNEL_TYPE_NONE = 0,
|
||||
TUNNEL_TYPE_OTHER = 1,
|
||||
TUNNEL_TYPE_DIRECT = 2,
|
||||
TUNNEL_TYPE_6TO4 = 11,
|
||||
TUNNEL_TYPE_ISATAP = 13,
|
||||
TUNNEL_TYPE_TEREDO = 14,
|
||||
TUNNEL_TYPE_IPHTTPS = 15,
|
||||
}
|
||||
NL_PREFIX_ORIGIN :: enum i32 {
|
||||
IpPrefixOriginOther = 0,
|
||||
IpPrefixOriginManual = 1,
|
||||
IpPrefixOriginWellKnown = 2,
|
||||
IpPrefixOriginDhcp = 3,
|
||||
IpPrefixOriginRouterAdvertisement = 4,
|
||||
IpPrefixOriginUnchanged = 16,
|
||||
}
|
||||
|
||||
NL_SUFFIX_ORIGIN :: enum i32 {
|
||||
NlsoOther = 0,
|
||||
NlsoManual = 1,
|
||||
NlsoWellKnown = 2,
|
||||
NlsoDhcp = 3,
|
||||
NlsoLinkLayerAddress = 4,
|
||||
NlsoRandom = 5,
|
||||
IpSuffixOriginOther = 0,
|
||||
IpSuffixOriginManual = 1,
|
||||
IpSuffixOriginWellKnown = 2,
|
||||
IpSuffixOriginDhcp = 3,
|
||||
IpSuffixOriginLinkLayerAddress = 4,
|
||||
IpSuffixOriginRandom = 5,
|
||||
IpSuffixOriginUnchanged = 16,
|
||||
}
|
||||
|
||||
NL_DAD_STATE :: enum i32 {
|
||||
NldsInvalid = 0,
|
||||
NldsTentative = 1,
|
||||
NldsDuplicate = 2,
|
||||
NldsDeprecated = 3,
|
||||
NldsPreferred = 4,
|
||||
IpDadStateInvalid = 0,
|
||||
IpDadStateTentative = 1,
|
||||
IpDadStateDuplicate = 2,
|
||||
IpDadStateDeprecated = 3,
|
||||
IpDadStatePreferred = 4,
|
||||
}
|
||||
|
||||
@(default_calling_convention = "std")
|
||||
foreign iphlpapi {
|
||||
/*
|
||||
The GetAdaptersAddresses function retrieves the addresses associated with the adapters on the local computer.
|
||||
See: https://docs.microsoft.com/en-us/windows/win32/api/iphlpapi/nf-iphlpapi-getadaptersaddresses
|
||||
*/
|
||||
@(link_name="GetAdaptersAddresses") get_adapters_addresses :: proc(
|
||||
family: Address_Family,
|
||||
flags: GAA_Flags,
|
||||
_reserved: rawptr,
|
||||
adapter_addresses: [^]IP_Adapter_Addresses,
|
||||
size: ^u32,
|
||||
) -> ULONG ---
|
||||
|
||||
}
|
||||
@@ -3,6 +3,23 @@ package sys_windows
|
||||
|
||||
foreign import kernel32 "system:Kernel32.lib"
|
||||
|
||||
FOREGROUND_BLUE :: WORD(0x0001)
|
||||
FOREGROUND_GREEN :: WORD(0x0002)
|
||||
FOREGROUND_RED :: WORD(0x0004)
|
||||
FOREGROUND_INTENSITY :: WORD(0x0008)
|
||||
BACKGROUND_BLUE :: WORD(0x0010)
|
||||
BACKGROUND_GREEN :: WORD(0x0020)
|
||||
BACKGROUND_RED :: WORD(0x0040)
|
||||
BACKGROUND_INTENSITY :: WORD(0x0080)
|
||||
COMMON_LVB_LEADING_BYTE :: WORD(0x0100)
|
||||
COMMON_LVB_TRAILING_BYTE :: WORD(0x0200)
|
||||
COMMON_LVB_GRID_HORIZONTAL :: WORD(0x0400)
|
||||
COMMON_LVB_GRID_LVERTICAL :: WORD(0x0800)
|
||||
COMMON_LVB_GRID_RVERTICAL :: WORD(0x1000)
|
||||
COMMON_LVB_REVERSE_VIDEO :: WORD(0x4000)
|
||||
COMMON_LVB_UNDERSCORE :: WORD(0x8000)
|
||||
COMMON_LVB_SBCSDBCS :: WORD(0x0300)
|
||||
|
||||
@(default_calling_convention="stdcall")
|
||||
foreign kernel32 {
|
||||
OutputDebugStringA :: proc(lpOutputString: LPCSTR) --- // The only A thing that is allowed
|
||||
@@ -26,7 +43,10 @@ foreign kernel32 {
|
||||
dwMode: DWORD) -> BOOL ---
|
||||
SetConsoleCursorPosition :: proc(hConsoleHandle: HANDLE,
|
||||
dwCursorPosition: COORD) -> BOOL ---
|
||||
|
||||
SetConsoleTextAttribute :: proc(hConsoleOutput: HANDLE,
|
||||
wAttributes: WORD) -> BOOL ---
|
||||
SetConsoleOutputCP :: proc(wCodePageID: UINT) -> BOOL ---
|
||||
|
||||
GetFileInformationByHandle :: proc(hFile: HANDLE, lpFileInformation: LPBY_HANDLE_FILE_INFORMATION) -> BOOL ---
|
||||
SetHandleInformation :: proc(hObject: HANDLE,
|
||||
dwMask: DWORD,
|
||||
@@ -373,6 +393,8 @@ foreign kernel32 {
|
||||
GetConsoleScreenBufferInfo :: proc(hConsoleOutput: HANDLE, lpConsoleScreenBufferInfo: PCONSOLE_SCREEN_BUFFER_INFO) -> BOOL ---
|
||||
SetConsoleScreenBufferSize :: proc(hConsoleOutput: HANDLE, dwSize: COORD) -> BOOL ---
|
||||
SetConsoleWindowInfo :: proc(hConsoleOutput: HANDLE, bAbsolute : BOOL, lpConsoleWindow: ^SMALL_RECT) -> BOOL ---
|
||||
GetConsoleCursorInfo :: proc(hConsoleOutput: HANDLE, lpConsoleCursorInfo: PCONSOLE_CURSOR_INFO) -> BOOL ---
|
||||
SetConsoleCursorInfo :: proc(hConsoleOutput: HANDLE, lpConsoleCursorInfo: PCONSOLE_CURSOR_INFO) -> BOOL ---
|
||||
|
||||
GetDiskFreeSpaceExW :: proc(
|
||||
lpDirectoryName: LPCWSTR,
|
||||
|
||||
@@ -23,6 +23,8 @@ foreign shell32 {
|
||||
SHFileOperationW :: proc(lpFileOp: LPSHFILEOPSTRUCTW) -> c_int ---
|
||||
SHGetFolderPathW :: proc(hwnd: HWND, csidl: c_int, hToken: HANDLE, dwFlags: DWORD, pszPath: LPWSTR) -> HRESULT ---
|
||||
SHAppBarMessage :: proc(dwMessage: DWORD, pData: PAPPBARDATA) -> UINT_PTR ---
|
||||
|
||||
Shell_NotifyIconW :: proc(dwMessage: DWORD, lpData: ^NOTIFYICONDATAW) -> BOOL ---
|
||||
}
|
||||
|
||||
APPBARDATA :: struct {
|
||||
|
||||
+422
-162
@@ -145,8 +145,6 @@ PCONDITION_VARIABLE :: ^CONDITION_VARIABLE
|
||||
PLARGE_INTEGER :: ^LARGE_INTEGER
|
||||
PSRWLOCK :: ^SRWLOCK
|
||||
|
||||
MMRESULT :: UINT
|
||||
|
||||
CREATE_WAITABLE_TIMER_MANUAL_RESET :: 0x00000001
|
||||
CREATE_WAITABLE_TIMER_HIGH_RESOLUTION :: 0x00000002
|
||||
|
||||
@@ -154,10 +152,6 @@ TIMER_QUERY_STATE :: 0x0001
|
||||
TIMER_MODIFY_STATE :: 0x0002
|
||||
TIMER_ALL_ACCESS :: STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE | TIMER_QUERY_STATE | TIMER_MODIFY_STATE
|
||||
|
||||
SOCKET :: distinct uintptr // TODO
|
||||
socklen_t :: c_int
|
||||
ADDRESS_FAMILY :: USHORT
|
||||
|
||||
TRUE :: BOOL(true)
|
||||
FALSE :: BOOL(false)
|
||||
|
||||
@@ -265,26 +259,6 @@ GET_FILEEX_INFO_LEVELS :: distinct i32
|
||||
GetFileExInfoStandard: GET_FILEEX_INFO_LEVELS : 0
|
||||
GetFileExMaxInfoLevel: GET_FILEEX_INFO_LEVELS : 1
|
||||
|
||||
// String resource number bases (internal use)
|
||||
|
||||
MMSYSERR_BASE :: 0
|
||||
WAVERR_BASE :: 32
|
||||
MIDIERR_BASE :: 64
|
||||
TIMERR_BASE :: 96
|
||||
JOYERR_BASE :: 160
|
||||
MCIERR_BASE :: 256
|
||||
MIXERR_BASE :: 1024
|
||||
|
||||
MCI_STRING_OFFSET :: 512
|
||||
MCI_VD_OFFSET :: 1024
|
||||
MCI_CD_OFFSET :: 1088
|
||||
MCI_WAVE_OFFSET :: 1152
|
||||
MCI_SEQ_OFFSET :: 1216
|
||||
|
||||
// timer error return values
|
||||
TIMERR_NOERROR :: 0 // no error
|
||||
TIMERR_NOCANDO :: TIMERR_BASE + 1 // request not completed
|
||||
TIMERR_STRUCT :: TIMERR_BASE + 33 // time struct size
|
||||
|
||||
DIAGNOSTIC_REASON_VERSION :: 0
|
||||
|
||||
@@ -728,6 +702,14 @@ CWPRETSTRUCT :: struct {
|
||||
hwnd: HWND,
|
||||
}
|
||||
|
||||
MSLLHOOKSTRUCT :: struct {
|
||||
pt: POINT,
|
||||
mouseData: DWORD,
|
||||
flags: DWORD,
|
||||
time: DWORD,
|
||||
dwExtraInfo: ULONG_PTR,
|
||||
}
|
||||
|
||||
KBDLLHOOKSTRUCT :: struct {
|
||||
vkCode: DWORD,
|
||||
scanCode: DWORD,
|
||||
@@ -736,6 +718,59 @@ KBDLLHOOKSTRUCT :: struct {
|
||||
dwExtraInfo: ULONG_PTR,
|
||||
}
|
||||
|
||||
MOUSEINPUT :: struct {
|
||||
dx: LONG,
|
||||
dy: LONG,
|
||||
mouseData: DWORD,
|
||||
dwFlags: DWORD,
|
||||
time: DWORD,
|
||||
dwExtraInfo: ULONG_PTR,
|
||||
}
|
||||
|
||||
KEYBDINPUT :: struct {
|
||||
wVk: WORD,
|
||||
wScan: WORD,
|
||||
dwFlags: DWORD,
|
||||
time: DWORD,
|
||||
dwExtraInfo: ULONG_PTR,
|
||||
}
|
||||
|
||||
HARDWAREINPUT :: struct {
|
||||
uMsg: DWORD,
|
||||
wParamL: WORD,
|
||||
wParamH: WORD,
|
||||
}
|
||||
|
||||
INPUT_TYPE :: enum DWORD {
|
||||
MOUSE = 0,
|
||||
KEYBOARD = 1,
|
||||
HARDWARE = 2,
|
||||
}
|
||||
|
||||
INPUT :: struct {
|
||||
type: INPUT_TYPE,
|
||||
using _: struct #raw_union {
|
||||
mi: MOUSEINPUT,
|
||||
ki: KEYBDINPUT,
|
||||
hi: HARDWAREINPUT,
|
||||
},
|
||||
}
|
||||
|
||||
MOUSEEVENTF_MOVE :: 0x0001
|
||||
MOUSEEVENTF_LEFTDOWN :: 0x0002
|
||||
MOUSEEVENTF_LEFTUP :: 0x0004
|
||||
MOUSEEVENTF_RIGHTDOWN :: 0x0008
|
||||
MOUSEEVENTF_RIGHTUP :: 0x0010
|
||||
MOUSEEVENTF_MIDDLEDOWN :: 0x0020
|
||||
MOUSEEVENTF_MIDDLEUP :: 0x0040
|
||||
MOUSEEVENTF_XDOWN :: 0x0080
|
||||
MOUSEEVENTF_XUP :: 0x0100
|
||||
MOUSEEVENTF_WHEEL :: 0x0800
|
||||
MOUSEEVENTF_HWHEEL :: 0x1000
|
||||
MOUSEEVENTF_MOVE_NOCOALESCE :: 0x2000
|
||||
MOUSEEVENTF_VIRTUALDESK :: 0x4000
|
||||
MOUSEEVENTF_ABSOLUTE :: 0x8000
|
||||
|
||||
WNDCLASSA :: struct {
|
||||
style: UINT,
|
||||
lpfnWndProc: WNDPROC,
|
||||
@@ -803,6 +838,104 @@ MSG :: struct {
|
||||
|
||||
LPMSG :: ^MSG
|
||||
|
||||
NOTIFYICONDATAW :: struct {
|
||||
cbSize: DWORD,
|
||||
hWnd: HWND,
|
||||
uID: UINT,
|
||||
uFlags: UINT,
|
||||
uCallbackMessage: UINT,
|
||||
hIcon: HICON,
|
||||
szTip: [128]WCHAR,
|
||||
dwState: DWORD,
|
||||
dwStateMask: DWORD,
|
||||
szInfo: [256]WCHAR,
|
||||
using _: struct #raw_union {
|
||||
uTimeout: UINT,
|
||||
uVersion: UINT,
|
||||
},
|
||||
szInfoTitle: [64]WCHAR,
|
||||
dwInfoFlags: DWORD,
|
||||
guidItem: GUID,
|
||||
hBalloonIcon: HICON,
|
||||
}
|
||||
|
||||
NIF_MESSAGE :: 0x00000001
|
||||
NIF_ICON :: 0x00000002
|
||||
NIF_TIP :: 0x00000004
|
||||
NIF_STATE :: 0x00000008
|
||||
NIF_INFO :: 0x00000010
|
||||
NIF_GUID :: 0x00000020
|
||||
NIF_REALTIME :: 0x00000040
|
||||
NIF_SHOWTIP :: 0x00000080
|
||||
|
||||
NIM_ADD :: 0x00000000
|
||||
NIM_MODIFY :: 0x00000001
|
||||
NIM_DELETE :: 0x00000002
|
||||
NIM_SETFOCUS :: 0x00000003
|
||||
NIM_SETVERSION :: 0x00000004
|
||||
|
||||
// Menu flags for Add/Check/EnableMenuItem()
|
||||
MF_INSERT :: 0x00000000
|
||||
MF_CHANGE :: 0x00000080
|
||||
MF_APPEND :: 0x00000100
|
||||
MF_DELETE :: 0x00000200
|
||||
MF_REMOVE :: 0x00001000
|
||||
|
||||
MF_BYCOMMAND :: 0x00000000
|
||||
MF_BYPOSITION :: 0x00000400
|
||||
|
||||
MF_SEPARATOR :: 0x00000800
|
||||
|
||||
MF_ENABLED :: 0x00000000
|
||||
MF_GRAYED :: 0x00000001
|
||||
MF_DISABLED :: 0x00000002
|
||||
|
||||
MF_UNCHECKED :: 0x00000000
|
||||
MF_CHECKED :: 0x00000008
|
||||
MF_USECHECKBITMAPS :: 0x00000200
|
||||
|
||||
MF_STRING :: 0x00000000
|
||||
MF_BITMAP :: 0x00000004
|
||||
MF_OWNERDRAW :: 0x00000100
|
||||
|
||||
MF_POPUP :: 0x00000010
|
||||
MF_MENUBARBREAK :: 0x00000020
|
||||
MF_MENUBREAK :: 0x00000040
|
||||
|
||||
MF_UNHILITE :: 0x00000000
|
||||
MF_HILITE :: 0x00000080
|
||||
|
||||
MF_DEFAULT :: 0x00001000
|
||||
MF_SYSMENU :: 0x00002000
|
||||
MF_HELP :: 0x00004000
|
||||
MF_RIGHTJUSTIFY :: 0x00004000
|
||||
|
||||
MF_MOUSESELECT :: 0x00008000
|
||||
MF_END :: 0x00000080 // Obsolete -- only used by old RES files
|
||||
|
||||
// Flags for TrackPopupMenu
|
||||
TPM_LEFTBUTTON :: 0x0000
|
||||
TPM_RIGHTBUTTON :: 0x0002
|
||||
TPM_LEFTALIGN :: 0x0000
|
||||
TPM_CENTERALIGN :: 0x0004
|
||||
TPM_RIGHTALIGN :: 0x0008
|
||||
TPM_TOPALIGN :: 0x0000
|
||||
TPM_VCENTERALIGN :: 0x0010
|
||||
TPM_BOTTOMALIGN :: 0x0020
|
||||
|
||||
TPM_HORIZONTAL :: 0x0000 /* Horz alignment matters more */
|
||||
TPM_VERTICAL :: 0x0040 /* Vert alignment matters more */
|
||||
TPM_NONOTIFY :: 0x0080 /* Don't send any notification msgs */
|
||||
TPM_RETURNCMD :: 0x0100
|
||||
TPM_RECURSE :: 0x0001
|
||||
TPM_HORPOSANIMATION :: 0x0400
|
||||
TPM_HORNEGANIMATION :: 0x0800
|
||||
TPM_VERPOSANIMATION :: 0x1000
|
||||
TPM_VERNEGANIMATION :: 0x2000
|
||||
TPM_NOANIMATION :: 0x4000
|
||||
TPM_LAYOUTRTL :: 0x8000
|
||||
TPM_WORKAREA :: 0x10000
|
||||
|
||||
// WM_NCHITTEST and MOUSEHOOKSTRUCT Mouse Position Codes
|
||||
HTERROR :: -2
|
||||
HTTRANSPARENT :: -1
|
||||
@@ -1868,30 +2001,6 @@ BI_BITFIELDS :: 3
|
||||
BI_JPEG :: 4
|
||||
BI_PNG :: 5
|
||||
|
||||
WSA_FLAG_OVERLAPPED: DWORD : 0x01
|
||||
WSA_FLAG_NO_HANDLE_INHERIT: DWORD : 0x80
|
||||
|
||||
WSADESCRIPTION_LEN :: 256
|
||||
WSASYS_STATUS_LEN :: 128
|
||||
WSAPROTOCOL_LEN: DWORD : 255
|
||||
INVALID_SOCKET :: ~SOCKET(0)
|
||||
|
||||
WSAEACCES: c_int : 10013
|
||||
WSAEINVAL: c_int : 10022
|
||||
WSAEWOULDBLOCK: c_int : 10035
|
||||
WSAEPROTOTYPE: c_int : 10041
|
||||
WSAEADDRINUSE: c_int : 10048
|
||||
WSAEADDRNOTAVAIL: c_int : 10049
|
||||
WSAECONNABORTED: c_int : 10053
|
||||
WSAECONNRESET: c_int : 10054
|
||||
WSAENOTCONN: c_int : 10057
|
||||
WSAESHUTDOWN: c_int : 10058
|
||||
WSAETIMEDOUT: c_int : 10060
|
||||
WSAECONNREFUSED: c_int : 10061
|
||||
WSATRY_AGAIN: c_int : 11002
|
||||
|
||||
MAX_PROTOCOL_CHAIN: DWORD : 7
|
||||
|
||||
MAXIMUM_REPARSE_DATA_BUFFER_SIZE :: 16 * 1024
|
||||
FSCTL_GET_REPARSE_POINT: DWORD : 0x900a8
|
||||
IO_REPARSE_TAG_SYMLINK: DWORD : 0xa000000c
|
||||
@@ -1949,44 +2058,6 @@ CREATE_NEW_PROCESS_GROUP: DWORD : 0x00000200
|
||||
CREATE_UNICODE_ENVIRONMENT: DWORD : 0x00000400
|
||||
STARTF_USESTDHANDLES: DWORD : 0x00000100
|
||||
|
||||
AF_INET: c_int : 2
|
||||
AF_INET6: c_int : 23
|
||||
SD_BOTH: c_int : 2
|
||||
SD_RECEIVE: c_int : 0
|
||||
SD_SEND: c_int : 1
|
||||
SOCK_DGRAM: c_int : 2
|
||||
SOCK_STREAM: c_int : 1
|
||||
SOL_SOCKET: c_int : 0xffff
|
||||
SO_RCVTIMEO: c_int : 0x1006
|
||||
SO_SNDTIMEO: c_int : 0x1005
|
||||
SO_REUSEADDR: c_int : 0x0004
|
||||
IPPROTO_IP: c_int : 0
|
||||
IPPROTO_TCP: c_int : 6
|
||||
IPPROTO_IPV6: c_int : 41
|
||||
TCP_NODELAY: c_int : 0x0001
|
||||
IP_TTL: c_int : 4
|
||||
IPV6_V6ONLY: c_int : 27
|
||||
SO_ERROR: c_int : 0x1007
|
||||
SO_BROADCAST: c_int : 0x0020
|
||||
IP_MULTICAST_LOOP: c_int : 11
|
||||
IPV6_MULTICAST_LOOP: c_int : 11
|
||||
IP_MULTICAST_TTL: c_int : 10
|
||||
IP_ADD_MEMBERSHIP: c_int : 12
|
||||
IP_DROP_MEMBERSHIP: c_int : 13
|
||||
IPV6_ADD_MEMBERSHIP: c_int : 12
|
||||
IPV6_DROP_MEMBERSHIP: c_int : 13
|
||||
MSG_PEEK: c_int : 0x2
|
||||
|
||||
ip_mreq :: struct {
|
||||
imr_multiaddr: in_addr,
|
||||
imr_interface: in_addr,
|
||||
}
|
||||
|
||||
ipv6_mreq :: struct {
|
||||
ipv6mr_multiaddr: in6_addr,
|
||||
ipv6mr_interface: c_uint,
|
||||
}
|
||||
|
||||
VOLUME_NAME_DOS: DWORD : 0x0
|
||||
MOVEFILE_REPLACE_EXISTING: DWORD : 1
|
||||
|
||||
@@ -2369,11 +2440,6 @@ STARTUPINFO :: struct {
|
||||
hStdError: HANDLE,
|
||||
}
|
||||
|
||||
SOCKADDR :: struct {
|
||||
sa_family: ADDRESS_FAMILY,
|
||||
sa_data: [14]CHAR,
|
||||
}
|
||||
|
||||
FILETIME :: struct {
|
||||
dwLowDateTime: DWORD,
|
||||
dwHighDateTime: DWORD,
|
||||
@@ -2406,74 +2472,6 @@ ADDRESS_MODE :: enum c_int {
|
||||
AddrModeFlat,
|
||||
}
|
||||
|
||||
SOCKADDR_STORAGE_LH :: struct {
|
||||
ss_family: ADDRESS_FAMILY,
|
||||
__ss_pad1: [6]CHAR,
|
||||
__ss_align: i64,
|
||||
__ss_pad2: [112]CHAR,
|
||||
}
|
||||
|
||||
ADDRINFOA :: struct {
|
||||
ai_flags: c_int,
|
||||
ai_family: c_int,
|
||||
ai_socktype: c_int,
|
||||
ai_protocol: c_int,
|
||||
ai_addrlen: size_t,
|
||||
ai_canonname: ^c_char,
|
||||
ai_addr: ^SOCKADDR,
|
||||
ai_next: ^ADDRINFOA,
|
||||
}
|
||||
|
||||
PADDRINFOEXW :: ^ADDRINFOEXW
|
||||
LPADDRINFOEXW :: ^ADDRINFOEXW
|
||||
ADDRINFOEXW :: struct {
|
||||
ai_flags: c_int,
|
||||
ai_family: c_int,
|
||||
ai_socktype: c_int,
|
||||
ai_protocol: c_int,
|
||||
ai_addrlen: size_t,
|
||||
ai_canonname: wstring,
|
||||
ai_addr: ^sockaddr,
|
||||
ai_blob: rawptr,
|
||||
ai_bloblen: size_t,
|
||||
ai_provider: LPGUID,
|
||||
ai_next: ^ADDRINFOEXW,
|
||||
}
|
||||
|
||||
LPLOOKUPSERVICE_COMPLETION_ROUTINE :: #type proc "stdcall" (
|
||||
dwErrorCode: DWORD,
|
||||
dwNumberOfBytesTransfered: DWORD,
|
||||
lpOverlapped: LPOVERLAPPED,
|
||||
)
|
||||
|
||||
sockaddr :: struct {
|
||||
sa_family: USHORT,
|
||||
sa_data: [14]byte,
|
||||
}
|
||||
|
||||
sockaddr_in :: struct {
|
||||
sin_family: ADDRESS_FAMILY,
|
||||
sin_port: USHORT,
|
||||
sin_addr: in_addr,
|
||||
sin_zero: [8]CHAR,
|
||||
}
|
||||
|
||||
sockaddr_in6 :: struct {
|
||||
sin6_family: ADDRESS_FAMILY,
|
||||
sin6_port: USHORT,
|
||||
sin6_flowinfo: c_ulong,
|
||||
sin6_addr: in6_addr,
|
||||
sin6_scope_id: c_ulong,
|
||||
}
|
||||
|
||||
in_addr :: struct {
|
||||
s_addr: u32,
|
||||
}
|
||||
|
||||
in6_addr :: struct {
|
||||
s6_addr: [16]u8,
|
||||
}
|
||||
|
||||
EXCEPTION_DISPOSITION :: enum c_int {
|
||||
ExceptionContinueExecution,
|
||||
ExceptionContinueSearch,
|
||||
@@ -2564,6 +2562,27 @@ FILE_ATTRIBUTE_TAG_INFO :: struct {
|
||||
ReparseTag: DWORD,
|
||||
}
|
||||
|
||||
PADDRINFOEXW :: ^ADDRINFOEXW
|
||||
LPADDRINFOEXW :: ^ADDRINFOEXW
|
||||
ADDRINFOEXW :: struct {
|
||||
ai_flags: c_int,
|
||||
ai_family: c_int,
|
||||
ai_socktype: c_int,
|
||||
ai_protocol: c_int,
|
||||
ai_addrlen: size_t,
|
||||
ai_canonname: wstring,
|
||||
ai_addr: ^sockaddr,
|
||||
ai_blob: rawptr,
|
||||
ai_bloblen: size_t,
|
||||
ai_provider: LPGUID,
|
||||
ai_next: ^ADDRINFOEXW,
|
||||
}
|
||||
|
||||
LPLOOKUPSERVICE_COMPLETION_ROUTINE :: #type proc "stdcall" (
|
||||
dwErrorCode: DWORD,
|
||||
dwNumberOfBytesTransfered: DWORD,
|
||||
lpOverlapped: LPOVERLAPPED,
|
||||
)
|
||||
|
||||
|
||||
// https://docs.microsoft.com/en-gb/windows/win32/api/sysinfoapi/ns-sysinfoapi-system_info
|
||||
@@ -3900,5 +3919,246 @@ CONSOLE_SCREEN_BUFFER_INFO :: struct {
|
||||
dwMaximumWindowSize: COORD,
|
||||
}
|
||||
|
||||
CONSOLE_CURSOR_INFO :: struct {
|
||||
dwSize: DWORD,
|
||||
bVisible: BOOL,
|
||||
}
|
||||
|
||||
PCONSOLE_SCREEN_BUFFER_INFO :: ^CONSOLE_SCREEN_BUFFER_INFO
|
||||
|
||||
PCONSOLE_SCREEN_BUFFER_INFO :: ^CONSOLE_SCREEN_BUFFER_INFO
|
||||
PCONSOLE_CURSOR_INFO :: ^CONSOLE_CURSOR_INFO
|
||||
|
||||
//
|
||||
// Networking
|
||||
//
|
||||
WSA_FLAG_OVERLAPPED :: 1
|
||||
WSA_FLAG_MULTIPOINT_C_ROOT :: 2
|
||||
WSA_FLAG_MULTIPOINT_C_LEAF :: 4
|
||||
WSA_FLAG_MULTIPOINT_D_ROOT :: 8
|
||||
WSA_FLAG_MULTIPOINT_D_LEAF :: 16
|
||||
WSA_FLAG_ACCESS_SYSTEM_SECURITY :: 32
|
||||
WSA_FLAG_NO_HANDLE_INHERIT :: 128
|
||||
WSADESCRIPTION_LEN :: 256
|
||||
WSASYS_STATUS_LEN :: 128
|
||||
WSAPROTOCOL_LEN :: 255
|
||||
INVALID_SOCKET :: ~SOCKET(0)
|
||||
SOMAXCONN :: 128 // The number of messages that can be queued in memory after being received; use 2-4 for Bluetooth.
|
||||
// This is for the 'backlog' parameter to listen().
|
||||
SOCKET_ERROR :: -1
|
||||
|
||||
// Networking errors
|
||||
WSAEINTR :: 10004 // Call interrupted. CancelBlockingCall was called. (This is different on Linux.)
|
||||
WSAEACCES :: 10013 // If you try to bind a Udp socket to the broadcast address without the socket option set.
|
||||
WSAEFAULT :: 10014 // A pointer that was passed to a WSA function is invalid, such as a buffer size is smaller than you said it was
|
||||
WSAEINVAL :: 10022 // Invalid argument supplied
|
||||
WSAEMFILE :: 10024 // SOCKET handles exhausted
|
||||
WSAEWOULDBLOCK :: 10035 // No data is ready yet
|
||||
WSAENOTSOCK :: 10038 // Not a socket.
|
||||
WSAEINPROGRESS :: 10036 // WS1.1 call is in progress or callback function is still being processed
|
||||
WSAEALREADY :: 10037 // Already connecting in parallel.
|
||||
WSAEMSGSIZE :: 10040 // Message was truncated because it exceeded max datagram size.
|
||||
WSAEPROTOTYPE :: 10041 // Wrong protocol for the provided socket
|
||||
WSAENOPROTOOPT :: 10042 // TODO
|
||||
WSAEPROTONOSUPPORT :: 10043 // Protocol not supported
|
||||
WSAESOCKTNOSUPPORT :: 10044 // Socket type not supported in the given address family
|
||||
WSAEAFNOSUPPORT :: 10047 // Address family not supported
|
||||
WSAEOPNOTSUPP :: 10045 // Attempt to accept on non-stream socket, etc.
|
||||
WSAEADDRINUSE :: 10048 // Endpoint being bound is in use by another socket.
|
||||
WSAEADDRNOTAVAIL :: 10049 // Not a valid local IP address on this computer.
|
||||
WSAENETDOWN :: 10050 // Network subsystem failure on the local machine.
|
||||
WSAENETUNREACH :: 10051 // The local machine is not connected to the network.
|
||||
WSAENETRESET :: 10052 // Keepalive failure detected, or TTL exceeded when receiving UDP packets.
|
||||
WSAECONNABORTED :: 10053 // Connection has been aborted by software in the host machine.
|
||||
WSAECONNRESET :: 10054 // The connection was reset while trying to accept, read or write.
|
||||
WSAENOBUFS :: 10055 // No buffer space is available. The outgoing queue may be full in which case you should probably try again after a pause.
|
||||
WSAEISCONN :: 10056 // The socket is already connected.
|
||||
WSAENOTCONN :: 10057 // The socket is not connected yet, or no address was supplied to sendto.
|
||||
WSAESHUTDOWN :: 10058 // The socket has been shutdown in the direction required.
|
||||
WSAETIMEDOUT :: 10060 // The timeout duration was reached before any data was received / before all data was sent.
|
||||
WSAECONNREFUSED :: 10061 // The remote machine is not listening on that endpoint.
|
||||
WSAEHOSTDOWN :: 10064 // Destination host was down.
|
||||
WSAEHOSTUNREACH :: 10065 // The remote machine is not connected to the network.
|
||||
WSAENOTINITIALISED :: 10093 // Needs WSAStartup call
|
||||
WSAEINVALIDPROCTABLE :: 10104 // Invalid or incomplete procedure table was returned
|
||||
WSAEINVALIDPROVIDER :: 10105 // Service provider version is not 2.2
|
||||
WSAEPROVIDERFAILEDINIT :: 10106 // Service provider failed to initialize
|
||||
|
||||
// Address families
|
||||
AF_UNSPEC : c_int : 0 // Unspecified
|
||||
AF_INET : c_int : 2 // IPv4
|
||||
AF_INET6 : c_int : 23 // IPv6
|
||||
AF_IRDA : c_int : 26 // Infrared
|
||||
AF_BTH : c_int : 32 // Bluetooth
|
||||
|
||||
// Socket types
|
||||
SOCK_STREAM : c_int : 1 // TCP
|
||||
SOCK_DGRAM : c_int : 2 // UDP
|
||||
SOCK_RAW : c_int : 3 // Requires options IP_HDRINCL for v4, IPV6_HDRINCL for v6, on the socket
|
||||
SOCK_RDM : c_int : 4 // Requires "Reliable Multicast Protocol" to be installed - see WSAEnumProtocols
|
||||
SOCK_SEQPACKET : c_int : 5 // Provides psuedo-stream packet based on DGRAMs.
|
||||
|
||||
// Protocols
|
||||
IPPROTO_IP : c_int : 0
|
||||
IPPROTO_ICMP : c_int : 1 // (AF_UNSPEC, AF_INET, AF_INET6) + SOCK_RAW | not specified
|
||||
IPPROTO_IGMP : c_int : 2 // (AF_UNSPEC, AF_INET, AF_INET6) + SOCK_RAW | not specified
|
||||
BTHPROTO_RFCOMM : c_int : 3 // Bluetooth: AF_BTH + SOCK_STREAM
|
||||
IPPROTO_TCP : c_int : 6 // (AF_INET, AF_INET6) + SOCK_STREAM
|
||||
IPPROTO_UDP : c_int : 17 // (AF_INET, AF_INET6) + SOCK_DGRAM
|
||||
IPPROTO_ICMPV6 : c_int : 58 // (AF_UNSPEC, AF_INET, AF_INET6) + SOCK_RAW
|
||||
IPPROTO_RM : c_int : 113 // AF_INET + SOCK_RDM [requires "Reliable Multicast Protocol" to be installed - see WSAEnumProtocols]
|
||||
|
||||
// Shutdown manners
|
||||
SD_RECEIVE : c_int : 0
|
||||
SD_SEND : c_int : 1
|
||||
SD_BOTH : c_int : 2
|
||||
|
||||
// Socket 'levels'
|
||||
SOL_SOCKET : c_int : 0xffff // Socket options for any socket.
|
||||
IPPROTO_IPV6 : c_int : 41 // Socket options for IPV6.
|
||||
|
||||
// Options for any sockets
|
||||
SO_ACCEPTCONN : c_int : 0x0002
|
||||
SO_REUSEADDR : c_int : 0x0004
|
||||
SO_KEEPALIVE : c_int : 0x0008
|
||||
SO_SNDTIMEO : c_int : 0x1005
|
||||
SO_RCVTIMEO : c_int : 0x1006
|
||||
SO_EXCLUSIVEADDRUSE : c_int : ~SO_REUSEADDR
|
||||
SO_CONDITIONAL_ACCEPT : c_int : 0x3002
|
||||
SO_DONTLINGER : c_int : ~SO_LINGER
|
||||
SO_OOBINLINE : c_int : 0x0100
|
||||
SO_LINGER : c_int : 0x0080
|
||||
SO_RCVBUF : c_int : 0x1002
|
||||
SO_SNDBUF : c_int : 0x1001
|
||||
SO_ERROR : c_int : 0x1007
|
||||
SO_BROADCAST : c_int : 0x0020
|
||||
|
||||
TCP_NODELAY: c_int : 0x0001
|
||||
IP_TTL: c_int : 4
|
||||
IPV6_V6ONLY: c_int : 27
|
||||
IP_MULTICAST_LOOP: c_int : 11
|
||||
IPV6_MULTICAST_LOOP: c_int : 11
|
||||
IP_MULTICAST_TTL: c_int : 10
|
||||
IP_ADD_MEMBERSHIP: c_int : 12
|
||||
|
||||
IPV6_ADD_MEMBERSHIP: c_int : 12
|
||||
IPV6_DROP_MEMBERSHIP: c_int : 13
|
||||
|
||||
MAX_PROTOCOL_CHAIN: DWORD : 7
|
||||
|
||||
// Used with the SO_LINGER socket option to setsockopt().
|
||||
LINGER :: struct {
|
||||
l_onoff: c.ushort,
|
||||
l_linger: c.ushort,
|
||||
}
|
||||
// Send/Receive flags.
|
||||
MSG_OOB : c_int : 1 // `send`/`recv` should process out-of-band data.
|
||||
MSG_PEEK : c_int : 2 // `recv` should not remove the data from the buffer. Only valid for non-overlapped operations.
|
||||
|
||||
|
||||
SOCKET :: distinct uintptr // TODO
|
||||
socklen_t :: c_int
|
||||
ADDRESS_FAMILY :: USHORT
|
||||
|
||||
ip_mreq :: struct {
|
||||
imr_multiaddr: in_addr,
|
||||
imr_interface: in_addr,
|
||||
}
|
||||
|
||||
ipv6_mreq :: struct {
|
||||
ipv6mr_multiaddr: in6_addr,
|
||||
ipv6mr_interface: c_uint,
|
||||
}
|
||||
|
||||
SOCKADDR_STORAGE_LH :: struct {
|
||||
ss_family: ADDRESS_FAMILY,
|
||||
__ss_pad1: [6]CHAR,
|
||||
__ss_align: i64,
|
||||
__ss_pad2: [112]CHAR,
|
||||
}
|
||||
|
||||
ADDRINFOA :: struct {
|
||||
ai_flags: c_int,
|
||||
ai_family: c_int,
|
||||
ai_socktype: c_int,
|
||||
ai_protocol: c_int,
|
||||
ai_addrlen: size_t,
|
||||
ai_canonname: ^c_char,
|
||||
ai_addr: ^SOCKADDR,
|
||||
ai_next: ^ADDRINFOA,
|
||||
}
|
||||
|
||||
sockaddr :: struct {
|
||||
sa_family: USHORT,
|
||||
sa_data: [14]byte,
|
||||
}
|
||||
|
||||
sockaddr_in :: struct {
|
||||
sin_family: ADDRESS_FAMILY,
|
||||
sin_port: u16be,
|
||||
sin_addr: in_addr,
|
||||
sin_zero: [8]CHAR,
|
||||
}
|
||||
sockaddr_in6 :: struct {
|
||||
sin6_family: ADDRESS_FAMILY,
|
||||
sin6_port: u16be,
|
||||
sin6_flowinfo: c_ulong,
|
||||
sin6_addr: in6_addr,
|
||||
sin6_scope_id: c_ulong,
|
||||
}
|
||||
|
||||
in_addr :: struct {
|
||||
s_addr: u32,
|
||||
}
|
||||
|
||||
in6_addr :: struct {
|
||||
s6_addr: [16]u8,
|
||||
}
|
||||
|
||||
|
||||
DNS_STATUS :: distinct DWORD // zero is success
|
||||
DNS_INFO_NO_RECORDS :: 9501
|
||||
DNS_QUERY_NO_RECURSION :: 0x00000004
|
||||
|
||||
DNS_RECORD :: struct {
|
||||
pNext: ^DNS_RECORD,
|
||||
pName: cstring,
|
||||
wType: WORD,
|
||||
wDataLength: USHORT,
|
||||
Flags: DWORD,
|
||||
dwTtl: DWORD,
|
||||
_: DWORD,
|
||||
Data: struct #raw_union {
|
||||
CNAME: DNS_PTR_DATAA,
|
||||
A: u32be, // Ipv4 Address
|
||||
AAAA: u128be, // Ipv6 Address
|
||||
TXT: DNS_TXT_DATAA,
|
||||
NS: DNS_PTR_DATAA,
|
||||
MX: DNS_MX_DATAA,
|
||||
SRV: DNS_SRV_DATAA,
|
||||
},
|
||||
}
|
||||
|
||||
DNS_TXT_DATAA :: struct {
|
||||
dwStringCount: DWORD,
|
||||
pStringArray: cstring,
|
||||
}
|
||||
|
||||
DNS_PTR_DATAA :: cstring
|
||||
|
||||
DNS_MX_DATAA :: struct {
|
||||
pNameExchange: cstring, // the hostname
|
||||
wPreference: WORD, // lower values preferred
|
||||
_: WORD, // padding.
|
||||
}
|
||||
DNS_SRV_DATAA :: struct {
|
||||
pNameTarget: cstring,
|
||||
wPriority: u16,
|
||||
wWeight: u16,
|
||||
wPort: u16,
|
||||
_: WORD, // padding
|
||||
}
|
||||
|
||||
SOCKADDR :: struct {
|
||||
sa_family: ADDRESS_FAMILY,
|
||||
sa_data: [14]CHAR,
|
||||
}
|
||||
|
||||
@@ -109,6 +109,12 @@ foreign user32 {
|
||||
GetDlgCtrlID :: proc(hWnd: HWND) -> c_int ---
|
||||
GetDlgItem :: proc(hDlg: HWND, nIDDlgItem: c_int) -> HWND ---
|
||||
|
||||
CreatePopupMenu :: proc() -> HMENU ---
|
||||
DestroyMenu :: proc(hMenu: HMENU) -> BOOL ---
|
||||
AppendMenuW :: proc(hMenu: HMENU, uFlags: UINT, uIDNewItem: UINT_PTR, lpNewItem: LPCWSTR) -> BOOL ---
|
||||
TrackPopupMenu :: proc(hMenu: HMENU, uFlags: UINT, x: int, y: int, nReserved: int, hWnd: HWND, prcRect: ^RECT) -> i32 ---
|
||||
RegisterWindowMessageW :: proc(lpString: LPCWSTR) -> UINT ---
|
||||
|
||||
GetUpdateRect :: proc(hWnd: HWND, lpRect: LPRECT, bErase: BOOL) -> BOOL ---
|
||||
ValidateRect :: proc(hWnd: HWND, lpRect: ^RECT) -> BOOL ---
|
||||
InvalidateRect :: proc(hWnd: HWND, lpRect: ^RECT, bErase: BOOL) -> BOOL ---
|
||||
@@ -206,6 +212,8 @@ foreign user32 {
|
||||
GetRegisteredRawInputDevices :: proc(pRawInputDevices: PRAWINPUTDEVICE, puiNumDevices: PUINT, cbSize: UINT) -> UINT ---
|
||||
RegisterRawInputDevices :: proc(pRawInputDevices: PCRAWINPUTDEVICE, uiNumDevices: UINT, cbSize: UINT) -> BOOL ---
|
||||
|
||||
SendInput :: proc(cInputs: UINT, pInputs: [^]INPUT, cbSize: ^c_int) -> UINT ---
|
||||
|
||||
SetLayeredWindowAttributes :: proc(hWnd: HWND, crKey: COLORREF, bAlpha: BYTE, dwFlags: DWORD) -> BOOL ---
|
||||
|
||||
FillRect :: proc(hDC: HDC, lprc: ^RECT, hbr: HBRUSH) -> int ---
|
||||
@@ -469,4 +477,4 @@ WINDOWINFO :: struct {
|
||||
atomWindowType: ATOM,
|
||||
wCreatorVersion: WORD,
|
||||
}
|
||||
PWINDOWINFO :: ^WINDOWINFO
|
||||
PWINDOWINFO :: ^WINDOWINFO
|
||||
|
||||
@@ -485,3 +485,24 @@ run_as_user :: proc(username, password, application, commandline: string, pi: ^P
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
ensure_winsock_initialized :: proc() {
|
||||
@static gate := false
|
||||
@static initted := false
|
||||
|
||||
if initted {
|
||||
return
|
||||
}
|
||||
|
||||
for intrinsics.atomic_compare_exchange_strong(&gate, false, true) {
|
||||
intrinsics.cpu_relax()
|
||||
}
|
||||
defer intrinsics.atomic_store(&gate, false)
|
||||
|
||||
unused_info: WSADATA
|
||||
version_requested := WORD(2) << 8 | 2
|
||||
res := WSAStartup(version_requested, &unused_info)
|
||||
assert(res == 0, "unable to initialized Winsock2")
|
||||
|
||||
initted = true
|
||||
}
|
||||
|
||||
@@ -3,9 +3,170 @@ package sys_windows
|
||||
|
||||
foreign import winmm "system:Winmm.lib"
|
||||
|
||||
MMRESULT :: UINT
|
||||
|
||||
@(default_calling_convention="stdcall")
|
||||
foreign winmm {
|
||||
timeGetDevCaps :: proc(ptc: LPTIMECAPS, cbtc: UINT) -> MMRESULT ---
|
||||
timeBeginPeriod :: proc(uPeriod: UINT) -> MMRESULT ---
|
||||
timeEndPeriod :: proc(uPeriod: UINT) -> MMRESULT ---
|
||||
timeGetTime :: proc() -> DWORD ---
|
||||
}
|
||||
|
||||
LPTIMECAPS :: ^TIMECAPS
|
||||
TIMECAPS :: struct {
|
||||
wPeriodMin: UINT,
|
||||
wPeriodMax: UINT,
|
||||
}
|
||||
|
||||
// String resource number bases (internal use)
|
||||
MMSYSERR_BASE :: 0
|
||||
WAVERR_BASE :: 32
|
||||
MIDIERR_BASE :: 64
|
||||
TIMERR_BASE :: 96
|
||||
JOYERR_BASE :: 160
|
||||
MCIERR_BASE :: 256
|
||||
MIXERR_BASE :: 1024
|
||||
|
||||
MCI_STRING_OFFSET :: 512
|
||||
MCI_VD_OFFSET :: 1024
|
||||
MCI_CD_OFFSET :: 1088
|
||||
MCI_WAVE_OFFSET :: 1152
|
||||
MCI_SEQ_OFFSET :: 1216
|
||||
|
||||
/* general error return values */
|
||||
MMSYSERR_NOERROR :: 0 /* no error */
|
||||
MMSYSERR_ERROR :: MMSYSERR_BASE + 1 /* unspecified error */
|
||||
MMSYSERR_BADDEVICEID :: MMSYSERR_BASE + 2 /* device ID out of range */
|
||||
MMSYSERR_NOTENABLED :: MMSYSERR_BASE + 3 /* driver failed enable */
|
||||
MMSYSERR_ALLOCATED :: MMSYSERR_BASE + 4 /* device already allocated */
|
||||
MMSYSERR_INVALHANDLE :: MMSYSERR_BASE + 5 /* device handle is invalid */
|
||||
MMSYSERR_NODRIVER :: MMSYSERR_BASE + 6 /* no device driver present */
|
||||
MMSYSERR_NOMEM :: MMSYSERR_BASE + 7 /* memory allocation error */
|
||||
MMSYSERR_NOTSUPPORTED :: MMSYSERR_BASE + 8 /* function isn't supported */
|
||||
MMSYSERR_BADERRNUM :: MMSYSERR_BASE + 9 /* error value out of range */
|
||||
MMSYSERR_INVALFLAG :: MMSYSERR_BASE + 10 /* invalid flag passed */
|
||||
MMSYSERR_INVALPARAM :: MMSYSERR_BASE + 11 /* invalid parameter passed */
|
||||
MMSYSERR_HANDLEBUSY :: MMSYSERR_BASE + 12 /* handle being used simultaneously on another thread (eg callback) */
|
||||
MMSYSERR_INVALIDALIAS :: MMSYSERR_BASE + 13 /* specified alias not found */
|
||||
MMSYSERR_BADDB :: MMSYSERR_BASE + 14 /* bad registry database */
|
||||
MMSYSERR_KEYNOTFOUND :: MMSYSERR_BASE + 15 /* registry key not found */
|
||||
MMSYSERR_READERROR :: MMSYSERR_BASE + 16 /* registry read error */
|
||||
MMSYSERR_WRITEERROR :: MMSYSERR_BASE + 17 /* registry write error */
|
||||
MMSYSERR_DELETEERROR :: MMSYSERR_BASE + 18 /* registry delete error */
|
||||
MMSYSERR_VALNOTFOUND :: MMSYSERR_BASE + 19 /* registry value not found */
|
||||
MMSYSERR_NODRIVERCB :: MMSYSERR_BASE + 20 /* driver does not call DriverCallback */
|
||||
MMSYSERR_MOREDATA :: MMSYSERR_BASE + 21 /* more data to be returned */
|
||||
MMSYSERR_LASTERROR :: MMSYSERR_BASE + 21 /* last error in range */
|
||||
|
||||
/* waveform audio error return values */
|
||||
WAVERR_BADFORMAT :: WAVERR_BASE + 0 /* unsupported wave format */
|
||||
WAVERR_STILLPLAYING :: WAVERR_BASE + 1 /* still something playing */
|
||||
WAVERR_UNPREPARED :: WAVERR_BASE + 2 /* header not prepared */
|
||||
WAVERR_SYNC :: WAVERR_BASE + 3 /* device is synchronous */
|
||||
WAVERR_LASTERROR :: WAVERR_BASE + 3 /* last error in range */
|
||||
|
||||
/* MIDI error return values */
|
||||
MIDIERR_UNPREPARED :: MIDIERR_BASE + 0 /* header not prepared */
|
||||
MIDIERR_STILLPLAYING :: MIDIERR_BASE + 1 /* still something playing */
|
||||
MIDIERR_NOMAP :: MIDIERR_BASE + 2 /* no configured instruments */
|
||||
MIDIERR_NOTREADY :: MIDIERR_BASE + 3 /* hardware is still busy */
|
||||
MIDIERR_NODEVICE :: MIDIERR_BASE + 4 /* port no longer connected */
|
||||
MIDIERR_INVALIDSETUP :: MIDIERR_BASE + 5 /* invalid MIF */
|
||||
MIDIERR_BADOPENMODE :: MIDIERR_BASE + 6 /* operation unsupported w/ open mode */
|
||||
MIDIERR_DONT_CONTINUE :: MIDIERR_BASE + 7 /* thru device 'eating' a message */
|
||||
MIDIERR_LASTERROR :: MIDIERR_BASE + 7 /* last error in range */
|
||||
|
||||
/* timer error return values */
|
||||
TIMERR_NOERROR :: 0 /* no error */
|
||||
TIMERR_NOCANDO :: TIMERR_BASE + 1 /* request not completed */
|
||||
TIMERR_STRUCT :: TIMERR_BASE + 33 /* time struct size */
|
||||
|
||||
/* joystick error return values */
|
||||
JOYERR_NOERROR :: 0 /* no error */
|
||||
JOYERR_PARMS :: JOYERR_BASE + 5 /* bad parameters */
|
||||
JOYERR_NOCANDO :: JOYERR_BASE + 6 /* request not completed */
|
||||
JOYERR_UNPLUGGED :: JOYERR_BASE + 7 /* joystick is unplugged */
|
||||
|
||||
/* MCI error return values */
|
||||
MCIERR_INVALID_DEVICE_ID :: MCIERR_BASE + 1
|
||||
MCIERR_UNRECOGNIZED_KEYWORD :: MCIERR_BASE + 3
|
||||
MCIERR_UNRECOGNIZED_COMMAND :: MCIERR_BASE + 5
|
||||
MCIERR_HARDWARE :: MCIERR_BASE + 6
|
||||
MCIERR_INVALID_DEVICE_NAME :: MCIERR_BASE + 7
|
||||
MCIERR_OUT_OF_MEMORY :: MCIERR_BASE + 8
|
||||
MCIERR_DEVICE_OPEN :: MCIERR_BASE + 9
|
||||
MCIERR_CANNOT_LOAD_DRIVER :: MCIERR_BASE + 10
|
||||
MCIERR_MISSING_COMMAND_STRING :: MCIERR_BASE + 11
|
||||
MCIERR_PARAM_OVERFLOW :: MCIERR_BASE + 12
|
||||
MCIERR_MISSING_STRING_ARGUMENT :: MCIERR_BASE + 13
|
||||
MCIERR_BAD_INTEGER :: MCIERR_BASE + 14
|
||||
MCIERR_PARSER_INTERNAL :: MCIERR_BASE + 15
|
||||
MCIERR_DRIVER_INTERNAL :: MCIERR_BASE + 16
|
||||
MCIERR_MISSING_PARAMETER :: MCIERR_BASE + 17
|
||||
MCIERR_UNSUPPORTED_FUNCTION :: MCIERR_BASE + 18
|
||||
MCIERR_FILE_NOT_FOUND :: MCIERR_BASE + 19
|
||||
MCIERR_DEVICE_NOT_READY :: MCIERR_BASE + 20
|
||||
MCIERR_INTERNAL :: MCIERR_BASE + 21
|
||||
MCIERR_DRIVER :: MCIERR_BASE + 22
|
||||
MCIERR_CANNOT_USE_ALL :: MCIERR_BASE + 23
|
||||
MCIERR_MULTIPLE :: MCIERR_BASE + 24
|
||||
MCIERR_EXTENSION_NOT_FOUND :: MCIERR_BASE + 25
|
||||
MCIERR_OUTOFRANGE :: MCIERR_BASE + 26
|
||||
MCIERR_FLAGS_NOT_COMPATIBLE :: MCIERR_BASE + 28
|
||||
MCIERR_FILE_NOT_SAVED :: MCIERR_BASE + 30
|
||||
MCIERR_DEVICE_TYPE_REQUIRED :: MCIERR_BASE + 31
|
||||
MCIERR_DEVICE_LOCKED :: MCIERR_BASE + 32
|
||||
MCIERR_DUPLICATE_ALIAS :: MCIERR_BASE + 33
|
||||
MCIERR_BAD_CONSTANT :: MCIERR_BASE + 34
|
||||
MCIERR_MUST_USE_SHAREABLE :: MCIERR_BASE + 35
|
||||
MCIERR_MISSING_DEVICE_NAME :: MCIERR_BASE + 36
|
||||
MCIERR_BAD_TIME_FORMAT :: MCIERR_BASE + 37
|
||||
MCIERR_NO_CLOSING_QUOTE :: MCIERR_BASE + 38
|
||||
MCIERR_DUPLICATE_FLAGS :: MCIERR_BASE + 39
|
||||
MCIERR_INVALID_FILE :: MCIERR_BASE + 40
|
||||
MCIERR_NULL_PARAMETER_BLOCK :: MCIERR_BASE + 41
|
||||
MCIERR_UNNAMED_RESOURCE :: MCIERR_BASE + 42
|
||||
MCIERR_NEW_REQUIRES_ALIAS :: MCIERR_BASE + 43
|
||||
MCIERR_NOTIFY_ON_AUTO_OPEN :: MCIERR_BASE + 44
|
||||
MCIERR_NO_ELEMENT_ALLOWED :: MCIERR_BASE + 45
|
||||
MCIERR_NONAPPLICABLE_FUNCTION :: MCIERR_BASE + 46
|
||||
MCIERR_ILLEGAL_FOR_AUTO_OPEN :: MCIERR_BASE + 47
|
||||
MCIERR_FILENAME_REQUIRED :: MCIERR_BASE + 48
|
||||
MCIERR_EXTRA_CHARACTERS :: MCIERR_BASE + 49
|
||||
MCIERR_DEVICE_NOT_INSTALLED :: MCIERR_BASE + 50
|
||||
MCIERR_GET_CD :: MCIERR_BASE + 51
|
||||
MCIERR_SET_CD :: MCIERR_BASE + 52
|
||||
MCIERR_SET_DRIVE :: MCIERR_BASE + 53
|
||||
MCIERR_DEVICE_LENGTH :: MCIERR_BASE + 54
|
||||
MCIERR_DEVICE_ORD_LENGTH :: MCIERR_BASE + 55
|
||||
MCIERR_NO_INTEGER :: MCIERR_BASE + 56
|
||||
MCIERR_WAVE_OUTPUTSINUSE :: MCIERR_BASE + 64
|
||||
MCIERR_WAVE_SETOUTPUTINUSE :: MCIERR_BASE + 65
|
||||
MCIERR_WAVE_INPUTSINUSE :: MCIERR_BASE + 66
|
||||
MCIERR_WAVE_SETINPUTINUSE :: MCIERR_BASE + 67
|
||||
MCIERR_WAVE_OUTPUTUNSPECIFIED :: MCIERR_BASE + 68
|
||||
MCIERR_WAVE_INPUTUNSPECIFIED :: MCIERR_BASE + 69
|
||||
MCIERR_WAVE_OUTPUTSUNSUITABLE :: MCIERR_BASE + 70
|
||||
MCIERR_WAVE_SETOUTPUTUNSUITABLE :: MCIERR_BASE + 71
|
||||
MCIERR_WAVE_INPUTSUNSUITABLE :: MCIERR_BASE + 72
|
||||
MCIERR_WAVE_SETINPUTUNSUITABLE :: MCIERR_BASE + 73
|
||||
MCIERR_SEQ_DIV_INCOMPATIBLE :: MCIERR_BASE + 80
|
||||
MCIERR_SEQ_PORT_INUSE :: MCIERR_BASE + 81
|
||||
MCIERR_SEQ_PORT_NONEXISTENT :: MCIERR_BASE + 82
|
||||
MCIERR_SEQ_PORT_MAPNODEVICE :: MCIERR_BASE + 83
|
||||
MCIERR_SEQ_PORT_MISCERROR :: MCIERR_BASE + 84
|
||||
MCIERR_SEQ_TIMER :: MCIERR_BASE + 85
|
||||
MCIERR_SEQ_PORTUNSPECIFIED :: MCIERR_BASE + 86
|
||||
MCIERR_SEQ_NOMIDIPRESENT :: MCIERR_BASE + 87
|
||||
MCIERR_NO_WINDOW :: MCIERR_BASE + 90
|
||||
MCIERR_CREATEWINDOW :: MCIERR_BASE + 91
|
||||
MCIERR_FILE_READ :: MCIERR_BASE + 92
|
||||
MCIERR_FILE_WRITE :: MCIERR_BASE + 93
|
||||
MCIERR_NO_IDENTITY :: MCIERR_BASE + 94
|
||||
|
||||
/* MMRESULT error return values specific to the mixer API */
|
||||
MIXERR_INVALLINE :: (MIXERR_BASE + 0)
|
||||
MIXERR_INVALCONTROL :: (MIXERR_BASE + 1)
|
||||
MIXERR_INVALVALUE :: (MIXERR_BASE + 2)
|
||||
MIXERR_LASTERROR :: (MIXERR_BASE + 2)
|
||||
@@ -54,7 +54,7 @@ foreign ws2_32 {
|
||||
buf: rawptr,
|
||||
len: c_int,
|
||||
flags: c_int,
|
||||
addr: ^SOCKADDR,
|
||||
addr: ^SOCKADDR_STORAGE_LH,
|
||||
addrlen: ^c_int,
|
||||
) -> c_int ---
|
||||
sendto :: proc(
|
||||
@@ -62,11 +62,11 @@ foreign ws2_32 {
|
||||
buf: rawptr,
|
||||
len: c_int,
|
||||
flags: c_int,
|
||||
addr: ^SOCKADDR,
|
||||
addr: ^SOCKADDR_STORAGE_LH,
|
||||
addrlen: c_int,
|
||||
) -> c_int ---
|
||||
shutdown :: proc(socket: SOCKET, how: c_int) -> c_int ---
|
||||
accept :: proc(socket: SOCKET, address: ^SOCKADDR, address_len: ^c_int) -> SOCKET ---
|
||||
accept :: proc(socket: SOCKET, address: ^SOCKADDR_STORAGE_LH, address_len: ^c_int) -> SOCKET ---
|
||||
|
||||
setsockopt :: proc(
|
||||
s: SOCKET,
|
||||
@@ -75,11 +75,11 @@ foreign ws2_32 {
|
||||
optval: rawptr,
|
||||
optlen: c_int,
|
||||
) -> c_int ---
|
||||
getsockname :: proc(socket: SOCKET, address: ^SOCKADDR, address_len: ^c_int) -> c_int ---
|
||||
getpeername :: proc(socket: SOCKET, address: ^SOCKADDR, address_len: ^c_int) -> c_int ---
|
||||
bind :: proc(socket: SOCKET, address: ^SOCKADDR, address_len: socklen_t) -> c_int ---
|
||||
getsockname :: proc(socket: SOCKET, address: ^SOCKADDR_STORAGE_LH, address_len: ^c_int) -> c_int ---
|
||||
getpeername :: proc(socket: SOCKET, address: ^SOCKADDR_STORAGE_LH, address_len: ^c_int) -> c_int ---
|
||||
bind :: proc(socket: SOCKET, address: ^SOCKADDR_STORAGE_LH, address_len: socklen_t) -> c_int ---
|
||||
listen :: proc(socket: SOCKET, backlog: c_int) -> c_int ---
|
||||
connect :: proc(socket: SOCKET, address: ^SOCKADDR, len: c_int) -> c_int ---
|
||||
connect :: proc(socket: SOCKET, address: ^SOCKADDR_STORAGE_LH, len: c_int) -> c_int ---
|
||||
getaddrinfo :: proc(
|
||||
node: cstring,
|
||||
service: cstring,
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
package table implements ascii/markdown/html/custom rendering of tables.
|
||||
|
||||
---
|
||||
|
||||
Custom rendering example:
|
||||
|
||||
```odin
|
||||
tbl := init(&Table{})
|
||||
padding(tbl, 0, 1)
|
||||
row(tbl, "A_LONG_ENUM", "= 54,", "// A comment about A_LONG_ENUM")
|
||||
row(tbl, "AN_EVEN_LONGER_ENUM", "= 1,", "// A comment about AN_EVEN_LONGER_ENUM")
|
||||
build(tbl)
|
||||
for row in 0..<tbl.nr_rows {
|
||||
for col in 0..<tbl.nr_cols {
|
||||
write_table_cell(stdio_writer(), tbl, row, col)
|
||||
}
|
||||
io.write_byte(stdio_writer(), '\n')
|
||||
}
|
||||
```
|
||||
|
||||
This outputs:
|
||||
```
|
||||
A_LONG_ENUM = 54, // A comment about A_LONG_ENUM
|
||||
AN_EVEN_LONGER_ENUM = 1, // A comment about AN_EVEN_LONGER_ENUM
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
ASCII rendering example:
|
||||
|
||||
```odin
|
||||
tbl := init(&Table{})
|
||||
defer destroy(tbl)
|
||||
|
||||
caption(tbl, "This is a table caption and it is very long")
|
||||
|
||||
padding(tbl, 1, 1) // Left/right padding of cells
|
||||
|
||||
header(tbl, "AAAAAAAAA", "B")
|
||||
header(tbl, "C") // Appends to previous header row. Same as if done header("AAAAAAAAA", "B", "C") from start.
|
||||
|
||||
// Create a row with two values. Since there are three columns the third
|
||||
// value will become the empty string.
|
||||
//
|
||||
// NOTE: header() is not allowed anymore after this.
|
||||
row(tbl, 123, "foo")
|
||||
|
||||
// Use `format()` if you need custom formatting. This will allocate into
|
||||
// the arena specified at init.
|
||||
row(tbl,
|
||||
format(tbl, "%09d", 5),
|
||||
format(tbl, "%.6f", 6.28318530717958647692528676655900576))
|
||||
|
||||
// A row with zero values is allowed as long as a previous row or header
|
||||
// exist. The value and alignment of each cell can then be set
|
||||
// individually.
|
||||
row(tbl)
|
||||
set_cell_value_and_alignment(tbl, last_row(tbl), 0, "a", .Center)
|
||||
set_cell_value(tbl, last_row(tbl), 1, "bbb")
|
||||
set_cell_value(tbl, last_row(tbl), 2, "c")
|
||||
|
||||
// Headers are regular cells, too. Use header_row() as row index to modify
|
||||
// header cells.
|
||||
set_cell_alignment(tbl, header_row(tbl), 1, .Center) // Sets alignment of 'B' column to Center.
|
||||
set_cell_alignment(tbl, header_row(tbl), 2, .Right) // Sets alignment of 'C' column to Right.
|
||||
|
||||
build(tbl)
|
||||
|
||||
write_ascii_table(stdio_writer(), tbl)
|
||||
write_markdown_table(stdio_writer(), tbl)
|
||||
```
|
||||
|
||||
This outputs:
|
||||
```
|
||||
+-----------------------------------------------+
|
||||
| This is a table caption and it is very long |
|
||||
+------------------+-----------------+----------+
|
||||
| AAAAAAAAA | B | C |
|
||||
+------------------+-----------------+----------+
|
||||
| 123 | foo | |
|
||||
| 000000005 | 6.283185 | |
|
||||
| a | bbb | c |
|
||||
+------------------+-----------------+----------+
|
||||
```
|
||||
|
||||
and
|
||||
|
||||
```
|
||||
| AAAAAAAAA | B | C |
|
||||
|:-----------------|:---------------:|---------:|
|
||||
| 123 | foo | |
|
||||
| 000000005 | 6.283185 | |
|
||||
| a | bbb | c |
|
||||
```
|
||||
|
||||
respectively.
|
||||
*/
|
||||
|
||||
package text_table
|
||||
@@ -0,0 +1,384 @@
|
||||
/*
|
||||
Copyright 2023 oskarnp <oskarnp@proton.me>
|
||||
Made available under Odin's BSD-3 license.
|
||||
|
||||
List of contributors:
|
||||
oskarnp: Initial implementation.
|
||||
*/
|
||||
|
||||
package text_table
|
||||
|
||||
import "core:io"
|
||||
import "core:os"
|
||||
import "core:fmt"
|
||||
import "core:mem"
|
||||
import "core:mem/virtual"
|
||||
import "core:runtime"
|
||||
import "core:strings"
|
||||
|
||||
Cell :: struct {
|
||||
text: string,
|
||||
alignment: Cell_Alignment,
|
||||
}
|
||||
|
||||
Cell_Alignment :: enum {
|
||||
Left,
|
||||
Center,
|
||||
Right,
|
||||
}
|
||||
|
||||
Table :: struct {
|
||||
lpad, rpad: int, // Cell padding (left/right)
|
||||
cells: [dynamic]Cell,
|
||||
caption: string,
|
||||
nr_rows, nr_cols: int,
|
||||
has_header_row: bool,
|
||||
table_allocator: runtime.Allocator, // Used for allocating cells/colw
|
||||
format_allocator: runtime.Allocator, // Used for allocating Cell.text when applicable
|
||||
|
||||
dirty: bool, // True if build() needs to be called before rendering
|
||||
|
||||
// The following are computed on build()
|
||||
colw: [dynamic]int, // Width of each column (including padding, excluding borders)
|
||||
tblw: int, // Width of entire table (including padding, excluding borders)
|
||||
}
|
||||
|
||||
init :: proc{init_with_allocator, init_with_virtual_arena, init_with_mem_arena}
|
||||
|
||||
init_with_allocator :: proc(tbl: ^Table, format_allocator := context.temp_allocator, table_allocator := context.allocator) -> ^Table {
|
||||
tbl.table_allocator = table_allocator
|
||||
tbl.cells = make([dynamic]Cell, tbl.table_allocator)
|
||||
tbl.colw = make([dynamic]int, tbl.table_allocator)
|
||||
tbl.format_allocator = format_allocator
|
||||
return tbl
|
||||
}
|
||||
init_with_virtual_arena :: proc(tbl: ^Table, format_arena: ^virtual.Arena, table_allocator := context.allocator) -> ^Table {
|
||||
return init_with_allocator(tbl, virtual.arena_allocator(format_arena), table_allocator)
|
||||
}
|
||||
init_with_mem_arena :: proc(tbl: ^Table, format_arena: ^mem.Arena, table_allocator := context.allocator) -> ^Table {
|
||||
return init_with_allocator(tbl, mem.arena_allocator(format_arena), table_allocator)
|
||||
}
|
||||
|
||||
destroy :: proc(tbl: ^Table) {
|
||||
free_all(tbl.format_allocator)
|
||||
delete(tbl.cells)
|
||||
delete(tbl.colw)
|
||||
}
|
||||
|
||||
caption :: proc(tbl: ^Table, value: string) {
|
||||
tbl.caption = value
|
||||
tbl.dirty = true
|
||||
}
|
||||
|
||||
padding :: proc(tbl: ^Table, lpad, rpad: int) {
|
||||
tbl.lpad = lpad
|
||||
tbl.rpad = rpad
|
||||
tbl.dirty = true
|
||||
}
|
||||
|
||||
get_cell :: proc(tbl: ^Table, row, col: int, loc := #caller_location) -> ^Cell {
|
||||
assert(col >= 0 && col < tbl.nr_cols, "cell column out of range", loc)
|
||||
assert(row >= 0 && row < tbl.nr_rows, "cell row out of range", loc)
|
||||
resize(&tbl.cells, tbl.nr_cols * tbl.nr_rows)
|
||||
return &tbl.cells[row*tbl.nr_cols + col]
|
||||
}
|
||||
|
||||
set_cell_value_and_alignment :: proc(tbl: ^Table, row, col: int, value: string, alignment: Cell_Alignment) {
|
||||
cell := get_cell(tbl, row, col)
|
||||
cell.text = format(tbl, "%v", value)
|
||||
cell.alignment = alignment
|
||||
tbl.dirty = true
|
||||
}
|
||||
|
||||
set_cell_value :: proc(tbl: ^Table, row, col: int, value: any, loc := #caller_location) {
|
||||
cell := get_cell(tbl, row, col, loc)
|
||||
switch val in value {
|
||||
case nil:
|
||||
cell.text = ""
|
||||
case string:
|
||||
cell.text = string(val)
|
||||
case cstring:
|
||||
cell.text = string(val)
|
||||
case:
|
||||
cell.text = format(tbl, "%v", val)
|
||||
if cell.text == "" {
|
||||
fmt.eprintf("{} text/table: format() resulted in empty string (arena out of memory?)\n", loc)
|
||||
}
|
||||
}
|
||||
tbl.dirty = true
|
||||
}
|
||||
|
||||
set_cell_alignment :: proc(tbl: ^Table, row, col: int, alignment: Cell_Alignment, loc := #caller_location) {
|
||||
cell := get_cell(tbl, row, col, loc)
|
||||
cell.alignment = alignment
|
||||
tbl.dirty = true
|
||||
}
|
||||
|
||||
format :: proc(tbl: ^Table, _fmt: string, args: ..any, loc := #caller_location) -> string {
|
||||
context.allocator = tbl.format_allocator
|
||||
return fmt.aprintf(fmt = _fmt, args = args)
|
||||
}
|
||||
|
||||
header :: proc(tbl: ^Table, values: ..any, loc := #caller_location) {
|
||||
if (tbl.has_header_row && tbl.nr_rows != 1) || (!tbl.has_header_row && tbl.nr_rows != 0) {
|
||||
panic("Cannot add headers after rows have been added", loc)
|
||||
}
|
||||
|
||||
if tbl.nr_rows == 0 {
|
||||
tbl.nr_rows += 1
|
||||
tbl.has_header_row = true
|
||||
}
|
||||
|
||||
col := tbl.nr_cols
|
||||
tbl.nr_cols += len(values)
|
||||
for val in values {
|
||||
set_cell_value(tbl, header_row(tbl), col, val, loc)
|
||||
col += 1
|
||||
}
|
||||
|
||||
tbl.dirty = true
|
||||
}
|
||||
|
||||
row :: proc(tbl: ^Table, values: ..any, loc := #caller_location) {
|
||||
if tbl.nr_cols == 0 {
|
||||
if len(values) == 0 {
|
||||
panic("Cannot create row without values unless knowing amount of columns in advance")
|
||||
} else {
|
||||
tbl.nr_cols = len(values)
|
||||
}
|
||||
}
|
||||
tbl.nr_rows += 1
|
||||
for col in 0..<tbl.nr_cols {
|
||||
val := values[col] if col < len(values) else nil
|
||||
set_cell_value(tbl, last_row(tbl), col, val)
|
||||
}
|
||||
tbl.dirty = true
|
||||
}
|
||||
|
||||
last_row :: proc(tbl: ^Table) -> int {
|
||||
return tbl.nr_rows - 1
|
||||
}
|
||||
|
||||
header_row :: proc(tbl: ^Table) -> int {
|
||||
return 0 if tbl.has_header_row else -1
|
||||
}
|
||||
|
||||
first_row :: proc(tbl: ^Table) -> int {
|
||||
return header_row(tbl)+1 if tbl.has_header_row else 0
|
||||
}
|
||||
|
||||
build :: proc(tbl: ^Table) {
|
||||
tbl.dirty = false
|
||||
|
||||
resize(&tbl.colw, tbl.nr_cols)
|
||||
mem.zero_slice(tbl.colw[:])
|
||||
|
||||
for row in 0..<tbl.nr_rows {
|
||||
for col in 0..<tbl.nr_cols {
|
||||
cell := get_cell(tbl, row, col)
|
||||
if w := len(cell.text) + tbl.lpad + tbl.rpad; w > tbl.colw[col] {
|
||||
tbl.colw[col] = w
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
colw_sum := 0
|
||||
for v in tbl.colw {
|
||||
colw_sum += v
|
||||
}
|
||||
|
||||
tbl.tblw = max(colw_sum, len(tbl.caption) + tbl.lpad + tbl.rpad)
|
||||
|
||||
// Resize columns to match total width of table
|
||||
remain := tbl.tblw-colw_sum
|
||||
for col := 0; remain > 0; col = (col + 1) % tbl.nr_cols {
|
||||
tbl.colw[col] += 1
|
||||
remain -= 1
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
write_html_table :: proc(w: io.Writer, tbl: ^Table) {
|
||||
if tbl.dirty {
|
||||
build(tbl)
|
||||
}
|
||||
|
||||
io.write_string(w, "<table>\n")
|
||||
if tbl.caption != "" {
|
||||
io.write_string(w, "<caption>")
|
||||
io.write_string(w, tbl.caption)
|
||||
io.write_string(w, "</caption>\n")
|
||||
}
|
||||
|
||||
align_attribute :: proc(cell: ^Cell) -> string {
|
||||
switch cell.alignment {
|
||||
case .Left: return ` align="left"`
|
||||
case .Center: return ` align="center"`
|
||||
case .Right: return ` align="right"`
|
||||
}
|
||||
unreachable()
|
||||
}
|
||||
|
||||
if tbl.has_header_row {
|
||||
io.write_string(w, "<thead>\n")
|
||||
io.write_string(w, " <tr>\n")
|
||||
for col in 0..<tbl.nr_cols {
|
||||
cell := get_cell(tbl, header_row(tbl), col)
|
||||
io.write_string(w, " <th")
|
||||
io.write_string(w, align_attribute(cell))
|
||||
io.write_string(w, ">")
|
||||
io.write_string(w, cell.text)
|
||||
io.write_string(w, "</th>\n")
|
||||
}
|
||||
io.write_string(w, " </tr>\n")
|
||||
io.write_string(w, "</thead>\n")
|
||||
}
|
||||
|
||||
io.write_string(w, "<tbody>\n")
|
||||
for row in 0..<tbl.nr_rows {
|
||||
if tbl.has_header_row && row == header_row(tbl) {
|
||||
continue
|
||||
}
|
||||
io.write_string(w, " <tr>\n")
|
||||
for col in 0..<tbl.nr_cols {
|
||||
cell := get_cell(tbl, row, col)
|
||||
io.write_string(w, " <td")
|
||||
io.write_string(w, align_attribute(cell))
|
||||
io.write_string(w, ">")
|
||||
io.write_string(w, cell.text)
|
||||
io.write_string(w, "</td>\n")
|
||||
}
|
||||
io.write_string(w, " </tr>\n")
|
||||
}
|
||||
io.write_string(w, " </tbody>\n")
|
||||
|
||||
io.write_string(w, "</table>\n")
|
||||
}
|
||||
|
||||
write_ascii_table :: proc(w: io.Writer, tbl: ^Table) {
|
||||
if tbl.dirty {
|
||||
build(tbl)
|
||||
}
|
||||
|
||||
write_caption_separator :: proc(w: io.Writer, tbl: ^Table) {
|
||||
io.write_byte(w, '+')
|
||||
write_byte_repeat(w, tbl.tblw + tbl.nr_cols - 1, '-')
|
||||
io.write_byte(w, '+')
|
||||
io.write_byte(w, '\n')
|
||||
}
|
||||
|
||||
write_table_separator :: proc(w: io.Writer, tbl: ^Table) {
|
||||
for col in 0..<tbl.nr_cols {
|
||||
if col == 0 {
|
||||
io.write_byte(w, '+')
|
||||
}
|
||||
write_byte_repeat(w, tbl.colw[col], '-')
|
||||
io.write_byte(w, '+')
|
||||
}
|
||||
io.write_byte(w, '\n')
|
||||
}
|
||||
|
||||
if tbl.caption != "" {
|
||||
write_caption_separator(w, tbl)
|
||||
io.write_byte(w, '|')
|
||||
write_text_align(w, tbl.tblw - tbl.lpad - tbl.rpad + tbl.nr_cols - 1,
|
||||
tbl.lpad, tbl.rpad, tbl.caption, .Center)
|
||||
io.write_byte(w, '|')
|
||||
io.write_byte(w, '\n')
|
||||
}
|
||||
|
||||
write_table_separator(w, tbl)
|
||||
for row in 0..<tbl.nr_rows {
|
||||
for col in 0..<tbl.nr_cols {
|
||||
if col == 0 {
|
||||
io.write_byte(w, '|')
|
||||
}
|
||||
write_table_cell(w, tbl, row, col)
|
||||
io.write_byte(w, '|')
|
||||
}
|
||||
io.write_byte(w, '\n')
|
||||
if tbl.has_header_row && row == header_row(tbl) {
|
||||
write_table_separator(w, tbl)
|
||||
}
|
||||
}
|
||||
write_table_separator(w, tbl)
|
||||
}
|
||||
|
||||
// Renders table according to GitHub Flavored Markdown (GFM) specification
|
||||
write_markdown_table :: proc(w: io.Writer, tbl: ^Table) {
|
||||
// NOTE(oskar): Captions or colspans are not supported by GFM as far as I can tell.
|
||||
|
||||
if tbl.dirty {
|
||||
build(tbl)
|
||||
}
|
||||
|
||||
for row in 0..<tbl.nr_rows {
|
||||
for col in 0..<tbl.nr_cols {
|
||||
cell := get_cell(tbl, row, col)
|
||||
if col == 0 {
|
||||
io.write_byte(w, '|')
|
||||
}
|
||||
write_text_align(w, tbl.colw[col] - tbl.lpad - tbl.rpad, tbl.lpad, tbl.rpad, cell.text,
|
||||
.Center if tbl.has_header_row && row == header_row(tbl) else .Left)
|
||||
io.write_string(w, "|")
|
||||
}
|
||||
io.write_byte(w, '\n')
|
||||
|
||||
if tbl.has_header_row && row == header_row(tbl) {
|
||||
for col in 0..<tbl.nr_cols {
|
||||
cell := get_cell(tbl, row, col)
|
||||
if col == 0 {
|
||||
io.write_byte(w, '|')
|
||||
}
|
||||
switch cell.alignment {
|
||||
case .Left:
|
||||
io.write_byte(w, ':')
|
||||
write_byte_repeat(w, max(1, tbl.colw[col]-1), '-')
|
||||
case .Center:
|
||||
io.write_byte(w, ':')
|
||||
write_byte_repeat(w, max(1, tbl.colw[col]-2), '-')
|
||||
io.write_byte(w, ':')
|
||||
case .Right:
|
||||
write_byte_repeat(w, max(1, tbl.colw[col]-1), '-')
|
||||
io.write_byte(w, ':')
|
||||
}
|
||||
io.write_byte(w, '|')
|
||||
}
|
||||
io.write_byte(w, '\n')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
write_byte_repeat :: proc(w: io.Writer, n: int, b: byte) {
|
||||
for _ in 0..<n {
|
||||
io.write_byte(w, b)
|
||||
}
|
||||
}
|
||||
|
||||
write_table_cell :: proc(w: io.Writer, tbl: ^Table, row, col: int) {
|
||||
if tbl.dirty {
|
||||
build(tbl)
|
||||
}
|
||||
cell := get_cell(tbl, row, col)
|
||||
write_text_align(w, tbl.colw[col]-tbl.lpad-tbl.rpad, tbl.lpad, tbl.rpad, cell.text, cell.alignment)
|
||||
}
|
||||
|
||||
write_text_align :: proc(w: io.Writer, colw, lpad, rpad: int, text: string, alignment: Cell_Alignment) {
|
||||
write_byte_repeat(w, lpad, ' ')
|
||||
switch alignment {
|
||||
case .Left:
|
||||
io.write_string(w, text)
|
||||
write_byte_repeat(w, colw - len(text), ' ')
|
||||
case .Center:
|
||||
pad := colw - len(text)
|
||||
odd := pad & 1 != 0
|
||||
write_byte_repeat(w, pad/2, ' ')
|
||||
io.write_string(w, text)
|
||||
write_byte_repeat(w, pad/2 + 1 if odd else pad/2, ' ')
|
||||
case .Right:
|
||||
write_byte_repeat(w, colw - len(text), ' ')
|
||||
io.write_string(w, text)
|
||||
}
|
||||
write_byte_repeat(w, rpad, ' ')
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package text_table
|
||||
|
||||
import "core:io"
|
||||
import "core:os"
|
||||
import "core:strings"
|
||||
|
||||
stdio_writer :: proc() -> io.Writer {
|
||||
return io.to_writer(os.stream_from_handle(os.stdout))
|
||||
}
|
||||
|
||||
strings_builder_writer :: proc(b: ^strings.Builder) -> io.Writer {
|
||||
return strings.to_writer(b)
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
//+build js
|
||||
package thread
|
||||
|
||||
import "core:intrinsics"
|
||||
import "core:sync"
|
||||
import "core:mem"
|
||||
|
||||
Thread_State :: enum u8 {
|
||||
Started,
|
||||
Joined,
|
||||
Done,
|
||||
}
|
||||
|
||||
Thread_Os_Specific :: struct {
|
||||
flags: bit_set[Thread_State; u8],
|
||||
}
|
||||
|
||||
_thread_priority_map := [Thread_Priority]i32{
|
||||
.Normal = 0,
|
||||
.Low = -2,
|
||||
.High = +2,
|
||||
}
|
||||
|
||||
_create :: proc(procedure: Thread_Proc, priority := Thread_Priority.Normal) -> ^Thread {
|
||||
unimplemented("core:thread procedure not supported on js target")
|
||||
}
|
||||
|
||||
_start :: proc(t: ^Thread) {
|
||||
unimplemented("core:thread procedure not supported on js target")
|
||||
}
|
||||
|
||||
_is_done :: proc(t: ^Thread) -> bool {
|
||||
unimplemented("core:thread procedure not supported on js target")
|
||||
}
|
||||
|
||||
_join :: proc(t: ^Thread) {
|
||||
unimplemented("core:thread procedure not supported on js target")
|
||||
}
|
||||
|
||||
_join_multiple :: proc(threads: ..^Thread) {
|
||||
unimplemented("core:thread procedure not supported on js target")
|
||||
}
|
||||
|
||||
_destroy :: proc(thread: ^Thread) {
|
||||
unimplemented("core:thread procedure not supported on js target")
|
||||
}
|
||||
|
||||
_terminate :: proc(using thread : ^Thread, exit_code: int) {
|
||||
unimplemented("core:thread procedure not supported on js target")
|
||||
}
|
||||
|
||||
_yield :: proc() {
|
||||
unimplemented("core:thread procedure not supported on js target")
|
||||
}
|
||||
|
||||
@@ -56,6 +56,12 @@ when ODIN_ARCH == .amd64 {
|
||||
}
|
||||
}
|
||||
|
||||
when ODIN_OS != .Darwin && ODIN_OS != .Linux && ODIN_OS != .FreeBSD {
|
||||
_get_tsc_frequency :: proc "contextless" () -> (u64, bool) {
|
||||
return 0, false
|
||||
}
|
||||
}
|
||||
|
||||
has_invariant_tsc :: proc "contextless" () -> bool {
|
||||
when ODIN_ARCH == .amd64 {
|
||||
return x86_has_invariant_tsc()
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
//+private
|
||||
//+build openbsd
|
||||
package time
|
||||
|
||||
_get_tsc_frequency :: proc "contextless" () -> (u64, bool) {
|
||||
return 0, false
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
//+private
|
||||
//+build windows
|
||||
package time
|
||||
|
||||
_get_tsc_frequency :: proc "contextless" () -> (u64, bool) {
|
||||
return 0, false
|
||||
}
|
||||
@@ -49,6 +49,7 @@ import whirlpool "core:crypto/whirlpool"
|
||||
import x25519 "core:crypto/x25519"
|
||||
|
||||
import dynlib "core:dynlib"
|
||||
import net "core:net"
|
||||
|
||||
import base32 "core:encoding/base32"
|
||||
import base64 "core:encoding/base64"
|
||||
@@ -161,6 +162,7 @@ _ :: crypto_util
|
||||
_ :: whirlpool
|
||||
_ :: x25519
|
||||
_ :: dynlib
|
||||
_ :: net
|
||||
_ :: base32
|
||||
_ :: base64
|
||||
_ :: csv
|
||||
@@ -214,4 +216,4 @@ _ :: sysinfo
|
||||
_ :: unicode
|
||||
_ :: utf8
|
||||
_ :: utf8string
|
||||
_ :: utf16
|
||||
_ :: utf16
|
||||
|
||||
@@ -1,337 +0,0 @@
|
||||
import "core:fmt.odin";
|
||||
import "core:os.odin";
|
||||
import "core:mem.odin";
|
||||
// import "http_test.odin" as ht;
|
||||
// import "game.odin" as game;
|
||||
// import "punity.odin" as pn;
|
||||
|
||||
main :: proc() {
|
||||
struct_padding();
|
||||
bounds_checking();
|
||||
type_introspection();
|
||||
any_type();
|
||||
crazy_introspection();
|
||||
namespaces_and_files();
|
||||
miscellany();
|
||||
|
||||
/*
|
||||
ht.run();
|
||||
game.run();
|
||||
{
|
||||
init :: proc(c: ^pn.Core) {}
|
||||
step :: proc(c: ^pn.Core) {}
|
||||
|
||||
pn.run(init, step);
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
struct_padding :: proc() {
|
||||
{
|
||||
A :: struct {
|
||||
a: u8,
|
||||
b: u32,
|
||||
c: u16,
|
||||
}
|
||||
|
||||
B :: struct {
|
||||
a: [7]u8,
|
||||
b: [3]u16,
|
||||
c: u8,
|
||||
d: u16,
|
||||
}
|
||||
|
||||
fmt.println("size_of(A):", size_of(A));
|
||||
fmt.println("size_of(B):", size_of(B));
|
||||
|
||||
// n.b. http://cbloomrants.blogspot.co.uk/2012/07/07-23-12-structs-are-not-what-you-want.html
|
||||
}
|
||||
{
|
||||
A :: struct #ordered {
|
||||
a: u8,
|
||||
b: u32,
|
||||
c: u16,
|
||||
}
|
||||
|
||||
B :: struct #ordered {
|
||||
a: [7]u8,
|
||||
b: [3]u16,
|
||||
c: u8,
|
||||
d: u16,
|
||||
}
|
||||
|
||||
fmt.println("size_of(A):", size_of(A));
|
||||
fmt.println("size_of(B):", size_of(B));
|
||||
|
||||
// C-style structure layout
|
||||
}
|
||||
{
|
||||
A :: struct #packed {
|
||||
a: u8,
|
||||
b: u32,
|
||||
c: u16,
|
||||
}
|
||||
|
||||
B :: struct #packed {
|
||||
a: [7]u8,
|
||||
b: [3]u16,
|
||||
c: u8,
|
||||
d: u16,
|
||||
}
|
||||
|
||||
fmt.println("size_of(A):", size_of(A));
|
||||
fmt.println("size_of(B):", size_of(B));
|
||||
|
||||
// Useful for explicit layout
|
||||
}
|
||||
|
||||
// Member sorting by priority
|
||||
// Alignment desc.
|
||||
// Size desc.
|
||||
// source order asc.
|
||||
|
||||
/*
|
||||
A :: struct {
|
||||
a: u8
|
||||
b: u32
|
||||
c: u16
|
||||
}
|
||||
|
||||
B :: struct {
|
||||
a: [7]u8
|
||||
b: [3]u16
|
||||
c: u8
|
||||
d: u16
|
||||
}
|
||||
|
||||
Equivalent too
|
||||
|
||||
A :: struct #ordered {
|
||||
b: u32
|
||||
c: u16
|
||||
a: u8
|
||||
}
|
||||
|
||||
B :: struct #ordered {
|
||||
b: [3]u16
|
||||
d: u16
|
||||
a: [7]u8
|
||||
c: u8
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
bounds_checking :: proc() {
|
||||
x: [4]int;
|
||||
// x[-1] = 0; // Compile Time
|
||||
// x[4] = 0; // Compile Time
|
||||
|
||||
{
|
||||
a, b := -1, 4;
|
||||
// x[a] = 0; // Runtime Time
|
||||
// x[b] = 0; // Runtime Time
|
||||
}
|
||||
|
||||
// Works for arrays, strings, slices, and related procedures & operations
|
||||
|
||||
{
|
||||
base: [10]int;
|
||||
s := base[2..6];
|
||||
a, b := -1, 6;
|
||||
|
||||
#no_bounds_check {
|
||||
s[a] = 0;
|
||||
// #bounds_check s[b] = 0;
|
||||
}
|
||||
|
||||
#no_bounds_check
|
||||
if s[a] == 0 {
|
||||
// Do whatever
|
||||
}
|
||||
|
||||
// Bounds checking can be toggled explicit
|
||||
// on a per statement basis.
|
||||
// _any statement_
|
||||
}
|
||||
}
|
||||
|
||||
type_introspection :: proc() {
|
||||
{
|
||||
info: ^Type_Info;
|
||||
x: int;
|
||||
|
||||
info = type_info_of(int); // by type
|
||||
info = type_info_of(x); // by value
|
||||
// See: runtime.odin
|
||||
|
||||
match i in info.variant {
|
||||
case Type_Info_Integer:
|
||||
fmt.println("integer!");
|
||||
case Type_Info_Float:
|
||||
fmt.println("float!");
|
||||
case:
|
||||
fmt.println("potato!");
|
||||
}
|
||||
|
||||
// Unsafe cast
|
||||
integer_info := cast(^Type_Info_Integer)cast(rawptr)info;
|
||||
}
|
||||
|
||||
{
|
||||
Vector2 :: struct { x, y: f32 }
|
||||
Vector3 :: struct { x, y, z: f32 }
|
||||
|
||||
v1: Vector2;
|
||||
v2: Vector3;
|
||||
v3: Vector3;
|
||||
|
||||
t1 := type_info_of(v1);
|
||||
t2 := type_info_of(v2);
|
||||
t3 := type_info_of(v3);
|
||||
|
||||
fmt.println();
|
||||
fmt.print("Type of v1 is:\n\t", t1);
|
||||
|
||||
fmt.println();
|
||||
fmt.print("Type of v2 is:\n\t", t2);
|
||||
|
||||
fmt.println("\n");
|
||||
fmt.println("t1 == t2:", t1 == t2);
|
||||
fmt.println("t2 == t3:", t2 == t3);
|
||||
}
|
||||
}
|
||||
|
||||
any_type :: proc() {
|
||||
a: any;
|
||||
|
||||
x: int = 123;
|
||||
y: f64 = 6.28;
|
||||
z: string = "Yo-Yo Ma";
|
||||
// All types can be implicit cast to `any`
|
||||
a = x;
|
||||
a = y;
|
||||
a = z;
|
||||
a = a; // This the "identity" type, it doesn't get converted
|
||||
|
||||
a = 123; // Literals are copied onto the stack first
|
||||
|
||||
// any has two members
|
||||
// data - rawptr to the data
|
||||
// type_info - pointer to the type info
|
||||
|
||||
fmt.println(x, y, z);
|
||||
// See: fmt.odin
|
||||
// For variadic any procedures in action
|
||||
}
|
||||
|
||||
crazy_introspection :: proc() {
|
||||
{
|
||||
Fruit :: enum {
|
||||
APPLE,
|
||||
BANANA,
|
||||
GRAPE,
|
||||
MELON,
|
||||
PEACH,
|
||||
TOMATO,
|
||||
}
|
||||
|
||||
s: string;
|
||||
// s = enum_to_string(Fruit.PEACH);
|
||||
fmt.println(s);
|
||||
|
||||
f := Fruit.GRAPE;
|
||||
// s = enum_to_string(f);
|
||||
fmt.println(s);
|
||||
|
||||
fmt.println(f);
|
||||
// See: runtime.odin
|
||||
}
|
||||
|
||||
|
||||
{
|
||||
// NOTE(bill): This is not safe code and I would not recommend this at all
|
||||
// I'd recommend you use `match type` to get the subtype rather than
|
||||
// casting pointers
|
||||
|
||||
Fruit :: enum {
|
||||
APPLE,
|
||||
BANANA,
|
||||
GRAPE,
|
||||
MELON,
|
||||
PEACH,
|
||||
TOMATO,
|
||||
}
|
||||
|
||||
fruit_ti := type_info_of(Fruit);
|
||||
name := fruit_ti.variant.(Type_Info_Named).name;
|
||||
info, _ := type_info_base(fruit_ti).variant.(Type_Info_Enum);
|
||||
|
||||
fmt.printf("%s :: enum %T {\n", name, info.base);
|
||||
for _, i in info.values {
|
||||
fmt.printf("\t%s\t= %v,\n", info.names[i], info.values[i]);
|
||||
}
|
||||
fmt.printf("}\n");
|
||||
|
||||
// NOTE(bill): look at that type-safe printf!
|
||||
}
|
||||
|
||||
{
|
||||
Vector3 :: struct {x, y, z: f32}
|
||||
|
||||
a := Vector3{x = 1, y = 4, z = 9};
|
||||
fmt.println(a);
|
||||
b := Vector3{x = 9, y = 3, z = 1};
|
||||
fmt.println(b);
|
||||
|
||||
// NOTE(bill): See fmt.odin
|
||||
}
|
||||
|
||||
// n.b. This pretty much "solves" serialization (to strings)
|
||||
}
|
||||
|
||||
// #import "test.odin"
|
||||
|
||||
namespaces_and_files :: proc() {
|
||||
|
||||
// test.thing()
|
||||
// test.format.println()
|
||||
// test.println()
|
||||
/*
|
||||
// Non-exporting import
|
||||
#import "file.odin"
|
||||
#import "file.odin" as file
|
||||
#import "file.odin" as .
|
||||
#import "file.odin" as _
|
||||
|
||||
// Exporting import
|
||||
#include "file.odin"
|
||||
*/
|
||||
|
||||
// Talk about scope rules and diagram
|
||||
}
|
||||
|
||||
miscellany :: proc() {
|
||||
/*
|
||||
win32 `__imp__` prefix
|
||||
#dll_import
|
||||
#dll_export
|
||||
|
||||
Change exported name/symbol for linking
|
||||
#link_name
|
||||
|
||||
Custom calling conventions
|
||||
#stdcall
|
||||
#fastcall
|
||||
|
||||
Runtime stuff
|
||||
#shared_global_scope
|
||||
*/
|
||||
|
||||
// assert(false)
|
||||
// #assert(false)
|
||||
// panic("Panic message goes here")
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,879 +0,0 @@
|
||||
// Demo 002
|
||||
export "core:fmt.odin";
|
||||
export "core:math.odin";
|
||||
export "core:mem.odin";
|
||||
// export "game.odin"
|
||||
|
||||
#thread_local tls_int: int;
|
||||
|
||||
main :: proc() {
|
||||
// Forenotes
|
||||
|
||||
// Semicolons are now optional
|
||||
// Rule for when a semicolon is expected after a statement
|
||||
// - If the next token is not on the same line
|
||||
// - if the next token is a closing brace }
|
||||
// - Otherwise, a semicolon is needed
|
||||
//
|
||||
// Expections:
|
||||
// for, if, match
|
||||
// if x := thing(); x < 123 {}
|
||||
// for i := 0; i < 123; i++ {}
|
||||
|
||||
// Q: Should I use the new rule or go back to the old one without optional semicolons?
|
||||
|
||||
|
||||
// #thread_local - see runtime.odin and above at `tls_int`
|
||||
// #foreign_system_library - see win32.odin
|
||||
|
||||
// struct_compound_literals();
|
||||
// enumerations();
|
||||
// variadic_procedures();
|
||||
// new_builtins();
|
||||
// match_statement();
|
||||
// namespacing();
|
||||
// subtyping();
|
||||
// tagged_unions();
|
||||
}
|
||||
|
||||
struct_compound_literals :: proc() {
|
||||
Thing :: struct {
|
||||
id: int,
|
||||
x: f32,
|
||||
name: string,
|
||||
};
|
||||
{
|
||||
t1: Thing;
|
||||
t1.id = 1;
|
||||
|
||||
t3 := Thing{};
|
||||
t4 := Thing{1, 2, "Fred"};
|
||||
// t5 := Thing{1, 2};
|
||||
|
||||
t6 := Thing{
|
||||
name = "Tom",
|
||||
x = 23,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
enumerations :: proc() {
|
||||
{
|
||||
Fruit :: enum {
|
||||
APPLE, // 0
|
||||
BANANA, // 1
|
||||
PEAR, // 2
|
||||
};
|
||||
|
||||
f := Fruit.APPLE;
|
||||
// g12: int = Fruit.BANANA
|
||||
g: int = cast(int)Fruit.BANANA;
|
||||
// However, you can use enums are index values as _any_ integer allowed
|
||||
}
|
||||
{
|
||||
Fruit1 :: enum int {
|
||||
APPLE,
|
||||
BANANA,
|
||||
PEAR,
|
||||
}
|
||||
|
||||
Fruit2 :: enum u8 {
|
||||
APPLE,
|
||||
BANANA,
|
||||
PEAR,
|
||||
}
|
||||
|
||||
Fruit3 :: enum u8 {
|
||||
APPLE = 1,
|
||||
BANANA, // 2
|
||||
PEAR = 5,
|
||||
TOMATO, // 6
|
||||
}
|
||||
}
|
||||
|
||||
// Q: remove the need for `type` if it's a record (struct/enum/raw_union/union)?
|
||||
}
|
||||
|
||||
variadic_procedures :: proc() {
|
||||
print_ints :: proc(args: ..int) {
|
||||
for arg, i in args {
|
||||
if i > 0 do print(", ");
|
||||
print(arg);
|
||||
}
|
||||
}
|
||||
|
||||
print_ints(); // nl()
|
||||
print_ints(1); nl();
|
||||
print_ints(1, 2, 3); nl();
|
||||
|
||||
print_prefix_f32s :: proc(prefix: string, args: ..f32) {
|
||||
print(prefix);
|
||||
print(": ");
|
||||
for arg, i in args {
|
||||
if i > 0 do print(", ");
|
||||
print(arg);
|
||||
}
|
||||
}
|
||||
|
||||
print_prefix_f32s("a"); nl();
|
||||
print_prefix_f32s("b", 1); nl();
|
||||
print_prefix_f32s("c", 1, 2, 3); nl();
|
||||
|
||||
// Internally, the variadic procedures get allocated to an array on the stack,
|
||||
// and this array is passed a slice
|
||||
|
||||
// This is first step for a `print` procedure but I do not have an `any` type
|
||||
// yet as this requires a few other things first - i.e. introspection
|
||||
|
||||
// NOTE(bill): I haven't yet added the feature of expanding a slice or array into
|
||||
// a variadic a parameter but it's pretty trivial to add
|
||||
}
|
||||
|
||||
new_builtins :: proc() {
|
||||
{
|
||||
a := new(int);
|
||||
b := make([]int, 12);
|
||||
c := make([]int, 12, 16);
|
||||
|
||||
defer free(a);
|
||||
defer free(b);
|
||||
defer free(c);
|
||||
|
||||
// NOTE(bill): These use the current context's allocator not the default allocator
|
||||
// see runtime.odin
|
||||
|
||||
// Q: Should this be `free` rather than `free` and should I overload it for slices too?
|
||||
|
||||
push_allocator default_allocator() {
|
||||
a := new(int);
|
||||
defer free(a);
|
||||
|
||||
// Do whatever
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
a: int = 123;
|
||||
b: type_of(a) = 321;
|
||||
|
||||
// NOTE(bill): This matches the current naming scheme
|
||||
// size_of
|
||||
// align_of
|
||||
// offset_of
|
||||
//
|
||||
// size_of_val
|
||||
// align_of_val
|
||||
// offset_of_val
|
||||
// type_of_val
|
||||
}
|
||||
|
||||
{
|
||||
// Compile time assert
|
||||
COND :: true;
|
||||
#assert(COND);
|
||||
// #assert(!COND)
|
||||
|
||||
// Runtime assert
|
||||
x := true;
|
||||
assert(x);
|
||||
// assert(!x);
|
||||
}
|
||||
|
||||
{
|
||||
x: ^u32 = nil;
|
||||
y := x+100;
|
||||
z := y-x;
|
||||
w := slice_ptr(x, 12);
|
||||
t := slice_ptr(x, 12, 16);
|
||||
|
||||
// NOTE(bill): These are here because I've removed:
|
||||
// pointer arithmetic
|
||||
// pointer indexing
|
||||
// pointer slicing
|
||||
|
||||
// Reason
|
||||
|
||||
a: [16]int;
|
||||
a[1] = 1;
|
||||
b := &a;
|
||||
// Auto pointer deref
|
||||
// consistent with record members
|
||||
assert(b[1] == 1);
|
||||
|
||||
// Q: Should I add them back in at the cost of inconsitency?
|
||||
}
|
||||
|
||||
{
|
||||
a, b := -1, 2;
|
||||
print(min(a, b)); nl();
|
||||
print(max(a, b)); nl();
|
||||
print(abs(a)); nl();
|
||||
|
||||
// These work at compile time too
|
||||
A :: -1;
|
||||
B :: 2;
|
||||
C :: min(A, B);
|
||||
D :: max(A, B);
|
||||
E :: abs(A);
|
||||
|
||||
print(C); nl();
|
||||
print(D); nl();
|
||||
print(E); nl();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
match_statement :: proc() {
|
||||
// NOTE(bill): `match` statements are similar to `switch` statements
|
||||
// in other languages but there are few differences
|
||||
|
||||
{
|
||||
match x := 5; x {
|
||||
case 1: // cases must be constant expression
|
||||
print("1!\n");
|
||||
// break by default
|
||||
|
||||
case 2:
|
||||
s := "2!\n"; // Each case has its own scope
|
||||
print(s);
|
||||
break; // explicit break
|
||||
|
||||
case 3, 4: // multiple cases
|
||||
print("3 or 4!\n");
|
||||
|
||||
case 5:
|
||||
print("5!\n");
|
||||
fallthrough; // explicit fallthrough
|
||||
|
||||
case:
|
||||
print("default!\n");
|
||||
}
|
||||
|
||||
|
||||
|
||||
match x := 1.5; x {
|
||||
case 1.5:
|
||||
print("1.5!\n");
|
||||
// break by default
|
||||
case TAU:
|
||||
print("τ!\n");
|
||||
case:
|
||||
print("default!\n");
|
||||
}
|
||||
|
||||
|
||||
|
||||
match x := "Hello"; x {
|
||||
case "Hello":
|
||||
print("greeting\n");
|
||||
// break by default
|
||||
case "Goodbye":
|
||||
print("farewell\n");
|
||||
case:
|
||||
print("???\n");
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
a := 53;
|
||||
match {
|
||||
case a == 1:
|
||||
print("one\n");
|
||||
case a == 2:
|
||||
print("a couple\n");
|
||||
case a < 7, a == 7:
|
||||
print("a few\n");
|
||||
case a < 12: // intentional bug
|
||||
print("several\n");
|
||||
case a >= 12 && a < 100:
|
||||
print("dozens\n");
|
||||
case a >= 100 && a < 1000:
|
||||
print("hundreds\n");
|
||||
case:
|
||||
print("a fuck ton\n");
|
||||
}
|
||||
|
||||
// Identical to this
|
||||
|
||||
b := 53;
|
||||
if b == 1 {
|
||||
print("one\n");
|
||||
} else if b == 2 {
|
||||
print("a couple\n");
|
||||
} else if b < 7 || b == 7 {
|
||||
print("a few\n");
|
||||
} else if b < 12 { // intentional bug
|
||||
print("several\n");
|
||||
} else if b >= 12 && b < 100 {
|
||||
print("dozens\n");
|
||||
} else if b >= 100 && b < 1000 {
|
||||
print("hundreds\n");
|
||||
} else {
|
||||
print("a fuck ton\n");
|
||||
}
|
||||
|
||||
// However, match statements allow for `break` and `fallthrough` unlike
|
||||
// an if statement
|
||||
}
|
||||
}
|
||||
|
||||
Vector3 :: struct {x, y, z: f32}
|
||||
|
||||
print_floats :: proc(args: ..f32) {
|
||||
for arg, i in args {
|
||||
if i > 0 do print(", ");
|
||||
print(arg);
|
||||
}
|
||||
println();
|
||||
}
|
||||
|
||||
namespacing :: proc() {
|
||||
{
|
||||
Thing :: #type struct {
|
||||
x: f32,
|
||||
name: string,
|
||||
};
|
||||
|
||||
a: Thing;
|
||||
a.x = 3;
|
||||
{
|
||||
Thing :: #type struct {
|
||||
y: int,
|
||||
test: bool,
|
||||
};
|
||||
|
||||
b: Thing; // Uses this scope's Thing
|
||||
b.test = true;
|
||||
}
|
||||
}
|
||||
/*
|
||||
{
|
||||
Entity :: struct {
|
||||
Guid :: int
|
||||
Nested :: struct {
|
||||
MyInt :: int
|
||||
i: int
|
||||
}
|
||||
|
||||
CONSTANT :: 123
|
||||
|
||||
|
||||
guid: Guid
|
||||
name: string
|
||||
pos: Vector3
|
||||
vel: Vector3
|
||||
nested: Nested
|
||||
}
|
||||
|
||||
guid: Entity.Guid = Entity.CONSTANT
|
||||
i: Entity.Nested.MyInt
|
||||
|
||||
|
||||
|
||||
{
|
||||
using Entity
|
||||
guid: Guid = CONSTANT
|
||||
using Nested
|
||||
i: MyInt
|
||||
}
|
||||
|
||||
|
||||
{
|
||||
using Entity.Nested
|
||||
guid: Entity.Guid = Entity.CONSTANT
|
||||
i: MyInt
|
||||
}
|
||||
|
||||
|
||||
{
|
||||
e: Entity
|
||||
using e
|
||||
guid = 27832
|
||||
name = "Bob"
|
||||
|
||||
print(e.guid as int); nl()
|
||||
print(e.name); nl()
|
||||
}
|
||||
|
||||
{
|
||||
using e: Entity
|
||||
guid = 78456
|
||||
name = "Thing"
|
||||
|
||||
print(e.guid as int); nl()
|
||||
print(e.name); nl()
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
Entity :: struct {
|
||||
Guid :: int
|
||||
Nested :: struct {
|
||||
MyInt :: int
|
||||
i: int
|
||||
}
|
||||
|
||||
CONSTANT :: 123
|
||||
|
||||
|
||||
guid: Guid
|
||||
name: string
|
||||
using pos: Vector3
|
||||
vel: Vector3
|
||||
using nested: ^Nested
|
||||
}
|
||||
|
||||
e := Entity{nested = new(Entity.Nested)}
|
||||
e.x = 123
|
||||
e.i = Entity.CONSTANT
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
{
|
||||
Entity :: struct {
|
||||
position: Vector3
|
||||
}
|
||||
|
||||
print_pos_1 :: proc(entity: ^Entity) {
|
||||
print("print_pos_1: ");
|
||||
print_floats(entity.position.x, entity.position.y, entity.position.z);
|
||||
}
|
||||
|
||||
print_pos_2 :: proc(entity: ^Entity) {
|
||||
using entity;
|
||||
print("print_pos_2: ");
|
||||
print_floats(position.x, position.y, position.z);
|
||||
}
|
||||
|
||||
print_pos_3 :: proc(using entity: ^Entity) {
|
||||
print("print_pos_3: ");
|
||||
print_floats(position.x, position.y, position.z);
|
||||
}
|
||||
|
||||
print_pos_4 :: proc(using entity: ^Entity) {
|
||||
using position;
|
||||
print("print_pos_4: ");
|
||||
print_floats(x, y, z);
|
||||
}
|
||||
|
||||
e := Entity{position = Vector3{1, 2, 3}};
|
||||
print_pos_1(&e);
|
||||
print_pos_2(&e);
|
||||
print_pos_3(&e);
|
||||
print_pos_4(&e);
|
||||
|
||||
// This is similar to C++'s `this` pointer that is implicit and only available in methods
|
||||
}
|
||||
}
|
||||
|
||||
subtyping :: proc() {
|
||||
{
|
||||
// C way for subtyping/subclassing
|
||||
|
||||
Entity :: struct {
|
||||
position: Vector3,
|
||||
}
|
||||
|
||||
Frog :: struct {
|
||||
entity: Entity,
|
||||
jump_height: f32,
|
||||
}
|
||||
|
||||
f: Frog;
|
||||
f.entity.position = Vector3{1, 2, 3};
|
||||
|
||||
using f.entity;
|
||||
position = Vector3{1, 2, 3};
|
||||
|
||||
}
|
||||
|
||||
{
|
||||
// C++ way for subtyping/subclassing
|
||||
|
||||
Entity :: struct {
|
||||
position: Vector3
|
||||
}
|
||||
|
||||
Frog :: struct {
|
||||
using entity: Entity,
|
||||
jump_height: f32,
|
||||
}
|
||||
|
||||
f: Frog;
|
||||
f.position = Vector3{1, 2, 3};
|
||||
|
||||
|
||||
print_pos :: proc(using entity: Entity) {
|
||||
print("print_pos: ");
|
||||
print_floats(position.x, position.y, position.z);
|
||||
}
|
||||
|
||||
print_pos(f.entity);
|
||||
// print_pos(f);
|
||||
|
||||
// Subtype Polymorphism
|
||||
}
|
||||
|
||||
{
|
||||
// More than C++ way for subtyping/subclassing
|
||||
|
||||
Entity :: struct {
|
||||
position: Vector3,
|
||||
}
|
||||
|
||||
Frog :: struct {
|
||||
jump_height: f32,
|
||||
using entity: ^Entity, // Doesn't have to be first member!
|
||||
}
|
||||
|
||||
f: Frog;
|
||||
f.entity = new(Entity);
|
||||
f.position = Vector3{1, 2, 3};
|
||||
|
||||
|
||||
print_pos :: proc(using entity: ^Entity) {
|
||||
print("print_pos: ");
|
||||
print_floats(position.x, position.y, position.z);
|
||||
}
|
||||
|
||||
print_pos(f.entity);
|
||||
// print_pos(^f);
|
||||
// print_pos(f);
|
||||
}
|
||||
|
||||
{
|
||||
// More efficient subtyping
|
||||
|
||||
Entity :: struct {
|
||||
position: Vector3,
|
||||
}
|
||||
|
||||
Frog :: struct {
|
||||
jump_height: f32,
|
||||
using entity: ^Entity,
|
||||
}
|
||||
|
||||
MAX_ENTITES :: 64;
|
||||
entities: [MAX_ENTITES]Entity;
|
||||
entity_count := 0;
|
||||
|
||||
next_entity :: proc(entities: []Entity, entity_count: ^int) -> ^Entity {
|
||||
e := &entities[entity_count^];
|
||||
entity_count^ += 1;
|
||||
return e;
|
||||
}
|
||||
|
||||
f: Frog;
|
||||
f.entity = next_entity(entities[..], &entity_count);
|
||||
f.position = Vector3{3, 4, 6};
|
||||
|
||||
using f.position;
|
||||
print_floats(x, y, z);
|
||||
}
|
||||
|
||||
/*{
|
||||
// Down casting
|
||||
|
||||
Entity :: struct {
|
||||
position: Vector3,
|
||||
}
|
||||
|
||||
Frog :: struct {
|
||||
jump_height: f32,
|
||||
using entity: Entity,
|
||||
}
|
||||
|
||||
f: Frog;
|
||||
f.jump_height = 564;
|
||||
e := ^f.entity;
|
||||
|
||||
frog := down_cast(^Frog)e;
|
||||
print("down_cast: ");
|
||||
print(frog.jump_height); nl();
|
||||
|
||||
// NOTE(bill): `down_cast` is unsafe and there are not check are compile time or run time
|
||||
// Q: Should I completely remove `down_cast` as I added it in about 30 minutes
|
||||
}*/
|
||||
|
||||
{
|
||||
// Multiple "inheritance"/subclassing
|
||||
|
||||
Entity :: struct {
|
||||
position: Vector3,
|
||||
}
|
||||
Climber :: struct {
|
||||
speed: f32,
|
||||
}
|
||||
|
||||
Frog :: struct {
|
||||
using entity: Entity,
|
||||
using climber: Climber,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tagged_unions :: proc() {
|
||||
{
|
||||
Entity_Kind :: enum {
|
||||
INVALID,
|
||||
FROG,
|
||||
GIRAFFE,
|
||||
HELICOPTER,
|
||||
}
|
||||
|
||||
Entity :: struct {
|
||||
kind: Entity_Kind
|
||||
using data: struct #raw_union {
|
||||
frog: struct {
|
||||
jump_height: f32,
|
||||
colour: u32,
|
||||
},
|
||||
giraffe: struct {
|
||||
neck_length: f32,
|
||||
spot_count: int,
|
||||
},
|
||||
helicopter: struct {
|
||||
blade_count: int,
|
||||
weight: f32,
|
||||
pilot_name: string,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
e: Entity;
|
||||
e.kind = Entity_Kind.FROG;
|
||||
e.frog.jump_height = 12;
|
||||
|
||||
f: type_of(e.frog);
|
||||
|
||||
// But this is very unsafe and extremely cumbersome to write
|
||||
// In C++, I use macros to alleviate this but it's not a solution
|
||||
}
|
||||
|
||||
{
|
||||
Frog :: struct {
|
||||
jump_height: f32,
|
||||
colour: u32,
|
||||
}
|
||||
Giraffe :: struct {
|
||||
neck_length: f32,
|
||||
spot_count: int,
|
||||
}
|
||||
Helicopter :: struct {
|
||||
blade_count: int,
|
||||
weight: f32,
|
||||
pilot_name: string,
|
||||
}
|
||||
Entity :: union {Frog, Giraffe, Helicopter};
|
||||
|
||||
f1: Frog = Frog{12, 0xff9900};
|
||||
f2: Entity = Frog{12, 0xff9900}; // Implicit cast
|
||||
f3 := cast(Entity)Frog{12, 0xff9900}; // Explicit cast
|
||||
|
||||
// f3.Frog.jump_height = 12 // There are "members" of a union
|
||||
|
||||
|
||||
|
||||
e, f, g, h: Entity;
|
||||
f = Frog{12, 0xff9900};
|
||||
g = Giraffe{2.1, 23};
|
||||
h = Helicopter{4, 1000, "Frank"};
|
||||
|
||||
|
||||
|
||||
|
||||
// Requires a pointer to the union
|
||||
// `x` will be a pointer to type of the case
|
||||
|
||||
match x in &f {
|
||||
case Frog:
|
||||
print("Frog!\n");
|
||||
print(x.jump_height); nl();
|
||||
// x.jump_height = 3;
|
||||
print(x.jump_height); nl();
|
||||
case Giraffe:
|
||||
print("Giraffe!\n");
|
||||
case Helicopter:
|
||||
print("ROFLCOPTER!\n");
|
||||
case:
|
||||
print("invalid entity\n");
|
||||
}
|
||||
|
||||
|
||||
// Q: Allow for a non pointer version with takes a copy instead?
|
||||
// Or it takes the pointer the data and not a copy
|
||||
|
||||
|
||||
// fp := cast(^Frog)^f; // Unsafe
|
||||
// print(fp.jump_height); nl();
|
||||
|
||||
|
||||
// Internals of a tagged union
|
||||
/*
|
||||
struct {
|
||||
data: [size_of_biggest_tag]u8,
|
||||
tag_index: int,
|
||||
}
|
||||
*/
|
||||
// This is to allow for pointer casting if needed
|
||||
|
||||
|
||||
// Advantage over subtyping version
|
||||
MAX_ENTITES :: 64;
|
||||
entities: [MAX_ENTITES]Entity;
|
||||
|
||||
entities[0] = Frog{};
|
||||
entities[1] = Helicopter{};
|
||||
// etc.
|
||||
}
|
||||
|
||||
|
||||
{
|
||||
// Transliteration of code from this actual compiler
|
||||
// Some stuff is missing
|
||||
Type :: struct {};
|
||||
Scope :: struct {};
|
||||
Token :: struct {};
|
||||
AstNode :: struct {};
|
||||
ExactValue :: struct {};
|
||||
|
||||
Entity_Kind :: enum {
|
||||
Invalid,
|
||||
Constant,
|
||||
Variable,
|
||||
Using_Variable,
|
||||
TypeName,
|
||||
Procedure,
|
||||
Builtin,
|
||||
Count,
|
||||
}
|
||||
|
||||
Guid :: i64;
|
||||
Entity :: struct {
|
||||
|
||||
kind: Entity_Kind,
|
||||
guid: Guid,
|
||||
|
||||
scope: ^Scope,
|
||||
token: Token,
|
||||
type_: ^Type,
|
||||
|
||||
using data: struct #raw_union {
|
||||
Constant: struct {
|
||||
value: ExactValue,
|
||||
},
|
||||
Variable: struct {
|
||||
visited: bool, // Cycle detection
|
||||
used: bool, // Variable is used
|
||||
is_field: bool, // Is struct field
|
||||
anonymous: bool, // Variable is an anonymous
|
||||
},
|
||||
Using_Variable: struct {
|
||||
},
|
||||
TypeName: struct {
|
||||
},
|
||||
Procedure: struct {
|
||||
used: bool,
|
||||
},
|
||||
Builtin: struct {
|
||||
id: int,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Plus all the constructing procedures that go along with them!!!!
|
||||
// It's a nightmare
|
||||
}
|
||||
|
||||
{
|
||||
Type :: struct {};
|
||||
Scope :: struct {};
|
||||
Token :: struct {};
|
||||
AstNode :: struct {};
|
||||
ExactValue :: struct {};
|
||||
|
||||
|
||||
Guid :: i64;
|
||||
Entity_Base :: struct {
|
||||
}
|
||||
|
||||
|
||||
Constant :: struct {
|
||||
value: ExactValue,
|
||||
}
|
||||
Variable :: struct {
|
||||
visited: bool, // Cycle detection
|
||||
used: bool, // Variable is used
|
||||
is_field: bool, // Is struct field
|
||||
anonymous: bool, // Variable is an anonymous
|
||||
}
|
||||
Using_Variable :: struct {
|
||||
}
|
||||
TypeName :: struct {
|
||||
}
|
||||
Procedure :: struct {
|
||||
used: bool,
|
||||
}
|
||||
Builtin :: struct {
|
||||
id: int,
|
||||
}
|
||||
|
||||
Entity :: struct {
|
||||
guid: Guid,
|
||||
|
||||
scope: ^Scope,
|
||||
token: Token,
|
||||
type_: ^Type,
|
||||
|
||||
variant: union {Constant, Variable, Using_Variable, TypeName, Procedure, Builtin},
|
||||
}
|
||||
|
||||
e := Entity{
|
||||
variant = Variable{
|
||||
used = true,
|
||||
anonymous = false,
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
|
||||
// Q: Allow a "base" type to be added to a union?
|
||||
// Or even `using` on union to get the same properties?
|
||||
}
|
||||
|
||||
|
||||
{
|
||||
// `Raw` unions still have uses, especially for mathematic types
|
||||
|
||||
Vector2 :: struct #raw_union {
|
||||
using xy_: struct { x, y: f32 },
|
||||
e: [2]f32,
|
||||
v: [vector 2]f32,
|
||||
}
|
||||
|
||||
Vector3 :: struct #raw_union {
|
||||
using xyz_: struct { x, y, z: f32 },
|
||||
xy: Vector2,
|
||||
e: [3]f32,
|
||||
v: [vector 3]f32,
|
||||
}
|
||||
|
||||
v2: Vector2;
|
||||
v2.x = 1;
|
||||
v2.e[0] = 1;
|
||||
v2.v[0] = 1;
|
||||
|
||||
v3: Vector3;
|
||||
v3.x = 1;
|
||||
v3.e[0] = 1;
|
||||
v3.v[0] = 1;
|
||||
v3.xy.x = 1;
|
||||
}
|
||||
}
|
||||
|
||||
nl :: proc() { println(); }
|
||||
@@ -1,66 +0,0 @@
|
||||
import "core:fmt.odin";
|
||||
import "core:utf8.odin";
|
||||
import "core:hash.odin";
|
||||
import "core:mem.odin";
|
||||
|
||||
main :: proc() {
|
||||
{ // New Standard Library stuff
|
||||
s := "Hello";
|
||||
fmt.println(s,
|
||||
utf8.valid_string(s),
|
||||
hash.murmur64(cast([]u8)s));
|
||||
|
||||
// utf8.odin
|
||||
// hash.odin
|
||||
// - crc, fnv, fnva, murmur
|
||||
// mem.odin
|
||||
// - Custom allocators
|
||||
// - Helpers
|
||||
}
|
||||
|
||||
{
|
||||
arena: mem.Arena;
|
||||
mem.init_arena_from_context(&arena, mem.megabytes(16)); // Uses default allocator
|
||||
defer mem.destroy_arena(&arena);
|
||||
|
||||
push_allocator mem.arena_allocator(&arena) {
|
||||
x := new(int);
|
||||
x^ = 1337;
|
||||
|
||||
fmt.println(x^);
|
||||
}
|
||||
|
||||
/*
|
||||
push_allocator x {
|
||||
..
|
||||
}
|
||||
|
||||
is equivalent to:
|
||||
|
||||
{
|
||||
prev_allocator := __context.allocator
|
||||
__context.allocator = x
|
||||
defer __context.allocator = prev_allocator
|
||||
|
||||
..
|
||||
}
|
||||
*/
|
||||
|
||||
// You can also "push" a context
|
||||
|
||||
c := context; // Create copy of the allocator
|
||||
c.allocator = mem.arena_allocator(&arena);
|
||||
|
||||
push_context c {
|
||||
x := new(int);
|
||||
x^ = 365;
|
||||
|
||||
fmt.println(x^);
|
||||
}
|
||||
}
|
||||
|
||||
// Backend improvements
|
||||
// - Minimal dependency building (only build what is needed)
|
||||
// - Numerous bugs fixed
|
||||
// - Mild parsing recovery after bad syntax error
|
||||
}
|
||||
@@ -1,283 +0,0 @@
|
||||
import "core:fmt.odin";
|
||||
import "core:utf8.odin";
|
||||
// import "core:atomic.odin";
|
||||
// import "core:hash.odin";
|
||||
// import "core:math.odin";
|
||||
// import "core:mem.odin";
|
||||
// import "core:opengl.odin";
|
||||
// import "core:os.odin";
|
||||
// import "core:sync.odin";
|
||||
// import win32 "core:sys/windows.odin";
|
||||
|
||||
main :: proc() {
|
||||
// syntax();
|
||||
procedure_overloading();
|
||||
}
|
||||
|
||||
syntax :: proc() {
|
||||
// Cyclic type checking
|
||||
// Uncomment to see the error
|
||||
// A :: struct {b: B};
|
||||
// B :: struct {a: A};
|
||||
|
||||
x: int;
|
||||
y := cast(f32)x;
|
||||
z := transmute(u32)y;
|
||||
// down_cast, union_cast are similar too
|
||||
|
||||
|
||||
|
||||
// Basic directives
|
||||
fmt.printf("Basic directives = %s(%d): %s\n", #file, #line, #procedure);
|
||||
// NOTE: new and improved `printf`
|
||||
// TODO: It does need accurate float printing
|
||||
|
||||
|
||||
|
||||
// record fields use the same syntax a procedure signatures
|
||||
Thing1 :: struct {
|
||||
x: f32,
|
||||
y: int,
|
||||
z: ^[]int,
|
||||
};
|
||||
Thing2 :: struct {x: f32, y: int, z: ^[]int};
|
||||
|
||||
// Slice interals are now just a `ptr+len+cap`
|
||||
slice: []int; #assert(size_of(slice) == 3*size_of(int));
|
||||
|
||||
// Helper type - Help the reader understand what it is quicker
|
||||
My_Int :: #type int;
|
||||
My_Proc :: #type proc(int) -> f32;
|
||||
|
||||
|
||||
// All declarations with : are either variable or constant
|
||||
// To make these declarations syntactically consistent
|
||||
v_variable := 123;
|
||||
c_constant :: 123;
|
||||
c_type1 :: int;
|
||||
c_type2 :: []int;
|
||||
c_proc :: proc() { /* code here */ };
|
||||
|
||||
|
||||
/*
|
||||
x += 1;
|
||||
x -= 1;
|
||||
// ++ and -- have been removed
|
||||
// x++;
|
||||
// x--;
|
||||
// Question: Should they be added again?
|
||||
// They were removed as they are redundant and statements, not expressions
|
||||
// like in C/C++
|
||||
*/
|
||||
|
||||
// You can now build files as a `.dll`
|
||||
// `odin build_dll demo.odin`
|
||||
|
||||
|
||||
// New vector syntax
|
||||
u, v: [vector 3]f32;
|
||||
v[0] = 123;
|
||||
v.x = 123; // valid for all vectors with count 1 to 4
|
||||
|
||||
// Next part
|
||||
prefixes();
|
||||
}
|
||||
|
||||
|
||||
Prefix_Type :: struct {x: int, y: f32, z: rawptr};
|
||||
|
||||
#thread_local my_tls: Prefix_Type;
|
||||
|
||||
prefixes :: proc() {
|
||||
using var: Prefix_Type;
|
||||
var.x = 123;
|
||||
x = 123;
|
||||
|
||||
|
||||
foo :: proc(using pt: Prefix_Type) {
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Same as C99's `restrict`
|
||||
bar :: proc(#no_alias a, b: ^int) {
|
||||
// Assumes a never equals b so it can perform optimizations with that fact
|
||||
}
|
||||
|
||||
|
||||
when_statements();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
when_statements :: proc() {
|
||||
X :: 123 + 12;
|
||||
Y :: X/5;
|
||||
COND :: Y > 0;
|
||||
|
||||
when COND {
|
||||
fmt.println("Y > 0");
|
||||
} else {
|
||||
fmt.println("Y <= 0");
|
||||
}
|
||||
|
||||
|
||||
when false {
|
||||
this_code_does_not_exist(123, 321);
|
||||
but_its_syntax_is_valid();
|
||||
x :: ^^^^int;
|
||||
}
|
||||
|
||||
foreign_procedures();
|
||||
}
|
||||
|
||||
when ODIN_OS == "windows" {
|
||||
foreign_system_library win32_user "user32.lib";
|
||||
}
|
||||
// NOTE: This is done on purpose for two reasons:
|
||||
// * Makes it clear where the platform specific stuff is
|
||||
// * Removes the need to solve the travelling salesman problem when importing files :P
|
||||
|
||||
foreign_procedures :: proc() {
|
||||
foreign win32_user {
|
||||
ShowWindow :: proc(hwnd: rawptr, cmd_show: i32) -> i32 ---;
|
||||
show_window :: proc(hwnd: rawptr, cmd_show: i32) -> i32 #link_name "ShowWindow" ---;
|
||||
}
|
||||
// NOTE: If that library doesn't get used, it doesn't get linked with
|
||||
// NOTE: There is not link checking yet to see if that procedure does come from that library
|
||||
|
||||
// See sys/windows.odin for more examples
|
||||
|
||||
special_expressions();
|
||||
}
|
||||
|
||||
special_expressions :: proc() {
|
||||
/*
|
||||
// Block expression
|
||||
x := {
|
||||
a: f32 = 123;
|
||||
b := a-123;
|
||||
c := b/a;
|
||||
give c;
|
||||
}; // semicolon is required as it's an expression
|
||||
|
||||
y := if x < 50 {
|
||||
give x;
|
||||
} else {
|
||||
// TODO: Type cohesion is not yet finished
|
||||
give 123;
|
||||
}; // semicolon is required as it's an expression
|
||||
*/
|
||||
|
||||
// This is allows for inline blocks of code and will be a useful feature to have when
|
||||
// macros will be implemented into the language
|
||||
|
||||
loops();
|
||||
}
|
||||
|
||||
loops :: proc() {
|
||||
// The C-style for loop
|
||||
for i := 0; i < 123; i += 1 {
|
||||
break;
|
||||
}
|
||||
for i := 0; i < 123; {
|
||||
break;
|
||||
}
|
||||
for false {
|
||||
break;
|
||||
}
|
||||
for {
|
||||
break;
|
||||
}
|
||||
|
||||
for i in 0..123 { // 123 exclusive
|
||||
}
|
||||
|
||||
for i in 0..123-1 { // 122 inclusive
|
||||
}
|
||||
|
||||
for val, idx in 12..16 {
|
||||
fmt.println(val, idx);
|
||||
}
|
||||
|
||||
primes := [?]int{2, 3, 5, 7, 11, 13, 17, 19};
|
||||
|
||||
for p in primes {
|
||||
fmt.println(p);
|
||||
}
|
||||
|
||||
// Pointers to arrays, slices, or strings are allowed
|
||||
for _ in &primes {
|
||||
// ignore the value and just iterate across it
|
||||
}
|
||||
|
||||
|
||||
|
||||
name := "你好,世界";
|
||||
fmt.println(name);
|
||||
for r in name {
|
||||
#assert(type_of(r) == rune);
|
||||
fmt.printf("%r\n", r);
|
||||
}
|
||||
|
||||
when false {
|
||||
for i, size := 0; i < name.count; i += size {
|
||||
r: rune;
|
||||
r, size = utf8.decode_rune(name[i..]);
|
||||
fmt.printf("%r\n", r);
|
||||
}
|
||||
}
|
||||
|
||||
procedure_overloading();
|
||||
}
|
||||
|
||||
|
||||
procedure_overloading :: proc() {
|
||||
THINGF :: 14451.1;
|
||||
THINGI :: 14451;
|
||||
|
||||
foo :: proc() {
|
||||
fmt.printf("Zero args\n");
|
||||
}
|
||||
foo :: proc(i: int) {
|
||||
fmt.printf("int arg, i=%d\n", i);
|
||||
}
|
||||
foo :: proc(f: f64) {
|
||||
i := cast(int)f;
|
||||
fmt.printf("f64 arg, f=%d\n", i);
|
||||
}
|
||||
|
||||
foo();
|
||||
foo(THINGF);
|
||||
// foo(THINGI); // 14451 is just a number so it could go to either procedures
|
||||
foo(cast(int)THINGI);
|
||||
|
||||
|
||||
|
||||
|
||||
foo :: proc(x: ^i32) -> (int, int) {
|
||||
fmt.println("^int");
|
||||
return 123, cast(int)(x^);
|
||||
}
|
||||
foo :: proc(x: rawptr) {
|
||||
fmt.println("rawptr");
|
||||
}
|
||||
|
||||
|
||||
a: i32 = 123;
|
||||
b: f32;
|
||||
c: rawptr;
|
||||
fmt.println(foo(&a));
|
||||
foo(&b);
|
||||
foo(c);
|
||||
// foo(nil); // nil could go to numerous types thus the ambiguity
|
||||
|
||||
f: proc();
|
||||
f = foo; // The correct `foo` to chosen
|
||||
f();
|
||||
|
||||
|
||||
// See math.odin and atomic.odin for more examples
|
||||
}
|
||||
@@ -1,310 +0,0 @@
|
||||
// import "core:atomic.odin";
|
||||
import "core:hash.odin";
|
||||
import "core:mem.odin";
|
||||
import "core:opengl.odin";
|
||||
import "core:strconv.odin";
|
||||
import "core:sync.odin";
|
||||
import win32 "core:sys/windows.odin";
|
||||
|
||||
import "core:fmt.odin";
|
||||
import "core:os.odin";
|
||||
import "core:math.odin";
|
||||
|
||||
|
||||
main :: proc() {
|
||||
when true {
|
||||
/*
|
||||
Added:
|
||||
* Unexported entities and fields using an underscore prefix
|
||||
- See `sync.odin` and explain
|
||||
|
||||
Removed:
|
||||
* Maybe/option types
|
||||
* Remove `type` keyword and other "reserved" keywords
|
||||
* ..< and .. removed and replace with .. (half-closed range)
|
||||
|
||||
Changed:
|
||||
* `#assert` and `assert` return the value of the condition for semantic reasons
|
||||
* thread_local -> #thread_local
|
||||
* #include -> #load
|
||||
* Files only get checked if they are actually used
|
||||
* match x in y {} // For type match statements
|
||||
* Version numbering now starts from 0.1.0 and uses the convention:
|
||||
- major.minor.patch
|
||||
* Core library additions to Windows specific stuff
|
||||
*/
|
||||
|
||||
{
|
||||
Fruit :: enum {
|
||||
APPLE,
|
||||
BANANA,
|
||||
COCONUT,
|
||||
}
|
||||
fmt.println(Fruit.names);
|
||||
}
|
||||
|
||||
{
|
||||
A :: struct {x, y: f32};
|
||||
B :: struct #align 16 {x, y: f32};
|
||||
fmt.println("align_of(A) =", align_of(A));
|
||||
fmt.println("align_of(B) =", align_of(B));
|
||||
}
|
||||
|
||||
{
|
||||
// Removal of ..< and ..
|
||||
for i in 0..16 {
|
||||
}
|
||||
// Is similar to
|
||||
for i := 0; i < 16; i += 1 {
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
thing: for i in 0..10 {
|
||||
for j in i+1..10 {
|
||||
if j == 2 {
|
||||
fmt.println(i, j);
|
||||
continue thing;
|
||||
}
|
||||
if j == 3 {
|
||||
break thing;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Works with, `for`, `for in`, `match`, `match in`
|
||||
// NOTE(bill): This solves most of the problems I need `goto` for
|
||||
}
|
||||
|
||||
{
|
||||
t := type_info_of(int);
|
||||
match i in t.variant {
|
||||
case Type_Info_Integer, Type_Info_Float:
|
||||
fmt.println("It's a number");
|
||||
}
|
||||
|
||||
|
||||
x: any = 123;
|
||||
foo: match i in x {
|
||||
case int, f32:
|
||||
fmt.println("It's an int or f32");
|
||||
break foo;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
cond := true;
|
||||
x: int;
|
||||
if cond {
|
||||
x = 3;
|
||||
} else {
|
||||
x = 4;
|
||||
}
|
||||
|
||||
|
||||
// Ternary operator
|
||||
y := cond ? 3 : 4;
|
||||
|
||||
FOO :: true ? 123 : 432; // Constant ternary expression
|
||||
fmt.println("Ternary values:", y, FOO);
|
||||
}
|
||||
|
||||
{
|
||||
// Slices now store a capacity
|
||||
buf: [256]u8;
|
||||
s: []u8;
|
||||
s = buf[..0]; // == buf[0..0];
|
||||
fmt.println("count =", len(s));
|
||||
fmt.println("capacity =", cap(s));
|
||||
append(&s, 1, 2, 3);
|
||||
fmt.println(s);
|
||||
|
||||
s = buf[1..2..3];
|
||||
fmt.println("count =", len(s));
|
||||
fmt.println("capacity =", cap(s));
|
||||
fmt.println(s);
|
||||
|
||||
clear(&s); // Sets count to zero
|
||||
}
|
||||
|
||||
{
|
||||
Foo :: struct {
|
||||
x, y, z: f32,
|
||||
ok: bool,
|
||||
flags: u32,
|
||||
}
|
||||
foo_array: [256]Foo;
|
||||
foo_as_bytes: []u8 = mem.slice_to_bytes(foo_array[..]);
|
||||
// Useful for things like
|
||||
// os.write(handle, foo_as_bytes);
|
||||
|
||||
foo_slice := mem.slice_ptr(cast(^Foo)&foo_as_bytes[0], len(foo_as_bytes)/size_of(Foo), cap(foo_as_bytes)/size_of(Foo));
|
||||
// Question: Should there be a bytes_to_slice procedure or is it clearer to do this even if it is error prone?
|
||||
// And if so what would the syntax be?
|
||||
// slice_transmute([]Foo, foo_as_bytes);
|
||||
}
|
||||
|
||||
{
|
||||
Vec3 :: [vector 3]f32;
|
||||
|
||||
x := Vec3{1, 2, 3};
|
||||
y := Vec3{4, 5, 6};
|
||||
fmt.println(x < y);
|
||||
fmt.println(x + y);
|
||||
fmt.println(x - y);
|
||||
fmt.println(x * y);
|
||||
fmt.println(x / y);
|
||||
|
||||
for i in x {
|
||||
fmt.println(i);
|
||||
}
|
||||
|
||||
#assert(size_of([vector 7]bool) >= size_of([7]bool));
|
||||
#assert(size_of([vector 7]i32) >= size_of([7]i32));
|
||||
// align_of([vector 7]i32) != align_of([7]i32) // this may be the case
|
||||
}
|
||||
|
||||
{
|
||||
// fmt.* changes
|
||||
// bprint* returns `string`
|
||||
|
||||
data: [256]u8;
|
||||
str := fmt.bprintf(data[..], "Hellope %d %s %c", 123, "others", '!');
|
||||
fmt.println(str);
|
||||
}
|
||||
|
||||
{
|
||||
x: [dynamic]f64;
|
||||
reserve(&x, 16);
|
||||
defer free(x); // `free` is overloaded for numerous types
|
||||
// Number literals can have underscores in them for readability
|
||||
append(&x, 2_000_000.500_000, 123, 5, 7); // variadic append
|
||||
|
||||
for p, i in x {
|
||||
if i > 0 { fmt.print(", "); }
|
||||
fmt.print(p);
|
||||
}
|
||||
fmt.println();
|
||||
}
|
||||
|
||||
{
|
||||
// Dynamic array "literals"
|
||||
x := [dynamic]f64{2_000_000.500_000, 3, 5, 7};
|
||||
defer free(x);
|
||||
fmt.println(x); // fmt.print* supports printing of dynamic types
|
||||
clear(&x);
|
||||
fmt.println(x);
|
||||
}
|
||||
|
||||
{
|
||||
m: map[f32]int;
|
||||
reserve(&m, 16);
|
||||
defer free(m);
|
||||
|
||||
m[1.0] = 1278;
|
||||
m[2.0] = 7643;
|
||||
m[3.0] = 564;
|
||||
_, ok := m[3.0];
|
||||
c := m[3.0];
|
||||
assert(ok && c == 564);
|
||||
|
||||
fmt.print("map[");
|
||||
i := 0;
|
||||
for val, key in m {
|
||||
if i > 0 {
|
||||
fmt.print(", ");
|
||||
}
|
||||
fmt.printf("%v=%v", key, val);
|
||||
i += 1;
|
||||
}
|
||||
fmt.println("]");
|
||||
}
|
||||
{
|
||||
m := map[string]u32{
|
||||
"a" = 56,
|
||||
"b" = 13453,
|
||||
"c" = 7654,
|
||||
};
|
||||
defer free(m);
|
||||
|
||||
c := m["c"];
|
||||
_, ok := m["c"];
|
||||
assert(ok && c == 7654);
|
||||
fmt.println(m);
|
||||
|
||||
delete(&m, "c"); // deletes entry with key "c"
|
||||
_, found := m["c"];
|
||||
assert(!found);
|
||||
|
||||
fmt.println(m);
|
||||
clear(&m);
|
||||
fmt.println(m);
|
||||
|
||||
// NOTE: Fixed size maps are planned but we have not yet implemented
|
||||
// them as we have had no need for them as of yet
|
||||
}
|
||||
|
||||
{
|
||||
Vector3 :: struct{x, y, z: f32};
|
||||
Quaternion :: struct{x, y, z, w: f32};
|
||||
|
||||
// Variants
|
||||
Frog :: struct {
|
||||
ribbit_volume: f32,
|
||||
jump_height: f32,
|
||||
}
|
||||
Door :: struct {
|
||||
openness: f32,
|
||||
}
|
||||
Map :: struct {
|
||||
width, height: f32,
|
||||
place_positions: []Vector3,
|
||||
place_names: []string,
|
||||
}
|
||||
|
||||
Entity :: struct {
|
||||
// Common Fields
|
||||
id: u64,
|
||||
name: string,
|
||||
using position: Vector3,
|
||||
orientation: Quaternion,
|
||||
flags: u32,
|
||||
|
||||
variant: union { Frog, Door, Map },
|
||||
}
|
||||
|
||||
entity: Entity;
|
||||
entity.id = 1337;
|
||||
// implicit conversion from variant to base type
|
||||
entity.variant = Frog{
|
||||
ribbit_volume = 0.5,
|
||||
jump_height = 2.1,
|
||||
/*other data */
|
||||
};
|
||||
|
||||
entity.name = "Frank";
|
||||
entity.position = Vector3{1, 4, 9};
|
||||
|
||||
match e in entity.variant {
|
||||
case Frog:
|
||||
fmt.println("Ribbit");
|
||||
case Door:
|
||||
fmt.println("Creak");
|
||||
case Map:
|
||||
fmt.println("Rustle");
|
||||
case:
|
||||
fmt.println("Just a normal entity");
|
||||
}
|
||||
|
||||
if frog, ok := entity.variant.(Frog); ok {
|
||||
fmt.printf("The frog jumps %f feet high at %v\n", frog.jump_height, entity.position);
|
||||
}
|
||||
|
||||
// Panics if not the correct type
|
||||
frog: Frog;
|
||||
frog = entity.variant.(Frog);
|
||||
frog, _ = entity.variant.(Frog); // ignore error and force cast
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,570 +0,0 @@
|
||||
import "core:fmt.odin"
|
||||
import "core:strconv.odin"
|
||||
import "core:mem.odin"
|
||||
import "core:bits.odin"
|
||||
import "core:hash.odin"
|
||||
import "core:math.odin"
|
||||
import "core:os.odin"
|
||||
import "core:raw.odin"
|
||||
import "core:sort.odin"
|
||||
import "core:strings.odin"
|
||||
import "core:types.odin"
|
||||
import "core:utf16.odin"
|
||||
import "core:utf8.odin"
|
||||
|
||||
when ODIN_OS == "windows" {
|
||||
import "core:atomics.odin"
|
||||
import "core:opengl.odin"
|
||||
import "core:thread.odin"
|
||||
import win32 "core:sys/windows.odin"
|
||||
}
|
||||
|
||||
general_stuff :: proc() {
|
||||
{ // `do` for inline statmes rather than block
|
||||
foo :: proc() do fmt.println("Foo!");
|
||||
if false do foo();
|
||||
for false do foo();
|
||||
when false do foo();
|
||||
|
||||
if false do foo();
|
||||
else do foo();
|
||||
}
|
||||
|
||||
{ // Removal of `++` and `--` (again)
|
||||
x: int;
|
||||
x += 1;
|
||||
x -= 1;
|
||||
}
|
||||
{ // Casting syntaxes
|
||||
i := i32(137);
|
||||
ptr := &i;
|
||||
|
||||
fp1 := (^f32)(ptr);
|
||||
// ^f32(ptr) == ^(f32(ptr))
|
||||
fp2 := cast(^f32)ptr;
|
||||
|
||||
f1 := (^f32)(ptr)^;
|
||||
f2 := (cast(^f32)ptr)^;
|
||||
|
||||
// Questions: Should there be two ways to do it?
|
||||
}
|
||||
|
||||
/*
|
||||
* Remove *_val_of built-in procedures
|
||||
* size_of, align_of, offset_of
|
||||
* type_of, type_info_of
|
||||
*/
|
||||
|
||||
{ // `expand_to_tuple` built-in procedure
|
||||
Foo :: struct {
|
||||
x: int,
|
||||
b: bool,
|
||||
}
|
||||
f := Foo{137, true};
|
||||
x, b := expand_to_tuple(f);
|
||||
fmt.println(f);
|
||||
fmt.println(x, b);
|
||||
fmt.println(expand_to_tuple(f));
|
||||
}
|
||||
|
||||
{
|
||||
// .. half-closed range
|
||||
// .. open range
|
||||
|
||||
for in 0..2 {} // 0, 1
|
||||
for in 0..2 {} // 0, 1, 2
|
||||
}
|
||||
}
|
||||
|
||||
default_struct_values :: proc() {
|
||||
{
|
||||
Vector3 :: struct {
|
||||
x: f32,
|
||||
y: f32,
|
||||
z: f32,
|
||||
}
|
||||
v: Vector3;
|
||||
fmt.println(v);
|
||||
}
|
||||
{
|
||||
// Default values must be constants
|
||||
Vector3 :: struct {
|
||||
x: f32 = 1,
|
||||
y: f32 = 4,
|
||||
z: f32 = 9,
|
||||
}
|
||||
v: Vector3;
|
||||
fmt.println(v);
|
||||
|
||||
v = Vector3{};
|
||||
fmt.println(v);
|
||||
|
||||
// Uses the same semantics as a default values in a procedure
|
||||
v = Vector3{137};
|
||||
fmt.println(v);
|
||||
|
||||
v = Vector3{z = 137};
|
||||
fmt.println(v);
|
||||
}
|
||||
|
||||
{
|
||||
Vector3 :: struct {
|
||||
x := 1.0,
|
||||
y := 4.0,
|
||||
z := 9.0,
|
||||
}
|
||||
stack_default: Vector3;
|
||||
stack_literal := Vector3{};
|
||||
heap_one := new(Vector3); defer free(heap_one);
|
||||
heap_two := new_clone(Vector3{}); defer free(heap_two);
|
||||
|
||||
fmt.println("stack_default - ", stack_default);
|
||||
fmt.println("stack_literal - ", stack_literal);
|
||||
fmt.println("heap_one - ", heap_one^);
|
||||
fmt.println("heap_two - ", heap_two^);
|
||||
|
||||
|
||||
N :: 4;
|
||||
stack_array: [N]Vector3;
|
||||
heap_array := new([N]Vector3); defer free(heap_array);
|
||||
heap_slice := make([]Vector3, N); defer free(heap_slice);
|
||||
fmt.println("stack_array[1] - ", stack_array[1]);
|
||||
fmt.println("heap_array[1] - ", heap_array[1]);
|
||||
fmt.println("heap_slice[1] - ", heap_slice[1]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
union_type :: proc() {
|
||||
{
|
||||
val: union{int, bool};
|
||||
val = 137;
|
||||
if i, ok := val.(int); ok {
|
||||
fmt.println(i);
|
||||
}
|
||||
val = true;
|
||||
fmt.println(val);
|
||||
|
||||
val = nil;
|
||||
|
||||
switch v in val {
|
||||
case int: fmt.println("int", v);
|
||||
case bool: fmt.println("bool", v);
|
||||
case: fmt.println("nil");
|
||||
}
|
||||
}
|
||||
{
|
||||
// There is a duality between `any` and `union`
|
||||
// An `any` has a pointer to the data and allows for any type (open)
|
||||
// A `union` has as binary blob to store the data and allows only certain types (closed)
|
||||
// The following code is with `any` but has the same syntax
|
||||
val: any;
|
||||
val = 137;
|
||||
if i, ok := val.(int); ok {
|
||||
fmt.println(i);
|
||||
}
|
||||
val = true;
|
||||
fmt.println(val);
|
||||
|
||||
val = nil;
|
||||
|
||||
switch v in val {
|
||||
case int: fmt.println("int", v);
|
||||
case bool: fmt.println("bool", v);
|
||||
case: fmt.println("nil");
|
||||
}
|
||||
}
|
||||
|
||||
Vector3 :: struct {x, y, z: f32};
|
||||
Quaternion :: struct {x, y, z: f32, w: f32 = 1};
|
||||
|
||||
// More realistic examples
|
||||
{
|
||||
// NOTE(bill): For the above basic examples, you may not have any
|
||||
// particular use for it. However, my main use for them is not for these
|
||||
// simple cases. My main use is for hierarchical types. Many prefer
|
||||
// subtyping, embedding the base data into the derived types. Below is
|
||||
// an example of this for a basic game Entity.
|
||||
|
||||
Entity :: struct {
|
||||
id: u64,
|
||||
name: string,
|
||||
position: Vector3,
|
||||
orientation: Quaternion,
|
||||
|
||||
derived: any,
|
||||
}
|
||||
|
||||
Frog :: struct {
|
||||
using entity: Entity,
|
||||
jump_height: f32,
|
||||
}
|
||||
|
||||
Monster :: struct {
|
||||
using entity: Entity,
|
||||
is_robot: bool,
|
||||
is_zombie: bool,
|
||||
}
|
||||
|
||||
// See `parametric_polymorphism` procedure for details
|
||||
new_entity :: proc(T: type) -> ^Entity {
|
||||
t := new(T);
|
||||
t.derived = t^;
|
||||
return t;
|
||||
}
|
||||
|
||||
entity := new_entity(Monster);
|
||||
|
||||
switch e in entity.derived {
|
||||
case Frog:
|
||||
fmt.println("Ribbit");
|
||||
case Monster:
|
||||
if e.is_robot do fmt.println("Robotic");
|
||||
if e.is_zombie do fmt.println("Grrrr!");
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// NOTE(bill): A union can be used to achieve something similar. Instead
|
||||
// of embedding the base data into the derived types, the derived data
|
||||
// in embedded into the base type. Below is the same example of the
|
||||
// basic game Entity but using an union.
|
||||
|
||||
Entity :: struct {
|
||||
id: u64,
|
||||
name: string,
|
||||
position: Vector3,
|
||||
orientation: Quaternion,
|
||||
|
||||
derived: union {Frog, Monster},
|
||||
}
|
||||
|
||||
Frog :: struct {
|
||||
using entity: ^Entity,
|
||||
jump_height: f32,
|
||||
}
|
||||
|
||||
Monster :: struct {
|
||||
using entity: ^Entity,
|
||||
is_robot: bool,
|
||||
is_zombie: bool,
|
||||
}
|
||||
|
||||
// See `parametric_polymorphism` procedure for details
|
||||
new_entity :: proc(T: type) -> ^Entity {
|
||||
t := new(Entity);
|
||||
t.derived = T{entity = t};
|
||||
return t;
|
||||
}
|
||||
|
||||
entity := new_entity(Monster);
|
||||
|
||||
switch e in entity.derived {
|
||||
case Frog:
|
||||
fmt.println("Ribbit");
|
||||
case Monster:
|
||||
if e.is_robot do fmt.println("Robotic");
|
||||
if e.is_zombie do fmt.println("Grrrr!");
|
||||
}
|
||||
|
||||
// NOTE(bill): As you can see, the usage code has not changed, only its
|
||||
// memory layout. Both approaches have their own advantages but they can
|
||||
// be used together to achieve different results. The subtyping approach
|
||||
// can allow for a greater control of the memory layout and memory
|
||||
// allocation, e.g. storing the derivatives together. However, this is
|
||||
// also its disadvantage. You must either preallocate arrays for each
|
||||
// derivative separation (which can be easily missed) or preallocate a
|
||||
// bunch of "raw" memory; determining the maximum size of the derived
|
||||
// types would require the aid of metaprogramming. Unions solve this
|
||||
// particular problem as the data is stored with the base data.
|
||||
// Therefore, it is possible to preallocate, e.g. [100]Entity.
|
||||
|
||||
// It should be noted that the union approach can have the same memory
|
||||
// layout as the any and with the same type restrictions by using a
|
||||
// pointer type for the derivatives.
|
||||
|
||||
/*
|
||||
Entity :: struct {
|
||||
..
|
||||
derived: union{^Frog, ^Monster};
|
||||
}
|
||||
|
||||
Frog :: struct {
|
||||
using entity: Entity;
|
||||
..
|
||||
}
|
||||
Monster :: struct {
|
||||
using entity: Entity;
|
||||
..
|
||||
|
||||
}
|
||||
new_entity :: proc(T: type) -> ^Entity {
|
||||
t := new(T);
|
||||
t.derived = t;
|
||||
return t;
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
parametric_polymorphism :: proc() {
|
||||
print_value :: proc(value: $T) {
|
||||
fmt.printf("print_value: %T %v\n", value, value);
|
||||
}
|
||||
|
||||
v1: int = 1;
|
||||
v2: f32 = 2.1;
|
||||
v3: f64 = 3.14;
|
||||
v4: string = "message";
|
||||
|
||||
print_value(v1);
|
||||
print_value(v2);
|
||||
print_value(v3);
|
||||
print_value(v4);
|
||||
|
||||
fmt.println();
|
||||
|
||||
add :: proc(p, q: $T) -> T {
|
||||
x: T = p + q;
|
||||
return x;
|
||||
}
|
||||
|
||||
a := add(3, 4);
|
||||
fmt.printf("a: %T = %v\n", a, a);
|
||||
|
||||
b := add(3.2, 4.3);
|
||||
fmt.printf("b: %T = %v\n", b, b);
|
||||
|
||||
// This is how `new` is implemented
|
||||
alloc_type :: proc(T: type) -> ^T {
|
||||
t := cast(^T)alloc(size_of(T), align_of(T));
|
||||
t^ = T{}; // Use default initialization value
|
||||
return t;
|
||||
}
|
||||
|
||||
copy_slice :: proc(dst, src: []$T) -> int {
|
||||
n := min(len(dst), len(src));
|
||||
if n > 0 {
|
||||
mem.copy(&dst[0], &src[0], n*size_of(T));
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
double_params :: proc(a: $A, b: $B) -> A {
|
||||
return a + A(b);
|
||||
}
|
||||
|
||||
fmt.println(double_params(12, 1.345));
|
||||
|
||||
|
||||
|
||||
{ // Polymorphic Types and Type Specialization
|
||||
Table_Slot :: struct(Key, Value: type) {
|
||||
occupied: bool,
|
||||
hash: u32,
|
||||
key: Key,
|
||||
value: Value,
|
||||
}
|
||||
TABLE_SIZE_MIN :: 32;
|
||||
Table :: struct(Key, Value: type) {
|
||||
count: int,
|
||||
allocator: Allocator,
|
||||
slots: []Table_Slot(Key, Value),
|
||||
}
|
||||
|
||||
// Only allow types that are specializations of a (polymorphic) slice
|
||||
make_slice :: proc(T: type/[]$E, len: int) -> T {
|
||||
return make(T, len);
|
||||
}
|
||||
|
||||
|
||||
// Only allow types that are specializations of `Table`
|
||||
allocate :: proc(table: ^$T/Table, capacity: int) {
|
||||
c := context;
|
||||
if table.allocator.procedure != nil do c.allocator = table.allocator;
|
||||
|
||||
push_context c {
|
||||
table.slots = make_slice(type_of(table.slots), max(capacity, TABLE_SIZE_MIN));
|
||||
}
|
||||
}
|
||||
|
||||
expand :: proc(table: ^$T/Table) {
|
||||
c := context;
|
||||
if table.allocator.procedure != nil do c.allocator = table.allocator;
|
||||
|
||||
push_context c {
|
||||
old_slots := table.slots;
|
||||
|
||||
cap := max(2*cap(table.slots), TABLE_SIZE_MIN);
|
||||
allocate(table, cap);
|
||||
|
||||
for s in old_slots do if s.occupied {
|
||||
put(table, s.key, s.value);
|
||||
}
|
||||
|
||||
free(old_slots);
|
||||
}
|
||||
}
|
||||
|
||||
// Polymorphic determination of a polymorphic struct
|
||||
// put :: proc(table: ^$T/Table, key: T.Key, value: T.Value) {
|
||||
put :: proc(table: ^Table($Key, $Value), key: Key, value: Value) {
|
||||
hash := get_hash(key); // Ad-hoc method which would fail in a different scope
|
||||
index := find_index(table, key, hash);
|
||||
if index < 0 {
|
||||
if f64(table.count) >= 0.75*f64(cap(table.slots)) {
|
||||
expand(table);
|
||||
}
|
||||
assert(table.count <= cap(table.slots));
|
||||
|
||||
hash := get_hash(key);
|
||||
index = int(hash % u32(cap(table.slots)));
|
||||
|
||||
for table.slots[index].occupied {
|
||||
if index += 1; index >= cap(table.slots) {
|
||||
index = 0;
|
||||
}
|
||||
}
|
||||
|
||||
table.count += 1;
|
||||
}
|
||||
|
||||
slot := &table.slots[index];
|
||||
slot.occupied = true;
|
||||
slot.hash = hash;
|
||||
slot.key = key;
|
||||
slot.value = value;
|
||||
}
|
||||
|
||||
|
||||
// find :: proc(table: ^$T/Table, key: T.Key) -> (T.Value, bool) {
|
||||
find :: proc(table: ^Table($Key, $Value), key: Key) -> (Value, bool) {
|
||||
hash := get_hash(key);
|
||||
index := find_index(table, key, hash);
|
||||
if index < 0 {
|
||||
return Value{}, false;
|
||||
}
|
||||
return table.slots[index].value, true;
|
||||
}
|
||||
|
||||
find_index :: proc(table: ^Table($Key, $Value), key: Key, hash: u32) -> int {
|
||||
if cap(table.slots) <= 0 do return -1;
|
||||
|
||||
index := int(hash % u32(cap(table.slots)));
|
||||
for table.slots[index].occupied {
|
||||
if table.slots[index].hash == hash {
|
||||
if table.slots[index].key == key {
|
||||
return index;
|
||||
}
|
||||
}
|
||||
|
||||
if index += 1; index >= cap(table.slots) {
|
||||
index = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
get_hash :: proc(s: string) -> u32 { // fnv32a
|
||||
h: u32 = 0x811c9dc5;
|
||||
for i in 0..len(s) {
|
||||
h = (h ~ u32(s[i])) * 0x01000193;
|
||||
}
|
||||
return h;
|
||||
}
|
||||
|
||||
|
||||
table: Table(string, int);
|
||||
|
||||
for i in 0..36 do put(&table, "Hellope", i);
|
||||
for i in 0..42 do put(&table, "World!", i);
|
||||
|
||||
found, _ := find(&table, "Hellope");
|
||||
fmt.printf("`found` is %v\n", found);
|
||||
|
||||
found, _ = find(&table, "World!");
|
||||
fmt.printf("`found` is %v\n", found);
|
||||
|
||||
// I would not personally design a hash table like this in production
|
||||
// but this is a nice basic example
|
||||
// A better approach would either use a `u64` or equivalent for the key
|
||||
// and let the user specify the hashing function or make the user store
|
||||
// the hashing procedure with the table
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
prefix_table := [?]string{
|
||||
"White",
|
||||
"Red",
|
||||
"Green",
|
||||
"Blue",
|
||||
"Octarine",
|
||||
"Black",
|
||||
};
|
||||
|
||||
threading_example :: proc() {
|
||||
when ODIN_OS == "windows" {
|
||||
unordered_remove :: proc(array: ^[]$T, index: int, loc := #caller_location) {
|
||||
__bounds_check_error_loc(loc, index, len(array));
|
||||
array[index] = array[len(array)-1];
|
||||
pop(array);
|
||||
}
|
||||
ordered_remove :: proc(array: ^[]$T, index: int, loc := #caller_location) {
|
||||
__bounds_check_error_loc(loc, index, len(array));
|
||||
copy(array[index..], array[index+1..]);
|
||||
pop(array);
|
||||
}
|
||||
|
||||
worker_proc :: proc(t: ^thread.Thread) -> int {
|
||||
for iteration in 1..5 {
|
||||
fmt.printf("Thread %d is on iteration %d\n", t.user_index, iteration);
|
||||
fmt.printf("`%s`: iteration %d\n", prefix_table[t.user_index], iteration);
|
||||
// win32.sleep(1);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
threads := make([]^thread.Thread, 0, len(prefix_table));
|
||||
defer free(threads);
|
||||
|
||||
for i in 0..len(prefix_table) {
|
||||
if t := thread.create(worker_proc); t != nil {
|
||||
t.init_context = context;
|
||||
t.use_init_context = true;
|
||||
t.user_index = len(threads);
|
||||
append(&threads, t);
|
||||
thread.start(t);
|
||||
}
|
||||
}
|
||||
|
||||
for len(threads) > 0 {
|
||||
for i := 0; i < len(threads); /**/ {
|
||||
if t := threads[i]; thread.is_done(t) {
|
||||
fmt.printf("Thread %d is done\n", t.user_index);
|
||||
thread.destroy(t);
|
||||
|
||||
ordered_remove(&threads, i);
|
||||
} else {
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
main :: proc() {
|
||||
when false {
|
||||
fmt.println("\n# general_stuff"); general_stuff();
|
||||
fmt.println("\n# default_struct_values"); default_struct_values();
|
||||
fmt.println("\n# union_type"); union_type();
|
||||
fmt.println("\n# parametric_polymorphism"); parametric_polymorphism();
|
||||
fmt.println("\n# threading_example"); threading_example();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,778 +0,0 @@
|
||||
import "core:fmt.odin"
|
||||
import "core:strconv.odin"
|
||||
import "core:mem.odin"
|
||||
import "core:bits.odin"
|
||||
import "core:hash.odin"
|
||||
import "core:math.odin"
|
||||
import "core:math/rand.odin"
|
||||
import "core:os.odin"
|
||||
import "core:raw.odin"
|
||||
import "core:sort.odin"
|
||||
import "core:strings.odin"
|
||||
import "core:types.odin"
|
||||
import "core:utf16.odin"
|
||||
import "core:utf8.odin"
|
||||
|
||||
// File scope `when` statements
|
||||
when ODIN_OS == "windows" {
|
||||
import "core:atomics.odin"
|
||||
import "core:thread.odin"
|
||||
import win32 "core:sys/windows.odin"
|
||||
}
|
||||
|
||||
@(link_name="general_stuff")
|
||||
general_stuff :: proc() {
|
||||
fmt.println("# general_stuff");
|
||||
{ // `do` for inline statements rather than block
|
||||
foo :: proc() do fmt.println("Foo!");
|
||||
if false do foo();
|
||||
for false do foo();
|
||||
when false do foo();
|
||||
|
||||
if false do foo();
|
||||
else do foo();
|
||||
}
|
||||
|
||||
{ // Removal of `++` and `--` (again)
|
||||
x: int;
|
||||
x += 1;
|
||||
x -= 1;
|
||||
}
|
||||
{ // Casting syntaxes
|
||||
i := i32(137);
|
||||
ptr := &i;
|
||||
|
||||
_ = (^f32)(ptr);
|
||||
// ^f32(ptr) == ^(f32(ptr))
|
||||
_ = cast(^f32)ptr;
|
||||
|
||||
_ = (^f32)(ptr)^;
|
||||
_ = (cast(^f32)ptr)^;
|
||||
|
||||
// Questions: Should there be two ways to do it?
|
||||
}
|
||||
|
||||
/*
|
||||
* Remove *_val_of built-in procedures
|
||||
* size_of, align_of, offset_of
|
||||
* type_of, type_info_of
|
||||
*/
|
||||
|
||||
{ // `expand_to_tuple` built-in procedure
|
||||
Foo :: struct {
|
||||
x: int,
|
||||
b: bool,
|
||||
}
|
||||
f := Foo{137, true};
|
||||
x, b := expand_to_tuple(f);
|
||||
fmt.println(f);
|
||||
fmt.println(x, b);
|
||||
fmt.println(expand_to_tuple(f));
|
||||
}
|
||||
|
||||
{
|
||||
// .. half-closed range
|
||||
// .. open range
|
||||
|
||||
for in 0..2 {} // 0, 1
|
||||
for in 0..2 {} // 0, 1, 2
|
||||
}
|
||||
|
||||
{ // Multiple sized booleans
|
||||
|
||||
x0: bool; // default
|
||||
x1: b8 = true;
|
||||
x2: b16 = false;
|
||||
x3: b32 = true;
|
||||
x4: b64 = false;
|
||||
|
||||
fmt.printf("x1: %T = %v;\n", x1, x1);
|
||||
fmt.printf("x2: %T = %v;\n", x2, x2);
|
||||
fmt.printf("x3: %T = %v;\n", x3, x3);
|
||||
fmt.printf("x4: %T = %v;\n", x4, x4);
|
||||
|
||||
// Having specific sized booleans is very useful when dealing with foreign code
|
||||
// and to enforce specific alignment for a boolean, especially within a struct
|
||||
}
|
||||
|
||||
{ // `distinct` types
|
||||
// Originally, all type declarations would create a distinct type unless #type_alias was present.
|
||||
// Now the behaviour has been reversed. All type declarations create a type alias unless `distinct` is present.
|
||||
// If the type expression is `struct`, `union`, `enum`, or `proc`, the types will always been distinct.
|
||||
|
||||
Int32 :: i32;
|
||||
#assert(Int32 == i32);
|
||||
|
||||
My_Int32 :: distinct i32;
|
||||
#assert(My_Int32 != i32);
|
||||
|
||||
My_Struct :: struct{x: int};
|
||||
#assert(My_Struct != struct{x: int});
|
||||
}
|
||||
}
|
||||
|
||||
default_struct_values :: proc() {
|
||||
fmt.println("# default_struct_values");
|
||||
{
|
||||
Vector3 :: struct {
|
||||
x: f32,
|
||||
y: f32,
|
||||
z: f32,
|
||||
}
|
||||
v: Vector3;
|
||||
fmt.println(v);
|
||||
}
|
||||
{
|
||||
// Default values must be constants
|
||||
Vector3 :: struct {
|
||||
x: f32 = 1,
|
||||
y: f32 = 4,
|
||||
z: f32 = 9,
|
||||
}
|
||||
v: Vector3;
|
||||
fmt.println(v);
|
||||
|
||||
v = Vector3{};
|
||||
fmt.println(v);
|
||||
|
||||
// Uses the same semantics as a default values in a procedure
|
||||
v = Vector3{137};
|
||||
fmt.println(v);
|
||||
|
||||
v = Vector3{z = 137};
|
||||
fmt.println(v);
|
||||
}
|
||||
|
||||
{
|
||||
Vector3 :: struct {
|
||||
x := 1.0,
|
||||
y := 4.0,
|
||||
z := 9.0,
|
||||
}
|
||||
stack_default: Vector3;
|
||||
stack_literal := Vector3{};
|
||||
heap_one := new(Vector3); defer free(heap_one);
|
||||
heap_two := new_clone(Vector3{}); defer free(heap_two);
|
||||
|
||||
fmt.println("stack_default - ", stack_default);
|
||||
fmt.println("stack_literal - ", stack_literal);
|
||||
fmt.println("heap_one - ", heap_one^);
|
||||
fmt.println("heap_two - ", heap_two^);
|
||||
|
||||
|
||||
N :: 4;
|
||||
stack_array: [N]Vector3;
|
||||
heap_array := new([N]Vector3); defer free(heap_array);
|
||||
heap_slice := make([]Vector3, N); defer free(heap_slice);
|
||||
fmt.println("stack_array[1] - ", stack_array[1]);
|
||||
fmt.println("heap_array[1] - ", heap_array[1]);
|
||||
fmt.println("heap_slice[1] - ", heap_slice[1]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
union_type :: proc() {
|
||||
fmt.println("\n# union_type");
|
||||
{
|
||||
val: union{int, bool};
|
||||
val = 137;
|
||||
if i, ok := val.(int); ok {
|
||||
fmt.println(i);
|
||||
}
|
||||
val = true;
|
||||
fmt.println(val);
|
||||
|
||||
val = nil;
|
||||
|
||||
switch v in val {
|
||||
case int: fmt.println("int", v);
|
||||
case bool: fmt.println("bool", v);
|
||||
case: fmt.println("nil");
|
||||
}
|
||||
}
|
||||
{
|
||||
// There is a duality between `any` and `union`
|
||||
// An `any` has a pointer to the data and allows for any type (open)
|
||||
// A `union` has as binary blob to store the data and allows only certain types (closed)
|
||||
// The following code is with `any` but has the same syntax
|
||||
val: any;
|
||||
val = 137;
|
||||
if i, ok := val.(int); ok {
|
||||
fmt.println(i);
|
||||
}
|
||||
val = true;
|
||||
fmt.println(val);
|
||||
|
||||
val = nil;
|
||||
|
||||
switch v in val {
|
||||
case int: fmt.println("int", v);
|
||||
case bool: fmt.println("bool", v);
|
||||
case: fmt.println("nil");
|
||||
}
|
||||
}
|
||||
|
||||
Vector3 :: struct {x, y, z: f32};
|
||||
Quaternion :: struct {x, y, z: f32, w: f32 = 1};
|
||||
|
||||
// More realistic examples
|
||||
{
|
||||
// NOTE(bill): For the above basic examples, you may not have any
|
||||
// particular use for it. However, my main use for them is not for these
|
||||
// simple cases. My main use is for hierarchical types. Many prefer
|
||||
// subtyping, embedding the base data into the derived types. Below is
|
||||
// an example of this for a basic game Entity.
|
||||
|
||||
Entity :: struct {
|
||||
id: u64,
|
||||
name: string,
|
||||
position: Vector3,
|
||||
orientation: Quaternion,
|
||||
|
||||
derived: any,
|
||||
}
|
||||
|
||||
Frog :: struct {
|
||||
using entity: Entity,
|
||||
jump_height: f32,
|
||||
}
|
||||
|
||||
Monster :: struct {
|
||||
using entity: Entity,
|
||||
is_robot: bool,
|
||||
is_zombie: bool,
|
||||
}
|
||||
|
||||
// See `parametric_polymorphism` procedure for details
|
||||
new_entity :: proc(T: type) -> ^Entity {
|
||||
t := new(T);
|
||||
t.derived = t^;
|
||||
return t;
|
||||
}
|
||||
|
||||
entity := new_entity(Monster);
|
||||
|
||||
switch e in entity.derived {
|
||||
case Frog:
|
||||
fmt.println("Ribbit");
|
||||
case Monster:
|
||||
if e.is_robot do fmt.println("Robotic");
|
||||
if e.is_zombie do fmt.println("Grrrr!");
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// NOTE(bill): A union can be used to achieve something similar. Instead
|
||||
// of embedding the base data into the derived types, the derived data
|
||||
// in embedded into the base type. Below is the same example of the
|
||||
// basic game Entity but using an union.
|
||||
|
||||
Entity :: struct {
|
||||
id: u64,
|
||||
name: string,
|
||||
position: Vector3,
|
||||
orientation: Quaternion,
|
||||
|
||||
derived: union {Frog, Monster},
|
||||
}
|
||||
|
||||
Frog :: struct {
|
||||
using entity: ^Entity,
|
||||
jump_height: f32,
|
||||
}
|
||||
|
||||
Monster :: struct {
|
||||
using entity: ^Entity,
|
||||
is_robot: bool,
|
||||
is_zombie: bool,
|
||||
}
|
||||
|
||||
// See `parametric_polymorphism` procedure for details
|
||||
new_entity :: proc(T: type) -> ^Entity {
|
||||
t := new(Entity);
|
||||
t.derived = T{entity = t};
|
||||
return t;
|
||||
}
|
||||
|
||||
entity := new_entity(Monster);
|
||||
|
||||
switch e in entity.derived {
|
||||
case Frog:
|
||||
fmt.println("Ribbit");
|
||||
case Monster:
|
||||
if e.is_robot do fmt.println("Robotic");
|
||||
if e.is_zombie do fmt.println("Grrrr!");
|
||||
}
|
||||
|
||||
// NOTE(bill): As you can see, the usage code has not changed, only its
|
||||
// memory layout. Both approaches have their own advantages but they can
|
||||
// be used together to achieve different results. The subtyping approach
|
||||
// can allow for a greater control of the memory layout and memory
|
||||
// allocation, e.g. storing the derivatives together. However, this is
|
||||
// also its disadvantage. You must either preallocate arrays for each
|
||||
// derivative separation (which can be easily missed) or preallocate a
|
||||
// bunch of "raw" memory; determining the maximum size of the derived
|
||||
// types would require the aid of metaprogramming. Unions solve this
|
||||
// particular problem as the data is stored with the base data.
|
||||
// Therefore, it is possible to preallocate, e.g. [100]Entity.
|
||||
|
||||
// It should be noted that the union approach can have the same memory
|
||||
// layout as the any and with the same type restrictions by using a
|
||||
// pointer type for the derivatives.
|
||||
|
||||
/*
|
||||
Entity :: struct {
|
||||
..
|
||||
derived: union{^Frog, ^Monster},
|
||||
}
|
||||
|
||||
Frog :: struct {
|
||||
using entity: Entity,
|
||||
..
|
||||
}
|
||||
Monster :: struct {
|
||||
using entity: Entity,
|
||||
..
|
||||
|
||||
}
|
||||
new_entity :: proc(T: type) -> ^Entity {
|
||||
t := new(T);
|
||||
t.derived = t;
|
||||
return t;
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
parametric_polymorphism :: proc() {
|
||||
fmt.println("# parametric_polymorphism");
|
||||
|
||||
print_value :: proc(value: $T) {
|
||||
fmt.printf("print_value: %T %v\n", value, value);
|
||||
}
|
||||
|
||||
v1: int = 1;
|
||||
v2: f32 = 2.1;
|
||||
v3: f64 = 3.14;
|
||||
v4: string = "message";
|
||||
|
||||
print_value(v1);
|
||||
print_value(v2);
|
||||
print_value(v3);
|
||||
print_value(v4);
|
||||
|
||||
fmt.println();
|
||||
|
||||
add :: proc(p, q: $T) -> T {
|
||||
x: T = p + q;
|
||||
return x;
|
||||
}
|
||||
|
||||
a := add(3, 4);
|
||||
fmt.printf("a: %T = %v\n", a, a);
|
||||
|
||||
b := add(3.2, 4.3);
|
||||
fmt.printf("b: %T = %v\n", b, b);
|
||||
|
||||
// This is how `new` is implemented
|
||||
alloc_type :: proc(T: type) -> ^T {
|
||||
t := cast(^T)alloc(size_of(T), align_of(T));
|
||||
t^ = T{}; // Use default initialization value
|
||||
return t;
|
||||
}
|
||||
|
||||
copy_slice :: proc(dst, src: []$T) -> int {
|
||||
return mem.copy(&dst[0], &src[0], n*size_of(T));
|
||||
}
|
||||
|
||||
double_params :: proc(a: $A, b: $B) -> A {
|
||||
return a + A(b);
|
||||
}
|
||||
|
||||
fmt.println(double_params(12, 1.345));
|
||||
|
||||
|
||||
|
||||
{ // Polymorphic Types and Type Specialization
|
||||
Table_Slot :: struct(Key, Value: type) {
|
||||
occupied: bool,
|
||||
hash: u32,
|
||||
key: Key,
|
||||
value: Value,
|
||||
}
|
||||
TABLE_SIZE_MIN :: 32;
|
||||
Table :: struct(Key, Value: type) {
|
||||
count: int,
|
||||
allocator: Allocator,
|
||||
slots: []Table_Slot(Key, Value),
|
||||
}
|
||||
|
||||
// Only allow types that are specializations of a (polymorphic) slice
|
||||
make_slice :: proc(T: type/[]$E, len: int) -> T {
|
||||
return make(T, len);
|
||||
}
|
||||
|
||||
|
||||
// Only allow types that are specializations of `Table`
|
||||
allocate :: proc(table: ^$T/Table, capacity: int) {
|
||||
c := context;
|
||||
if table.allocator.procedure != nil do c.allocator = table.allocator;
|
||||
|
||||
context <- c {
|
||||
table.slots = make_slice(type_of(table.slots), max(capacity, TABLE_SIZE_MIN));
|
||||
}
|
||||
}
|
||||
|
||||
expand :: proc(table: ^$T/Table) {
|
||||
c := context;
|
||||
if table.allocator.procedure != nil do c.allocator = table.allocator;
|
||||
|
||||
context <- c {
|
||||
old_slots := table.slots;
|
||||
|
||||
cap := max(2*len(table.slots), TABLE_SIZE_MIN);
|
||||
allocate(table, cap);
|
||||
|
||||
for s in old_slots do if s.occupied {
|
||||
put(table, s.key, s.value);
|
||||
}
|
||||
|
||||
free(old_slots);
|
||||
}
|
||||
}
|
||||
|
||||
// Polymorphic determination of a polymorphic struct
|
||||
// put :: proc(table: ^$T/Table, key: T.Key, value: T.Value) {
|
||||
put :: proc(table: ^Table($Key, $Value), key: Key, value: Value) {
|
||||
hash := get_hash(key); // Ad-hoc method which would fail in a different scope
|
||||
index := find_index(table, key, hash);
|
||||
if index < 0 {
|
||||
if f64(table.count) >= 0.75*f64(len(table.slots)) {
|
||||
expand(table);
|
||||
}
|
||||
assert(table.count <= len(table.slots));
|
||||
|
||||
hash := get_hash(key);
|
||||
index = int(hash % u32(len(table.slots)));
|
||||
|
||||
for table.slots[index].occupied {
|
||||
if index += 1; index >= len(table.slots) {
|
||||
index = 0;
|
||||
}
|
||||
}
|
||||
|
||||
table.count += 1;
|
||||
}
|
||||
|
||||
slot := &table.slots[index];
|
||||
slot.occupied = true;
|
||||
slot.hash = hash;
|
||||
slot.key = key;
|
||||
slot.value = value;
|
||||
}
|
||||
|
||||
|
||||
// find :: proc(table: ^$T/Table, key: T.Key) -> (T.Value, bool) {
|
||||
find :: proc(table: ^Table($Key, $Value), key: Key) -> (Value, bool) {
|
||||
hash := get_hash(key);
|
||||
index := find_index(table, key, hash);
|
||||
if index < 0 {
|
||||
return Value{}, false;
|
||||
}
|
||||
return table.slots[index].value, true;
|
||||
}
|
||||
|
||||
find_index :: proc(table: ^Table($Key, $Value), key: Key, hash: u32) -> int {
|
||||
if len(table.slots) <= 0 do return -1;
|
||||
|
||||
index := int(hash % u32(len(table.slots)));
|
||||
for table.slots[index].occupied {
|
||||
if table.slots[index].hash == hash {
|
||||
if table.slots[index].key == key {
|
||||
return index;
|
||||
}
|
||||
}
|
||||
|
||||
if index += 1; index >= len(table.slots) {
|
||||
index = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
get_hash :: proc(s: string) -> u32 { // fnv32a
|
||||
h: u32 = 0x811c9dc5;
|
||||
for i in 0..len(s) {
|
||||
h = (h ~ u32(s[i])) * 0x01000193;
|
||||
}
|
||||
return h;
|
||||
}
|
||||
|
||||
|
||||
table: Table(string, int);
|
||||
|
||||
for i in 0..36 do put(&table, "Hellope", i);
|
||||
for i in 0..42 do put(&table, "World!", i);
|
||||
|
||||
found, _ := find(&table, "Hellope");
|
||||
fmt.printf("`found` is %v\n", found);
|
||||
|
||||
found, _ = find(&table, "World!");
|
||||
fmt.printf("`found` is %v\n", found);
|
||||
|
||||
// I would not personally design a hash table like this in production
|
||||
// but this is a nice basic example
|
||||
// A better approach would either use a `u64` or equivalent for the key
|
||||
// and let the user specify the hashing function or make the user store
|
||||
// the hashing procedure with the table
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
prefix_table := [?]string{
|
||||
"White",
|
||||
"Red",
|
||||
"Green",
|
||||
"Blue",
|
||||
"Octarine",
|
||||
"Black",
|
||||
};
|
||||
|
||||
threading_example :: proc() {
|
||||
when ODIN_OS == "windows" {
|
||||
fmt.println("# threading_example");
|
||||
|
||||
unordered_remove :: proc(array: ^[dynamic]$T, index: int, loc := #caller_location) {
|
||||
__bounds_check_error_loc(loc, index, len(array));
|
||||
array[index] = array[len(array)-1];
|
||||
pop(array);
|
||||
}
|
||||
ordered_remove :: proc(array: ^[dynamic]$T, index: int, loc := #caller_location) {
|
||||
__bounds_check_error_loc(loc, index, len(array));
|
||||
copy(array[index..], array[index+1..]);
|
||||
pop(array);
|
||||
}
|
||||
|
||||
worker_proc :: proc(t: ^thread.Thread) -> int {
|
||||
for iteration in 1..5 {
|
||||
fmt.printf("Thread %d is on iteration %d\n", t.user_index, iteration);
|
||||
fmt.printf("`%s`: iteration %d\n", prefix_table[t.user_index], iteration);
|
||||
// win32.sleep(1);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
threads := make([dynamic]^thread.Thread, 0, len(prefix_table));
|
||||
defer free(threads);
|
||||
|
||||
for in prefix_table {
|
||||
if t := thread.create(worker_proc); t != nil {
|
||||
t.init_context = context;
|
||||
t.use_init_context = true;
|
||||
t.user_index = len(threads);
|
||||
append(&threads, t);
|
||||
thread.start(t);
|
||||
}
|
||||
}
|
||||
|
||||
for len(threads) > 0 {
|
||||
for i := 0; i < len(threads); /**/ {
|
||||
if t := threads[i]; thread.is_done(t) {
|
||||
fmt.printf("Thread %d is done\n", t.user_index);
|
||||
thread.destroy(t);
|
||||
|
||||
ordered_remove(&threads, i);
|
||||
} else {
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
array_programming :: proc() {
|
||||
fmt.println("# array_programming");
|
||||
{
|
||||
a := [3]f32{1, 2, 3};
|
||||
b := [3]f32{5, 6, 7};
|
||||
c := a * b;
|
||||
d := a + b;
|
||||
e := 1 + (c - d) / 2;
|
||||
fmt.printf("%.1f\n", e); // [0.5, 3.0, 6.5]
|
||||
}
|
||||
|
||||
{
|
||||
a := [3]f32{1, 2, 3};
|
||||
b := swizzle(a, 2, 1, 0);
|
||||
assert(b == [3]f32{3, 2, 1});
|
||||
|
||||
c := swizzle(a, 0, 0);
|
||||
assert(c == [2]f32{1, 1});
|
||||
assert(c == 1);
|
||||
}
|
||||
|
||||
{
|
||||
Vector3 :: distinct [3]f32;
|
||||
a := Vector3{1, 2, 3};
|
||||
b := Vector3{5, 6, 7};
|
||||
c := (a * b)/2 + 1;
|
||||
d := c.x + c.y + c.z;
|
||||
fmt.printf("%.1f\n", d); // 22.0
|
||||
|
||||
cross :: proc(a, b: Vector3) -> Vector3 {
|
||||
i := swizzle(a, 1, 2, 0) * swizzle(b, 2, 0, 1);
|
||||
j := swizzle(a, 2, 0, 1) * swizzle(b, 1, 2, 0);
|
||||
return i - j;
|
||||
}
|
||||
|
||||
blah :: proc(a: Vector3) -> f32 {
|
||||
return a.x + a.y + a.z;
|
||||
}
|
||||
|
||||
x := cross(a, b);
|
||||
fmt.println(x);
|
||||
fmt.println(blah(x));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
using println in import "core:fmt.odin"
|
||||
|
||||
using_in :: proc() {
|
||||
fmt.println("# using in");
|
||||
using print in fmt;
|
||||
|
||||
println("Hellope1");
|
||||
print("Hellope2\n");
|
||||
|
||||
Foo :: struct {
|
||||
x, y: int,
|
||||
b: bool,
|
||||
}
|
||||
f: Foo;
|
||||
f.x, f.y = 123, 321;
|
||||
println(f);
|
||||
using x, y in f;
|
||||
x, y = 456, 654;
|
||||
println(f);
|
||||
}
|
||||
|
||||
named_proc_return_parameters :: proc() {
|
||||
fmt.println("# named proc return parameters");
|
||||
|
||||
foo0 :: proc() -> int {
|
||||
return 123;
|
||||
}
|
||||
foo1 :: proc() -> (a: int) {
|
||||
a = 123;
|
||||
return;
|
||||
}
|
||||
foo2 :: proc() -> (a, b: int) {
|
||||
// Named return values act like variables within the scope
|
||||
a = 321;
|
||||
b = 567;
|
||||
return b, a;
|
||||
}
|
||||
fmt.println("foo0 =", foo0()); // 123
|
||||
fmt.println("foo1 =", foo1()); // 123
|
||||
fmt.println("foo2 =", foo2()); // 567 321
|
||||
}
|
||||
|
||||
|
||||
enum_export :: proc() {
|
||||
fmt.println("# enum #export");
|
||||
|
||||
Foo :: enum #export {A, B, C};
|
||||
|
||||
f0 := A;
|
||||
f1 := B;
|
||||
f2 := C;
|
||||
fmt.println(f0, f1, f2);
|
||||
}
|
||||
|
||||
explicit_procedure_overloading :: proc() {
|
||||
fmt.println("# explicit procedure overloading");
|
||||
|
||||
add_ints :: proc(a, b: int) -> int {
|
||||
x := a + b;
|
||||
fmt.println("add_ints", x);
|
||||
return x;
|
||||
}
|
||||
add_floats :: proc(a, b: f32) -> f32 {
|
||||
x := a + b;
|
||||
fmt.println("add_floats", x);
|
||||
return x;
|
||||
}
|
||||
add_numbers :: proc(a: int, b: f32, c: u8) -> int {
|
||||
x := int(a) + int(b) + int(c);
|
||||
fmt.println("add_numbers", x);
|
||||
return x;
|
||||
}
|
||||
|
||||
add :: proc[add_ints, add_floats, add_numbers];
|
||||
|
||||
add(int(1), int(2));
|
||||
add(f32(1), f32(2));
|
||||
add(int(1), f32(2), u8(3));
|
||||
|
||||
add(1, 2); // untyped ints coerce to int tighter than f32
|
||||
add(1.0, 2.0); // untyped floats coerce to f32 tighter than int
|
||||
add(1, 2, 3); // three parameters
|
||||
|
||||
// Ambiguous answers
|
||||
// add(1.0, 2);
|
||||
// add(1, 2.0);
|
||||
}
|
||||
|
||||
complete_switch :: proc() {
|
||||
fmt.println("# complete_switch");
|
||||
{ // enum
|
||||
Foo :: enum #export {
|
||||
A,
|
||||
B,
|
||||
C,
|
||||
D,
|
||||
}
|
||||
|
||||
b := Foo.B;
|
||||
f := Foo.A;
|
||||
#complete switch f {
|
||||
case A: fmt.println("A");
|
||||
case B: fmt.println("B");
|
||||
case C: fmt.println("C");
|
||||
case D: fmt.println("D");
|
||||
case: fmt.println("?");
|
||||
}
|
||||
}
|
||||
{ // union
|
||||
Foo :: union {int, bool};
|
||||
f: Foo = 123;
|
||||
#complete switch in f {
|
||||
case int: fmt.println("int");
|
||||
case bool: fmt.println("bool");
|
||||
case:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
main :: proc() {
|
||||
when true {
|
||||
general_stuff();
|
||||
default_struct_values();
|
||||
union_type();
|
||||
parametric_polymorphism();
|
||||
threading_example();
|
||||
array_programming();
|
||||
using_in();
|
||||
named_proc_return_parameters();
|
||||
enum_export();
|
||||
explicit_procedure_overloading();
|
||||
complete_switch();
|
||||
}
|
||||
}
|
||||
@@ -1,412 +0,0 @@
|
||||
#include "win32.odin"
|
||||
|
||||
assume :: proc(cond: bool) #foreign "llvm.assume"
|
||||
|
||||
__debug_trap :: proc() #foreign "llvm.debugtrap"
|
||||
__trap :: proc() #foreign "llvm.trap"
|
||||
read_cycle_counter :: proc() -> u64 #foreign "llvm.readcyclecounter"
|
||||
|
||||
bit_reverse16 :: proc(b: u16) -> u16 #foreign "llvm.bitreverse.i16"
|
||||
bit_reverse32 :: proc(b: u32) -> u32 #foreign "llvm.bitreverse.i32"
|
||||
bit_reverse64 :: proc(b: u64) -> u64 #foreign "llvm.bitreverse.i64"
|
||||
|
||||
byte_swap16 :: proc(b: u16) -> u16 #foreign "llvm.bswap.i16"
|
||||
byte_swap32 :: proc(b: u32) -> u32 #foreign "llvm.bswap.i32"
|
||||
byte_swap64 :: proc(b: u64) -> u64 #foreign "llvm.bswap.i64"
|
||||
|
||||
fmuladd_f32 :: proc(a, b, c: f32) -> f32 #foreign "llvm.fmuladd.f32"
|
||||
fmuladd_f64 :: proc(a, b, c: f64) -> f64 #foreign "llvm.fmuladd.f64"
|
||||
|
||||
// TODO(bill): make custom heap procedures
|
||||
heap_alloc :: proc(len: int) -> rawptr #foreign "malloc"
|
||||
heap_dealloc :: proc(ptr: rawptr) #foreign "free"
|
||||
|
||||
memory_zero :: proc(data: rawptr, len: int) {
|
||||
d := slice_ptr(data as ^byte, len)
|
||||
for i := 0; i < len; i++ {
|
||||
d[i] = 0
|
||||
}
|
||||
}
|
||||
|
||||
memory_compare :: proc(dst, src: rawptr, len: int) -> int {
|
||||
s1, s2: ^byte = dst, src
|
||||
for i := 0; i < len; i++ {
|
||||
a := ptr_offset(s1, i)^
|
||||
b := ptr_offset(s2, i)^
|
||||
if a != b {
|
||||
return (a - b) as int
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
memory_copy :: proc(dst, src: rawptr, n: int) #inline {
|
||||
if dst == src {
|
||||
return
|
||||
}
|
||||
|
||||
v128b :: type {4}u32
|
||||
#assert(align_of(v128b) == 16)
|
||||
|
||||
d, s: ^byte = dst, src
|
||||
|
||||
for ; s as uint % 16 != 0 && n != 0; n-- {
|
||||
d^ = s^
|
||||
d, s = ptr_offset(d, 1), ptr_offset(s, 1)
|
||||
}
|
||||
|
||||
if d as uint % 16 == 0 {
|
||||
for ; n >= 16; d, s, n = ptr_offset(d, 16), ptr_offset(s, 16), n-16 {
|
||||
(d as ^v128b)^ = (s as ^v128b)^
|
||||
}
|
||||
|
||||
if n&8 != 0 {
|
||||
(d as ^u64)^ = (s as ^u64)^
|
||||
d, s = ptr_offset(d, 8), ptr_offset(s, 8)
|
||||
}
|
||||
if n&4 != 0 {
|
||||
(d as ^u32)^ = (s as ^u32)^;
|
||||
d, s = ptr_offset(d, 4), ptr_offset(s, 4)
|
||||
}
|
||||
if n&2 != 0 {
|
||||
(d as ^u16)^ = (s as ^u16)^
|
||||
d, s = ptr_offset(d, 2), ptr_offset(s, 2)
|
||||
}
|
||||
if n&1 != 0 {
|
||||
d^ = s^
|
||||
d, s = ptr_offset(d, 1), ptr_offset(s, 1)
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// IMPORTANT NOTE(bill): Little endian only
|
||||
LS :: proc(a, b: u32) -> u32 #inline { return a << b }
|
||||
RS :: proc(a, b: u32) -> u32 #inline { return a >> b }
|
||||
/* NOTE(bill): Big endian version
|
||||
LS :: proc(a, b: u32) -> u32 #inline { return a >> b; }
|
||||
RS :: proc(a, b: u32) -> u32 #inline { return a << b; }
|
||||
*/
|
||||
|
||||
w, x: u32
|
||||
|
||||
if d as uint % 4 == 1 {
|
||||
w = (s as ^u32)^
|
||||
d^ = s^; d = ptr_offset(d, 1); s = ptr_offset(s, 1)
|
||||
d^ = s^; d = ptr_offset(d, 1); s = ptr_offset(s, 1)
|
||||
d^ = s^; d = ptr_offset(d, 1); s = ptr_offset(s, 1)
|
||||
n -= 3
|
||||
|
||||
for n > 16 {
|
||||
d32 := d as ^u32
|
||||
s32 := ptr_offset(s, 1) as ^u32
|
||||
x = s32^; d32^ = LS(w, 24) | RS(x, 8)
|
||||
d32, s32 = ptr_offset(d32, 1), ptr_offset(s32, 1)
|
||||
w = s32^; d32^ = LS(x, 24) | RS(w, 8)
|
||||
d32, s32 = ptr_offset(d32, 1), ptr_offset(s32, 1)
|
||||
x = s32^; d32^ = LS(w, 24) | RS(x, 8)
|
||||
d32, s32 = ptr_offset(d32, 1), ptr_offset(s32, 1)
|
||||
w = s32^; d32^ = LS(x, 24) | RS(w, 8)
|
||||
d32, s32 = ptr_offset(d32, 1), ptr_offset(s32, 1)
|
||||
|
||||
d, s, n = ptr_offset(d, 16), ptr_offset(s, 16), n-16
|
||||
}
|
||||
|
||||
} else if d as uint % 4 == 2 {
|
||||
w = (s as ^u32)^
|
||||
d^ = s^; d = ptr_offset(d, 1); s = ptr_offset(s, 1)
|
||||
d^ = s^; d = ptr_offset(d, 1); s = ptr_offset(s, 1)
|
||||
n -= 2
|
||||
|
||||
for n > 17 {
|
||||
d32 := d as ^u32
|
||||
s32 := ptr_offset(s, 2) as ^u32
|
||||
x = s32^; d32^ = LS(w, 16) | RS(x, 16)
|
||||
d32, s32 = ptr_offset(d32, 1), ptr_offset(s32, 1)
|
||||
w = s32^; d32^ = LS(x, 16) | RS(w, 16)
|
||||
d32, s32 = ptr_offset(d32, 1), ptr_offset(s32, 1)
|
||||
x = s32^; d32^ = LS(w, 16) | RS(x, 16)
|
||||
d32, s32 = ptr_offset(d32, 1), ptr_offset(s32, 1)
|
||||
w = s32^; d32^ = LS(x, 16) | RS(w, 16)
|
||||
d32, s32 = ptr_offset(d32, 1), ptr_offset(s32, 1)
|
||||
|
||||
d, s, n = ptr_offset(d, 16), ptr_offset(s, 16), n-16
|
||||
}
|
||||
|
||||
} else if d as uint % 4 == 3 {
|
||||
w = (s as ^u32)^
|
||||
d^ = s^
|
||||
n -= 1
|
||||
|
||||
for n > 18 {
|
||||
d32 := d as ^u32
|
||||
s32 := ptr_offset(s, 3) as ^u32
|
||||
x = s32^; d32^ = LS(w, 8) | RS(x, 24)
|
||||
d32, s32 = ptr_offset(d32, 1), ptr_offset(s32, 1)
|
||||
w = s32^; d32^ = LS(x, 8) | RS(w, 24)
|
||||
d32, s32 = ptr_offset(d32, 1), ptr_offset(s32, 1)
|
||||
x = s32^; d32^ = LS(w, 8) | RS(x, 24)
|
||||
d32, s32 = ptr_offset(d32, 1), ptr_offset(s32, 1)
|
||||
w = s32^; d32^ = LS(x, 8) | RS(w, 24)
|
||||
d32, s32 = ptr_offset(d32, 1), ptr_offset(s32, 1)
|
||||
|
||||
d, s, n = ptr_offset(d, 16), ptr_offset(s, 16), n-16
|
||||
}
|
||||
}
|
||||
|
||||
if n&16 != 0 {
|
||||
(d as ^v128b)^ = (s as ^v128b)^
|
||||
d, s = ptr_offset(d, 16), ptr_offset(s, 16)
|
||||
}
|
||||
if n&8 != 0 {
|
||||
(d as ^u64)^ = (s as ^u64)^
|
||||
d, s = ptr_offset(d, 8), ptr_offset(s, 8)
|
||||
}
|
||||
if n&4 != 0 {
|
||||
(d as ^u32)^ = (s as ^u32)^;
|
||||
d, s = ptr_offset(d, 4), ptr_offset(s, 4)
|
||||
}
|
||||
if n&2 != 0 {
|
||||
(d as ^u16)^ = (s as ^u16)^
|
||||
d, s = ptr_offset(d, 2), ptr_offset(s, 2)
|
||||
}
|
||||
if n&1 != 0 {
|
||||
d^ = s^
|
||||
}
|
||||
}
|
||||
|
||||
memory_move :: proc(dst, src: rawptr, n: int) #inline {
|
||||
d, s: ^byte = dst, src
|
||||
if d == s {
|
||||
return
|
||||
}
|
||||
if d >= ptr_offset(s, n) || ptr_offset(d, n) <= s {
|
||||
memory_copy(d, s, n)
|
||||
return
|
||||
}
|
||||
|
||||
// TODO(bill): Vectorize the shit out of this
|
||||
if d < s {
|
||||
if s as int % size_of(int) == d as int % size_of(int) {
|
||||
for d as int % size_of(int) != 0 {
|
||||
if n == 0 {
|
||||
return
|
||||
}
|
||||
n--
|
||||
d^ = s^
|
||||
d, s = ptr_offset(d, 1), ptr_offset(s, 1)
|
||||
}
|
||||
di, si := d as ^int, s as ^int
|
||||
for n >= size_of(int) {
|
||||
di^ = si^
|
||||
di, si = ptr_offset(di, 1), ptr_offset(si, 1)
|
||||
n -= size_of(int)
|
||||
}
|
||||
}
|
||||
for ; n > 0; n-- {
|
||||
d^ = s^
|
||||
d, s = ptr_offset(d, 1), ptr_offset(s, 1)
|
||||
}
|
||||
} else {
|
||||
if s as int % size_of(int) == d as int % size_of(int) {
|
||||
for ptr_offset(d, n) as int % size_of(int) != 0 {
|
||||
if n == 0 {
|
||||
return
|
||||
}
|
||||
n--
|
||||
d^ = s^
|
||||
d, s = ptr_offset(d, 1), ptr_offset(s, 1)
|
||||
}
|
||||
for n >= size_of(int) {
|
||||
n -= size_of(int)
|
||||
di := ptr_offset(d, n) as ^int
|
||||
si := ptr_offset(s, n) as ^int
|
||||
di^ = si^
|
||||
}
|
||||
for ; n > 0; n-- {
|
||||
d^ = s^
|
||||
d, s = ptr_offset(d, 1), ptr_offset(s, 1)
|
||||
}
|
||||
}
|
||||
for n > 0 {
|
||||
n--
|
||||
dn := ptr_offset(d, n)
|
||||
sn := ptr_offset(s, n)
|
||||
dn^ = sn^
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
__string_eq :: proc(a, b: string) -> bool {
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
}
|
||||
if ^a[0] == ^b[0] {
|
||||
return true
|
||||
}
|
||||
return memory_compare(^a[0], ^b[0], len(a)) == 0
|
||||
}
|
||||
|
||||
__string_cmp :: proc(a, b : string) -> int {
|
||||
min_len := len(a)
|
||||
if len(b) < min_len {
|
||||
min_len = len(b)
|
||||
}
|
||||
for i := 0; i < min_len; i++ {
|
||||
x := a[i]
|
||||
y := b[i]
|
||||
if x < y {
|
||||
return -1
|
||||
} else if x > y {
|
||||
return +1
|
||||
}
|
||||
}
|
||||
|
||||
if len(a) < len(b) {
|
||||
return -1
|
||||
} else if len(a) > len(b) {
|
||||
return +1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
__string_ne :: proc(a, b : string) -> bool #inline { return !__string_eq(a, b) }
|
||||
__string_lt :: proc(a, b : string) -> bool #inline { return __string_cmp(a, b) < 0 }
|
||||
__string_gt :: proc(a, b : string) -> bool #inline { return __string_cmp(a, b) > 0 }
|
||||
__string_le :: proc(a, b : string) -> bool #inline { return __string_cmp(a, b) <= 0 }
|
||||
__string_ge :: proc(a, b : string) -> bool #inline { return __string_cmp(a, b) >= 0 }
|
||||
|
||||
|
||||
|
||||
|
||||
Allocation_Mode :: type enum {
|
||||
ALLOC,
|
||||
DEALLOC,
|
||||
DEALLOC_ALL,
|
||||
RESIZE,
|
||||
}
|
||||
|
||||
|
||||
|
||||
Allocator_Proc :: type proc(allocator_data: rawptr, mode: Allocation_Mode,
|
||||
size, alignment: int,
|
||||
old_memory: rawptr, old_size: int, flags: u64) -> rawptr
|
||||
|
||||
Allocator :: type struct {
|
||||
procedure: Allocator_Proc;
|
||||
data: rawptr
|
||||
}
|
||||
|
||||
|
||||
Context :: type struct {
|
||||
thread_ptr: rawptr
|
||||
|
||||
user_data: rawptr
|
||||
user_index: int
|
||||
|
||||
allocator: Allocator
|
||||
}
|
||||
|
||||
#thread_local context: Context
|
||||
|
||||
DEFAULT_ALIGNMENT :: 2*size_of(int)
|
||||
|
||||
|
||||
__check_context :: proc() {
|
||||
if context.allocator.procedure == null {
|
||||
context.allocator = __default_allocator()
|
||||
}
|
||||
if context.thread_ptr == null {
|
||||
// TODO(bill):
|
||||
// context.thread_ptr = current_thread_pointer()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
alloc :: proc(size: int) -> rawptr #inline { return alloc_align(size, DEFAULT_ALIGNMENT) }
|
||||
|
||||
alloc_align :: proc(size, alignment: int) -> rawptr #inline {
|
||||
__check_context()
|
||||
a := context.allocator
|
||||
return a.procedure(a.data, Allocation_Mode.ALLOC, size, alignment, null, 0, 0)
|
||||
}
|
||||
|
||||
dealloc :: proc(ptr: rawptr) #inline {
|
||||
__check_context()
|
||||
a := context.allocator
|
||||
_ = a.procedure(a.data, Allocation_Mode.DEALLOC, 0, 0, ptr, 0, 0)
|
||||
}
|
||||
dealloc_all :: proc(ptr: rawptr) #inline {
|
||||
__check_context()
|
||||
a := context.allocator
|
||||
_ = a.procedure(a.data, Allocation_Mode.DEALLOC_ALL, 0, 0, ptr, 0, 0)
|
||||
}
|
||||
|
||||
|
||||
resize :: proc(ptr: rawptr, old_size, new_size: int) -> rawptr #inline { return resize_align(ptr, old_size, new_size, DEFAULT_ALIGNMENT) }
|
||||
resize_align :: proc(ptr: rawptr, old_size, new_size, alignment: int) -> rawptr #inline {
|
||||
__check_context()
|
||||
a := context.allocator
|
||||
return a.procedure(a.data, Allocation_Mode.RESIZE, new_size, alignment, ptr, old_size, 0)
|
||||
}
|
||||
|
||||
|
||||
|
||||
default_resize_align :: proc(old_memory: rawptr, old_size, new_size, alignment: int) -> rawptr {
|
||||
if old_memory == null {
|
||||
return alloc_align(new_size, alignment)
|
||||
}
|
||||
|
||||
if new_size == 0 {
|
||||
dealloc(old_memory)
|
||||
return null
|
||||
}
|
||||
|
||||
if new_size == old_size {
|
||||
return old_memory
|
||||
}
|
||||
|
||||
new_memory := alloc_align(new_size, alignment)
|
||||
if new_memory == null {
|
||||
return null
|
||||
}
|
||||
|
||||
memory_copy(new_memory, old_memory, min(old_size, new_size));
|
||||
dealloc(old_memory)
|
||||
return new_memory
|
||||
}
|
||||
|
||||
|
||||
__default_allocator_proc :: proc(allocator_data: rawptr, mode: Allocation_Mode,
|
||||
size, alignment: int,
|
||||
old_memory: rawptr, old_size: int, flags: u64) -> rawptr {
|
||||
using Allocation_Mode
|
||||
match mode {
|
||||
case ALLOC:
|
||||
return heap_alloc(size)
|
||||
case RESIZE:
|
||||
return default_resize_align(old_memory, old_size, size, alignment)
|
||||
case DEALLOC:
|
||||
heap_dealloc(old_memory)
|
||||
case DEALLOC_ALL:
|
||||
// NOTE(bill): Does nothing
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
__default_allocator :: proc() -> Allocator {
|
||||
return Allocator{
|
||||
__default_allocator_proc,
|
||||
null,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
__assert :: proc(msg: string) {
|
||||
file_write(file_get_standard(File_Standard.ERROR), msg as []byte)
|
||||
// TODO(bill): Which is better?
|
||||
// __trap()
|
||||
__debug_trap()
|
||||
}
|
||||
@@ -1,430 +0,0 @@
|
||||
import (
|
||||
"fmt.odin";
|
||||
"atomics.odin";
|
||||
"bits.odin";
|
||||
"decimal.odin";
|
||||
"hash.odin";
|
||||
"math.odin";
|
||||
"mem.odin";
|
||||
"opengl.odin";
|
||||
"os.odin";
|
||||
"raw.odin";
|
||||
"strconv.odin";
|
||||
"strings.odin";
|
||||
"sync.odin";
|
||||
"sort.odin";
|
||||
"types.odin";
|
||||
"utf8.odin";
|
||||
"utf16.odin";
|
||||
/*
|
||||
*/
|
||||
)
|
||||
|
||||
|
||||
general_stuff :: proc() {
|
||||
// Complex numbers
|
||||
a := 3 + 4i;
|
||||
b: complex64 = 3 + 4i;
|
||||
c: complex128 = 3 + 4i;
|
||||
d := complex(2, 3);
|
||||
|
||||
e := a / conj(a);
|
||||
fmt.println("(3+4i)/(3-4i) =", e);
|
||||
fmt.println(real(e), "+", imag(e), "i");
|
||||
|
||||
|
||||
// C-style variadic procedures
|
||||
foreign __llvm_core {
|
||||
// The variadic part allows for extra type checking too which C does not provide
|
||||
c_printf :: proc(fmt: ^u8, #c_vararg args: ..any) -> i32 #link_name "printf" ---;
|
||||
}
|
||||
str := "%d\n\x00";
|
||||
// c_printf(&str[0], i32(789456123));
|
||||
|
||||
|
||||
Foo :: struct {
|
||||
x: int;
|
||||
y: f32;
|
||||
z: string;
|
||||
}
|
||||
foo := Foo{123, 0.513, "A string"};
|
||||
x, y, z := expand_to_tuple(foo);
|
||||
fmt.println(x, y, z);
|
||||
#assert(type_of(x) == int);
|
||||
#assert(type_of(y) == f32);
|
||||
#assert(type_of(z) == string);
|
||||
|
||||
|
||||
// By default, all variables are zeroed
|
||||
// This can be overridden with the "uninitialized value"
|
||||
// This is similar to `nil` but applied to everything
|
||||
undef_int: int = ---;
|
||||
|
||||
|
||||
// Context system is now implemented using Implicit Parameter Passing (IPP)
|
||||
// The previous implementation was Thread Local Storage (TLS)
|
||||
// IPP has the advantage that it works on systems without TLS and that you can
|
||||
// link the context to the stack frame and thus look at previous contexts
|
||||
//
|
||||
// It does mean that a pointer is implicitly passed procedures with the default
|
||||
// Odin calling convention (#cc_odin)
|
||||
// This can be overridden with something like #cc_contextless or #cc_c if performance
|
||||
// is worried about
|
||||
|
||||
}
|
||||
|
||||
foreign_blocks :: proc() {
|
||||
// See sys/windows.odin
|
||||
}
|
||||
|
||||
default_arguments :: proc() {
|
||||
hello :: proc(a: int = 9, b: int = 9) do fmt.printf("a is %d; b is %d\n", a, b);
|
||||
fmt.println("\nTesting default arguments:");
|
||||
hello(1, 2);
|
||||
hello(1);
|
||||
hello();
|
||||
}
|
||||
|
||||
named_arguments :: proc() {
|
||||
Colour :: enum {
|
||||
Red,
|
||||
Orange,
|
||||
Yellow,
|
||||
Green,
|
||||
Blue,
|
||||
Octarine,
|
||||
};
|
||||
using Colour;
|
||||
|
||||
make_character :: proc(name, catch_phrase: string, favourite_colour, least_favourite_colour: Colour) {
|
||||
fmt.println();
|
||||
fmt.printf("My name is %v and I like %v. %v\n", name, favourite_colour, catch_phrase);
|
||||
}
|
||||
|
||||
make_character("Frank", "¡Ay, caramba!", Blue, Green);
|
||||
|
||||
|
||||
// As the procedures have more and more parameters, it is very easy
|
||||
// to get many of the arguments in the wrong order especialy if the
|
||||
// types are the same
|
||||
make_character("¡Ay, caramba!", "Frank", Green, Blue);
|
||||
|
||||
// Named arguments help to disambiguate this problem
|
||||
make_character(catch_phrase = "¡Ay, caramba!", name = "Frank",
|
||||
least_favourite_colour = Green, favourite_colour = Blue);
|
||||
|
||||
|
||||
// The named arguments can be specifed in any order.
|
||||
make_character(favourite_colour = Octarine, catch_phrase = "U wot m8!",
|
||||
least_favourite_colour = Green, name = "Dennis");
|
||||
|
||||
|
||||
// NOTE: You cannot mix named arguments with normal values
|
||||
/*
|
||||
make_character("Dennis",
|
||||
favourite_colour = Octarine, catch_phrase = "U wot m8!",
|
||||
least_favourite_colour = Green);
|
||||
*/
|
||||
|
||||
|
||||
// Named arguments can also aid with default arguments
|
||||
numerous_things :: proc(s: string, a := 1, b := 2, c := 3.14,
|
||||
d := "The Best String!", e := false, f := 10.3/3.1, g := false) {
|
||||
g_str := g ? "true" : "false";
|
||||
fmt.printf("How many?! %s: %v\n", s, g_str);
|
||||
}
|
||||
|
||||
numerous_things("First");
|
||||
numerous_things(s = "Second", g = true);
|
||||
|
||||
|
||||
// Default values can be placed anywhere, not just at the end like in other languages
|
||||
weird :: proc(pre: string, mid: int = 0, post: string) {
|
||||
fmt.println(pre, mid, post);
|
||||
}
|
||||
|
||||
weird("How many things", 42, "huh?");
|
||||
weird(pre = "Prefix", post = "Pat");
|
||||
|
||||
}
|
||||
|
||||
|
||||
default_return_values :: proc() {
|
||||
foo :: proc(x: int) -> (first: string = "Hellope", second := "world!") {
|
||||
match x {
|
||||
case 0: return;
|
||||
case 1: return "Goodbye";
|
||||
case 2: return "Goodbye", "cruel world..";
|
||||
case 3: return second = "cruel world..", first = "Goodbye";
|
||||
}
|
||||
|
||||
return second = "my old friend.";
|
||||
}
|
||||
|
||||
fmt.printf("%s %s\n", foo(0));
|
||||
fmt.printf("%s %s\n", foo(1));
|
||||
fmt.printf("%s %s\n", foo(2));
|
||||
fmt.printf("%s %s\n", foo(3));
|
||||
fmt.printf("%s %s\n", foo(4));
|
||||
fmt.println();
|
||||
|
||||
|
||||
// A more "real" example
|
||||
Error :: enum {
|
||||
None,
|
||||
WhyTheNumberThree,
|
||||
TenIsTooBig,
|
||||
};
|
||||
|
||||
Entity :: struct {
|
||||
name: string;
|
||||
id: u32;
|
||||
}
|
||||
|
||||
some_thing :: proc(input: int) -> (result: ^Entity = nil, err := Error.None) {
|
||||
match {
|
||||
case input == 3: return err = Error.WhyTheNumberThree;
|
||||
case input >= 10: return err = Error.TenIsTooBig;
|
||||
}
|
||||
|
||||
e := new(Entity);
|
||||
e.id = u32(input);
|
||||
|
||||
return result = e;
|
||||
}
|
||||
}
|
||||
|
||||
call_location :: proc() {
|
||||
amazing :: proc(n: int, using loc := #caller_location) {
|
||||
fmt.printf("%s(%d:%d) just asked to do something amazing.\n",
|
||||
fully_pathed_filename, line, column);
|
||||
fmt.printf("Normal -> %d\n", n);
|
||||
fmt.printf("Amazing -> %d\n", n+1);
|
||||
fmt.println();
|
||||
}
|
||||
|
||||
loc := #location(main);
|
||||
fmt.println("`main` is located at", loc);
|
||||
|
||||
fmt.println("This line is located at", #location());
|
||||
fmt.println();
|
||||
|
||||
amazing(3);
|
||||
amazing(4, #location(call_location));
|
||||
|
||||
// See _preload.odin for the implementations of `assert` and `panic`
|
||||
|
||||
}
|
||||
|
||||
|
||||
explicit_parametric_polymorphic_procedures :: proc() {
|
||||
// This is how `new` is actually implemented, see _preload.odin
|
||||
alloc_type :: proc(T: type) -> ^T do return cast(^T)alloc(size_of(T), align_of(T));
|
||||
|
||||
int_ptr := alloc_type(int);
|
||||
defer free(int_ptr);
|
||||
int_ptr^ = 137;
|
||||
fmt.println(int_ptr, int_ptr^);
|
||||
|
||||
// Named arguments work too!
|
||||
another_ptr := alloc_type(T = f32);
|
||||
defer free(another_ptr);
|
||||
|
||||
|
||||
add :: proc(T: type, args: ..T) -> T {
|
||||
res: T;
|
||||
for arg in args do res += arg;
|
||||
return res;
|
||||
}
|
||||
|
||||
fmt.println("add =", add(int, 1, 2, 3, 4, 5, 6));
|
||||
|
||||
swap :: proc(T: type, a, b: ^T) {
|
||||
tmp := a^;
|
||||
a^ = b^;
|
||||
b^ = tmp;
|
||||
}
|
||||
|
||||
a, b: int = 3, 4;
|
||||
fmt.println("Pre-swap:", a, b);
|
||||
swap(int, &a, &b);
|
||||
fmt.println("Post-swap:", a, b);
|
||||
a, b = b, a; // Or use this syntax for this silly example case
|
||||
|
||||
|
||||
Vector2 :: struct {x, y: f32;};
|
||||
{
|
||||
// A more complicated example using subtyping
|
||||
// Something like this could be used in a game
|
||||
|
||||
Entity :: struct {
|
||||
using position: Vector2;
|
||||
flags: u64;
|
||||
id: u64;
|
||||
derived: any;
|
||||
}
|
||||
|
||||
Rock :: struct {
|
||||
using entity: Entity;
|
||||
heavy: bool;
|
||||
}
|
||||
Door :: struct {
|
||||
using entity: Entity;
|
||||
open: bool;
|
||||
}
|
||||
Monster :: struct {
|
||||
using entity: Entity;
|
||||
is_robot: bool;
|
||||
is_zombie: bool;
|
||||
}
|
||||
|
||||
new_entity :: proc(T: type, x, y: f32) -> ^T {
|
||||
result := new(T);
|
||||
result.derived = result^;
|
||||
result.x = x;
|
||||
result.y = y;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
entities: [dynamic]^Entity;
|
||||
|
||||
rock := new_entity(Rock, 3, 5);
|
||||
|
||||
// Named arguments work too!
|
||||
door := new_entity(T = Door, x = 3, y = 6);
|
||||
|
||||
// And named arguments can be any order
|
||||
monster := new_entity(
|
||||
y = 1,
|
||||
x = 2,
|
||||
T = Monster,
|
||||
);
|
||||
|
||||
append(&entities, rock, door, monster);
|
||||
|
||||
fmt.println("Subtyping");
|
||||
for entity in entities {
|
||||
match e in entity.derived {
|
||||
case Rock: fmt.println("Rock", e.x, e.y);
|
||||
case Door: fmt.println("Door", e.x, e.y);
|
||||
case Monster: fmt.println("Monster", e.x, e.y);
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
Entity :: struct {
|
||||
using position: Vector2;
|
||||
flags: u64;
|
||||
id: u64;
|
||||
variant: union { Rock, Door, Monster };
|
||||
}
|
||||
|
||||
Rock :: struct {
|
||||
using entity: ^Entity;
|
||||
heavy: bool;
|
||||
}
|
||||
Door :: struct {
|
||||
using entity: ^Entity;
|
||||
open: bool;
|
||||
}
|
||||
Monster :: struct {
|
||||
using entity: ^Entity;
|
||||
is_robot: bool;
|
||||
is_zombie: bool;
|
||||
}
|
||||
|
||||
new_entity :: proc(T: type, x, y: f32) -> ^T {
|
||||
result := new(Entity);
|
||||
result.variant = T{entity = result};
|
||||
result.x = x;
|
||||
result.y = y;
|
||||
|
||||
return cast(^T)&result.variant;
|
||||
}
|
||||
|
||||
entities: [dynamic]^Entity;
|
||||
|
||||
rock := new_entity(Rock, 3, 5);
|
||||
|
||||
// Named arguments work too!
|
||||
door := new_entity(T = Door, x = 3, y = 6);
|
||||
|
||||
// And named arguments can be any order
|
||||
monster := new_entity(
|
||||
y = 1,
|
||||
x = 2,
|
||||
T = Monster,
|
||||
);
|
||||
|
||||
append(&entities, rock, door, monster);
|
||||
|
||||
fmt.println("Union");
|
||||
for entity in entities {
|
||||
match e in entity.variant {
|
||||
case Rock: fmt.println("Rock", e.x, e.y);
|
||||
case Door: fmt.println("Door", e.x, e.y);
|
||||
case Monster: fmt.println("Monster", e.x, e.y);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
implicit_polymorphic_assignment :: proc() {
|
||||
yep :: proc(p: proc(x: int)) {
|
||||
p(123);
|
||||
}
|
||||
|
||||
frank :: proc(x: $T) do fmt.println("frank ->", x);
|
||||
tim :: proc(x, y: $T) do fmt.println("tim ->", x, y);
|
||||
yep(frank);
|
||||
// yep(tim);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
main :: proc() {
|
||||
/*
|
||||
foo :: proc(x: i64, y: f32) do fmt.println("#1", x, y);
|
||||
foo :: proc(x: type, y: f32) do fmt.println("#2", type_info(x), y);
|
||||
foo :: proc(x: type) do fmt.println("#3", type_info(x));
|
||||
|
||||
f :: foo;
|
||||
|
||||
f(y = 3785.1546, x = 123);
|
||||
f(x = int, y = 897.513);
|
||||
f(x = f32);
|
||||
|
||||
general_stuff();
|
||||
foreign_blocks();
|
||||
default_arguments();
|
||||
named_arguments();
|
||||
default_return_values();
|
||||
call_location();
|
||||
explicit_parametric_polymorphic_procedures();
|
||||
implicit_polymorphic_assignment();
|
||||
|
||||
|
||||
// Command line argument(s)!
|
||||
// -opt=0,1,2,3
|
||||
*/
|
||||
/*
|
||||
program := "+ + * - /";
|
||||
accumulator := 0;
|
||||
|
||||
for token in program {
|
||||
match token {
|
||||
case '+': accumulator += 1;
|
||||
case '-': accumulator -= 1;
|
||||
case '*': accumulator *= 2;
|
||||
case '/': accumulator /= 2;
|
||||
case: // Ignore everything else
|
||||
}
|
||||
}
|
||||
|
||||
fmt.printf("The program \"%s\" calculates the value %d\n",
|
||||
program, accumulator);
|
||||
*/
|
||||
}
|
||||
@@ -275,6 +275,7 @@ struct BuildContext {
|
||||
bool no_output_files;
|
||||
bool no_crt;
|
||||
bool no_entry_point;
|
||||
bool no_thread_local;
|
||||
bool use_lld;
|
||||
bool vet;
|
||||
bool vet_extra;
|
||||
@@ -1255,7 +1256,7 @@ gb_internal void init_build_context(TargetMetrics *cross_target) {
|
||||
gb_exit(1);
|
||||
}
|
||||
|
||||
bc->optimization_level = gb_clamp(bc->optimization_level, 0, 3);
|
||||
bc->optimization_level = gb_clamp(bc->optimization_level, -1, 2);
|
||||
|
||||
// ENFORCE DYNAMIC MAP CALLS
|
||||
bc->dynamic_map_calls = true;
|
||||
@@ -1369,6 +1370,7 @@ gb_internal char const *target_features_set_to_cstring(gbAllocator allocator, bo
|
||||
gb_memmove(features + len, feature.text, feature.len);
|
||||
len += feature.len;
|
||||
if (with_quotes) features[len++] = '"';
|
||||
i += 1;
|
||||
}
|
||||
features[len++] = 0;
|
||||
|
||||
|
||||
+4
-1
@@ -1143,9 +1143,12 @@ gb_internal void check_global_variable_decl(CheckerContext *ctx, Entity *&e, Ast
|
||||
|
||||
if (is_arch_wasm() && e->Variable.thread_local_model.len != 0) {
|
||||
e->Variable.thread_local_model.len = 0;
|
||||
// NOTE(bill): ignore this message for the time begin
|
||||
// NOTE(bill): ignore this message for the time being
|
||||
// error(e->token, "@(thread_local) is not supported for this target platform");
|
||||
}
|
||||
if(build_context.no_thread_local) {
|
||||
e->Variable.thread_local_model.len = 0;
|
||||
}
|
||||
|
||||
String context_name = str_lit("variable declaration");
|
||||
|
||||
|
||||
+12
-5
@@ -6800,14 +6800,21 @@ gb_internal ExprKind check_call_expr(CheckerContext *c, Operand *operand, Ast *c
|
||||
operand->type = t_invalid;
|
||||
}
|
||||
} else {
|
||||
gbString str = type_to_string(t);
|
||||
defer (gb_string_free(str));
|
||||
|
||||
operand->mode = Addressing_Invalid;
|
||||
isize arg_count = args.count;
|
||||
switch (arg_count) {
|
||||
case 0: error(call, "Missing argument in conversion to '%s'", str); break;
|
||||
default: error(call, "Too many arguments in conversion to '%s'", str); break;
|
||||
case 0:
|
||||
{
|
||||
gbString str = type_to_string(t);
|
||||
error(call, "Missing argument in conversion to '%s'", str);
|
||||
gb_string_free(str);
|
||||
} break;
|
||||
default:
|
||||
{
|
||||
gbString str = type_to_string(t);
|
||||
error(call, "Too many arguments in conversion to '%s'", str);
|
||||
gb_string_free(str);
|
||||
} break;
|
||||
case 1: {
|
||||
Ast *arg = args[0];
|
||||
if (arg->kind == Ast_FieldValue) {
|
||||
|
||||
+26
-2
@@ -1184,6 +1184,8 @@ gb_internal void check_type_switch_stmt(CheckerContext *ctx, Ast *node, u32 mod_
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
Ast *nil_seen = nullptr;
|
||||
PtrSet<Type *> seen = {};
|
||||
defer (ptr_set_destroy(&seen));
|
||||
|
||||
@@ -1194,6 +1196,7 @@ gb_internal void check_type_switch_stmt(CheckerContext *ctx, Ast *node, u32 mod_
|
||||
}
|
||||
ast_node(cc, CaseClause, stmt);
|
||||
|
||||
bool saw_nil = false;
|
||||
// TODO(bill): Make robust
|
||||
Type *bt = base_type(type_deref(x.type));
|
||||
|
||||
@@ -1202,6 +1205,25 @@ gb_internal void check_type_switch_stmt(CheckerContext *ctx, Ast *node, u32 mod_
|
||||
if (type_expr != nullptr) { // Otherwise it's a default expression
|
||||
Operand y = {};
|
||||
check_expr_or_type(ctx, &y, type_expr);
|
||||
|
||||
if (is_operand_nil(y)) {
|
||||
if (!type_has_nil(type_deref(x.type))) {
|
||||
error(type_expr, "'nil' case is not allowed for the type '%s'", type_to_string(type_deref(x.type)));
|
||||
continue;
|
||||
}
|
||||
saw_nil = true;
|
||||
|
||||
if (nil_seen) {
|
||||
ERROR_BLOCK();
|
||||
error(type_expr, "'nil' case has already been handled previously");
|
||||
error_line("\t 'nil' was already previously seen at %s", token_pos_to_string(ast_token(nil_seen).pos));
|
||||
} else {
|
||||
nil_seen = type_expr;
|
||||
}
|
||||
case_type = y.type;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (y.mode != Addressing_Type) {
|
||||
gbString str = expr_to_string(type_expr);
|
||||
error(type_expr, "Expected a type as a case, got %s", str);
|
||||
@@ -1255,14 +1277,16 @@ gb_internal void check_type_switch_stmt(CheckerContext *ctx, Ast *node, u32 mod_
|
||||
is_reference = true;
|
||||
}
|
||||
|
||||
if (cc->list.count > 1) {
|
||||
if (cc->list.count > 1 || saw_nil) {
|
||||
case_type = nullptr;
|
||||
}
|
||||
if (case_type == nullptr) {
|
||||
case_type = x.type;
|
||||
}
|
||||
if (switch_kind == TypeSwitch_Any) {
|
||||
add_type_info_type(ctx, case_type);
|
||||
if (!is_type_untyped(case_type)) {
|
||||
add_type_info_type(ctx, case_type);
|
||||
}
|
||||
}
|
||||
|
||||
check_open_scope(ctx, stmt);
|
||||
|
||||
@@ -674,6 +674,10 @@ gb_internal void check_union_type(CheckerContext *ctx, Type *union_type, Ast *no
|
||||
for_array(i, ut->variants) {
|
||||
Ast *node = ut->variants[i];
|
||||
Type *t = check_type_expr(ctx, node, nullptr);
|
||||
if (union_type->Union.is_polymorphic && poly_operands == nullptr) {
|
||||
// NOTE(bill): don't add any variants if this is this is an unspecialized polymorphic record
|
||||
continue;
|
||||
}
|
||||
if (t != nullptr && t != t_invalid) {
|
||||
bool ok = true;
|
||||
t = default_type(t);
|
||||
@@ -686,8 +690,12 @@ gb_internal void check_union_type(CheckerContext *ctx, Type *union_type, Ast *no
|
||||
for_array(j, variants) {
|
||||
if (are_types_identical(t, variants[j])) {
|
||||
ok = false;
|
||||
ERROR_BLOCK();
|
||||
gbString str = type_to_string(t);
|
||||
error(node, "Duplicate variant type '%s'", str);
|
||||
if (j < ut->variants.count) {
|
||||
error_line("\tPrevious found at %s\n", token_pos_to_string(ast_token(ut->variants[j]).pos));
|
||||
}
|
||||
gb_string_free(str);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -4447,6 +4447,14 @@ gb_internal DECL_ATTRIBUTE_PROC(foreign_import_decl_attribute) {
|
||||
ac->foreign_import_priority_index = exact_value_to_i64(ev);
|
||||
}
|
||||
return true;
|
||||
} else if (name == "extra_linker_flags") {
|
||||
ExactValue ev = check_decl_attribute_value(c, value);
|
||||
if (ev.kind != ExactValue_String) {
|
||||
error(elem, "Expected a string value for '%.*s'", LIT(name));
|
||||
} else {
|
||||
ac->extra_linker_flags = ev.value_string;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -4506,6 +4514,10 @@ gb_internal void check_add_foreign_import_decl(CheckerContext *ctx, Ast *decl) {
|
||||
if (ac.foreign_import_priority_index != 0) {
|
||||
e->LibraryName.priority_index = ac.foreign_import_priority_index;
|
||||
}
|
||||
String extra_linker_flags = string_trim_whitespace(ac.extra_linker_flags);
|
||||
if (extra_linker_flags.len != 0) {
|
||||
e->LibraryName.extra_linker_flags = extra_linker_flags;
|
||||
}
|
||||
|
||||
if (has_asm_extension(fullpath)) {
|
||||
if (build_context.metrics.arch != TargetArch_amd64 ||
|
||||
|
||||
@@ -121,6 +121,7 @@ struct AttributeContext {
|
||||
bool set_cold : 1;
|
||||
u32 optimization_mode; // ProcedureOptimizationMode
|
||||
i64 foreign_import_priority_index;
|
||||
String extra_linker_flags;
|
||||
|
||||
String objc_class;
|
||||
String objc_name;
|
||||
|
||||
+5
-10
@@ -60,7 +60,6 @@ gb_internal void virtual_memory_dealloc(MemoryBlock *block);
|
||||
gb_internal void *arena_alloc(Arena *arena, isize min_size, isize alignment);
|
||||
gb_internal void arena_free_all(Arena *arena);
|
||||
|
||||
|
||||
gb_internal isize arena_align_forward_offset(Arena *arena, isize alignment) {
|
||||
isize alignment_offset = 0;
|
||||
isize ptr = cast(isize)(arena->curr_block->base + arena->curr_block->used);
|
||||
@@ -75,7 +74,7 @@ gb_internal void *arena_alloc(Arena *arena, isize min_size, isize alignment) {
|
||||
GB_ASSERT(gb_is_power_of_two(alignment));
|
||||
|
||||
mutex_lock(&arena->mutex);
|
||||
|
||||
|
||||
isize size = 0;
|
||||
if (arena->curr_block != nullptr) {
|
||||
size = min_size + arena_align_forward_offset(arena, alignment);
|
||||
@@ -390,15 +389,11 @@ gb_internal bool IS_ODIN_DEBUG(void);
|
||||
gb_internal GB_ALLOCATOR_PROC(heap_allocator_proc);
|
||||
|
||||
|
||||
gb_global gb_thread_local Arena heap_arena = {nullptr, DEFAULT_MINIMUM_BLOCK_SIZE};
|
||||
gb_internal gbAllocator heap_allocator(void) {
|
||||
if (IS_ODIN_DEBUG()) {
|
||||
gbAllocator a;
|
||||
a.proc = heap_allocator_proc;
|
||||
a.data = nullptr;
|
||||
return a;
|
||||
}
|
||||
return arena_allocator(&heap_arena);
|
||||
gbAllocator a;
|
||||
a.proc = heap_allocator_proc;
|
||||
a.data = nullptr;
|
||||
return a;
|
||||
}
|
||||
|
||||
|
||||
|
||||
+7
-5
@@ -915,18 +915,20 @@ gb_internal void odin_doc_update_entities(OdinDocWriter *w) {
|
||||
auto entities = array_make<Entity *>(heap_allocator(), 0, w->entity_cache.count);
|
||||
defer (array_free(&entities));
|
||||
|
||||
for (auto const &entry : w->entity_cache) {
|
||||
array_add(&entities, entry.key);
|
||||
for (u32 i = 0; i < w->entity_cache.count; i++) {
|
||||
Entity *e = w->entity_cache.entries[i].key;
|
||||
array_add(&entities, e);
|
||||
}
|
||||
for (Entity *e : entities) {
|
||||
GB_ASSERT(e != nullptr);
|
||||
OdinDocTypeIndex type_index = odin_doc_type(w, e->type);
|
||||
gb_unused(type_index);
|
||||
}
|
||||
}
|
||||
|
||||
for (auto const &entry : w->entity_cache) {
|
||||
Entity *e = entry.key;
|
||||
OdinDocEntityIndex entity_index = entry.value;
|
||||
for (u32 i = 0; i < w->entity_cache.count; i++) {
|
||||
Entity *e = w->entity_cache.entries[i].key;
|
||||
OdinDocEntityIndex entity_index = w->entity_cache.entries[i].value;
|
||||
OdinDocTypeIndex type_index = odin_doc_type(w, e->type);
|
||||
|
||||
OdinDocEntityIndex foreign_library = 0;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user