Compare commits

...

303 Commits

Author SHA1 Message Date
gingerBill adcaace03c Fix allow_field_separator for foreign import 2023-04-03 21:09:26 +01:00
Jeroen van Rijn f205df1996 Merge pull request #2436 from Lperlind/batch-fail
Make tests scripts error if a test fails
2023-04-03 08:56:43 +02:00
Lucas Perlind c59ad24856 Make tests scripts error if a test fails
Additionally fixes tests that were found broken because
of this.
2023-04-03 16:49:14 +10:00
Jeroen van Rijn 2b9b0ac62e Merge pull request #2434 from Lperlind/documentation/stylistic-changes
Small improvements strings documentation
2023-04-03 08:37:48 +02:00
Lucas Perlind 67e6f57192 Small improvements strings documentation
* Use new 'Returns:' and 'Inputs:' keywords used by the website generator
* Make order item order resemble website, i.e. 'Returns:' comes before
  'Example:'
* Add a few missing input items
* Add a few missing return items
2023-04-03 08:44:14 +10:00
Jeroen van Rijn 24ddb8506f Merge pull request #2430 from Lperlind/documentation/enforced_names
Enforce example names in documentation
2023-04-01 08:36:37 +02:00
Lucas Perlind 6ff0cc0b40 Enforce example names in documentation 2023-04-01 09:13:15 +11:00
Jeroen van Rijn 7620fe1ac6 Merge pull request #2429 from Lperlind/master
Add documentation tester and make it a part of CI workflow
2023-03-30 09:45:45 +02:00
Lucas Perlind 22e0f5ecd0 Add documentation tester and make it apart of CI workflow 2023-03-30 18:14:57 +11:00
Jeroen van Rijn fce2042375 Merge pull request #2428 from elusivePorpoise/pr2428
relocations and add some error consts to winmm
2023-03-29 22:18:55 +02:00
Elusive Porpoise 57594153a1 relocations and add some error consts to winmm
Summary:

Test Plan:
2023-03-29 13:06:42 -07:00
Jeroen van Rijn ff93ea5bf1 Merge pull request #2426 from elusivePorpoise/pr2426
Add TIMECAPS stuff
2023-03-29 22:05:26 +02:00
Elusive Porpoise 4a54676f31 Add TIMECAPS stuff
Summary:

Test Plan:
2023-03-29 12:10:44 -07:00
Jeroen van Rijn 0d900521bc Merge pull request #2424 from Naught00/master
Change help text to output to stdout
2023-03-29 17:34:01 +02:00
Mark Naughton bd7ffcc048 Change help text to output to stdout 2023-03-29 16:30:02 +01:00
gingerBill 2f771bee7b Merge pull request #2412 from oskarnp/text_table
text/table: Initial implementation
2023-03-29 15:08:33 +01:00
Jeroen van Rijn ae5214c1f2 Merge pull request #2422 from Lperlind/documentation-fixup
Fix website formatting and incorrect examples
2023-03-29 07:29:46 +02:00
Lucas Perlind 84d8798ad3 Fix website formatting and incorrect examples 2023-03-29 12:19:05 +11:00
Jeroen van Rijn ab7e1e01de Merge pull request #2420 from jon-lipstate/string_docs
string code docs
2023-03-28 21:05:13 +02:00
Jon Lipstate bbafc3fbd6 harmonize to use null for c-string endings 2023-03-28 11:57:12 -07:00
Jon Lipstate 194fa7cd98 rename nul to null, allocation clarifications 2023-03-28 11:51:39 -07:00
Jeroen van Rijn 4c12addcaf Update builtin.odin 2023-03-28 20:29:30 +02:00
Jeroen van Rijn 0bd27381c3 Typo 2023-03-28 20:23:08 +02:00
Jon Lipstate 6dce07790a add backticks on variables, code review comments 2023-03-28 11:07:33 -07:00
Jon Lipstate 203ae32b79 pr pickups 2023-03-28 10:24:41 -07:00
Jeroen van Rijn 3c493194c9 Remove old deprecated demos
They're so outdated they'll likely lead to confusion now.
2023-03-28 15:14:02 +02:00
Jeroen van Rijn 692764aad3 Document offset_of
Closes #2419
2023-03-28 15:01:10 +02:00
Jon Lipstate 937e5de1d8 add missing eof newline 2023-03-27 22:23:13 -07:00
Jon Lipstate 7de67f8c1b markdown compliant spaces 2023-03-27 22:20:24 -07:00
Jon Lipstate f5d66bcb6f transform into odin-site parsable format 2023-03-27 22:00:53 -07:00
Jon Lipstate bf82c40964 string code docs 2023-03-27 20:09:51 -07:00
Jeroen van Rijn aa6299f114 Merge pull request #2411 from jon-lipstate/fmt_docs
Fmt docs
2023-03-27 11:19:12 +02:00
Jeroen van Rijn 7ffca8ed58 Fix caprintf comment 2023-03-27 11:12:21 +02:00
Jeroen van Rijn 030405dbb6 Update fmt.odin
Fix hardcoded 64 bit, use assert instead of branched panic.
2023-03-27 11:06:29 +02:00
oskarnp 8862f9118b Fix typos in doc 2023-03-27 09:31:24 +02:00
oskarnp e2e98672bd Fix typo 2023-03-26 21:51:57 +02:00
oskarnp 51f295cacc Rename init procs 2023-03-26 21:46:36 +02:00
oskarnp 0c50ac3396 Remove unnecessary #partial switch 2023-03-26 21:45:37 +02:00
oskarnp 2da81a4a26 Remove unnecessary C style loop 2023-03-26 21:44:31 +02:00
oskarnp b6d4853a33 Fix cell alignment to default to Left using ZII 2023-03-26 21:41:16 +02:00
oskarnp 020b147222 Move helper procs into utility.odin 2023-03-26 21:33:27 +02:00
jon lipstate 34b037f19b Update fmt.odin
Update example to use set/register procs.
2023-03-26 11:23:37 -07:00
oskarnp 88ee5d1a6d text/table: Initial implementation 2023-03-26 16:10:27 +02:00
Jon Lipstate 0892d84c17 corrected bprint 2023-03-25 23:55:37 -07:00
Jon Lipstate 2501d50f9c fmt docs 2023-03-25 23:45:53 -07:00
Jeroen van Rijn 1e4a4181e2 Typo 2023-03-25 07:37:43 +01:00
Jeroen van Rijn 3e1daa002c Merge pull request #2407 from igordreher/json.destroy_value
Add allocator parameter to `json.detroy_value`
2023-03-25 07:34:01 +01:00
Jeroen van Rijn 4c13dee18f Update types.odin
Use `context.allocator := allocator` idiom.
2023-03-25 07:33:34 +01:00
Igor Dreher 95497626e3 Add allocator parameter to json.detroy_value 2023-03-24 21:01:23 -03:00
Jeroen van Rijn 9ada48054f Merge pull request #2406 from aloussase/master
Fix typo in warning message in parser
2023-03-24 21:38:48 +01:00
Alexander Goussas 99d6c58971 Fix typo in warning message in parser 2023-03-24 15:37:17 -05:00
gingerBill b974b3ccfd Merge pull request #2405 from rasa-silva/fix_raylib_bindings
Fix raylib bindings for MeasureTextEx
2023-03-24 14:23:30 +00:00
Ricardo Silva 75cf45f0be Fix raylib bindings for MeasureTextEx 2023-03-24 14:16:46 +00:00
Jeroen van Rijn d337a11e83 Add tests for string case conversion 2023-03-24 11:47:45 +01:00
Jeroen van Rijn a86386d882 Merge pull request #2404 from Kelimion/save_to_buffer
Rename `save_to_memory` for consistency.
2023-03-24 11:06:05 +01:00
Jeroen van Rijn bbf40bf318 Rename save_to_memory for consistency. 2023-03-24 10:47:33 +01:00
Jeroen van Rijn b054585066 Merge pull request #2402 from oskarnp/fix-ada-case
Fix strings.to_ada_case()
2023-03-24 10:40:52 +01:00
gingerBill 90c44c34a9 Make core:image packages work on js platform (wasm32) by not requiring core:os 2023-03-23 20:53:19 +00:00
oskarnp e449cc9e2d Fix strings.to_ada_case() 2023-03-23 21:30:24 +01:00
Jeroen van Rijn 909ed93cd3 Merge pull request #2400 from Lperlind/documentation/raylib
Improve raylib overview formatting on pkg website
2023-03-22 12:38:30 +01:00
Jeroen van Rijn 9c97b11ab9 Remove stray backtick 2023-03-22 12:21:25 +01:00
Jeroen van Rijn 5ae44b25da Merge pull request #2397 from DragosPopse/master
Made most libraries panic on js targets instead of not compiling
2023-03-22 12:18:09 +01:00
Dragos Popescu b2ecb37b35 Changed js panics to unimplemented where sensible 2023-03-22 12:10:27 +01:00
Dragos Popescu 144d034475 Merge branch 'odin-lang:master' into master 2023-03-22 12:08:45 +01:00
Lucas Perlind 50d8dc91cf Improve raylib overview formatting on pkg website 2023-03-22 20:19:53 +11:00
gingerBill e58915e12f Fix typo!!!! 2023-03-21 19:20:44 +00:00
gingerBill 7f8c2a44a4 Add newlines to improve documentation generation 2023-03-21 19:20:11 +00:00
gingerBill d986eee36b Fix typo 2023-03-21 15:28:52 +00:00
gingerBill b3e712e0b8 Correctly handle end comment for doc generation 2023-03-21 15:22:11 +00:00
gingerBill 05434daa69 Merge pull request #2398 from odin-lang/raylib-4.5
raylib 4.5
2023-03-21 14:22:37 +00:00
gingerBill 2c4a478987 Add @(extra_linker_flags=<string>) 2023-03-21 13:30:58 +00:00
gingerBill a80ca23937 Keep -vet and -strict-style happy 2023-03-21 13:23:06 +00:00
gingerBill ba02ef8f25 Change trailing comma require to -strict-style only 2023-03-21 13:16:03 +00:00
Dragos Popescu ef3d8bdc42 Fixed more compile time errors when including os and thread to js targets 2023-03-21 04:17:31 +01:00
Dragos Popescu 951511704d Responded to PR review. Made dynlib return false on js instead of panic 2023-03-20 21:57:51 +01:00
Dragos Popescu 23aae6ab0f Merge branch 'odin-lang:master' into master 2023-03-20 21:52:03 +01:00
gingerBill 3748e117a9 Merge pull request #2370 from fabiansperber/parser_fix
Fix core:odin/parser #force_inline/force_no_inline call expression when it's a statement
2023-03-20 19:48:58 +00:00
Fabian Sperber 33798b8b80 Need to forward the name of the directive, not the hash token 2023-03-20 20:09:30 +01:00
gingerBill 2e85083d0a Add msvcrt.lib to raylib on Windows 2023-03-20 16:34:03 +00:00
gingerBill 23b8a9033a Update vendor:raylib to raylib 4.5 2023-03-20 16:27:34 +00:00
gingerBill 313b6874b1 Merge pull request #2382 from fabiansperber/freestanding-hide-default-temp-allocator
Remove usage of global_default_temp_allocator_data when not needed
2023-03-20 12:06:34 +00:00
gingerBill 6004412365 Merge pull request #2396 from WraithGlade/patch-1
Fixed incorrect precision value in `fmt` doc.
2023-03-20 12:05:37 +00:00
Dragos Popescu adac039a2b Made most libraries panic on js targets instead of not compiling 2023-03-20 04:08:48 +01:00
WraithGlade adcc865c70 Fixed incorrect precision value in fmt doc.
It seems like `%.2f` is the correct implementation of "precision 2" for displaying floats, not `$.3f`. It prints two decimal places.

Either that or the next case (`%8.3f`) would be wrong instead, if it's the other way around. 

