Compare commits

..

179 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
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
Jeroen van Rijn c02ff3af27 Update comments 2023-03-02 13:45:12 +01: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
Daniel 34cb558279 Add win32 SetConsoleTextAttributes for setting cmd prompt colors 2023-01-25 14:17:20 -05:00
156 changed files with 14237 additions and 6583 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: |
+9 -1
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 ---
+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
@@ -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 {
+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 -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)
}
}
}
+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
+560 -98
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
}
-36
View File
@@ -4,7 +4,6 @@ import "core:bytes"
import "core:fmt"
import "core:image"
import "core:mem"
import "core:os"
import "core:strconv"
import "core:strings"
import "core:unicode"
@@ -27,23 +26,6 @@ PFM :: Formats{.Pf, .PF}
ASCII :: Formats{.P1, .P2, .P3}
BINARY :: Formats{.P4, .P5, .P6} + PAM + PFM
load :: proc {
load_from_file,
load_from_bytes,
}
load_from_file :: proc(filename: string, allocator := context.allocator) -> (img: ^Image, err: Error) {
context.allocator = allocator
data, ok := os.read_entire_file(filename); defer delete(data)
if !ok {
err = .Unable_To_Read_File
return
}
return load_from_bytes(data)
}
load_from_bytes :: proc(data: []byte, allocator := context.allocator) -> (img: ^Image, err: Error) {
context.allocator = allocator
@@ -67,24 +49,6 @@ load_from_bytes :: proc(data: []byte, allocator := context.allocator) -> (img: ^
return img, nil
}
save :: proc {
save_to_file,
save_to_buffer,
}
save_to_file :: proc(filename: string, img: ^Image, custom_info: Info = {}, allocator := context.allocator) -> (err: Error) {
context.allocator = allocator
data: []byte; defer delete(data)
data = save_to_buffer(img, custom_info) or_return
if ok := os.write_entire_file(filename, data); !ok {
return .Unable_To_Write_File
}
return Format_Error.None
}
save_to_buffer :: proc(img: ^Image, custom_info: Info = {}, allocator := context.allocator) -> (buffer: []byte, err: Error) {
context.allocator = allocator
+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
}
-16
View File
@@ -17,7 +17,6 @@ import "core:compress"
import "core:compress/zlib"
import "core:image"
import "core:os"
import "core:hash"
import "core:bytes"
import "core:io"
@@ -336,19 +335,6 @@ load_from_bytes :: proc(data: []byte, options := Options{}, allocator := context
return img, err
}
load_from_file :: proc(filename: string, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) {
context.allocator = allocator
data, ok := os.read_entire_file(filename)
defer delete(data)
if ok {
return load_from_bytes(data, options)
} else {
return nil, .Unable_To_Read_File
}
}
load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) {
context.allocator = allocator
options := options
@@ -1641,8 +1627,6 @@ defilter :: proc(img: ^Image, filter_bytes: ^bytes.Buffer, header: ^image.PNG_IH
return nil
}
load :: proc{load_from_file, load_from_bytes, load_from_context}
@(init, private)
_register :: proc() {
+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
}
}
+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))
}
+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
@@ -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)
+240
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)
@@ -318,6 +458,18 @@ foreign libc {
@(link_name="strerror") _darwin_string_error :: proc(num : c.int) -> cstring ---
@(link_name="sysctlbyname") _sysctlbyname :: proc(path: cstring, oldp: rawptr, oldlenp: rawptr, newp: rawptr, newlen: int) -> c.int ---
@(link_name="socket") _unix_socket :: proc(domain: int, type: int, protocol: int) -> int ---
@(link_name="listen") _unix_listen :: proc(socket: int, backlog: int) -> int ---
@(link_name="accept") _unix_accept :: proc(socket: int, addr: rawptr, addr_len: rawptr) -> int ---
@(link_name="connect") _unix_connect :: proc(socket: int, addr: rawptr, addr_len: socklen_t) -> int ---
@(link_name="bind") _unix_bind :: proc(socket: int, addr: rawptr, addr_len: socklen_t) -> int ---
@(link_name="setsockopt") _unix_setsockopt :: proc(socket: int, level: int, opt_name: int, opt_val: rawptr, opt_len: socklen_t) -> int ---
@(link_name="recvfrom") _unix_recvfrom :: proc(socket: int, buffer: rawptr, buffer_len: c.size_t, flags: int, addr: rawptr, addr_len: ^socklen_t) -> c.ssize_t ---
@(link_name="recv") _unix_recv :: proc(socket: int, buffer: rawptr, buffer_len: c.size_t, flags: int) -> c.ssize_t ---
@(link_name="sendto") _unix_sendto :: proc(socket: int, buffer: rawptr, buffer_len: c.size_t, flags: int, addr: rawptr, addr_len: socklen_t) -> c.ssize_t ---
@(link_name="send") _unix_send :: proc(socket: int, buffer: rawptr, buffer_len: c.size_t, flags: int) -> c.ssize_t ---
@(link_name="shutdown") _unix_shutdown :: proc(socket: int, how: int) -> int ---
@(link_name="exit") _unix_exit :: proc(status: c.int) -> ! ---
}
@@ -815,3 +967,91 @@ _alloc_command_line_arguments :: proc() -> []string {
}
return res
}
socket :: proc(domain: int, type: int, protocol: int) -> (Socket, Errno) {
result := _unix_socket(domain, type, protocol)
if result < 0 {
return 0, Errno(get_last_error())
}
return Socket(result), ERROR_NONE
}
connect :: proc(sd: Socket, addr: ^SOCKADDR, len: socklen_t) -> (Errno) {
result := _unix_connect(int(sd), addr, len)
if result < 0 {
return Errno(get_last_error())
}
return ERROR_NONE
}
bind :: proc(sd: Socket, addr: ^SOCKADDR, len: socklen_t) -> (Errno) {
result := _unix_bind(int(sd), addr, len)
if result < 0 {
return Errno(get_last_error())
}
return ERROR_NONE
}
accept :: proc(sd: Socket, addr: ^SOCKADDR, len: rawptr) -> (Socket, Errno) {
result := _unix_accept(int(sd), rawptr(addr), len)
if result < 0 {
return 0, Errno(get_last_error())
}
return Socket(result), ERROR_NONE
}
listen :: proc(sd: Socket, backlog: int) -> (Errno) {
result := _unix_listen(int(sd), backlog)
if result < 0 {
return Errno(get_last_error())
}
return ERROR_NONE
}
setsockopt :: proc(sd: Socket, level: int, optname: int, optval: rawptr, optlen: socklen_t) -> (Errno) {
result := _unix_setsockopt(int(sd), level, optname, optval, optlen)
if result < 0 {
return Errno(get_last_error())
}
return ERROR_NONE
}
recvfrom :: proc(sd: Socket, data: []byte, flags: int, addr: ^SOCKADDR, addr_size: ^socklen_t) -> (u32, Errno) {
result := _unix_recvfrom(int(sd), raw_data(data), len(data), flags, addr, addr_size)
if result < 0 {
return 0, Errno(get_last_error())
}
return u32(result), ERROR_NONE
}
recv :: proc(sd: Socket, data: []byte, flags: int) -> (u32, Errno) {
result := _unix_recv(int(sd), raw_data(data), len(data), flags)
if result < 0 {
return 0, Errno(get_last_error())
}
return u32(result), ERROR_NONE
}
sendto :: proc(sd: Socket, data: []u8, flags: int, addr: ^SOCKADDR, addrlen: socklen_t) -> (u32, Errno) {
result := _unix_sendto(int(sd), raw_data(data), len(data), flags, addr, addrlen)
if result < 0 {
return 0, Errno(get_last_error())
}
return u32(result), ERROR_NONE
}
send :: proc(sd: Socket, data: []byte, flags: int) -> (u32, Errno) {
result := _unix_send(int(sd), raw_data(data), len(data), 0)
if result < 0 {
return 0, Errno(get_last_error())
}
return u32(result), ERROR_NONE
}
shutdown :: proc(sd: Socket, how: int) -> (Errno) {
result := _unix_shutdown(int(sd), how)
if result < 0 {
return Errno(get_last_error())
}
return ERROR_NONE
}
+275 -2
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
}
+265 -1
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
@@ -297,6 +459,9 @@ foreign dl {
@(link_name="dlsym") _unix_dlsym :: proc(handle: rawptr, symbol: cstring) -> rawptr ---
@(link_name="dlclose") _unix_dlclose :: proc(handle: rawptr) -> c.int ---
@(link_name="dlerror") _unix_dlerror :: proc() -> cstring ---
@(link_name="getifaddrs") _getifaddrs :: proc(ifap: ^^ifaddrs) -> (c.int) ---
@(link_name="freeifaddrs") _freeifaddrs :: proc(ifa: ^ifaddrs) ---
}
is_path_separator :: proc(r: rune) -> bool {
@@ -748,7 +913,7 @@ get_current_directory :: proc() -> string {
#no_bounds_check res := unix.sys_getcwd(&buf[0], uint(len(buf)))
if res >= 0 {
return strings.string_from_nul_terminated_ptr(&buf[0], len(buf))
return strings.string_from_null_terminated_ptr(&buf[0], len(buf))
}
if _get_errno(res) != ERANGE {
delete(buf)
@@ -823,3 +988,102 @@ _alloc_command_line_arguments :: proc() -> []string {
}
return res
}
socket :: proc(domain: int, type: int, protocol: int) -> (Socket, Errno) {
result := unix.sys_socket(domain, type, protocol)
if result < 0 {
return 0, _get_errno(result)
}
return Socket(result), ERROR_NONE
}
bind :: proc(sd: Socket, addr: ^SOCKADDR, len: socklen_t) -> (Errno) {
result := unix.sys_bind(int(sd), addr, len)
if result < 0 {
return _get_errno(result)
}
return ERROR_NONE
}
connect :: proc(sd: Socket, addr: ^SOCKADDR, len: socklen_t) -> (Errno) {
result := unix.sys_connect(int(sd), addr, len)
if result < 0 {
return _get_errno(result)
}
return ERROR_NONE
}
accept :: proc(sd: Socket, addr: ^SOCKADDR, len: rawptr) -> (Socket, Errno) {
result := unix.sys_accept(int(sd), rawptr(addr), len)
if result < 0 {
return 0, _get_errno(result)
}
return Socket(result), ERROR_NONE
}
listen :: proc(sd: Socket, backlog: int) -> (Errno) {
result := unix.sys_listen(int(sd), backlog)
if result < 0 {
return _get_errno(result)
}
return ERROR_NONE
}
setsockopt :: proc(sd: Socket, level: int, optname: int, optval: rawptr, optlen: socklen_t) -> (Errno) {
result := unix.sys_setsockopt(int(sd), level, optname, optval, optlen)
if result < 0 {
return _get_errno(result)
}
return ERROR_NONE
}
recvfrom :: proc(sd: Socket, data: []byte, flags: int, addr: ^SOCKADDR, addr_size: ^socklen_t) -> (u32, Errno) {
result := unix.sys_recvfrom(int(sd), raw_data(data), len(data), flags, addr, uintptr(addr_size))
if result < 0 {
return 0, _get_errno(int(result))
}
return u32(result), ERROR_NONE
}
recv :: proc(sd: Socket, data: []byte, flags: int) -> (u32, Errno) {
result := unix.sys_recvfrom(int(sd), raw_data(data), len(data), flags, nil, 0)
if result < 0 {
return 0, _get_errno(int(result))
}
return u32(result), ERROR_NONE
}
sendto :: proc(sd: Socket, data: []u8, flags: int, addr: ^SOCKADDR, addrlen: socklen_t) -> (u32, Errno) {
result := unix.sys_sendto(int(sd), raw_data(data), len(data), flags, addr, addrlen)
if result < 0 {
return 0, _get_errno(int(result))
}
return u32(result), ERROR_NONE
}
send :: proc(sd: Socket, data: []byte, flags: int) -> (u32, Errno) {
result := unix.sys_sendto(int(sd), raw_data(data), len(data), 0, nil, 0)
if result < 0 {
return 0, _get_errno(int(result))
}
return u32(result), ERROR_NONE
}
shutdown :: proc(sd: Socket, how: int) -> (Errno) {
result := unix.sys_shutdown(int(sd), how)
if result < 0 {
return _get_errno(result)
}
return ERROR_NONE
}
fcntl :: proc(fd: int, cmd: int, arg: int) -> (int, Errno) {
result := unix.sys_fcntl(fd, cmd, arg)
if result < 0 {
return 0, _get_errno(result)
}
return result, ERROR_NONE
}
+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
+10 -3
View File
@@ -1,4 +1,4 @@
package prof_spall
package spall
import "core:os"
import "core:time"
@@ -95,7 +95,7 @@ context_destroy :: proc(ctx: ^Context) {
}
buffer_create :: proc(data: []byte, tid: u32 = 0, pid: u32 = 0) -> (buffer: Buffer, ok: bool) #optional_ok {
assert(len(data) > 0)
assert(len(data) >= 1024)
buffer.data = data
buffer.tid = tid
buffer.pid = pid
@@ -105,8 +105,13 @@ buffer_create :: proc(data: []byte, tid: u32 = 0, pid: u32 = 0) -> (buffer: Buff
}
buffer_flush :: proc(ctx: ^Context, buffer: ^Buffer) {
start := _trace_now(ctx)
os.write(ctx.fd, buffer.data[:buffer.head])
buffer.head = 0
end := _trace_now(ctx)
buffer.head += _build_begin(buffer.data[buffer.head:], "Spall Trace Buffer Flush", "", start, buffer.tid, buffer.pid)
buffer.head += _build_end(buffer.data[buffer.head:], end, buffer.tid, buffer.pid)
}
buffer_destroy :: proc(ctx: ^Context, buffer: ^Buffer) {
@@ -171,10 +176,11 @@ _build_begin :: proc "contextless" (buffer: []u8, name: string, args: string, ts
mem.copy(raw_data(buffer[size_of(Begin_Event):]), raw_data(name), name_len)
mem.copy(raw_data(buffer[size_of(Begin_Event)+name_len:]), raw_data(args), args_len)
ok = true
return
}
_build_end :: proc(buffer: []u8, ts: f64, tid: u32, pid: u32) -> (event_size: int, ok: bool) #optional_ok {
_build_end :: proc "contextless" (buffer: []u8, ts: f64, tid: u32, pid: u32) -> (event_size: int, ok: bool) #optional_ok {
ev := (^End_Event)(raw_data(buffer))
event_size = size_of(End_Event)
if event_size > len(buffer) {
@@ -186,6 +192,7 @@ _build_end :: proc(buffer: []u8, ts: f64, tid: u32, pid: u32) -> (event_size: in
ev.tid = u32le(tid)
ev.ts = f64le(ts)
ok = true
return
}
+8 -1
View File
@@ -449,7 +449,14 @@ struct_field_value_by_name :: proc(a: any, field: string, allow_using := false)
return nil
}
@(require_results)
struct_field_value :: proc(a: any, field: Struct_Field) -> any {
if a == nil { return nil }
return any {
rawptr(uintptr(a.data) + field.offset),
field.type.id,
}
}
@(require_results)
struct_field_names :: proc(T: typeid) -> []string {
+3 -1
View File
@@ -621,7 +621,9 @@ __init_context :: proc "contextless" (c: ^Context) {
c.allocator.data = nil
c.temp_allocator.procedure = default_temp_allocator_proc
c.temp_allocator.data = &global_default_temp_allocator_data
when !NO_DEFAULT_TEMP_ALLOCATOR {
c.temp_allocator.data = &global_default_temp_allocator_data
}
when !ODIN_DISABLE_ASSERT {
c.assertion_failure_proc = default_assertion_failure_proc
+7 -3
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)
}
}
@@ -1,9 +1,9 @@
package runtime
DEFAULT_TEMP_ALLOCATOR_BACKING_SIZE: int : #config(DEFAULT_TEMP_ALLOCATOR_BACKING_SIZE, 4 * Megabyte)
NO_DEFAULT_TEMP_ALLOCATOR: bool : ODIN_OS == .Freestanding || ODIN_OS == .JS || ODIN_DEFAULT_TO_NIL_ALLOCATOR
when ODIN_OS == .Freestanding || ODIN_OS == .JS || ODIN_DEFAULT_TO_NIL_ALLOCATOR {
when NO_DEFAULT_TEMP_ALLOCATOR {
Default_Temp_Allocator :: struct {}
default_temp_allocator_init :: proc(s: ^Default_Temp_Allocator, size: int, backing_allocator := context.allocator) {}
@@ -54,6 +54,11 @@ when ODIN_OS == .Freestanding || ODIN_OS == .JS || ODIN_DEFAULT_TO_NIL_ALLOCATOR
default_temp_allocator_temp_end :: proc(temp: Arena_Temp, loc := #caller_location) {
arena_temp_end(temp, loc)
}
@(fini, private)
_destroy_temp_allocator_fini :: proc() {
default_temp_allocator_destroy(&global_default_temp_allocator_data)
}
}
@(deferred_out=default_temp_allocator_temp_end)
@@ -72,8 +77,3 @@ default_temp_allocator :: proc(allocator: ^Default_Temp_Allocator) -> Allocator
data = allocator,
}
}
@(fini, private)
_destroy_temp_allocator_fini :: proc() {
default_temp_allocator_destroy(&global_default_temp_allocator_data)
}
+8 -2
View File
@@ -6,7 +6,7 @@ _INTEGER_DIGITS :: "0123456789abcdefghijklmnopqrstuvwxyz"
_INTEGER_DIGITS_VAR := _INTEGER_DIGITS
when !ODIN_DISALLOW_RTTI {
print_any_single :: proc(arg: any) {
print_any_single :: proc "contextless" (arg: any) {
x := arg
if loc, ok := x.(Source_Code_Location); ok {
print_caller_location(loc)
@@ -49,6 +49,12 @@ when !ODIN_DISALLOW_RTTI {
case uint: print_uint(v)
case uintptr: print_uintptr(v)
case bool: print_string("true" if v else "false")
case b8: print_string("true" if v else "false")
case b16: print_string("true" if v else "false")
case b32: print_string("true" if v else "false")
case b64: print_string("true" if v else "false")
case:
ti := type_info_of(x.id)
#partial switch v in ti.variant {
@@ -60,7 +66,7 @@ when !ODIN_DISALLOW_RTTI {
print_string("<invalid-value>")
}
}
println_any :: proc(args: ..any) {
println_any :: proc "contextless" (args: ..any) {
loop: for arg, i in args {
if i != 0 {
print_string(" ")
+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)
+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 -505
View File
File diff suppressed because it is too large Load Diff
+82 -62
View File
@@ -223,11 +223,10 @@ init_os_version :: proc () {
// Grab Windows DisplayVersion (like 20H02)
format_display_version :: proc (b: ^strings.Builder) -> (version: string) {
dv, ok := read_reg(
dv, ok := read_reg_string(
sys.HKEY_LOCAL_MACHINE,
"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion",
"DisplayVersion",
string,
)
defer delete(dv) // It'll be interned into `version_string_buf`
@@ -243,11 +242,10 @@ init_os_version :: proc () {
// Grab build number and UBR
format_build_number :: proc (b: ^strings.Builder, major_build: int) -> (ubr: int) {
res, ok := read_reg(
res, ok := read_reg_i32(
sys.HKEY_LOCAL_MACHINE,
"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion",
"UBR",
i32,
)
if ok {
@@ -289,17 +287,17 @@ init_gpu_info :: proc() {
for {
key := fmt.tprintf("%v\\%04d", GPU_INFO_BASE, gpu_index)
if vendor, ok := read_reg(sys.HKEY_LOCAL_MACHINE, key, "ProviderName", string); ok {
if vendor, ok := read_reg_string(sys.HKEY_LOCAL_MACHINE, key, "ProviderName"); ok {
append(&gpu_list, GPU{vendor_name = vendor})
} else {
break
}
if desc, ok := read_reg(sys.HKEY_LOCAL_MACHINE, key, "DriverDesc", string); ok {
if desc, ok := read_reg_string(sys.HKEY_LOCAL_MACHINE, key, "DriverDesc"); ok {
gpu_list[gpu_index].model_name = desc
}
if vram, ok := read_reg(sys.HKEY_LOCAL_MACHINE, key, "HardwareInformation.qwMemorySize", i64); ok {
if vram, ok := read_reg_i64(sys.HKEY_LOCAL_MACHINE, key, "HardwareInformation.qwMemorySize"); ok {
gpu_list[gpu_index].total_ram = int(vram)
}
gpu_index += 1
@@ -308,71 +306,93 @@ init_gpu_info :: proc() {
}
@(private)
read_reg :: proc(hkey: sys.HKEY, subkey, val: string, $T: typeid) -> (res: T, ok: bool) {
BUF_SIZE :: 1024
read_reg_string :: proc(hkey: sys.HKEY, subkey, val: string) -> (res: string, ok: bool) {
if len(subkey) == 0 || len(val) == 0 {
return {}, false
return
}
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
BUF_SIZE :: 1024
key_name_wide := make([]u16, BUF_SIZE, context.temp_allocator)
val_name_wide := make([]u16, BUF_SIZE, context.temp_allocator)
utf16.encode_string(key_name_wide, subkey)
utf16.encode_string(val_name_wide, val)
when T == string {
result_wide := make([]u16, BUF_SIZE, context.temp_allocator)
result_size := sys.DWORD(BUF_SIZE * size_of(u16))
result_wide := make([]u16, BUF_SIZE, context.temp_allocator)
result_size := sys.DWORD(BUF_SIZE * size_of(u16))
status := sys.RegGetValueW(
hkey,
&key_name_wide[0],
&val_name_wide[0],
sys.RRF_RT_REG_SZ,
nil,
raw_data(result_wide[:]),
&result_size,
)
if status != 0 {
// Couldn't retrieve string
return
}
// Result string will be allocated for the caller.
result_utf8 := make([]u8, BUF_SIZE * 4, context.temp_allocator)
utf16.decode_to_utf8(result_utf8, result_wide[:result_size])
return strings.clone_from_cstring(cstring(raw_data(result_utf8))), true
} else when T == i32 {
result_size := sys.DWORD(size_of(i32))
status := sys.RegGetValueW(
hkey,
&key_name_wide[0],
&val_name_wide[0],
sys.RRF_RT_REG_DWORD,
nil,
&res,
&result_size,
)
return res, status == 0
} else when T == i64 {
result_size := sys.DWORD(size_of(i64))
status := sys.RegGetValueW(
hkey,
&key_name_wide[0],
&val_name_wide[0],
sys.RRF_RT_REG_QWORD,
nil,
&res,
&result_size,
)
return res, status == 0
} else {
#assert(false, "Unhandled type for read_reg")
status := sys.RegGetValueW(
hkey,
&key_name_wide[0],
&val_name_wide[0],
sys.RRF_RT_REG_SZ,
nil,
raw_data(result_wide[:]),
&result_size,
)
if status != 0 {
// Couldn't retrieve string
return
}
return
}
// Result string will be allocated for the caller.
result_utf8 := make([]u8, BUF_SIZE * 4, context.temp_allocator)
utf16.decode_to_utf8(result_utf8, result_wide[:result_size])
return strings.clone_from_cstring(cstring(raw_data(result_utf8))), true
}
@(private)
read_reg_i32 :: proc(hkey: sys.HKEY, subkey, val: string) -> (res: i32, ok: bool) {
if len(subkey) == 0 || len(val) == 0 {
return
}
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
BUF_SIZE :: 1024
key_name_wide := make([]u16, BUF_SIZE, context.temp_allocator)
val_name_wide := make([]u16, BUF_SIZE, context.temp_allocator)
utf16.encode_string(key_name_wide, subkey)
utf16.encode_string(val_name_wide, val)
result_size := sys.DWORD(size_of(i32))
status := sys.RegGetValueW(
hkey,
&key_name_wide[0],
&val_name_wide[0],
sys.RRF_RT_REG_DWORD,
nil,
&res,
&result_size,
)
return res, status == 0
}
@(private)
read_reg_i64 :: proc(hkey: sys.HKEY, subkey, val: string) -> (res: i64, ok: bool) {
if len(subkey) == 0 || len(val) == 0 {
return
}
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
BUF_SIZE :: 1024
key_name_wide := make([]u16, BUF_SIZE, context.temp_allocator)
val_name_wide := make([]u16, BUF_SIZE, context.temp_allocator)
utf16.encode_string(key_name_wide, subkey)
utf16.encode_string(val_name_wide, val)
result_size := sys.DWORD(size_of(i64))
status := sys.RegGetValueW(
hkey,
&key_name_wide[0],
&val_name_wide[0],
sys.RRF_RT_REG_QWORD,
nil,
&res,
&result_size,
)
return res, status == 0
}
+41
View File
@@ -1518,6 +1518,7 @@ when ODIN_ARCH == .amd64 {
#panic("Unsupported architecture")
}
// syscall related constants
AT_FDCWD :: ~uintptr(99)
AT_REMOVEDIR :: uintptr(0x200)
@@ -2008,6 +2009,42 @@ sys_utimensat :: proc "contextless" (dfd: int, path: cstring, times: rawptr, fla
return int(intrinsics.syscall(SYS_utimensat, uintptr(dfd), uintptr(rawptr(path)), uintptr(times), uintptr(flags)))
}
sys_socket :: proc "contextless" (domain: int, type: int, protocol: int) -> int {
return int(intrinsics.syscall(SYS_socket, uintptr(domain), uintptr(type), uintptr(protocol)))
}
sys_connect :: proc "contextless" (sd: int, addr: rawptr, len: i32) -> int {
return int(intrinsics.syscall(SYS_connect, uintptr(sd), uintptr(addr), uintptr(len)))
}
sys_accept :: proc "contextless" (sd: int, addr: rawptr, len: rawptr) -> int {
return int(intrinsics.syscall(SYS_accept4, uintptr(sd), uintptr(addr), uintptr(len), uintptr(0)))
}
sys_listen :: proc "contextless" (sd: int, backlog: int) -> int {
return int(intrinsics.syscall(SYS_listen, uintptr(sd), uintptr(backlog)))
}
sys_bind :: proc "contextless" (sd: int, addr: rawptr, len: i32) -> int {
return int(intrinsics.syscall(SYS_bind, uintptr(sd), uintptr(addr), uintptr(len)))
}
sys_setsockopt :: proc "contextless" (sd: int, level: int, optname: int, optval: rawptr, optlen: i32) -> int {
return int(intrinsics.syscall(SYS_setsockopt, uintptr(sd), uintptr(level), uintptr(optname), uintptr(optval), uintptr(optlen)))
}
sys_recvfrom :: proc "contextless" (sd: int, buf: rawptr, len: uint, flags: int, addr: rawptr, alen: uintptr) -> i64 {
return i64(intrinsics.syscall(SYS_recvfrom, uintptr(sd), uintptr(buf), uintptr(len), uintptr(flags), uintptr(addr), uintptr(alen)))
}
sys_sendto :: proc "contextless" (sd: int, buf: rawptr, len: uint, flags: int, addr: rawptr, alen: i32) -> i64 {
return i64(intrinsics.syscall(SYS_sendto, uintptr(sd), uintptr(buf), uintptr(len), uintptr(flags), uintptr(addr), uintptr(alen)))
}
sys_shutdown :: proc "contextless" (sd: int, how: int) -> int {
return int(intrinsics.syscall(SYS_shutdown, uintptr(sd), uintptr(how)))
}
sys_perf_event_open :: proc "contextless" (event_attr: rawptr, pid: i32, cpu: i32, group_fd: i32, flags: u32) -> int {
return int(intrinsics.syscall(SYS_perf_event_open, uintptr(event_attr), uintptr(pid), uintptr(cpu), uintptr(group_fd), uintptr(flags)))
}
@@ -2016,6 +2053,10 @@ sys_personality :: proc(persona: u64) -> int {
return int(intrinsics.syscall(SYS_personality, uintptr(persona)))
}
sys_fcntl :: proc "contextless" (fd: int, cmd: int, arg: int) -> int {
return int(intrinsics.syscall(SYS_fcntl, uintptr(fd), uintptr(cmd), uintptr(arg)))
}
get_errno :: proc "contextless" (res: int) -> i32 {
if res < 0 && res > -4096 {
return i32(-res)
+10
View File
@@ -0,0 +1,10 @@
// +build windows
package sys_windows
foreign import "system:Dnsapi.lib"
@(default_calling_convention="std")
foreign Dnsapi {
DnsQuery_UTF8 :: proc(name: cstring, type: u16, options: DWORD, extra: PVOID, results: ^^DNS_RECORD, reserved: PVOID) -> DNS_STATUS ---
DnsRecordListFree :: proc(list: ^DNS_RECORD, options: DWORD) ---
}
+234
View File
@@ -0,0 +1,234 @@
// +build windows
package sys_windows
foreign import "system:iphlpapi.lib"
Address_Family :: enum u32 {
Unspecified = 0, // Return both IPv4 and IPv6 addresses associated with adapters with them enabled.
IPv4 = 2, // Return only IPv4 addresses associated with adapters with it enabled.
IPv6 = 23, // Return only IPv6 addresses associated with adapters with it enabled.
}
GAA_Flag :: enum u32 {
Skip_Unicast = 0, // Do not return unicast addresses.
Skip_Anycast = 1, // Do not return IPv6 anycast addresses.
Skip_Multicast = 2, // Do not return multicast addresses.
Skip_DNS_Server = 3, // Do not return addresses of DNS servers.
Include_Prefix = 4, // (XP SP1+) Return a list of IP address prefixes on this adapter. When this flag is set, IP address prefixes are returned for both IPv6 and IPv4 addresses.
Skip_Friendly_Name = 5, // Do not return the adapter friendly name.
Include_WINS_info = 6, // (Vista+) Return addresses of Windows Internet Name Service (WINS) servers.
Include_Gateways = 7, // (Vista+) Return the addresses of default gateways.
Include_All_Interfaces = 8, // (Vista+) Return addresses for all NDIS interfaces.
Include_All_Compartments = 9, // (Reserved, Unsupported) Return addresses in all routing compartments.
Include_Tunnel_Binding_Order = 10, // (Vista+) Return the adapter addresses sorted in tunnel binding order.
}
GAA_Flags :: bit_set[GAA_Flag; u32]
IP_Adapter_Addresses :: struct {
Raw: struct #raw_union {
Alignment: u64,
Anonymous: struct {
Length: u32,
IfIndex: u32,
},
},
Next: ^IP_Adapter_Addresses,
AdapterName: cstring,
FirstUnicastAddress: ^IP_ADAPTER_UNICAST_ADDRESS_LH,
FirstAnycastAddress: ^IP_ADAPTER_ANYCAST_ADDRESS_XP,
FirstMulticastAddress: ^IP_ADAPTER_MULTICAST_ADDRESS_XP,
FirstDnsServerAddress: ^IP_ADAPTER_DNS_SERVER_ADDRESS_XP,
DnsSuffix: ^u16,
Description: ^u16,
FriendlyName: ^u16,
PhysicalAddress: [8]u8,
PhysicalAddressLength: u32,
Anonymous2: struct #raw_union {
Flags: u32,
Anonymous: struct {
_bitfield: u32,
},
},
MTU: u32,
IfType: u32,
OperStatus: IF_OPER_STATUS,
Ipv6IfIndex: u32,
ZoneIndices: [16]u32,
FirstPrefix: rawptr, // ^IP_ADAPTER_PREFIX_XP,
TransmitLinkSpeed: u64,
ReceiveLinkSpeed: u64,
FirstWinsServerAddress: rawptr, // ^IP_ADAPTER_WINS_SERVER_ADDRESS_LH,
FirstGatewayAddress: ^IP_ADAPTER_GATEWAY_ADDRESS_LH,
Ipv4Metric: u32,
Ipv6Metric: u32,
Luid: NET_LUID_LH,
Dhcpv4Server: SOCKET_ADDRESS,
CompartmentId: u32,
NetworkGuid: GUID,
ConnectionType: NET_IF_CONNECTION_TYPE,
TunnelType: TUNNEL_TYPE,
Dhcpv6Server: SOCKET_ADDRESS,
Dhcpv6ClientDuid: [130]u8,
Dhcpv6ClientDuidLength: u32,
Dhcpv6Iaid: u32,
FirstDnsSuffix: rawptr, // ^IP_ADAPTER_DNS_SUFFIX,
}
IP_ADAPTER_UNICAST_ADDRESS_LH :: struct {
Anonymous: struct #raw_union {
Alignment: u64,
Anonymous: struct {
Length: u32,
Flags: u32,
},
},
Next: ^IP_ADAPTER_UNICAST_ADDRESS_LH,
Address: SOCKET_ADDRESS,
PrefixOrigin: NL_PREFIX_ORIGIN,
SuffixOrigin: NL_SUFFIX_ORIGIN,
DadState: NL_DAD_STATE,
ValidLifetime: u32,
PreferredLifetime: u32,
LeaseLifetime: u32,
OnLinkPrefixLength: u8,
}
IP_ADAPTER_ANYCAST_ADDRESS_XP :: struct {
Anonymous: struct #raw_union {
Alignment: u64,
Anonymous: struct {
Length: u32,
Flags: u32,
},
},
Next: ^IP_ADAPTER_ANYCAST_ADDRESS_XP,
Address: SOCKET_ADDRESS,
}
IP_ADAPTER_MULTICAST_ADDRESS_XP :: struct {
Anonymous: struct #raw_union {
Alignment: u64,
Anonymous: struct {
Length: u32,
Flags: u32,
},
},
Next: ^IP_ADAPTER_MULTICAST_ADDRESS_XP,
Address: SOCKET_ADDRESS,
}
IP_ADAPTER_GATEWAY_ADDRESS_LH :: struct {
Anonymous: struct #raw_union {
Alignment: u64,
Anonymous: struct {
Length: u32,
Reserved: u32,
},
},
Next: ^IP_ADAPTER_GATEWAY_ADDRESS_LH,
Address: SOCKET_ADDRESS,
}
IP_ADAPTER_DNS_SERVER_ADDRESS_XP :: struct {
Anonymous: struct #raw_union {
Alignment: u64,
Anonymous: struct {
Length: u32,
Reserved: u32,
},
},
Next: ^IP_ADAPTER_DNS_SERVER_ADDRESS_XP,
Address: SOCKET_ADDRESS,
}
IF_OPER_STATUS :: enum i32 {
Up = 1,
Down = 2,
Testing = 3,
Unknown = 4,
Dormant = 5,
NotPresent = 6,
LowerLayerDown = 7,
}
NET_LUID_LH :: struct #raw_union {
Value: u64,
Info: struct {
_bitfield: u64,
},
}
SOCKET_ADDRESS :: struct {
lpSockaddr: ^SOCKADDR,
iSockaddrLength: i32,
}
NET_IF_CONNECTION_TYPE :: enum i32 {
NET_IF_CONNECTION_DEDICATED = 1,
NET_IF_CONNECTION_PASSIVE = 2,
NET_IF_CONNECTION_DEMAND = 3,
NET_IF_CONNECTION_MAXIMUM = 4,
}
TUNNEL_TYPE :: enum i32 {
TUNNEL_TYPE_NONE = 0,
TUNNEL_TYPE_OTHER = 1,
TUNNEL_TYPE_DIRECT = 2,
TUNNEL_TYPE_6TO4 = 11,
TUNNEL_TYPE_ISATAP = 13,
TUNNEL_TYPE_TEREDO = 14,
TUNNEL_TYPE_IPHTTPS = 15,
}
NL_PREFIX_ORIGIN :: enum i32 {
IpPrefixOriginOther = 0,
IpPrefixOriginManual = 1,
IpPrefixOriginWellKnown = 2,
IpPrefixOriginDhcp = 3,
IpPrefixOriginRouterAdvertisement = 4,
IpPrefixOriginUnchanged = 16,
}
NL_SUFFIX_ORIGIN :: enum i32 {
NlsoOther = 0,
NlsoManual = 1,
NlsoWellKnown = 2,
NlsoDhcp = 3,
NlsoLinkLayerAddress = 4,
NlsoRandom = 5,
IpSuffixOriginOther = 0,
IpSuffixOriginManual = 1,
IpSuffixOriginWellKnown = 2,
IpSuffixOriginDhcp = 3,
IpSuffixOriginLinkLayerAddress = 4,
IpSuffixOriginRandom = 5,
IpSuffixOriginUnchanged = 16,
}
NL_DAD_STATE :: enum i32 {
NldsInvalid = 0,
NldsTentative = 1,
NldsDuplicate = 2,
NldsDeprecated = 3,
NldsPreferred = 4,
IpDadStateInvalid = 0,
IpDadStateTentative = 1,
IpDadStateDuplicate = 2,
IpDadStateDeprecated = 3,
IpDadStatePreferred = 4,
}
@(default_calling_convention = "std")
foreign iphlpapi {
/*
The GetAdaptersAddresses function retrieves the addresses associated with the adapters on the local computer.
See: https://docs.microsoft.com/en-us/windows/win32/api/iphlpapi/nf-iphlpapi-getadaptersaddresses
*/
@(link_name="GetAdaptersAddresses") get_adapters_addresses :: proc(
family: Address_Family,
flags: GAA_Flags,
_reserved: rawptr,
adapter_addresses: [^]IP_Adapter_Addresses,
size: ^u32,
) -> ULONG ---
}
+23 -1
View File
@@ -3,6 +3,23 @@ package sys_windows
foreign import kernel32 "system:Kernel32.lib"
FOREGROUND_BLUE :: WORD(0x0001)
FOREGROUND_GREEN :: WORD(0x0002)
FOREGROUND_RED :: WORD(0x0004)
FOREGROUND_INTENSITY :: WORD(0x0008)
BACKGROUND_BLUE :: WORD(0x0010)
BACKGROUND_GREEN :: WORD(0x0020)
BACKGROUND_RED :: WORD(0x0040)
BACKGROUND_INTENSITY :: WORD(0x0080)
COMMON_LVB_LEADING_BYTE :: WORD(0x0100)
COMMON_LVB_TRAILING_BYTE :: WORD(0x0200)
COMMON_LVB_GRID_HORIZONTAL :: WORD(0x0400)
COMMON_LVB_GRID_LVERTICAL :: WORD(0x0800)
COMMON_LVB_GRID_RVERTICAL :: WORD(0x1000)
COMMON_LVB_REVERSE_VIDEO :: WORD(0x4000)
COMMON_LVB_UNDERSCORE :: WORD(0x8000)
COMMON_LVB_SBCSDBCS :: WORD(0x0300)
@(default_calling_convention="stdcall")
foreign kernel32 {
OutputDebugStringA :: proc(lpOutputString: LPCSTR) --- // The only A thing that is allowed
@@ -26,7 +43,10 @@ foreign kernel32 {
dwMode: DWORD) -> BOOL ---
SetConsoleCursorPosition :: proc(hConsoleHandle: HANDLE,
dwCursorPosition: COORD) -> BOOL ---
SetConsoleTextAttribute :: proc(hConsoleOutput: HANDLE,
wAttributes: WORD) -> BOOL ---
SetConsoleOutputCP :: proc(wCodePageID: UINT) -> BOOL ---
GetFileInformationByHandle :: proc(hFile: HANDLE, lpFileInformation: LPBY_HANDLE_FILE_INFORMATION) -> BOOL ---
SetHandleInformation :: proc(hObject: HANDLE,
dwMask: DWORD,
@@ -373,6 +393,8 @@ foreign kernel32 {
GetConsoleScreenBufferInfo :: proc(hConsoleOutput: HANDLE, lpConsoleScreenBufferInfo: PCONSOLE_SCREEN_BUFFER_INFO) -> BOOL ---
SetConsoleScreenBufferSize :: proc(hConsoleOutput: HANDLE, dwSize: COORD) -> BOOL ---
SetConsoleWindowInfo :: proc(hConsoleOutput: HANDLE, bAbsolute : BOOL, lpConsoleWindow: ^SMALL_RECT) -> BOOL ---
GetConsoleCursorInfo :: proc(hConsoleOutput: HANDLE, lpConsoleCursorInfo: PCONSOLE_CURSOR_INFO) -> BOOL ---
SetConsoleCursorInfo :: proc(hConsoleOutput: HANDLE, lpConsoleCursorInfo: PCONSOLE_CURSOR_INFO) -> BOOL ---
GetDiskFreeSpaceExW :: proc(
lpDirectoryName: LPCWSTR,
+2
View File
@@ -23,6 +23,8 @@ foreign shell32 {
SHFileOperationW :: proc(lpFileOp: LPSHFILEOPSTRUCTW) -> c_int ---
SHGetFolderPathW :: proc(hwnd: HWND, csidl: c_int, hToken: HANDLE, dwFlags: DWORD, pszPath: LPWSTR) -> HRESULT ---
SHAppBarMessage :: proc(dwMessage: DWORD, pData: PAPPBARDATA) -> UINT_PTR ---
Shell_NotifyIconW :: proc(dwMessage: DWORD, lpData: ^NOTIFYICONDATAW) -> BOOL ---
}
APPBARDATA :: struct {
+422 -162
View File
@@ -145,8 +145,6 @@ PCONDITION_VARIABLE :: ^CONDITION_VARIABLE
PLARGE_INTEGER :: ^LARGE_INTEGER
PSRWLOCK :: ^SRWLOCK
MMRESULT :: UINT
CREATE_WAITABLE_TIMER_MANUAL_RESET :: 0x00000001
CREATE_WAITABLE_TIMER_HIGH_RESOLUTION :: 0x00000002
@@ -154,10 +152,6 @@ TIMER_QUERY_STATE :: 0x0001
TIMER_MODIFY_STATE :: 0x0002
TIMER_ALL_ACCESS :: STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE | TIMER_QUERY_STATE | TIMER_MODIFY_STATE
SOCKET :: distinct uintptr // TODO
socklen_t :: c_int
ADDRESS_FAMILY :: USHORT
TRUE :: BOOL(true)
FALSE :: BOOL(false)
@@ -265,26 +259,6 @@ GET_FILEEX_INFO_LEVELS :: distinct i32
GetFileExInfoStandard: GET_FILEEX_INFO_LEVELS : 0
GetFileExMaxInfoLevel: GET_FILEEX_INFO_LEVELS : 1
// String resource number bases (internal use)
MMSYSERR_BASE :: 0
WAVERR_BASE :: 32
MIDIERR_BASE :: 64
TIMERR_BASE :: 96
JOYERR_BASE :: 160
MCIERR_BASE :: 256
MIXERR_BASE :: 1024
MCI_STRING_OFFSET :: 512
MCI_VD_OFFSET :: 1024
MCI_CD_OFFSET :: 1088
MCI_WAVE_OFFSET :: 1152
MCI_SEQ_OFFSET :: 1216
// timer error return values
TIMERR_NOERROR :: 0 // no error
TIMERR_NOCANDO :: TIMERR_BASE + 1 // request not completed
TIMERR_STRUCT :: TIMERR_BASE + 33 // time struct size
DIAGNOSTIC_REASON_VERSION :: 0
@@ -728,6 +702,14 @@ CWPRETSTRUCT :: struct {
hwnd: HWND,
}
MSLLHOOKSTRUCT :: struct {
pt: POINT,
mouseData: DWORD,
flags: DWORD,
time: DWORD,
dwExtraInfo: ULONG_PTR,
}
KBDLLHOOKSTRUCT :: struct {
vkCode: DWORD,
scanCode: DWORD,
@@ -736,6 +718,59 @@ KBDLLHOOKSTRUCT :: struct {
dwExtraInfo: ULONG_PTR,
}
MOUSEINPUT :: struct {
dx: LONG,
dy: LONG,
mouseData: DWORD,
dwFlags: DWORD,
time: DWORD,
dwExtraInfo: ULONG_PTR,
}
KEYBDINPUT :: struct {
wVk: WORD,
wScan: WORD,
dwFlags: DWORD,
time: DWORD,
dwExtraInfo: ULONG_PTR,
}
HARDWAREINPUT :: struct {
uMsg: DWORD,
wParamL: WORD,
wParamH: WORD,
}
INPUT_TYPE :: enum DWORD {
MOUSE = 0,
KEYBOARD = 1,
HARDWARE = 2,
}
INPUT :: struct {
type: INPUT_TYPE,
using _: struct #raw_union {
mi: MOUSEINPUT,
ki: KEYBDINPUT,
hi: HARDWAREINPUT,
},
}
MOUSEEVENTF_MOVE :: 0x0001
MOUSEEVENTF_LEFTDOWN :: 0x0002
MOUSEEVENTF_LEFTUP :: 0x0004
MOUSEEVENTF_RIGHTDOWN :: 0x0008
MOUSEEVENTF_RIGHTUP :: 0x0010
MOUSEEVENTF_MIDDLEDOWN :: 0x0020
MOUSEEVENTF_MIDDLEUP :: 0x0040
MOUSEEVENTF_XDOWN :: 0x0080
MOUSEEVENTF_XUP :: 0x0100
MOUSEEVENTF_WHEEL :: 0x0800
MOUSEEVENTF_HWHEEL :: 0x1000
MOUSEEVENTF_MOVE_NOCOALESCE :: 0x2000
MOUSEEVENTF_VIRTUALDESK :: 0x4000
MOUSEEVENTF_ABSOLUTE :: 0x8000
WNDCLASSA :: struct {
style: UINT,
lpfnWndProc: WNDPROC,
@@ -803,6 +838,104 @@ MSG :: struct {
LPMSG :: ^MSG
NOTIFYICONDATAW :: struct {
cbSize: DWORD,
hWnd: HWND,
uID: UINT,
uFlags: UINT,
uCallbackMessage: UINT,
hIcon: HICON,
szTip: [128]WCHAR,
dwState: DWORD,
dwStateMask: DWORD,
szInfo: [256]WCHAR,
using _: struct #raw_union {
uTimeout: UINT,
uVersion: UINT,
},
szInfoTitle: [64]WCHAR,
dwInfoFlags: DWORD,
guidItem: GUID,
hBalloonIcon: HICON,
}
NIF_MESSAGE :: 0x00000001
NIF_ICON :: 0x00000002
NIF_TIP :: 0x00000004
NIF_STATE :: 0x00000008
NIF_INFO :: 0x00000010
NIF_GUID :: 0x00000020
NIF_REALTIME :: 0x00000040
NIF_SHOWTIP :: 0x00000080
NIM_ADD :: 0x00000000
NIM_MODIFY :: 0x00000001
NIM_DELETE :: 0x00000002
NIM_SETFOCUS :: 0x00000003
NIM_SETVERSION :: 0x00000004
// Menu flags for Add/Check/EnableMenuItem()
MF_INSERT :: 0x00000000
MF_CHANGE :: 0x00000080
MF_APPEND :: 0x00000100
MF_DELETE :: 0x00000200
MF_REMOVE :: 0x00001000
MF_BYCOMMAND :: 0x00000000
MF_BYPOSITION :: 0x00000400
MF_SEPARATOR :: 0x00000800
MF_ENABLED :: 0x00000000
MF_GRAYED :: 0x00000001
MF_DISABLED :: 0x00000002
MF_UNCHECKED :: 0x00000000
MF_CHECKED :: 0x00000008
MF_USECHECKBITMAPS :: 0x00000200
MF_STRING :: 0x00000000
MF_BITMAP :: 0x00000004
MF_OWNERDRAW :: 0x00000100
MF_POPUP :: 0x00000010
MF_MENUBARBREAK :: 0x00000020
MF_MENUBREAK :: 0x00000040
MF_UNHILITE :: 0x00000000
MF_HILITE :: 0x00000080
MF_DEFAULT :: 0x00001000
MF_SYSMENU :: 0x00002000
MF_HELP :: 0x00004000
MF_RIGHTJUSTIFY :: 0x00004000
MF_MOUSESELECT :: 0x00008000
MF_END :: 0x00000080 // Obsolete -- only used by old RES files
// Flags for TrackPopupMenu
TPM_LEFTBUTTON :: 0x0000
TPM_RIGHTBUTTON :: 0x0002
TPM_LEFTALIGN :: 0x0000
TPM_CENTERALIGN :: 0x0004
TPM_RIGHTALIGN :: 0x0008
TPM_TOPALIGN :: 0x0000
TPM_VCENTERALIGN :: 0x0010
TPM_BOTTOMALIGN :: 0x0020
TPM_HORIZONTAL :: 0x0000 /* Horz alignment matters more */
TPM_VERTICAL :: 0x0040 /* Vert alignment matters more */
TPM_NONOTIFY :: 0x0080 /* Don't send any notification msgs */
TPM_RETURNCMD :: 0x0100
TPM_RECURSE :: 0x0001
TPM_HORPOSANIMATION :: 0x0400
TPM_HORNEGANIMATION :: 0x0800
TPM_VERPOSANIMATION :: 0x1000
TPM_VERNEGANIMATION :: 0x2000
TPM_NOANIMATION :: 0x4000
TPM_LAYOUTRTL :: 0x8000
TPM_WORKAREA :: 0x10000
// WM_NCHITTEST and MOUSEHOOKSTRUCT Mouse Position Codes
HTERROR :: -2
HTTRANSPARENT :: -1
@@ -1868,30 +2001,6 @@ BI_BITFIELDS :: 3
BI_JPEG :: 4
BI_PNG :: 5
WSA_FLAG_OVERLAPPED: DWORD : 0x01
WSA_FLAG_NO_HANDLE_INHERIT: DWORD : 0x80
WSADESCRIPTION_LEN :: 256
WSASYS_STATUS_LEN :: 128
WSAPROTOCOL_LEN: DWORD : 255
INVALID_SOCKET :: ~SOCKET(0)
WSAEACCES: c_int : 10013
WSAEINVAL: c_int : 10022
WSAEWOULDBLOCK: c_int : 10035
WSAEPROTOTYPE: c_int : 10041
WSAEADDRINUSE: c_int : 10048
WSAEADDRNOTAVAIL: c_int : 10049
WSAECONNABORTED: c_int : 10053
WSAECONNRESET: c_int : 10054
WSAENOTCONN: c_int : 10057
WSAESHUTDOWN: c_int : 10058
WSAETIMEDOUT: c_int : 10060
WSAECONNREFUSED: c_int : 10061
WSATRY_AGAIN: c_int : 11002
MAX_PROTOCOL_CHAIN: DWORD : 7
MAXIMUM_REPARSE_DATA_BUFFER_SIZE :: 16 * 1024
FSCTL_GET_REPARSE_POINT: DWORD : 0x900a8
IO_REPARSE_TAG_SYMLINK: DWORD : 0xa000000c
@@ -1949,44 +2058,6 @@ CREATE_NEW_PROCESS_GROUP: DWORD : 0x00000200
CREATE_UNICODE_ENVIRONMENT: DWORD : 0x00000400
STARTF_USESTDHANDLES: DWORD : 0x00000100
AF_INET: c_int : 2
AF_INET6: c_int : 23
SD_BOTH: c_int : 2
SD_RECEIVE: c_int : 0
SD_SEND: c_int : 1
SOCK_DGRAM: c_int : 2
SOCK_STREAM: c_int : 1
SOL_SOCKET: c_int : 0xffff
SO_RCVTIMEO: c_int : 0x1006
SO_SNDTIMEO: c_int : 0x1005
SO_REUSEADDR: c_int : 0x0004
IPPROTO_IP: c_int : 0
IPPROTO_TCP: c_int : 6
IPPROTO_IPV6: c_int : 41
TCP_NODELAY: c_int : 0x0001
IP_TTL: c_int : 4
IPV6_V6ONLY: c_int : 27
SO_ERROR: c_int : 0x1007
SO_BROADCAST: c_int : 0x0020
IP_MULTICAST_LOOP: c_int : 11
IPV6_MULTICAST_LOOP: c_int : 11
IP_MULTICAST_TTL: c_int : 10
IP_ADD_MEMBERSHIP: c_int : 12
IP_DROP_MEMBERSHIP: c_int : 13
IPV6_ADD_MEMBERSHIP: c_int : 12
IPV6_DROP_MEMBERSHIP: c_int : 13
MSG_PEEK: c_int : 0x2
ip_mreq :: struct {
imr_multiaddr: in_addr,
imr_interface: in_addr,
}
ipv6_mreq :: struct {
ipv6mr_multiaddr: in6_addr,
ipv6mr_interface: c_uint,
}
VOLUME_NAME_DOS: DWORD : 0x0
MOVEFILE_REPLACE_EXISTING: DWORD : 1
@@ -2369,11 +2440,6 @@ STARTUPINFO :: struct {
hStdError: HANDLE,
}
SOCKADDR :: struct {
sa_family: ADDRESS_FAMILY,
sa_data: [14]CHAR,
}
FILETIME :: struct {
dwLowDateTime: DWORD,
dwHighDateTime: DWORD,
@@ -2406,74 +2472,6 @@ ADDRESS_MODE :: enum c_int {
AddrModeFlat,
}
SOCKADDR_STORAGE_LH :: struct {
ss_family: ADDRESS_FAMILY,
__ss_pad1: [6]CHAR,
__ss_align: i64,
__ss_pad2: [112]CHAR,
}
ADDRINFOA :: struct {
ai_flags: c_int,
ai_family: c_int,
ai_socktype: c_int,
ai_protocol: c_int,
ai_addrlen: size_t,
ai_canonname: ^c_char,
ai_addr: ^SOCKADDR,
ai_next: ^ADDRINFOA,
}
PADDRINFOEXW :: ^ADDRINFOEXW
LPADDRINFOEXW :: ^ADDRINFOEXW
ADDRINFOEXW :: struct {
ai_flags: c_int,
ai_family: c_int,
ai_socktype: c_int,
ai_protocol: c_int,
ai_addrlen: size_t,
ai_canonname: wstring,
ai_addr: ^sockaddr,
ai_blob: rawptr,
ai_bloblen: size_t,
ai_provider: LPGUID,
ai_next: ^ADDRINFOEXW,
}
LPLOOKUPSERVICE_COMPLETION_ROUTINE :: #type proc "stdcall" (
dwErrorCode: DWORD,
dwNumberOfBytesTransfered: DWORD,
lpOverlapped: LPOVERLAPPED,
)
sockaddr :: struct {
sa_family: USHORT,
sa_data: [14]byte,
}
sockaddr_in :: struct {
sin_family: ADDRESS_FAMILY,
sin_port: USHORT,
sin_addr: in_addr,
sin_zero: [8]CHAR,
}
sockaddr_in6 :: struct {
sin6_family: ADDRESS_FAMILY,
sin6_port: USHORT,
sin6_flowinfo: c_ulong,
sin6_addr: in6_addr,
sin6_scope_id: c_ulong,
}
in_addr :: struct {
s_addr: u32,
}
in6_addr :: struct {
s6_addr: [16]u8,
}
EXCEPTION_DISPOSITION :: enum c_int {
ExceptionContinueExecution,
ExceptionContinueSearch,
@@ -2564,6 +2562,27 @@ FILE_ATTRIBUTE_TAG_INFO :: struct {
ReparseTag: DWORD,
}
PADDRINFOEXW :: ^ADDRINFOEXW
LPADDRINFOEXW :: ^ADDRINFOEXW
ADDRINFOEXW :: struct {
ai_flags: c_int,
ai_family: c_int,
ai_socktype: c_int,
ai_protocol: c_int,
ai_addrlen: size_t,
ai_canonname: wstring,
ai_addr: ^sockaddr,
ai_blob: rawptr,
ai_bloblen: size_t,
ai_provider: LPGUID,
ai_next: ^ADDRINFOEXW,
}
LPLOOKUPSERVICE_COMPLETION_ROUTINE :: #type proc "stdcall" (
dwErrorCode: DWORD,
dwNumberOfBytesTransfered: DWORD,
lpOverlapped: LPOVERLAPPED,
)
// https://docs.microsoft.com/en-gb/windows/win32/api/sysinfoapi/ns-sysinfoapi-system_info
@@ -3900,5 +3919,246 @@ CONSOLE_SCREEN_BUFFER_INFO :: struct {
dwMaximumWindowSize: COORD,
}
CONSOLE_CURSOR_INFO :: struct {
dwSize: DWORD,
bVisible: BOOL,
}
PCONSOLE_SCREEN_BUFFER_INFO :: ^CONSOLE_SCREEN_BUFFER_INFO
PCONSOLE_SCREEN_BUFFER_INFO :: ^CONSOLE_SCREEN_BUFFER_INFO
PCONSOLE_CURSOR_INFO :: ^CONSOLE_CURSOR_INFO
//
// Networking
//
WSA_FLAG_OVERLAPPED :: 1
WSA_FLAG_MULTIPOINT_C_ROOT :: 2
WSA_FLAG_MULTIPOINT_C_LEAF :: 4
WSA_FLAG_MULTIPOINT_D_ROOT :: 8
WSA_FLAG_MULTIPOINT_D_LEAF :: 16
WSA_FLAG_ACCESS_SYSTEM_SECURITY :: 32
WSA_FLAG_NO_HANDLE_INHERIT :: 128
WSADESCRIPTION_LEN :: 256
WSASYS_STATUS_LEN :: 128
WSAPROTOCOL_LEN :: 255
INVALID_SOCKET :: ~SOCKET(0)
SOMAXCONN :: 128 // The number of messages that can be queued in memory after being received; use 2-4 for Bluetooth.
// This is for the 'backlog' parameter to listen().
SOCKET_ERROR :: -1
// Networking errors
WSAEINTR :: 10004 // Call interrupted. CancelBlockingCall was called. (This is different on Linux.)
WSAEACCES :: 10013 // If you try to bind a Udp socket to the broadcast address without the socket option set.
WSAEFAULT :: 10014 // A pointer that was passed to a WSA function is invalid, such as a buffer size is smaller than you said it was
WSAEINVAL :: 10022 // Invalid argument supplied
WSAEMFILE :: 10024 // SOCKET handles exhausted
WSAEWOULDBLOCK :: 10035 // No data is ready yet
WSAENOTSOCK :: 10038 // Not a socket.
WSAEINPROGRESS :: 10036 // WS1.1 call is in progress or callback function is still being processed
WSAEALREADY :: 10037 // Already connecting in parallel.
WSAEMSGSIZE :: 10040 // Message was truncated because it exceeded max datagram size.
WSAEPROTOTYPE :: 10041 // Wrong protocol for the provided socket
WSAENOPROTOOPT :: 10042 // TODO
WSAEPROTONOSUPPORT :: 10043 // Protocol not supported
WSAESOCKTNOSUPPORT :: 10044 // Socket type not supported in the given address family
WSAEAFNOSUPPORT :: 10047 // Address family not supported
WSAEOPNOTSUPP :: 10045 // Attempt to accept on non-stream socket, etc.
WSAEADDRINUSE :: 10048 // Endpoint being bound is in use by another socket.
WSAEADDRNOTAVAIL :: 10049 // Not a valid local IP address on this computer.
WSAENETDOWN :: 10050 // Network subsystem failure on the local machine.
WSAENETUNREACH :: 10051 // The local machine is not connected to the network.
WSAENETRESET :: 10052 // Keepalive failure detected, or TTL exceeded when receiving UDP packets.
WSAECONNABORTED :: 10053 // Connection has been aborted by software in the host machine.
WSAECONNRESET :: 10054 // The connection was reset while trying to accept, read or write.
WSAENOBUFS :: 10055 // No buffer space is available. The outgoing queue may be full in which case you should probably try again after a pause.
WSAEISCONN :: 10056 // The socket is already connected.
WSAENOTCONN :: 10057 // The socket is not connected yet, or no address was supplied to sendto.
WSAESHUTDOWN :: 10058 // The socket has been shutdown in the direction required.
WSAETIMEDOUT :: 10060 // The timeout duration was reached before any data was received / before all data was sent.
WSAECONNREFUSED :: 10061 // The remote machine is not listening on that endpoint.
WSAEHOSTDOWN :: 10064 // Destination host was down.
WSAEHOSTUNREACH :: 10065 // The remote machine is not connected to the network.
WSAENOTINITIALISED :: 10093 // Needs WSAStartup call
WSAEINVALIDPROCTABLE :: 10104 // Invalid or incomplete procedure table was returned
WSAEINVALIDPROVIDER :: 10105 // Service provider version is not 2.2
WSAEPROVIDERFAILEDINIT :: 10106 // Service provider failed to initialize
// Address families
AF_UNSPEC : c_int : 0 // Unspecified
AF_INET : c_int : 2 // IPv4
AF_INET6 : c_int : 23 // IPv6
AF_IRDA : c_int : 26 // Infrared
AF_BTH : c_int : 32 // Bluetooth
// Socket types
SOCK_STREAM : c_int : 1 // TCP
SOCK_DGRAM : c_int : 2 // UDP
SOCK_RAW : c_int : 3 // Requires options IP_HDRINCL for v4, IPV6_HDRINCL for v6, on the socket
SOCK_RDM : c_int : 4 // Requires "Reliable Multicast Protocol" to be installed - see WSAEnumProtocols
SOCK_SEQPACKET : c_int : 5 // Provides psuedo-stream packet based on DGRAMs.
// Protocols
IPPROTO_IP : c_int : 0
IPPROTO_ICMP : c_int : 1 // (AF_UNSPEC, AF_INET, AF_INET6) + SOCK_RAW | not specified
IPPROTO_IGMP : c_int : 2 // (AF_UNSPEC, AF_INET, AF_INET6) + SOCK_RAW | not specified
BTHPROTO_RFCOMM : c_int : 3 // Bluetooth: AF_BTH + SOCK_STREAM
IPPROTO_TCP : c_int : 6 // (AF_INET, AF_INET6) + SOCK_STREAM
IPPROTO_UDP : c_int : 17 // (AF_INET, AF_INET6) + SOCK_DGRAM
IPPROTO_ICMPV6 : c_int : 58 // (AF_UNSPEC, AF_INET, AF_INET6) + SOCK_RAW
IPPROTO_RM : c_int : 113 // AF_INET + SOCK_RDM [requires "Reliable Multicast Protocol" to be installed - see WSAEnumProtocols]
// Shutdown manners
SD_RECEIVE : c_int : 0
SD_SEND : c_int : 1
SD_BOTH : c_int : 2
// Socket 'levels'
SOL_SOCKET : c_int : 0xffff // Socket options for any socket.
IPPROTO_IPV6 : c_int : 41 // Socket options for IPV6.
// Options for any sockets
SO_ACCEPTCONN : c_int : 0x0002
SO_REUSEADDR : c_int : 0x0004
SO_KEEPALIVE : c_int : 0x0008
SO_SNDTIMEO : c_int : 0x1005
SO_RCVTIMEO : c_int : 0x1006
SO_EXCLUSIVEADDRUSE : c_int : ~SO_REUSEADDR
SO_CONDITIONAL_ACCEPT : c_int : 0x3002
SO_DONTLINGER : c_int : ~SO_LINGER
SO_OOBINLINE : c_int : 0x0100
SO_LINGER : c_int : 0x0080
SO_RCVBUF : c_int : 0x1002
SO_SNDBUF : c_int : 0x1001
SO_ERROR : c_int : 0x1007
SO_BROADCAST : c_int : 0x0020
TCP_NODELAY: c_int : 0x0001
IP_TTL: c_int : 4
IPV6_V6ONLY: c_int : 27
IP_MULTICAST_LOOP: c_int : 11
IPV6_MULTICAST_LOOP: c_int : 11
IP_MULTICAST_TTL: c_int : 10
IP_ADD_MEMBERSHIP: c_int : 12
IPV6_ADD_MEMBERSHIP: c_int : 12
IPV6_DROP_MEMBERSHIP: c_int : 13
MAX_PROTOCOL_CHAIN: DWORD : 7
// Used with the SO_LINGER socket option to setsockopt().
LINGER :: struct {
l_onoff: c.ushort,
l_linger: c.ushort,
}
// Send/Receive flags.
MSG_OOB : c_int : 1 // `send`/`recv` should process out-of-band data.
MSG_PEEK : c_int : 2 // `recv` should not remove the data from the buffer. Only valid for non-overlapped operations.
SOCKET :: distinct uintptr // TODO
socklen_t :: c_int
ADDRESS_FAMILY :: USHORT
ip_mreq :: struct {
imr_multiaddr: in_addr,
imr_interface: in_addr,
}
ipv6_mreq :: struct {
ipv6mr_multiaddr: in6_addr,
ipv6mr_interface: c_uint,
}
SOCKADDR_STORAGE_LH :: struct {
ss_family: ADDRESS_FAMILY,
__ss_pad1: [6]CHAR,
__ss_align: i64,
__ss_pad2: [112]CHAR,
}
ADDRINFOA :: struct {
ai_flags: c_int,
ai_family: c_int,
ai_socktype: c_int,
ai_protocol: c_int,
ai_addrlen: size_t,
ai_canonname: ^c_char,
ai_addr: ^SOCKADDR,
ai_next: ^ADDRINFOA,
}
sockaddr :: struct {
sa_family: USHORT,
sa_data: [14]byte,
}
sockaddr_in :: struct {
sin_family: ADDRESS_FAMILY,
sin_port: u16be,
sin_addr: in_addr,
sin_zero: [8]CHAR,
}
sockaddr_in6 :: struct {
sin6_family: ADDRESS_FAMILY,
sin6_port: u16be,
sin6_flowinfo: c_ulong,
sin6_addr: in6_addr,
sin6_scope_id: c_ulong,
}
in_addr :: struct {
s_addr: u32,
}
in6_addr :: struct {
s6_addr: [16]u8,
}
DNS_STATUS :: distinct DWORD // zero is success
DNS_INFO_NO_RECORDS :: 9501
DNS_QUERY_NO_RECURSION :: 0x00000004
DNS_RECORD :: struct {
pNext: ^DNS_RECORD,
pName: cstring,
wType: WORD,
wDataLength: USHORT,
Flags: DWORD,
dwTtl: DWORD,
_: DWORD,
Data: struct #raw_union {
CNAME: DNS_PTR_DATAA,
A: u32be, // Ipv4 Address
AAAA: u128be, // Ipv6 Address
TXT: DNS_TXT_DATAA,
NS: DNS_PTR_DATAA,
MX: DNS_MX_DATAA,
SRV: DNS_SRV_DATAA,
},
}
DNS_TXT_DATAA :: struct {
dwStringCount: DWORD,
pStringArray: cstring,
}
DNS_PTR_DATAA :: cstring
DNS_MX_DATAA :: struct {
pNameExchange: cstring, // the hostname
wPreference: WORD, // lower values preferred
_: WORD, // padding.
}
DNS_SRV_DATAA :: struct {
pNameTarget: cstring,
wPriority: u16,
wWeight: u16,
wPort: u16,
_: WORD, // padding
}
SOCKADDR :: struct {
sa_family: ADDRESS_FAMILY,
sa_data: [14]CHAR,
}
+9 -1
View File
@@ -109,6 +109,12 @@ foreign user32 {
GetDlgCtrlID :: proc(hWnd: HWND) -> c_int ---
GetDlgItem :: proc(hDlg: HWND, nIDDlgItem: c_int) -> HWND ---
CreatePopupMenu :: proc() -> HMENU ---
DestroyMenu :: proc(hMenu: HMENU) -> BOOL ---
AppendMenuW :: proc(hMenu: HMENU, uFlags: UINT, uIDNewItem: UINT_PTR, lpNewItem: LPCWSTR) -> BOOL ---
TrackPopupMenu :: proc(hMenu: HMENU, uFlags: UINT, x: int, y: int, nReserved: int, hWnd: HWND, prcRect: ^RECT) -> i32 ---
RegisterWindowMessageW :: proc(lpString: LPCWSTR) -> UINT ---
GetUpdateRect :: proc(hWnd: HWND, lpRect: LPRECT, bErase: BOOL) -> BOOL ---
ValidateRect :: proc(hWnd: HWND, lpRect: ^RECT) -> BOOL ---
InvalidateRect :: proc(hWnd: HWND, lpRect: ^RECT, bErase: BOOL) -> BOOL ---
@@ -206,6 +212,8 @@ foreign user32 {
GetRegisteredRawInputDevices :: proc(pRawInputDevices: PRAWINPUTDEVICE, puiNumDevices: PUINT, cbSize: UINT) -> UINT ---
RegisterRawInputDevices :: proc(pRawInputDevices: PCRAWINPUTDEVICE, uiNumDevices: UINT, cbSize: UINT) -> BOOL ---
SendInput :: proc(cInputs: UINT, pInputs: [^]INPUT, cbSize: ^c_int) -> UINT ---
SetLayeredWindowAttributes :: proc(hWnd: HWND, crKey: COLORREF, bAlpha: BYTE, dwFlags: DWORD) -> BOOL ---
FillRect :: proc(hDC: HDC, lprc: ^RECT, hbr: HBRUSH) -> int ---
@@ -469,4 +477,4 @@ WINDOWINFO :: struct {
atomWindowType: ATOM,
wCreatorVersion: WORD,
}
PWINDOWINFO :: ^WINDOWINFO
PWINDOWINFO :: ^WINDOWINFO
+21
View File
@@ -485,3 +485,24 @@ run_as_user :: proc(username, password, application, commandline: string, pi: ^P
return false
}
}
ensure_winsock_initialized :: proc() {
@static gate := false
@static initted := false
if initted {
return
}
for intrinsics.atomic_compare_exchange_strong(&gate, false, true) {
intrinsics.cpu_relax()
}
defer intrinsics.atomic_store(&gate, false)
unused_info: WSADATA
version_requested := WORD(2) << 8 | 2
res := WSAStartup(version_requested, &unused_info)
assert(res == 0, "unable to initialized Winsock2")
initted = true
}
+161
View File
@@ -3,9 +3,170 @@ package sys_windows
foreign import winmm "system:Winmm.lib"
MMRESULT :: UINT
@(default_calling_convention="stdcall")
foreign winmm {
timeGetDevCaps :: proc(ptc: LPTIMECAPS, cbtc: UINT) -> MMRESULT ---
timeBeginPeriod :: proc(uPeriod: UINT) -> MMRESULT ---
timeEndPeriod :: proc(uPeriod: UINT) -> MMRESULT ---
timeGetTime :: proc() -> DWORD ---
}
LPTIMECAPS :: ^TIMECAPS
TIMECAPS :: struct {
wPeriodMin: UINT,
wPeriodMax: UINT,
}
// String resource number bases (internal use)
MMSYSERR_BASE :: 0
WAVERR_BASE :: 32
MIDIERR_BASE :: 64
TIMERR_BASE :: 96
JOYERR_BASE :: 160
MCIERR_BASE :: 256
MIXERR_BASE :: 1024
MCI_STRING_OFFSET :: 512
MCI_VD_OFFSET :: 1024
MCI_CD_OFFSET :: 1088
MCI_WAVE_OFFSET :: 1152
MCI_SEQ_OFFSET :: 1216
/* general error return values */
MMSYSERR_NOERROR :: 0 /* no error */
MMSYSERR_ERROR :: MMSYSERR_BASE + 1 /* unspecified error */
MMSYSERR_BADDEVICEID :: MMSYSERR_BASE + 2 /* device ID out of range */
MMSYSERR_NOTENABLED :: MMSYSERR_BASE + 3 /* driver failed enable */
MMSYSERR_ALLOCATED :: MMSYSERR_BASE + 4 /* device already allocated */
MMSYSERR_INVALHANDLE :: MMSYSERR_BASE + 5 /* device handle is invalid */
MMSYSERR_NODRIVER :: MMSYSERR_BASE + 6 /* no device driver present */
MMSYSERR_NOMEM :: MMSYSERR_BASE + 7 /* memory allocation error */
MMSYSERR_NOTSUPPORTED :: MMSYSERR_BASE + 8 /* function isn't supported */
MMSYSERR_BADERRNUM :: MMSYSERR_BASE + 9 /* error value out of range */
MMSYSERR_INVALFLAG :: MMSYSERR_BASE + 10 /* invalid flag passed */
MMSYSERR_INVALPARAM :: MMSYSERR_BASE + 11 /* invalid parameter passed */
MMSYSERR_HANDLEBUSY :: MMSYSERR_BASE + 12 /* handle being used simultaneously on another thread (eg callback) */
MMSYSERR_INVALIDALIAS :: MMSYSERR_BASE + 13 /* specified alias not found */
MMSYSERR_BADDB :: MMSYSERR_BASE + 14 /* bad registry database */
MMSYSERR_KEYNOTFOUND :: MMSYSERR_BASE + 15 /* registry key not found */
MMSYSERR_READERROR :: MMSYSERR_BASE + 16 /* registry read error */
MMSYSERR_WRITEERROR :: MMSYSERR_BASE + 17 /* registry write error */
MMSYSERR_DELETEERROR :: MMSYSERR_BASE + 18 /* registry delete error */
MMSYSERR_VALNOTFOUND :: MMSYSERR_BASE + 19 /* registry value not found */
MMSYSERR_NODRIVERCB :: MMSYSERR_BASE + 20 /* driver does not call DriverCallback */
MMSYSERR_MOREDATA :: MMSYSERR_BASE + 21 /* more data to be returned */
MMSYSERR_LASTERROR :: MMSYSERR_BASE + 21 /* last error in range */
/* waveform audio error return values */
WAVERR_BADFORMAT :: WAVERR_BASE + 0 /* unsupported wave format */
WAVERR_STILLPLAYING :: WAVERR_BASE + 1 /* still something playing */
WAVERR_UNPREPARED :: WAVERR_BASE + 2 /* header not prepared */
WAVERR_SYNC :: WAVERR_BASE + 3 /* device is synchronous */
WAVERR_LASTERROR :: WAVERR_BASE + 3 /* last error in range */
/* MIDI error return values */
MIDIERR_UNPREPARED :: MIDIERR_BASE + 0 /* header not prepared */
MIDIERR_STILLPLAYING :: MIDIERR_BASE + 1 /* still something playing */
MIDIERR_NOMAP :: MIDIERR_BASE + 2 /* no configured instruments */
MIDIERR_NOTREADY :: MIDIERR_BASE + 3 /* hardware is still busy */
MIDIERR_NODEVICE :: MIDIERR_BASE + 4 /* port no longer connected */
MIDIERR_INVALIDSETUP :: MIDIERR_BASE + 5 /* invalid MIF */
MIDIERR_BADOPENMODE :: MIDIERR_BASE + 6 /* operation unsupported w/ open mode */
MIDIERR_DONT_CONTINUE :: MIDIERR_BASE + 7 /* thru device 'eating' a message */
MIDIERR_LASTERROR :: MIDIERR_BASE + 7 /* last error in range */
/* timer error return values */
TIMERR_NOERROR :: 0 /* no error */
TIMERR_NOCANDO :: TIMERR_BASE + 1 /* request not completed */
TIMERR_STRUCT :: TIMERR_BASE + 33 /* time struct size */
/* joystick error return values */
JOYERR_NOERROR :: 0 /* no error */
JOYERR_PARMS :: JOYERR_BASE + 5 /* bad parameters */
JOYERR_NOCANDO :: JOYERR_BASE + 6 /* request not completed */
JOYERR_UNPLUGGED :: JOYERR_BASE + 7 /* joystick is unplugged */
/* MCI error return values */
MCIERR_INVALID_DEVICE_ID :: MCIERR_BASE + 1
MCIERR_UNRECOGNIZED_KEYWORD :: MCIERR_BASE + 3
MCIERR_UNRECOGNIZED_COMMAND :: MCIERR_BASE + 5
MCIERR_HARDWARE :: MCIERR_BASE + 6
MCIERR_INVALID_DEVICE_NAME :: MCIERR_BASE + 7
MCIERR_OUT_OF_MEMORY :: MCIERR_BASE + 8
MCIERR_DEVICE_OPEN :: MCIERR_BASE + 9
MCIERR_CANNOT_LOAD_DRIVER :: MCIERR_BASE + 10
MCIERR_MISSING_COMMAND_STRING :: MCIERR_BASE + 11
MCIERR_PARAM_OVERFLOW :: MCIERR_BASE + 12
MCIERR_MISSING_STRING_ARGUMENT :: MCIERR_BASE + 13
MCIERR_BAD_INTEGER :: MCIERR_BASE + 14
MCIERR_PARSER_INTERNAL :: MCIERR_BASE + 15
MCIERR_DRIVER_INTERNAL :: MCIERR_BASE + 16
MCIERR_MISSING_PARAMETER :: MCIERR_BASE + 17
MCIERR_UNSUPPORTED_FUNCTION :: MCIERR_BASE + 18
MCIERR_FILE_NOT_FOUND :: MCIERR_BASE + 19
MCIERR_DEVICE_NOT_READY :: MCIERR_BASE + 20
MCIERR_INTERNAL :: MCIERR_BASE + 21
MCIERR_DRIVER :: MCIERR_BASE + 22
MCIERR_CANNOT_USE_ALL :: MCIERR_BASE + 23
MCIERR_MULTIPLE :: MCIERR_BASE + 24
MCIERR_EXTENSION_NOT_FOUND :: MCIERR_BASE + 25
MCIERR_OUTOFRANGE :: MCIERR_BASE + 26
MCIERR_FLAGS_NOT_COMPATIBLE :: MCIERR_BASE + 28
MCIERR_FILE_NOT_SAVED :: MCIERR_BASE + 30
MCIERR_DEVICE_TYPE_REQUIRED :: MCIERR_BASE + 31
MCIERR_DEVICE_LOCKED :: MCIERR_BASE + 32
MCIERR_DUPLICATE_ALIAS :: MCIERR_BASE + 33
MCIERR_BAD_CONSTANT :: MCIERR_BASE + 34
MCIERR_MUST_USE_SHAREABLE :: MCIERR_BASE + 35
MCIERR_MISSING_DEVICE_NAME :: MCIERR_BASE + 36
MCIERR_BAD_TIME_FORMAT :: MCIERR_BASE + 37
MCIERR_NO_CLOSING_QUOTE :: MCIERR_BASE + 38
MCIERR_DUPLICATE_FLAGS :: MCIERR_BASE + 39
MCIERR_INVALID_FILE :: MCIERR_BASE + 40
MCIERR_NULL_PARAMETER_BLOCK :: MCIERR_BASE + 41
MCIERR_UNNAMED_RESOURCE :: MCIERR_BASE + 42
MCIERR_NEW_REQUIRES_ALIAS :: MCIERR_BASE + 43
MCIERR_NOTIFY_ON_AUTO_OPEN :: MCIERR_BASE + 44
MCIERR_NO_ELEMENT_ALLOWED :: MCIERR_BASE + 45
MCIERR_NONAPPLICABLE_FUNCTION :: MCIERR_BASE + 46
MCIERR_ILLEGAL_FOR_AUTO_OPEN :: MCIERR_BASE + 47
MCIERR_FILENAME_REQUIRED :: MCIERR_BASE + 48
MCIERR_EXTRA_CHARACTERS :: MCIERR_BASE + 49
MCIERR_DEVICE_NOT_INSTALLED :: MCIERR_BASE + 50
MCIERR_GET_CD :: MCIERR_BASE + 51
MCIERR_SET_CD :: MCIERR_BASE + 52
MCIERR_SET_DRIVE :: MCIERR_BASE + 53
MCIERR_DEVICE_LENGTH :: MCIERR_BASE + 54
MCIERR_DEVICE_ORD_LENGTH :: MCIERR_BASE + 55
MCIERR_NO_INTEGER :: MCIERR_BASE + 56
MCIERR_WAVE_OUTPUTSINUSE :: MCIERR_BASE + 64
MCIERR_WAVE_SETOUTPUTINUSE :: MCIERR_BASE + 65
MCIERR_WAVE_INPUTSINUSE :: MCIERR_BASE + 66
MCIERR_WAVE_SETINPUTINUSE :: MCIERR_BASE + 67
MCIERR_WAVE_OUTPUTUNSPECIFIED :: MCIERR_BASE + 68
MCIERR_WAVE_INPUTUNSPECIFIED :: MCIERR_BASE + 69
MCIERR_WAVE_OUTPUTSUNSUITABLE :: MCIERR_BASE + 70
MCIERR_WAVE_SETOUTPUTUNSUITABLE :: MCIERR_BASE + 71
MCIERR_WAVE_INPUTSUNSUITABLE :: MCIERR_BASE + 72
MCIERR_WAVE_SETINPUTUNSUITABLE :: MCIERR_BASE + 73
MCIERR_SEQ_DIV_INCOMPATIBLE :: MCIERR_BASE + 80
MCIERR_SEQ_PORT_INUSE :: MCIERR_BASE + 81
MCIERR_SEQ_PORT_NONEXISTENT :: MCIERR_BASE + 82
MCIERR_SEQ_PORT_MAPNODEVICE :: MCIERR_BASE + 83
MCIERR_SEQ_PORT_MISCERROR :: MCIERR_BASE + 84
MCIERR_SEQ_TIMER :: MCIERR_BASE + 85
MCIERR_SEQ_PORTUNSPECIFIED :: MCIERR_BASE + 86
MCIERR_SEQ_NOMIDIPRESENT :: MCIERR_BASE + 87
MCIERR_NO_WINDOW :: MCIERR_BASE + 90
MCIERR_CREATEWINDOW :: MCIERR_BASE + 91
MCIERR_FILE_READ :: MCIERR_BASE + 92
MCIERR_FILE_WRITE :: MCIERR_BASE + 93
MCIERR_NO_IDENTITY :: MCIERR_BASE + 94
/* MMRESULT error return values specific to the mixer API */
MIXERR_INVALLINE :: (MIXERR_BASE + 0)
MIXERR_INVALCONTROL :: (MIXERR_BASE + 1)
MIXERR_INVALVALUE :: (MIXERR_BASE + 2)
MIXERR_LASTERROR :: (MIXERR_BASE + 2)
+7 -7
View File
@@ -54,7 +54,7 @@ foreign ws2_32 {
buf: rawptr,
len: c_int,
flags: c_int,
addr: ^SOCKADDR,
addr: ^SOCKADDR_STORAGE_LH,
addrlen: ^c_int,
) -> c_int ---
sendto :: proc(
@@ -62,11 +62,11 @@ foreign ws2_32 {
buf: rawptr,
len: c_int,
flags: c_int,
addr: ^SOCKADDR,
addr: ^SOCKADDR_STORAGE_LH,
addrlen: c_int,
) -> c_int ---
shutdown :: proc(socket: SOCKET, how: c_int) -> c_int ---
accept :: proc(socket: SOCKET, address: ^SOCKADDR, address_len: ^c_int) -> SOCKET ---
accept :: proc(socket: SOCKET, address: ^SOCKADDR_STORAGE_LH, address_len: ^c_int) -> SOCKET ---
setsockopt :: proc(
s: SOCKET,
@@ -75,11 +75,11 @@ foreign ws2_32 {
optval: rawptr,
optlen: c_int,
) -> c_int ---
getsockname :: proc(socket: SOCKET, address: ^SOCKADDR, address_len: ^c_int) -> c_int ---
getpeername :: proc(socket: SOCKET, address: ^SOCKADDR, address_len: ^c_int) -> c_int ---
bind :: proc(socket: SOCKET, address: ^SOCKADDR, address_len: socklen_t) -> c_int ---
getsockname :: proc(socket: SOCKET, address: ^SOCKADDR_STORAGE_LH, address_len: ^c_int) -> c_int ---
getpeername :: proc(socket: SOCKET, address: ^SOCKADDR_STORAGE_LH, address_len: ^c_int) -> c_int ---
bind :: proc(socket: SOCKET, address: ^SOCKADDR_STORAGE_LH, address_len: socklen_t) -> c_int ---
listen :: proc(socket: SOCKET, backlog: c_int) -> c_int ---
connect :: proc(socket: SOCKET, address: ^SOCKADDR, len: c_int) -> c_int ---
connect :: proc(socket: SOCKET, address: ^SOCKADDR_STORAGE_LH, len: c_int) -> c_int ---
getaddrinfo :: proc(
node: cstring,
service: cstring,
+100
View File
@@ -0,0 +1,100 @@
/*
package table implements ascii/markdown/html/custom rendering of tables.
---
Custom rendering example:
```odin
tbl := init(&Table{})
padding(tbl, 0, 1)
row(tbl, "A_LONG_ENUM", "= 54,", "// A comment about A_LONG_ENUM")
row(tbl, "AN_EVEN_LONGER_ENUM", "= 1,", "// A comment about AN_EVEN_LONGER_ENUM")
build(tbl)
for row in 0..<tbl.nr_rows {
for col in 0..<tbl.nr_cols {
write_table_cell(stdio_writer(), tbl, row, col)
}
io.write_byte(stdio_writer(), '\n')
}
```
This outputs:
```
A_LONG_ENUM = 54, // A comment about A_LONG_ENUM
AN_EVEN_LONGER_ENUM = 1, // A comment about AN_EVEN_LONGER_ENUM
```
---
ASCII rendering example:
```odin
tbl := init(&Table{})
defer destroy(tbl)
caption(tbl, "This is a table caption and it is very long")
padding(tbl, 1, 1) // Left/right padding of cells
header(tbl, "AAAAAAAAA", "B")
header(tbl, "C") // Appends to previous header row. Same as if done header("AAAAAAAAA", "B", "C") from start.
// Create a row with two values. Since there are three columns the third
// value will become the empty string.
//
// NOTE: header() is not allowed anymore after this.
row(tbl, 123, "foo")
// Use `format()` if you need custom formatting. This will allocate into
// the arena specified at init.
row(tbl,
format(tbl, "%09d", 5),
format(tbl, "%.6f", 6.28318530717958647692528676655900576))
// A row with zero values is allowed as long as a previous row or header
// exist. The value and alignment of each cell can then be set
// individually.
row(tbl)
set_cell_value_and_alignment(tbl, last_row(tbl), 0, "a", .Center)
set_cell_value(tbl, last_row(tbl), 1, "bbb")
set_cell_value(tbl, last_row(tbl), 2, "c")
// Headers are regular cells, too. Use header_row() as row index to modify
// header cells.
set_cell_alignment(tbl, header_row(tbl), 1, .Center) // Sets alignment of 'B' column to Center.
set_cell_alignment(tbl, header_row(tbl), 2, .Right) // Sets alignment of 'C' column to Right.
build(tbl)
write_ascii_table(stdio_writer(), tbl)
write_markdown_table(stdio_writer(), tbl)
```
This outputs:
```
+-----------------------------------------------+
| This is a table caption and it is very long |
+------------------+-----------------+----------+
| AAAAAAAAA | B | C |
+------------------+-----------------+----------+
| 123 | foo | |
| 000000005 | 6.283185 | |
| a | bbb | c |
+------------------+-----------------+----------+
```
and
```
| AAAAAAAAA | B | C |
|:-----------------|:---------------:|---------:|
| 123 | foo | |
| 000000005 | 6.283185 | |
| a | bbb | c |
```
respectively.
*/
package text_table
+384
View File
@@ -0,0 +1,384 @@
/*
Copyright 2023 oskarnp <oskarnp@proton.me>
Made available under Odin's BSD-3 license.
List of contributors:
oskarnp: Initial implementation.
*/
package text_table
import "core:io"
import "core:os"
import "core:fmt"
import "core:mem"
import "core:mem/virtual"
import "core:runtime"
import "core:strings"
Cell :: struct {
text: string,
alignment: Cell_Alignment,
}
Cell_Alignment :: enum {
Left,
Center,
Right,
}
Table :: struct {
lpad, rpad: int, // Cell padding (left/right)
cells: [dynamic]Cell,
caption: string,
nr_rows, nr_cols: int,
has_header_row: bool,
table_allocator: runtime.Allocator, // Used for allocating cells/colw
format_allocator: runtime.Allocator, // Used for allocating Cell.text when applicable
dirty: bool, // True if build() needs to be called before rendering
// The following are computed on build()
colw: [dynamic]int, // Width of each column (including padding, excluding borders)
tblw: int, // Width of entire table (including padding, excluding borders)
}
init :: proc{init_with_allocator, init_with_virtual_arena, init_with_mem_arena}
init_with_allocator :: proc(tbl: ^Table, format_allocator := context.temp_allocator, table_allocator := context.allocator) -> ^Table {
tbl.table_allocator = table_allocator
tbl.cells = make([dynamic]Cell, tbl.table_allocator)
tbl.colw = make([dynamic]int, tbl.table_allocator)
tbl.format_allocator = format_allocator
return tbl
}
init_with_virtual_arena :: proc(tbl: ^Table, format_arena: ^virtual.Arena, table_allocator := context.allocator) -> ^Table {
return init_with_allocator(tbl, virtual.arena_allocator(format_arena), table_allocator)
}
init_with_mem_arena :: proc(tbl: ^Table, format_arena: ^mem.Arena, table_allocator := context.allocator) -> ^Table {
return init_with_allocator(tbl, mem.arena_allocator(format_arena), table_allocator)
}
destroy :: proc(tbl: ^Table) {
free_all(tbl.format_allocator)
delete(tbl.cells)
delete(tbl.colw)
}
caption :: proc(tbl: ^Table, value: string) {
tbl.caption = value
tbl.dirty = true
}
padding :: proc(tbl: ^Table, lpad, rpad: int) {
tbl.lpad = lpad
tbl.rpad = rpad
tbl.dirty = true
}
get_cell :: proc(tbl: ^Table, row, col: int, loc := #caller_location) -> ^Cell {
assert(col >= 0 && col < tbl.nr_cols, "cell column out of range", loc)
assert(row >= 0 && row < tbl.nr_rows, "cell row out of range", loc)
resize(&tbl.cells, tbl.nr_cols * tbl.nr_rows)
return &tbl.cells[row*tbl.nr_cols + col]
}
set_cell_value_and_alignment :: proc(tbl: ^Table, row, col: int, value: string, alignment: Cell_Alignment) {
cell := get_cell(tbl, row, col)
cell.text = format(tbl, "%v", value)
cell.alignment = alignment
tbl.dirty = true
}
set_cell_value :: proc(tbl: ^Table, row, col: int, value: any, loc := #caller_location) {
cell := get_cell(tbl, row, col, loc)
switch val in value {
case nil:
cell.text = ""
case string:
cell.text = string(val)
case cstring:
cell.text = string(val)
case:
cell.text = format(tbl, "%v", val)
if cell.text == "" {
fmt.eprintf("{} text/table: format() resulted in empty string (arena out of memory?)\n", loc)
}
}
tbl.dirty = true
}
set_cell_alignment :: proc(tbl: ^Table, row, col: int, alignment: Cell_Alignment, loc := #caller_location) {
cell := get_cell(tbl, row, col, loc)
cell.alignment = alignment
tbl.dirty = true
}
format :: proc(tbl: ^Table, _fmt: string, args: ..any, loc := #caller_location) -> string {
context.allocator = tbl.format_allocator
return fmt.aprintf(fmt = _fmt, args = args)
}
header :: proc(tbl: ^Table, values: ..any, loc := #caller_location) {
if (tbl.has_header_row && tbl.nr_rows != 1) || (!tbl.has_header_row && tbl.nr_rows != 0) {
panic("Cannot add headers after rows have been added", loc)
}
if tbl.nr_rows == 0 {
tbl.nr_rows += 1
tbl.has_header_row = true
}
col := tbl.nr_cols
tbl.nr_cols += len(values)
for val in values {
set_cell_value(tbl, header_row(tbl), col, val, loc)
col += 1
}
tbl.dirty = true
}
row :: proc(tbl: ^Table, values: ..any, loc := #caller_location) {
if tbl.nr_cols == 0 {
if len(values) == 0 {
panic("Cannot create row without values unless knowing amount of columns in advance")
} else {
tbl.nr_cols = len(values)
}
}
tbl.nr_rows += 1
for col in 0..<tbl.nr_cols {
val := values[col] if col < len(values) else nil
set_cell_value(tbl, last_row(tbl), col, val)
}
tbl.dirty = true
}
last_row :: proc(tbl: ^Table) -> int {
return tbl.nr_rows - 1
}
header_row :: proc(tbl: ^Table) -> int {
return 0 if tbl.has_header_row else -1
}
first_row :: proc(tbl: ^Table) -> int {
return header_row(tbl)+1 if tbl.has_header_row else 0
}
build :: proc(tbl: ^Table) {
tbl.dirty = false
resize(&tbl.colw, tbl.nr_cols)
mem.zero_slice(tbl.colw[:])
for row in 0..<tbl.nr_rows {
for col in 0..<tbl.nr_cols {
cell := get_cell(tbl, row, col)
if w := len(cell.text) + tbl.lpad + tbl.rpad; w > tbl.colw[col] {
tbl.colw[col] = w
}
}
}
colw_sum := 0
for v in tbl.colw {
colw_sum += v
}
tbl.tblw = max(colw_sum, len(tbl.caption) + tbl.lpad + tbl.rpad)
// Resize columns to match total width of table
remain := tbl.tblw-colw_sum
for col := 0; remain > 0; col = (col + 1) % tbl.nr_cols {
tbl.colw[col] += 1
remain -= 1
}
return
}
write_html_table :: proc(w: io.Writer, tbl: ^Table) {
if tbl.dirty {
build(tbl)
}
io.write_string(w, "<table>\n")
if tbl.caption != "" {
io.write_string(w, "<caption>")
io.write_string(w, tbl.caption)
io.write_string(w, "</caption>\n")
}
align_attribute :: proc(cell: ^Cell) -> string {
switch cell.alignment {
case .Left: return ` align="left"`
case .Center: return ` align="center"`
case .Right: return ` align="right"`
}
unreachable()
}
if tbl.has_header_row {
io.write_string(w, "<thead>\n")
io.write_string(w, " <tr>\n")
for col in 0..<tbl.nr_cols {
cell := get_cell(tbl, header_row(tbl), col)
io.write_string(w, " <th")
io.write_string(w, align_attribute(cell))
io.write_string(w, ">")
io.write_string(w, cell.text)
io.write_string(w, "</th>\n")
}
io.write_string(w, " </tr>\n")
io.write_string(w, "</thead>\n")
}
io.write_string(w, "<tbody>\n")
for row in 0..<tbl.nr_rows {
if tbl.has_header_row && row == header_row(tbl) {
continue
}
io.write_string(w, " <tr>\n")
for col in 0..<tbl.nr_cols {
cell := get_cell(tbl, row, col)
io.write_string(w, " <td")
io.write_string(w, align_attribute(cell))
io.write_string(w, ">")
io.write_string(w, cell.text)
io.write_string(w, "</td>\n")
}
io.write_string(w, " </tr>\n")
}
io.write_string(w, " </tbody>\n")
io.write_string(w, "</table>\n")
}
write_ascii_table :: proc(w: io.Writer, tbl: ^Table) {
if tbl.dirty {
build(tbl)
}
write_caption_separator :: proc(w: io.Writer, tbl: ^Table) {
io.write_byte(w, '+')
write_byte_repeat(w, tbl.tblw + tbl.nr_cols - 1, '-')
io.write_byte(w, '+')
io.write_byte(w, '\n')
}
write_table_separator :: proc(w: io.Writer, tbl: ^Table) {
for col in 0..<tbl.nr_cols {
if col == 0 {
io.write_byte(w, '+')
}
write_byte_repeat(w, tbl.colw[col], '-')
io.write_byte(w, '+')
}
io.write_byte(w, '\n')
}
if tbl.caption != "" {
write_caption_separator(w, tbl)
io.write_byte(w, '|')
write_text_align(w, tbl.tblw - tbl.lpad - tbl.rpad + tbl.nr_cols - 1,
tbl.lpad, tbl.rpad, tbl.caption, .Center)
io.write_byte(w, '|')
io.write_byte(w, '\n')
}
write_table_separator(w, tbl)
for row in 0..<tbl.nr_rows {
for col in 0..<tbl.nr_cols {
if col == 0 {
io.write_byte(w, '|')
}
write_table_cell(w, tbl, row, col)
io.write_byte(w, '|')
}
io.write_byte(w, '\n')
if tbl.has_header_row && row == header_row(tbl) {
write_table_separator(w, tbl)
}
}
write_table_separator(w, tbl)
}
// Renders table according to GitHub Flavored Markdown (GFM) specification
write_markdown_table :: proc(w: io.Writer, tbl: ^Table) {
// NOTE(oskar): Captions or colspans are not supported by GFM as far as I can tell.
if tbl.dirty {
build(tbl)
}
for row in 0..<tbl.nr_rows {
for col in 0..<tbl.nr_cols {
cell := get_cell(tbl, row, col)
if col == 0 {
io.write_byte(w, '|')
}
write_text_align(w, tbl.colw[col] - tbl.lpad - tbl.rpad, tbl.lpad, tbl.rpad, cell.text,
.Center if tbl.has_header_row && row == header_row(tbl) else .Left)
io.write_string(w, "|")
}
io.write_byte(w, '\n')
if tbl.has_header_row && row == header_row(tbl) {
for col in 0..<tbl.nr_cols {
cell := get_cell(tbl, row, col)
if col == 0 {
io.write_byte(w, '|')
}
switch cell.alignment {
case .Left:
io.write_byte(w, ':')
write_byte_repeat(w, max(1, tbl.colw[col]-1), '-')
case .Center:
io.write_byte(w, ':')
write_byte_repeat(w, max(1, tbl.colw[col]-2), '-')
io.write_byte(w, ':')
case .Right:
write_byte_repeat(w, max(1, tbl.colw[col]-1), '-')
io.write_byte(w, ':')
}
io.write_byte(w, '|')
}
io.write_byte(w, '\n')
}
}
}
write_byte_repeat :: proc(w: io.Writer, n: int, b: byte) {
for _ in 0..<n {
io.write_byte(w, b)
}
}
write_table_cell :: proc(w: io.Writer, tbl: ^Table, row, col: int) {
if tbl.dirty {
build(tbl)
}
cell := get_cell(tbl, row, col)
write_text_align(w, tbl.colw[col]-tbl.lpad-tbl.rpad, tbl.lpad, tbl.rpad, cell.text, cell.alignment)
}
write_text_align :: proc(w: io.Writer, colw, lpad, rpad: int, text: string, alignment: Cell_Alignment) {
write_byte_repeat(w, lpad, ' ')
switch alignment {
case .Left:
io.write_string(w, text)
write_byte_repeat(w, colw - len(text), ' ')
case .Center:
pad := colw - len(text)
odd := pad & 1 != 0
write_byte_repeat(w, pad/2, ' ')
io.write_string(w, text)
write_byte_repeat(w, pad/2 + 1 if odd else pad/2, ' ')
case .Right:
write_byte_repeat(w, colw - len(text), ' ')
io.write_string(w, text)
}
write_byte_repeat(w, rpad, ' ')
}
+13
View File
@@ -0,0 +1,13 @@
package text_table
import "core:io"
import "core:os"
import "core:strings"
stdio_writer :: proc() -> io.Writer {
return io.to_writer(os.stream_from_handle(os.stdout))
}
strings_builder_writer :: proc(b: ^strings.Builder) -> io.Writer {
return strings.to_writer(b)
}
+55
View File
@@ -0,0 +1,55 @@
//+build js
package thread
import "core:intrinsics"
import "core:sync"
import "core:mem"
Thread_State :: enum u8 {
Started,
Joined,
Done,
}
Thread_Os_Specific :: struct {
flags: bit_set[Thread_State; u8],
}
_thread_priority_map := [Thread_Priority]i32{
.Normal = 0,
.Low = -2,
.High = +2,
}
_create :: proc(procedure: Thread_Proc, priority := Thread_Priority.Normal) -> ^Thread {
unimplemented("core:thread procedure not supported on js target")
}
_start :: proc(t: ^Thread) {
unimplemented("core:thread procedure not supported on js target")
}
_is_done :: proc(t: ^Thread) -> bool {
unimplemented("core:thread procedure not supported on js target")
}
_join :: proc(t: ^Thread) {
unimplemented("core:thread procedure not supported on js target")
}
_join_multiple :: proc(threads: ..^Thread) {
unimplemented("core:thread procedure not supported on js target")
}
_destroy :: proc(thread: ^Thread) {
unimplemented("core:thread procedure not supported on js target")
}
_terminate :: proc(using thread : ^Thread, exit_code: int) {
unimplemented("core:thread procedure not supported on js target")
}
_yield :: proc() {
unimplemented("core:thread procedure not supported on js target")
}
+6
View File
@@ -56,6 +56,12 @@ when ODIN_ARCH == .amd64 {
}
}
when ODIN_OS != .Darwin && ODIN_OS != .Linux && ODIN_OS != .FreeBSD {
_get_tsc_frequency :: proc "contextless" () -> (u64, bool) {
return 0, false
}
}
has_invariant_tsc :: proc "contextless" () -> bool {
when ODIN_ARCH == .amd64 {
return x86_has_invariant_tsc()
-7
View File
@@ -1,7 +0,0 @@
//+private
//+build openbsd
package time
_get_tsc_frequency :: proc "contextless" () -> (u64, bool) {
return 0, false
}
-7
View File
@@ -1,7 +0,0 @@
//+private
//+build windows
package time
_get_tsc_frequency :: proc "contextless" () -> (u64, bool) {
return 0, false
}
+3 -1
View File
@@ -49,6 +49,7 @@ import whirlpool "core:crypto/whirlpool"
import x25519 "core:crypto/x25519"
import dynlib "core:dynlib"
import net "core:net"
import base32 "core:encoding/base32"
import base64 "core:encoding/base64"
@@ -161,6 +162,7 @@ _ :: crypto_util
_ :: whirlpool
_ :: x25519
_ :: dynlib
_ :: net
_ :: base32
_ :: base64
_ :: csv
@@ -214,4 +216,4 @@ _ :: sysinfo
_ :: unicode
_ :: utf8
_ :: utf8string
_ :: utf16
_ :: utf16
-337
View File
@@ -1,337 +0,0 @@
import "core:fmt.odin";
import "core:os.odin";
import "core:mem.odin";
// import "http_test.odin" as ht;
// import "game.odin" as game;
// import "punity.odin" as pn;
main :: proc() {
struct_padding();
bounds_checking();
type_introspection();
any_type();
crazy_introspection();
namespaces_and_files();
miscellany();
/*
ht.run();
game.run();
{
init :: proc(c: ^pn.Core) {}
step :: proc(c: ^pn.Core) {}
pn.run(init, step);
}
*/
}
struct_padding :: proc() {
{
A :: struct {
a: u8,
b: u32,
c: u16,
}
B :: struct {
a: [7]u8,
b: [3]u16,
c: u8,
d: u16,
}
fmt.println("size_of(A):", size_of(A));
fmt.println("size_of(B):", size_of(B));
// n.b. http://cbloomrants.blogspot.co.uk/2012/07/07-23-12-structs-are-not-what-you-want.html
}
{
A :: struct #ordered {
a: u8,
b: u32,
c: u16,
}
B :: struct #ordered {
a: [7]u8,
b: [3]u16,
c: u8,
d: u16,
}
fmt.println("size_of(A):", size_of(A));
fmt.println("size_of(B):", size_of(B));
// C-style structure layout
}
{
A :: struct #packed {
a: u8,
b: u32,
c: u16,
}
B :: struct #packed {
a: [7]u8,
b: [3]u16,
c: u8,
d: u16,
}
fmt.println("size_of(A):", size_of(A));
fmt.println("size_of(B):", size_of(B));
// Useful for explicit layout
}
// Member sorting by priority
// Alignment desc.
// Size desc.
// source order asc.
/*
A :: struct {
a: u8
b: u32
c: u16
}
B :: struct {
a: [7]u8
b: [3]u16
c: u8
d: u16
}
Equivalent too
A :: struct #ordered {
b: u32
c: u16
a: u8
}
B :: struct #ordered {
b: [3]u16
d: u16
a: [7]u8
c: u8
}
*/
}
bounds_checking :: proc() {
x: [4]int;
// x[-1] = 0; // Compile Time
// x[4] = 0; // Compile Time
{
a, b := -1, 4;
// x[a] = 0; // Runtime Time
// x[b] = 0; // Runtime Time
}
// Works for arrays, strings, slices, and related procedures & operations
{
base: [10]int;
s := base[2..6];
a, b := -1, 6;
#no_bounds_check {
s[a] = 0;
// #bounds_check s[b] = 0;
}
#no_bounds_check
if s[a] == 0 {
// Do whatever
}
// Bounds checking can be toggled explicit
// on a per statement basis.
// _any statement_
}
}
type_introspection :: proc() {
{
info: ^Type_Info;
x: int;
info = type_info_of(int); // by type
info = type_info_of(x); // by value
// See: runtime.odin
match i in info.variant {
case Type_Info_Integer:
fmt.println("integer!");
case Type_Info_Float:
fmt.println("float!");
case:
fmt.println("potato!");
}
// Unsafe cast
integer_info := cast(^Type_Info_Integer)cast(rawptr)info;
}
{
Vector2 :: struct { x, y: f32 }
Vector3 :: struct { x, y, z: f32 }
v1: Vector2;
v2: Vector3;
v3: Vector3;
t1 := type_info_of(v1);
t2 := type_info_of(v2);
t3 := type_info_of(v3);
fmt.println();
fmt.print("Type of v1 is:\n\t", t1);
fmt.println();
fmt.print("Type of v2 is:\n\t", t2);
fmt.println("\n");
fmt.println("t1 == t2:", t1 == t2);
fmt.println("t2 == t3:", t2 == t3);
}
}
any_type :: proc() {
a: any;
x: int = 123;
y: f64 = 6.28;
z: string = "Yo-Yo Ma";
// All types can be implicit cast to `any`
a = x;
a = y;
a = z;
a = a; // This the "identity" type, it doesn't get converted
a = 123; // Literals are copied onto the stack first
// any has two members
// data - rawptr to the data
// type_info - pointer to the type info
fmt.println(x, y, z);
// See: fmt.odin
// For variadic any procedures in action
}
crazy_introspection :: proc() {
{
Fruit :: enum {
APPLE,
BANANA,
GRAPE,
MELON,
PEACH,
TOMATO,
}
s: string;
// s = enum_to_string(Fruit.PEACH);
fmt.println(s);
f := Fruit.GRAPE;
// s = enum_to_string(f);
fmt.println(s);
fmt.println(f);
// See: runtime.odin
}
{
// NOTE(bill): This is not safe code and I would not recommend this at all
// I'd recommend you use `match type` to get the subtype rather than
// casting pointers
Fruit :: enum {
APPLE,
BANANA,
GRAPE,
MELON,
PEACH,
TOMATO,
}
fruit_ti := type_info_of(Fruit);
name := fruit_ti.variant.(Type_Info_Named).name;
info, _ := type_info_base(fruit_ti).variant.(Type_Info_Enum);
fmt.printf("%s :: enum %T {\n", name, info.base);
for _, i in info.values {
fmt.printf("\t%s\t= %v,\n", info.names[i], info.values[i]);
}
fmt.printf("}\n");
// NOTE(bill): look at that type-safe printf!
}
{
Vector3 :: struct {x, y, z: f32}
a := Vector3{x = 1, y = 4, z = 9};
fmt.println(a);
b := Vector3{x = 9, y = 3, z = 1};
fmt.println(b);
// NOTE(bill): See fmt.odin
}
// n.b. This pretty much "solves" serialization (to strings)
}
// #import "test.odin"
namespaces_and_files :: proc() {
// test.thing()
// test.format.println()
// test.println()
/*
// Non-exporting import
#import "file.odin"
#import "file.odin" as file
#import "file.odin" as .
#import "file.odin" as _
// Exporting import
#include "file.odin"
*/
// Talk about scope rules and diagram
}
miscellany :: proc() {
/*
win32 `__imp__` prefix
#dll_import
#dll_export
Change exported name/symbol for linking
#link_name
Custom calling conventions
#stdcall
#fastcall
Runtime stuff
#shared_global_scope
*/
// assert(false)
// #assert(false)
// panic("Panic message goes here")
}
-879
View File
@@ -1,879 +0,0 @@
// Demo 002
export "core:fmt.odin";
export "core:math.odin";
export "core:mem.odin";
// export "game.odin"
#thread_local tls_int: int;
main :: proc() {
// Forenotes
// Semicolons are now optional
// Rule for when a semicolon is expected after a statement
// - If the next token is not on the same line
// - if the next token is a closing brace }
// - Otherwise, a semicolon is needed
//
// Expections:
// for, if, match
// if x := thing(); x < 123 {}
// for i := 0; i < 123; i++ {}
// Q: Should I use the new rule or go back to the old one without optional semicolons?
// #thread_local - see runtime.odin and above at `tls_int`
// #foreign_system_library - see win32.odin
// struct_compound_literals();
// enumerations();
// variadic_procedures();
// new_builtins();
// match_statement();
// namespacing();
// subtyping();
// tagged_unions();
}
struct_compound_literals :: proc() {
Thing :: struct {
id: int,
x: f32,
name: string,
};
{
t1: Thing;
t1.id = 1;
t3 := Thing{};
t4 := Thing{1, 2, "Fred"};
// t5 := Thing{1, 2};
t6 := Thing{
name = "Tom",
x = 23,
};
}
}
enumerations :: proc() {
{
Fruit :: enum {
APPLE, // 0
BANANA, // 1
PEAR, // 2
};
f := Fruit.APPLE;
// g12: int = Fruit.BANANA
g: int = cast(int)Fruit.BANANA;
// However, you can use enums are index values as _any_ integer allowed
}
{
Fruit1 :: enum int {
APPLE,
BANANA,
PEAR,
}
Fruit2 :: enum u8 {
APPLE,
BANANA,
PEAR,
}
Fruit3 :: enum u8 {
APPLE = 1,
BANANA, // 2
PEAR = 5,
TOMATO, // 6
}
}
// Q: remove the need for `type` if it's a record (struct/enum/raw_union/union)?
}
variadic_procedures :: proc() {
print_ints :: proc(args: ..int) {
for arg, i in args {
if i > 0 do print(", ");
print(arg);
}
}
print_ints(); // nl()
print_ints(1); nl();
print_ints(1, 2, 3); nl();
print_prefix_f32s :: proc(prefix: string, args: ..f32) {
print(prefix);
print(": ");
for arg, i in args {
if i > 0 do print(", ");
print(arg);
}
}
print_prefix_f32s("a"); nl();
print_prefix_f32s("b", 1); nl();
print_prefix_f32s("c", 1, 2, 3); nl();
// Internally, the variadic procedures get allocated to an array on the stack,
// and this array is passed a slice
// This is first step for a `print` procedure but I do not have an `any` type
// yet as this requires a few other things first - i.e. introspection
// NOTE(bill): I haven't yet added the feature of expanding a slice or array into
// a variadic a parameter but it's pretty trivial to add
}
new_builtins :: proc() {
{
a := new(int);
b := make([]int, 12);
c := make([]int, 12, 16);
defer free(a);
defer free(b);
defer free(c);
// NOTE(bill): These use the current context's allocator not the default allocator
// see runtime.odin
// Q: Should this be `free` rather than `free` and should I overload it for slices too?
push_allocator default_allocator() {
a := new(int);
defer free(a);
// Do whatever
}
}
{
a: int = 123;
b: type_of(a) = 321;
// NOTE(bill): This matches the current naming scheme
// size_of
// align_of
// offset_of
//
// size_of_val
// align_of_val
// offset_of_val
// type_of_val
}
{
// Compile time assert
COND :: true;
#assert(COND);
// #assert(!COND)
// Runtime assert
x := true;
assert(x);
// assert(!x);
}
{
x: ^u32 = nil;
y := x+100;
z := y-x;
w := slice_ptr(x, 12);
t := slice_ptr(x, 12, 16);
// NOTE(bill): These are here because I've removed:
// pointer arithmetic
// pointer indexing
// pointer slicing
// Reason
a: [16]int;
a[1] = 1;
b := &a;
// Auto pointer deref
// consistent with record members
assert(b[1] == 1);
// Q: Should I add them back in at the cost of inconsitency?
}
{
a, b := -1, 2;
print(min(a, b)); nl();
print(max(a, b)); nl();
print(abs(a)); nl();
// These work at compile time too
A :: -1;
B :: 2;
C :: min(A, B);
D :: max(A, B);
E :: abs(A);
print(C); nl();
print(D); nl();
print(E); nl();
}
}
match_statement :: proc() {
// NOTE(bill): `match` statements are similar to `switch` statements
// in other languages but there are few differences
{
match x := 5; x {
case 1: // cases must be constant expression
print("1!\n");
// break by default
case 2:
s := "2!\n"; // Each case has its own scope
print(s);
break; // explicit break
case 3, 4: // multiple cases
print("3 or 4!\n");
case 5:
print("5!\n");
fallthrough; // explicit fallthrough
case:
print("default!\n");
}
match x := 1.5; x {
case 1.5:
print("1.5!\n");
// break by default
case TAU:
print("τ!\n");
case:
print("default!\n");
}
match x := "Hello"; x {
case "Hello":
print("greeting\n");
// break by default
case "Goodbye":
print("farewell\n");
case:
print("???\n");
}
a := 53;
match {
case a == 1:
print("one\n");
case a == 2:
print("a couple\n");
case a < 7, a == 7:
print("a few\n");
case a < 12: // intentional bug
print("several\n");
case a >= 12 && a < 100:
print("dozens\n");
case a >= 100 && a < 1000:
print("hundreds\n");
case:
print("a fuck ton\n");
}
// Identical to this
b := 53;
if b == 1 {
print("one\n");
} else if b == 2 {
print("a couple\n");
} else if b < 7 || b == 7 {
print("a few\n");
} else if b < 12 { // intentional bug
print("several\n");
} else if b >= 12 && b < 100 {
print("dozens\n");
} else if b >= 100 && b < 1000 {
print("hundreds\n");
} else {
print("a fuck ton\n");
}
// However, match statements allow for `break` and `fallthrough` unlike
// an if statement
}
}
Vector3 :: struct {x, y, z: f32}
print_floats :: proc(args: ..f32) {
for arg, i in args {
if i > 0 do print(", ");
print(arg);
}
println();
}
namespacing :: proc() {
{
Thing :: #type struct {
x: f32,
name: string,
};
a: Thing;
a.x = 3;
{
Thing :: #type struct {
y: int,
test: bool,
};
b: Thing; // Uses this scope's Thing
b.test = true;
}
}
/*
{
Entity :: struct {
Guid :: int
Nested :: struct {
MyInt :: int
i: int
}
CONSTANT :: 123
guid: Guid
name: string
pos: Vector3
vel: Vector3
nested: Nested
}
guid: Entity.Guid = Entity.CONSTANT
i: Entity.Nested.MyInt
{
using Entity
guid: Guid = CONSTANT
using Nested
i: MyInt
}
{
using Entity.Nested
guid: Entity.Guid = Entity.CONSTANT
i: MyInt
}
{
e: Entity
using e
guid = 27832
name = "Bob"
print(e.guid as int); nl()
print(e.name); nl()
}
{
using e: Entity
guid = 78456
name = "Thing"
print(e.guid as int); nl()
print(e.name); nl()
}
}
{
Entity :: struct {
Guid :: int
Nested :: struct {
MyInt :: int
i: int
}
CONSTANT :: 123
guid: Guid
name: string
using pos: Vector3
vel: Vector3
using nested: ^Nested
}
e := Entity{nested = new(Entity.Nested)}
e.x = 123
e.i = Entity.CONSTANT
}
*/
{
Entity :: struct {
position: Vector3
}
print_pos_1 :: proc(entity: ^Entity) {
print("print_pos_1: ");
print_floats(entity.position.x, entity.position.y, entity.position.z);
}
print_pos_2 :: proc(entity: ^Entity) {
using entity;
print("print_pos_2: ");
print_floats(position.x, position.y, position.z);
}
print_pos_3 :: proc(using entity: ^Entity) {
print("print_pos_3: ");
print_floats(position.x, position.y, position.z);
}
print_pos_4 :: proc(using entity: ^Entity) {
using position;
print("print_pos_4: ");
print_floats(x, y, z);
}
e := Entity{position = Vector3{1, 2, 3}};
print_pos_1(&e);
print_pos_2(&e);
print_pos_3(&e);
print_pos_4(&e);
// This is similar to C++'s `this` pointer that is implicit and only available in methods
}
}
subtyping :: proc() {
{
// C way for subtyping/subclassing
Entity :: struct {
position: Vector3,
}
Frog :: struct {
entity: Entity,
jump_height: f32,
}
f: Frog;
f.entity.position = Vector3{1, 2, 3};
using f.entity;
position = Vector3{1, 2, 3};
}
{
// C++ way for subtyping/subclassing
Entity :: struct {
position: Vector3
}
Frog :: struct {
using entity: Entity,
jump_height: f32,
}
f: Frog;
f.position = Vector3{1, 2, 3};
print_pos :: proc(using entity: Entity) {
print("print_pos: ");
print_floats(position.x, position.y, position.z);
}
print_pos(f.entity);
// print_pos(f);
// Subtype Polymorphism
}
{
// More than C++ way for subtyping/subclassing
Entity :: struct {
position: Vector3,
}
Frog :: struct {
jump_height: f32,
using entity: ^Entity, // Doesn't have to be first member!
}
f: Frog;
f.entity = new(Entity);
f.position = Vector3{1, 2, 3};
print_pos :: proc(using entity: ^Entity) {
print("print_pos: ");
print_floats(position.x, position.y, position.z);
}
print_pos(f.entity);
// print_pos(^f);
// print_pos(f);
}
{
// More efficient subtyping
Entity :: struct {
position: Vector3,
}
Frog :: struct {
jump_height: f32,
using entity: ^Entity,
}
MAX_ENTITES :: 64;
entities: [MAX_ENTITES]Entity;
entity_count := 0;
next_entity :: proc(entities: []Entity, entity_count: ^int) -> ^Entity {
e := &entities[entity_count^];
entity_count^ += 1;
return e;
}
f: Frog;
f.entity = next_entity(entities[..], &entity_count);
f.position = Vector3{3, 4, 6};
using f.position;
print_floats(x, y, z);
}
/*{
// Down casting
Entity :: struct {
position: Vector3,
}
Frog :: struct {
jump_height: f32,
using entity: Entity,
}
f: Frog;
f.jump_height = 564;
e := ^f.entity;
frog := down_cast(^Frog)e;
print("down_cast: ");
print(frog.jump_height); nl();
// NOTE(bill): `down_cast` is unsafe and there are not check are compile time or run time
// Q: Should I completely remove `down_cast` as I added it in about 30 minutes
}*/
{
// Multiple "inheritance"/subclassing
Entity :: struct {
position: Vector3,
}
Climber :: struct {
speed: f32,
}
Frog :: struct {
using entity: Entity,
using climber: Climber,
}
}
}
tagged_unions :: proc() {
{
Entity_Kind :: enum {
INVALID,
FROG,
GIRAFFE,
HELICOPTER,
}
Entity :: struct {
kind: Entity_Kind
using data: struct #raw_union {
frog: struct {
jump_height: f32,
colour: u32,
},
giraffe: struct {
neck_length: f32,
spot_count: int,
},
helicopter: struct {
blade_count: int,
weight: f32,
pilot_name: string,
},
}
}
e: Entity;
e.kind = Entity_Kind.FROG;
e.frog.jump_height = 12;
f: type_of(e.frog);
// But this is very unsafe and extremely cumbersome to write
// In C++, I use macros to alleviate this but it's not a solution
}
{
Frog :: struct {
jump_height: f32,
colour: u32,
}
Giraffe :: struct {
neck_length: f32,
spot_count: int,
}
Helicopter :: struct {
blade_count: int,
weight: f32,
pilot_name: string,
}
Entity :: union {Frog, Giraffe, Helicopter};
f1: Frog = Frog{12, 0xff9900};
f2: Entity = Frog{12, 0xff9900}; // Implicit cast
f3 := cast(Entity)Frog{12, 0xff9900}; // Explicit cast
// f3.Frog.jump_height = 12 // There are "members" of a union
e, f, g, h: Entity;
f = Frog{12, 0xff9900};
g = Giraffe{2.1, 23};
h = Helicopter{4, 1000, "Frank"};
// Requires a pointer to the union
// `x` will be a pointer to type of the case
match x in &f {
case Frog:
print("Frog!\n");
print(x.jump_height); nl();
// x.jump_height = 3;
print(x.jump_height); nl();
case Giraffe:
print("Giraffe!\n");
case Helicopter:
print("ROFLCOPTER!\n");
case:
print("invalid entity\n");
}
// Q: Allow for a non pointer version with takes a copy instead?
// Or it takes the pointer the data and not a copy
// fp := cast(^Frog)^f; // Unsafe
// print(fp.jump_height); nl();
// Internals of a tagged union
/*
struct {
data: [size_of_biggest_tag]u8,
tag_index: int,
}
*/
// This is to allow for pointer casting if needed
// Advantage over subtyping version
MAX_ENTITES :: 64;
entities: [MAX_ENTITES]Entity;
entities[0] = Frog{};
entities[1] = Helicopter{};
// etc.
}
{
// Transliteration of code from this actual compiler
// Some stuff is missing
Type :: struct {};
Scope :: struct {};
Token :: struct {};
AstNode :: struct {};
ExactValue :: struct {};
Entity_Kind :: enum {
Invalid,
Constant,
Variable,
Using_Variable,
TypeName,
Procedure,
Builtin,
Count,
}
Guid :: i64;
Entity :: struct {
kind: Entity_Kind,
guid: Guid,
scope: ^Scope,
token: Token,
type_: ^Type,
using data: struct #raw_union {
Constant: struct {
value: ExactValue,
},
Variable: struct {
visited: bool, // Cycle detection
used: bool, // Variable is used
is_field: bool, // Is struct field
anonymous: bool, // Variable is an anonymous
},
Using_Variable: struct {
},
TypeName: struct {
},
Procedure: struct {
used: bool,
},
Builtin: struct {
id: int,
},
},
}
// Plus all the constructing procedures that go along with them!!!!
// It's a nightmare
}
{
Type :: struct {};
Scope :: struct {};
Token :: struct {};
AstNode :: struct {};
ExactValue :: struct {};
Guid :: i64;
Entity_Base :: struct {
}
Constant :: struct {
value: ExactValue,
}
Variable :: struct {
visited: bool, // Cycle detection
used: bool, // Variable is used
is_field: bool, // Is struct field
anonymous: bool, // Variable is an anonymous
}
Using_Variable :: struct {
}
TypeName :: struct {
}
Procedure :: struct {
used: bool,
}
Builtin :: struct {
id: int,
}
Entity :: struct {
guid: Guid,
scope: ^Scope,
token: Token,
type_: ^Type,
variant: union {Constant, Variable, Using_Variable, TypeName, Procedure, Builtin},
}
e := Entity{
variant = Variable{
used = true,
anonymous = false,
},
};
// Q: Allow a "base" type to be added to a union?
// Or even `using` on union to get the same properties?
}
{
// `Raw` unions still have uses, especially for mathematic types
Vector2 :: struct #raw_union {
using xy_: struct { x, y: f32 },
e: [2]f32,
v: [vector 2]f32,
}
Vector3 :: struct #raw_union {
using xyz_: struct { x, y, z: f32 },
xy: Vector2,
e: [3]f32,
v: [vector 3]f32,
}
v2: Vector2;
v2.x = 1;
v2.e[0] = 1;
v2.v[0] = 1;
v3: Vector3;
v3.x = 1;
v3.e[0] = 1;
v3.v[0] = 1;
v3.xy.x = 1;
}
}
nl :: proc() { println(); }
-66
View File
@@ -1,66 +0,0 @@
import "core:fmt.odin";
import "core:utf8.odin";
import "core:hash.odin";
import "core:mem.odin";
main :: proc() {
{ // New Standard Library stuff
s := "Hello";
fmt.println(s,
utf8.valid_string(s),
hash.murmur64(cast([]u8)s));
// utf8.odin
// hash.odin
// - crc, fnv, fnva, murmur
// mem.odin
// - Custom allocators
// - Helpers
}
{
arena: mem.Arena;
mem.init_arena_from_context(&arena, mem.megabytes(16)); // Uses default allocator
defer mem.destroy_arena(&arena);
push_allocator mem.arena_allocator(&arena) {
x := new(int);
x^ = 1337;
fmt.println(x^);
}
/*
push_allocator x {
..
}
is equivalent to:
{
prev_allocator := __context.allocator
__context.allocator = x
defer __context.allocator = prev_allocator
..
}
*/
// You can also "push" a context
c := context; // Create copy of the allocator
c.allocator = mem.arena_allocator(&arena);
push_context c {
x := new(int);
x^ = 365;
fmt.println(x^);
}
}
// Backend improvements
// - Minimal dependency building (only build what is needed)
// - Numerous bugs fixed
// - Mild parsing recovery after bad syntax error
}
-283
View File
@@ -1,283 +0,0 @@
import "core:fmt.odin";
import "core:utf8.odin";
// import "core:atomic.odin";
// import "core:hash.odin";
// import "core:math.odin";
// import "core:mem.odin";
// import "core:opengl.odin";
// import "core:os.odin";
// import "core:sync.odin";
// import win32 "core:sys/windows.odin";
main :: proc() {
// syntax();
procedure_overloading();
}
syntax :: proc() {
// Cyclic type checking
// Uncomment to see the error
// A :: struct {b: B};
// B :: struct {a: A};
x: int;
y := cast(f32)x;
z := transmute(u32)y;
// down_cast, union_cast are similar too
// Basic directives
fmt.printf("Basic directives = %s(%d): %s\n", #file, #line, #procedure);
// NOTE: new and improved `printf`
// TODO: It does need accurate float printing
// record fields use the same syntax a procedure signatures
Thing1 :: struct {
x: f32,
y: int,
z: ^[]int,
};
Thing2 :: struct {x: f32, y: int, z: ^[]int};
// Slice interals are now just a `ptr+len+cap`
slice: []int; #assert(size_of(slice) == 3*size_of(int));
// Helper type - Help the reader understand what it is quicker
My_Int :: #type int;
My_Proc :: #type proc(int) -> f32;
// All declarations with : are either variable or constant
// To make these declarations syntactically consistent
v_variable := 123;
c_constant :: 123;
c_type1 :: int;
c_type2 :: []int;
c_proc :: proc() { /* code here */ };
/*
x += 1;
x -= 1;
// ++ and -- have been removed
// x++;
// x--;
// Question: Should they be added again?
// They were removed as they are redundant and statements, not expressions
// like in C/C++
*/
// You can now build files as a `.dll`
// `odin build_dll demo.odin`
// New vector syntax
u, v: [vector 3]f32;
v[0] = 123;
v.x = 123; // valid for all vectors with count 1 to 4
// Next part
prefixes();
}
Prefix_Type :: struct {x: int, y: f32, z: rawptr};
#thread_local my_tls: Prefix_Type;
prefixes :: proc() {
using var: Prefix_Type;
var.x = 123;
x = 123;
foo :: proc(using pt: Prefix_Type) {
}
// Same as C99's `restrict`
bar :: proc(#no_alias a, b: ^int) {
// Assumes a never equals b so it can perform optimizations with that fact
}
when_statements();
}
when_statements :: proc() {
X :: 123 + 12;
Y :: X/5;
COND :: Y > 0;
when COND {
fmt.println("Y > 0");
} else {
fmt.println("Y <= 0");
}
when false {
this_code_does_not_exist(123, 321);
but_its_syntax_is_valid();
x :: ^^^^int;
}
foreign_procedures();
}
when ODIN_OS == "windows" {
foreign_system_library win32_user "user32.lib";
}
// NOTE: This is done on purpose for two reasons:
// * Makes it clear where the platform specific stuff is
// * Removes the need to solve the travelling salesman problem when importing files :P
foreign_procedures :: proc() {
foreign win32_user {
ShowWindow :: proc(hwnd: rawptr, cmd_show: i32) -> i32 ---;
show_window :: proc(hwnd: rawptr, cmd_show: i32) -> i32 #link_name "ShowWindow" ---;
}
// NOTE: If that library doesn't get used, it doesn't get linked with
// NOTE: There is not link checking yet to see if that procedure does come from that library
// See sys/windows.odin for more examples
special_expressions();
}
special_expressions :: proc() {
/*
// Block expression
x := {
a: f32 = 123;
b := a-123;
c := b/a;
give c;
}; // semicolon is required as it's an expression
y := if x < 50 {
give x;
} else {
// TODO: Type cohesion is not yet finished
give 123;
}; // semicolon is required as it's an expression
*/
// This is allows for inline blocks of code and will be a useful feature to have when
// macros will be implemented into the language
loops();
}
loops :: proc() {
// The C-style for loop
for i := 0; i < 123; i += 1 {
break;
}
for i := 0; i < 123; {
break;
}
for false {
break;
}
for {
break;
}
for i in 0..123 { // 123 exclusive
}
for i in 0..123-1 { // 122 inclusive
}
for val, idx in 12..16 {
fmt.println(val, idx);
}
primes := [?]int{2, 3, 5, 7, 11, 13, 17, 19};
for p in primes {
fmt.println(p);
}
// Pointers to arrays, slices, or strings are allowed
for _ in &primes {
// ignore the value and just iterate across it
}
name := "你好,世界";
fmt.println(name);
for r in name {
#assert(type_of(r) == rune);
fmt.printf("%r\n", r);
}
when false {
for i, size := 0; i < name.count; i += size {
r: rune;
r, size = utf8.decode_rune(name[i..]);
fmt.printf("%r\n", r);
}
}
procedure_overloading();
}
procedure_overloading :: proc() {
THINGF :: 14451.1;
THINGI :: 14451;
foo :: proc() {
fmt.printf("Zero args\n");
}
foo :: proc(i: int) {
fmt.printf("int arg, i=%d\n", i);
}
foo :: proc(f: f64) {
i := cast(int)f;
fmt.printf("f64 arg, f=%d\n", i);
}
foo();
foo(THINGF);
// foo(THINGI); // 14451 is just a number so it could go to either procedures
foo(cast(int)THINGI);
foo :: proc(x: ^i32) -> (int, int) {
fmt.println("^int");
return 123, cast(int)(x^);
}
foo :: proc(x: rawptr) {
fmt.println("rawptr");
}
a: i32 = 123;
b: f32;
c: rawptr;
fmt.println(foo(&a));
foo(&b);
foo(c);
// foo(nil); // nil could go to numerous types thus the ambiguity
f: proc();
f = foo; // The correct `foo` to chosen
f();
// See math.odin and atomic.odin for more examples
}
-310
View File
@@ -1,310 +0,0 @@
// import "core:atomic.odin";
import "core:hash.odin";
import "core:mem.odin";
import "core:opengl.odin";
import "core:strconv.odin";
import "core:sync.odin";
import win32 "core:sys/windows.odin";
import "core:fmt.odin";
import "core:os.odin";
import "core:math.odin";
main :: proc() {
when true {
/*
Added:
* Unexported entities and fields using an underscore prefix
- See `sync.odin` and explain
Removed:
* Maybe/option types
* Remove `type` keyword and other "reserved" keywords
* ..< and .. removed and replace with .. (half-closed range)
Changed:
* `#assert` and `assert` return the value of the condition for semantic reasons
* thread_local -> #thread_local
* #include -> #load
* Files only get checked if they are actually used
* match x in y {} // For type match statements
* Version numbering now starts from 0.1.0 and uses the convention:
- major.minor.patch
* Core library additions to Windows specific stuff
*/
{
Fruit :: enum {
APPLE,
BANANA,
COCONUT,
}
fmt.println(Fruit.names);
}
{
A :: struct {x, y: f32};
B :: struct #align 16 {x, y: f32};
fmt.println("align_of(A) =", align_of(A));
fmt.println("align_of(B) =", align_of(B));
}
{
// Removal of ..< and ..
for i in 0..16 {
}
// Is similar to
for i := 0; i < 16; i += 1 {
}
}
{
thing: for i in 0..10 {
for j in i+1..10 {
if j == 2 {
fmt.println(i, j);
continue thing;
}
if j == 3 {
break thing;
}
}
}
// Works with, `for`, `for in`, `match`, `match in`
// NOTE(bill): This solves most of the problems I need `goto` for
}
{
t := type_info_of(int);
match i in t.variant {
case Type_Info_Integer, Type_Info_Float:
fmt.println("It's a number");
}
x: any = 123;
foo: match i in x {
case int, f32:
fmt.println("It's an int or f32");
break foo;
}
}
{
cond := true;
x: int;
if cond {
x = 3;
} else {
x = 4;
}
// Ternary operator
y := cond ? 3 : 4;
FOO :: true ? 123 : 432; // Constant ternary expression
fmt.println("Ternary values:", y, FOO);
}
{
// Slices now store a capacity
buf: [256]u8;
s: []u8;
s = buf[..0]; // == buf[0..0];
fmt.println("count =", len(s));
fmt.println("capacity =", cap(s));
append(&s, 1, 2, 3);
fmt.println(s);
s = buf[1..2..3];
fmt.println("count =", len(s));
fmt.println("capacity =", cap(s));
fmt.println(s);
clear(&s); // Sets count to zero
}
{
Foo :: struct {
x, y, z: f32,
ok: bool,
flags: u32,
}
foo_array: [256]Foo;
foo_as_bytes: []u8 = mem.slice_to_bytes(foo_array[..]);
// Useful for things like
// os.write(handle, foo_as_bytes);
foo_slice := mem.slice_ptr(cast(^Foo)&foo_as_bytes[0], len(foo_as_bytes)/size_of(Foo), cap(foo_as_bytes)/size_of(Foo));
// Question: Should there be a bytes_to_slice procedure or is it clearer to do this even if it is error prone?
// And if so what would the syntax be?
// slice_transmute([]Foo, foo_as_bytes);
}
{
Vec3 :: [vector 3]f32;
x := Vec3{1, 2, 3};
y := Vec3{4, 5, 6};
fmt.println(x < y);
fmt.println(x + y);
fmt.println(x - y);
fmt.println(x * y);
fmt.println(x / y);
for i in x {
fmt.println(i);
}
#assert(size_of([vector 7]bool) >= size_of([7]bool));
#assert(size_of([vector 7]i32) >= size_of([7]i32));
// align_of([vector 7]i32) != align_of([7]i32) // this may be the case
}
{
// fmt.* changes
// bprint* returns `string`
data: [256]u8;
str := fmt.bprintf(data[..], "Hellope %d %s %c", 123, "others", '!');
fmt.println(str);
}
{
x: [dynamic]f64;
reserve(&x, 16);
defer free(x); // `free` is overloaded for numerous types
// Number literals can have underscores in them for readability
append(&x, 2_000_000.500_000, 123, 5, 7); // variadic append
for p, i in x {
if i > 0 { fmt.print(", "); }
fmt.print(p);
}
fmt.println();
}
{
// Dynamic array "literals"
x := [dynamic]f64{2_000_000.500_000, 3, 5, 7};
defer free(x);
fmt.println(x); // fmt.print* supports printing of dynamic types
clear(&x);
fmt.println(x);
}
{
m: map[f32]int;
reserve(&m, 16);
defer free(m);
m[1.0] = 1278;
m[2.0] = 7643;
m[3.0] = 564;
_, ok := m[3.0];
c := m[3.0];
assert(ok && c == 564);
fmt.print("map[");
i := 0;
for val, key in m {
if i > 0 {
fmt.print(", ");
}
fmt.printf("%v=%v", key, val);
i += 1;
}
fmt.println("]");
}
{
m := map[string]u32{
"a" = 56,
"b" = 13453,
"c" = 7654,
};
defer free(m);
c := m["c"];
_, ok := m["c"];
assert(ok && c == 7654);
fmt.println(m);
delete(&m, "c"); // deletes entry with key "c"
_, found := m["c"];
assert(!found);
fmt.println(m);
clear(&m);
fmt.println(m);
// NOTE: Fixed size maps are planned but we have not yet implemented
// them as we have had no need for them as of yet
}
{
Vector3 :: struct{x, y, z: f32};
Quaternion :: struct{x, y, z, w: f32};
// Variants
Frog :: struct {
ribbit_volume: f32,
jump_height: f32,
}
Door :: struct {
openness: f32,
}
Map :: struct {
width, height: f32,
place_positions: []Vector3,
place_names: []string,
}
Entity :: struct {
// Common Fields
id: u64,
name: string,
using position: Vector3,
orientation: Quaternion,
flags: u32,
variant: union { Frog, Door, Map },
}
entity: Entity;
entity.id = 1337;
// implicit conversion from variant to base type
entity.variant = Frog{
ribbit_volume = 0.5,
jump_height = 2.1,
/*other data */
};
entity.name = "Frank";
entity.position = Vector3{1, 4, 9};
match e in entity.variant {
case Frog:
fmt.println("Ribbit");
case Door:
fmt.println("Creak");
case Map:
fmt.println("Rustle");
case:
fmt.println("Just a normal entity");
}
if frog, ok := entity.variant.(Frog); ok {
fmt.printf("The frog jumps %f feet high at %v\n", frog.jump_height, entity.position);
}
// Panics if not the correct type
frog: Frog;
frog = entity.variant.(Frog);
frog, _ = entity.variant.(Frog); // ignore error and force cast
}
}
}
-570
View File
@@ -1,570 +0,0 @@
import "core:fmt.odin"
import "core:strconv.odin"
import "core:mem.odin"
import "core:bits.odin"
import "core:hash.odin"
import "core:math.odin"
import "core:os.odin"
import "core:raw.odin"
import "core:sort.odin"
import "core:strings.odin"
import "core:types.odin"
import "core:utf16.odin"
import "core:utf8.odin"
when ODIN_OS == "windows" {
import "core:atomics.odin"
import "core:opengl.odin"
import "core:thread.odin"
import win32 "core:sys/windows.odin"
}
general_stuff :: proc() {
{ // `do` for inline statmes rather than block
foo :: proc() do fmt.println("Foo!");
if false do foo();
for false do foo();
when false do foo();
if false do foo();
else do foo();
}
{ // Removal of `++` and `--` (again)
x: int;
x += 1;
x -= 1;
}
{ // Casting syntaxes
i := i32(137);
ptr := &i;
fp1 := (^f32)(ptr);
// ^f32(ptr) == ^(f32(ptr))
fp2 := cast(^f32)ptr;
f1 := (^f32)(ptr)^;
f2 := (cast(^f32)ptr)^;
// Questions: Should there be two ways to do it?
}
/*
* Remove *_val_of built-in procedures
* size_of, align_of, offset_of
* type_of, type_info_of
*/
{ // `expand_to_tuple` built-in procedure
Foo :: struct {
x: int,
b: bool,
}
f := Foo{137, true};
x, b := expand_to_tuple(f);
fmt.println(f);
fmt.println(x, b);
fmt.println(expand_to_tuple(f));
}
{
// .. half-closed range
// .. open range
for in 0..2 {} // 0, 1
for in 0..2 {} // 0, 1, 2
}
}
default_struct_values :: proc() {
{
Vector3 :: struct {
x: f32,
y: f32,
z: f32,
}
v: Vector3;
fmt.println(v);
}
{
// Default values must be constants
Vector3 :: struct {
x: f32 = 1,
y: f32 = 4,
z: f32 = 9,
}
v: Vector3;
fmt.println(v);
v = Vector3{};
fmt.println(v);
// Uses the same semantics as a default values in a procedure
v = Vector3{137};
fmt.println(v);
v = Vector3{z = 137};
fmt.println(v);
}
{
Vector3 :: struct {
x := 1.0,
y := 4.0,
z := 9.0,
}
stack_default: Vector3;
stack_literal := Vector3{};
heap_one := new(Vector3); defer free(heap_one);
heap_two := new_clone(Vector3{}); defer free(heap_two);
fmt.println("stack_default - ", stack_default);
fmt.println("stack_literal - ", stack_literal);
fmt.println("heap_one - ", heap_one^);
fmt.println("heap_two - ", heap_two^);
N :: 4;
stack_array: [N]Vector3;
heap_array := new([N]Vector3); defer free(heap_array);
heap_slice := make([]Vector3, N); defer free(heap_slice);
fmt.println("stack_array[1] - ", stack_array[1]);
fmt.println("heap_array[1] - ", heap_array[1]);
fmt.println("heap_slice[1] - ", heap_slice[1]);
}
}
union_type :: proc() {
{
val: union{int, bool};
val = 137;
if i, ok := val.(int); ok {
fmt.println(i);
}
val = true;
fmt.println(val);
val = nil;
switch v in val {
case int: fmt.println("int", v);
case bool: fmt.println("bool", v);
case: fmt.println("nil");
}
}
{
// There is a duality between `any` and `union`
// An `any` has a pointer to the data and allows for any type (open)
// A `union` has as binary blob to store the data and allows only certain types (closed)
// The following code is with `any` but has the same syntax
val: any;
val = 137;
if i, ok := val.(int); ok {
fmt.println(i);
}
val = true;
fmt.println(val);
val = nil;
switch v in val {
case int: fmt.println("int", v);
case bool: fmt.println("bool", v);
case: fmt.println("nil");
}
}
Vector3 :: struct {x, y, z: f32};
Quaternion :: struct {x, y, z: f32, w: f32 = 1};
// More realistic examples
{
// NOTE(bill): For the above basic examples, you may not have any
// particular use for it. However, my main use for them is not for these
// simple cases. My main use is for hierarchical types. Many prefer
// subtyping, embedding the base data into the derived types. Below is
// an example of this for a basic game Entity.
Entity :: struct {
id: u64,
name: string,
position: Vector3,
orientation: Quaternion,
derived: any,
}
Frog :: struct {
using entity: Entity,
jump_height: f32,
}
Monster :: struct {
using entity: Entity,
is_robot: bool,
is_zombie: bool,
}
// See `parametric_polymorphism` procedure for details
new_entity :: proc(T: type) -> ^Entity {
t := new(T);
t.derived = t^;
return t;
}
entity := new_entity(Monster);
switch e in entity.derived {
case Frog:
fmt.println("Ribbit");
case Monster:
if e.is_robot do fmt.println("Robotic");
if e.is_zombie do fmt.println("Grrrr!");
}
}
{
// NOTE(bill): A union can be used to achieve something similar. Instead
// of embedding the base data into the derived types, the derived data
// in embedded into the base type. Below is the same example of the
// basic game Entity but using an union.
Entity :: struct {
id: u64,
name: string,
position: Vector3,
orientation: Quaternion,
derived: union {Frog, Monster},
}
Frog :: struct {
using entity: ^Entity,
jump_height: f32,
}
Monster :: struct {
using entity: ^Entity,
is_robot: bool,
is_zombie: bool,
}
// See `parametric_polymorphism` procedure for details
new_entity :: proc(T: type) -> ^Entity {
t := new(Entity);
t.derived = T{entity = t};
return t;
}
entity := new_entity(Monster);
switch e in entity.derived {
case Frog:
fmt.println("Ribbit");
case Monster:
if e.is_robot do fmt.println("Robotic");
if e.is_zombie do fmt.println("Grrrr!");
}
// NOTE(bill): As you can see, the usage code has not changed, only its
// memory layout. Both approaches have their own advantages but they can
// be used together to achieve different results. The subtyping approach
// can allow for a greater control of the memory layout and memory
// allocation, e.g. storing the derivatives together. However, this is
// also its disadvantage. You must either preallocate arrays for each
// derivative separation (which can be easily missed) or preallocate a
// bunch of "raw" memory; determining the maximum size of the derived
// types would require the aid of metaprogramming. Unions solve this
// particular problem as the data is stored with the base data.
// Therefore, it is possible to preallocate, e.g. [100]Entity.
// It should be noted that the union approach can have the same memory
// layout as the any and with the same type restrictions by using a
// pointer type for the derivatives.
/*
Entity :: struct {
..
derived: union{^Frog, ^Monster};
}
Frog :: struct {
using entity: Entity;
..
}
Monster :: struct {
using entity: Entity;
..
}
new_entity :: proc(T: type) -> ^Entity {
t := new(T);
t.derived = t;
return t;
}
*/
}
}
parametric_polymorphism :: proc() {
print_value :: proc(value: $T) {
fmt.printf("print_value: %T %v\n", value, value);
}
v1: int = 1;
v2: f32 = 2.1;
v3: f64 = 3.14;
v4: string = "message";
print_value(v1);
print_value(v2);
print_value(v3);
print_value(v4);
fmt.println();
add :: proc(p, q: $T) -> T {
x: T = p + q;
return x;
}
a := add(3, 4);
fmt.printf("a: %T = %v\n", a, a);
b := add(3.2, 4.3);
fmt.printf("b: %T = %v\n", b, b);
// This is how `new` is implemented
alloc_type :: proc(T: type) -> ^T {
t := cast(^T)alloc(size_of(T), align_of(T));
t^ = T{}; // Use default initialization value
return t;
}
copy_slice :: proc(dst, src: []$T) -> int {
n := min(len(dst), len(src));
if n > 0 {
mem.copy(&dst[0], &src[0], n*size_of(T));
}
return n;
}
double_params :: proc(a: $A, b: $B) -> A {
return a + A(b);
}
fmt.println(double_params(12, 1.345));
{ // Polymorphic Types and Type Specialization
Table_Slot :: struct(Key, Value: type) {
occupied: bool,
hash: u32,
key: Key,
value: Value,
}
TABLE_SIZE_MIN :: 32;
Table :: struct(Key, Value: type) {
count: int,
allocator: Allocator,
slots: []Table_Slot(Key, Value),
}
// Only allow types that are specializations of a (polymorphic) slice
make_slice :: proc(T: type/[]$E, len: int) -> T {
return make(T, len);
}
// Only allow types that are specializations of `Table`
allocate :: proc(table: ^$T/Table, capacity: int) {
c := context;
if table.allocator.procedure != nil do c.allocator = table.allocator;
push_context c {
table.slots = make_slice(type_of(table.slots), max(capacity, TABLE_SIZE_MIN));
}
}
expand :: proc(table: ^$T/Table) {
c := context;
if table.allocator.procedure != nil do c.allocator = table.allocator;
push_context c {
old_slots := table.slots;
cap := max(2*cap(table.slots), TABLE_SIZE_MIN);
allocate(table, cap);
for s in old_slots do if s.occupied {
put(table, s.key, s.value);
}
free(old_slots);
}
}
// Polymorphic determination of a polymorphic struct
// put :: proc(table: ^$T/Table, key: T.Key, value: T.Value) {
put :: proc(table: ^Table($Key, $Value), key: Key, value: Value) {
hash := get_hash(key); // Ad-hoc method which would fail in a different scope
index := find_index(table, key, hash);
if index < 0 {
if f64(table.count) >= 0.75*f64(cap(table.slots)) {
expand(table);
}
assert(table.count <= cap(table.slots));
hash := get_hash(key);
index = int(hash % u32(cap(table.slots)));
for table.slots[index].occupied {
if index += 1; index >= cap(table.slots) {
index = 0;
}
}
table.count += 1;
}
slot := &table.slots[index];
slot.occupied = true;
slot.hash = hash;
slot.key = key;
slot.value = value;
}
// find :: proc(table: ^$T/Table, key: T.Key) -> (T.Value, bool) {
find :: proc(table: ^Table($Key, $Value), key: Key) -> (Value, bool) {
hash := get_hash(key);
index := find_index(table, key, hash);
if index < 0 {
return Value{}, false;
}
return table.slots[index].value, true;
}
find_index :: proc(table: ^Table($Key, $Value), key: Key, hash: u32) -> int {
if cap(table.slots) <= 0 do return -1;
index := int(hash % u32(cap(table.slots)));
for table.slots[index].occupied {
if table.slots[index].hash == hash {
if table.slots[index].key == key {
return index;
}
}
if index += 1; index >= cap(table.slots) {
index = 0;
}
}
return -1;
}
get_hash :: proc(s: string) -> u32 { // fnv32a
h: u32 = 0x811c9dc5;
for i in 0..len(s) {
h = (h ~ u32(s[i])) * 0x01000193;
}
return h;
}
table: Table(string, int);
for i in 0..36 do put(&table, "Hellope", i);
for i in 0..42 do put(&table, "World!", i);
found, _ := find(&table, "Hellope");
fmt.printf("`found` is %v\n", found);
found, _ = find(&table, "World!");
fmt.printf("`found` is %v\n", found);
// I would not personally design a hash table like this in production
// but this is a nice basic example
// A better approach would either use a `u64` or equivalent for the key
// and let the user specify the hashing function or make the user store
// the hashing procedure with the table
}
}
prefix_table := [?]string{
"White",
"Red",
"Green",
"Blue",
"Octarine",
"Black",
};
threading_example :: proc() {
when ODIN_OS == "windows" {
unordered_remove :: proc(array: ^[]$T, index: int, loc := #caller_location) {
__bounds_check_error_loc(loc, index, len(array));
array[index] = array[len(array)-1];
pop(array);
}
ordered_remove :: proc(array: ^[]$T, index: int, loc := #caller_location) {
__bounds_check_error_loc(loc, index, len(array));
copy(array[index..], array[index+1..]);
pop(array);
}
worker_proc :: proc(t: ^thread.Thread) -> int {
for iteration in 1..5 {
fmt.printf("Thread %d is on iteration %d\n", t.user_index, iteration);
fmt.printf("`%s`: iteration %d\n", prefix_table[t.user_index], iteration);
// win32.sleep(1);
}
return 0;
}
threads := make([]^thread.Thread, 0, len(prefix_table));
defer free(threads);
for i in 0..len(prefix_table) {
if t := thread.create(worker_proc); t != nil {
t.init_context = context;
t.use_init_context = true;
t.user_index = len(threads);
append(&threads, t);
thread.start(t);
}
}
for len(threads) > 0 {
for i := 0; i < len(threads); /**/ {
if t := threads[i]; thread.is_done(t) {
fmt.printf("Thread %d is done\n", t.user_index);
thread.destroy(t);
ordered_remove(&threads, i);
} else {
i += 1;
}
}
}
}
}
main :: proc() {
when false {
fmt.println("\n# general_stuff"); general_stuff();
fmt.println("\n# default_struct_values"); default_struct_values();
fmt.println("\n# union_type"); union_type();
fmt.println("\n# parametric_polymorphism"); parametric_polymorphism();
fmt.println("\n# threading_example"); threading_example();
}
}
-778
View File
@@ -1,778 +0,0 @@
import "core:fmt.odin"
import "core:strconv.odin"
import "core:mem.odin"
import "core:bits.odin"
import "core:hash.odin"
import "core:math.odin"
import "core:math/rand.odin"
import "core:os.odin"
import "core:raw.odin"
import "core:sort.odin"
import "core:strings.odin"
import "core:types.odin"
import "core:utf16.odin"
import "core:utf8.odin"
// File scope `when` statements
when ODIN_OS == "windows" {
import "core:atomics.odin"
import "core:thread.odin"
import win32 "core:sys/windows.odin"
}
@(link_name="general_stuff")
general_stuff :: proc() {
fmt.println("# general_stuff");
{ // `do` for inline statements rather than block
foo :: proc() do fmt.println("Foo!");
if false do foo();
for false do foo();
when false do foo();
if false do foo();
else do foo();
}
{ // Removal of `++` and `--` (again)
x: int;
x += 1;
x -= 1;
}
{ // Casting syntaxes
i := i32(137);
ptr := &i;
_ = (^f32)(ptr);
// ^f32(ptr) == ^(f32(ptr))
_ = cast(^f32)ptr;
_ = (^f32)(ptr)^;
_ = (cast(^f32)ptr)^;
// Questions: Should there be two ways to do it?
}
/*
* Remove *_val_of built-in procedures
* size_of, align_of, offset_of
* type_of, type_info_of
*/
{ // `expand_to_tuple` built-in procedure
Foo :: struct {
x: int,
b: bool,
}
f := Foo{137, true};
x, b := expand_to_tuple(f);
fmt.println(f);
fmt.println(x, b);
fmt.println(expand_to_tuple(f));
}
{
// .. half-closed range
// .. open range
for in 0..2 {} // 0, 1
for in 0..2 {} // 0, 1, 2
}
{ // Multiple sized booleans
x0: bool; // default
x1: b8 = true;
x2: b16 = false;
x3: b32 = true;
x4: b64 = false;
fmt.printf("x1: %T = %v;\n", x1, x1);
fmt.printf("x2: %T = %v;\n", x2, x2);
fmt.printf("x3: %T = %v;\n", x3, x3);
fmt.printf("x4: %T = %v;\n", x4, x4);
// Having specific sized booleans is very useful when dealing with foreign code
// and to enforce specific alignment for a boolean, especially within a struct
}
{ // `distinct` types
// Originally, all type declarations would create a distinct type unless #type_alias was present.
// Now the behaviour has been reversed. All type declarations create a type alias unless `distinct` is present.
// If the type expression is `struct`, `union`, `enum`, or `proc`, the types will always been distinct.
Int32 :: i32;
#assert(Int32 == i32);
My_Int32 :: distinct i32;
#assert(My_Int32 != i32);
My_Struct :: struct{x: int};
#assert(My_Struct != struct{x: int});
}
}
default_struct_values :: proc() {
fmt.println("# default_struct_values");
{
Vector3 :: struct {
x: f32,
y: f32,
z: f32,
}
v: Vector3;
fmt.println(v);
}
{
// Default values must be constants
Vector3 :: struct {
x: f32 = 1,
y: f32 = 4,
z: f32 = 9,
}
v: Vector3;
fmt.println(v);
v = Vector3{};
fmt.println(v);
// Uses the same semantics as a default values in a procedure
v = Vector3{137};
fmt.println(v);
v = Vector3{z = 137};
fmt.println(v);
}
{
Vector3 :: struct {
x := 1.0,
y := 4.0,
z := 9.0,
}
stack_default: Vector3;
stack_literal := Vector3{};
heap_one := new(Vector3); defer free(heap_one);
heap_two := new_clone(Vector3{}); defer free(heap_two);
fmt.println("stack_default - ", stack_default);
fmt.println("stack_literal - ", stack_literal);
fmt.println("heap_one - ", heap_one^);
fmt.println("heap_two - ", heap_two^);
N :: 4;
stack_array: [N]Vector3;
heap_array := new([N]Vector3); defer free(heap_array);
heap_slice := make([]Vector3, N); defer free(heap_slice);
fmt.println("stack_array[1] - ", stack_array[1]);
fmt.println("heap_array[1] - ", heap_array[1]);
fmt.println("heap_slice[1] - ", heap_slice[1]);
}
}
union_type :: proc() {
fmt.println("\n# union_type");
{
val: union{int, bool};
val = 137;
if i, ok := val.(int); ok {
fmt.println(i);
}
val = true;
fmt.println(val);
val = nil;
switch v in val {
case int: fmt.println("int", v);
case bool: fmt.println("bool", v);
case: fmt.println("nil");
}
}
{
// There is a duality between `any` and `union`
// An `any` has a pointer to the data and allows for any type (open)
// A `union` has as binary blob to store the data and allows only certain types (closed)
// The following code is with `any` but has the same syntax
val: any;
val = 137;
if i, ok := val.(int); ok {
fmt.println(i);
}
val = true;
fmt.println(val);
val = nil;
switch v in val {
case int: fmt.println("int", v);
case bool: fmt.println("bool", v);
case: fmt.println("nil");
}
}
Vector3 :: struct {x, y, z: f32};
Quaternion :: struct {x, y, z: f32, w: f32 = 1};
// More realistic examples
{
// NOTE(bill): For the above basic examples, you may not have any
// particular use for it. However, my main use for them is not for these
// simple cases. My main use is for hierarchical types. Many prefer
// subtyping, embedding the base data into the derived types. Below is
// an example of this for a basic game Entity.
Entity :: struct {
id: u64,
name: string,
position: Vector3,
orientation: Quaternion,
derived: any,
}
Frog :: struct {
using entity: Entity,
jump_height: f32,
}
Monster :: struct {
using entity: Entity,
is_robot: bool,
is_zombie: bool,
}
// See `parametric_polymorphism` procedure for details
new_entity :: proc(T: type) -> ^Entity {
t := new(T);
t.derived = t^;
return t;
}
entity := new_entity(Monster);
switch e in entity.derived {
case Frog:
fmt.println("Ribbit");
case Monster:
if e.is_robot do fmt.println("Robotic");
if e.is_zombie do fmt.println("Grrrr!");
}
}
{
// NOTE(bill): A union can be used to achieve something similar. Instead
// of embedding the base data into the derived types, the derived data
// in embedded into the base type. Below is the same example of the
// basic game Entity but using an union.
Entity :: struct {
id: u64,
name: string,
position: Vector3,
orientation: Quaternion,
derived: union {Frog, Monster},
}
Frog :: struct {
using entity: ^Entity,
jump_height: f32,
}
Monster :: struct {
using entity: ^Entity,
is_robot: bool,
is_zombie: bool,
}
// See `parametric_polymorphism` procedure for details
new_entity :: proc(T: type) -> ^Entity {
t := new(Entity);
t.derived = T{entity = t};
return t;
}
entity := new_entity(Monster);
switch e in entity.derived {
case Frog:
fmt.println("Ribbit");
case Monster:
if e.is_robot do fmt.println("Robotic");
if e.is_zombie do fmt.println("Grrrr!");
}
// NOTE(bill): As you can see, the usage code has not changed, only its
// memory layout. Both approaches have their own advantages but they can
// be used together to achieve different results. The subtyping approach
// can allow for a greater control of the memory layout and memory
// allocation, e.g. storing the derivatives together. However, this is
// also its disadvantage. You must either preallocate arrays for each
// derivative separation (which can be easily missed) or preallocate a
// bunch of "raw" memory; determining the maximum size of the derived
// types would require the aid of metaprogramming. Unions solve this
// particular problem as the data is stored with the base data.
// Therefore, it is possible to preallocate, e.g. [100]Entity.
// It should be noted that the union approach can have the same memory
// layout as the any and with the same type restrictions by using a
// pointer type for the derivatives.
/*
Entity :: struct {
..
derived: union{^Frog, ^Monster},
}
Frog :: struct {
using entity: Entity,
..
}
Monster :: struct {
using entity: Entity,
..
}
new_entity :: proc(T: type) -> ^Entity {
t := new(T);
t.derived = t;
return t;
}
*/
}
}
parametric_polymorphism :: proc() {
fmt.println("# parametric_polymorphism");
print_value :: proc(value: $T) {
fmt.printf("print_value: %T %v\n", value, value);
}
v1: int = 1;
v2: f32 = 2.1;
v3: f64 = 3.14;
v4: string = "message";
print_value(v1);
print_value(v2);
print_value(v3);
print_value(v4);
fmt.println();
add :: proc(p, q: $T) -> T {
x: T = p + q;
return x;
}
a := add(3, 4);
fmt.printf("a: %T = %v\n", a, a);
b := add(3.2, 4.3);
fmt.printf("b: %T = %v\n", b, b);
// This is how `new` is implemented
alloc_type :: proc(T: type) -> ^T {
t := cast(^T)alloc(size_of(T), align_of(T));
t^ = T{}; // Use default initialization value
return t;
}
copy_slice :: proc(dst, src: []$T) -> int {
return mem.copy(&dst[0], &src[0], n*size_of(T));
}
double_params :: proc(a: $A, b: $B) -> A {
return a + A(b);
}
fmt.println(double_params(12, 1.345));
{ // Polymorphic Types and Type Specialization
Table_Slot :: struct(Key, Value: type) {
occupied: bool,
hash: u32,
key: Key,
value: Value,
}
TABLE_SIZE_MIN :: 32;
Table :: struct(Key, Value: type) {
count: int,
allocator: Allocator,
slots: []Table_Slot(Key, Value),
}
// Only allow types that are specializations of a (polymorphic) slice
make_slice :: proc(T: type/[]$E, len: int) -> T {
return make(T, len);
}
// Only allow types that are specializations of `Table`
allocate :: proc(table: ^$T/Table, capacity: int) {
c := context;
if table.allocator.procedure != nil do c.allocator = table.allocator;
context <- c {
table.slots = make_slice(type_of(table.slots), max(capacity, TABLE_SIZE_MIN));
}
}
expand :: proc(table: ^$T/Table) {
c := context;
if table.allocator.procedure != nil do c.allocator = table.allocator;
context <- c {
old_slots := table.slots;
cap := max(2*len(table.slots), TABLE_SIZE_MIN);
allocate(table, cap);
for s in old_slots do if s.occupied {
put(table, s.key, s.value);
}
free(old_slots);
}
}
// Polymorphic determination of a polymorphic struct
// put :: proc(table: ^$T/Table, key: T.Key, value: T.Value) {
put :: proc(table: ^Table($Key, $Value), key: Key, value: Value) {
hash := get_hash(key); // Ad-hoc method which would fail in a different scope
index := find_index(table, key, hash);
if index < 0 {
if f64(table.count) >= 0.75*f64(len(table.slots)) {
expand(table);
}
assert(table.count <= len(table.slots));
hash := get_hash(key);
index = int(hash % u32(len(table.slots)));
for table.slots[index].occupied {
if index += 1; index >= len(table.slots) {
index = 0;
}
}
table.count += 1;
}
slot := &table.slots[index];
slot.occupied = true;
slot.hash = hash;
slot.key = key;
slot.value = value;
}
// find :: proc(table: ^$T/Table, key: T.Key) -> (T.Value, bool) {
find :: proc(table: ^Table($Key, $Value), key: Key) -> (Value, bool) {
hash := get_hash(key);
index := find_index(table, key, hash);
if index < 0 {
return Value{}, false;
}
return table.slots[index].value, true;
}
find_index :: proc(table: ^Table($Key, $Value), key: Key, hash: u32) -> int {
if len(table.slots) <= 0 do return -1;
index := int(hash % u32(len(table.slots)));
for table.slots[index].occupied {
if table.slots[index].hash == hash {
if table.slots[index].key == key {
return index;
}
}
if index += 1; index >= len(table.slots) {
index = 0;
}
}
return -1;
}
get_hash :: proc(s: string) -> u32 { // fnv32a
h: u32 = 0x811c9dc5;
for i in 0..len(s) {
h = (h ~ u32(s[i])) * 0x01000193;
}
return h;
}
table: Table(string, int);
for i in 0..36 do put(&table, "Hellope", i);
for i in 0..42 do put(&table, "World!", i);
found, _ := find(&table, "Hellope");
fmt.printf("`found` is %v\n", found);
found, _ = find(&table, "World!");
fmt.printf("`found` is %v\n", found);
// I would not personally design a hash table like this in production
// but this is a nice basic example
// A better approach would either use a `u64` or equivalent for the key
// and let the user specify the hashing function or make the user store
// the hashing procedure with the table
}
}
prefix_table := [?]string{
"White",
"Red",
"Green",
"Blue",
"Octarine",
"Black",
};
threading_example :: proc() {
when ODIN_OS == "windows" {
fmt.println("# threading_example");
unordered_remove :: proc(array: ^[dynamic]$T, index: int, loc := #caller_location) {
__bounds_check_error_loc(loc, index, len(array));
array[index] = array[len(array)-1];
pop(array);
}
ordered_remove :: proc(array: ^[dynamic]$T, index: int, loc := #caller_location) {
__bounds_check_error_loc(loc, index, len(array));
copy(array[index..], array[index+1..]);
pop(array);
}
worker_proc :: proc(t: ^thread.Thread) -> int {
for iteration in 1..5 {
fmt.printf("Thread %d is on iteration %d\n", t.user_index, iteration);
fmt.printf("`%s`: iteration %d\n", prefix_table[t.user_index], iteration);
// win32.sleep(1);
}
return 0;
}
threads := make([dynamic]^thread.Thread, 0, len(prefix_table));
defer free(threads);
for in prefix_table {
if t := thread.create(worker_proc); t != nil {
t.init_context = context;
t.use_init_context = true;
t.user_index = len(threads);
append(&threads, t);
thread.start(t);
}
}
for len(threads) > 0 {
for i := 0; i < len(threads); /**/ {
if t := threads[i]; thread.is_done(t) {
fmt.printf("Thread %d is done\n", t.user_index);
thread.destroy(t);
ordered_remove(&threads, i);
} else {
i += 1;
}
}
}
}
}
array_programming :: proc() {
fmt.println("# array_programming");
{
a := [3]f32{1, 2, 3};
b := [3]f32{5, 6, 7};
c := a * b;
d := a + b;
e := 1 + (c - d) / 2;
fmt.printf("%.1f\n", e); // [0.5, 3.0, 6.5]
}
{
a := [3]f32{1, 2, 3};
b := swizzle(a, 2, 1, 0);
assert(b == [3]f32{3, 2, 1});
c := swizzle(a, 0, 0);
assert(c == [2]f32{1, 1});
assert(c == 1);
}
{
Vector3 :: distinct [3]f32;
a := Vector3{1, 2, 3};
b := Vector3{5, 6, 7};
c := (a * b)/2 + 1;
d := c.x + c.y + c.z;
fmt.printf("%.1f\n", d); // 22.0
cross :: proc(a, b: Vector3) -> Vector3 {
i := swizzle(a, 1, 2, 0) * swizzle(b, 2, 0, 1);
j := swizzle(a, 2, 0, 1) * swizzle(b, 1, 2, 0);
return i - j;
}
blah :: proc(a: Vector3) -> f32 {
return a.x + a.y + a.z;
}
x := cross(a, b);
fmt.println(x);
fmt.println(blah(x));
}
}
using println in import "core:fmt.odin"
using_in :: proc() {
fmt.println("# using in");
using print in fmt;
println("Hellope1");
print("Hellope2\n");
Foo :: struct {
x, y: int,
b: bool,
}
f: Foo;
f.x, f.y = 123, 321;
println(f);
using x, y in f;
x, y = 456, 654;
println(f);
}
named_proc_return_parameters :: proc() {
fmt.println("# named proc return parameters");
foo0 :: proc() -> int {
return 123;
}
foo1 :: proc() -> (a: int) {
a = 123;
return;
}
foo2 :: proc() -> (a, b: int) {
// Named return values act like variables within the scope
a = 321;
b = 567;
return b, a;
}
fmt.println("foo0 =", foo0()); // 123
fmt.println("foo1 =", foo1()); // 123
fmt.println("foo2 =", foo2()); // 567 321
}
enum_export :: proc() {
fmt.println("# enum #export");
Foo :: enum #export {A, B, C};
f0 := A;
f1 := B;
f2 := C;
fmt.println(f0, f1, f2);
}
explicit_procedure_overloading :: proc() {
fmt.println("# explicit procedure overloading");
add_ints :: proc(a, b: int) -> int {
x := a + b;
fmt.println("add_ints", x);
return x;
}
add_floats :: proc(a, b: f32) -> f32 {
x := a + b;
fmt.println("add_floats", x);
return x;
}
add_numbers :: proc(a: int, b: f32, c: u8) -> int {
x := int(a) + int(b) + int(c);
fmt.println("add_numbers", x);
return x;
}
add :: proc[add_ints, add_floats, add_numbers];
add(int(1), int(2));
add(f32(1), f32(2));
add(int(1), f32(2), u8(3));
add(1, 2); // untyped ints coerce to int tighter than f32
add(1.0, 2.0); // untyped floats coerce to f32 tighter than int
add(1, 2, 3); // three parameters
// Ambiguous answers
// add(1.0, 2);
// add(1, 2.0);
}
complete_switch :: proc() {
fmt.println("# complete_switch");
{ // enum
Foo :: enum #export {
A,
B,
C,
D,
}
b := Foo.B;
f := Foo.A;
#complete switch f {
case A: fmt.println("A");
case B: fmt.println("B");
case C: fmt.println("C");
case D: fmt.println("D");
case: fmt.println("?");
}
}
{ // union
Foo :: union {int, bool};
f: Foo = 123;
#complete switch in f {
case int: fmt.println("int");
case bool: fmt.println("bool");
case:
}
}
}
main :: proc() {
when true {
general_stuff();
default_struct_values();
union_type();
parametric_polymorphism();
threading_example();
array_programming();
using_in();
named_proc_return_parameters();
enum_export();
explicit_procedure_overloading();
complete_switch();
}
}
-412
View File
@@ -1,412 +0,0 @@
#include "win32.odin"
assume :: proc(cond: bool) #foreign "llvm.assume"
__debug_trap :: proc() #foreign "llvm.debugtrap"
__trap :: proc() #foreign "llvm.trap"
read_cycle_counter :: proc() -> u64 #foreign "llvm.readcyclecounter"
bit_reverse16 :: proc(b: u16) -> u16 #foreign "llvm.bitreverse.i16"
bit_reverse32 :: proc(b: u32) -> u32 #foreign "llvm.bitreverse.i32"
bit_reverse64 :: proc(b: u64) -> u64 #foreign "llvm.bitreverse.i64"
byte_swap16 :: proc(b: u16) -> u16 #foreign "llvm.bswap.i16"
byte_swap32 :: proc(b: u32) -> u32 #foreign "llvm.bswap.i32"
byte_swap64 :: proc(b: u64) -> u64 #foreign "llvm.bswap.i64"
fmuladd_f32 :: proc(a, b, c: f32) -> f32 #foreign "llvm.fmuladd.f32"
fmuladd_f64 :: proc(a, b, c: f64) -> f64 #foreign "llvm.fmuladd.f64"
// TODO(bill): make custom heap procedures
heap_alloc :: proc(len: int) -> rawptr #foreign "malloc"
heap_dealloc :: proc(ptr: rawptr) #foreign "free"
memory_zero :: proc(data: rawptr, len: int) {
d := slice_ptr(data as ^byte, len)
for i := 0; i < len; i++ {
d[i] = 0
}
}
memory_compare :: proc(dst, src: rawptr, len: int) -> int {
s1, s2: ^byte = dst, src
for i := 0; i < len; i++ {
a := ptr_offset(s1, i)^
b := ptr_offset(s2, i)^
if a != b {
return (a - b) as int
}
}
return 0
}
memory_copy :: proc(dst, src: rawptr, n: int) #inline {
if dst == src {
return
}
v128b :: type {4}u32
#assert(align_of(v128b) == 16)
d, s: ^byte = dst, src
for ; s as uint % 16 != 0 && n != 0; n-- {
d^ = s^
d, s = ptr_offset(d, 1), ptr_offset(s, 1)
}
if d as uint % 16 == 0 {
for ; n >= 16; d, s, n = ptr_offset(d, 16), ptr_offset(s, 16), n-16 {
(d as ^v128b)^ = (s as ^v128b)^
}
if n&8 != 0 {
(d as ^u64)^ = (s as ^u64)^
d, s = ptr_offset(d, 8), ptr_offset(s, 8)
}
if n&4 != 0 {
(d as ^u32)^ = (s as ^u32)^;
d, s = ptr_offset(d, 4), ptr_offset(s, 4)
}
if n&2 != 0 {
(d as ^u16)^ = (s as ^u16)^
d, s = ptr_offset(d, 2), ptr_offset(s, 2)
}
if n&1 != 0 {
d^ = s^
d, s = ptr_offset(d, 1), ptr_offset(s, 1)
}
return;
}
// IMPORTANT NOTE(bill): Little endian only
LS :: proc(a, b: u32) -> u32 #inline { return a << b }
RS :: proc(a, b: u32) -> u32 #inline { return a >> b }
/* NOTE(bill): Big endian version
LS :: proc(a, b: u32) -> u32 #inline { return a >> b; }
RS :: proc(a, b: u32) -> u32 #inline { return a << b; }
*/
w, x: u32
if d as uint % 4 == 1 {
w = (s as ^u32)^
d^ = s^; d = ptr_offset(d, 1); s = ptr_offset(s, 1)
d^ = s^; d = ptr_offset(d, 1); s = ptr_offset(s, 1)
d^ = s^; d = ptr_offset(d, 1); s = ptr_offset(s, 1)
n -= 3
for n > 16 {
d32 := d as ^u32
s32 := ptr_offset(s, 1) as ^u32
x = s32^; d32^ = LS(w, 24) | RS(x, 8)
d32, s32 = ptr_offset(d32, 1), ptr_offset(s32, 1)
w = s32^; d32^ = LS(x, 24) | RS(w, 8)
d32, s32 = ptr_offset(d32, 1), ptr_offset(s32, 1)
x = s32^; d32^ = LS(w, 24) | RS(x, 8)
d32, s32 = ptr_offset(d32, 1), ptr_offset(s32, 1)
w = s32^; d32^ = LS(x, 24) | RS(w, 8)
d32, s32 = ptr_offset(d32, 1), ptr_offset(s32, 1)
d, s, n = ptr_offset(d, 16), ptr_offset(s, 16), n-16
}
} else if d as uint % 4 == 2 {
w = (s as ^u32)^
d^ = s^; d = ptr_offset(d, 1); s = ptr_offset(s, 1)
d^ = s^; d = ptr_offset(d, 1); s = ptr_offset(s, 1)
n -= 2
for n > 17 {
d32 := d as ^u32
s32 := ptr_offset(s, 2) as ^u32
x = s32^; d32^ = LS(w, 16) | RS(x, 16)
d32, s32 = ptr_offset(d32, 1), ptr_offset(s32, 1)
w = s32^; d32^ = LS(x, 16) | RS(w, 16)
d32, s32 = ptr_offset(d32, 1), ptr_offset(s32, 1)
x = s32^; d32^ = LS(w, 16) | RS(x, 16)
d32, s32 = ptr_offset(d32, 1), ptr_offset(s32, 1)
w = s32^; d32^ = LS(x, 16) | RS(w, 16)
d32, s32 = ptr_offset(d32, 1), ptr_offset(s32, 1)
d, s, n = ptr_offset(d, 16), ptr_offset(s, 16), n-16
}
} else if d as uint % 4 == 3 {
w = (s as ^u32)^
d^ = s^
n -= 1
for n > 18 {
d32 := d as ^u32
s32 := ptr_offset(s, 3) as ^u32
x = s32^; d32^ = LS(w, 8) | RS(x, 24)
d32, s32 = ptr_offset(d32, 1), ptr_offset(s32, 1)
w = s32^; d32^ = LS(x, 8) | RS(w, 24)
d32, s32 = ptr_offset(d32, 1), ptr_offset(s32, 1)
x = s32^; d32^ = LS(w, 8) | RS(x, 24)
d32, s32 = ptr_offset(d32, 1), ptr_offset(s32, 1)
w = s32^; d32^ = LS(x, 8) | RS(w, 24)
d32, s32 = ptr_offset(d32, 1), ptr_offset(s32, 1)
d, s, n = ptr_offset(d, 16), ptr_offset(s, 16), n-16
}
}
if n&16 != 0 {
(d as ^v128b)^ = (s as ^v128b)^
d, s = ptr_offset(d, 16), ptr_offset(s, 16)
}
if n&8 != 0 {
(d as ^u64)^ = (s as ^u64)^
d, s = ptr_offset(d, 8), ptr_offset(s, 8)
}
if n&4 != 0 {
(d as ^u32)^ = (s as ^u32)^;
d, s = ptr_offset(d, 4), ptr_offset(s, 4)
}
if n&2 != 0 {
(d as ^u16)^ = (s as ^u16)^
d, s = ptr_offset(d, 2), ptr_offset(s, 2)
}
if n&1 != 0 {
d^ = s^
}
}
memory_move :: proc(dst, src: rawptr, n: int) #inline {
d, s: ^byte = dst, src
if d == s {
return
}
if d >= ptr_offset(s, n) || ptr_offset(d, n) <= s {
memory_copy(d, s, n)
return
}
// TODO(bill): Vectorize the shit out of this
if d < s {
if s as int % size_of(int) == d as int % size_of(int) {
for d as int % size_of(int) != 0 {
if n == 0 {
return
}
n--
d^ = s^
d, s = ptr_offset(d, 1), ptr_offset(s, 1)
}
di, si := d as ^int, s as ^int
for n >= size_of(int) {
di^ = si^
di, si = ptr_offset(di, 1), ptr_offset(si, 1)
n -= size_of(int)
}
}
for ; n > 0; n-- {
d^ = s^
d, s = ptr_offset(d, 1), ptr_offset(s, 1)
}
} else {
if s as int % size_of(int) == d as int % size_of(int) {
for ptr_offset(d, n) as int % size_of(int) != 0 {
if n == 0 {
return
}
n--
d^ = s^
d, s = ptr_offset(d, 1), ptr_offset(s, 1)
}
for n >= size_of(int) {
n -= size_of(int)
di := ptr_offset(d, n) as ^int
si := ptr_offset(s, n) as ^int
di^ = si^
}
for ; n > 0; n-- {
d^ = s^
d, s = ptr_offset(d, 1), ptr_offset(s, 1)
}
}
for n > 0 {
n--
dn := ptr_offset(d, n)
sn := ptr_offset(s, n)
dn^ = sn^
}
}
}
__string_eq :: proc(a, b: string) -> bool {
if len(a) != len(b) {
return false
}
if ^a[0] == ^b[0] {
return true
}
return memory_compare(^a[0], ^b[0], len(a)) == 0
}
__string_cmp :: proc(a, b : string) -> int {
min_len := len(a)
if len(b) < min_len {
min_len = len(b)
}
for i := 0; i < min_len; i++ {
x := a[i]
y := b[i]
if x < y {
return -1
} else if x > y {
return +1
}
}
if len(a) < len(b) {
return -1
} else if len(a) > len(b) {
return +1
}
return 0
}
__string_ne :: proc(a, b : string) -> bool #inline { return !__string_eq(a, b) }
__string_lt :: proc(a, b : string) -> bool #inline { return __string_cmp(a, b) < 0 }
__string_gt :: proc(a, b : string) -> bool #inline { return __string_cmp(a, b) > 0 }
__string_le :: proc(a, b : string) -> bool #inline { return __string_cmp(a, b) <= 0 }
__string_ge :: proc(a, b : string) -> bool #inline { return __string_cmp(a, b) >= 0 }
Allocation_Mode :: type enum {
ALLOC,
DEALLOC,
DEALLOC_ALL,
RESIZE,
}
Allocator_Proc :: type proc(allocator_data: rawptr, mode: Allocation_Mode,
size, alignment: int,
old_memory: rawptr, old_size: int, flags: u64) -> rawptr
Allocator :: type struct {
procedure: Allocator_Proc;
data: rawptr
}
Context :: type struct {
thread_ptr: rawptr
user_data: rawptr
user_index: int
allocator: Allocator
}
#thread_local context: Context
DEFAULT_ALIGNMENT :: 2*size_of(int)
__check_context :: proc() {
if context.allocator.procedure == null {
context.allocator = __default_allocator()
}
if context.thread_ptr == null {
// TODO(bill):
// context.thread_ptr = current_thread_pointer()
}
}
alloc :: proc(size: int) -> rawptr #inline { return alloc_align(size, DEFAULT_ALIGNMENT) }
alloc_align :: proc(size, alignment: int) -> rawptr #inline {
__check_context()
a := context.allocator
return a.procedure(a.data, Allocation_Mode.ALLOC, size, alignment, null, 0, 0)
}
dealloc :: proc(ptr: rawptr) #inline {
__check_context()
a := context.allocator
_ = a.procedure(a.data, Allocation_Mode.DEALLOC, 0, 0, ptr, 0, 0)
}
dealloc_all :: proc(ptr: rawptr) #inline {
__check_context()
a := context.allocator
_ = a.procedure(a.data, Allocation_Mode.DEALLOC_ALL, 0, 0, ptr, 0, 0)
}
resize :: proc(ptr: rawptr, old_size, new_size: int) -> rawptr #inline { return resize_align(ptr, old_size, new_size, DEFAULT_ALIGNMENT) }
resize_align :: proc(ptr: rawptr, old_size, new_size, alignment: int) -> rawptr #inline {
__check_context()
a := context.allocator
return a.procedure(a.data, Allocation_Mode.RESIZE, new_size, alignment, ptr, old_size, 0)
}
default_resize_align :: proc(old_memory: rawptr, old_size, new_size, alignment: int) -> rawptr {
if old_memory == null {
return alloc_align(new_size, alignment)
}
if new_size == 0 {
dealloc(old_memory)
return null
}
if new_size == old_size {
return old_memory
}
new_memory := alloc_align(new_size, alignment)
if new_memory == null {
return null
}
memory_copy(new_memory, old_memory, min(old_size, new_size));
dealloc(old_memory)
return new_memory
}
__default_allocator_proc :: proc(allocator_data: rawptr, mode: Allocation_Mode,
size, alignment: int,
old_memory: rawptr, old_size: int, flags: u64) -> rawptr {
using Allocation_Mode
match mode {
case ALLOC:
return heap_alloc(size)
case RESIZE:
return default_resize_align(old_memory, old_size, size, alignment)
case DEALLOC:
heap_dealloc(old_memory)
case DEALLOC_ALL:
// NOTE(bill): Does nothing
}
return null
}
__default_allocator :: proc() -> Allocator {
return Allocator{
__default_allocator_proc,
null,
}
}
__assert :: proc(msg: string) {
file_write(file_get_standard(File_Standard.ERROR), msg as []byte)
// TODO(bill): Which is better?
// __trap()
__debug_trap()
}
-430
View File
@@ -1,430 +0,0 @@
import (
"fmt.odin";
"atomics.odin";
"bits.odin";
"decimal.odin";
"hash.odin";
"math.odin";
"mem.odin";
"opengl.odin";
"os.odin";
"raw.odin";
"strconv.odin";
"strings.odin";
"sync.odin";
"sort.odin";
"types.odin";
"utf8.odin";
"utf16.odin";
/*
*/
)
general_stuff :: proc() {
// Complex numbers
a := 3 + 4i;
b: complex64 = 3 + 4i;
c: complex128 = 3 + 4i;
d := complex(2, 3);
e := a / conj(a);
fmt.println("(3+4i)/(3-4i) =", e);
fmt.println(real(e), "+", imag(e), "i");
// C-style variadic procedures
foreign __llvm_core {
// The variadic part allows for extra type checking too which C does not provide
c_printf :: proc(fmt: ^u8, #c_vararg args: ..any) -> i32 #link_name "printf" ---;
}
str := "%d\n\x00";
// c_printf(&str[0], i32(789456123));
Foo :: struct {
x: int;
y: f32;
z: string;
}
foo := Foo{123, 0.513, "A string"};
x, y, z := expand_to_tuple(foo);
fmt.println(x, y, z);
#assert(type_of(x) == int);
#assert(type_of(y) == f32);
#assert(type_of(z) == string);
// By default, all variables are zeroed
// This can be overridden with the "uninitialized value"
// This is similar to `nil` but applied to everything
undef_int: int = ---;
// Context system is now implemented using Implicit Parameter Passing (IPP)
// The previous implementation was Thread Local Storage (TLS)
// IPP has the advantage that it works on systems without TLS and that you can
// link the context to the stack frame and thus look at previous contexts
//
// It does mean that a pointer is implicitly passed procedures with the default
// Odin calling convention (#cc_odin)
// This can be overridden with something like #cc_contextless or #cc_c if performance
// is worried about
}
foreign_blocks :: proc() {
// See sys/windows.odin
}
default_arguments :: proc() {
hello :: proc(a: int = 9, b: int = 9) do fmt.printf("a is %d; b is %d\n", a, b);
fmt.println("\nTesting default arguments:");
hello(1, 2);
hello(1);
hello();
}
named_arguments :: proc() {
Colour :: enum {
Red,
Orange,
Yellow,
Green,
Blue,
Octarine,
};
using Colour;
make_character :: proc(name, catch_phrase: string, favourite_colour, least_favourite_colour: Colour) {
fmt.println();
fmt.printf("My name is %v and I like %v. %v\n", name, favourite_colour, catch_phrase);
}
make_character("Frank", "¡Ay, caramba!", Blue, Green);
// As the procedures have more and more parameters, it is very easy
// to get many of the arguments in the wrong order especialy if the
// types are the same
make_character("¡Ay, caramba!", "Frank", Green, Blue);
// Named arguments help to disambiguate this problem
make_character(catch_phrase = "¡Ay, caramba!", name = "Frank",
least_favourite_colour = Green, favourite_colour = Blue);
// The named arguments can be specifed in any order.
make_character(favourite_colour = Octarine, catch_phrase = "U wot m8!",
least_favourite_colour = Green, name = "Dennis");
// NOTE: You cannot mix named arguments with normal values
/*
make_character("Dennis",
favourite_colour = Octarine, catch_phrase = "U wot m8!",
least_favourite_colour = Green);
*/
// Named arguments can also aid with default arguments
numerous_things :: proc(s: string, a := 1, b := 2, c := 3.14,
d := "The Best String!", e := false, f := 10.3/3.1, g := false) {
g_str := g ? "true" : "false";
fmt.printf("How many?! %s: %v\n", s, g_str);
}
numerous_things("First");
numerous_things(s = "Second", g = true);
// Default values can be placed anywhere, not just at the end like in other languages
weird :: proc(pre: string, mid: int = 0, post: string) {
fmt.println(pre, mid, post);
}
weird("How many things", 42, "huh?");
weird(pre = "Prefix", post = "Pat");
}
default_return_values :: proc() {
foo :: proc(x: int) -> (first: string = "Hellope", second := "world!") {
match x {
case 0: return;
case 1: return "Goodbye";
case 2: return "Goodbye", "cruel world..";
case 3: return second = "cruel world..", first = "Goodbye";
}
return second = "my old friend.";
}
fmt.printf("%s %s\n", foo(0));
fmt.printf("%s %s\n", foo(1));
fmt.printf("%s %s\n", foo(2));
fmt.printf("%s %s\n", foo(3));
fmt.printf("%s %s\n", foo(4));
fmt.println();
// A more "real" example
Error :: enum {
None,
WhyTheNumberThree,
TenIsTooBig,
};
Entity :: struct {
name: string;
id: u32;
}
some_thing :: proc(input: int) -> (result: ^Entity = nil, err := Error.None) {
match {
case input == 3: return err = Error.WhyTheNumberThree;
case input >= 10: return err = Error.TenIsTooBig;
}
e := new(Entity);
e.id = u32(input);
return result = e;
}
}
call_location :: proc() {
amazing :: proc(n: int, using loc := #caller_location) {
fmt.printf("%s(%d:%d) just asked to do something amazing.\n",
fully_pathed_filename, line, column);
fmt.printf("Normal -> %d\n", n);
fmt.printf("Amazing -> %d\n", n+1);
fmt.println();
}
loc := #location(main);
fmt.println("`main` is located at", loc);
fmt.println("This line is located at", #location());
fmt.println();
amazing(3);
amazing(4, #location(call_location));
// See _preload.odin for the implementations of `assert` and `panic`
}
explicit_parametric_polymorphic_procedures :: proc() {
// This is how `new` is actually implemented, see _preload.odin
alloc_type :: proc(T: type) -> ^T do return cast(^T)alloc(size_of(T), align_of(T));
int_ptr := alloc_type(int);
defer free(int_ptr);
int_ptr^ = 137;
fmt.println(int_ptr, int_ptr^);
// Named arguments work too!
another_ptr := alloc_type(T = f32);
defer free(another_ptr);
add :: proc(T: type, args: ..T) -> T {
res: T;
for arg in args do res += arg;
return res;
}
fmt.println("add =", add(int, 1, 2, 3, 4, 5, 6));
swap :: proc(T: type, a, b: ^T) {
tmp := a^;
a^ = b^;
b^ = tmp;
}
a, b: int = 3, 4;
fmt.println("Pre-swap:", a, b);
swap(int, &a, &b);
fmt.println("Post-swap:", a, b);
a, b = b, a; // Or use this syntax for this silly example case
Vector2 :: struct {x, y: f32;};
{
// A more complicated example using subtyping
// Something like this could be used in a game
Entity :: struct {
using position: Vector2;
flags: u64;
id: u64;
derived: any;
}
Rock :: struct {
using entity: Entity;
heavy: bool;
}
Door :: struct {
using entity: Entity;
open: bool;
}
Monster :: struct {
using entity: Entity;
is_robot: bool;
is_zombie: bool;
}
new_entity :: proc(T: type, x, y: f32) -> ^T {
result := new(T);
result.derived = result^;
result.x = x;
result.y = y;
return result;
}
entities: [dynamic]^Entity;
rock := new_entity(Rock, 3, 5);
// Named arguments work too!
door := new_entity(T = Door, x = 3, y = 6);
// And named arguments can be any order
monster := new_entity(
y = 1,
x = 2,
T = Monster,
);
append(&entities, rock, door, monster);
fmt.println("Subtyping");
for entity in entities {
match e in entity.derived {
case Rock: fmt.println("Rock", e.x, e.y);
case Door: fmt.println("Door", e.x, e.y);
case Monster: fmt.println("Monster", e.x, e.y);
}
}
}
{
Entity :: struct {
using position: Vector2;
flags: u64;
id: u64;
variant: union { Rock, Door, Monster };
}
Rock :: struct {
using entity: ^Entity;
heavy: bool;
}
Door :: struct {
using entity: ^Entity;
open: bool;
}
Monster :: struct {
using entity: ^Entity;
is_robot: bool;
is_zombie: bool;
}
new_entity :: proc(T: type, x, y: f32) -> ^T {
result := new(Entity);
result.variant = T{entity = result};
result.x = x;
result.y = y;
return cast(^T)&result.variant;
}
entities: [dynamic]^Entity;
rock := new_entity(Rock, 3, 5);
// Named arguments work too!
door := new_entity(T = Door, x = 3, y = 6);
// And named arguments can be any order
monster := new_entity(
y = 1,
x = 2,
T = Monster,
);
append(&entities, rock, door, monster);
fmt.println("Union");
for entity in entities {
match e in entity.variant {
case Rock: fmt.println("Rock", e.x, e.y);
case Door: fmt.println("Door", e.x, e.y);
case Monster: fmt.println("Monster", e.x, e.y);
}
}
}
}
implicit_polymorphic_assignment :: proc() {
yep :: proc(p: proc(x: int)) {
p(123);
}
frank :: proc(x: $T) do fmt.println("frank ->", x);
tim :: proc(x, y: $T) do fmt.println("tim ->", x, y);
yep(frank);
// yep(tim);
}
main :: proc() {
/*
foo :: proc(x: i64, y: f32) do fmt.println("#1", x, y);
foo :: proc(x: type, y: f32) do fmt.println("#2", type_info(x), y);
foo :: proc(x: type) do fmt.println("#3", type_info(x));
f :: foo;
f(y = 3785.1546, x = 123);
f(x = int, y = 897.513);
f(x = f32);
general_stuff();
foreign_blocks();
default_arguments();
named_arguments();
default_return_values();
call_location();
explicit_parametric_polymorphic_procedures();
implicit_polymorphic_assignment();
// Command line argument(s)!
// -opt=0,1,2,3
*/
/*
program := "+ + * - /";
accumulator := 0;
for token in program {
match token {
case '+': accumulator += 1;
case '-': accumulator -= 1;
case '*': accumulator *= 2;
case '/': accumulator /= 2;
case: // Ignore everything else
}
}
fmt.printf("The program \"%s\" calculates the value %d\n",
program, accumulator);
*/
}
+3 -1
View File
@@ -275,6 +275,7 @@ struct BuildContext {
bool no_output_files;
bool no_crt;
bool no_entry_point;
bool no_thread_local;
bool use_lld;
bool vet;
bool vet_extra;
@@ -1255,7 +1256,7 @@ gb_internal void init_build_context(TargetMetrics *cross_target) {
gb_exit(1);
}
bc->optimization_level = gb_clamp(bc->optimization_level, 0, 3);
bc->optimization_level = gb_clamp(bc->optimization_level, -1, 2);
// ENFORCE DYNAMIC MAP CALLS
bc->dynamic_map_calls = true;
@@ -1369,6 +1370,7 @@ gb_internal char const *target_features_set_to_cstring(gbAllocator allocator, bo
gb_memmove(features + len, feature.text, feature.len);
len += feature.len;
if (with_quotes) features[len++] = '"';
i += 1;
}
features[len++] = 0;
+4 -1
View File
@@ -1143,9 +1143,12 @@ gb_internal void check_global_variable_decl(CheckerContext *ctx, Entity *&e, Ast
if (is_arch_wasm() && e->Variable.thread_local_model.len != 0) {
e->Variable.thread_local_model.len = 0;
// NOTE(bill): ignore this message for the time begin
// NOTE(bill): ignore this message for the time being
// error(e->token, "@(thread_local) is not supported for this target platform");
}
if(build_context.no_thread_local) {
e->Variable.thread_local_model.len = 0;
}
String context_name = str_lit("variable declaration");
+12 -5
View File
@@ -6800,14 +6800,21 @@ gb_internal ExprKind check_call_expr(CheckerContext *c, Operand *operand, Ast *c
operand->type = t_invalid;
}
} else {
gbString str = type_to_string(t);
defer (gb_string_free(str));
operand->mode = Addressing_Invalid;
isize arg_count = args.count;
switch (arg_count) {
case 0: error(call, "Missing argument in conversion to '%s'", str); break;
default: error(call, "Too many arguments in conversion to '%s'", str); break;
case 0:
{
gbString str = type_to_string(t);
error(call, "Missing argument in conversion to '%s'", str);
gb_string_free(str);
} break;
default:
{
gbString str = type_to_string(t);
error(call, "Too many arguments in conversion to '%s'", str);
gb_string_free(str);
} break;
case 1: {
Ast *arg = args[0];
if (arg->kind == Ast_FieldValue) {
+26 -2
View File
@@ -1184,6 +1184,8 @@ gb_internal void check_type_switch_stmt(CheckerContext *ctx, Ast *node, u32 mod_
return;
}
Ast *nil_seen = nullptr;
PtrSet<Type *> seen = {};
defer (ptr_set_destroy(&seen));
@@ -1194,6 +1196,7 @@ gb_internal void check_type_switch_stmt(CheckerContext *ctx, Ast *node, u32 mod_
}
ast_node(cc, CaseClause, stmt);
bool saw_nil = false;
// TODO(bill): Make robust
Type *bt = base_type(type_deref(x.type));
@@ -1202,6 +1205,25 @@ gb_internal void check_type_switch_stmt(CheckerContext *ctx, Ast *node, u32 mod_
if (type_expr != nullptr) { // Otherwise it's a default expression
Operand y = {};
check_expr_or_type(ctx, &y, type_expr);
if (is_operand_nil(y)) {
if (!type_has_nil(type_deref(x.type))) {
error(type_expr, "'nil' case is not allowed for the type '%s'", type_to_string(type_deref(x.type)));
continue;
}
saw_nil = true;
if (nil_seen) {
ERROR_BLOCK();
error(type_expr, "'nil' case has already been handled previously");
error_line("\t 'nil' was already previously seen at %s", token_pos_to_string(ast_token(nil_seen).pos));
} else {
nil_seen = type_expr;
}
case_type = y.type;
continue;
}
if (y.mode != Addressing_Type) {
gbString str = expr_to_string(type_expr);
error(type_expr, "Expected a type as a case, got %s", str);
@@ -1255,14 +1277,16 @@ gb_internal void check_type_switch_stmt(CheckerContext *ctx, Ast *node, u32 mod_
is_reference = true;
}
if (cc->list.count > 1) {
if (cc->list.count > 1 || saw_nil) {
case_type = nullptr;
}
if (case_type == nullptr) {
case_type = x.type;
}
if (switch_kind == TypeSwitch_Any) {
add_type_info_type(ctx, case_type);
if (!is_type_untyped(case_type)) {
add_type_info_type(ctx, case_type);
}
}
check_open_scope(ctx, stmt);
+8
View File
@@ -674,6 +674,10 @@ gb_internal void check_union_type(CheckerContext *ctx, Type *union_type, Ast *no
for_array(i, ut->variants) {
Ast *node = ut->variants[i];
Type *t = check_type_expr(ctx, node, nullptr);
if (union_type->Union.is_polymorphic && poly_operands == nullptr) {
// NOTE(bill): don't add any variants if this is this is an unspecialized polymorphic record
continue;
}
if (t != nullptr && t != t_invalid) {
bool ok = true;
t = default_type(t);
@@ -686,8 +690,12 @@ gb_internal void check_union_type(CheckerContext *ctx, Type *union_type, Ast *no
for_array(j, variants) {
if (are_types_identical(t, variants[j])) {
ok = false;
ERROR_BLOCK();
gbString str = type_to_string(t);
error(node, "Duplicate variant type '%s'", str);
if (j < ut->variants.count) {
error_line("\tPrevious found at %s\n", token_pos_to_string(ast_token(ut->variants[j]).pos));
}
gb_string_free(str);
break;
}
+12
View File
@@ -4447,6 +4447,14 @@ gb_internal DECL_ATTRIBUTE_PROC(foreign_import_decl_attribute) {
ac->foreign_import_priority_index = exact_value_to_i64(ev);
}
return true;
} else if (name == "extra_linker_flags") {
ExactValue ev = check_decl_attribute_value(c, value);
if (ev.kind != ExactValue_String) {
error(elem, "Expected a string value for '%.*s'", LIT(name));
} else {
ac->extra_linker_flags = ev.value_string;
}
return true;
}
return false;
}
@@ -4506,6 +4514,10 @@ gb_internal void check_add_foreign_import_decl(CheckerContext *ctx, Ast *decl) {
if (ac.foreign_import_priority_index != 0) {
e->LibraryName.priority_index = ac.foreign_import_priority_index;
}
String extra_linker_flags = string_trim_whitespace(ac.extra_linker_flags);
if (extra_linker_flags.len != 0) {
e->LibraryName.extra_linker_flags = extra_linker_flags;
}
if (has_asm_extension(fullpath)) {
if (build_context.metrics.arch != TargetArch_amd64 ||
+1
View File
@@ -121,6 +121,7 @@ struct AttributeContext {
bool set_cold : 1;
u32 optimization_mode; // ProcedureOptimizationMode
i64 foreign_import_priority_index;
String extra_linker_flags;
String objc_class;
String objc_name;
+5 -10
View File
@@ -60,7 +60,6 @@ gb_internal void virtual_memory_dealloc(MemoryBlock *block);
gb_internal void *arena_alloc(Arena *arena, isize min_size, isize alignment);
gb_internal void arena_free_all(Arena *arena);
gb_internal isize arena_align_forward_offset(Arena *arena, isize alignment) {
isize alignment_offset = 0;
isize ptr = cast(isize)(arena->curr_block->base + arena->curr_block->used);
@@ -75,7 +74,7 @@ gb_internal void *arena_alloc(Arena *arena, isize min_size, isize alignment) {
GB_ASSERT(gb_is_power_of_two(alignment));
mutex_lock(&arena->mutex);
isize size = 0;
if (arena->curr_block != nullptr) {
size = min_size + arena_align_forward_offset(arena, alignment);
@@ -390,15 +389,11 @@ gb_internal bool IS_ODIN_DEBUG(void);
gb_internal GB_ALLOCATOR_PROC(heap_allocator_proc);
gb_global gb_thread_local Arena heap_arena = {nullptr, DEFAULT_MINIMUM_BLOCK_SIZE};
gb_internal gbAllocator heap_allocator(void) {
if (IS_ODIN_DEBUG()) {
gbAllocator a;
a.proc = heap_allocator_proc;
a.data = nullptr;
return a;
}
return arena_allocator(&heap_arena);
gbAllocator a;
a.proc = heap_allocator_proc;
a.data = nullptr;
return a;
}
+7 -5
View File
@@ -915,18 +915,20 @@ gb_internal void odin_doc_update_entities(OdinDocWriter *w) {
auto entities = array_make<Entity *>(heap_allocator(), 0, w->entity_cache.count);
defer (array_free(&entities));
for (auto const &entry : w->entity_cache) {
array_add(&entities, entry.key);
for (u32 i = 0; i < w->entity_cache.count; i++) {
Entity *e = w->entity_cache.entries[i].key;
array_add(&entities, e);
}
for (Entity *e : entities) {
GB_ASSERT(e != nullptr);
OdinDocTypeIndex type_index = odin_doc_type(w, e->type);
gb_unused(type_index);
}
}
for (auto const &entry : w->entity_cache) {
Entity *e = entry.key;
OdinDocEntityIndex entity_index = entry.value;
for (u32 i = 0; i < w->entity_cache.count; i++) {
Entity *e = w->entity_cache.entries[i].key;
OdinDocEntityIndex entity_index = w->entity_cache.entries[i].value;
OdinDocTypeIndex type_index = odin_doc_type(w, e->type);
OdinDocEntityIndex foreign_library = 0;

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