So, there's a mistake here one way or the other at the least.
2023-03-19 22:06:39 -04:00
gingerBill fe533fb809 Improve llreg integer type generation for SysV ABI 2023-03-19 01:29:53 +00:00
gingerBill fa62963da7 Merge branch 'master' of https://github.com/odin-lang/Odin 2023-03-19 00:52:09 +00:00
gingerBill 1f5bb99548 Improve SysV ABI for multiple return values that fit into a single register; Fixes #2384 2023-03-19 00:51:57 +00:00
Jeroen van Rijn f1cd56c28a Merge pull request #2394 from krixano/master
Add SetConsoleCursorInfo and GetConsoleCursorInfo to sys/windows package
2023-03-18 21:57:14 +01:00
Christian Seibold 852c8b533c Add SetConsoleCursorInfo and GetConsoleCursorInfo to sys/windows package 2023-03-18 15:43:31 -05:00
Jeroen van Rijn 582a72574e Merge pull request #2392 from Pingar5/master
Added parameter names to all ENet procs
2023-03-18 14:17:28 +01:00
Brennen Shaughnessy b249ddde48 Added parameter names to all ENet procs 2023-03-18 09:09:45 -04:00
Jeroen van Rijn b020ba2b5f Merge pull request #2391 from ftphikari/master
sys/windows: added some functions and types for input hooks and tray …
2023-03-18 08:06:55 +01:00
hikari 03c6862d51 sys/windows: added some functions and types for input hooks and tray icons 2023-03-18 06:44:16 +02:00
gingerBill b7f953b2ee Merge branch 'master' of https://github.com/odin-lang/Odin 2023-03-17 11:48:04 +00:00
gingerBill 0b064765c9 Add reflect.struct_field_value 2023-03-17 11:47:39 +00:00
Jeroen van Rijn eb3ddce706 Merge pull request #2390 from MoustaphaSaad/fix-linalg-refract
Fix #2389
2023-03-16 23:46:08 +01:00
Mostafa Saad 5fdc9fa3b6 Fix #2389 2023-03-17 00:29:50 +02:00
gingerBill bfb231fb8a Simplify copy elision on variable declarations 2023-03-16 17:24:29 +00:00
gingerBill 74fb74d9cb Keep -vet happy 2023-03-16 16:41:22 +00:00
gingerBill 97d7e295dd Fix to split_multi_iterator 2023-03-16 16:35:30 +00:00
gingerBill 0727e91aeb Simplify the implementation of strings.split_multi; add strings.index_multi 2023-03-16 16:30:48 +00:00
gingerBill 8dc70f797c Increase use of temporary_allocator() where possible 2023-03-16 15:16:17 +00:00
gingerBill 2cf8a9da6f Merge branch 'master' of https://github.com/odin-lang/Odin 2023-03-16 15:05:06 +00:00
gingerBill c1c7128634 Minimize severe memory usage by enforcing the heap_allocator() in places 2023-03-16 15:04:57 +00:00
Jeroen van Rijn 0e9ef50e63 Update build flag 2023-03-16 15:16:09 +01:00
gingerBill e05944601a Minor fixes 2023-03-16 13:35:38 +00:00
gingerBill 49cf0125a9 Fix minor memory leak 2023-03-16 13:01:06 +00:00
gingerBill 0602a16ad6 Reserve memory for procedures when generating the LLVM IR 2023-03-16 12:44:03 +00:00
gingerBill 09a0dad115 Add contextless to internal parse_hex call 2023-03-16 12:43:10 +00:00
Jeroen van Rijn 243a3f5006 Fix #2386 2023-03-16 12:35:05 +01:00
Jeroen van Rijn 33ca85bd4e Fix #2385 2023-03-16 10:59:19 +01:00
Jeroen van Rijn ca15eb26f0 Merge pull request #2378 from markodevv/directx12-message-callback
Add RegisterMessageCallback for d3d12
2023-03-16 08:20:26 +01:00
Jeroen van Rijn 28eebc14d0 Merge pull request #2387 from elusivePorpoise/main
add SetConsoleOutputCP
2023-03-16 07:15:20 +01:00
Elusive Porpoise 4210aa9ab9 add SetConsoleOuputCP 2023-03-15 17:15:25 -07:00
gingerBill 5bbdbadc25 Remove where ORD(E) on procedures that don't need it 2023-03-14 14:05:23 +00:00
gingerBill 00f24a3249 Merge pull request #2380 from flysand7/master
Add -no-thread-local flag
2023-03-14 13:04:34 +00:00
gingerBill d8a798372b Merge pull request #2383 from eisbehr/target-features-fix
Fix: -target-feature list missing commas
2023-03-14 12:49:43 +00:00
Florian Behr 8d5c865814 Fix missing commas in -target-featues string by adding missing i increment. 2023-03-14 13:39:08 +01:00
bumbread 5134d6bc63 rename -no-tls to -no-thread-local 2023-03-14 16:32:42 +11:00
Marko ede57720fd Fix brace style and indentation 2023-03-13 23:08:15 +01:00
Fabian Sperber 830d2007a6 Remove usage of global_default_temp_allocator_data when there is no default allocator (freestanding, JS or --default-to-nil-allocator) 2023-03-13 20:12:54 +01:00
bumbread 5f3b6c9722 Added -no-tls flag 2023-03-13 20:25:13 +11:00
gingerBill 93f7d3bfb9 Allow case nil within a type switch statement (experimental idea) 2023-03-12 16:33:21 +00:00
gingerBill f0ef10aa57 Merge branch 'master' of https://github.com/odin-lang/Odin 2023-03-12 12:39:41 +00:00
gingerBill bf91fcc6f7 Improve type checking on polymorphic unions 2023-03-12 12:39:31 +00:00
Jeroen van Rijn 2d894a0164 Merge pull request #2377 from jon-lipstate/spall_pkg_name
resolve doc/spall package name conflict
2023-03-11 10:00:14 +01:00
Marko 731b9c902f Add RegisterMessageCallback for d3d12 2023-03-11 05:25:17 +01:00
Jon Lipstate ac0f3c8433 resolve doc/spall package name conflict 2023-03-10 19:24:11 -08:00
Jeroen van Rijn 56bfbbf501 Merge pull request #2375 from wjlroe/patch-1
Fix documentation example of strings.to_upper
2023-03-10 17:12:52 +01:00
William Roe 63b5d472fa Fix documentation example of strings.to_upper
This looks like it was a copy/paste mistake
2023-03-10 16:07:06 +00:00
Jeroen van Rijn 233e3c76fd Merge pull request #2373 from colrdavidson/spall_flushes
log buffer flushes to trace
2023-03-10 08:04:46 +01:00
Colin Davidson 2334dadb6a add main scope 2023-03-09 16:34:43 -08:00
Colin Davidson 6f4f2754d6 add basic usage example 2023-03-09 16:05:16 -08:00
Colin Davidson 30ced04137 log buffer flushes to trace 2023-03-09 15:26:27 -08:00
gingerBill c39bd7e089 Fix range loop & vals debug info 2023-03-09 15:57:29 +00:00
gingerBill 3470d986f0 Fix debug symbols for range loops 2023-03-09 15:48:02 +00:00
gingerBill 7c0257fcda Fix value elision on declaration 2023-03-09 15:39:41 +00:00
Jeroen van Rijn 9af6d6c9c6 Merge pull request #2369 from Sokus/non-blocking-socket-mode
Add `set_blocking` for network sockets
2023-03-08 14:35:40 +01:00
Sokus 1ecab2fcbc Add set_blocking for network sockets 2023-03-08 13:30:12 +01:00
gingerBill a262c0bbf3 Separate out the read_reg into three non-parapoly procedures 2023-03-07 16:25:46 +00:00
gingerBill 7f3f164736 Update help usage 2023-03-07 15:32:32 +00:00
gingerBill 085db569f1 Add -o:none optimization mode (useful for -debug builds) 2023-03-07 15:31:55 +00:00
gingerBill 133af6f826 Remove delete with wrong allocator 2023-03-07 15:24:59 +00:00
gingerBill 1c2301e2f1 Use atof in float_from_string to allow for debug C-like semantic purposes 2023-03-06 19:52:03 +00:00
gingerBill ef999f660b Remove debug code 2023-03-06 19:46:50 +00:00
gingerBill fad330acd1 Fix bug with nil pointer 2023-03-06 15:21:20 +00:00
gingerBill 8f1af2630d Fix typo in parse_components 2023-03-06 13:40:06 +00:00
gingerBill eea92a3371 Add booleans to print_any_single 2023-03-06 13:33:29 +00:00
gingerBill ff275df5ea Fix parsing C-like hex floats 2023-03-06 12:39:52 +00:00
Jeroen van Rijn 6e56e5457d Merge pull request #2363 from colrdavidson/tsc_multiplatform_fix
add null-impl for tsc_frequency for alt-platforms
2023-03-05 09:13:26 +01:00
Colin Davidson afaa5f2deb add null-impl for tsc_frequency for alt-platforms 2023-03-04 22:40:14 -08:00
gingerBill 0674b1b6ee Merge pull request #2314 from SentientCoffee/pr/win32_console_text_attributes
Add win32 SetConsoleTextAttribute for setting cmd prompt colors
2023-03-04 15:19:34 +00:00
Jeroen van Rijn 1ef8602f19 Merge pull request #2359 from colrdavidson/core_net_update
manually merge core_net
2023-03-04 15:02:35 +01:00
Jeroen van Rijn ee597fc9b8 Add .None to Linux & Darwin, too. 2023-03-04 11:12:11 +01:00
Jeroen van Rijn e254581a1b Apply #shared_nil to Network_Error 2023-03-04 10:39:20 +01:00
Jeroen van Rijn 38ea140b3f Update addr.odin
Fix comment
2023-03-04 10:04:55 +01:00
Jeroen van Rijn d939d6079a Don't try to check core:net on the BSDs. 2023-03-03 18:24:26 +01:00
Colin Davidson 6e9475d61d add core_net to examples 2023-03-03 09:09:50 -08:00
Jeroen van Rijn f6134422e6 Fix one last review comment. 2023-03-03 17:50:49 +01:00
Jeroen van Rijn 5c05038af0 Finish cleaning up core_net. 2023-03-03 17:26:44 +01:00
Jeroen van Rijn 5da5ebff13 More coalescing. 2023-03-03 17:12:21 +01:00
Jeroen van Rijn 798932523e Coalesce socket_windows 2023-03-03 15:21:40 +01:00
Jeroen van Rijn 5267a864db Coalesce more. 2023-03-03 14:15:15 +01:00
Jeroen van Rijn f02334237a Merge branch 'master' into pr/2359 2023-03-03 13:01:49 +01:00
Jeroen van Rijn d5ea492ef5 Make more private. 2023-03-03 13:00:43 +01:00
Jeroen van Rijn 96ac405952 Alignment + unnecessary allocator param. 2023-03-03 12:04:36 +01:00
Colin Davidson 38d58e818c ripple bill-suggestions 2023-03-02 06:56:54 -08:00
gingerBill 2d71ab6f29 Improve error message on undefined operators 2023-03-02 14:54:27 +00:00
Colin Davidson 090723179b Merge branch 'master' into core_net_update 2023-03-02 06:50:25 -08:00
Colin Davidson 5b55fbff23 cleanup openbsd errors more 2023-03-02 06:47:05 -08:00
Colin Davidson 64f200dc74 big error cleanup 2023-03-02 06:43:20 -08:00
gingerBill 99e2f0c91e Merge pull request #2357 from JeppeSS/sys-windows-additions
Added missing Windows functions for console manipulation
2023-03-02 13:21:38 +00:00
Jeroen van Rijn c02ff3af27 Update comments 2023-03-02 13:45:12 +01:00
gingerBill 553f338f6f Merge pull request #2360 from Lperlind/documentation/dynlib
Document core:dynlib
2023-03-02 12:07:32 +00:00
Lucas Perlind bb72c804fb Document core:dynlib 2023-03-02 19:20:45 +11:00
Colin Davidson 13c6352b8e catch alloc error on wstring_to_utf8 convert 2023-03-01 18:55:02 -08:00
Colin Davidson 707c2b3d7a remove win32 ref 2023-03-01 18:24:37 -08:00
Colin Davidson 14eed79a21 make baby pandas (and Jeroen) happy 2023-03-01 08:33:48 -08:00
Colin Davidson 2ca30e3acd more test cleanup 2023-03-01 08:27:07 -08:00
Colin Davidson caf9716bf1 more cleanup ripple 2023-03-01 08:21:53 -08:00
Colin Davidson d569daae33 more manual type carryover 2023-03-01 08:17:41 -08:00
Colin Davidson 28f7f57247 manually start merging core_net 2023-03-01 07:58:30 -08:00
Jeppe Skov ffc592c7cf Added missing Windows functions for console manipulation
This commit adds several missing types and functions to the Windows implementation to enable manipulation of console windows. The types added include 'SMALL_RECT', 'CONSOLE_SCREEN_BUFFER_INFO', and 'PCONSOLE_SCREEN_BUFFER_INFO'. The functions added include 'GetConsoleScreenBufferInfo', 'SetConsoleScreenBufferSize', and 'SetConsoleWindowInfo'. These functions were necessary to properly manage the console window.
2023-02-28 23:18:10 +01:00
Jeroen van Rijn 3567c006e6 Merge pull request #2356 from flysand7/master
Add option to link to glfw3 dynamically
2023-02-28 18:01:32 +01:00
bumbread b9db450a7d Fix missing underscore 2023-03-01 03:43:59 +11:00
bumbread 0d65c6dcf7 Add option to link to glfw3 dynamically 2023-03-01 03:05:04 +11:00
gingerBill dfee7c103e Document virtual.Arena 2023-02-28 13:07:52 +00:00
gingerBill 025fc2685d Add docs to core:path/filepath 2023-02-28 12:55:13 +00:00
gingerBill 5b5154eda0 Add temp allocator guard; clean up indentation 2023-02-28 12:38:36 +00:00
gingerBill ecf65303cd Make arena_free_all keep the first memory block for a .Growing arena 2023-02-28 12:37:05 +00:00
gingerBill 8ea6972496 Merge pull request #2327 from odin-lang/new-temp-allocator
New default `context.temp_allocator`
2023-02-28 12:22:45 +00:00
gingerBill 9afd9f9bea Merge branch 'master' into new-temp-allocator 2023-02-28 12:15:54 +00:00
gingerBill c8d3a9121b Merge pull request #2354 from elusivePorpoise/master
FindFirstChangeNotification series of calls
2023-02-28 10:49:42 +00:00
gingerBill f0950e2286 Merge pull request #2355 from colrdavidson/os_linux_cleanup
os/linux cleanup
2023-02-28 10:48:09 +00:00
Colin Davidson edd78ae129 cleanup of os/linux 2023-02-28 01:17:43 -08:00
Elusive Porpoise 8738695bd8 FindFirstChangeNotification series of calls 2023-02-27 18:43:43 -08:00
gingerBill 76cb3b7874 Add better fallback for ast_token 2023-02-27 16:15:19 +00:00
gingerBill 02a098eb48 Merge branch 'master' of https://github.com/odin-lang/Odin 2023-02-27 15:58:42 +00:00
gingerBill 1f17a391c6 Improve error line squiggle logic 2023-02-27 15:58:32 +00:00
Jeroen van Rijn a8fb346fd7 Merge pull request #2350 from Kelimion/d3d_license
Add license for dxcompiler libs.
2023-02-26 19:54:53 +01:00
Jeroen van Rijn 96fb5eb0ba Add license for dxcompiler libs. 2023-02-26 19:48:44 +01:00
gingerBill 9c7656d59a Add core:prof/spall 2023-02-26 14:00:39 +00:00
gingerBill a53bff5645 Fix typed #caller_location bug. 2023-02-26 13:52:02 +00:00
gingerBill a9182cfd8c Allow compound literals to access fields through using 2023-02-26 13:26:35 +00:00
gingerBill de6c0f682f Merge pull request #2343 from Hyp-X/pr-heapflags
Fix d3d12 HEAP_FLAG_ALLOW_ONLY_BUFFERS flags
2023-02-24 11:58:30 +00:00
gingerBill 9cfcb43725 Merge pull request #2345 from Hyp-X/pr-dxc
Add vendor:directx/dxc package
2023-02-24 11:57:59 +00:00
gingerBill d9b590c76e Merge pull request #2344 from Hyp-X/pr-more-shader-reflection
Add d3d12shader missing types and UUID's
2023-02-24 11:51:11 +00:00
Hyp-X 450c535e9a Add vendor:directx/dxc package:
DirectX Shader Compiler
2023-02-24 11:34:00 +01:00
Hyp-X 0dc166e594 Add d3d12shader missing types and UUID's 2023-02-24 10:02:12 +01:00
Hyp-X 8ba080a66d Fix d3d12 HEAP_FLAG_ALLOW_ONLY_BUFFERS flags 2023-02-23 17:15:13 +01:00
gingerBill 7801582819 Merge pull request #2341 from Hyp-X/pr-getresourceallocationinfo
Fix d3d12 GetResourceAllocationInfo signature
2023-02-23 15:48:02 +00:00
gingerBill bc882e678d Merge pull request #2340 from Hyp-X/pr-shader-reflection-fix
Fixed d3d12 shader reflection vtables
2023-02-23 15:40:15 +00:00
gingerBill 58e173f279 Merge pull request #2339 from Hyp-X/pr-dxgidebug
Add dxgidebug.h implementation to dxgi package
2023-02-23 15:40:02 +00:00
Hyp-X b7d7b9d6b3 Fix d3d12 GetResourceAllocationInfo signature 2023-02-23 16:30:28 +01:00
Hyp-X cf091a48b4 Fixed d3d12 shader reflection vtables 2023-02-23 14:48:58 +01:00
Hyp-X d9bfe9e5d4 Add dxgidebug.h implementation to dxgi package 2023-02-23 14:39:39 +01:00
gingerBill 245a6697ef Improve truncated verbose line error 2023-02-22 22:57:11 +00:00
gingerBill 6226c2978d Change padding of showing the error in line 2023-02-22 22:04:00 +00:00
gingerBill 3d325e52c6 Merge branch 'master' of https://github.com/odin-lang/Odin 2023-02-22 21:50:51 +00:00
gingerBill 6a6d7701f9 Improve error bounds for check_comparison 2023-02-22 21:50:49 +00:00
Jeroen van Rijn 50c688f0f7 Merge pull request #2338 from Tetralux/fix-here
Remove debug print
2023-02-22 22:49:52 +01:00
Tetralux ef99d03f21 Remove debug print 2023-02-22 21:43:42 +00:00
gingerBill af265250c2 Merge pull request #2336 from colrdavidson/tsc_freq
Add TSC frequency getter
2023-02-22 21:22:10 +00:00
Colin Davidson c6f463b8c9 shuffle tsc around a little 2023-02-22 12:28:24 -08:00
gingerBill b7d75e2f1d Override to have ansi colors if env has ODIN_TERMINAL=ansi 2023-02-22 12:41:53 +00:00
gingerBill 6aa54cbe9a Begin work on adding colours to error messages on Windows Terminals 2023-02-22 12:31:51 +00:00
gingerBill 090e30f07b Make -verbose-errors the default; -terse-errors to disable it 2023-02-22 11:48:10 +00:00
gingerBill f5d507a9b9 Improve errors about conversions of constant integers 2023-02-22 11:30:08 +00:00
Colin Davidson 8e5e43f335 add sleep-fallback and invariant check 2023-02-21 17:48:49 -08:00
gingerBill b9f7b2fdfa Improve error message for typed constants that cannot be represented by a type 2023-02-21 23:16:25 +00:00
gingerBill 59a601f2cf Improve error messages when trying to access a non-existent field on a type 2023-02-21 23:08:02 +00:00
gingerBill b6a5c5f5d2 Improve some error messages when casting a constant value which needs to be truncated/rounded 2023-02-21 17:24:22 +00:00
gingerBill a2f02b8b32 Fix bug with for in statements and pointer intervals 2023-02-21 16:31:22 +00:00
gingerBill ee4ed126e1 Improve error message for accidentally using a type as an expression statement 2023-02-21 16:25:28 +00:00
gingerBill c36dc91849 Minor changes in runtime 2023-02-21 16:24:28 +00:00
Colin Davidson 91dccf8d62 more function name changes 2023-02-21 06:46:36 -08:00
Colin Davidson 1fc3a25f47 block all x86 tsc functions in when block 2023-02-21 06:28:55 -08:00
Colin Davidson 7322b63991 adjust func names 2023-02-21 06:22:19 -08:00
Colin Davidson f860b09065 use the libc call on darwin so sysctlbyname works 2023-02-21 05:38:07 -08:00
Colin Davidson 45b742be23 sort out units to make things happier 2023-02-19 20:50:30 -08:00
Colin Davidson d325ee4b91 more typo. yay. 2023-02-19 20:45:56 -08:00
Colin Davidson 87d6910bb8 intrinsics typo 2023-02-19 20:44:49 -08:00
Colin Davidson 9c9300ed58 derp. raw-syscalls 2023-02-19 20:44:00 -08:00
Colin Davidson e559cf32fe oops, add intrinsics import 2023-02-19 20:39:36 -08:00
Colin Davidson f2202db517 make darwin syscalls contextless 2023-02-19 20:38:46 -08:00
Colin Davidson fb735883be add a tsc frequency get for windows 2023-02-19 20:33:48 -08:00
Colin Davidson 6a2ef1f4f3 add osx support 2023-02-19 20:23:35 -08:00
Colin Davidson 051c9cb564 begin adding tsc frequency getters 2023-02-19 20:08:11 -08:00
gingerBill eb60ec3899 Fix unreachable error 2023-02-19 12:53:22 +00:00
gingerBill 233f47cc99 Fix #2329 2023-02-19 12:47:14 +00:00
gingerBill c386c72d10 Check for procedure literals in $ parameters 2023-02-19 12:11:57 +00:00
gingerBill 20eacc4a84 Fix issue that conflicts with constant parapoly procedures and deferred_* procedures 2023-02-19 12:10:28 +00:00
Jeroen van Rijn a28699b42d Merge pull request #2335 from colrdavidson/add_panel
Add open file dialog panel to foundation
2023-02-19 08:32:01 +01:00
Colin Davidson 4d74d5bc99 Add user-defaults config to enable force-smooth-scrolling for SDL 2023-02-18 19:54:40 -08:00
Colin Davidson ed371f2b0d Add open file dialog panel to foundation 2023-02-18 14:56:51 -08:00
gingerBill 66f2881a78 Allow comparisons between empty struct{} and union{} 2023-02-17 17:02:37 +00:00
gingerBill 7d4e9497eb Reduce stack usage of some type switch cases 2023-02-17 16:51:57 +00:00
gingerBill c08809e29d Improve handling of passing constants to implicit immutable const ref parameters 2023-02-17 14:49:37 +00:00
gingerBill 99460c9e32 Minimize stack wastage with compound literals defining variables 2023-02-17 14:26:22 +00:00
gingerBill d86df8321c Fix #2330 2023-02-17 13:08:20 +00:00
gingerBill 806f56ca38 Remove debug string 2023-02-17 13:04:09 +00:00
gingerBill c40b6c7c2f Add constant data to the identifier directly 2023-02-17 13:02:41 +00:00
gingerBill 896b7145b3 Merge branch 'master' of https://github.com/odin-lang/Odin 2023-02-17 13:01:12 +00:00
gingerBill 8a2a70a3c2 Fix overriding procedure information for literals 2023-02-17 13:00:37 +00:00
gingerBill 97352538ad Merge pull request #2332 from thePHTest/master
Fix #by_ptr argument overrides for Linux
2023-02-16 10:22:25 +00:00
Phil Homan c6c4ad6188 fix #by_ptr argument overrides for Linux 2023-02-15 16:51:00 -08:00
gingerBill 210f47b8ab Merge branch 'master' of https://github.com/odin-lang/Odin 2023-02-15 11:32:02 +00:00
gingerBill 94c1331c07 Implement @(fini) (opposite of @(init)) 2023-02-15 11:31:51 +00:00
gingerBill d6407e9636 Merge pull request #2331 from colrdavidson/platform_file_cleanup
make file access a little more normal across platforms
2023-02-15 11:07:42 +00:00
Colin Davidson df58a00564 fix errno/signatures 2023-02-14 18:43:48 -08:00
Colin Davidson d546677ae7 fix typo 2023-02-14 18:39:09 -08:00
Colin Davidson 04b1023988 make file access a little more normal across platforms 2023-02-14 18:34:03 -08:00
gingerBill 9a81071687 Merge branch 'master' into new-temp-allocator 2023-02-14 23:59:49 +00:00
gingerBill 48685e8bf1 Remove set volatile for store 2023-02-14 23:52:36 +00:00
gingerBill 0f697a0f26 Move in_multi_assignment check tighter 2023-02-14 23:52:23 +00:00
gingerBill 8ddb493b96 Add #optional_allocator_error to make_map 2023-02-14 10:28:04 +00:00
gingerBill 039d9938b9 Fix return value 2023-02-10 17:20:14 +00:00
gingerBill f50ea649f6 Minor fix 2023-02-10 17:15:40 +00:00
gingerBill 6e647a88eb Keep -vet happy 2023-02-10 16:36:50 +00:00
gingerBill 986cba584e Add runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD where appropriate 2023-02-10 16:23:33 +00:00
gingerBill b427a4c8c9 Minor change to arena_temp_end 2023-02-10 14:44:46 +00:00
gingerBill 133ee70a5b Add default_temp_allocator_temp_begin and default_temp_allocator_temp_end 2023-02-10 14:36:57 +00:00
gingerBill 494612827a Add Arena_Temp 2023-02-10 14:30:41 +00:00
gingerBill 1113f23475 Remove unused variable 2023-02-10 14:10:06 +00:00
gingerBill 8626f58773 Replace current default context.temp_allocator to use a growing arena rather than a ring buffer 2023-02-10 13:18:33 +00:00
gingerBill 7032867421 Pass #caller_location down correctly 2023-02-10 13:18:03 +00:00
gingerBill e6239ca3c2 Warn on 'expand_to_tuple' has been replaced with 'expand_values' 2023-02-10 13:17:04 +00:00
gingerBill 162628000f Calculate the size needed before allocating 2023-02-10 11:55:08 +00:00
gingerBill 55b79c078c Remove := context.allocator usage in package os2 2023-02-10 11:46:29 +00:00
gingerBill 570b127869 Fix crash when a variable declaration must be an identifier 2023-02-08 11:46:33 +00:00
gingerBill 6179d4feb1 Rename to Type_Info_Parameters 2023-02-08 11:23:21 +00:00
gingerBill 2ff5d016d5 Merge pull request #2326 from ftphikari/master
Updated documentation to reflect changes from commit 8a16fd7
2023-02-08 11:09:23 +00:00
hikari 854a95327a Updated documentation to reflect changes from commit 8a16fd7 2023-02-08 12:24:10 +02:00
gingerBill 8a16fd7699 Rename built-in procedure to expand_values 2023-02-07 15:39:39 +00:00
gingerBill 7bbcf22deb Remove dead code (sort/map.odin) 2023-02-05 18:33:53 +00:00
gingerBill 0324281634 Enforce dynamic map calls for the time being 2023-02-03 15:17:30 +00:00
gingerBill de0a3e0ab9 Minor change to byval for readonly parameters 2023-02-03 15:07:44 +00:00
gingerBill d26110da7f Change attributes for the static map get 2023-02-03 14:25:30 +00:00
gingerBill 60e73d91f6 Remove internal readonly attribute 2023-02-03 13:42:23 +00:00
gingerBill 5eeb436626 Temporarily make all map get calls dynamic 2023-02-03 12:43:21 +00:00
gingerBill 802333e454 Fix arena.free_all 2023-02-03 12:40:52 +00:00
gingerBill eb457d688d Make static map calls the default; add -dynamic-map-calls 2023-02-03 12:16:58 +00:00
Daniel 34cb558279 Add win32 SetConsoleTextAttributes for setting cmd prompt colors 2023-01-25 14:17:20 -05:00
223 changed files with 17932 additions and 7493 deletions
+7
View File
@@ -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: |
+10 -2
View File
@@ -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 ---
@@ -109,7 +117,7 @@ jmag :: proc(value: Quaternion) -> Float ---
kmag :: proc(value: Quaternion) -> Float ---
conj :: proc(value: Complex_Or_Quaternion) -> Complex_Or_Quaternion ---
expand_to_tuple :: proc(value: Struct_Or_Array) -> (A, B, C, ...) ---
expand_values :: proc(value: Struct_Or_Array) -> (A, B, C, ...) ---
min :: proc(values: ..T) -> T ---
max :: proc(values: ..T) -> T ---
+1 -1
View File
@@ -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
+2
View File
@@ -212,6 +212,8 @@ read_slice_from_memory :: #force_inline proc(z: ^Context_Memory_Input, size: int
@(optimization_mode="speed")
read_slice_from_stream :: #force_inline proc(z: ^Context_Stream_Input, size: int) -> (res: []u8, err: io.Error) {
// TODO: REMOVE ALL USE OF context.temp_allocator here
// the is literally no need for it
b := make([]u8, size, context.temp_allocator)
_, e := z.input->impl_read(b[:])
if e == .None {
+1 -1
View File
@@ -12,7 +12,7 @@ _rand_bytes :: proc (dst: []byte) {
for l > 0 {
to_read := min(l, _MAX_PER_CALL_BYTES)
ret := unix.sys_getrandom(raw_data(dst), to_read, 0)
ret := unix.sys_getrandom(raw_data(dst), uint(to_read), 0)
if ret < 0 {
switch os.Errno(-ret) {
case os.EINTR:
+2
View File
@@ -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 {
+7
View File
@@ -0,0 +1,7 @@
/*
Package core:dynlib implements loading of shared libraries/DLLs and their symbols.
The behaviour of dynamically loaded libraries is specific to the target platform of the program.
For in depth detail on the underlying behaviour please refer to your target platform's documentation.
*/
package dynlib
+81 -2
View File
@@ -1,15 +1,94 @@
package dynlib
/*
A handle to a dynamically loaded library.
*/
Library :: distinct rawptr
load_library :: proc(path: string, global_symbols := false) -> (Library, bool) {
/*
Loads a dynamic library from the filesystem. The paramater `global_symbols` makes the symbols in the loaded
library available to resolve references in subsequently loaded libraries.
The paramater `global_symbols` is only used for the platforms `linux`, `darwin`, `freebsd` and `openbsd`.
On `windows` this paramater is ignored.
The underlying behaviour is platform specific.
On `linux`, `darwin`, `freebsd` and `openbsd` refer to `dlopen`.
On `windows` refer to `LoadLibraryW`.
**Implicit Allocators**
`context.temp_allocator`
Example:
import "core:dynlib"
import "core:fmt"
load_my_library :: proc() {
LIBRARY_PATH :: "my_library.dll"
library, ok := dynlib.load_library(LIBRARY_PATH)
if ! ok {
return
}
fmt.println("The library %q was successfully loaded", LIBRARY_PATH)
}
*/
load_library :: proc(path: string, global_symbols := false) -> (library: Library, did_load: bool) {
return _load_library(path, global_symbols)
}
unload_library :: proc(library: Library) -> bool {
/*
Unloads a dynamic library.
The underlying behaviour is platform specific.
On `linux`, `darwin`, `freebsd` and `openbsd` refer to `dlclose`.
On `windows` refer to `FreeLibrary`.
Example:
import "core:dynlib"
import "core:fmt"
load_then_unload_my_library :: proc() {
LIBRARY_PATH :: "my_library.dll"
library, ok := dynlib.load_library(LIBRARY_PATH)
if ! ok {
return
}
did_unload := dynlib.unload_library(library)
if ! did_unload {
return
}
fmt.println("The library %q was successfully unloaded", LIBRARY_PATH)
}
*/
unload_library :: proc(library: Library) -> (did_unload: bool) {
return _unload_library(library)
}
/*
Loads the address of a procedure/variable from a dynamic library.
The underlying behaviour is platform specific.
On `linux`, `darwin`, `freebsd` and `openbsd` refer to `dlsym`.
On `windows` refer to `GetProcAddress`.
**Implicit Allocators**
`context.temp_allocator`
Example:
import "core:dynlib"
import "core:fmt"
find_a_in_my_library :: proc() {
LIBRARY_PATH :: "my_library.dll"
library, ok := dynlib.load_library(LIBRARY_PATH)
if ! ok {
return
}
a, found_a := dynlib.symbol_address(library, "a")
if found_a do fmt.printf("The symbol %q was found at the address %v", "a", a)
}
*/
symbol_address :: proc(library: Library, symbol: string) -> (ptr: rawptr, found: bool) #optional_ok {
return _symbol_address(library, symbol)
}
+15
View File
@@ -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
}
+3
View File
@@ -4,10 +4,12 @@ package dynlib
import win32 "core:sys/windows"
import "core:strings"
import "core:runtime"
_load_library :: proc(path: string, global_symbols := false) -> (Library, bool) {
// NOTE(bill): 'global_symbols' is here only for consistency with POSIX which has RTLD_GLOBAL
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
wide_path := win32.utf8_to_wstring(path, context.temp_allocator)
handle := cast(Library)win32.LoadLibraryW(wide_path)
return handle, handle != nil
@@ -19,6 +21,7 @@ _unload_library :: proc(library: Library) -> bool {
}
_symbol_address :: proc(library: Library, symbol: string) -> (ptr: rawptr, found: bool) {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
c_str := strings.clone_to_cstring(symbol, context.temp_allocator)
ptr = win32.GetProcAddress(cast(win32.HMODULE)library, c_str)
found = ptr != nil
+1 -1
View File
@@ -198,7 +198,7 @@ marshal_to_writer :: proc(w: io.Writer, v: any, opt: ^Marshal_Options) -> (err:
case runtime.Type_Info_Procedure:
return .Unsupported_Type
case runtime.Type_Info_Tuple:
case runtime.Type_Info_Parameters:
return .Unsupported_Type
case runtime.Type_Info_Simd_Vector:
+3 -3
View File
@@ -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)
}
}
}
+2
View File
@@ -346,6 +346,8 @@ unmarshal_object :: proc(p: ^Parser, v: any, end_token: Token_Kind) -> (err: Unm
fields := reflect.struct_fields_zipped(ti.id)
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == context.allocator)
field_used := make([]bool, len(fields), context.temp_allocator)
use_field_idx := -1
+3 -1
View File
@@ -33,6 +33,7 @@ import "core:intrinsics"
import "core:mem"
import "core:os"
import "core:strings"
import "core:runtime"
likely :: intrinsics.expect
@@ -408,7 +409,7 @@ parse_bytes :: proc(data: []u8, options := DEFAULT_OPTIONS, path := "", error_ha
next := scan(t)
#partial switch next.kind {
case .Ident:
if len(next.text) == 3 && strings.to_lower(next.text, context.temp_allocator) == "xml" {
if len(next.text) == 3 && strings.equal_fold(next.text, "xml") {
parse_prologue(doc) or_return
} else if len(doc.prologue) > 0 {
/*
@@ -614,6 +615,7 @@ parse_prologue :: proc(doc: ^Document) -> (err: Error) {
}
case "encoding":
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
switch strings.to_lower(attr.val, context.temp_allocator) {
case "utf-8", "utf8":
doc.encoding = .UTF_8
+1 -1
View File
@@ -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
+562 -100
View File
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
}
+10
View File
@@ -0,0 +1,10 @@
//+build js
package image
load :: proc{
load_from_bytes,
}
which :: proc{
which_bytes,
}
-61
View File
@@ -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)
}
}
+38
View File
@@ -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
}
+3 -36
View File
@@ -4,10 +4,10 @@ import "core:bytes"
import "core:fmt"
import "core:image"
import "core:mem"
import "core:os"
import "core:strconv"
import "core:strings"
import "core:unicode"
import "core:runtime"
Image :: image.Image
Format :: image.Netpbm_Format
@@ -26,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
@@ -66,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
@@ -407,6 +372,8 @@ _parse_header_pam :: proc(data: []byte, allocator := context.allocator) -> (head
}
length = header_end_index + len(HEADER_END)
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator)
// string buffer for the tupltype
tupltype: strings.Builder
strings.builder_init(&tupltype, context.temp_allocator); defer strings.builder_destroy(&tupltype)
+10
View File
@@ -0,0 +1,10 @@
//+build js
package netpbm
load :: proc {
load_from_bytes,
}
save :: proc {
save_to_buffer,
}
+41
View File
@@ -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 -14
View File
@@ -16,6 +16,7 @@ import coretime "core:time"
import "core:strings"
import "core:bytes"
import "core:mem"
import "core:runtime"
/*
Cleanup of image-specific data.
@@ -91,6 +92,8 @@ core_time :: proc(c: image.PNG_Chunk) -> (t: coretime.Time, ok: bool) {
}
text :: proc(c: image.PNG_Chunk) -> (res: Text, ok: bool) {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == context.allocator)
assert(len(c.data) == int(c.header.length))
#partial switch c.header.type {
case .tEXt:
@@ -194,18 +197,18 @@ text_destroy :: proc(text: Text) {
}
iccp :: proc(c: image.PNG_Chunk) -> (res: iCCP, ok: bool) {
ok = true
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == context.allocator)
fields := bytes.split_n(s=c.data, sep=[]u8{0}, n=3, allocator=context.temp_allocator)
if len(fields[0]) < 1 || len(fields[0]) > 79 {
// Invalid profile name
ok = false; return
return
}
if len(fields[1]) != 0 {
// Compression method should be a zero, which the split turned into an empty slice.
ok = false; return
return
}
// Set up ZLIB context and decompress iCCP payload
@@ -213,12 +216,12 @@ iccp :: proc(c: image.PNG_Chunk) -> (res: iCCP, ok: bool) {
zlib_error := zlib.inflate_from_byte_array(fields[2], &buf)
if zlib_error != nil {
bytes.buffer_destroy(&buf)
ok = false; return
return
}
res.name = strings.clone(string(fields[0]))
res.profile = bytes.buffer_to_bytes(&buf)
ok = true
return
}
@@ -256,18 +259,18 @@ plte :: proc(c: image.PNG_Chunk) -> (res: PLTE, ok: bool) {
splt :: proc(c: image.PNG_Chunk) -> (res: sPLT, ok: bool) {
if c.header.type != .sPLT {
return {}, false
return
}
ok = true
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == context.allocator)
fields := bytes.split_n(s=c.data, sep=[]u8{0}, n=2, allocator=context.temp_allocator)
if len(fields) != 2 {
return {}, false
return
}
res.depth = fields[1][0]
if res.depth != 8 && res.depth != 16 {
return {}, false
return
}
data := fields[1][1:]
@@ -275,21 +278,21 @@ splt :: proc(c: image.PNG_Chunk) -> (res: sPLT, ok: bool) {
if res.depth == 8 {
if len(data) % 6 != 0 {
return {}, false
return
}
count = len(data) / 6
if count > 256 {
return {}, false
return
}
res.entries = mem.slice_data_cast([][4]u8, data)
} else { // res.depth == 16
if len(data) % 10 != 0 {
return {}, false
return
}
count = len(data) / 10
if count > 256 {
return {}, false
return
}
res.entries = mem.slice_data_cast([][4]u16, data)
@@ -297,7 +300,7 @@ splt :: proc(c: image.PNG_Chunk) -> (res: sPLT, ok: bool) {
res.name = strings.clone(string(fields[0]))
res.used = u16(count)
ok = true
return
}
+11 -23
View File
@@ -17,12 +17,12 @@ import "core:compress"
import "core:compress/zlib"
import "core:image"
import "core:os"
import "core:hash"
import "core:bytes"
import "core:io"
import "core:mem"
import "core:intrinsics"
import "core:runtime"
// Limit chunk sizes.
// By default: IDAT = 8k x 8k x 16-bits + 8k filter bytes.
@@ -335,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
@@ -1247,6 +1234,8 @@ defilter_8 :: proc(params: ^Filter_Params) -> (ok: bool) {
// TODO: See about doing a Duff's #unroll where practicable
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
// Apron so we don't need to special case first rows.
up := make([]u8, row_stride, context.temp_allocator)
ok = true
@@ -1299,10 +1288,9 @@ defilter_8 :: proc(params: ^Filter_Params) -> (ok: bool) {
}
// @(optimization_mode="speed")
defilter_less_than_8 :: proc(params: ^Filter_Params) -> (ok: bool) #no_bounds_check {
defilter_less_than_8 :: proc(params: ^Filter_Params) -> bool #no_bounds_check {
using params
ok = true
row_stride_in := ((channels * width * depth) + 7) >> 3
row_stride_out := channels * width
@@ -1314,6 +1302,8 @@ defilter_less_than_8 :: proc(params: ^Filter_Params) -> (ok: bool) #no_bounds_ch
// TODO: See about doing a Duff's #unroll where practicable
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
// Apron so we don't need to special case first rows.
up := make([]u8, row_stride_out, context.temp_allocator)
@@ -1457,18 +1447,18 @@ defilter_less_than_8 :: proc(params: ^Filter_Params) -> (ok: bool) #no_bounds_ch
}
}
return
return true
}
// @(optimization_mode="speed")
defilter_16 :: proc(params: ^Filter_Params) -> (ok: bool) {
defilter_16 :: proc(params: ^Filter_Params) -> bool {
using params
ok = true
stride := channels * 2
row_stride := width * stride
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
// TODO: See about doing a Duff's #unroll where practicable
// Apron so we don't need to special case first rows.
up := make([]u8, row_stride, context.temp_allocator)
@@ -1518,7 +1508,7 @@ defilter_16 :: proc(params: ^Filter_Params) -> (ok: bool) {
dest = dest[row_stride:]
}
return
return true
}
defilter :: proc(img: ^Image, filter_bytes: ^bytes.Buffer, header: ^image.PNG_IHDR, options: Options) -> (err: Error) {
@@ -1637,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() {
+4
View File
@@ -0,0 +1,4 @@
//+build js
package png
load :: proc{load_from_bytes, load_from_context}
+19
View File
@@ -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
View File
@@ -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.
*/
+6
View File
@@ -0,0 +1,6 @@
//+build js
package qoi
save :: proc{save_to_buffer}
load :: proc{load_from_bytes, load_from_context}
+37
View File
@@ -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
View File
@@ -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 {
+5
View File
@@ -0,0 +1,5 @@
//+build js
package tga
save :: proc{save_to_buffer}
load :: proc{load_from_bytes, load_from_context}
+34
View File
@@ -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
}
}
+2 -2
View File
@@ -283,7 +283,7 @@ wasm_memory_atomic_wait32 :: proc(ptr: ^u32, expected: u32, timeout_ns: i64) -
wasm_memory_atomic_notify32 :: proc(ptr: ^u32, waiters: u32) -> (waiters_woken_up: u32) ---
// x86 Targets (i386, amd64)
x86_cpuid :: proc(ax, cx: u32) -> (eax, ebc, ecx, edx: u32) ---
x86_cpuid :: proc(ax, cx: u32) -> (eax, ebx, ecx, edx: u32) ---
x86_xgetbv :: proc(cx: u32) -> (eax, edx: u32) ---
@@ -305,4 +305,4 @@ valgrind_client_request :: proc(default: uintptr, request: uintptr, a0, a1, a2,
// Internal compiler use only
__entry_point :: proc() ---
__entry_point :: proc() ---
+4 -4
View 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))
}
+61 -14
View File
@@ -9,6 +9,13 @@ Arena_Kind :: enum uint {
Buffer = 2, // Uses a fixed sized buffer.
}
/*
Arena is a generalized arena allocator that supports 3 different variants.
Growing: A linked list of `Memory_Block`s allocated with virtual memory.
Static: A single `Memory_Block` allocated with virtual memory.
Buffer: A single `Memory_Block` created from a user provided []byte.
*/
Arena :: struct {
kind: Arena_Kind,
curr_block: ^Memory_Block,
@@ -29,6 +36,8 @@ DEFAULT_ARENA_STATIC_RESERVE_SIZE :: mem.Gigabyte when size_of(uintptr) == 8 els
// Initialization of an `Arena` to be a `.Growing` variant.
// A growing arena is a linked list of `Memory_Block`s allocated with virtual memory.
@(require_results)
arena_init_growing :: proc(arena: ^Arena, reserved: uint = DEFAULT_ARENA_GROWING_MINIMUM_BLOCK_SIZE) -> (err: Allocator_Error) {
arena.kind = .Growing
@@ -39,6 +48,8 @@ arena_init_growing :: proc(arena: ^Arena, reserved: uint = DEFAULT_ARENA_GROWING
}
// Initialization of an `Arena` to be a `.Static` variant.
// A static arena contains a single `Memory_Block` allocated with virtual memory.
@(require_results)
arena_init_static :: proc(arena: ^Arena, reserved: uint, commit_size: uint = DEFAULT_ARENA_STATIC_COMMIT_SIZE) -> (err: Allocator_Error) {
arena.kind = .Static
@@ -48,6 +59,8 @@ arena_init_static :: proc(arena: ^Arena, reserved: uint, commit_size: uint = DEF
return
}
// Initialization of an `Arena` to be a `.Buffer` variant.
// A buffer arena contains single `Memory_Block` created from a user provided []byte.
@(require_results)
arena_init_buffer :: proc(arena: ^Arena, buffer: []byte) -> (err: Allocator_Error) {
if len(buffer) < size_of(Memory_Block) {
@@ -71,6 +84,7 @@ arena_init_buffer :: proc(arena: ^Arena, buffer: []byte) -> (err: Allocator_Erro
return
}
// Allocates memory from the provided arena.
@(require_results)
arena_alloc :: proc(arena: ^Arena, size: uint, alignment: uint, loc := #caller_location) -> (data: []byte, err: Allocator_Error) {
assert(alignment & (alignment-1) == 0, "non-power of two alignment", loc)
@@ -119,6 +133,7 @@ arena_alloc :: proc(arena: ^Arena, size: uint, alignment: uint, loc := #caller_l
return
}
// Resets the memory of a Static or Buffer arena to a specific `pos`ition (offset) and zeroes the previously used memory.
arena_static_reset_to :: proc(arena: ^Arena, pos: uint, loc := #caller_location) -> bool {
sync.mutex_guard(&arena.mutex)
@@ -140,50 +155,72 @@ arena_static_reset_to :: proc(arena: ^Arena, pos: uint, loc := #caller_location)
return false
}
// Frees the last memory block of a Growing Arena
arena_growing_free_last_memory_block :: proc(arena: ^Arena, loc := #caller_location) {
sync.mutex_guard(&arena.mutex)
if free_block := arena.curr_block; free_block != nil {
assert(arena.kind == .Growing, "expected a .Growing arena", loc)
arena.total_used -= free_block.used
arena.total_reserved -= free_block.reserved
arena.curr_block = free_block.prev
memory_block_dealloc(free_block)
}
}
arena_free_all :: proc(arena: ^Arena) {
// Deallocates all but the first memory block of the arena and resets the allocator's usage to 0.
arena_free_all :: proc(arena: ^Arena, loc := #caller_location) {
switch arena.kind {
case .Growing:
sync.mutex_guard(&arena.mutex)
for arena.curr_block != nil {
arena_growing_free_last_memory_block(arena)
// NOTE(bill): Free all but the first memory block (if it exists)
for arena.curr_block != nil && arena.curr_block.prev != nil {
arena_growing_free_last_memory_block(arena, loc)
}
arena.total_reserved = 0
// Zero the first block's memory
if arena.curr_block != nil {
mem.zero(arena.curr_block.base, int(arena.curr_block.used))
arena.curr_block.used = 0
}
arena.total_used = 0
case .Static, .Buffer:
arena_static_reset_to(arena, 0)
}
arena.total_used = 0
}
arena_destroy :: proc(arena: ^Arena) {
arena_free_all(arena)
if arena.kind != .Buffer {
// Frees all of the memory allocated by the arena and zeros all of the values of an arena.
// A buffer based arena does not `delete` the provided `[]byte` bufffer.
arena_destroy :: proc(arena: ^Arena, loc := #caller_location) {
sync.mutex_guard(&arena.mutex)
switch arena.kind {
case .Growing:
for arena.curr_block != nil {
arena_growing_free_last_memory_block(arena, loc)
}
case .Static:
memory_block_dealloc(arena.curr_block)
case .Buffer:
// nothing
}
arena.curr_block = nil
arena.curr_block = nil
arena.total_used = 0
arena.total_reserved = 0
arena.temp_count = 0
}
// Ability to bootstrap allocate a struct with an arena within the struct itself using the growing variant strategy.
arena_growing_bootstrap_new :: proc{
arena_growing_bootstrap_new_by_offset,
arena_growing_bootstrap_new_by_name,
}
// Ability to bootstrap allocate a struct with an arena within the struct itself using the static variant strategy.
arena_static_bootstrap_new :: proc{
arena_static_bootstrap_new_by_offset,
arena_static_bootstrap_new_by_name,
}
// Ability to bootstrap allocate a struct with an arena within the struct itself using the growing variant strategy.
@(require_results)
arena_growing_bootstrap_new_by_offset :: proc($T: typeid, offset_to_arena: uintptr, minimum_block_size: uint = DEFAULT_ARENA_GROWING_MINIMUM_BLOCK_SIZE) -> (ptr: ^T, err: Allocator_Error) {
bootstrap: Arena
@@ -199,11 +236,13 @@ arena_growing_bootstrap_new_by_offset :: proc($T: typeid, offset_to_arena: uintp
return
}
// Ability to bootstrap allocate a struct with an arena within the struct itself using the growing variant strategy.
@(require_results)
arena_growing_bootstrap_new_by_name :: proc($T: typeid, $field_name: string, minimum_block_size: uint = DEFAULT_ARENA_GROWING_MINIMUM_BLOCK_SIZE) -> (ptr: ^T, err: Allocator_Error) {
return arena_growing_bootstrap_new_by_offset(T, offset_of_by_string(T, field_name), minimum_block_size)
}
// Ability to bootstrap allocate a struct with an arena within the struct itself using the growing variant strategy.
@(require_results)
arena_static_bootstrap_new_by_offset :: proc($T: typeid, offset_to_arena: uintptr, reserved: uint) -> (ptr: ^T, err: Allocator_Error) {
bootstrap: Arena
@@ -219,17 +258,20 @@ arena_static_bootstrap_new_by_offset :: proc($T: typeid, offset_to_arena: uintpt
return
}
// Ability to bootstrap allocate a struct with an arena within the struct itself using the growing variant strategy.
@(require_results)
arena_static_bootstrap_new_by_name :: proc($T: typeid, $field_name: string, reserved: uint) -> (ptr: ^T, err: Allocator_Error) {
return arena_static_bootstrap_new_by_offset(T, offset_of_by_string(T, field_name), reserved)
}
// Create an `Allocator` from the provided `Arena`
@(require_results)
arena_allocator :: proc(arena: ^Arena) -> mem.Allocator {
return mem.Allocator{arena_allocator_proc, arena}
}
// The allocator procedured by an `Allocator` produced by `arena_allocator`
arena_allocator_proc :: proc(allocator_data: rawptr, mode: mem.Allocator_Mode,
size, alignment: int,
old_memory: rawptr, old_size: int,
@@ -241,17 +283,17 @@ arena_allocator_proc :: proc(allocator_data: rawptr, mode: mem.Allocator_Mode,
switch mode {
case .Alloc, .Alloc_Non_Zeroed:
return arena_alloc(arena, size, alignment)
return arena_alloc(arena, size, alignment, location)
case .Free:
err = .Mode_Not_Implemented
case .Free_All:
arena_free_all(arena)
arena_free_all(arena, location)
case .Resize:
old_data := ([^]byte)(old_memory)
switch {
case old_data == nil:
return arena_alloc(arena, size, alignment)
return arena_alloc(arena, size, alignment, location)
case size == old_size:
// return old memory
data = old_data[:size]
@@ -265,7 +307,7 @@ arena_allocator_proc :: proc(allocator_data: rawptr, mode: mem.Allocator_Mode,
return
}
new_memory := arena_alloc(arena, size, alignment) or_return
new_memory := arena_alloc(arena, size, alignment, location) or_return
if new_memory == nil {
return
}
@@ -286,12 +328,15 @@ arena_allocator_proc :: proc(allocator_data: rawptr, mode: mem.Allocator_Mode,
// An `Arena_Temp` is a way to produce temporary watermarks to reset a arena to a previous state.
// All uses of an `Arena_Temp` must be handled by ending them with `arena_temp_end` or ignoring them with `arena_temp_ignore`.
Arena_Temp :: struct {
arena: ^Arena,
block: ^Memory_Block,
used: uint,
}
// Begins the section of temporary arena memory.
@(require_results)
arena_temp_begin :: proc(arena: ^Arena, loc := #caller_location) -> (temp: Arena_Temp) {
assert(arena != nil, "nil arena", loc)
@@ -306,6 +351,7 @@ arena_temp_begin :: proc(arena: ^Arena, loc := #caller_location) -> (temp: Arena
return
}
// Ends the section of temporary arena memory by resetting the memory to the stored position.
arena_temp_end :: proc(temp: Arena_Temp, loc := #caller_location) {
assert(temp.arena != nil, "nil arena", loc)
arena := temp.arena
@@ -339,7 +385,7 @@ arena_temp_end :: proc(temp: Arena_Temp, loc := #caller_location) {
arena.temp_count -= 1
}
// Ignore the use of a `arena_temp_begin` entirely
// Ignore the use of a `arena_temp_begin` entirely by __not__ resetting to the stored position.
arena_temp_ignore :: proc(temp: Arena_Temp, loc := #caller_location) {
assert(temp.arena != nil, "nil arena", loc)
arena := temp.arena
@@ -349,6 +395,7 @@ arena_temp_ignore :: proc(temp: Arena_Temp, loc := #caller_location) {
arena.temp_count -= 1
}
// Asserts that all uses of `Arena_Temp` has been used by an `Arena`
arena_check_temp :: proc(arena: ^Arena, loc := #caller_location) {
assert(arena.temp_count == 0, "Arena_Temp not been ended", loc)
}
+744
View File
@@ -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
}
+416
View File
@@ -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,
}
+863
View File
@@ -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
}
+83
View File
@@ -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[:])
}
+159
View File
@@ -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
}
+46
View File
@@ -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
+206
View File
@@ -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`
}
+201
View File
@@ -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))
}
+273
View File
@@ -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,
}
+79
View File
@@ -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)
}
+32
View File
@@ -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()
}
+140
View File
@@ -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[:], {}
}
+177
View File
@@ -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()
}
+183
View File
@@ -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)
}
+354
View File
@@ -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
}
+407
View File
@@ -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
}
+367
View File
@@ -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
}
+235
View File
@@ -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);
}
*/
+1 -1
View File
@@ -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
+1 -1
View File
@@ -50,7 +50,7 @@ read_dir :: proc(fd: Handle, n: int, allocator := context.allocator) -> (fi: []F
continue
}
fullpath := make([]byte, len(dirpath)+1+len(filename))
fullpath := make([]byte, len(dirpath)+1+len(filename), context.temp_allocator)
copy(fullpath, dirpath)
copy(fullpath[len(dirpath):], "/")
copy(fullpath[len(dirpath)+1:], filename)
+2
View File
@@ -2,6 +2,7 @@ package os
import "core:strings"
import "core:mem"
import "core:runtime"
read_dir :: proc(fd: Handle, n: int, allocator := context.allocator) -> (fi: []File_Info, err: Errno) {
dirp: Dir
@@ -51,6 +52,7 @@ read_dir :: proc(fd: Handle, n: int, allocator := context.allocator) -> (fi: []F
continue
}
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator)
fullpath := strings.join( []string{ dirpath, filename }, "/", context.temp_allocator)
defer delete(fullpath, context.temp_allocator)
+6 -2
View File
@@ -2,6 +2,7 @@ package os
import win32 "core:sys/windows"
import "core:strings"
import "core:runtime"
read_dir :: proc(fd: Handle, n: int, allocator := context.allocator) -> (fi: []File_Info, err: Errno) {
find_data_to_file_info :: proc(base_path: string, d: ^win32.WIN32_FIND_DATAW) -> (fi: File_Info) {
@@ -65,13 +66,16 @@ read_dir :: proc(fd: Handle, n: int, allocator := context.allocator) -> (fi: []F
n = -1
size = 100
}
dfi := make([dynamic]File_Info, 0, size)
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator)
wpath: []u16
wpath, err = cleanpath_from_handle_u16(fd)
wpath, err = cleanpath_from_handle_u16(fd, context.temp_allocator)
if len(wpath) == 0 || err != ERROR_NONE {
return
}
dfi := make([dynamic]File_Info, 0, size)
wpath_search := make([]u16, len(wpath)+3, context.temp_allocator)
copy(wpath_search, wpath)
wpath_search[len(wpath)+0] = '\\'
+4
View File
@@ -1,6 +1,7 @@
package os
import win32 "core:sys/windows"
import "core:runtime"
// lookup_env gets the value of the environment variable named by the key
// If the variable is found in the environment the value (which can be empty) is returned and the boolean is true
@@ -18,6 +19,8 @@ lookup_env :: proc(key: string, allocator := context.allocator) -> (value: strin
return "", false
}
}
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator)
b := make([dynamic]u16, n, context.temp_allocator)
n = win32.GetEnvironmentVariableW(wkey, raw_data(b), u32(len(b)))
if n == 0 {
@@ -87,6 +90,7 @@ environ :: proc(allocator := context.allocator) -> []string {
// clear_env deletes all environment variables
clear_env :: proc() {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
envs := environ(context.temp_allocator)
for env in envs {
for j in 1..<len(env) {
+12
View File
@@ -2,6 +2,7 @@ package os
import win32 "core:sys/windows"
import "core:intrinsics"
import "core:runtime"
import "core:unicode/utf16"
is_path_separator :: proc(c: byte) -> bool {
@@ -327,6 +328,7 @@ get_std_handle :: proc "contextless" (h: uint) -> Handle {
exists :: proc(path: string) -> bool {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
wpath := win32.utf8_to_wstring(path, context.temp_allocator)
attribs := win32.GetFileAttributesW(wpath)
@@ -334,6 +336,7 @@ exists :: proc(path: string) -> bool {
}
is_file :: proc(path: string) -> bool {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
wpath := win32.utf8_to_wstring(path, context.temp_allocator)
attribs := win32.GetFileAttributesW(wpath)
@@ -344,6 +347,7 @@ is_file :: proc(path: string) -> bool {
}
is_dir :: proc(path: string) -> bool {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
wpath := win32.utf8_to_wstring(path, context.temp_allocator)
attribs := win32.GetFileAttributesW(wpath)
@@ -359,6 +363,8 @@ is_dir :: proc(path: string) -> bool {
get_current_directory :: proc(allocator := context.allocator) -> string {
win32.AcquireSRWLockExclusive(&cwd_lock)
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator)
sz_utf16 := win32.GetCurrentDirectoryW(0, nil)
dir_buf_wstr := make([]u16, sz_utf16, context.temp_allocator) // the first time, it _includes_ the NUL.
@@ -387,6 +393,7 @@ set_current_directory :: proc(path: string) -> (err: Errno) {
change_directory :: proc(path: string) -> (err: Errno) {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
wpath := win32.utf8_to_wstring(path, context.temp_allocator)
if !win32.SetCurrentDirectoryW(wpath) {
@@ -396,6 +403,7 @@ change_directory :: proc(path: string) -> (err: Errno) {
}
make_directory :: proc(path: string, mode: u32 = 0) -> (err: Errno) {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
// Mode is unused on Windows, but is needed on *nix
wpath := win32.utf8_to_wstring(path, context.temp_allocator)
@@ -407,6 +415,7 @@ make_directory :: proc(path: string, mode: u32 = 0) -> (err: Errno) {
remove_directory :: proc(path: string) -> (err: Errno) {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
wpath := win32.utf8_to_wstring(path, context.temp_allocator)
if !win32.RemoveDirectoryW(wpath) {
@@ -479,12 +488,14 @@ fix_long_path :: proc(path: string) -> string {
link :: proc(old_name, new_name: string) -> (err: Errno) {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
n := win32.utf8_to_wstring(fix_long_path(new_name))
o := win32.utf8_to_wstring(fix_long_path(old_name))
return Errno(win32.CreateHardLinkW(n, o, nil))
}
unlink :: proc(path: string) -> (err: Errno) {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
wpath := win32.utf8_to_wstring(path, context.temp_allocator)
if !win32.DeleteFileW(wpath) {
@@ -496,6 +507,7 @@ unlink :: proc(path: string) -> (err: Errno) {
rename :: proc(old_path, new_path: string) -> (err: Errno) {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
from := win32.utf8_to_wstring(old_path, context.temp_allocator)
to := win32.utf8_to_wstring(new_path, context.temp_allocator)
+5 -3
View File
@@ -1,10 +1,12 @@
package os2
import "core:runtime"
// get_env retrieves the value of the environment variable named by the key
// It returns the value, which will be empty if the variable is not present
// To distinguish between an empty value and an unset value, use lookup_env
// NOTE: the value will be allocated with the supplied allocator
get_env :: proc(key: string, allocator := context.allocator) -> string {
get_env :: proc(key: string, allocator: runtime.Allocator) -> string {
value, _ := lookup_env(key, allocator)
return value
}
@@ -13,7 +15,7 @@ get_env :: proc(key: string, allocator := context.allocator) -> string {
// If the variable is found in the environment the value (which can be empty) is returned and the boolean is true
// Otherwise the returned value will be empty and the boolean will be false
// NOTE: the value will be allocated with the supplied allocator
lookup_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) {
lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string, found: bool) {
return _lookup_env(key, allocator)
}
@@ -36,7 +38,7 @@ clear_env :: proc() {
// environ returns a copy of strings representing the environment, in the form "key=value"
// NOTE: the slice of strings and the strings with be allocated using the supplied allocator
environ :: proc(allocator := context.allocator) -> []string {
environ :: proc(allocator: runtime.Allocator) -> []string {
return _environ(allocator)
}
+4 -2
View File
@@ -1,7 +1,9 @@
//+private
package os2
_get_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) {
import "core:runtime"
_get_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string, found: bool) {
//TODO
return
}
@@ -20,7 +22,7 @@ _clear_env :: proc() {
//TODO
}
_environ :: proc(allocator := context.allocator) -> []string {
_environ :: proc(allocator: runtime.Allocator) -> []string {
//TODO
return nil
}
+13 -1
View File
@@ -65,7 +65,19 @@ _environ :: proc(allocator: runtime.Allocator) -> []string {
}
defer win32.FreeEnvironmentStringsW(envs)
r := make([dynamic]string, 0, 50, allocator)
n := 0
for from, i, p := 0, 0, envs; true; i += 1 {
c := ([^]u16)(p)[i]
if c == 0 {
if i <= from {
break
}
n += 1
from = i + 1
}
}
r := make([dynamic]string, 0, n, allocator)
for from, i, p := 0, 0, envs; true; i += 1 {
c := ([^]u16)(p)[i]
if c == 0 {
+7 -14
View File
@@ -39,10 +39,8 @@ _file_allocator :: proc() -> runtime.Allocator {
}
_open :: proc(name: string, flags: File_Flags, perm: File_Mode) -> (^File, Error) {
name_cstr, allocated := _name_to_cstring(name)
defer if allocated {
delete(name_cstr)
}
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
name_cstr := _name_to_cstring(name)
flags_i: int
switch flags & O_RDONLY|O_WRONLY|O_RDWR {
@@ -254,7 +252,7 @@ _symlink :: proc(old_name, new_name: string) -> Error {
return _ok_or_error(unix.sys_symlink(old_name_cstr, new_name_cstr))
}
_read_link_cstr :: proc(name_cstr: cstring, allocator := context.allocator) -> (string, Error) {
_read_link_cstr :: proc(name_cstr: cstring, allocator: runtime.Allocator) -> (string, Error) {
bufsz : uint = 256
buf := make([]byte, bufsz, allocator)
for {
@@ -272,7 +270,7 @@ _read_link_cstr :: proc(name_cstr: cstring, allocator := context.allocator) -> (
}
}
_read_link :: proc(name: string, allocator := context.allocator) -> (string, Error) {
_read_link :: proc(name: string, allocator: runtime.Allocator) -> (string, Error) {
name_cstr, allocated := _name_to_cstring(name)
defer if allocated {
delete(name_cstr)
@@ -411,12 +409,7 @@ _is_dir_fd :: proc(fd: int) -> bool {
// defined as 512, however, it is well known that paths can exceed that limit.
// So, in theory you could have a path larger than the entire temp_allocator's
// buffer. Therefor, any large paths will use context.allocator.
_name_to_cstring :: proc(name: string) -> (cname: cstring, allocated: bool) {
if len(name) > _CSTRING_NAME_HEAP_THRESHOLD {
cname = strings.clone_to_cstring(name)
allocated = true
return
}
cname = strings.clone_to_cstring(name, context.temp_allocator)
return
@(private="file")
_temp_name_to_cstring :: proc(name: string) -> (cname: cstring) {
return strings.clone_to_cstring(name, context.temp_allocator)
}
+2 -1
View File
@@ -1,6 +1,7 @@
package os2
import "core:mem"
import "core:runtime"
import "core:strconv"
import "core:unicode/utf8"
@@ -74,7 +75,7 @@ read_ptr :: proc(f: ^File, data: rawptr, len: int) -> (n: int, err: Error) {
read_entire_file :: proc(name: string, allocator := context.allocator) -> (data: []byte, err: Error) {
read_entire_file :: proc(name: string, allocator: runtime.Allocator) -> (data: []byte, err: Error) {
f, ferr := open(name)
if ferr != nil {
return nil, ferr
+2 -2
View File
@@ -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)
@@ -229,7 +229,7 @@ _setwd :: proc(dir: string) -> Error {
return _ok_or_error(unix.sys_chdir(dir_cstr))
}
_get_full_path :: proc(fd: int, allocator := context.allocator) -> string {
_get_full_path :: proc(fd: int, allocator: runtime.Allocator) -> string {
PROC_FD_PATH :: "/proc/self/fd/"
buf: [32]u8
+3 -3
View File
@@ -83,7 +83,7 @@ _Stat :: struct {
}
_fstat :: proc(f: ^File, allocator := context.allocator) -> (File_Info, Error) {
_fstat :: proc(f: ^File, allocator: runtime.Allocator) -> (File_Info, Error) {
return _fstat_internal(f.impl.fd, allocator)
}
@@ -111,7 +111,7 @@ _fstat_internal :: proc(fd: int, allocator: runtime.Allocator) -> (File_Info, Er
}
// NOTE: _stat and _lstat are using _fstat to avoid a race condition when populating fullpath
_stat :: proc(name: string, allocator := context.allocator) -> (File_Info, Error) {
_stat :: proc(name: string, allocator: runtime.Allocator) -> (File_Info, Error) {
name_cstr, allocated := _name_to_cstring(name)
defer if allocated {
delete(name_cstr)
@@ -125,7 +125,7 @@ _stat :: proc(name: string, allocator := context.allocator) -> (File_Info, Error
return _fstat_internal(fd, allocator)
}
_lstat :: proc(name: string, allocator := context.allocator) -> (File_Info, Error) {
_lstat :: proc(name: string, allocator: runtime.Allocator) -> (File_Info, Error) {
name_cstr, allocated := _name_to_cstring(name)
defer if allocated {
delete(name_cstr)
+296 -34
View File
@@ -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)
@@ -277,8 +417,10 @@ foreign libc {
@(link_name="open") _unix_open :: proc(path: cstring, flags: i32, mode: u16) -> Handle ---
@(link_name="close") _unix_close :: proc(handle: Handle) -> c.int ---
@(link_name="read") _unix_read :: proc(handle: Handle, buffer: rawptr, count: int) -> int ---
@(link_name="write") _unix_write :: proc(handle: Handle, buffer: rawptr, count: int) -> int ---
@(link_name="read") _unix_read :: proc(handle: Handle, buffer: rawptr, count: c.size_t) -> int ---
@(link_name="write") _unix_write :: proc(handle: Handle, buffer: rawptr, count: c.size_t) -> int ---
@(link_name="pread") _unix_pread :: proc(handle: Handle, buffer: rawptr, count: c.size_t, offset: i64) -> int ---
@(link_name="pwrite") _unix_pwrite :: proc(handle: Handle, buffer: rawptr, count: c.size_t, offset: i64) -> int ---
@(link_name="lseek") _unix_lseek :: proc(fs: Handle, offset: int, whence: int) -> int ---
@(link_name="gettid") _unix_gettid :: proc() -> u64 ---
@(link_name="getpagesize") _unix_getpagesize :: proc() -> i32 ---
@@ -316,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) -> ! ---
}
@@ -353,6 +507,7 @@ open :: proc(path: string, flags: int = O_RDWR, mode: int = 0) -> (Handle, Errno
flags = O_RDONLY
}
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
cstr := strings.clone_to_cstring(path, context.temp_allocator)
handle := _unix_open(cstr, i32(flags), u16(mode))
if handle == -1 {
@@ -385,45 +540,51 @@ close :: proc(fd: Handle) -> bool {
@(private)
MAX_RW :: 0x7fffffff // The limit on Darwin is max(i32), trying to read/write more than that fails.
write :: proc(fd: Handle, data: []u8) -> (int, Errno) {
assert(fd != -1)
bytes_total := len(data)
bytes_written_total := 0
for bytes_written_total < bytes_total {
bytes_to_write := min(bytes_total - bytes_written_total, MAX_RW)
slice := data[bytes_written_total:bytes_written_total + bytes_to_write]
bytes_written := _unix_write(fd, raw_data(slice), bytes_to_write)
if bytes_written == -1 {
return bytes_written_total, 1
}
bytes_written_total += bytes_written
write :: proc(fd: Handle, data: []byte) -> (int, Errno) {
if len(data) == 0 {
return 0, ERROR_NONE
}
return bytes_written_total, 0
bytes_written := _unix_write(fd, raw_data(data), c.size_t(len(data)))
if bytes_written < 0 {
return -1, Errno(get_last_error())
}
return bytes_written, ERROR_NONE
}
read :: proc(fd: Handle, data: []u8) -> (int, Errno) {
assert(fd != -1)
bytes_total := len(data)
bytes_read_total := 0
for bytes_read_total < bytes_total {
bytes_to_read := min(bytes_total - bytes_read_total, MAX_RW)
slice := data[bytes_read_total:bytes_read_total + bytes_to_read]
bytes_read := _unix_read(fd, raw_data(slice), bytes_to_read)
if bytes_read == -1 {
return bytes_read_total, 1
}
if bytes_read == 0 {
break
}
bytes_read_total += bytes_read
if len(data) == 0 {
return 0, ERROR_NONE
}
return bytes_read_total, 0
bytes_read := _unix_read(fd, raw_data(data), c.size_t(len(data)))
if bytes_read < 0 {
return -1, Errno(get_last_error())
}
return bytes_read, ERROR_NONE
}
read_at :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Errno) {
if len(data) == 0 {
return 0, ERROR_NONE
}
bytes_read := _unix_pread(fd, raw_data(data), c.size_t(len(data)), offset)
if bytes_read < 0 {
return -1, Errno(get_last_error())
}
return bytes_read, ERROR_NONE
}
write_at :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Errno) {
if len(data) == 0 {
return 0, ERROR_NONE
}
bytes_written := _unix_pwrite(fd, raw_data(data), c.size_t(len(data)), offset)
if bytes_written < 0 {
return -1, Errno(get_last_error())
}
return bytes_written, ERROR_NONE
}
seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Errno) {
@@ -508,24 +669,28 @@ is_file :: proc {is_file_path, is_file_handle}
is_dir :: proc {is_dir_path, is_dir_handle}
exists :: proc(path: string) -> bool {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
cpath := strings.clone_to_cstring(path, context.temp_allocator)
res := _unix_access(cpath, O_RDONLY)
return res == 0
}
rename :: proc(old: string, new: string) -> bool {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
old_cstr := strings.clone_to_cstring(old, context.temp_allocator)
new_cstr := strings.clone_to_cstring(new, context.temp_allocator)
return _unix_rename(old_cstr, new_cstr) != -1
}
remove :: proc(path: string) -> bool {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
path_cstr := strings.clone_to_cstring(path, context.temp_allocator)
return _unix_remove(path_cstr) != -1
}
@private
_stat :: proc(path: string) -> (OS_Stat, Errno) {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
cstr := strings.clone_to_cstring(path, context.temp_allocator)
s: OS_Stat
@@ -538,6 +703,7 @@ _stat :: proc(path: string) -> (OS_Stat, Errno) {
@private
_lstat :: proc(path: string) -> (OS_Stat, Errno) {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
cstr := strings.clone_to_cstring(path, context.temp_allocator)
s: OS_Stat
@@ -603,6 +769,7 @@ _readdir :: proc(dirp: Dir) -> (entry: Dirent, err: Errno, end_of_stream: bool)
@private
_readlink :: proc(path: string) -> (string, Errno) {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == context.allocator)
path_cstr := strings.clone_to_cstring(path, context.temp_allocator)
bufsz : uint = 256
@@ -640,6 +807,7 @@ absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Errno) {
rel = "."
}
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == context.allocator)
rel_cstr := strings.clone_to_cstring(rel, context.temp_allocator)
path_ptr := _unix_realpath(rel_cstr, nil)
@@ -655,6 +823,7 @@ absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Errno) {
}
access :: proc(path: string, mask: int) -> bool {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
cstr := strings.clone_to_cstring(path, context.temp_allocator)
return _unix_access(cstr, mask) == 0
}
@@ -679,6 +848,7 @@ heap_free :: proc(ptr: rawptr) {
}
lookup_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator)
path_str := strings.clone_to_cstring(key, context.temp_allocator)
cstr := _unix_getenv(path_str)
if cstr == nil {
@@ -710,6 +880,7 @@ get_current_directory :: proc() -> string {
}
set_current_directory :: proc(path: string) -> (err: Errno) {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
cstr := strings.clone_to_cstring(path, context.temp_allocator)
res := _unix_chdir(cstr)
if res == -1 {
@@ -719,6 +890,7 @@ set_current_directory :: proc(path: string) -> (err: Errno) {
}
make_directory :: proc(path: string, mode: u16 = 0o775) -> Errno {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
path_cstr := strings.clone_to_cstring(path, context.temp_allocator)
res := _unix_mkdir(path_cstr, mode)
if res == -1 {
@@ -743,12 +915,14 @@ current_thread_id :: proc "contextless" () -> int {
}
dlopen :: proc(filename: string, flags: int) -> rawptr {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
cstr := strings.clone_to_cstring(filename, context.temp_allocator)
handle := _unix_dlopen(cstr, flags)
return handle
}
dlsym :: proc(handle: rawptr, symbol: string) -> rawptr {
assert(handle != nil)
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
cstr := strings.clone_to_cstring(symbol, context.temp_allocator)
proc_handle := _unix_dlsym(handle, cstr)
return proc_handle
@@ -793,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
}
+19 -1
View File
@@ -309,6 +309,7 @@ get_last_error :: proc "contextless" () -> int {
}
open :: proc(path: string, flags: int = O_RDONLY, mode: int = 0) -> (Handle, Errno) {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
cstr := strings.clone_to_cstring(path, context.temp_allocator)
handle := _unix_open(cstr, c.int(flags), c.int(mode))
if handle == -1 {
@@ -361,6 +362,7 @@ file_size :: proc(fd: Handle) -> (i64, Errno) {
}
rename :: proc(old_path, new_path: string) -> Errno {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
old_path_cstr := strings.clone_to_cstring(old_path, context.temp_allocator)
new_path_cstr := strings.clone_to_cstring(new_path, context.temp_allocator)
res := _unix_rename(old_path_cstr, new_path_cstr)
@@ -371,6 +373,7 @@ rename :: proc(old_path, new_path: string) -> Errno {
}
remove :: proc(path: string) -> Errno {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
path_cstr := strings.clone_to_cstring(path, context.temp_allocator)
res := _unix_unlink(path_cstr)
if res == -1 {
@@ -380,6 +383,7 @@ remove :: proc(path: string) -> Errno {
}
make_directory :: proc(path: string, mode: mode_t = 0o775) -> Errno {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
path_cstr := strings.clone_to_cstring(path, context.temp_allocator)
res := _unix_mkdir(path_cstr, mode)
if res == -1 {
@@ -389,6 +393,7 @@ make_directory :: proc(path: string, mode: mode_t = 0o775) -> Errno {
}
remove_directory :: proc(path: string) -> Errno {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
path_cstr := strings.clone_to_cstring(path, context.temp_allocator)
res := _unix_rmdir(path_cstr)
if res == -1 {
@@ -474,6 +479,7 @@ last_write_time_by_name :: proc(name: string) -> (File_Time, Errno) {
@private
_stat :: proc(path: string) -> (OS_Stat, Errno) {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
cstr := strings.clone_to_cstring(path, context.temp_allocator)
s: OS_Stat = ---
result := _unix_lstat(cstr, &s)
@@ -485,6 +491,7 @@ _stat :: proc(path: string) -> (OS_Stat, Errno) {
@private
_lstat :: proc(path: string) -> (OS_Stat, Errno) {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
cstr := strings.clone_to_cstring(path, context.temp_allocator)
// deliberately uninitialized
@@ -550,6 +557,8 @@ _readdir :: proc(dirp: Dir) -> (entry: Dirent, err: Errno, end_of_stream: bool)
@private
_readlink :: proc(path: string) -> (string, Errno) {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == context.allocator)
path_cstr := strings.clone_to_cstring(path, context.temp_allocator)
bufsz : uint = MAX_PATH
@@ -567,7 +576,8 @@ _readlink :: proc(path: string) -> (string, Errno) {
return strings.string_from_ptr(&buf[0], rc), ERROR_NONE
}
}
unreachable()
return "", Errno{}
}
// XXX FreeBSD
@@ -580,6 +590,7 @@ absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Errno) {
if rel == "" {
rel = "."
}
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == context.allocator)
rel_cstr := strings.clone_to_cstring(rel, context.temp_allocator)
@@ -596,6 +607,8 @@ absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Errno) {
}
access :: proc(path: string, mask: int) -> (bool, Errno) {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
cstr := strings.clone_to_cstring(path, context.temp_allocator)
result := _unix_access(cstr, c.int(mask))
if result == -1 {
@@ -626,6 +639,8 @@ heap_free :: proc(ptr: rawptr) {
}
lookup_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator)
path_str := strings.clone_to_cstring(key, context.temp_allocator)
cstr := _unix_getenv(path_str)
if cstr == nil {
@@ -660,6 +675,7 @@ get_current_directory :: proc() -> string {
}
set_current_directory :: proc(path: string) -> (err: Errno) {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
cstr := strings.clone_to_cstring(path, context.temp_allocator)
res := _unix_chdir(cstr)
if res == -1 do return Errno(get_last_error())
@@ -676,12 +692,14 @@ current_thread_id :: proc "contextless" () -> int {
}
dlopen :: proc(filename: string, flags: int) -> rawptr {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
cstr := strings.clone_to_cstring(filename, context.temp_allocator)
handle := _unix_dlopen(cstr, c.int(flags))
return handle
}
dlsym :: proc(handle: rawptr, symbol: string) -> rawptr {
assert(handle != nil)
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
cstr := strings.clone_to_cstring(symbol, context.temp_allocator)
proc_handle := _unix_dlsym(handle, cstr)
return proc_handle
+275 -2
View File
@@ -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
}
+363 -168
View File
@@ -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
@@ -236,13 +398,13 @@ S_IRUSR :: 0o0400 // R for owner
S_IWUSR :: 0o0200 // W for owner
S_IXUSR :: 0o0100 // X for owner
// Read, write, execute/search by group
// Read, write, execute/search by group
S_IRWXG :: 0o0070 // RWX mask for group
S_IRGRP :: 0o0040 // R for group
S_IWGRP :: 0o0020 // W for group
S_IXGRP :: 0o0010 // X for group
// Read, write, execute/search by others
// Read, write, execute/search by others
S_IRWXO :: 0o0007 // RWX mask for other
S_IROTH :: 0o0004 // R for other
S_IWOTH :: 0o0002 // W for other
@@ -270,136 +432,6 @@ AT_FDCWD :: ~uintptr(99) /* -100 */
AT_REMOVEDIR :: uintptr(0x200)
AT_SYMLINK_NOFOLLOW :: uintptr(0x100)
_unix_personality :: proc(persona: u64) -> int {
return int(intrinsics.syscall(unix.SYS_personality, uintptr(persona)))
}
_unix_fork :: proc() -> Pid {
when ODIN_ARCH != .arm64 {
res := int(intrinsics.syscall(unix.SYS_fork))
} else {
res := int(intrinsics.syscall(unix.SYS_clone, unix.SIGCHLD))
}
return -1 if res < 0 else Pid(res)
}
_unix_open :: proc(path: cstring, flags: int, mode: int = 0o000) -> Handle {
when ODIN_ARCH != .arm64 {
res := int(intrinsics.syscall(unix.SYS_open, uintptr(rawptr(path)), uintptr(flags), uintptr(mode)))
} else { // NOTE: arm64 does not have open
res := int(intrinsics.syscall(unix.SYS_openat, AT_FDCWD, uintptr(rawptr(path)), uintptr(flags), uintptr(mode)))
}
return -1 if res < 0 else Handle(res)
}
_unix_close :: proc(fd: Handle) -> int {
return int(intrinsics.syscall(unix.SYS_close, uintptr(fd)))
}
_unix_read :: proc(fd: Handle, buf: rawptr, size: uint) -> int {
return int(intrinsics.syscall(unix.SYS_read, uintptr(fd), uintptr(buf), uintptr(size)))
}
_unix_write :: proc(fd: Handle, buf: rawptr, size: uint) -> int {
return int(intrinsics.syscall(unix.SYS_write, uintptr(fd), uintptr(buf), uintptr(size)))
}
_unix_seek :: proc(fd: Handle, offset: i64, whence: int) -> i64 {
when ODIN_ARCH == .amd64 || ODIN_ARCH == .arm64 {
return i64(intrinsics.syscall(unix.SYS_lseek, uintptr(fd), uintptr(offset), uintptr(whence)))
} else {
low := uintptr(offset & 0xFFFFFFFF)
high := uintptr(offset >> 32)
result: i64
res := i64(intrinsics.syscall(unix.SYS__llseek, uintptr(fd), high, low, uintptr(&result), uintptr(whence)))
return -1 if res < 0 else result
}
}
_unix_stat :: proc(path: cstring, stat: ^OS_Stat) -> int {
when ODIN_ARCH == .amd64 {
return int(intrinsics.syscall(unix.SYS_stat, uintptr(rawptr(path)), uintptr(stat)))
} else when ODIN_ARCH != .arm64 {
return int(intrinsics.syscall(unix.SYS_stat64, uintptr(rawptr(path)), uintptr(stat)))
} else { // NOTE: arm64 does not have stat
return int(intrinsics.syscall(unix.SYS_fstatat, AT_FDCWD, uintptr(rawptr(path)), uintptr(stat), 0))
}
}
_unix_fstat :: proc(fd: Handle, stat: ^OS_Stat) -> int {
when ODIN_ARCH == .amd64 || ODIN_ARCH == .arm64 {
return int(intrinsics.syscall(unix.SYS_fstat, uintptr(fd), uintptr(stat)))
} else {
return int(intrinsics.syscall(unix.SYS_fstat64, uintptr(fd), uintptr(stat)))
}
}
_unix_lstat :: proc(path: cstring, stat: ^OS_Stat) -> int {
when ODIN_ARCH == .amd64 {
return int(intrinsics.syscall(unix.SYS_lstat, uintptr(rawptr(path)), uintptr(stat)))
} else when ODIN_ARCH != .arm64 {
return int(intrinsics.syscall(unix.SYS_lstat64, uintptr(rawptr(path)), uintptr(stat)))
} else { // NOTE: arm64 does not have any lstat
return int(intrinsics.syscall(unix.SYS_fstatat, AT_FDCWD, uintptr(rawptr(path)), uintptr(stat), AT_SYMLINK_NOFOLLOW))
}
}
_unix_readlink :: proc(path: cstring, buf: rawptr, bufsiz: uint) -> int {
when ODIN_ARCH != .arm64 {
return int(intrinsics.syscall(unix.SYS_readlink, uintptr(rawptr(path)), uintptr(buf), uintptr(bufsiz)))
} else { // NOTE: arm64 does not have readlink
return int(intrinsics.syscall(unix.SYS_readlinkat, AT_FDCWD, uintptr(rawptr(path)), uintptr(buf), uintptr(bufsiz)))
}
}
_unix_access :: proc(path: cstring, mask: int) -> int {
when ODIN_ARCH != .arm64 {
return int(intrinsics.syscall(unix.SYS_access, uintptr(rawptr(path)), uintptr(mask)))
} else { // NOTE: arm64 does not have access
return int(intrinsics.syscall(unix.SYS_faccessat, AT_FDCWD, uintptr(rawptr(path)), uintptr(mask)))
}
}
_unix_getcwd :: proc(buf: rawptr, size: uint) -> int {
return int(intrinsics.syscall(unix.SYS_getcwd, uintptr(buf), uintptr(size)))
}
_unix_chdir :: proc(path: cstring) -> int {
return int(intrinsics.syscall(unix.SYS_chdir, uintptr(rawptr(path))))
}
_unix_rename :: proc(old, new: cstring) -> int {
when ODIN_ARCH != .arm64 {
return int(intrinsics.syscall(unix.SYS_rename, uintptr(rawptr(old)), uintptr(rawptr(new))))
} else { // NOTE: arm64 does not have rename
return int(intrinsics.syscall(unix.SYS_renameat, AT_FDCWD, uintptr(rawptr(old)), uintptr(rawptr(new))))
}
}
_unix_unlink :: proc(path: cstring) -> int {
when ODIN_ARCH != .arm64 {
return int(intrinsics.syscall(unix.SYS_unlink, uintptr(rawptr(path))))
} else { // NOTE: arm64 does not have unlink
return int(intrinsics.syscall(unix.SYS_unlinkat, AT_FDCWD, uintptr(rawptr(path)), 0))
}
}
_unix_rmdir :: proc(path: cstring) -> int {
when ODIN_ARCH != .arm64 {
return int(intrinsics.syscall(unix.SYS_rmdir, uintptr(rawptr(path))))
} else { // NOTE: arm64 does not have rmdir
return int(intrinsics.syscall(unix.SYS_unlinkat, AT_FDCWD, uintptr(rawptr(path)), AT_REMOVEDIR))
}
}
_unix_mkdir :: proc(path: cstring, mode: u32) -> int {
when ODIN_ARCH != .arm64 {
return int(intrinsics.syscall(unix.SYS_mkdir, uintptr(rawptr(path)), uintptr(mode)))
} else { // NOTE: arm64 does not have mkdir
return int(intrinsics.syscall(unix.SYS_mkdirat, AT_FDCWD, uintptr(rawptr(path)), uintptr(mode)))
}
}
foreign libc {
@(link_name="__errno_location") __errno_location :: proc() -> ^int ---
@@ -415,6 +447,7 @@ foreign libc {
@(link_name="free") _unix_free :: proc(ptr: rawptr) ---
@(link_name="realloc") _unix_realloc :: proc(ptr: rawptr, size: c.size_t) -> rawptr ---
@(link_name="execvp") _unix_execvp :: proc(path: cstring, argv: [^]cstring) -> int ---
@(link_name="getenv") _unix_getenv :: proc(cstring) -> cstring ---
@(link_name="putenv") _unix_putenv :: proc(cstring) -> c.int ---
@(link_name="realpath") _unix_realpath :: proc(path: cstring, resolved_path: rawptr) -> rawptr ---
@@ -426,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 {
@@ -447,7 +483,7 @@ get_last_error :: proc "contextless" () -> int {
}
personality :: proc(persona: u64) -> (Errno) {
res := _unix_personality(persona)
res := unix.sys_personality(persona)
if res == -1 {
return _get_errno(res)
}
@@ -455,28 +491,48 @@ personality :: proc(persona: u64) -> (Errno) {
}
fork :: proc() -> (Pid, Errno) {
pid := _unix_fork()
pid := unix.sys_fork()
if pid == -1 {
return -1, _get_errno(int(pid))
return -1, _get_errno(pid)
}
return pid, ERROR_NONE
return Pid(pid), ERROR_NONE
}
open :: proc(path: string, flags: int = O_RDONLY, mode: int = 0) -> (Handle, Errno) {
cstr := strings.clone_to_cstring(path, context.temp_allocator)
handle := _unix_open(cstr, flags, mode)
if handle < 0 {
return INVALID_HANDLE, _get_errno(int(handle))
execvp :: proc(path: string, args: []string) -> Errno {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
path_cstr := strings.clone_to_cstring(path, context.temp_allocator)
args_cstrs := make([]cstring, len(args) + 2, context.temp_allocator)
args_cstrs[0] = strings.clone_to_cstring(path, context.temp_allocator)
for i := 0; i < len(args); i += 1 {
args_cstrs[i+1] = strings.clone_to_cstring(args[i], context.temp_allocator)
}
return handle, ERROR_NONE
_unix_execvp(path_cstr, raw_data(args_cstrs))
return Errno(get_last_error())
}
open :: proc(path: string, flags: int = O_RDONLY, mode: int = 0o000) -> (Handle, Errno) {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
cstr := strings.clone_to_cstring(path, context.temp_allocator)
handle := unix.sys_open(cstr, flags, uint(mode))
if handle < 0 {
return INVALID_HANDLE, _get_errno(handle)
}
return Handle(handle), ERROR_NONE
}
close :: proc(fd: Handle) -> Errno {
return _get_errno(_unix_close(fd))
return _get_errno(unix.sys_close(int(fd)))
}
read :: proc(fd: Handle, data: []byte) -> (int, Errno) {
bytes_read := _unix_read(fd, &data[0], c.size_t(len(data)))
if len(data) == 0 {
return 0, ERROR_NONE
}
bytes_read := unix.sys_read(int(fd), raw_data(data), len(data))
if bytes_read < 0 {
return -1, _get_errno(bytes_read)
}
@@ -487,50 +543,78 @@ write :: proc(fd: Handle, data: []byte) -> (int, Errno) {
if len(data) == 0 {
return 0, ERROR_NONE
}
bytes_written := _unix_write(fd, &data[0], uint(len(data)))
bytes_written := unix.sys_write(int(fd), raw_data(data), len(data))
if bytes_written < 0 {
return -1, _get_errno(bytes_written)
}
return int(bytes_written), ERROR_NONE
return bytes_written, ERROR_NONE
}
read_at :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Errno) {
if len(data) == 0 {
return 0, ERROR_NONE
}
bytes_read := unix.sys_pread(int(fd), raw_data(data), len(data), offset)
if bytes_read < 0 {
return -1, _get_errno(bytes_read)
}
return bytes_read, ERROR_NONE
}
write_at :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Errno) {
if len(data) == 0 {
return 0, ERROR_NONE
}
bytes_written := unix.sys_pwrite(int(fd), raw_data(data), uint(len(data)), offset)
if bytes_written < 0 {
return -1, _get_errno(bytes_written)
}
return bytes_written, ERROR_NONE
}
seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Errno) {
res := _unix_seek(fd, offset, whence)
res := unix.sys_lseek(int(fd), offset, whence)
if res < 0 {
return -1, _get_errno(int(res))
}
return res, ERROR_NONE
return i64(res), ERROR_NONE
}
file_size :: proc(fd: Handle) -> (i64, Errno) {
// deliberately uninitialized; the syscall fills this buffer for us
s: OS_Stat = ---
result := _unix_fstat(fd, &s)
if result < 0 {
return 0, _get_errno(result)
}
return max(s.size, 0), ERROR_NONE
// deliberately uninitialized; the syscall fills this buffer for us
s: OS_Stat = ---
result := unix.sys_fstat(int(fd), rawptr(&s))
if result < 0 {
return 0, _get_errno(result)
}
return max(s.size, 0), ERROR_NONE
}
rename :: proc(old_path, new_path: string) -> Errno {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
old_path_cstr := strings.clone_to_cstring(old_path, context.temp_allocator)
new_path_cstr := strings.clone_to_cstring(new_path, context.temp_allocator)
return _get_errno(_unix_rename(old_path_cstr, new_path_cstr))
return _get_errno(unix.sys_rename(old_path_cstr, new_path_cstr))
}
remove :: proc(path: string) -> Errno {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
path_cstr := strings.clone_to_cstring(path, context.temp_allocator)
return _get_errno(_unix_unlink(path_cstr))
return _get_errno(unix.sys_unlink(path_cstr))
}
make_directory :: proc(path: string, mode: u32 = 0o775) -> Errno {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
path_cstr := strings.clone_to_cstring(path, context.temp_allocator)
return _get_errno(_unix_mkdir(path_cstr, mode))
return _get_errno(unix.sys_mkdir(path_cstr, uint(mode)))
}
remove_directory :: proc(path: string) -> Errno {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
path_cstr := strings.clone_to_cstring(path, context.temp_allocator)
return _get_errno(_unix_rmdir(path_cstr))
return _get_errno(unix.sys_rmdir(path_cstr))
}
is_file_handle :: proc(fd: Handle) -> bool {
@@ -582,8 +666,9 @@ is_file :: proc {is_file_path, is_file_handle}
is_dir :: proc {is_dir_path, is_dir_handle}
exists :: proc(path: string) -> bool {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
cpath := strings.clone_to_cstring(path, context.temp_allocator)
res := _unix_access(cpath, O_RDONLY)
res := unix.sys_access(cpath, O_RDONLY)
return res == 0
}
@@ -617,11 +702,12 @@ last_write_time_by_name :: proc(name: string) -> (File_Time, Errno) {
@private
_stat :: proc(path: string) -> (OS_Stat, Errno) {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
cstr := strings.clone_to_cstring(path, context.temp_allocator)
// deliberately uninitialized; the syscall fills this buffer for us
s: OS_Stat = ---
result := _unix_stat(cstr, &s)
result := unix.sys_stat(cstr, &s)
if result < 0 {
return s, _get_errno(result)
}
@@ -630,11 +716,12 @@ _stat :: proc(path: string) -> (OS_Stat, Errno) {
@private
_lstat :: proc(path: string) -> (OS_Stat, Errno) {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
cstr := strings.clone_to_cstring(path, context.temp_allocator)
// deliberately uninitialized; the syscall fills this buffer for us
s: OS_Stat = ---
result := _unix_lstat(cstr, &s)
result := unix.sys_lstat(cstr, &s)
if result < 0 {
return s, _get_errno(result)
}
@@ -645,7 +732,7 @@ _lstat :: proc(path: string) -> (OS_Stat, Errno) {
_fstat :: proc(fd: Handle) -> (OS_Stat, Errno) {
// deliberately uninitialized; the syscall fills this buffer for us
s: OS_Stat = ---
result := _unix_fstat(fd, &s)
result := unix.sys_fstat(int(fd), rawptr(&s))
if result < 0 {
return s, _get_errno(result)
}
@@ -697,12 +784,13 @@ _readdir :: proc(dirp: Dir) -> (entry: Dirent, err: Errno, end_of_stream: bool)
@private
_readlink :: proc(path: string) -> (string, Errno) {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == context.allocator)
path_cstr := strings.clone_to_cstring(path, context.temp_allocator)
bufsz : uint = 256
buf := make([]byte, bufsz)
for {
rc := _unix_readlink(path_cstr, &(buf[0]), bufsz)
rc := unix.sys_readlink(path_cstr, &(buf[0]), bufsz)
if rc < 0 {
delete(buf)
return "", _get_errno(rc)
@@ -732,6 +820,7 @@ absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Errno) {
if rel == "" {
rel = "."
}
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == context.allocator)
rel_cstr := strings.clone_to_cstring(rel, context.temp_allocator)
@@ -748,8 +837,9 @@ absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Errno) {
}
access :: proc(path: string, mask: int) -> (bool, Errno) {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
cstr := strings.clone_to_cstring(path, context.temp_allocator)
result := _unix_access(cstr, mask)
result := unix.sys_access(cstr, mask)
if result < 0 {
return false, _get_errno(result)
}
@@ -778,6 +868,7 @@ heap_free :: proc(ptr: rawptr) {
}
lookup_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator)
path_str := strings.clone_to_cstring(key, context.temp_allocator)
// NOTE(tetra): Lifetime of 'cstr' is unclear, but _unix_free(cstr) segfaults.
cstr := _unix_getenv(path_str)
@@ -793,6 +884,7 @@ get_env :: proc(key: string, allocator := context.allocator) -> (value: string)
}
set_env :: proc(key, value: string) -> Errno {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
s := strings.concatenate({key, "=", value, "\x00"}, context.temp_allocator)
res := _unix_putenv(strings.unsafe_string_to_cstring(s))
if res < 0 {
@@ -802,6 +894,7 @@ set_env :: proc(key, value: string) -> Errno {
}
unset_env :: proc(key: string) -> Errno {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
s := strings.clone_to_cstring(key, context.temp_allocator)
res := _unix_putenv(s)
if res < 0 {
@@ -817,10 +910,10 @@ get_current_directory :: proc() -> string {
page_size := get_page_size()
buf := make([dynamic]u8, page_size)
for {
#no_bounds_check res := _unix_getcwd(&buf[0], uint(len(buf)))
#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)
@@ -832,8 +925,9 @@ get_current_directory :: proc() -> string {
}
set_current_directory :: proc(path: string) -> (err: Errno) {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
cstr := strings.clone_to_cstring(path, context.temp_allocator)
res := _unix_chdir(cstr)
res := unix.sys_chdir(cstr)
if res < 0 {
return _get_errno(res)
}
@@ -850,12 +944,14 @@ current_thread_id :: proc "contextless" () -> int {
}
dlopen :: proc(filename: string, flags: int) -> rawptr {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
cstr := strings.clone_to_cstring(filename, context.temp_allocator)
handle := _unix_dlopen(cstr, c.int(flags))
return handle
}
dlsym :: proc(handle: rawptr, symbol: string) -> rawptr {
assert(handle != nil)
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
cstr := strings.clone_to_cstring(symbol, context.temp_allocator)
proc_handle := _unix_dlsym(handle, cstr)
return proc_handle
@@ -892,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
}
+14 -1
View File
@@ -308,6 +308,7 @@ fork :: proc() -> (Pid, Errno) {
}
open :: proc(path: string, flags: int = O_RDONLY, mode: int = 0) -> (Handle, Errno) {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
cstr := strings.clone_to_cstring(path, context.temp_allocator)
handle := _unix_open(cstr, c.int(flags), c.int(mode))
if handle == -1 {
@@ -360,6 +361,7 @@ file_size :: proc(fd: Handle) -> (i64, Errno) {
}
rename :: proc(old_path, new_path: string) -> Errno {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
old_path_cstr := strings.clone_to_cstring(old_path, context.temp_allocator)
new_path_cstr := strings.clone_to_cstring(new_path, context.temp_allocator)
res := _unix_rename(old_path_cstr, new_path_cstr)
@@ -370,6 +372,7 @@ rename :: proc(old_path, new_path: string) -> Errno {
}
remove :: proc(path: string) -> Errno {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
path_cstr := strings.clone_to_cstring(path, context.temp_allocator)
res := _unix_unlink(path_cstr)
if res == -1 {
@@ -379,6 +382,7 @@ remove :: proc(path: string) -> Errno {
}
make_directory :: proc(path: string, mode: mode_t = 0o775) -> Errno {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
path_cstr := strings.clone_to_cstring(path, context.temp_allocator)
res := _unix_mkdir(path_cstr, mode)
if res == -1 {
@@ -388,6 +392,7 @@ make_directory :: proc(path: string, mode: mode_t = 0o775) -> Errno {
}
remove_directory :: proc(path: string) -> Errno {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
path_cstr := strings.clone_to_cstring(path, context.temp_allocator)
res := _unix_rmdir(path_cstr)
if res == -1 {
@@ -473,6 +478,7 @@ last_write_time_by_name :: proc(name: string) -> (File_Time, Errno) {
@private
_stat :: proc(path: string) -> (OS_Stat, Errno) {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
cstr := strings.clone_to_cstring(path, context.temp_allocator)
// deliberately uninitialized
@@ -486,6 +492,7 @@ _stat :: proc(path: string) -> (OS_Stat, Errno) {
@private
_lstat :: proc(path: string) -> (OS_Stat, Errno) {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
cstr := strings.clone_to_cstring(path, context.temp_allocator)
// deliberately uninitialized
@@ -552,6 +559,7 @@ _readdir :: proc(dirp: Dir) -> (entry: Dirent, err: Errno, end_of_stream: bool)
@private
_readlink :: proc(path: string) -> (string, Errno) {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == context.allocator)
path_cstr := strings.clone_to_cstring(path, context.temp_allocator)
bufsz : uint = MAX_PATH
@@ -569,7 +577,6 @@ _readlink :: proc(path: string) -> (string, Errno) {
return strings.string_from_ptr(&buf[0], rc), ERROR_NONE
}
}
unreachable()
}
// XXX OpenBSD
@@ -583,6 +590,7 @@ absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Errno) {
rel = "."
}
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == context.allocator)
rel_cstr := strings.clone_to_cstring(rel, context.temp_allocator)
path_ptr := _unix_realpath(rel_cstr, nil)
@@ -598,6 +606,7 @@ absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Errno) {
}
access :: proc(path: string, mask: int) -> (bool, Errno) {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
cstr := strings.clone_to_cstring(path, context.temp_allocator)
res := _unix_access(cstr, c.int(mask))
if res == -1 {
@@ -628,6 +637,7 @@ heap_free :: proc(ptr: rawptr) {
}
lookup_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator)
path_str := strings.clone_to_cstring(key, context.temp_allocator)
cstr := _unix_getenv(path_str)
if cstr == nil {
@@ -658,6 +668,7 @@ get_current_directory :: proc() -> string {
}
set_current_directory :: proc(path: string) -> (err: Errno) {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
cstr := strings.clone_to_cstring(path, context.temp_allocator)
res := _unix_chdir(cstr)
if res == -1 {
@@ -676,12 +687,14 @@ current_thread_id :: proc "contextless" () -> int {
}
dlopen :: proc(filename: string, flags: int) -> rawptr {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
cstr := strings.clone_to_cstring(filename, context.temp_allocator)
handle := _unix_dlopen(cstr, c.int(flags))
return handle
}
dlsym :: proc(handle: rawptr, symbol: string) -> rawptr {
assert(handle != nil)
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
cstr := strings.clone_to_cstring(symbol, context.temp_allocator)
proc_handle := _unix_dlsym(handle, cstr)
return proc_handle
+1
View File
@@ -134,6 +134,7 @@ _processor_core_count :: proc() -> int {
thread_count := 0
if !result && win32.GetLastError() == 122 && length > 0 {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
processors := make([]win32.SYSTEM_LOGICAL_PROCESSOR_INFORMATION, length, context.temp_allocator)
result = win32.GetLogicalProcessorInformation(&processors[0], &length)
+7 -3
View File
@@ -1,6 +1,7 @@
package os
import "core:time"
import "core:runtime"
import win32 "core:sys/windows"
@(private)
@@ -11,6 +12,7 @@ full_path_from_name :: proc(name: string, allocator := context.allocator) -> (pa
if name == "" {
name = "."
}
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator)
p := win32.utf8_to_utf16(name, context.temp_allocator)
buf := make([dynamic]u16, 100)
defer delete(buf)
@@ -36,6 +38,7 @@ _stat :: proc(name: string, create_file_attributes: u32, allocator := context.al
context.allocator = allocator
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator)
wname := win32.utf8_to_wstring(fix_long_path(name), context.temp_allocator)
fa: win32.WIN32_FILE_ATTRIBUTE_DATA
@@ -132,14 +135,15 @@ cleanpath_strip_prefix :: proc(buf: []u16) -> []u16 {
@(private)
cleanpath_from_handle :: proc(fd: Handle) -> (string, Errno) {
buf, err := cleanpath_from_handle_u16(fd)
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == context.allocator)
buf, err := cleanpath_from_handle_u16(fd, context.temp_allocator)
if err != 0 {
return "", err
}
return win32.utf16_to_utf8(buf, context.allocator) or_else "", err
}
@(private)
cleanpath_from_handle_u16 :: proc(fd: Handle) -> ([]u16, Errno) {
cleanpath_from_handle_u16 :: proc(fd: Handle, allocator: runtime.Allocator) -> ([]u16, Errno) {
if fd == 0 {
return nil, ERROR_INVALID_HANDLE
}
@@ -149,7 +153,7 @@ cleanpath_from_handle_u16 :: proc(fd: Handle) -> ([]u16, Errno) {
if n == 0 {
return nil, Errno(win32.GetLastError())
}
buf := make([]u16, max(n, win32.DWORD(260))+1, context.temp_allocator)
buf := make([]u16, max(n, win32.DWORD(260))+1, allocator)
buf_len := win32.GetFinalPathNameByHandleW(h, raw_data(buf), n, 0)
return buf[:buf_len], ERROR_NONE
}
+2 -1
View File
@@ -218,7 +218,6 @@ get_escape :: proc(chunk: string) -> (r: rune, next_chunk: string, err: Match_Er
//
// glob ignores file system errors
//
glob :: proc(pattern: string, allocator := context.allocator) -> (matches: []string, err: Match_Error) {
context.allocator = allocator
@@ -261,6 +260,8 @@ glob :: proc(pattern: string, allocator := context.allocator) -> (matches: []str
}
return
}
// Internal implementation of `glob`, not meant to be used by the user. Prefer `glob`.
_glob :: proc(dir, pattern: string, matches: ^[dynamic]string, allocator := context.allocator) -> (m: [dynamic]string, e: Match_Error) {
context.allocator = allocator
+44 -5
View File
@@ -20,6 +20,8 @@ is_slash :: proc(c: byte) -> bool {
return c == '\\' || c == '/'
}
// Splits path immediate following the last separator; separating the path into a directory and file.
// If no separator is found, `dir` will be empty and `path` set to `path`.
split :: proc(path: string) -> (dir, file: string) {
vol := volume_name(path)
i := len(path) - 1
@@ -29,10 +31,18 @@ split :: proc(path: string) -> (dir, file: string) {
return path[:i+1], path[i+1:]
}
/*
Returns leading volume name.
e.g.
"C:\foo\bar\baz" will return "C:" on Windows.
Everything else will be "".
*/
volume_name :: proc(path: string) -> string {
return path[:volume_name_len(path)]
}
// Returns the length of the volume name in bytes.
volume_name_len :: proc(path: string) -> int {
if ODIN_OS == .Windows {
if len(path) < 2 {
@@ -74,7 +84,7 @@ volume_name_len :: proc(path: string) -> int {
/*
Gets the file name and extension from a path.
i.e:
e.g.
'path/to/name.tar.gz' -> 'name.tar.gz'
'path/to/name.txt' -> 'name.txt'
'path/to/name' -> 'name'
@@ -114,7 +124,7 @@ base :: proc(path: string) -> string {
Only the last dot is considered when splitting the file extension.
See `short_stem`.
i.e:
e.g.
'name.tar.gz' -> 'name.tar'
'name.txt' -> 'name'
@@ -147,7 +157,7 @@ stem :: proc(path: string) -> string {
The first dot is used to split off the file extension, unlike `stem` which uses the last dot.
i.e:
e.g.
'name.tar.gz' -> 'name'
'name.txt' -> 'name'
@@ -170,7 +180,7 @@ short_stem :: proc(path: string) -> string {
Only the last dot is considered when splitting the file extension.
See `long_ext`.
i.e:
e.g.
'name.tar.gz' -> '.gz'
'name.txt' -> '.txt'
@@ -193,7 +203,7 @@ ext :: proc(path: string) -> string {
The first dot is used to split off the file extension, unlike `ext` which uses the last dot.
i.e:
e.g.
'name.tar.gz' -> '.tar.gz'
'name.txt' -> '.txt'
@@ -219,6 +229,21 @@ long_ext :: proc(path: string) -> string {
return ""
}
/*
Returns the shortest path name equivalent to `path` through solely lexical processing.
It applies the folliwng rules until none of them can be applied:
* Replace multiple separators with a single one
* Remove each current directory (`.`) path name element
* Remove each inner parent directory (`..`) path and the preceding paths
* Remove `..` that begin at the root of a path
* All possible separators are replaced with the OS specific separator
The return path ends in a slash only if it represents the root of a directory (`C:\` on Windows and `/` on *nix systems).
If the result of the path is an empty string, the returned path with be `"."`.
*/
clean :: proc(path: string, allocator := context.allocator) -> string {
context.allocator = allocator
@@ -299,6 +324,7 @@ clean :: proc(path: string, allocator := context.allocator) -> string {
return cleaned
}
// Returns the result of replacing each forward slash `/` character in the path with the separate OS specific character.
from_slash :: proc(path: string, allocator := context.allocator) -> (new_path: string, new_allocation: bool) {
if SEPARATOR == '/' {
return path, false
@@ -306,6 +332,7 @@ from_slash :: proc(path: string, allocator := context.allocator) -> (new_path: s
return strings.replace_all(path, "/", SEPARATOR_STRING, allocator)
}
// Returns the result of replacing each OS specific separator with a forward slash `/` character.
to_slash :: proc(path: string, allocator := context.allocator) -> (new_path: string, new_allocation: bool) {
if SEPARATOR == '/' {
return path, false
@@ -320,6 +347,13 @@ Relative_Error :: enum {
Cannot_Relate,
}
/*
Returns a relative path that is lexically equivalent to the `target_path` when joined with the `base_path` with an OS specific separator.
e.g. `join(base_path, rel(base_path, target_path))` is equivalent to `target_path`
On failure, the `Relative_Error` will be state it cannot compute the necessary relative path.
*/
rel :: proc(base_path, target_path: string, allocator := context.allocator) -> (string, Relative_Error) {
context.allocator = allocator
base_clean, target_clean := clean(base_path), clean(target_path)
@@ -398,6 +432,11 @@ rel :: proc(base_path, target_path: string, allocator := context.allocator) -> (
return target[t0:], .None
}
/*
Returns all but the last element path, usually the path's directory. Once the final element has been removed,
`dir` calls `clean` on the path and trailing separators are removed. If the path consists purely of separators,
then `"."` is returned.
*/
dir :: proc(path: string, allocator := context.allocator) -> string {
context.allocator = allocator
vol := volume_name(path)
+2
View File
@@ -7,6 +7,7 @@ when ODIN_OS == .Darwin {
foreign import libc "system:c"
}
import "core:runtime"
import "core:strings"
SEPARATOR :: '/'
@@ -41,6 +42,7 @@ abs :: proc(path: string, allocator := context.allocator) -> (string, bool) {
join :: proc(elems: []string, allocator := context.allocator) -> string {
for e, i in elems {
if e != "" {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator)
p := strings.join(elems[i:], SEPARATOR_STRING, context.temp_allocator)
return clean(p, allocator)
}
+15 -12
View File
@@ -1,6 +1,7 @@
package filepath
import "core:strings"
import "core:runtime"
import "core:os"
import win32 "core:sys/windows"
@@ -60,25 +61,25 @@ temp_full_path :: proc(name: string) -> (path: string, err: os.Errno) {
}
p := win32.utf8_to_utf16(name, ta)
buf := make([dynamic]u16, 100, ta)
for {
n := win32.GetFullPathNameW(raw_data(p), u32(len(buf)), raw_data(buf), nil)
if n == 0 {
delete(buf)
return "", os.Errno(win32.GetLastError())
}
if n <= u32(len(buf)) {
return win32.utf16_to_utf8(buf[:n], ta) or_else "", os.ERROR_NONE
}
resize(&buf, len(buf)*2)
n := win32.GetFullPathNameW(raw_data(p), 0, nil, nil)
if n == 0 {
return "", os.Errno(win32.GetLastError())
}
return
buf := make([]u16, n, ta)
n = win32.GetFullPathNameW(raw_data(p), u32(len(buf)), raw_data(buf), nil)
if n == 0 {
delete(buf)
return "", os.Errno(win32.GetLastError())
}
return win32.utf16_to_utf8(buf[:n], ta) or_else "", os.ERROR_NONE
}
abs :: proc(path: string, allocator := context.allocator) -> (string, bool) {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = allocator == context.temp_allocator)
full_path, err := temp_full_path(path)
if err != 0 {
return "", false
@@ -99,6 +100,8 @@ join :: proc(elems: []string, allocator := context.allocator) -> string {
join_non_empty :: proc(elems: []string, allocator := context.allocator) -> string {
context.allocator = allocator
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = allocator == context.temp_allocator)
if len(elems[0]) == 2 && elems[0][1] == ':' {
i := 1
+3 -1
View File
@@ -5,6 +5,7 @@
// To manipulate operating system specific paths, use the path/filepath package
package slashpath
import "core:runtime"
import "core:strings"
// is_separator checks whether the byte is a valid separator character
@@ -150,8 +151,9 @@ join :: proc(elems: []string, allocator := context.allocator) -> string {
context.allocator = allocator
for elem, i in elems {
if elem != "" {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator)
s := strings.join(elems[i:], "/", context.temp_allocator)
return clean(s)
return clean(s, allocator)
}
}
return ""
+26
View File
@@ -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
+215
View File
@@ -0,0 +1,215 @@
package spall
import "core:os"
import "core:time"
import "core:intrinsics"
import "core:mem"
// File Format
MANUAL_MAGIC :: u64le(0x0BADF00D)
Manual_Header :: struct #packed {
magic: u64le,
version: u64le,
timestamp_scale: f64le,
reserved: u64le,
}
Manual_Event_Type :: enum u8 {
Invalid = 0,
Begin = 3,
End = 4,
Instant = 5,
Pad_Skip = 7,
}
Begin_Event :: struct #packed {
type: Manual_Event_Type,
category: u8,
pid: u32le,
tid: u32le,
ts: f64le,
name_len: u8,
args_len: u8,
}
BEGIN_EVENT_MAX :: size_of(Begin_Event) + 255 + 255
End_Event :: struct #packed {
type: Manual_Event_Type,
pid: u32le,
tid: u32le,
ts: f64le,
}
Pad_Skip :: struct #packed {
type: Manual_Event_Type,
size: u32le,
}
// User Interface
Context :: struct {
precise_time: bool,
timestamp_scale: f64,
fd: os.Handle,
}
Buffer :: struct {
data: []u8,
head: int,
tid: u32,
pid: u32,
}
BUFFER_DEFAULT_SIZE :: 0x10_0000
context_create :: proc(filename: string) -> (ctx: Context, ok: bool) #optional_ok {
fd, err := os.open(filename, os.O_WRONLY | os.O_APPEND | os.O_CREATE | os.O_TRUNC, 0o600)
if err != os.ERROR_NONE {
return
}
ctx.fd = fd
freq, freq_ok := time.tsc_frequency()
ctx.precise_time = freq_ok
ctx.timestamp_scale = ((1 / f64(freq)) * 1_000_000) if freq_ok else 1
temp := [size_of(Manual_Header)]u8{}
_build_header(temp[:], ctx.timestamp_scale)
os.write(ctx.fd, temp[:])
ok = true
return
}
context_destroy :: proc(ctx: ^Context) {
if ctx == nil {
return
}
os.close(ctx.fd)
ctx^ = Context{}
}
buffer_create :: proc(data: []byte, tid: u32 = 0, pid: u32 = 0) -> (buffer: Buffer, ok: bool) #optional_ok {
assert(len(data) >= 1024)
buffer.data = data
buffer.tid = tid
buffer.pid = pid
buffer.head = 0
ok = true
return
}
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) {
buffer_flush(ctx, buffer)
buffer^ = Buffer{}
}
@(deferred_in=_scoped_buffer_end)
SCOPED_EVENT :: proc(ctx: ^Context, buffer: ^Buffer, name: string, args: string = "", location := #caller_location) -> bool {
_buffer_begin(ctx, buffer, name, args, location)
return true
}
@(private)
_scoped_buffer_end :: proc(ctx: ^Context, buffer: ^Buffer, _, _: string, _ := #caller_location) {
_buffer_end(ctx, buffer)
}
_trace_now :: proc "contextless" (ctx: ^Context) -> f64 {
if !ctx.precise_time {
return f64(time.tick_now()._nsec) / 1_000
}
return f64(intrinsics.read_cycle_counter())
}
_build_header :: proc "contextless" (buffer: []u8, timestamp_scale: f64) -> (header_size: int, ok: bool) #optional_ok {
header_size = size_of(Manual_Header)
if header_size > len(buffer) {
return 0, false
}
hdr := (^Manual_Header)(raw_data(buffer))
hdr.magic = MANUAL_MAGIC
hdr.version = 1
hdr.timestamp_scale = f64le(timestamp_scale)
hdr.reserved = 0
ok = true
return
}
_build_begin :: proc "contextless" (buffer: []u8, name: string, args: string, ts: f64, tid: u32, pid: u32) -> (event_size: int, ok: bool) #optional_ok {
ev := (^Begin_Event)(raw_data(buffer))
name_len := min(len(name), 255)
args_len := min(len(args), 255)
event_size = size_of(Begin_Event) + name_len + args_len
if event_size > len(buffer) {
return 0, false
}
ev.type = .Begin
ev.pid = u32le(pid)
ev.tid = u32le(tid)
ev.ts = f64le(ts)
ev.name_len = u8(name_len)
ev.args_len = u8(args_len)
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 "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) {
return 0, false
}
ev.type = .End
ev.pid = u32le(pid)
ev.tid = u32le(tid)
ev.ts = f64le(ts)
ok = true
return
}
_buffer_begin :: proc(ctx: ^Context, buffer: ^Buffer, name: string, args: string = "", location := #caller_location) {
if buffer.head + BEGIN_EVENT_MAX > len(buffer.data) {
buffer_flush(ctx, buffer)
}
name := location.procedure if name == "" else name
buffer.head += _build_begin(buffer.data[buffer.head:], name, args, _trace_now(ctx), buffer.tid, buffer.pid)
}
_buffer_end :: proc(ctx: ^Context, buffer: ^Buffer) {
ts := _trace_now(ctx)
if buffer.head + size_of(End_Event) > len(buffer.data) {
buffer_flush(ctx, buffer)
}
buffer.head += _build_end(buffer.data[buffer.head:], ts, buffer.tid, buffer.pid)
}
+12 -4
View File
@@ -25,7 +25,8 @@ Type_Info_Array :: runtime.Type_Info_Array
Type_Info_Enumerated_Array :: runtime.Type_Info_Enumerated_Array
Type_Info_Dynamic_Array :: runtime.Type_Info_Dynamic_Array
Type_Info_Slice :: runtime.Type_Info_Slice
Type_Info_Tuple :: runtime.Type_Info_Tuple
Type_Info_Parameters :: runtime.Type_Info_Parameters
Type_Info_Tuple :: runtime.Type_Info_Parameters
Type_Info_Struct :: runtime.Type_Info_Struct
Type_Info_Union :: runtime.Type_Info_Union
Type_Info_Enum :: runtime.Type_Info_Enum
@@ -96,7 +97,7 @@ type_kind :: proc(T: typeid) -> Type_Kind {
case Type_Info_Enumerated_Array: return .Enumerated_Array
case Type_Info_Dynamic_Array: return .Dynamic_Array
case Type_Info_Slice: return .Slice
case Type_Info_Tuple: return .Tuple
case Type_Info_Parameters: return .Tuple
case Type_Info_Struct: return .Struct
case Type_Info_Union: return .Union
case Type_Info_Enum: return .Enum
@@ -448,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 {
@@ -1438,7 +1446,7 @@ equal :: proc(a, b: any, including_indirect_array_recursion := false, recursion_
switch v in t.variant {
case Type_Info_Named:
unreachable()
case Type_Info_Tuple:
case Type_Info_Parameters:
unreachable()
case Type_Info_Any:
if !including_indirect_array_recursion {
+11 -5
View File
@@ -101,8 +101,8 @@ are_types_identical :: proc(a, b: ^Type_Info) -> bool {
y := b.variant.(Type_Info_Slice) or_return
return are_types_identical(x.elem, y.elem)
case Type_Info_Tuple:
y := b.variant.(Type_Info_Tuple) or_return
case Type_Info_Parameters:
y := b.variant.(Type_Info_Parameters) or_return
if len(x.types) != len(y.types) { return false }
for _, i in x.types {
xt, yt := x.types[i], y.types[i]
@@ -335,9 +335,15 @@ is_slice :: proc(info: ^Type_Info) -> bool {
return ok
}
@(require_results)
is_parameters :: proc(info: ^Type_Info) -> bool {
if info == nil { return false }
_, ok := type_info_base(info).variant.(Type_Info_Parameters)
return ok
}
@(require_results, deprecated="prefer is_parameters")
is_tuple :: proc(info: ^Type_Info) -> bool {
if info == nil { return false }
_, ok := type_info_base(info).variant.(Type_Info_Tuple)
_, ok := type_info_base(info).variant.(Type_Info_Parameters)
return ok
}
@(require_results)
@@ -490,7 +496,7 @@ write_type_writer :: proc(w: io.Writer, ti: ^Type_Info, n_written: ^int = nil) -
if info.params == nil {
io.write_string(w, "()", &n) or_return
} else {
t := info.params.variant.(Type_Info_Tuple)
t := info.params.variant.(Type_Info_Parameters)
io.write_string(w, "(", &n) or_return
for t, i in t.types {
if i > 0 {
@@ -504,7 +510,7 @@ write_type_writer :: proc(w: io.Writer, ti: ^Type_Info, n_written: ^int = nil) -
io.write_string(w, " -> ", &n) or_return
write_type(w, info.results, &n) or_return
}
case Type_Info_Tuple:
case Type_Info_Parameters:
count := len(info.names)
if count != 1 {
io.write_string(w, "(", &n) or_return
+11 -10
View File
@@ -83,8 +83,8 @@ Type_Info_Multi_Pointer :: struct {
elem: ^Type_Info,
}
Type_Info_Procedure :: struct {
params: ^Type_Info, // Type_Info_Tuple
results: ^Type_Info, // Type_Info_Tuple
params: ^Type_Info, // Type_Info_Parameters
results: ^Type_Info, // Type_Info_Parameters
variadic: bool,
convention: Calling_Convention,
}
@@ -104,10 +104,12 @@ Type_Info_Enumerated_Array :: struct {
}
Type_Info_Dynamic_Array :: struct {elem: ^Type_Info, elem_size: int}
Type_Info_Slice :: struct {elem: ^Type_Info, elem_size: int}
Type_Info_Tuple :: struct { // Only used for procedures parameters and results
Type_Info_Parameters :: struct { // Only used for procedures parameters and results
types: []^Type_Info,
names: []string,
}
Type_Info_Tuple :: Type_Info_Parameters // Will be removed eventually
Type_Info_Struct :: struct {
types: []^Type_Info,
@@ -208,7 +210,7 @@ Type_Info :: struct {
Type_Info_Enumerated_Array,
Type_Info_Dynamic_Array,
Type_Info_Slice,
Type_Info_Tuple,
Type_Info_Parameters,
Type_Info_Struct,
Type_Info_Union,
Type_Info_Enum,
@@ -505,11 +507,8 @@ Odin_Endian_Type :: type_of(ODIN_ENDIAN)
foreign {
@(link_name="__$startup_runtime")
_startup_runtime :: proc "odin" () ---
}
@(link_name="__$cleanup_runtime")
_cleanup_runtime :: proc() {
default_temp_allocator_destroy(&global_default_temp_allocator_data)
@(link_name="__$cleanup_runtime")
_cleanup_runtime :: proc "odin" () ---
}
_cleanup_runtime_contextless :: proc "contextless" () {
@@ -622,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
+12 -11
View File
@@ -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)
}
}
@@ -231,13 +235,12 @@ make_dynamic_array_len_cap :: proc($T: typeid/[dynamic]$E, #any_int len: int, #a
return
}
@(builtin)
make_map :: proc($T: typeid/map[$K]$E, #any_int capacity: int = 1<<MAP_MIN_LOG2_CAPACITY, allocator := context.allocator, loc := #caller_location) -> T {
make_map :: proc($T: typeid/map[$K]$E, #any_int capacity: int = 1<<MAP_MIN_LOG2_CAPACITY, allocator := context.allocator, loc := #caller_location) -> (m: T, err: Allocator_Error) #optional_allocator_error {
make_map_expr_error_loc(loc, capacity)
context.allocator = allocator
m: T
reserve_map(&m, capacity, loc)
return m
err = reserve_map(&m, capacity, loc)
return
}
@(builtin)
make_multi_pointer :: proc($T: typeid/[^]$E, #any_int len: int, allocator := context.allocator, loc := #caller_location) -> (mp: T, err: Allocator_Error) #optional_allocator_error {
@@ -276,10 +279,8 @@ clear_map :: proc "contextless" (m: ^$T/map[$K]$V) {
}
@builtin
reserve_map :: proc(m: ^$T/map[$K]$V, capacity: int, loc := #caller_location) {
if m != nil {
__dynamic_map_reserve((^Raw_Map)(m), map_info(T), uint(capacity), loc)
}
reserve_map :: proc(m: ^$T/map[$K]$V, capacity: int, loc := #caller_location) -> Allocator_Error {
return __dynamic_map_reserve((^Raw_Map)(m), map_info(T), uint(capacity), loc) if m != nil else nil
}
/*
+304
View File
@@ -0,0 +1,304 @@
package runtime
import "core:intrinsics"
DEFAULT_ARENA_GROWING_MINIMUM_BLOCK_SIZE :: uint(DEFAULT_TEMP_ALLOCATOR_BACKING_SIZE)
Memory_Block :: struct {
prev: ^Memory_Block,
allocator: Allocator,
base: [^]byte,
used: uint,
capacity: uint,
}
Arena :: struct {
backing_allocator: Allocator,
curr_block: ^Memory_Block,
total_used: uint,
total_capacity: uint,
minimum_block_size: uint,
temp_count: uint,
}
@(private, require_results)
safe_add :: #force_inline proc "contextless" (x, y: uint) -> (uint, bool) {
z, did_overflow := intrinsics.overflow_add(x, y)
return z, !did_overflow
}
@(require_results)
memory_block_alloc :: proc(allocator: Allocator, capacity: uint, loc := #caller_location) -> (block: ^Memory_Block, err: Allocator_Error) {
total_size := uint(capacity + size_of(Memory_Block))
base_offset := uintptr(size_of(Memory_Block))
min_alignment: int = max(16, align_of(Memory_Block))
data := mem_alloc(int(total_size), min_alignment, allocator, loc) or_return
block = (^Memory_Block)(raw_data(data))
end := uintptr(raw_data(data)[len(data):])
block.allocator = allocator
block.base = ([^]byte)(uintptr(block) + base_offset)
block.capacity = uint(end - uintptr(block.base))
// Should be zeroed
assert(block.used == 0)
assert(block.prev == nil)
return
}
memory_block_dealloc :: proc(block_to_free: ^Memory_Block, loc := #caller_location) {
if block_to_free != nil {
allocator := block_to_free.allocator
mem_free(block_to_free, allocator, loc)
}
}
@(require_results)
alloc_from_memory_block :: proc(block: ^Memory_Block, min_size, alignment: uint) -> (data: []byte, err: Allocator_Error) {
calc_alignment_offset :: proc "contextless" (block: ^Memory_Block, alignment: uintptr) -> uint {
alignment_offset := uint(0)
ptr := uintptr(block.base[block.used:])
mask := alignment-1
if ptr & mask != 0 {
alignment_offset = uint(alignment - (ptr & mask))
}
return alignment_offset
}
if block == nil {
return nil, .Out_Of_Memory
}
alignment_offset := calc_alignment_offset(block, uintptr(alignment))
size, size_ok := safe_add(min_size, alignment_offset)
if !size_ok {
err = .Out_Of_Memory
return
}
if to_be_used, ok := safe_add(block.used, size); !ok || to_be_used > block.capacity {
err = .Out_Of_Memory
return
}
data = block.base[block.used+alignment_offset:][:min_size]
block.used += size
return
}
@(require_results)
arena_alloc :: proc(arena: ^Arena, size, alignment: uint, loc := #caller_location) -> (data: []byte, err: Allocator_Error) {
align_forward_uint :: proc "contextless" (ptr, align: uint) -> uint {
p := ptr
modulo := p & (align-1)
if modulo != 0 {
p += align - modulo
}
return p
}
assert(alignment & (alignment-1) == 0, "non-power of two alignment", loc)
size := size
if size == 0 {
return
}
if arena.curr_block == nil || (safe_add(arena.curr_block.used, size) or_else 0) > arena.curr_block.capacity {
size = align_forward_uint(size, alignment)
if arena.minimum_block_size == 0 {
arena.minimum_block_size = DEFAULT_ARENA_GROWING_MINIMUM_BLOCK_SIZE
}
block_size := max(size, arena.minimum_block_size)
if arena.backing_allocator.procedure == nil {
arena.backing_allocator = default_allocator()
}
new_block := memory_block_alloc(arena.backing_allocator, block_size, loc) or_return
new_block.prev = arena.curr_block
arena.curr_block = new_block
arena.total_capacity += new_block.capacity
}
prev_used := arena.curr_block.used
data, err = alloc_from_memory_block(arena.curr_block, size, alignment)
arena.total_used += arena.curr_block.used - prev_used
return
}
// `arena_init` will initialize the arena with a usuable block.
// This procedure is not necessary to use the Arena as the default zero as `arena_alloc` will set things up if necessary
@(require_results)
arena_init :: proc(arena: ^Arena, size: uint, backing_allocator: Allocator, loc := #caller_location) -> Allocator_Error {
arena^ = {}
arena.backing_allocator = backing_allocator
arena.minimum_block_size = max(size, 1<<12) // minimum block size of 4 KiB
new_block := memory_block_alloc(arena.backing_allocator, arena.minimum_block_size, loc) or_return
arena.curr_block = new_block
arena.total_capacity += new_block.capacity
return nil
}
arena_free_last_memory_block :: proc(arena: ^Arena, loc := #caller_location) {
if free_block := arena.curr_block; free_block != nil {
arena.curr_block = free_block.prev
arena.total_capacity -= free_block.capacity
memory_block_dealloc(free_block, loc)
}
}
// `arena_free_all` will free all but the first memory block, and then reset the memory block
arena_free_all :: proc(arena: ^Arena, loc := #caller_location) {
for arena.curr_block != nil && arena.curr_block.prev != nil {
arena_free_last_memory_block(arena, loc)
}
if arena.curr_block != nil {
intrinsics.mem_zero(arena.curr_block.base, arena.curr_block.used)
arena.curr_block.used = 0
}
arena.total_used = 0
}
arena_destroy :: proc(arena: ^Arena, loc := #caller_location) {
for arena.curr_block != nil {
free_block := arena.curr_block
arena.curr_block = free_block.prev
arena.total_capacity -= free_block.capacity
memory_block_dealloc(free_block, loc)
}
arena.total_used = 0
arena.total_capacity = 0
}
arena_allocator :: proc(arena: ^Arena) -> Allocator {
return Allocator{arena_allocator_proc, arena}
}
arena_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode,
size, alignment: int,
old_memory: rawptr, old_size: int,
location := #caller_location) -> (data: []byte, err: Allocator_Error) {
arena := (^Arena)(allocator_data)
size, alignment := uint(size), uint(alignment)
old_size := uint(old_size)
switch mode {
case .Alloc, .Alloc_Non_Zeroed:
return arena_alloc(arena, size, alignment, location)
case .Free:
err = .Mode_Not_Implemented
case .Free_All:
arena_free_all(arena, location)
case .Resize:
old_data := ([^]byte)(old_memory)
switch {
case old_data == nil:
return arena_alloc(arena, size, alignment, location)
case size == old_size:
// return old memory
data = old_data[:size]
return
case size == 0:
err = .Mode_Not_Implemented
return
case (uintptr(old_data) & uintptr(alignment-1) == 0) && size < old_size:
// shrink data in-place
data = old_data[:size]
return
}
new_memory := arena_alloc(arena, size, alignment, location) or_return
if new_memory == nil {
return
}
copy(new_memory, old_data[:old_size])
return new_memory, nil
case .Query_Features:
set := (^Allocator_Mode_Set)(old_memory)
if set != nil {
set^ = {.Alloc, .Alloc_Non_Zeroed, .Free_All, .Resize, .Query_Features}
}
case .Query_Info:
err = .Mode_Not_Implemented
}
return
}
Arena_Temp :: struct {
arena: ^Arena,
block: ^Memory_Block,
used: uint,
}
@(require_results)
arena_temp_begin :: proc(arena: ^Arena, loc := #caller_location) -> (temp: Arena_Temp) {
assert(arena != nil, "nil arena", loc)
temp.arena = arena
temp.block = arena.curr_block
if arena.curr_block != nil {
temp.used = arena.curr_block.used
}
arena.temp_count += 1
return
}
arena_temp_end :: proc(temp: Arena_Temp, loc := #caller_location) {
if temp.arena == nil {
assert(temp.block == nil)
assert(temp.used == 0)
return
}
arena := temp.arena
if temp.block != nil {
memory_block_found := false
for block := arena.curr_block; block != nil; block = block.prev {
if block == temp.block {
memory_block_found = true
break
}
}
if !memory_block_found {
assert(arena.curr_block == temp.block, "memory block stored within Arena_Temp not owned by Arena", loc)
}
for arena.curr_block != temp.block {
arena_free_last_memory_block(arena)
}
if block := arena.curr_block; block != nil {
assert(block.used >= temp.used, "out of order use of arena_temp_end", loc)
amount_to_zero := min(block.used-temp.used, block.capacity-block.used)
intrinsics.mem_zero(block.base[temp.used:], amount_to_zero)
block.used = temp.used
}
}
assert(arena.temp_count > 0, "double-use of arena_temp_end", loc)
arena.temp_count -= 1
}
// Ignore the use of a `arena_temp_begin` entirely
arena_temp_ignore :: proc(temp: Arena_Temp, loc := #caller_location) {
assert(temp.arena != nil, "nil arena", loc)
arena := temp.arena
assert(arena.temp_count > 0, "double-use of arena_temp_end", loc)
arena.temp_count -= 1
}
arena_check_temp :: proc(arena: ^Arena, loc := #caller_location) {
assert(arena.temp_count == 0, "Arena_Temp not been ended", loc)
}
+43 -164
View File
@@ -1,159 +1,38 @@
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, backup_allocator := context.allocator) {}
default_temp_allocator_init :: proc(s: ^Default_Temp_Allocator, size: int, backing_allocator := context.allocator) {}
default_temp_allocator_destroy :: proc(s: ^Default_Temp_Allocator) {}
default_temp_allocator_proc :: nil_allocator_proc
@(require_results)
default_temp_allocator_temp_begin :: proc(loc := #caller_location) -> (temp: Arena_Temp) {
return
}
default_temp_allocator_temp_end :: proc(temp: Arena_Temp, loc := #caller_location) {
}
} else {
Default_Temp_Allocator :: struct {
data: []byte,
curr_offset: int,
prev_allocation: rawptr,
backup_allocator: Allocator,
leaked_allocations: [dynamic][]byte,
arena: Arena,
}
default_temp_allocator_init :: proc(s: ^Default_Temp_Allocator, size: int, backup_allocator := context.allocator) {
s.data = make_aligned([]byte, size, 2*align_of(rawptr), backup_allocator)
s.curr_offset = 0
s.prev_allocation = nil
s.backup_allocator = backup_allocator
s.leaked_allocations.allocator = backup_allocator
default_temp_allocator_init :: proc(s: ^Default_Temp_Allocator, size: int, backing_allocator := context.allocator) {
_ = arena_init(&s.arena, uint(size), backing_allocator)
}
default_temp_allocator_destroy :: proc(s: ^Default_Temp_Allocator) {
if s == nil {
return
if s != nil {
arena_destroy(&s.arena)
s^ = {}
}
for ptr in s.leaked_allocations {
free(raw_data(ptr), s.backup_allocator)
}
delete(s.leaked_allocations)
delete(s.data, s.backup_allocator)
s^ = {}
}
@(private)
default_temp_allocator_alloc :: proc(s: ^Default_Temp_Allocator, size, alignment: int, loc := #caller_location) -> ([]byte, Allocator_Error) {
size := size
size = align_forward_int(size, alignment)
switch {
case s.curr_offset+size <= len(s.data):
start := uintptr(raw_data(s.data))
ptr := start + uintptr(s.curr_offset)
ptr = align_forward_uintptr(ptr, uintptr(alignment))
mem_zero(rawptr(ptr), size)
s.prev_allocation = rawptr(ptr)
offset := int(ptr - start)
s.curr_offset = offset + size
return byte_slice(rawptr(ptr), size), .None
case size <= len(s.data):
start := uintptr(raw_data(s.data))
ptr := align_forward_uintptr(start, uintptr(alignment))
mem_zero(rawptr(ptr), size)
s.prev_allocation = rawptr(ptr)
offset := int(ptr - start)
s.curr_offset = offset + size
return byte_slice(rawptr(ptr), size), .None
}
a := s.backup_allocator
if a.procedure == nil {
a = context.allocator
s.backup_allocator = a
}
data, err := mem_alloc_bytes(size, alignment, a, loc)
if err != nil {
return data, err
}
if s.leaked_allocations == nil {
s.leaked_allocations = make([dynamic][]byte, a)
}
append(&s.leaked_allocations, data)
// TODO(bill): Should leaks be notified about?
if logger := context.logger; logger.lowest_level <= .Warning {
if logger.procedure != nil {
logger.procedure(logger.data, .Warning, "default temp allocator resorted to backup_allocator" , logger.options, loc)
}
}
return data, .None
}
@(private)
default_temp_allocator_free :: proc(s: ^Default_Temp_Allocator, old_memory: rawptr, loc := #caller_location) -> Allocator_Error {
if old_memory == nil {
return .None
}
start := uintptr(raw_data(s.data))
end := start + uintptr(len(s.data))
old_ptr := uintptr(old_memory)
if s.prev_allocation == old_memory {
s.curr_offset = int(uintptr(s.prev_allocation) - start)
s.prev_allocation = nil
return .None
}
if start <= old_ptr && old_ptr < end {
// NOTE(bill): Cannot free this pointer but it is valid
return .None
}
if len(s.leaked_allocations) != 0 {
for data, i in s.leaked_allocations {
ptr := raw_data(data)
if ptr == old_memory {
free(ptr, s.backup_allocator)
ordered_remove(&s.leaked_allocations, i)
return .None
}
}
}
return .Invalid_Pointer
// panic("invalid pointer passed to default_temp_allocator");
}
@(private)
default_temp_allocator_free_all :: proc(s: ^Default_Temp_Allocator, loc := #caller_location) {
s.curr_offset = 0
s.prev_allocation = nil
for data in s.leaked_allocations {
free(raw_data(data), s.backup_allocator)
}
clear(&s.leaked_allocations)
}
@(private)
default_temp_allocator_resize :: proc(s: ^Default_Temp_Allocator, old_memory: rawptr, old_size, size, alignment: int, loc := #caller_location) -> ([]byte, Allocator_Error) {
begin := uintptr(raw_data(s.data))
end := begin + uintptr(len(s.data))
old_ptr := uintptr(old_memory)
if old_memory == s.prev_allocation && old_ptr & uintptr(alignment)-1 == 0 {
if old_ptr+uintptr(size) < end {
s.curr_offset = int(old_ptr-begin)+size
return byte_slice(old_memory, size), .None
}
}
data, err := default_temp_allocator_alloc(s, size, alignment, loc)
if err == .None {
copy(data, byte_slice(old_memory, old_size))
err = default_temp_allocator_free(s, old_memory, loc)
}
return data, err
}
default_temp_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode,
@@ -161,40 +40,40 @@ when ODIN_OS == .Freestanding || ODIN_OS == .JS || ODIN_DEFAULT_TO_NIL_ALLOCATOR
old_memory: rawptr, old_size: int, loc := #caller_location) -> (data: []byte, err: Allocator_Error) {
s := (^Default_Temp_Allocator)(allocator_data)
return arena_allocator_proc(&s.arena, mode, size, alignment, old_memory, old_size, loc)
}
if s.data == nil {
default_temp_allocator_init(s, DEFAULT_TEMP_ALLOCATOR_BACKING_SIZE, default_allocator())
@(require_results)
default_temp_allocator_temp_begin :: proc(loc := #caller_location) -> (temp: Arena_Temp) {
if context.temp_allocator.data == &global_default_temp_allocator_data {
temp = arena_temp_begin(&global_default_temp_allocator_data.arena, loc)
}
switch mode {
case .Alloc, .Alloc_Non_Zeroed:
data, err = default_temp_allocator_alloc(s, size, alignment, loc)
case .Free:
err = default_temp_allocator_free(s, old_memory, loc)
case .Free_All:
default_temp_allocator_free_all(s, loc)
case .Resize:
data, err = default_temp_allocator_resize(s, old_memory, old_size, size, alignment, loc)
case .Query_Features:
set := (^Allocator_Mode_Set)(old_memory)
if set != nil {
set^ = {.Alloc, .Alloc_Non_Zeroed, .Free, .Free_All, .Resize, .Query_Features}
}
case .Query_Info:
return nil, .Mode_Not_Implemented
}
return
}
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)
DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD :: #force_inline proc(ignore := false, loc := #caller_location) -> (Arena_Temp, Source_Code_Location) {
if ignore {
return {}, loc
} else {
return default_temp_allocator_temp_begin(loc), loc
}
}
default_temp_allocator :: proc(allocator: ^Default_Temp_Allocator) -> Allocator {
return Allocator{
procedure = default_temp_allocator_proc,
data = allocator,
data = allocator,
}
}
+11 -10
View File
@@ -184,32 +184,33 @@ mem_free_all :: #force_inline proc(allocator := context.allocator, loc := #calle
return
}
mem_resize :: proc(ptr: rawptr, old_size, new_size: int, alignment: int = DEFAULT_ALIGNMENT, allocator := context.allocator, loc := #caller_location) -> ([]byte, Allocator_Error) {
mem_resize :: proc(ptr: rawptr, old_size, new_size: int, alignment: int = DEFAULT_ALIGNMENT, allocator := context.allocator, loc := #caller_location) -> (data: []byte, err: Allocator_Error) {
if allocator.procedure == nil {
return nil, nil
}
if new_size == 0 {
if ptr != nil {
_, err := allocator.procedure(allocator.data, .Free, 0, 0, ptr, old_size, loc)
return nil, err
_, err = allocator.procedure(allocator.data, .Free, 0, 0, ptr, old_size, loc)
return
}
return nil, nil
return
} else if ptr == nil {
return allocator.procedure(allocator.data, .Alloc, new_size, alignment, nil, 0, loc)
} else if old_size == new_size && uintptr(ptr) % uintptr(alignment) == 0 {
return ([^]byte)(ptr)[:old_size], nil
data = ([^]byte)(ptr)[:old_size]
return
}
data, err := allocator.procedure(allocator.data, .Resize, new_size, alignment, ptr, old_size, loc)
data, err = allocator.procedure(allocator.data, .Resize, new_size, alignment, ptr, old_size, loc)
if err == .Mode_Not_Implemented {
data, err = allocator.procedure(allocator.data, .Alloc, new_size, alignment, nil, 0, loc)
if err != nil {
return data, err
return
}
copy(data, ([^]byte)(ptr)[:old_size])
_, err = allocator.procedure(allocator.data, .Free, 0, 0, ptr, old_size, loc)
}
return data, err
return
}
memory_equal :: proc "contextless" (x, y: rawptr, n: int) -> bool {
@@ -223,7 +224,7 @@ memory_equal :: proc "contextless" (x, y: rawptr, n: int) -> bool {
when size_of(uint) == 8 {
if word_length := length >> 3; word_length != 0 {
for i in 0..<word_length {
for _ in 0..<word_length {
if intrinsics.unaligned_load((^u64)(a)) != intrinsics.unaligned_load((^u64)(b)) {
return false
}
@@ -254,7 +255,7 @@ memory_equal :: proc "contextless" (x, y: rawptr, n: int) -> bool {
return true
} else {
if word_length := length >> 2; word_length != 0 {
for i in 0..<word_length {
for _ in 0..<word_length {
if intrinsics.unaligned_load((^u32)(a)) != intrinsics.unaligned_load((^u32)(b)) {
return false
}
+30 -23
View File
@@ -2,8 +2,11 @@ package runtime
_INTEGER_DIGITS :: "0123456789abcdefghijklmnopqrstuvwxyz"
@(private="file")
_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)
@@ -46,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 {
@@ -57,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(" ")
@@ -105,14 +114,14 @@ encode_rune :: proc "contextless" (c: rune) -> ([4]u8, int) {
return buf, 4
}
print_string :: proc "contextless" (str: string) -> (int, _OS_Errno) {
return os_write(transmute([]byte)str)
print_string :: proc "contextless" (str: string) -> (n: int) {
n, _ = os_write(transmute([]byte)str)
return
}
print_strings :: proc "contextless" (args: ..string) -> (n: int, err: _OS_Errno) {
print_strings :: proc "contextless" (args: ..string) -> (n: int) {
for str in args {
m: int
m, err = os_write(transmute([]byte)str)
m, err := os_write(transmute([]byte)str)
n += m
if err != 0 {
break
@@ -121,8 +130,9 @@ print_strings :: proc "contextless" (args: ..string) -> (n: int, err: _OS_Errno)
return
}
print_byte :: proc "contextless" (b: byte) -> (int, _OS_Errno) {
return os_write([]byte{b})
print_byte :: proc "contextless" (b: byte) -> (n: int) {
n, _ = os_write([]byte{b})
return
}
print_encoded_rune :: proc "contextless" (r: rune) {
@@ -141,11 +151,10 @@ print_encoded_rune :: proc "contextless" (r: rune) {
if r <= 0 {
print_string("\\x00")
} else if r < 32 {
digits := _INTEGER_DIGITS
n0, n1 := u8(r) >> 4, u8(r) & 0xf
print_string("\\x")
print_byte(digits[n0])
print_byte(digits[n1])
print_byte(_INTEGER_DIGITS_VAR[n0])
print_byte(_INTEGER_DIGITS_VAR[n1])
} else {
print_rune(r)
}
@@ -153,7 +162,7 @@ print_encoded_rune :: proc "contextless" (r: rune) {
print_byte('\'')
}
print_rune :: proc "contextless" (r: rune) -> (int, _OS_Errno) #no_bounds_check {
print_rune :: proc "contextless" (r: rune) -> int #no_bounds_check {
RUNE_SELF :: 0x80
if r < RUNE_SELF {
@@ -161,29 +170,27 @@ print_rune :: proc "contextless" (r: rune) -> (int, _OS_Errno) #no_bounds_check
}
b, n := encode_rune(r)
return os_write(b[:n])
m, _ := os_write(b[:n])
return m
}
print_u64 :: proc "contextless" (x: u64) #no_bounds_check {
digits := _INTEGER_DIGITS
a: [129]byte
i := len(a)
b := u64(10)
u := x
for u >= b {
i -= 1; a[i] = digits[u % b]
i -= 1; a[i] = _INTEGER_DIGITS_VAR[u % b]
u /= b
}
i -= 1; a[i] = digits[u % b]
i -= 1; a[i] = _INTEGER_DIGITS_VAR[u % b]
os_write(a[i:])
}
print_i64 :: proc "contextless" (x: i64) #no_bounds_check {
digits := _INTEGER_DIGITS
b :: i64(10)
u := x
@@ -193,10 +200,10 @@ print_i64 :: proc "contextless" (x: i64) #no_bounds_check {
a: [129]byte
i := len(a)
for u >= b {
i -= 1; a[i] = digits[u % b]
i -= 1; a[i] = _INTEGER_DIGITS_VAR[u % b]
u /= b
}
i -= 1; a[i] = digits[u % b]
i -= 1; a[i] = _INTEGER_DIGITS_VAR[u % b]
if neg {
i -= 1; a[i] = '-'
}
@@ -303,7 +310,7 @@ print_type :: proc "contextless" (ti: ^Type_Info) {
if info.params == nil {
print_string("()")
} else {
t := info.params.variant.(Type_Info_Tuple)
t := info.params.variant.(Type_Info_Parameters)
print_byte('(')
for t, i in t.types {
if i > 0 { print_string(", ") }
@@ -315,7 +322,7 @@ print_type :: proc "contextless" (ti: ^Type_Info) {
print_string(" -> ")
print_type(info.results)
}
case Type_Info_Tuple:
case Type_Info_Parameters:
count := len(info.names)
if count != 1 { print_byte('(') }
for name, i in info.names {
+2 -2
View File
@@ -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)
-36
View File
@@ -1,36 +0,0 @@
package sort
import "core:intrinsics"
import "core:runtime"
import "core:slice"
_ :: runtime
_ :: slice
map_entries_by_key :: proc(m: ^$M/map[$K]$V, loc := #caller_location) where intrinsics.type_is_ordered(K) {
Entry :: struct {
hash: uintptr,
next: int,
key: K,
value: V,
}
header := runtime.__get_map_header(m)
entries := (^[dynamic]Entry)(&header.m.entries)
slice.sort_by_key(entries[:], proc(e: Entry) -> K { return e.key })
runtime.__dynamic_map_reset_entries(header, loc)
}
map_entries_by_value :: proc(m: ^$M/map[$K]$V, loc := #caller_location) where intrinsics.type_is_ordered(V) {
Entry :: struct {
hash: uintptr,
next: int,
key: K,
value: V,
}
header := runtime.__get_map_header(m)
entries := (^[dynamic]Entry)(&header.m.entries)
slice.sort_by_key(entries[:], proc(e: Entry) -> V { return e.value })
runtime.__dynamic_map_reset_entries(header, loc)
}
+44 -15
View File
@@ -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
+24 -3
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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 -502
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -236,4 +236,4 @@ _panic :: proc "contextless" (msg: string) -> ! {
runtime.print_string(msg)
runtime.print_byte('\n')
runtime.trap()
}
}
@@ -97,6 +97,7 @@ clone_to_cstring :: proc(s: string, allocator: runtime.Allocator, loc := #caller
sys_open :: proc(path: string, oflag: Open_Flags, mode: Permission) -> (c.int, bool) {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
cmode: u32 = 0
cflags: u32 = 0
@@ -132,30 +133,35 @@ sys_open :: proc(path: string, oflag: Open_Flags, mode: Permission) -> (c.int, b
}
sys_mkdir :: proc(path: string, mode: Permission) -> bool {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
cpath: cstring = clone_to_cstring(path, context.temp_allocator)
cflags := _sys_permission_mode(mode)
return syscall_mkdir(cpath, cflags) != -1
}
sys_mkdir_at :: proc(fd: c.int, path: string, mode: Permission) -> bool {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
cpath: cstring = clone_to_cstring(path, context.temp_allocator)
cflags := _sys_permission_mode(mode)
return syscall_mkdir_at(fd, cpath, cflags) != -1
}
sys_rmdir :: proc(path: string, mode: Permission) -> bool {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
cpath: cstring = clone_to_cstring(path, context.temp_allocator)
cflags := _sys_permission_mode(mode)
return syscall_rmdir(cpath, cflags) != -1
}
sys_rename :: proc(path: string, new_path: string) -> bool {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
cpath: cstring = clone_to_cstring(path, context.temp_allocator)
cnpath: cstring = clone_to_cstring(new_path, context.temp_allocator)
return syscall_rename(cpath, cnpath) != -1
}
sys_rename_at :: proc(fd: c.int, path: string, to_fd: c.int, new_path: string) -> bool {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
cpath: cstring = clone_to_cstring(path, context.temp_allocator)
cnpath: cstring = clone_to_cstring(new_path, context.temp_allocator)
return syscall_rename_at(fd, cpath, to_fd, cnpath) != -1
@@ -166,12 +172,14 @@ sys_lseek :: proc(fd: c.int, offset: i64, whence: Offset_From) -> i64 {
}
sys_chmod :: proc(path: string, mode: Permission) -> bool {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
cpath: cstring = clone_to_cstring(path, context.temp_allocator)
cmode := _sys_permission_mode(mode)
return syscall_chmod(cpath, cmode) != -1
}
sys_lstat :: proc(path: string, status: ^stat) -> bool {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
cpath: cstring = clone_to_cstring(path, context.temp_allocator)
return syscall_lstat(cpath, status) != -1
}

Some files were not shown because too many files have changed in this diff Show More