Compare commits

...

303 Commits

Author SHA1 Message Date
Jeroen van Rijn 2250eb3e78 Enable core:fmt tests. 2024-05-10 17:05:18 +02:00
gingerBill aebb5a5178 Merge pull request #3506 from IllusionMan1212/windows-hid
core/sys/windows: added various procedures and constants for the hid library
2024-05-10 16:03:13 +01:00
gingerBill b4d0b1d17d Merge pull request #3544 from ntn9995/fix-parser-empty-or-no-pkg
Fix core:odin/parser crashing on empty and/or no package files
2024-05-10 16:01:16 +01:00
gingerBill 20d35acce1 Fix parser 2024-05-10 15:59:14 +01:00
gingerBill 6c4672c158 Merge pull request #3566 from Feoramund/fmt-refactor
Refactor `wprintf`
2024-05-10 15:56:26 +01:00
gingerBill 3095f46d7e Add runtime.Typeid_Bit_Field 2024-05-10 15:50:47 +01:00
ikarus 3add85e7a7 fix typo & free memory when skipping empty files 2024-05-10 14:51:09 +01:00
gingerBill ad5c9469d8 Fix #3522 2024-05-10 14:22:43 +01:00
gingerBill 710bb4369f Fix #3567 2024-05-10 13:55:15 +01:00
gingerBill 944fdd11f2 Keep -vet happy 2024-05-10 12:03:08 +01:00
gingerBill 07739b48ee Merge pull request #3565 from wschiefer/update-d3d12
Update d3d12 API
2024-05-10 12:00:39 +01:00
gingerBill b269fd00f0 Merge pull request #3560 from wschiefer/add-more-win32-console-apis
Add more win32 console APIs
2024-05-10 12:00:28 +01:00
gingerBill 04c391074d Merge pull request #3561 from wschiefer/update-dxgi
Adding DXGI 1.5 & 1.6 APIs
2024-05-10 12:00:11 +01:00
Feoramund fb2549a7da Extend test suite for fmt 2024-05-09 19:44:16 -04:00
Feoramund 11180e36ae Amend fmt documentation 2024-05-09 19:38:44 -04:00
Feoramund 8c111f1baf Fix %e printing incorrect precision
For example, `%.2e` with 30.56 as the value would produce `30.60e+01`,
as opposed to C's printf producing `30.6e+01`.
2024-05-09 19:37:17 -04:00
Feoramund 5e149d2cae Refactor wprintf
- Extracts common code between C and Python-like syntax into its own
   sub-procedures.
 - Fixes Python-like syntax from treating `}` as a verb.
 - Makes C-like syntax treat ' ' as a missing verb.
 - Fixes EXTRA arguments being formatted with options that were
   previously set, instead using default options now.
 - Makes error messaging more consistent between C and Python-like
   syntax.
 - Requires argument index to be specified immediately before the verb
   in C-like syntax, per the documentation, instead of after `%` _or_
   before the verb.
 - Tracks argument usage through a `bit_set`, allowing for reporting of
   unused arguments even when reordered.
 - Moves exit for C-like syntax if next byte is `%` to beginning of
   block instead of needlessly trying to parse through all options.
 - Pops next unused argument for unspecified formatters like `%i` or
   `{}`, instead of taking the argument after the last one used.
 - Fixes unspecified precision `%.f` from not setting precision to zero,
   per the documentation.
2024-05-09 19:28:04 -04:00
Jeroen van Rijn 41bd8cf714 Merge pull request #3563 from laytan/fix-duplicate-suggestions
fix duplicate suggestions and add missing newline
2024-05-09 20:20:18 +02:00
Jeroen van Rijn 1604f37cb8 Merge pull request #3564 from laytan/fix-some-vet-style-failures
fix a couple of `-vet-style` failures
2024-05-09 20:19:46 +02:00
Waldemar Schiefer 935865a978 Update d3d12 API 2024-05-09 19:46:02 +02:00
Laytan d7fdccb08c fix a couple of -vet-style failures after f54977336b 2024-05-09 19:39:48 +02:00
Laytan 98827c867d fix duplicate suggestions and add missing newline 2024-05-09 19:21:39 +02:00
Jeroen van Rijn 8a1e7bb6fb Merge pull request #3562 from Kelimion/bitset_to_enum_slice
Bitset to enum slice
2024-05-09 18:03:42 +02:00
Jeroen van Rijn 8f706a14f8 Add allocator param. 2024-05-09 17:58:48 +02:00
Jeroen van Rijn e5af98eabe Simplify bitset_to_enum_slice 2024-05-09 17:55:50 +02:00
Jeroen van Rijn 858c78b844 Pass new -vet-style check. 2024-05-09 17:47:19 +02:00
Jeroen van Rijn a61d8daec1 Add make version of bitset to slice. 2024-05-09 17:44:39 +02:00
Waldemar Schiefer cceac781e7 Add DXGI 1.5 & 1.6 APIs 2024-05-09 17:36:26 +02:00
Jeroen van Rijn 113feacbc7 Merge pull request #3559 from Kelimion/bitset_to_enum_slice
Add slice.enum_slice_to_bitset & slice.bitset_to_enum_slice
2024-05-09 17:21:04 +02:00
Jeroen van Rijn 7bcf3b1a0d Add slice.enum_slice_to_bitset & slice.bitset_to_enum_slice 2024-05-09 17:15:45 +02:00
gingerBill f54977336b With -vet-style, give suggestion of separating where clauses with a comma rather than '&&'
This improves the error messages
2024-05-09 15:56:00 +01:00
gingerBill b0f0e4d02a Add intrinsics type_bit_set_elem_type & type_bit_set_underlying_type 2024-05-09 15:47:09 +01:00
Waldemar Schiefer abcbb8b47a Add more win32 console APIs 2024-05-09 16:41:09 +02:00
gingerBill d85c8f0b2c Fix #3555 2024-05-09 10:58:57 +01:00
gingerBill 97e9c50d11 Fix #3556 2024-05-09 10:44:06 +01:00
gingerBill 9b75656400 Merge pull request #3308 from laytan/llvm-18
Support LLVM 18 (non-windows targets for now)
2024-05-08 13:39:57 +01:00
ikarus 60b6c798a5 tabs for indentation 2024-05-08 09:07:53 +01:00
IllusionMan1212 ad3675cdd6 core/sys/windows: added various procedures and constants related to the hid library 2024-05-07 21:18:25 +02:00
Laytan Laats 9d1db48549 remove is_packed bodge 2024-05-07 17:25:17 +02:00
Laytan b0b60fe7ed fix segfault in release builds of the compiler with clang++-18 2024-05-07 16:52:46 +02:00
Laytan 87b099b5aa fix merge conflict 2024-05-07 16:52:46 +02:00
Laytan Laats 77efdcd899 fix packed gep loads with wrong alignment 2024-05-07 16:52:46 +02:00
Laytan Laats 58c0abb98d revert wrong approach in fixing the load alignment 2024-05-07 16:52:46 +02:00
Laytan d93cc18dac fix packed gep loads with wrong alignment 2024-05-07 16:52:46 +02:00
Laytan Laats ecddf3b7f1 llvm-18: cleanup 2024-05-07 16:52:46 +02:00
Laytan Laats a3821615dc llvm-18: fix windows build error because of include 2024-05-07 16:52:46 +02:00
Laytan Laats f9a7d2bf04 llvm-18: enable static map calls on non amd64sysv targets 2024-05-07 16:52:46 +02:00
Laytan c219ca5b1b llvm-18: fix undocumented breaking change on i128 alignment 2024-05-07 16:52:46 +02:00
Laytan Laats f64e8ffd64 llvm-18: fix linking the compiler with clang-18 2024-05-07 16:52:46 +02:00
Laytan Laats e3e04ffa22 llvm-18: enable sroa and static map calls 2024-05-07 16:52:46 +02:00
Laytan 043dd98e91 fix demo out of bounds error 2024-05-07 16:52:46 +02:00
Laytan b91e7f5c51 llvm-18: linux (amd64) 2024-05-07 16:52:46 +02:00
Laytan Laats bb58926b7a llvm 18: general unix and darwin specifics 2024-05-07 16:52:46 +02:00
gingerBill 1d3c061add Fix typo 2024-05-07 15:52:13 +01:00
gingerBill de5ce90fa7 Add metadata to packed structs field accesses to state it is packed 2024-05-07 15:28:09 +01:00
gingerBill 3f7a369aa1 Check for specialization in typeid/T for parapoly records 2024-05-07 14:53:02 +01:00
gingerBill 94b4af5d36 Merge pull request #3540 from VictorSohier/handle-fixed-soa-arrays
Handle fixed soa arrays
2024-05-07 14:33:25 +01:00
gingerBill e378516011 Merge pull request #3547 from hnakamur/pass_size_to_epoll_create
sys/linux: Pass size to epoll_create
2024-05-07 14:33:18 +01:00
Jeroen van Rijn 2b1afa0762 Merge pull request #3521 from Su3h7aM/correct-clang++
Uses correct `clang++` binary for non-standard versions
2024-05-07 13:25:59 +02:00
gingerBill 0cec2d7827 Fix #3527 2024-05-07 11:51:06 +01:00
gingerBill 8d96c68528 Merge branch 'master' of https://github.com/odin-lang/Odin 2024-05-07 11:42:53 +01:00
gingerBill 0da6a3e214 Fix #3530 2024-05-07 11:42:48 +01:00
gingerBill 41d38bf964 Merge pull request #3468 from Feoramund/fix-pthread-cancel
Fix discrepancy with pthread cancelability state
2024-05-07 11:30:07 +01:00
gingerBill 05a1704898 Fix #3464 2024-05-07 11:24:21 +01:00
gingerBill 1818df786d Free memory for the error messages just in case 2024-05-07 11:21:12 +01:00
gingerBill b23f1dd5ff Merge neighbouring error messages with the same location 2024-05-07 11:19:16 +01:00
Jeroen van Rijn 96abe8627c Merge pull request #3551 from Feoramund/fix-max-bitfield-size
Fix lack of adding to `total_bit_size`
2024-05-07 00:47:15 +02:00
Feoramund 6ec7845249 Fix #3531
Individual `bit_field` size was not being added to the total size.
Error message was changed to be more explicit.
2024-05-06 18:30:04 -04:00
Victor Sohier 56b62996c3 Remove breakpoint hook 2024-05-06 18:20:20 -04:00
Victor Sohier 6cb0f5d8c5 Explicitly handle previously implicitly handled case 2024-05-06 18:04:35 -04:00
Hiroaki Nakamura df53fec828 sys/linux: Pass size to epoll_create
Without this fix, you can see a random size value is passed to
epoll_create by using strace.
2024-05-06 21:44:59 +09:00
ikarus 16fbfd0418 ignore empty files and errors on no package 2024-05-06 09:25:22 +01:00
Victor Sohier a9b18c1ec0 Formatting 2024-05-05 21:15:41 -04:00
Victor Sohier 8e4f9cb777 Fix: Fixed #soa arrays 2024-05-05 21:08:33 -04:00
gingerBill 15f7148eae Merge pull request #3526 from laytan/target-features
Improve target features support
2024-05-05 21:50:57 +01:00
gingerBill 1e5267c8e7 Merge pull request #3532 from laytan/fix-invalid-json
fix invalid JSON when an error does not have a position
2024-05-05 16:48:12 +01:00
gingerBill 96a4cecee5 Merge pull request #3536 from laytan/linux-fix-open-bits
sys/linux: fix open bits
2024-05-05 16:47:42 +01:00
gingerBill 8ba36ca85c Merge pull request #3535 from Feoramund/freebsd-arm64-minimal
Minimally support compiling Odin on FreeBSD arm64
2024-05-05 16:47:28 +01:00
Laytan Laats eaab17f8fb sys/linux: fix open bits 2024-05-05 14:16:21 +02:00
Feoramund 1165d65c94 Minimally support compiling Odin on FreeBSD arm64
This is enough to get Odin itself compiling and the demo running.
2024-05-05 07:26:45 -04:00
Laytan Laats ee818304f3 fix invalid JSON when an error does not have a position 2024-05-05 00:01:14 +02:00
Su3h7aM 30ff15e538 build: uses correct clang++ binary for non-standard versions 2024-05-03 13:20:51 -03:00
gingerBill 17a01a81d8 Merge pull request #3528 from laytan/fix-some-sync-issues
Fix some sync issues
2024-05-03 17:14:50 +01:00
gingerBill 595726e6c5 Merge pull request #3369 from joakin/fix-dynamic-library-from-vendor-on-linux
Fix vendor dynamic libraries not working on Linux
2024-05-03 17:14:32 +01:00
gingerBill fed03e896c Merge pull request #3525 from Feoramund/fix-more-error-racecond
Fix more race conditions in error reporting
2024-05-03 17:14:13 +01:00
gingerBill 2201f365a1 Allow #no_alias on multi-pointers 2024-05-03 14:51:02 +01:00
gingerBill f2505b096d Improve error message's suggestion for if !integer 2024-05-03 14:22:30 +01:00
gingerBill 242307dd44 Revert to old StringMap internal layout 2024-05-03 12:34:12 +01:00
Laytan Laats 8aab395c70 darwin: fix minimum os version check not being inclusive 2024-05-02 21:18:49 +02:00
Laytan Laats 8506e64345 sync: fix deadlock in one shot event 2024-05-02 20:49:29 +02:00
Laytan Laats 25f1d0906d compiler: improve target features support 2024-05-02 00:59:52 +02:00
Feoramund 67b786c738 Fix more race conditions in error reporting 2024-05-01 16:41:02 -04:00
Jeroen van Rijn fd582015fe Remove stray binary. 2024-04-30 20:58:36 +02:00
Jeroen van Rijn 58e12f0b17 Merge pull request #3520 from flysand7/sys-linux-oflags
[sys/linux]: Fix bit numbers for open flags
2024-04-30 20:40:24 +02:00
flysand7 9ffa4a4eb1 [sys/linux]: Fix bit numbers for open flags 2024-05-01 05:24:02 +11:00
gingerBill eb06cb5d23 Merge pull request #3518 from laytan/sysinfo-arm-additions
sys/info: add arm feature detection, fix Linux implementation, show more CPU info on Darwin
2024-04-30 12:48:48 +01:00
gingerBill 5c1201fa42 Fix #3459 2024-04-30 09:10:00 +01:00
Laytan Laats c0ca26ac17 sys/info: add missing @(private)'s 2024-04-30 00:24:09 +02:00
Laytan Laats b41395e3b4 sys/info: update doc.odin 2024-04-30 00:24:09 +02:00
Laytan Laats d40c207fde sys/info: retrieve better CPU description on Darwin
Previously either `ARM` or `ARM64`, now you get something like `Apple
M1`
2024-04-30 00:24:09 +02:00
Laytan Laats 8660718ebe sys/info: add feature detection for Darwin and Linux ARM 2024-04-30 00:24:09 +02:00
Laytan Laats 485afb011c sys/info: improve platform_linux
1. fix the `linux.open` call, passing `{ .RDONLY }` becomes `0x00000001`
   while `RDONLY` is supposed to be `0x00000000`
2. fix the case where `/etc/os-release` starts with `PRETTY_NAME`
   `strings.index` was used but was checking `> 0` while `0` is valid
3. remove unneccesary temporary allocations
4. simplify the logic
2024-04-30 00:24:09 +02:00
Laytan Laats 9e94e9dac1 sys/info: remove unneccesary build tags 2024-04-30 00:24:09 +02:00
Laytan Laats cebe6bd982 sys/unix: add sysctlbyname for darwin 2024-04-30 00:24:09 +02:00
Laytan Laats c58da76562 sys/darwin: fix sysctl and sysctlbyname syscalls 2024-04-30 00:24:09 +02:00
Laytan Laats e896efdaeb sys/info: add easy way of getting the MacOS version 2024-04-30 00:24:09 +02:00
gingerBill ff0973e0f5 Merge pull request #3513 from thetarnav/patch-2
Remove instrinsics and utf16 imports from os/os_js
2024-04-29 16:51:45 +01:00
Damian Tarnawski ae322739b5 Remove instrinsics and utf16 imports from os/os_js 2024-04-29 16:59:52 +02:00
gingerBill f6345d20f7 Merge pull request #3508 from Feoramund/fmt-alloc-print-reqres
Require results for non-buffered `print` procs
2024-04-29 12:04:58 +01:00
gingerBill 227aab8f39 Merge pull request #3507 from IllusionMan1212/glsl-fix
fix(linalg/glsl): incorrect quat by vector3 multiplication
2024-04-29 12:04:07 +01:00
gingerBill d6824ea607 Merge pull request #3505 from Feoramund/allow-disabled-init-procs
Allow `@(init)` procs to be `@(disabled)`
2024-04-29 12:03:54 +01:00
gingerBill 2f88ded81a Merge pull request #3504 from Feoramund/fix-racecond-error-va
Fix race condition in `error_va`
2024-04-29 12:03:26 +01:00
Feoramund bbebb4ad60 Fix unseen print call in demo 2024-04-28 17:20:52 -04:00
Feoramund 700f9c94bd Combine adjacent sbprint*/to_string calls
The `sbprint*` procs already return a string conversion.
2024-04-28 17:18:46 -04:00
Feoramund c712de0cd0 Require results for non-buffered print procs 2024-04-28 17:17:01 -04:00
IllusionMan1212 1f5f417116 fix(linalg/glsl): incorrect quat by vector3 multiplication 2024-04-28 21:44:34 +02:00
Feoramund a573161abd Allow @(init) procs to be @(disabled) 2024-04-28 14:42:04 -04:00
Feoramund f1c13d6bd8 Fix race condition in error_va
If the error count exceeded `MAX_ERROR_COLLECTOR_COUNT`, multiple
threads could print and exit simultaneously, causing a segfault.

This change moves the mutex lock back before the conditional.
2024-04-28 14:03:11 -04:00
gingerBill a37826e646 Merge pull request #3268 from olesya-wo/core-mem-tracking_allocator-improve
Improved statistics for core/mem/Tracking_Allocator
2024-04-28 16:01:01 +01:00
gingerBill 3b4169c903 Merge pull request #3495 from Feoramund/fix-rune-literal-error-double-reporting
Fix invalid rune literal reported twice
2024-04-28 15:22:05 +01:00
gingerBill 0eb97dba6e Merge pull request #3496 from laytan/allow-default-to-nil-allocator-on-wasm
wasm: allow `-default-to-nil-allocator`
2024-04-28 15:21:45 +01:00
gingerBill fa5e6d2d84 Merge pull request #3497 from laytan/fix-buddy-allocator-wrong-query-info
fix: buddy allocator wrong query info pointer
2024-04-28 15:21:35 +01:00
gingerBill 4668dafa2b Merge pull request #3498 from laytan/add-fprint-to-wasm
wasm: add the `fprint` procedures to `fmt`
2024-04-28 15:21:27 +01:00
Laytan Laats cc5faecced wasm: add the fprint procedures to fmt
This makes the `log` package work on wasm
2024-04-28 16:10:04 +02:00
Laytan Laats 0530f86a48 fix: buddy allocator wrong query info pointer 2024-04-28 16:09:03 +02:00
Laytan Laats 4fea5720a5 wasm: allow -default-to-nil-allocator 2024-04-28 16:05:41 +02:00
gingerBill 30cfdd73b0 Add extra asserts 2024-04-28 14:45:59 +01:00
gingerBill 950fd2d5ce Rename trace_linux.odin to trace_cpp.odin 2024-04-28 13:52:52 +01:00
gingerBill 74d75fb7fb Correct types on windows 2024-04-28 13:51:46 +01:00
gingerBill 7ee2c1084f Update doc.odin 2024-04-28 13:51:10 +01:00
gingerBill c0b7dd7da6 Remove need for allocator and MAX_FRAMES in trace.frames 2024-04-28 13:05:19 +01:00
gingerBill be09584ea5 Increase MAX_FRAMES 2024-04-28 12:56:53 +01:00
gingerBill 5ac8e8f9fd Add doc.odin 2024-04-28 12:52:02 +01:00
gingerBill 2eea06fc73 Set in_resolve for linux 2024-04-28 12:51:10 +01:00
gingerBill 0fa269811a Change layout of Frame_Location 2024-04-28 12:49:17 +01:00
gingerBill 6c185a5dca Add core:debug/trace for Linux 2024-04-28 12:43:27 +01:00
gingerBill f428f26c8e Merge branch 'master' of https://github.com/odin-lang/Odin 2024-04-28 12:05:21 +01:00
gingerBill 44c9b988bb Add default debug/trace to do nothing 2024-04-28 12:05:15 +01:00
Feoramund 29987c20c0 Fix invalid rune literal reported twice
The tokenizer and the parser were reporting it in different positions.
This way, they'll report in the same spot.
2024-04-28 07:02:21 -04:00
gingerBill 51d4dde63c Merge pull request #3494 from Feoramund/fix-3481
Clear unused `global_error_collector.curr_error`
2024-04-28 11:59:18 +01:00
gingerBill 362aa82f59 Begin work on core:debug/trace 2024-04-28 11:58:16 +01:00
Feoramund ebfbe4d260 Clear unused global_error_collector.curr_error
This should cleanly prevent acknowledging duplicate errors on the same
position as seems to be the intent based on the prior `else if`
condition.
2024-04-28 06:44:28 -04:00
gingerBill e71cf96bbc Keep -vet happy 2024-04-28 11:35:51 +01:00
gingerBill 383c17e842 Merge branch 'master' of https://github.com/odin-lang/Odin 2024-04-28 11:31:55 +01:00
gingerBill 00b1a41540 Add stack frame procedures for core:sys/windows 2024-04-28 11:31:50 +01:00
Jeroen van Rijn 8fd318ea7a Merge pull request #3492 from DreepyYunky/Add-SetMenu
Add `SetMenu()`
2024-04-27 20:04:15 +02:00
Yunky 1deb53cddb Add SetMenu 2024-04-27 18:58:46 +01:00
gingerBill 05b5b8503d Merge pull request #3491 from Feoramund/fix-json-omitempty
Fix `omitempty` in `json.marshal`
2024-04-27 15:32:21 +01:00
gingerBill 5e1b376e22 Disallow for x in bitset_or_map if x is a variable that matches the "key" 2024-04-27 10:34:17 +01:00
Feoramund 309a770cbf Fix omitempty in json.marshal 2024-04-27 05:24:59 -04:00
gingerBill 393e4a9db6 Generalize Odin call-based "iterators" to work with more than 2-values: for x, y, z, w in iterate(&it)
It has an artificial limitation of 100 values because if you need for than that, you're doing something wrong.
2024-04-27 09:53:02 +01:00
gingerBill efae99971b Fix missing _ = 2024-04-27 09:19:50 +01:00
gingerBill 4454849252 Add attributes to procedures in text/scanner 2024-04-27 09:16:36 +01:00
gingerBill c752d0b541 Fix printing of big endian integers in a bit_field 2024-04-27 09:16:18 +01:00
gingerBill 5969796fbf Merge pull request #3490 from odin-lang/new-string-map
Change layout of compiler hash map types
2024-04-27 09:03:05 +01:00
gingerBill 6520794764 Fix wrong allocator usage 2024-04-27 08:50:05 +01:00
gingerBill 9d3f835e31 Merge pull request #3462 from Yawning/feature/math-sys-rand
core:math/rand: Use `crypto.rand_bytes()` for the system RNG
2024-04-27 08:43:02 +01:00
gingerBill 70aa2ff90a Merge pull request #3489 from Feoramund/fix-build-freebsd
Fix wrong llvm-config in build script for FreeBSD
2024-04-27 08:40:23 +01:00
gingerBill a60a7f64b9 Merge pull request #3483 from laytan/negative-improvements
improve some Negative_Read/Negative_Write logic
2024-04-27 08:38:29 +01:00
gingerBill 2368014d06 Merge pull request #3486 from Feoramund/fix-chan-memleak
Fix memory leak in `sync/chan`
2024-04-27 08:37:52 +01:00
Feoramund 92402a75f6 Fix wrong llvm-config in build script for FreeBSD 2024-04-26 18:40:59 -04:00
gingerBill 4bea5dbac1 Correct map usage 2024-04-26 15:09:08 +01:00
gingerBill 2b26384b89 Implement dumb PtrMap 2024-04-26 15:04:46 +01:00
gingerBill c685b404ea Implement dumb StringMap 2024-04-26 14:15:22 +01:00
gingerBill a3e77dcc3b Minor clean up 2024-04-26 13:25:08 +01:00
gingerBill 7305478261 Minor changes 2024-04-26 13:12:23 +01:00
gingerBill 94e0707456 Fix minor bug 2024-04-26 13:12:15 +01:00
Feoramund f95bb77f72 Fix memory leak in sync/chan 2024-04-26 05:19:52 -04:00
Laytan Laats d3bd1c2110 improve some Negative_Read/Negative_Write logic
Returns the actual error if one is set, instead of swallowing it for the
less descriptive negative error.

Also fixes a out-of-bounds slice error in `bufio.writer_write` because
it wasn't checking the returned `m`.
2024-04-25 19:08:48 +02:00
gingerBill f745fff640 Merge pull request #3467 from laytan/clang-18-linking-backport
fix linking with clang-18
2024-04-25 16:09:17 +01:00
gingerBill 8f4e3b552e Merge pull request #3478 from laytan/fix-send-event-proc-signature
sys/darwin/foundation: fix Application->sendEvent signature
2024-04-25 12:32:10 +01:00
gingerBill 1ea353dbf7 Merge pull request #3479 from laytan/support-0-sized-return-arm64-abi
compiler: support returning 0 sized types in arm64 abi
2024-04-25 12:31:56 +01:00
gingerBill e3d41f0a9e Merge branch 'master' of https://github.com/odin-lang/Odin 2024-04-24 20:55:22 +01:00
gingerBill 3b53c99576 Improve support for big-endian bit_fields 2024-04-24 20:55:18 +01:00
Laytan Laats a4cec2e8b8 sys/darwin/foundation: fix Application->sendEvent signature 2024-04-24 19:51:08 +02:00
Laytan Laats e8c5bb4629 compiler: support returning 0 sized types in arm64 abi 2024-04-24 19:50:39 +02:00
gingerBill c72a269b7c Merge pull request #3477 from laytan/fix-macos-versions
ci: fix macOS versions
2024-04-24 17:48:45 +01:00
gingerBill 94d35d9918 Disallow mixing endian types within a bit_field 2024-04-24 17:31:31 +01:00
Laytan Laats 448827c0e4 ci: fix macOS versions 2024-04-24 18:29:28 +02:00
gingerBill 04278cd654 Remove line info in message with -json-errors 2024-04-24 17:13:53 +01:00
gingerBill 15942fbf25 Merge branch 'master' of https://github.com/odin-lang/Odin 2024-04-24 17:01:16 +01:00
gingerBill 214537b420 Improve codegen for bit_field [N]T compound literals 2024-04-24 17:01:09 +01:00
gingerBill c330e5b5c1 Improve codegen for bit_field compound literals with an integer backing 2024-04-24 14:46:34 +01:00
gingerBill ec5a84a537 Improve code generation for loading bit_field fields 2024-04-24 13:10:58 +01:00
gingerBill 5b6c96cd18 Merge pull request #3466 from laytan/fix-proc-args-debug-info
fix direct proc args debug info
2024-04-23 13:07:25 +01:00
Yawning Angel e2fa9be7e2 core/math/rand: Use core:crypto for the system RNG
This removes some code duplication and expands support for the system
RNG to all targets that `core:crypto` supports.
2024-04-23 11:47:43 +09:00
Yawning Angel a6eb64df6c core/crypto: Add a HAS_RAND_BYTES constant 2024-04-23 11:47:43 +09:00
Feoramund 9cdb7b2584 Fix discrepancy with pthread cancelability state 2024-04-22 17:16:33 -04:00
Laytan d1a1e8f646 fix linking with clang-18
Because we currently just use the clang from the user's path linking
suddenly breaks when the user updates their system clang to 18 with an
error about an unknown option -arch.

I had already fixed it for my LLVM 18 PR but it seems like a good idea
to get this in already to avoid that breakage (had a few people come to
the Discord with it and an issue).

This fixes #3461
2024-04-22 20:58:54 +02:00
gingerBill 75fcd50b9a Merge branch 'master' of https://github.com/odin-lang/Odin 2024-04-22 18:41:51 +01:00
gingerBill c6a446fe87 Add check for build. and run. typos 2024-04-22 18:41:48 +01:00
Laytan Laats 90369b669b fix direct proc args debug info 2024-04-22 19:36:24 +02:00
gingerBill f5719ae47c Merge pull request #3465 from thetarnav/fmt-printfln-js
Add printfln and eprintfln procs to fmt_js.odin
2024-04-22 16:01:36 +01:00
Damian Tarnawski 2a70faca14 Add printfln and eprintfln functions to fmt_js.odin 2024-04-21 22:37:04 +02:00
Yawning Angel 902e877467 repo: Add more test binaries to .gitignore 2024-04-21 21:22:05 +09:00
Jeroen van Rijn caa8863c97 Merge pull request #3455 from Hyrtwol/normalize-path
Normalize ODIN_ROOT path
2024-04-20 12:08:12 +02:00
Thomas la Cour ebb1a07dd0 spelling 2024-04-20 09:37:30 +02:00
Thomas la Cour 0a16f7a6f1 normalize_path 2024-04-20 09:37:30 +02:00
gingerBill 3620e62ff7 Merge pull request #3453 from GoNZooo/gonz.add-nosignal-broken-pipe
fix(net/linux): add `NOSIGNAL` to `send` options
2024-04-19 14:05:49 +01:00
gingerBill d84b29866f Merge pull request #3447 from matias-eduardo/fix-soa-hash-formatting
fix #soa '%#v' formatting
2024-04-19 14:05:15 +01:00
Rickard Andersson 68f663ea85 fix(net): fix return type for send_tcp
Was `.Connection_Closed` but this is only inferrable if our return type
is not a sub-union of another.
2024-04-19 15:39:04 +03:00
Rickard Andersson efc84cd390 docs(net): add comment about EPIPE -> Connection_Closed 2024-04-19 15:37:20 +03:00
gingerBill ea49331799 Merge branch 'master' of https://github.com/odin-lang/Odin 2024-04-19 13:33:02 +01:00
gingerBill 059175de3b Do not print column of a runtime.Source_Code_Location if the column == 0 2024-04-19 13:32:55 +01:00
Rickard Andersson 7b95562827 feat(net): turn EPIPE into Connection_Closed 2024-04-19 15:29:28 +03:00
Rickard Andersson c44f618b7d fix(net): add NOSIGNAL to send options
This is a better default than not having it, since it turns errors that
would be signals into error values instead. We could take these as
options but given that we currently don't I think this at the very least
improves on the status quo.
2024-04-19 15:17:21 +03:00
joakin 60ef4fda4d Recognize dynamic library names like libraylib.so.5.0.0 2024-04-19 13:35:53 +02:00
Jeroen van Rijn 1b143b9fa3 Merge pull request #3452 from mgavioli/Fix_i18n_get-section_number_default
Fix #3451 - `core:text/i18n` default `number` value in `get_*_section`
2024-04-19 09:24:17 +02:00
Maurizio M. Gavioli ec7e75a57f Fix #3451 - core:text/i18n default number value in get_*_section proc.
In the to procs `get_single_section()` and `get_by_section()` the `number` parameter should have a default of `1` rather than `0`.

See the issue for more details.
2024-04-19 08:23:28 +02:00
gingerBill 20223345a4 Return partial reads 2024-04-19 00:33:31 +01:00
gingerBill 3812d5e002 Only override the comma value on *_init if it is "invalid" 2024-04-19 00:19:02 +01:00
gingerBill 2416380f34 Enforce as global constant 2024-04-18 12:56:18 +01:00
gingerBill 5200e3fe7a Set __$ti- stuff to be private linkage 2024-04-18 12:45:20 +01:00
gingerBill b72d49ceb5 Set linkage to private for __$type_info_data 2024-04-18 12:41:05 +01:00
gingerBill 689982a38d Force runtime type table to be in rodata/rdata section 2024-04-18 12:22:41 +01:00
gingerBill 334e08c750 Update numerous package declaration names 2024-04-18 12:17:39 +01:00
gingerBill f84a092977 Fix typo. 2024-04-18 11:28:46 +01:00
gingerBill aad41fc762 Fix #3445 2024-04-18 11:27:42 +01:00
gingerBill ece78d22d2 Add -no-type-assert and ODIN_NO_TYPE_ASSERT 2024-04-18 11:22:31 +01:00
gingerBill 5c52f3cf2f Add ODIN_NO_BOUNDS_CHECK 2024-04-18 11:13:44 +01:00
gingerBill 889cd5461c Add @(optimization_mode="size") to runtime.print_type 2024-04-18 11:00:47 +01:00
gingerBill 6127339c56 Add #force_no_inline to many of the runtime print procedures 2024-04-18 10:59:02 +01:00
Matias Fernandez e296b050ee fix #soa '%#v' formatting 2024-04-17 22:54:30 -04:00
gingerBill 9bbe26f80f Merge pull request #3441 from Chickenkeeper/copy-from-string-docs-fix
Correct `copy_from_string` docs
2024-04-17 13:50:08 +01:00
gingerBill 7cd2bc26f4 Clear error message on collisions with using on struct fields 2024-04-17 13:31:32 +01:00
Chris 6dcf38b85b Correct copy_from_string docs 2024-04-16 21:36:54 +01:00
gingerBill a61ae7c861 Fix #3427 2024-04-16 13:31:49 +01:00
gingerBill 8a0f9ae108 Print to string buffer before printing errors 2024-04-16 13:15:23 +01:00
Jeroen van Rijn fd1eb17771 Merge pull request #3437 from Feoramund/fisher-yates-shuffle
Implement Fisher-Yates shuffle
2024-04-16 10:28:35 +02:00
Feoramund 3e449e93dd Implement Fisher-Yates shuffle 2024-04-15 17:07:05 -04:00
Jeroen van Rijn 436c5dc40c Merge pull request #3436 from karl-zylinski/fix-vet-unused-in-dynlib
Fix for dynlib:initialize_symbols not passing -vet-unused
2024-04-15 21:55:12 +02:00
Karl Zylinski 0729f2b4fb Fix for dynlib:initialize_symbols not passing -vet-unused 2024-04-15 21:26:30 +02:00
gingerBill 76229cabfa Fix typo 2024-04-15 15:44:03 +01:00
gingerBill a7e492e2c2 Merge branch 'master' of https://github.com/odin-lang/Odin 2024-04-15 15:40:43 +01:00
gingerBill 69db9c6390 Add loads of nil checks when doing s.builder 2024-04-15 15:40:32 +01:00
Jeroen van Rijn 6def86bb4b Merge pull request #3431 from Feoramund/fix-randprime-2ndmsb
Fix `big.internal_random_prime` with `Second_MSB_On`
2024-04-15 16:30:16 +02:00
gingerBill 38c1fd5824 Keep -vet happy 2024-04-15 14:35:51 +01:00
gingerBill d5e6d722d3 Merge pull request #3045 from laytan/cbor
encoding/cbor
2024-04-15 14:28:52 +01:00
gingerBill 2af777b6cb Merge pull request #3419 from puzzleddev/fixup-d3d12-thiscall-affected-methods
Fixup vendor/d3d12 calling convention mistakes
2024-04-15 13:19:19 +01:00
gingerBill 5bda2546f7 Merge branch 'master' of https://github.com/odin-lang/Odin 2024-04-15 12:43:50 +01:00
gingerBill 36644a3c09 Add template specialization for compared against "" with String internally 2024-04-15 12:43:45 +01:00
gingerBill 7e582dd671 Add basic suggestion to missing package name 2024-04-15 12:43:27 +01:00
gingerBill c7ac28f6a1 Merge pull request #3434 from flysand7/sys-linux-fixes
[sys/linux]: Fix syscall calls for open and fstat
2024-04-15 12:28:59 +01:00
flysand7 4bfa1ea76c [sys/linux]: Fix syscall calls for open and fstat 2024-04-15 22:16:03 +11:00
gingerBill 758ace844c Merge pull request #3428 from mgavioli/fix_core_docs
Fix the format of some `doc.odin` files of the `core` library…
2024-04-15 12:12:20 +01:00
gingerBill b9d7b8d616 Merge pull request #3429 from flysand7/sys-linux-wait4-fix
[sys/linux]: Fix signature on wait4 syscall
2024-04-15 11:51:51 +01:00
Feoramund a294f067a9 Fix big.internal_random_prime with Second_MSB_On 2024-04-15 05:49:15 -04:00
gingerBill 2e29687cee Fix #3425 2024-04-15 10:28:14 +01:00
flysand7 a0e25be196 [sys/linux]: Fix signature on wait4 syscall 2024-04-15 11:16:52 +11:00
Maurizio M. Gavioli a0cff82320 Fix the format of some doc.odin files of the core library which did not made into the documentation.
`c/frontend/tokenizer`:
   add proper "Example:" header to demo example code,
   removed empty lines.
`container/bit_array`:
   moved comment before package;
   aligned narrative lines to left margin;
   converted case lines into bulleted lines ("- ");
   converted individual examples to single-tab-indented preformatted text.
`dynlib`:
   removed "//+build ignore" line;
   added newline at EOF.
`image/netpmb`:
   converted indented lines of "Reading", "Wrting" and "Some syntax..." into bulleted lists;
   "Formats" indented lines kept as they are as the preformatted text seems relevant to keep the alignments;
   doubly indented lines kept as single-indented to keep them different (as the format does not allow for two-level bulleted lists);
   removed empy lines.
`os/os2`:	WIP, not modified
`sys/info`:
   removed "//+build ignore" line;
   converted tab-indented initial description into regular left-margin comment;
   moved uncommented sample code within the doc comment as an "Example:";
   moved simple- and double-tabbed separate comments with sample Windows and macOS outputs within the doc comment as bulleted headlines with preformatted output listings;
   removed now empty comments and blank lines after the package line.
`text/i18n`:
   removed "//+build ignore" line;
   moved the pacakge line at the end;
   de-indented the tab-indented introductory narrative;
   moved sample code comments into the doc comment as tab-indented code with a proper "Example:" heading;
   removed "```" MD attempts at code formatting.
`text/table`:
   unindented the comment lines of a descriptive kind;
   headlines of major subdivisions are marked as bold;
   kept code samples as tab-indented preformatted text (as there are several of them, the standard "Example:" and "Output:" headings cannot be used) removing the "```" MD attempts at code formatting;
   removed in-between blank lines.
2024-04-14 17:18:08 +02:00
Jeroen van Rijn d510d5e49f Merge pull request #3426 from Kelimion/crlf
Tell Git to always treat Makefile and build_odin.sh as Nix EOL
2024-04-14 16:51:42 +02:00
Jeroen van Rijn 9591eb2ed5 Tell Git to always treat Makefile and build_odin.sh as Nix EOL 2024-04-14 16:41:10 +02:00
gingerBill 21969fec61 Merge pull request #3421 from laytan/add-odin-root-command
add `odin root` command
2024-04-13 22:44:53 +01:00
gingerBill 6ee818b394 Merge pull request #3422 from wrapperup/add-dxc-vendor-docs
Add dxc to all_vendor.odin, fix dxc build on freebsd/openbsd
2024-04-13 22:42:03 +01:00
wrapperup b22e43c335 add freebsd, openbsd to dxcdef_unix.odin 2024-04-13 15:27:02 -04:00
gingerBill 6dc9fdb718 Merge pull request #3420 from laytan/fix-wasm-runtime-store-string-with-unicode
fix wasm runtime.js storeString to support Unicode
2024-04-13 20:15:41 +01:00
wrapperup f252084b1f add dxc to all_vendor.odin 2024-04-13 15:05:51 -04:00
Laytan Laats 9f97056c14 add 'odin root' command 2024-04-13 20:08:57 +02:00
Laytan Laats d2ca91b830 fix wasm runtime.js storeString to support Unicode 2024-04-13 19:34:13 +02:00
gingerBill 59705035f9 Merge pull request #3365 from yay/core-foundation-security
Core Foundation and Security core packages.
2024-04-13 10:56:24 +01:00
Vitalii Kravchenko befb0f7868 Core Foundation and Security vendor libraries. 2024-04-13 00:11:42 +01:00
tim4242 d5bb67e9e6 Fixup vendor/d3d12 calling convention mistakes 2024-04-12 20:47:02 +02:00
Jeroen van Rijn f086b4710a Merge pull request #3418 from blob1807/add-url-fragment-to-split-join-url
core:net Add support for URL fragments
2024-04-12 17:19:29 +02:00
blob1807 a4d16e97a1 Fix CI's parser 2024-04-13 01:14:55 +10:00
blob1807 6348b56c8b Move rounded tests 2024-04-13 00:57:36 +10:00
blob1807 2d1260bec9 uniformity change
small change to check things uniform
2024-04-13 00:47:49 +10:00
blob1807 c753711d86 Added support for URL fragments
Added support for a URL's fragment/anchor to `split_url` & `join_url` in `core:net` plus 4 new tests to cover it.
2024-04-13 00:39:32 +10:00
gingerBill 5726b7d954 Remove warning on clang 2024-04-12 14:51:22 +01:00
gingerBill 4240e0025e Improve scalar -> array assignment when the scalar is constant in LLVM 2024-04-12 14:20:46 +01:00
gingerBill caa344c88d Simplify scalar -> array conversions in LLVM to use a loop after a certain size 2024-04-12 14:05:36 +01:00
gingerBill 46b9bd8c0e Improve error messages for switch and for r-values with a suggestion 2024-04-12 13:35:14 +01:00
gingerBill 3426af2d6c Fix #3415 2024-04-12 12:33:25 +01:00
gingerBill efc3f9916e Fix #3414 2024-04-12 12:30:16 +01:00
Jeroen van Rijn 95bc1892f5 Merge pull request #3413 from hodgka/trig_funcs_doc
Added docs for trig function procedure groups
2024-04-11 19:02:41 +02:00
alec hodgkinson 4cdadeedc3 Added docs for trig function procedure groups 2024-04-11 09:55:43 -07:00
gingerBill cb0a57eaa9 Add -target:freestanding_amd64_win64 2024-04-11 17:18:51 +01:00
olesya-wo 51a4d97f03 type conversion fix 2024-03-12 18:25:54 +03:00
olesya-wo d979129a50 Naming and type changes 2024-03-12 16:32:17 +03:00
olesya-wo 9045c9ed0c Improved statistics for core/mem/Tracking_Allocator 2024-03-12 13:53:31 +03:00
Laytan Laats 9a5f3fed8c encoding/cbor: fix conflict 2024-03-04 17:29:30 +01:00
Laytan Laats 04bd3cc525 encoding/cbor: rename diagnose to to_diagnostic_format to be clearer 2024-03-04 17:26:19 +01:00
Laytan 9fc8587e2c encoding/cbor: untouch net/common.odin 2024-03-04 17:26:19 +01:00
Laytan 2a39c60fe4 encoding/cbor: respect default to panic allocator 2024-03-04 17:26:19 +01:00
Laytan b11d839fb6 encoding/cbor: make temp allocations more explicit 2024-03-04 17:26:19 +01:00
Laytan 0076c07076 encoding/cbor: core -> base 2024-03-04 17:26:19 +01:00
Laytan a664d9804f encoding/cbor: remove usage of incl_elem and excl_elem 2024-03-04 17:26:19 +01:00
Laytan Laats c4e45d509a encoding/cbor: adhere to new quaternion rules of master 2024-03-04 17:26:19 +01:00
Laytan Laats c1cf6c1a95 encoding/cbor: add general docs and example 2024-03-04 17:26:19 +01:00
Laytan Laats 317931a3c5 encoding/cbor: deterministically store bit sets as big endian 2024-03-04 17:26:19 +01:00
Laytan Laats 759d095548 encoding/cbor: ignore struct fields with cbor:"-" 2024-03-04 17:26:19 +01:00
Laytan Laats 7854aa22d9 encoding/cbor: fix unused import 2024-03-04 17:26:19 +01:00
Laytan Laats 72d5b87b52 encoding/cbor: clean 2024-03-04 17:26:19 +01:00
Laytan Laats 154e0d41c6 encoding/cbor: fix wrong allocator bug 2024-03-04 17:26:19 +01:00
Laytan Laats 3fccc77829 encoding/cbor: clean and fixup some allocations 2024-03-04 17:26:19 +01:00
Laytan Laats 85f1a60cf3 encoding/cbor: cleanup comments about tags 2024-03-04 17:26:19 +01:00
Laytan Laats cb8bb8bfd8 encoding/cbor: cleanup default temp allocator 2024-03-04 17:26:19 +01:00
Laytan Laats 46b58ad48d encoding/cbor: don't zero bytes we are going to write/read to/from anyway 2024-03-04 17:26:19 +01:00
Laytan Laats 7283b5e75c encoding/cbor: minor things 2024-03-04 17:26:19 +01:00
Laytan Laats 21e6e28a3a encoding/cbor: add decoder flags and protect from malicious untrusted input 2024-03-04 17:26:19 +01:00
Laytan Laats d77ae9abab encoding/cbor: fully support marshal/unmarshal of unions 2024-03-04 17:26:19 +01:00
Laytan Laats 363769d4d3 encoding/cbor: cleanup base64 tag 2024-03-04 17:26:19 +01:00
Laytan Laats b6c47e7963 encoding/base64: add decode_into, add tests 2024-03-04 17:26:17 +01:00
Laytan Laats 5533a327eb encoding/cbor: initial package implementation 2024-03-04 17:25:34 +01:00
187 changed files with 11899 additions and 2127 deletions
+5 -1
View File
@@ -1,2 +1,6 @@
*.odin linguist-language=Odin
* text=auto
* text=auto
# These files must always have *nix line-endings
Makefile text eol=lf
*.sh text eol=lf
+1 -1
View File
@@ -62,7 +62,7 @@ jobs:
timeout-minutes: 10
build_macOS:
name: MacOS Build, Check, and Test
runs-on: macos-latest
runs-on: macos-13
steps:
- uses: actions/checkout@v1
- name: Download LLVM, and setup PATH
+1 -1
View File
@@ -77,7 +77,7 @@ jobs:
build_macos:
name: MacOS Build
if: github.repository == 'odin-lang/Odin'
runs-on: macos-latest
runs-on: macos-13
steps:
- uses: actions/checkout@v1
- name: Download LLVM and setup PATH
+7 -1
View File
@@ -27,6 +27,8 @@ tests/documentation/all.odin-doc
tests/internal/test_map
tests/internal/test_pow
tests/internal/test_rtti
tests/core/test_base64
tests/core/test_cbor
tests/core/test_core_compress
tests/core/test_core_container
tests/core/test_core_filepath
@@ -40,8 +42,10 @@ tests/core/test_core_net
tests/core/test_core_os_exit
tests/core/test_core_reflect
tests/core/test_core_strings
tests/core/test_core_time
tests/core/test_crypto
tests/core/test_hash
tests/core/test_hex
tests/core/test_hxa
tests/core/test_json
tests/core/test_linalg_glsl_math
@@ -318,4 +322,6 @@ build.sh
!core/debug/
# RAD debugger project file
*.raddbg
*.raddbg
misc/featuregen/featuregen
+19 -10
View File
@@ -169,15 +169,18 @@ type_has_nil :: proc($T: typeid) -> bool ---
type_is_specialization_of :: proc($T, $S: typeid) -> bool ---
type_is_variant_of :: proc($U, $V: typeid) -> bool where type_is_union(U) ---
type_union_tag_type :: proc($T: typeid) -> typeid where type_is_union(T) ---
type_union_tag_offset :: proc($T: typeid) -> uintptr where type_is_union(T) ---
type_union_base_tag_value :: proc($T: typeid) -> int where type_is_union(U) ---
type_union_variant_count :: proc($T: typeid) -> int where type_is_union(T) ---
type_variant_type_of :: proc($T: typeid, $index: int) -> typeid where type_is_union(T) ---
type_variant_index_of :: proc($U, $V: typeid) -> int where type_is_union(U) ---
type_is_variant_of :: proc($U, $V: typeid) -> bool where type_is_union(U) ---
type_union_tag_type :: proc($T: typeid) -> typeid where type_is_union(T) ---
type_union_tag_offset :: proc($T: typeid) -> uintptr where type_is_union(T) ---
type_union_base_tag_value :: proc($T: typeid) -> int where type_is_union(U) ---
type_union_variant_count :: proc($T: typeid) -> int where type_is_union(T) ---
type_variant_type_of :: proc($T: typeid, $index: int) -> typeid where type_is_union(T) ---
type_variant_index_of :: proc($U, $V: typeid) -> int where type_is_union(U) ---
type_has_field :: proc($T: typeid, $name: string) -> bool ---
type_bit_set_elem_type :: proc($T: typeid) -> typeid where type_is_bit_set(T) ---
type_bit_set_underlying_type :: proc($T: typeid) -> typeid where type_is_bit_set(T) ---
type_has_field :: proc($T: typeid, $name: string) -> bool ---
type_field_type :: proc($T: typeid, $name: string) -> typeid ---
type_proc_parameter_count :: proc($T: typeid) -> int where type_is_proc(T) ---
@@ -282,6 +285,12 @@ simd_reverse :: proc(a: #simd[N]T) -> #simd[N]T ---
simd_rotate_left :: proc(a: #simd[N]T, $offset: int) -> #simd[N]T ---
simd_rotate_right :: proc(a: #simd[N]T, $offset: int) -> #simd[N]T ---
// Checks if the current target supports the given target features.
//
// Takes a constant comma-seperated string (eg: "sha512,sse4.1"), or a procedure type which has either
// `@(require_target_feature)` or `@(enable_target_feature)` as its input and returns a boolean indicating
// if all listed features are supported.
has_target_feature :: proc($test: $T) -> bool where type_is_string(T) || type_is_proc(T) ---
// WASM targets only
wasm_memory_grow :: proc(index, delta: uintptr) -> int ---
@@ -293,9 +302,9 @@ wasm_memory_size :: proc(index: uintptr) -> int ---
// 0 - indicates that the thread blocked and then was woken up
// 1 - the loaded value from `ptr` did not match `expected`, the thread did not block
// 2 - the thread blocked, but the timeout
@(enable_target_feature="atomics")
@(require_target_feature="atomics")
wasm_memory_atomic_wait32 :: proc(ptr: ^u32, expected: u32, timeout_ns: i64) -> u32 ---
@(enable_target_feature="atomics")
@(require_target_feature="atomics")
wasm_memory_atomic_notify32 :: proc(ptr: ^u32, waiters: u32) -> (waiters_woken_up: u32) ---
// x86 Targets (i386, amd64)
+8 -8
View File
@@ -273,14 +273,14 @@ Typeid_Kind :: enum u8 {
}
#assert(len(Typeid_Kind) < 32)
// Typeid_Bit_Field :: bit_field #align(align_of(uintptr)) {
// index: 8*size_of(uintptr) - 8,
// kind: 5, // Typeid_Kind
// named: 1,
// special: 1, // signed, cstring, etc
// reserved: 1,
// }
// #assert(size_of(Typeid_Bit_Field) == size_of(uintptr));
Typeid_Bit_Field :: bit_field uintptr {
index: uintptr | 8*size_of(uintptr) - 8,
kind: Typeid_Kind | 5, // Typeid_Kind
named: bool | 1,
special: bool | 1, // signed, cstring, etc
reserved: bool | 1,
}
#assert(size_of(Typeid_Bit_Field) == size_of(uintptr))
// NOTE(bill): only the ones that are needed (not all types)
// This will be set by the compiler
+2 -2
View File
@@ -40,7 +40,7 @@ copy_slice :: proc "contextless" (dst, src: $T/[]$E) -> int {
}
return n
}
// `copy_from_string` is a built-in procedure that copies elements from a source slice `src` to a destination string `dst`.
// `copy_from_string` is a built-in procedure that copies elements from a source string `src` to a destination slice `dst`.
// The source and destination may overlap. Copy returns the number of elements copied, which will be the minimum
// of len(src) and len(dst).
//
@@ -53,7 +53,7 @@ copy_from_string :: proc "contextless" (dst: $T/[]$E/u8, src: $S/string) -> int
}
return n
}
// `copy` is a built-in procedure that copies elements from a source slice `src` to a destination slice/string `dst`.
// `copy` is a built-in procedure that copies elements from a source slice/string `src` to a destination slice `dst`.
// The source and destination may overlap. Copy returns the number of elements copied, which will be the minimum
// of len(src) and len(dst).
@builtin
+13 -1
View File
@@ -19,6 +19,7 @@ type_assertion_trap :: proc "contextless" () -> ! {
}
@(disabled=ODIN_NO_BOUNDS_CHECK)
bounds_check_error :: proc "contextless" (file: string, line, column: i32, index, count: int) {
if uint(index) < uint(count) {
return
@@ -61,6 +62,7 @@ multi_pointer_slice_handle_error :: proc "contextless" (file: string, line, colu
}
@(disabled=ODIN_NO_BOUNDS_CHECK)
multi_pointer_slice_expr_error :: proc "contextless" (file: string, line, column: i32, lo, hi: int) {
if lo <= hi {
return
@@ -68,6 +70,7 @@ multi_pointer_slice_expr_error :: proc "contextless" (file: string, line, column
multi_pointer_slice_handle_error(file, line, column, lo, hi)
}
@(disabled=ODIN_NO_BOUNDS_CHECK)
slice_expr_error_hi :: proc "contextless" (file: string, line, column: i32, hi: int, len: int) {
if 0 <= hi && hi <= len {
return
@@ -75,6 +78,7 @@ slice_expr_error_hi :: proc "contextless" (file: string, line, column: i32, hi:
slice_handle_error(file, line, column, 0, hi, len)
}
@(disabled=ODIN_NO_BOUNDS_CHECK)
slice_expr_error_lo_hi :: proc "contextless" (file: string, line, column: i32, lo, hi: int, len: int) {
if 0 <= lo && lo <= len && lo <= hi && hi <= len {
return
@@ -82,6 +86,7 @@ slice_expr_error_lo_hi :: proc "contextless" (file: string, line, column: i32, l
slice_handle_error(file, line, column, lo, hi, len)
}
@(disabled=ODIN_NO_BOUNDS_CHECK)
dynamic_array_expr_error :: proc "contextless" (file: string, line, column: i32, low, high, max: int) {
if 0 <= low && low <= high && high <= max {
return
@@ -102,6 +107,7 @@ dynamic_array_expr_error :: proc "contextless" (file: string, line, column: i32,
}
@(disabled=ODIN_NO_BOUNDS_CHECK)
matrix_bounds_check_error :: proc "contextless" (file: string, line, column: i32, row_index, column_index, row_count, column_count: int) {
if uint(row_index) < uint(row_count) &&
uint(column_index) < uint(column_count) {
@@ -224,6 +230,7 @@ when ODIN_NO_RTTI {
}
@(disabled=ODIN_NO_BOUNDS_CHECK)
make_slice_error_loc :: #force_inline proc "contextless" (loc := #caller_location, len: int) {
if 0 <= len {
return
@@ -239,6 +246,7 @@ make_slice_error_loc :: #force_inline proc "contextless" (loc := #caller_locatio
handle_error(loc, len)
}
@(disabled=ODIN_NO_BOUNDS_CHECK)
make_dynamic_array_error_loc :: #force_inline proc "contextless" (loc := #caller_location, len, cap: int) {
if 0 <= len && len <= cap {
return
@@ -256,6 +264,7 @@ make_dynamic_array_error_loc :: #force_inline proc "contextless" (loc := #caller
handle_error(loc, len, cap)
}
@(disabled=ODIN_NO_BOUNDS_CHECK)
make_map_expr_error_loc :: #force_inline proc "contextless" (loc := #caller_location, cap: int) {
if 0 <= cap {
return
@@ -274,19 +283,22 @@ make_map_expr_error_loc :: #force_inline proc "contextless" (loc := #caller_loca
@(disabled=ODIN_NO_BOUNDS_CHECK)
bounds_check_error_loc :: #force_inline proc "contextless" (loc := #caller_location, index, count: int) {
bounds_check_error(loc.file_path, loc.line, loc.column, index, count)
}
@(disabled=ODIN_NO_BOUNDS_CHECK)
slice_expr_error_hi_loc :: #force_inline proc "contextless" (loc := #caller_location, hi: int, len: int) {
slice_expr_error_hi(loc.file_path, loc.line, loc.column, hi, len)
}
@(disabled=ODIN_NO_BOUNDS_CHECK)
slice_expr_error_lo_hi_loc :: #force_inline proc "contextless" (loc := #caller_location, lo, hi: int, len: int) {
slice_expr_error_lo_hi(loc.file_path, loc.line, loc.column, lo, hi, len)
}
@(disabled=ODIN_NO_BOUNDS_CHECK)
dynamic_array_expr_error_loc :: #force_inline proc "contextless" (loc := #caller_location, low, high, max: int) {
dynamic_array_expr_error(loc.file_path, loc.line, loc.column, low, high, max)
}
+6 -8
View File
@@ -1042,19 +1042,17 @@ fixdfti :: proc(a: u64) -> i128 {
__write_bits :: proc "contextless" (dst, src: [^]byte, offset: uintptr, size: uintptr) {
for i in 0..<size {
j := offset+i
the_bit := byte((src[i/8]) & (1<<(i&7)) != 0)
b := the_bit<<(j&7)
dst[j/8] &~= b
dst[j/8] |= b
the_bit := byte((src[i>>3]) & (1<<(i&7)) != 0)
dst[j>>3] &~= 1<<(j&7)
dst[j>>3] |= the_bit<<(j&7)
}
}
__read_bits :: proc "contextless" (dst, src: [^]byte, offset: uintptr, size: uintptr) {
for j in 0..<size {
i := offset+j
the_bit := byte((src[i/8]) & (1<<(i&7)) != 0)
b := the_bit<<(j&7)
dst[j/8] &~= b
dst[j/8] |= b
the_bit := byte((src[i>>3]) & (1<<(i&7)) != 0)
dst[j>>3] &~= 1<<(j&7)
dst[j>>3] |= the_bit<<(j&7)
}
}
+22 -16
View File
@@ -6,7 +6,7 @@ _INTEGER_DIGITS :: "0123456789abcdefghijklmnopqrstuvwxyz"
_INTEGER_DIGITS_VAR := _INTEGER_DIGITS
when !ODIN_NO_RTTI {
print_any_single :: proc "contextless" (arg: any) {
print_any_single :: #force_no_inline proc "contextless" (arg: any) {
x := arg
if x.data == nil {
print_string("nil")
@@ -72,7 +72,7 @@ when !ODIN_NO_RTTI {
print_string("<invalid-value>")
}
}
println_any :: proc "contextless" (args: ..any) {
println_any :: #force_no_inline proc "contextless" (args: ..any) {
context = default_context()
loop: for arg, i in args {
assert(arg.id != nil)
@@ -122,12 +122,12 @@ encode_rune :: proc "contextless" (c: rune) -> ([4]u8, int) {
return buf, 4
}
print_string :: proc "contextless" (str: string) -> (n: int) {
print_string :: #force_no_inline proc "contextless" (str: string) -> (n: int) {
n, _ = stderr_write(transmute([]byte)str)
return
}
print_strings :: proc "contextless" (args: ..string) -> (n: int) {
print_strings :: #force_no_inline proc "contextless" (args: ..string) -> (n: int) {
for str in args {
m, err := stderr_write(transmute([]byte)str)
n += m
@@ -138,12 +138,12 @@ print_strings :: proc "contextless" (args: ..string) -> (n: int) {
return
}
print_byte :: proc "contextless" (b: byte) -> (n: int) {
print_byte :: #force_no_inline proc "contextless" (b: byte) -> (n: int) {
n, _ = stderr_write([]byte{b})
return
}
print_encoded_rune :: proc "contextless" (r: rune) {
print_encoded_rune :: #force_no_inline proc "contextless" (r: rune) {
print_byte('\'')
switch r {
@@ -170,7 +170,7 @@ print_encoded_rune :: proc "contextless" (r: rune) {
print_byte('\'')
}
print_rune :: proc "contextless" (r: rune) -> int #no_bounds_check {
print_rune :: #force_no_inline proc "contextless" (r: rune) -> int #no_bounds_check {
RUNE_SELF :: 0x80
if r < RUNE_SELF {
@@ -183,7 +183,7 @@ print_rune :: proc "contextless" (r: rune) -> int #no_bounds_check {
}
print_u64 :: proc "contextless" (x: u64) #no_bounds_check {
print_u64 :: #force_no_inline proc "contextless" (x: u64) #no_bounds_check {
a: [129]byte
i := len(a)
b := u64(10)
@@ -198,7 +198,7 @@ print_u64 :: proc "contextless" (x: u64) #no_bounds_check {
}
print_i64 :: proc "contextless" (x: i64) #no_bounds_check {
print_i64 :: #force_no_inline proc "contextless" (x: i64) #no_bounds_check {
b :: i64(10)
u := x
@@ -223,25 +223,29 @@ print_uint :: proc "contextless" (x: uint) { print_u64(u64(x)) }
print_uintptr :: proc "contextless" (x: uintptr) { print_u64(u64(x)) }
print_int :: proc "contextless" (x: int) { print_i64(i64(x)) }
print_caller_location :: proc "contextless" (loc: Source_Code_Location) {
print_caller_location :: #force_no_inline proc "contextless" (loc: Source_Code_Location) {
print_string(loc.file_path)
when ODIN_ERROR_POS_STYLE == .Default {
print_byte('(')
print_u64(u64(loc.line))
print_byte(':')
print_u64(u64(loc.column))
if loc.column != 0 {
print_byte(':')
print_u64(u64(loc.column))
}
print_byte(')')
} else when ODIN_ERROR_POS_STYLE == .Unix {
print_byte(':')
print_u64(u64(loc.line))
print_byte(':')
print_u64(u64(loc.column))
if loc.column != 0 {
print_byte(':')
print_u64(u64(loc.column))
}
print_byte(':')
} else {
#panic("unhandled ODIN_ERROR_POS_STYLE")
}
}
print_typeid :: proc "contextless" (id: typeid) {
print_typeid :: #force_no_inline proc "contextless" (id: typeid) {
when ODIN_NO_RTTI {
if id == nil {
print_string("nil")
@@ -257,7 +261,9 @@ print_typeid :: proc "contextless" (id: typeid) {
}
}
}
print_type :: proc "contextless" (ti: ^Type_Info) {
@(optimization_mode="size")
print_type :: #force_no_inline proc "contextless" (ti: ^Type_Info) {
if ti == nil {
print_string("nil")
return
+14 -12
View File
@@ -2,7 +2,6 @@
set -eu
: ${CPPFLAGS=}
: ${CXX=clang++}
: ${CXXFLAGS=}
: ${LDFLAGS=}
: ${LLVM_CONFIG=}
@@ -26,17 +25,19 @@ error() {
if [ -z "$LLVM_CONFIG" ]; then
# darwin, linux, openbsd
if [ -n "$(command -v llvm-config-17)" ]; then LLVM_CONFIG="llvm-config-17"
if [ -n "$(command -v llvm-config-18)" ]; then LLVM_CONFIG="llvm-config-18"
elif [ -n "$(command -v llvm-config-17)" ]; then LLVM_CONFIG="llvm-config-17"
elif [ -n "$(command -v llvm-config-14)" ]; then LLVM_CONFIG="llvm-config-14"
elif [ -n "$(command -v llvm-config-13)" ]; then LLVM_CONFIG="llvm-config-13"
elif [ -n "$(command -v llvm-config-12)" ]; then LLVM_CONFIG="llvm-config-12"
elif [ -n "$(command -v llvm-config-11)" ]; then LLVM_CONFIG="llvm-config-11"
# freebsd
elif [ -n "$(command -v llvm-config17)" ]; then LLVM_CONFIG="llvm-config-17"
elif [ -n "$(command -v llvm-config14)" ]; then LLVM_CONFIG="llvm-config-14"
elif [ -n "$(command -v llvm-config13)" ]; then LLVM_CONFIG="llvm-config-13"
elif [ -n "$(command -v llvm-config12)" ]; then LLVM_CONFIG="llvm-config-12"
elif [ -n "$(command -v llvm-config11)" ]; then LLVM_CONFIG="llvm-config-11"
elif [ -n "$(command -v llvm-config18)" ]; then LLVM_CONFIG="llvm-config18"
elif [ -n "$(command -v llvm-config17)" ]; then LLVM_CONFIG="llvm-config17"
elif [ -n "$(command -v llvm-config14)" ]; then LLVM_CONFIG="llvm-config14"
elif [ -n "$(command -v llvm-config13)" ]; then LLVM_CONFIG="llvm-config13"
elif [ -n "$(command -v llvm-config12)" ]; then LLVM_CONFIG="llvm-config12"
elif [ -n "$(command -v llvm-config11)" ]; then LLVM_CONFIG="llvm-config11"
# fallback
elif [ -n "$(command -v llvm-config)" ]; then LLVM_CONFIG="llvm-config"
else
@@ -44,21 +45,22 @@ if [ -z "$LLVM_CONFIG" ]; then
fi
fi
: ${CXX=$($LLVM_CONFIG --bindir)/clang++}
LLVM_VERSION="$($LLVM_CONFIG --version)"
LLVM_VERSION_MAJOR="$(echo $LLVM_VERSION | awk -F. '{print $1}')"
LLVM_VERSION_MINOR="$(echo $LLVM_VERSION | awk -F. '{print $2}')"
LLVM_VERSION_PATCH="$(echo $LLVM_VERSION | awk -F. '{print $3}')"
if [ $LLVM_VERSION_MAJOR -lt 11 ] ||
([ $LLVM_VERSION_MAJOR -gt 14 ] && [ $LLVM_VERSION_MAJOR -lt 17 ]); then
error "Invalid LLVM version $LLVM_VERSION: must be 11, 12, 13, 14 or 17"
if [ $LLVM_VERSION_MAJOR -lt 11 ] || ([ $LLVM_VERSION_MAJOR -gt 14 ] && [ $LLVM_VERSION_MAJOR -lt 17 ]) || [ $LLVM_VERSION_MAJOR -gt 18 ]; then
error "Invalid LLVM version $LLVM_VERSION: must be 11, 12, 13, 14, 17 or 18"
fi
case "$OS_NAME" in
Darwin)
if [ "$OS_ARCH" = "arm64" ]; then
if [ $LLVM_VERSION_MAJOR -lt 13 ] || [ $LLVM_VERSION_MAJOR -gt 17 ]; then
error "Darwin Arm64 requires LLVM 13, 14 or 17"
if [ $LLVM_VERSION_MAJOR -lt 13 ]; then
error "Invalid LLVM version $LLVM_VERSION: Darwin Arm64 requires LLVM 13, 14, 17 or 18"
fi
fi
+4 -4
View File
@@ -81,7 +81,7 @@ _reader_read_new_chunk :: proc(b: ^Reader) -> io.Error {
for i := b.max_consecutive_empty_reads; i > 0; i -= 1 {
n, err := io.read(b.rd, b.buf[b.w:])
if n < 0 {
return .Negative_Read
return err if err != nil else .Negative_Read
}
b.w += n
if err != nil {
@@ -189,7 +189,7 @@ reader_read :: proc(b: ^Reader, p: []byte) -> (n: int, err: io.Error) {
if len(p) >= len(b.buf) {
n, b.err = io.read(b.rd, p)
if n < 0 {
return 0, .Negative_Read
return 0, b.err if b.err != nil else .Negative_Read
}
if n > 0 {
@@ -202,7 +202,7 @@ reader_read :: proc(b: ^Reader, p: []byte) -> (n: int, err: io.Error) {
b.r, b.w = 0, 0
n, b.err = io.read(b.rd, b.buf)
if n < 0 {
return 0, .Negative_Read
return 0, b.err if b.err != nil else .Negative_Read
}
if n == 0 {
return 0, _reader_consume_err(b)
@@ -290,7 +290,7 @@ reader_write_to :: proc(b: ^Reader, w: io.Writer) -> (n: i64, err: io.Error) {
write_buf :: proc(b: ^Reader, w: io.Writer) -> (i64, io.Error) {
n, err := io.write(w, b.buf[b.r:b.w])
if n < 0 {
return 0, .Negative_Write
return 0, err if err != nil else .Negative_Write
}
b.r += n
return i64(n), err
+4
View File
@@ -95,6 +95,10 @@ writer_write :: proc(b: ^Writer, p: []byte) -> (n: int, err: io.Error) {
m: int
if writer_buffered(b) == 0 {
m, b.err = io.write(b.wr, p)
if m < 0 && b.err == nil {
b.err = .Negative_Write
break
}
} else {
m = copy(b.buf[b.n:], p)
b.n += m
+1 -1
View File
@@ -359,7 +359,7 @@ buffer_read_from :: proc(b: ^Buffer, r: io.Reader) -> (n: i64, err: io.Error) #n
resize(&b.buf, i)
m, e := io.read(r, b.buf[i:cap(b.buf)])
if m < 0 {
err = .Negative_Read
err = e if e != nil else .Negative_Read
return
}
+21 -24
View File
@@ -1,34 +1,31 @@
/*
package demo
Example:
package demo
import tokenizer "core:c/frontend/tokenizer"
import preprocessor "core:c/frontend/preprocessor"
import "core:fmt"
import tokenizer "core:c/frontend/tokenizer"
import preprocessor "core:c/frontend/preprocessor"
import "core:fmt"
main :: proc() {
t := &tokenizer.Tokenizer{};
tokenizer.init_defaults(t);
main :: proc() {
t := &tokenizer.Tokenizer{};
tokenizer.init_defaults(t);
cpp := &preprocessor.Preprocessor{};
cpp.warn, cpp.err = t.warn, t.err;
preprocessor.init_lookup_tables(cpp);
preprocessor.init_default_macros(cpp);
cpp.include_paths = {"my/path/to/include"};
cpp := &preprocessor.Preprocessor{};
cpp.warn, cpp.err = t.warn, t.err;
preprocessor.init_lookup_tables(cpp);
preprocessor.init_default_macros(cpp);
cpp.include_paths = {"my/path/to/include"};
tok := tokenizer.tokenize_file(t, "the/source/file.c", 1);
tok := tokenizer.tokenize_file(t, "the/source/file.c", 1);
tok = preprocessor.preprocess(cpp, tok);
if tok != nil {
for t := tok; t.kind != .EOF; t = t.next {
fmt.println(t.lit);
tok = preprocessor.preprocess(cpp, tok);
if tok != nil {
for t := tok; t.kind != .EOF; t = t.next {
fmt.println(t.lit);
}
}
fmt.println("[Done]");
}
fmt.println("[Done]");
}
*/
package c_frontend_tokenizer
+1 -1
View File
@@ -1,5 +1,5 @@
//+build ignore
package gzip
package compress_gzip
/*
Copyright 2021 Jeroen van Rijn <nom@duclavier.com>.
+1 -1
View File
@@ -1,4 +1,4 @@
package gzip
package compress_gzip
/*
Copyright 2021 Jeroen van Rijn <nom@duclavier.com>.
+1 -1
View File
@@ -5,7 +5,7 @@
*/
// package shoco is an implementation of the shoco short string compressor
package shoco
package compress_shoco
DEFAULT_MODEL :: Shoco_Model {
min_char = 39,
+1 -1
View File
@@ -9,7 +9,7 @@
*/
// package shoco is an implementation of the shoco short string compressor
package shoco
package compress_shoco
import "base:intrinsics"
import "core:compress"
+1 -1
View File
@@ -1,5 +1,5 @@
//+build ignore
package zlib
package compress_zlib
/*
Copyright 2021 Jeroen van Rijn <nom@duclavier.com>.
+1 -1
View File
@@ -1,5 +1,5 @@
//+vet !using-param
package zlib
package compress_zlib
/*
Copyright 2021 Jeroen van Rijn <nom@duclavier.com>.
+1 -1
View File
@@ -1,4 +1,4 @@
package dynamic_bit_array
package container_dynamic_bit_array
import "base:intrinsics"
import "core:mem"
+40 -41
View File
@@ -1,53 +1,52 @@
package dynamic_bit_array
/*
The Bit Array can be used in several ways:
The Bit Array can be used in several ways:
-- By default you don't need to instantiate a Bit Array:
- By default you don't need to instantiate a Bit Array:
package test
package test
import "core:fmt"
import "core:container/bit_array"
import "core:fmt"
import "core:container/bit_array"
main :: proc() {
using bit_array
main :: proc() {
using bit_array
bits: Bit_Array
bits: Bit_Array
// returns `true`
fmt.println(set(&bits, 42))
// returns `true`
fmt.println(set(&bits, 42))
// returns `false`, `false`, because this Bit Array wasn't created to allow negative indices.
was_set, was_retrieved := get(&bits, -1)
fmt.println(was_set, was_retrieved)
destroy(&bits)
// returns `false`, `false`, because this Bit Array wasn't created to allow negative indices.
was_set, was_retrieved := get(&bits, -1)
fmt.println(was_set, was_retrieved)
destroy(&bits)
}
- A Bit Array can optionally allow for negative indices, if the minimum value was given during creation:
package test
import "core:fmt"
import "core:container/bit_array"
main :: proc() {
Foo :: enum int {
Negative_Test = -42,
Bar = 420,
Leaves = 69105,
}
-- A Bit Array can optionally allow for negative indices, if the mininum value was given during creation:
using bit_array
package test
bits := create(int(max(Foo)), int(min(Foo)))
defer destroy(bits)
import "core:fmt"
import "core:container/bit_array"
main :: proc() {
Foo :: enum int {
Negative_Test = -42,
Bar = 420,
Leaves = 69105,
}
using bit_array
bits := create(int(max(Foo)), int(min(Foo)))
defer destroy(bits)
fmt.printf("Set(Bar): %v\n", set(bits, Foo.Bar))
fmt.printf("Get(Bar): %v, %v\n", get(bits, Foo.Bar))
fmt.printf("Set(Negative_Test): %v\n", set(bits, Foo.Negative_Test))
fmt.printf("Get(Leaves): %v, %v\n", get(bits, Foo.Leaves))
fmt.printf("Get(Negative_Test): %v, %v\n", get(bits, Foo.Negative_Test))
fmt.printf("Freed.\n")
}
*/
fmt.printf("Set(Bar): %v\n", set(bits, Foo.Bar))
fmt.printf("Get(Bar): %v, %v\n", get(bits, Foo.Bar))
fmt.printf("Set(Negative_Test): %v\n", set(bits, Foo.Negative_Test))
fmt.printf("Get(Leaves): %v, %v\n", get(bits, Foo.Leaves))
fmt.printf("Get(Negative_Test): %v, %v\n", get(bits, Foo.Negative_Test))
fmt.printf("Freed.\n")
}
*/
package container_dynamic_bit_array
+3 -6
View File
@@ -49,15 +49,12 @@ compare_byte_ptrs_constant_time :: proc "contextless" (a, b: ^byte, n: int) -> i
// the system entropy source. This routine will block if the system entropy
// source is not ready yet. All system entropy source failures are treated
// as catastrophic, resulting in a panic.
//
// Support for the system entropy source can be checked with the
// `HAS_RAND_BYTES` boolean constant.
rand_bytes :: proc (dst: []byte) {
// zero-fill the buffer first
mem.zero_explicit(raw_data(dst), len(dst))
_rand_bytes(dst)
}
// has_rand_bytes returns true iff the target has support for accessing the
// system entropty source.
has_rand_bytes :: proc () -> bool {
return _has_rand_bytes()
}
+3 -4
View File
@@ -3,14 +3,13 @@ package crypto
foreign import libc "system:c"
HAS_RAND_BYTES :: true
foreign libc {
arc4random_buf :: proc(buf: [^]byte, nbytes: uint) ---
}
@(private)
_rand_bytes :: proc(dst: []byte) {
arc4random_buf(raw_data(dst), len(dst))
}
_has_rand_bytes :: proc () -> bool {
return true
}
+10 -9
View File
@@ -1,16 +1,17 @@
package crypto
import "core:fmt"
import "core:sys/darwin"
import CF "core:sys/darwin/CoreFoundation"
import Sec "core:sys/darwin/Security"
HAS_RAND_BYTES :: true
@(private)
_rand_bytes :: proc(dst: []byte) {
res := darwin.SecRandomCopyBytes(count=len(dst), bytes=raw_data(dst))
if res != .Success {
msg := darwin.CFStringCopyToOdinString(darwin.SecCopyErrorMessageString(res))
panic(fmt.tprintf("crypto/rand_bytes: SecRandomCopyBytes returned non-zero result: %v %s", res, msg))
err := Sec.RandomCopyBytes(count=len(dst), bytes=raw_data(dst))
if err != .Success {
msg := CF.StringCopyToOdinString(Sec.CopyErrorMessageString(err))
panic(fmt.tprintf("crypto/rand_bytes: SecRandomCopyBytes returned non-zero result: %v %s", err, msg))
}
}
_has_rand_bytes :: proc () -> bool {
return true
}
+3 -4
View File
@@ -6,10 +6,9 @@
//+build !js
package crypto
HAS_RAND_BYTES :: false
@(private)
_rand_bytes :: proc(dst: []byte) {
unimplemented("crypto: rand_bytes not supported on this OS")
}
_has_rand_bytes :: proc () -> bool {
return false
}
+4 -4
View File
@@ -6,8 +6,12 @@ foreign odin_env {
env_rand_bytes :: proc "contextless" (buf: []byte) ---
}
HAS_RAND_BYTES :: true
@(private)
_MAX_PER_CALL_BYTES :: 65536 // 64kiB
@(private)
_rand_bytes :: proc(dst: []byte) {
dst := dst
@@ -18,7 +22,3 @@ _rand_bytes :: proc(dst: []byte) {
dst = dst[to_read:]
}
}
_has_rand_bytes :: proc () -> bool {
return true
}
+4 -4
View File
@@ -4,8 +4,12 @@ import "core:fmt"
import "core:sys/linux"
HAS_RAND_BYTES :: true
@(private)
_MAX_PER_CALL_BYTES :: 33554431 // 2^25 - 1
@(private)
_rand_bytes :: proc (dst: []byte) {
dst := dst
l := len(dst)
@@ -34,7 +38,3 @@ _rand_bytes :: proc (dst: []byte) {
dst = dst[n_read:]
}
}
_has_rand_bytes :: proc () -> bool {
return true
}
+3 -4
View File
@@ -4,6 +4,9 @@ import win32 "core:sys/windows"
import "core:os"
import "core:fmt"
HAS_RAND_BYTES :: true
@(private)
_rand_bytes :: proc(dst: []byte) {
ret := (os.Errno)(win32.BCryptGenRandom(nil, raw_data(dst), u32(len(dst)), win32.BCRYPT_USE_SYSTEM_PREFERRED_RNG))
if ret != os.ERROR_NONE {
@@ -21,7 +24,3 @@ _rand_bytes :: proc(dst: []byte) {
}
}
}
_has_rand_bytes :: proc () -> bool {
return true
}
+51
View File
@@ -0,0 +1,51 @@
/*
A debug stack trace library. Only works when debug symbols are enabled `-debug`.
Example:
import "base:runtime"
import "core:debug/trace"
import "core:fmt"
global_trace_ctx: trace.Context
debug_trace_assertion_failure_proc :: proc(prefix, message: string, loc := #caller_location) -> ! {
runtime.print_caller_location(loc)
runtime.print_string(" ")
runtime.print_string(prefix)
if len(message) > 0 {
runtime.print_string(": ")
runtime.print_string(message)
}
runtime.print_byte('\n')
ctx := &trace_ctx
if !trace.in_resolve(ctx) {
buf: [64]trace.Frame
runtime.print_string("Debug Trace:\n")
frames := trace.frames(ctx, 1, buf[:])
for f, i in frames {
fl := trace.resolve(ctx, f, context.temp_allocator)
if fl.loc.file_path == "" && fl.loc.line == 0 {
continue
}
runtime.print_caller_location(fl.loc)
runtime.print_string(" - frame ")
runtime.print_int(i)
runtime.print_byte('\n')
}
}
runtime.trap()
}
main :: proc() {
trace.init(&global_trace_ctx)
defer trace.destroy(&global_trace_ctx)
context.assertion_failure_proc = debug_trace_assertion_failure_proc
...
}
*/
package debug_trace
+47
View File
@@ -0,0 +1,47 @@
package debug_trace
import "base:intrinsics"
import "base:runtime"
Frame :: distinct uintptr
Frame_Location :: struct {
using loc: runtime.Source_Code_Location,
allocator: runtime.Allocator,
}
delete_frame_location :: proc(fl: Frame_Location) -> runtime.Allocator_Error {
allocator := fl.allocator
delete(fl.loc.procedure, allocator) or_return
delete(fl.loc.file_path, allocator) or_return
return nil
}
Context :: struct {
in_resolve: bool, // atomic
impl: _Context,
}
init :: proc(ctx: ^Context) -> bool {
return _init(ctx)
}
destroy :: proc(ctx: ^Context) -> bool {
return _destroy(ctx)
}
@(require_results)
frames :: proc(ctx: ^Context, skip: uint, frames_buffer: []Frame) -> []Frame {
return _frames(ctx, skip, frames_buffer)
}
@(require_results)
resolve :: proc(ctx: ^Context, frame: Frame, allocator: runtime.Allocator) -> (result: Frame_Location) {
return _resolve(ctx, frame, allocator)
}
@(require_results)
in_resolve :: proc "contextless" (ctx: ^Context) -> bool {
return intrinsics.atomic_load(&ctx.in_resolve)
}
+195
View File
@@ -0,0 +1,195 @@
//+private file
//+build linux, darwin
package debug_trace
import "base:intrinsics"
import "base:runtime"
import "core:strings"
import "core:fmt"
import "core:c"
// NOTE: Relies on C++23 which adds <stacktrace> and becomes ABI and that can be used
foreign import stdcpplibbacktrace "system:stdc++_libbacktrace"
foreign import libdl "system:dl"
backtrace_state :: struct {}
backtrace_error_callback :: proc "c" (data: rawptr, msg: cstring, errnum: c.int)
backtrace_simple_callback :: proc "c" (data: rawptr, pc: uintptr) -> c.int
backtrace_full_callback :: proc "c" (data: rawptr, pc: uintptr, filename: cstring, lineno: c.int, function: cstring) -> c.int
backtrace_syminfo_callback :: proc "c" (data: rawptr, pc: uintptr, symname: cstring, symval: uintptr, symsize: uintptr)
@(default_calling_convention="c", link_prefix="__glibcxx_")
foreign stdcpplibbacktrace {
backtrace_create_state :: proc(
filename: cstring,
threaded: c.int,
error_callback: backtrace_error_callback,
data: rawptr,
) -> ^backtrace_state ---
backtrace_simple :: proc(
state: ^backtrace_state,
skip: c.int,
callback: backtrace_simple_callback,
error_callback: backtrace_error_callback,
data: rawptr,
) -> c.int ---
backtrace_pcinfo :: proc(
state: ^backtrace_state,
pc: uintptr,
callback: backtrace_full_callback,
error_callback: backtrace_error_callback,
data: rawptr,
) -> c.int ---
backtrace_syminfo :: proc(
state: ^backtrace_state,
addr: uintptr,
callback: backtrace_syminfo_callback,
error_callback: backtrace_error_callback,
data: rawptr,
) -> c.int ---
// NOTE(bill): this is technically an internal procedure, but it is exposed
backtrace_free :: proc(
state: ^backtrace_state,
p: rawptr,
size: c.size_t, // unused
error_callback: backtrace_error_callback, // unused
data: rawptr, // unused
) ---
}
Dl_info :: struct {
dli_fname: cstring,
dli_fbase: rawptr,
dli_sname: cstring,
dli_saddr: rawptr,
}
@(default_calling_convention="c")
foreign libdl {
dladdr :: proc(addr: rawptr, info: ^Dl_info) -> c.int ---
}
@(private="package")
_Context :: struct {
state: ^backtrace_state,
}
@(private="package")
_init :: proc(ctx: ^Context) -> (ok: bool) {
defer if !ok do destroy(ctx)
ctx.impl.state = backtrace_create_state("odin-debug-trace", 1, nil, ctx)
return ctx.impl.state != nil
}
@(private="package")
_destroy :: proc(ctx: ^Context) -> bool {
if ctx != nil {
backtrace_free(ctx.impl.state, nil, 0, nil, nil)
}
return true
}
@(private="package")
_frames :: proc "contextless" (ctx: ^Context, skip: uint, frames_buffer: []Frame) -> (frames: []Frame) {
Backtrace_Context :: struct {
ctx: ^Context,
frames: []Frame,
frame_count: int,
}
btc := &Backtrace_Context{
ctx = ctx,
frames = frames_buffer,
}
backtrace_simple(
ctx.impl.state,
c.int(skip + 2),
proc "c" (user: rawptr, address: uintptr) -> c.int {
btc := (^Backtrace_Context)(user)
address := Frame(address)
if address == 0 {
return 1
}
if btc.frame_count == len(btc.frames) {
return 1
}
btc.frames[btc.frame_count] = address
btc.frame_count += 1
return 0
},
nil,
btc,
)
if btc.frame_count > 0 {
frames = btc.frames[:btc.frame_count]
}
return
}
@(private="package")
_resolve :: proc(ctx: ^Context, frame: Frame, allocator: runtime.Allocator) -> Frame_Location {
intrinsics.atomic_store(&ctx.in_resolve, true)
defer intrinsics.atomic_store(&ctx.in_resolve, false)
Backtrace_Context :: struct {
rt_ctx: runtime.Context,
allocator: runtime.Allocator,
frame: Frame_Location,
}
btc := &Backtrace_Context{
rt_ctx = context,
allocator = allocator,
}
done := backtrace_pcinfo(
ctx.impl.state,
uintptr(frame),
proc "c" (data: rawptr, address: uintptr, file: cstring, line: c.int, symbol: cstring) -> c.int {
btc := (^Backtrace_Context)(data)
context = btc.rt_ctx
frame := &btc.frame
if file != nil {
frame.file_path = strings.clone_from_cstring(file, btc.allocator)
} else if info: Dl_info; dladdr(rawptr(address), &info) != 0 && info.dli_fname != "" {
frame.file_path = strings.clone_from_cstring(info.dli_fname, btc.allocator)
}
if symbol != nil {
frame.procedure = strings.clone_from_cstring(symbol, btc.allocator)
} else if info: Dl_info; dladdr(rawptr(address), &info) != 0 && info.dli_sname != "" {
frame.procedure = strings.clone_from_cstring(info.dli_sname, btc.allocator)
} else {
frame.procedure = fmt.aprintf("(procedure: 0x%x)", allocator=btc.allocator)
}
frame.line = i32(line)
return 0
},
nil,
btc,
)
if done != 0 {
return btc.frame
}
// NOTE(bill): pcinfo cannot resolve, but it might be possible to get the procedure name at least
backtrace_syminfo(
ctx.impl.state,
uintptr(frame),
proc "c" (data: rawptr, address: uintptr, symbol: cstring, _ignore0, _ignore1: uintptr) {
if symbol != nil {
btc := (^Backtrace_Context)(data)
context = btc.rt_ctx
btc.frame.procedure = strings.clone_from_cstring(symbol, btc.allocator)
}
},
nil,
btc,
)
return btc.frame
}
+18
View File
@@ -0,0 +1,18 @@
//+build !windows !linux !darwin
package debug_trace
_Context :: struct {
}
_init :: proc(ctx: ^Context) -> (ok: bool) {
return true
}
_destroy :: proc(ctx: ^Context) -> bool {
return true
}
_frames :: proc(ctx: ^Context, skip: uint, allocator: runtime.Allocator) -> []Frame {
return nil
}
_resolve :: proc(ctx: ^Context, frame: Frame, allocator: runtime.Allocator) -> (result: runtime.Source_Code_Location) {
return
}
+68
View File
@@ -0,0 +1,68 @@
//+private
//+build windows
package debug_trace
import "base:intrinsics"
import "base:runtime"
import win32 "core:sys/windows"
import "core:fmt"
_Context :: struct {
hProcess: win32.HANDLE,
lock: win32.SRWLOCK,
}
_init :: proc "contextless" (ctx: ^Context) -> (ok: bool) {
defer if !ok { _destroy(ctx) }
ctx.impl.hProcess = win32.GetCurrentProcess()
win32.SymInitialize(ctx.impl.hProcess, nil, true) or_return
win32.SymSetOptions(win32.SYMOPT_LOAD_LINES)
return true
}
_destroy :: proc "contextless" (ctx: ^Context) -> bool {
if ctx != nil {
win32.SymCleanup(ctx.impl.hProcess)
}
return true
}
_frames :: proc "contextless" (ctx: ^Context, skip: uint, frames_buffer: []Frame) -> []Frame {
frame_count := win32.RtlCaptureStackBackTrace(u32(skip) + 2, u32(len(frames_buffer)), ([^]rawptr)(&frames_buffer[0]), nil)
for i in 0..<frame_count {
// NOTE: Return address is one after the call instruction so subtract a byte to
// end up back inside the call instruction which is needed for SymFromAddr.
frames_buffer[i] -= 1
}
return frames_buffer[:frame_count]
}
_resolve :: proc(ctx: ^Context, frame: Frame, allocator: runtime.Allocator) -> (fl: Frame_Location) {
intrinsics.atomic_store(&ctx.in_resolve, true)
defer intrinsics.atomic_store(&ctx.in_resolve, false)
// NOTE(bill): Dbghelp is not thread-safe
win32.AcquireSRWLockExclusive(&ctx.impl.lock)
defer win32.ReleaseSRWLockExclusive(&ctx.impl.lock)
data: [size_of(win32.SYMBOL_INFOW) + size_of([256]win32.WCHAR)]byte
symbol := (^win32.SYMBOL_INFOW)(&data[0])
symbol.SizeOfStruct = size_of(symbol)
symbol.MaxNameLen = 255
if win32.SymFromAddrW(ctx.impl.hProcess, win32.DWORD64(frame), &{}, symbol) {
fl.procedure, _ = win32.wstring_to_utf8(&symbol.Name[0], -1, allocator)
} else {
fl.procedure = fmt.aprintf("(procedure: 0x%x)", frame, allocator=allocator)
}
line: win32.IMAGEHLP_LINE64
line.SizeOfStruct = size_of(line)
if win32.SymGetLineFromAddrW64(ctx.impl.hProcess, win32.DWORD64(frame), &{}, &line) {
fl.file_path, _ = win32.wstring_to_utf8(line.FileName, -1, allocator)
fl.line = i32(line.LineNumber)
}
return
}
+2 -3
View File
@@ -1,6 +1,5 @@
//+build ignore
/*
Package core:dynlib implements loading of shared libraries/DLLs and their symbols.
Package `core:dynlib` implements loading of shared libraries/DLLs and their symbols.
The behaviour of dynamically loaded libraries is specific to the target platform of the program.
For in depth detail on the underlying behaviour please refer to your target platform's documentation.
@@ -8,4 +7,4 @@ For in depth detail on the underlying behaviour please refer to your target plat
See `example` directory for an example library exporting 3 symbols and a host program loading them automatically
by defining a symbol table struct.
*/
package dynlib
package dynlib
+1 -1
View File
@@ -135,7 +135,7 @@ initialize_symbols :: proc(
prefixed_symbol_buf: [2048]u8 = ---
count = 0
for field, i in reflect.struct_fields_zipped(T) {
for field in reflect.struct_fields_zipped(T) {
// Calculate address of struct member
field_ptr := rawptr(uintptr(symbol_table) + field.offset)
+1 -1
View File
@@ -1,4 +1,4 @@
package base32
package encoding_base32
// @note(zh): Encoding utility for Base32
// A secondary param can be used to supply a custom alphabet to
+125 -48
View File
@@ -1,4 +1,8 @@
package base64
package encoding_base64
import "core:io"
import "core:mem"
import "core:strings"
// @note(zh): Encoding utility for Base64
// A secondary param can be used to supply a custom alphabet to
@@ -39,59 +43,132 @@ DEC_TABLE := [128]int {
49, 50, 51, -1, -1, -1, -1, -1,
}
encode :: proc(data: []byte, ENC_TBL := ENC_TABLE, allocator := context.allocator) -> string #no_bounds_check {
length := len(data)
if length == 0 {
return ""
}
encode :: proc(data: []byte, ENC_TBL := ENC_TABLE, allocator := context.allocator) -> (encoded: string, err: mem.Allocator_Error) #optional_allocator_error {
out_length := encoded_len(data)
if out_length == 0 {
return
}
out_length := ((4 * length / 3) + 3) &~ 3
out := make([]byte, out_length, allocator)
out := strings.builder_make(0, out_length, allocator) or_return
ioerr := encode_into(strings.to_stream(&out), data, ENC_TBL)
c0, c1, c2, block: int
assert(ioerr == nil, "string builder should not IO error")
assert(strings.builder_cap(out) == out_length, "buffer resized, `encoded_len` was wrong")
for i, d := 0, 0; i < length; i, d = i + 3, d + 4 {
c0, c1, c2 = int(data[i]), -1, -1
if i + 1 < length { c1 = int(data[i + 1]) }
if i + 2 < length { c2 = int(data[i + 2]) }
block = (c0 << 16) | (max(c1, 0) << 8) | max(c2, 0)
out[d] = ENC_TBL[block >> 18 & 63]
out[d + 1] = ENC_TBL[block >> 12 & 63]
out[d + 2] = c1 == -1 ? PADDING : ENC_TBL[block >> 6 & 63]
out[d + 3] = c2 == -1 ? PADDING : ENC_TBL[block & 63]
}
return string(out)
return strings.to_string(out), nil
}
decode :: proc(data: string, DEC_TBL := DEC_TABLE, allocator := context.allocator) -> []byte #no_bounds_check {
length := len(data)
if length == 0 {
return nil
}
encode_into :: proc(w: io.Writer, data: []byte, ENC_TBL := ENC_TABLE) -> io.Error {
length := len(data)
if length == 0 {
return nil
}
pad_count := data[length - 1] == PADDING ? (data[length - 2] == PADDING ? 2 : 1) : 0
out_length := ((length * 6) >> 3) - pad_count
out := make([]byte, out_length, allocator)
c0, c1, c2, block: int
out: [4]byte
for i := 0; i < length; i += 3 {
#no_bounds_check {
c0, c1, c2 = int(data[i]), -1, -1
c0, c1, c2, c3: int
b0, b1, b2: int
if i + 1 < length { c1 = int(data[i + 1]) }
if i + 2 < length { c2 = int(data[i + 2]) }
for i, j := 0, 0; i < length; i, j = i + 4, j + 3 {
c0 = DEC_TBL[data[i]]
c1 = DEC_TBL[data[i + 1]]
c2 = DEC_TBL[data[i + 2]]
c3 = DEC_TBL[data[i + 3]]
b0 = (c0 << 2) | (c1 >> 4)
b1 = (c1 << 4) | (c2 >> 2)
b2 = (c2 << 6) | c3
out[j] = byte(b0)
out[j + 1] = byte(b1)
out[j + 2] = byte(b2)
}
return out
block = (c0 << 16) | (max(c1, 0) << 8) | max(c2, 0)
out[0] = ENC_TBL[block >> 18 & 63]
out[1] = ENC_TBL[block >> 12 & 63]
out[2] = c1 == -1 ? PADDING : ENC_TBL[block >> 6 & 63]
out[3] = c2 == -1 ? PADDING : ENC_TBL[block & 63]
}
io.write_full(w, out[:]) or_return
}
return nil
}
encoded_len :: proc(data: []byte) -> int {
length := len(data)
if length == 0 {
return 0
}
return ((4 * length / 3) + 3) &~ 3
}
decode :: proc(data: string, DEC_TBL := DEC_TABLE, allocator := context.allocator) -> (decoded: []byte, err: mem.Allocator_Error) #optional_allocator_error {
out_length := decoded_len(data)
out := strings.builder_make(0, out_length, allocator) or_return
ioerr := decode_into(strings.to_stream(&out), data, DEC_TBL)
assert(ioerr == nil, "string builder should not IO error")
assert(strings.builder_cap(out) == out_length, "buffer resized, `decoded_len` was wrong")
return out.buf[:], nil
}
decode_into :: proc(w: io.Writer, data: string, DEC_TBL := DEC_TABLE) -> io.Error {
length := decoded_len(data)
if length == 0 {
return nil
}
c0, c1, c2, c3: int
b0, b1, b2: int
buf: [3]byte
i, j: int
for ; j + 3 <= length; i, j = i + 4, j + 3 {
#no_bounds_check {
c0 = DEC_TBL[data[i]]
c1 = DEC_TBL[data[i + 1]]
c2 = DEC_TBL[data[i + 2]]
c3 = DEC_TBL[data[i + 3]]
b0 = (c0 << 2) | (c1 >> 4)
b1 = (c1 << 4) | (c2 >> 2)
b2 = (c2 << 6) | c3
buf[0] = byte(b0)
buf[1] = byte(b1)
buf[2] = byte(b2)
}
io.write_full(w, buf[:]) or_return
}
rest := length - j
if rest > 0 {
#no_bounds_check {
c0 = DEC_TBL[data[i]]
c1 = DEC_TBL[data[i + 1]]
c2 = DEC_TBL[data[i + 2]]
b0 = (c0 << 2) | (c1 >> 4)
b1 = (c1 << 4) | (c2 >> 2)
}
switch rest {
case 1: io.write_byte(w, byte(b0)) or_return
case 2: io.write_full(w, {byte(b0), byte(b1)}) or_return
}
}
return nil
}
decoded_len :: proc(data: string) -> int {
length := len(data)
if length == 0 {
return 0
}
padding: int
if data[length - 1] == PADDING {
if length > 1 && data[length - 2] == PADDING {
padding = 2
} else {
padding = 1
}
}
return ((length * 6) >> 3) - padding
}
+673
View File
@@ -0,0 +1,673 @@
package encoding_cbor
import "base:intrinsics"
import "core:encoding/json"
import "core:io"
import "core:mem"
import "core:strconv"
import "core:strings"
// If we are decoding a stream of either a map or list, the initial capacity will be this value.
INITIAL_STREAMED_CONTAINER_CAPACITY :: 8
// If we are decoding a stream of either text or bytes, the initial capacity will be this value.
INITIAL_STREAMED_BYTES_CAPACITY :: 16
// The default maximum amount of bytes to allocate on a buffer/container at once to prevent
// malicious input from causing massive allocations.
DEFAULT_MAX_PRE_ALLOC :: mem.Kilobyte
// Known/common headers are defined, undefined headers can still be valid.
// Higher 3 bits is for the major type and lower 5 bits for the additional information.
Header :: enum u8 {
U8 = (u8(Major.Unsigned) << 5) | u8(Add.One_Byte),
U16 = (u8(Major.Unsigned) << 5) | u8(Add.Two_Bytes),
U32 = (u8(Major.Unsigned) << 5) | u8(Add.Four_Bytes),
U64 = (u8(Major.Unsigned) << 5) | u8(Add.Eight_Bytes),
Neg_U8 = (u8(Major.Negative) << 5) | u8(Add.One_Byte),
Neg_U16 = (u8(Major.Negative) << 5) | u8(Add.Two_Bytes),
Neg_U32 = (u8(Major.Negative) << 5) | u8(Add.Four_Bytes),
Neg_U64 = (u8(Major.Negative) << 5) | u8(Add.Eight_Bytes),
False = (u8(Major.Other) << 5) | u8(Add.False),
True = (u8(Major.Other) << 5) | u8(Add.True),
Nil = (u8(Major.Other) << 5) | u8(Add.Nil),
Undefined = (u8(Major.Other) << 5) | u8(Add.Undefined),
Simple = (u8(Major.Other) << 5) | u8(Add.One_Byte),
F16 = (u8(Major.Other) << 5) | u8(Add.Two_Bytes),
F32 = (u8(Major.Other) << 5) | u8(Add.Four_Bytes),
F64 = (u8(Major.Other) << 5) | u8(Add.Eight_Bytes),
Break = (u8(Major.Other) << 5) | u8(Add.Break),
}
// The higher 3 bits of the header which denotes what type of value it is.
Major :: enum u8 {
Unsigned,
Negative,
Bytes,
Text,
Array,
Map,
Tag,
Other,
}
// The lower 3 bits of the header which denotes additional information for the type of value.
Add :: enum u8 {
False = 20,
True = 21,
Nil = 22,
Undefined = 23,
One_Byte = 24,
Two_Bytes = 25,
Four_Bytes = 26,
Eight_Bytes = 27,
Length_Unknown = 31,
Break = Length_Unknown,
}
Value :: union {
u8,
u16,
u32,
u64,
Negative_U8,
Negative_U16,
Negative_U32,
Negative_U64,
// Pointers so the size of the Value union stays small.
^Bytes,
^Text,
^Array,
^Map,
^Tag,
Simple,
f16,
f32,
f64,
bool,
Undefined,
Nil,
}
Bytes :: []byte
Text :: string
Array :: []Value
Map :: []Map_Entry
Map_Entry :: struct {
key: Value, // Can be any unsigned, negative, float, Simple, bool, Text.
value: Value,
}
Tag :: struct {
number: Tag_Number,
value: Value, // Value based on the number.
}
Tag_Number :: u64
Nil :: distinct rawptr
Undefined :: distinct rawptr
// A distinct atom-like number, range from `0..=19` and `32..=max(u8)`.
Simple :: distinct u8
Atom :: Simple
Unmarshal_Error :: union #shared_nil {
io.Error,
mem.Allocator_Error,
Decode_Data_Error,
Unmarshal_Data_Error,
Maybe(Unsupported_Type_Error),
}
Marshal_Error :: union #shared_nil {
io.Error,
mem.Allocator_Error,
Encode_Data_Error,
Marshal_Data_Error,
Maybe(Unsupported_Type_Error),
}
Decode_Error :: union #shared_nil {
io.Error,
mem.Allocator_Error,
Decode_Data_Error,
}
Encode_Error :: union #shared_nil {
io.Error,
mem.Allocator_Error,
Encode_Data_Error,
}
Decode_Data_Error :: enum {
None,
Bad_Major, // An invalid major type was encountered.
Bad_Argument, // A general unexpected value (most likely invalid additional info in header).
Bad_Tag_Value, // When the type of value for the given tag is not valid.
Nested_Indefinite_Length, // When an streamed/indefinite length container nests another, this is not allowed.
Nested_Tag, // When a tag's value is another tag, this is not allowed.
Length_Too_Big, // When the length of a container (map, array, bytes, string) is more than `max(int)`.
Disallowed_Streaming, // When the `.Disallow_Streaming` flag is set and a streaming header is encountered.
Break, // When the `break` header was found without any stream to break off.
}
Encode_Data_Error :: enum {
None,
Invalid_Simple, // When a simple is being encoded that is out of the range `0..=19` and `32..=max(u8)`.
Int_Too_Big, // When an int is being encoded that is larger than `max(u64)` or smaller than `min(u64)`.
Bad_Tag_Value, // When the type of value is not supported by the tag implementation.
}
Unmarshal_Data_Error :: enum {
None,
Invalid_Parameter, // When the given `any` can not be unmarshalled into.
Non_Pointer_Parameter, // When the given `any` is not a pointer.
}
Marshal_Data_Error :: enum {
None,
Invalid_CBOR_Tag, // When the struct tag `cbor_tag:""` is not a registered name or number.
}
// Error that is returned when a type couldn't be marshalled into or out of, as much information
// as possible/available is added.
Unsupported_Type_Error :: struct {
id: typeid,
hdr: Header,
add: Add,
}
_unsupported :: proc(v: any, hdr: Header, add: Add = nil) -> Maybe(Unsupported_Type_Error) {
return Unsupported_Type_Error{
id = v.id,
hdr = hdr,
add = add,
}
}
// Actual value is `-1 - x` (be careful of overflows).
Negative_U8 :: distinct u8
Negative_U16 :: distinct u16
Negative_U32 :: distinct u32
Negative_U64 :: distinct u64
// Turns the CBOR negative unsigned int type into a signed integer type.
negative_to_int :: proc {
negative_u8_to_int,
negative_u16_to_int,
negative_u32_to_int,
negative_u64_to_int,
}
negative_u8_to_int :: #force_inline proc(u: Negative_U8) -> i16 {
return -1 - i16(u)
}
negative_u16_to_int :: #force_inline proc(u: Negative_U16) -> i32 {
return -1 - i32(u)
}
negative_u32_to_int :: #force_inline proc(u: Negative_U32) -> i64 {
return -1 - i64(u)
}
negative_u64_to_int :: #force_inline proc(u: Negative_U64) -> i128 {
return -1 - i128(u)
}
// Utility for converting between the different errors when they are subsets of the other.
err_conv :: proc {
encode_to_marshal_err,
encode_to_marshal_err_p2,
decode_to_unmarshal_err,
decode_to_unmarshal_err_p,
decode_to_unmarshal_err_p2,
}
encode_to_marshal_err :: #force_inline proc(err: Encode_Error) -> Marshal_Error {
switch e in err {
case nil: return nil
case io.Error: return e
case mem.Allocator_Error: return e
case Encode_Data_Error: return e
case: return nil
}
}
encode_to_marshal_err_p2 :: #force_inline proc(v: $T, v2: $T2, err: Encode_Error) -> (T, T2, Marshal_Error) {
return v, v2, err_conv(err)
}
decode_to_unmarshal_err :: #force_inline proc(err: Decode_Error) -> Unmarshal_Error {
switch e in err {
case nil: return nil
case io.Error: return e
case mem.Allocator_Error: return e
case Decode_Data_Error: return e
case: return nil
}
}
decode_to_unmarshal_err_p :: #force_inline proc(v: $T, err: Decode_Error) -> (T, Unmarshal_Error) {
return v, err_conv(err)
}
decode_to_unmarshal_err_p2 :: #force_inline proc(v: $T, v2: $T2, err: Decode_Error) -> (T, T2, Unmarshal_Error) {
return v, v2, err_conv(err)
}
// Recursively frees all memory allocated when decoding the passed value.
destroy :: proc(val: Value, allocator := context.allocator) {
context.allocator = allocator
#partial switch v in val {
case ^Map:
if v == nil { return }
for entry in v {
destroy(entry.key)
destroy(entry.value)
}
delete(v^)
free(v)
case ^Array:
if v == nil { return }
for entry in v {
destroy(entry)
}
delete(v^)
free(v)
case ^Text:
if v == nil { return }
delete(v^)
free(v)
case ^Bytes:
if v == nil { return }
delete(v^)
free(v)
case ^Tag:
if v == nil { return }
destroy(v.value)
free(v)
}
}
/*
to_diagnostic_format either writes or returns a human-readable representation of the value,
optionally formatted, defined as the diagnostic format in [[RFC 8949 Section 8;https://www.rfc-editor.org/rfc/rfc8949.html#name-diagnostic-notation]].
Incidentally, if the CBOR does not contain any of the additional types defined on top of JSON
this will also be valid JSON.
*/
to_diagnostic_format :: proc {
to_diagnostic_format_string,
to_diagnostic_format_writer,
}
// Turns the given CBOR value into a human-readable string.
// See docs on the proc group `diagnose` for more info.
to_diagnostic_format_string :: proc(val: Value, padding := 0, allocator := context.allocator) -> (string, mem.Allocator_Error) #optional_allocator_error {
b := strings.builder_make(allocator)
w := strings.to_stream(&b)
err := to_diagnostic_format_writer(w, val, padding)
if err == .EOF {
// The string builder stream only returns .EOF, and only if it can't write (out of memory).
return "", .Out_Of_Memory
}
assert(err == nil)
return strings.to_string(b), nil
}
// Writes the given CBOR value into the writer as human-readable text.
// See docs on the proc group `diagnose` for more info.
to_diagnostic_format_writer :: proc(w: io.Writer, val: Value, padding := 0) -> io.Error {
@(require_results)
indent :: proc(padding: int) -> int {
padding := padding
if padding != -1 {
padding += 1
}
return padding
}
@(require_results)
dedent :: proc(padding: int) -> int {
padding := padding
if padding != -1 {
padding -= 1
}
return padding
}
comma :: proc(w: io.Writer, padding: int) -> io.Error {
_ = io.write_string(w, ", " if padding == -1 else ",") or_return
return nil
}
newline :: proc(w: io.Writer, padding: int) -> io.Error {
if padding != -1 {
io.write_string(w, "\n") or_return
for _ in 0..<padding {
io.write_string(w, "\t") or_return
}
}
return nil
}
padding := padding
switch v in val {
case u8: io.write_uint(w, uint(v)) or_return
case u16: io.write_uint(w, uint(v)) or_return
case u32: io.write_uint(w, uint(v)) or_return
case u64: io.write_u64(w, v) or_return
case Negative_U8: io.write_int(w, int(negative_to_int(v))) or_return
case Negative_U16: io.write_int(w, int(negative_to_int(v))) or_return
case Negative_U32: io.write_int(w, int(negative_to_int(v))) or_return
case Negative_U64: io.write_i128(w, i128(negative_to_int(v))) or_return
// NOTE: not using io.write_float because it removes the sign,
// which we want for the diagnostic format.
case f16:
buf: [64]byte
str := strconv.append_float(buf[:], f64(v), 'f', 2*size_of(f16), 8*size_of(f16))
if str[0] == '+' && str != "+Inf" { str = str[1:] }
io.write_string(w, str) or_return
case f32:
buf: [128]byte
str := strconv.append_float(buf[:], f64(v), 'f', 2*size_of(f32), 8*size_of(f32))
if str[0] == '+' && str != "+Inf" { str = str[1:] }
io.write_string(w, str) or_return
case f64:
buf: [256]byte
str := strconv.append_float(buf[:], f64(v), 'f', 2*size_of(f64), 8*size_of(f64))
if str[0] == '+' && str != "+Inf" { str = str[1:] }
io.write_string(w, str) or_return
case bool: io.write_string(w, "true" if v else "false") or_return
case Nil: io.write_string(w, "nil") or_return
case Undefined: io.write_string(w, "undefined") or_return
case ^Bytes:
io.write_string(w, "h'") or_return
for b in v { io.write_int(w, int(b), 16) or_return }
io.write_string(w, "'") or_return
case ^Text:
io.write_string(w, `"`) or_return
io.write_string(w, v^) or_return
io.write_string(w, `"`) or_return
case ^Array:
if v == nil || len(v) == 0 {
io.write_string(w, "[]") or_return
return nil
}
io.write_string(w, "[") or_return
padding = indent(padding)
newline(w, padding) or_return
for entry, i in v {
to_diagnostic_format(w, entry, padding) or_return
if i != len(v)-1 {
comma(w, padding) or_return
newline(w, padding) or_return
}
}
padding = dedent(padding)
newline(w, padding) or_return
io.write_string(w, "]") or_return
case ^Map:
if v == nil || len(v) == 0 {
io.write_string(w, "{}") or_return
return nil
}
io.write_string(w, "{") or_return
padding = indent(padding)
newline(w, padding) or_return
for entry, i in v {
to_diagnostic_format(w, entry.key, padding) or_return
io.write_string(w, ": ") or_return
to_diagnostic_format(w, entry.value, padding) or_return
if i != len(v)-1 {
comma(w, padding) or_return
newline(w, padding) or_return
}
}
padding = dedent(padding)
newline(w, padding) or_return
io.write_string(w, "}") or_return
case ^Tag:
io.write_u64(w, v.number) or_return
io.write_string(w, "(") or_return
to_diagnostic_format(w, v.value, padding) or_return
io.write_string(w, ")") or_return
case Simple:
io.write_string(w, "simple(") or_return
io.write_uint(w, uint(v)) or_return
io.write_string(w, ")") or_return
}
return nil
}
/*
Converts from JSON to CBOR.
Everything is copied to the given allocator, the passed in JSON value can be deleted after.
*/
from_json :: proc(val: json.Value, allocator := context.allocator) -> (Value, mem.Allocator_Error) #optional_allocator_error {
internal :: proc(val: json.Value) -> (ret: Value, err: mem.Allocator_Error) {
switch v in val {
case json.Null: return Nil{}, nil
case json.Integer:
i, major := _int_to_uint(v)
#partial switch major {
case .Unsigned: return i, nil
case .Negative: return Negative_U64(i), nil
case: unreachable()
}
case json.Float: return v, nil
case json.Boolean: return v, nil
case json.String:
container := new(Text) or_return
// We need the string to have a nil byte at the end so we clone to cstring.
container^ = string(strings.clone_to_cstring(v) or_return)
return container, nil
case json.Array:
arr := new(Array) or_return
arr^ = make([]Value, len(v)) or_return
for _, i in arr {
arr[i] = internal(v[i]) or_return
}
return arr, nil
case json.Object:
m := new(Map) or_return
dm := make([dynamic]Map_Entry, 0, len(v)) or_return
for mkey, mval in v {
append(&dm, Map_Entry{from_json(mkey) or_return, from_json(mval) or_return})
}
m^ = dm[:]
return m, nil
}
return nil, nil
}
context.allocator = allocator
return internal(val)
}
/*
Converts from CBOR to JSON.
NOTE: overflow on integers or floats is not handled.
Everything is copied to the given allocator, the passed in CBOR value can be `destroy`'ed after.
If a CBOR map with non-string keys is encountered it is turned into an array of tuples.
*/
to_json :: proc(val: Value, allocator := context.allocator) -> (json.Value, mem.Allocator_Error) #optional_allocator_error {
internal :: proc(val: Value) -> (ret: json.Value, err: mem.Allocator_Error) {
switch v in val {
case Simple: return json.Integer(v), nil
case u8: return json.Integer(v), nil
case u16: return json.Integer(v), nil
case u32: return json.Integer(v), nil
case u64: return json.Integer(v), nil
case Negative_U8: return json.Integer(negative_to_int(v)), nil
case Negative_U16: return json.Integer(negative_to_int(v)), nil
case Negative_U32: return json.Integer(negative_to_int(v)), nil
case Negative_U64: return json.Integer(negative_to_int(v)), nil
case f16: return json.Float(v), nil
case f32: return json.Float(v), nil
case f64: return json.Float(v), nil
case bool: return json.Boolean(v), nil
case Undefined: return json.Null{}, nil
case Nil: return json.Null{}, nil
case ^Bytes: return json.String(strings.clone(string(v^)) or_return), nil
case ^Text: return json.String(strings.clone(v^) or_return), nil
case ^Map:
keys_all_strings :: proc(m: ^Map) -> bool {
for entry in m {
#partial switch kv in entry.key {
case ^Bytes:
case ^Text:
case: return false
}
}
return false
}
if keys_all_strings(v) {
obj := make(json.Object, len(v)) or_return
for entry in v {
k: string
#partial switch kv in entry.key {
case ^Bytes: k = string(kv^)
case ^Text: k = kv^
case: unreachable()
}
v := internal(entry.value) or_return
obj[k] = v
}
return obj, nil
} else {
// Resort to an array of tuples if keys aren't all strings.
arr := make(json.Array, 0, len(v)) or_return
for entry in v {
entry_arr := make(json.Array, 0, 2) or_return
append(&entry_arr, internal(entry.key) or_return) or_return
append(&entry_arr, internal(entry.value) or_return) or_return
append(&arr, entry_arr) or_return
}
return arr, nil
}
case ^Array:
arr := make(json.Array, 0, len(v)) or_return
for entry in v {
append(&arr, internal(entry) or_return) or_return
}
return arr, nil
case ^Tag:
obj := make(json.Object, 2) or_return
obj[strings.clone("number") or_return] = internal(v.number) or_return
obj[strings.clone("value") or_return] = internal(v.value) or_return
return obj, nil
case: return json.Null{}, nil
}
}
context.allocator = allocator
return internal(val)
}
_int_to_uint :: proc {
_i8_to_uint,
_i16_to_uint,
_i32_to_uint,
_i64_to_uint,
_i128_to_uint,
}
_u128_to_u64 :: #force_inline proc(v: u128) -> (u64, Encode_Data_Error) {
if v > u128(max(u64)) {
return 0, .Int_Too_Big
}
return u64(v), nil
}
_i8_to_uint :: #force_inline proc(v: i8) -> (u: u8, m: Major) {
if v < 0 {
return u8(abs(v)-1), .Negative
}
return u8(v), .Unsigned
}
_i16_to_uint :: #force_inline proc(v: i16) -> (u: u16, m: Major) {
if v < 0 {
return u16(abs(v)-1), .Negative
}
return u16(v), .Unsigned
}
_i32_to_uint :: #force_inline proc(v: i32) -> (u: u32, m: Major) {
if v < 0 {
return u32(abs(v)-1), .Negative
}
return u32(v), .Unsigned
}
_i64_to_uint :: #force_inline proc(v: i64) -> (u: u64, m: Major) {
if v < 0 {
return u64(abs(v)-1), .Negative
}
return u64(v), .Unsigned
}
_i128_to_uint :: proc(v: i128) -> (u: u64, m: Major, err: Encode_Data_Error) {
if v < 0 {
m = .Negative
u, err = _u128_to_u64(u128(abs(v) - 1))
return
}
m = .Unsigned
u, err = _u128_to_u64(u128(v))
return
}
+886
View File
@@ -0,0 +1,886 @@
package encoding_cbor
import "base:intrinsics"
import "base:runtime"
import "core:bytes"
import "core:encoding/endian"
import "core:io"
import "core:slice"
import "core:strings"
Encoder_Flag :: enum {
// CBOR defines a tag header that also acts as a file/binary header,
// this way decoders can check the first header of the binary and see if it is CBOR.
Self_Described_CBOR,
// Integers are stored in the smallest integer type it fits.
// This involves checking each int against the max of all its smaller types.
Deterministic_Int_Size,
// Floats are stored in the smallest size float type without losing precision.
// This involves casting each float down to its smaller types and checking if it changed.
Deterministic_Float_Size,
// Sort maps by their keys in bytewise lexicographic order of their deterministic encoding.
// NOTE: In order to do this, all keys of a map have to be pre-computed, sorted, and
// then written, this involves temporary allocations for the keys and a copy of the map itself.
Deterministic_Map_Sorting,
}
Encoder_Flags :: bit_set[Encoder_Flag]
// Flags for fully deterministic output (if you are not using streaming/indeterminate length).
ENCODE_FULLY_DETERMINISTIC :: Encoder_Flags{.Deterministic_Int_Size, .Deterministic_Float_Size, .Deterministic_Map_Sorting}
// Flags for the smallest encoding output.
ENCODE_SMALL :: Encoder_Flags{.Deterministic_Int_Size, .Deterministic_Float_Size}
Encoder :: struct {
flags: Encoder_Flags,
writer: io.Writer,
temp_allocator: runtime.Allocator,
}
Decoder_Flag :: enum {
// Rejects (with an error `.Disallowed_Streaming`) when a streaming CBOR header is encountered.
Disallow_Streaming,
// Pre-allocates buffers and containers with the size that was set in the CBOR header.
// This should only be enabled when you control both ends of the encoding, if you don't,
// attackers can craft input that causes massive (`max(u64)`) byte allocations for a few bytes of
// CBOR.
Trusted_Input,
// Makes the decoder shrink of excess capacity from allocated buffers/containers before returning.
Shrink_Excess,
}
Decoder_Flags :: bit_set[Decoder_Flag]
Decoder :: struct {
// The max amount of bytes allowed to pre-allocate when `.Trusted_Input` is not set on the
// flags.
max_pre_alloc: int,
flags: Decoder_Flags,
reader: io.Reader,
}
/*
Decodes both deterministic and non-deterministic CBOR into a `Value` variant.
`Text` and `Bytes` can safely be cast to cstrings because of an added 0 byte.
Allocations are done using the given allocator,
*no* allocations are done on the `context.temp_allocator`.
A value can be (fully and recursively) deallocated using the `destroy` proc in this package.
Disable streaming/indeterminate lengths with the `.Disallow_Streaming` flag.
Shrink excess bytes in buffers and containers with the `.Shrink_Excess` flag.
Mark the input as trusted input with the `.Trusted_Input` flag, this turns off the safety feature
of not pre-allocating more than `max_pre_alloc` bytes before reading into the bytes. You should only
do this when you own both sides of the encoding and are sure there can't be malicious bytes used as
an input.
*/
decode_from :: proc {
decode_from_string,
decode_from_reader,
decode_from_decoder,
}
decode :: decode_from
// Decodes the given string as CBOR.
// See docs on the proc group `decode` for more information.
decode_from_string :: proc(s: string, flags: Decoder_Flags = {}, allocator := context.allocator) -> (v: Value, err: Decode_Error) {
r: strings.Reader
strings.reader_init(&r, s)
return decode_from_reader(strings.reader_to_stream(&r), flags, allocator)
}
// Reads a CBOR value from the given reader.
// See docs on the proc group `decode` for more information.
decode_from_reader :: proc(r: io.Reader, flags: Decoder_Flags = {}, allocator := context.allocator) -> (v: Value, err: Decode_Error) {
return decode_from_decoder(
Decoder{ DEFAULT_MAX_PRE_ALLOC, flags, r },
allocator=allocator,
)
}
// Reads a CBOR value from the given decoder.
// See docs on the proc group `decode` for more information.
decode_from_decoder :: proc(d: Decoder, allocator := context.allocator) -> (v: Value, err: Decode_Error) {
context.allocator = allocator
d := d
if d.max_pre_alloc <= 0 {
d.max_pre_alloc = DEFAULT_MAX_PRE_ALLOC
}
v, err = _decode_from_decoder(d)
// Normal EOF does not exist here, we try to read the exact amount that is said to be provided.
if err == .EOF { err = .Unexpected_EOF }
return
}
_decode_from_decoder :: proc(d: Decoder, hdr: Header = Header(0)) -> (v: Value, err: Decode_Error) {
hdr := hdr
r := d.reader
if hdr == Header(0) { hdr = _decode_header(r) or_return }
switch hdr {
case .U8: return _decode_u8 (r)
case .U16: return _decode_u16(r)
case .U32: return _decode_u32(r)
case .U64: return _decode_u64(r)
case .Neg_U8: return Negative_U8 (_decode_u8 (r) or_return), nil
case .Neg_U16: return Negative_U16(_decode_u16(r) or_return), nil
case .Neg_U32: return Negative_U32(_decode_u32(r) or_return), nil
case .Neg_U64: return Negative_U64(_decode_u64(r) or_return), nil
case .Simple: return _decode_simple(r)
case .F16: return _decode_f16(r)
case .F32: return _decode_f32(r)
case .F64: return _decode_f64(r)
case .True: return true, nil
case .False: return false, nil
case .Nil: return Nil{}, nil
case .Undefined: return Undefined{}, nil
case .Break: return nil, .Break
}
maj, add := _header_split(hdr)
switch maj {
case .Unsigned: return _decode_tiny_u8(add)
case .Negative: return Negative_U8(_decode_tiny_u8(add) or_return), nil
case .Bytes: return _decode_bytes_ptr(d, add)
case .Text: return _decode_text_ptr(d, add)
case .Array: return _decode_array_ptr(d, add)
case .Map: return _decode_map_ptr(d, add)
case .Tag: return _decode_tag_ptr(d, add)
case .Other: return _decode_tiny_simple(add)
case: return nil, .Bad_Major
}
}
/*
Encodes the CBOR value into a binary CBOR.
Flags can be used to control the output (mainly determinism, which coincidently affects size).
The default flags `ENCODE_SMALL` (`.Deterministic_Int_Size`, `.Deterministic_Float_Size`) will try
to put ints and floats into their smallest possible byte size without losing equality.
Adding the `.Self_Described_CBOR` flag will wrap the value in a tag that lets generic decoders know
the contents are CBOR from just reading the first byte.
Adding the `.Deterministic_Map_Sorting` flag will sort the encoded maps by the byte content of the
encoded key. This flag has a cost on performance and memory efficiency because all keys in a map
have to be precomputed, sorted and only then written to the output.
Empty flags will do nothing extra to the value.
The allocations for the `.Deterministic_Map_Sorting` flag are done using the given temp_allocator.
but are followed by the necessary `delete` and `free` calls if the allocator supports them.
This is helpful when the CBOR size is so big that you don't want to collect all the temporary
allocations until the end.
*/
encode_into :: proc {
encode_into_bytes,
encode_into_builder,
encode_into_writer,
encode_into_encoder,
}
encode :: encode_into
// Encodes the CBOR value into binary CBOR allocated on the given allocator.
// See the docs on the proc group `encode_into` for more info.
encode_into_bytes :: proc(v: Value, flags := ENCODE_SMALL, allocator := context.allocator, temp_allocator := context.temp_allocator) -> (data: []byte, err: Encode_Error) {
b := strings.builder_make(allocator) or_return
encode_into_builder(&b, v, flags, temp_allocator) or_return
return b.buf[:], nil
}
// Encodes the CBOR value into binary CBOR written to the given builder.
// See the docs on the proc group `encode_into` for more info.
encode_into_builder :: proc(b: ^strings.Builder, v: Value, flags := ENCODE_SMALL, temp_allocator := context.temp_allocator) -> Encode_Error {
return encode_into_writer(strings.to_stream(b), v, flags, temp_allocator)
}
// Encodes the CBOR value into binary CBOR written to the given writer.
// See the docs on the proc group `encode_into` for more info.
encode_into_writer :: proc(w: io.Writer, v: Value, flags := ENCODE_SMALL, temp_allocator := context.temp_allocator) -> Encode_Error {
return encode_into_encoder(Encoder{flags, w, temp_allocator}, v)
}
// Encodes the CBOR value into binary CBOR written to the given encoder.
// See the docs on the proc group `encode_into` for more info.
encode_into_encoder :: proc(e: Encoder, v: Value) -> Encode_Error {
e := e
if e.temp_allocator.procedure == nil {
e.temp_allocator = context.temp_allocator
}
if .Self_Described_CBOR in e.flags {
_encode_u64(e, TAG_SELF_DESCRIBED_CBOR, .Tag) or_return
e.flags &~= { .Self_Described_CBOR }
}
switch v_spec in v {
case u8: return _encode_u8(e.writer, v_spec, .Unsigned)
case u16: return _encode_u16(e, v_spec, .Unsigned)
case u32: return _encode_u32(e, v_spec, .Unsigned)
case u64: return _encode_u64(e, v_spec, .Unsigned)
case Negative_U8: return _encode_u8(e.writer, u8(v_spec), .Negative)
case Negative_U16: return _encode_u16(e, u16(v_spec), .Negative)
case Negative_U32: return _encode_u32(e, u32(v_spec), .Negative)
case Negative_U64: return _encode_u64(e, u64(v_spec), .Negative)
case ^Bytes: return _encode_bytes(e, v_spec^)
case ^Text: return _encode_text(e, v_spec^)
case ^Array: return _encode_array(e, v_spec^)
case ^Map: return _encode_map(e, v_spec^)
case ^Tag: return _encode_tag(e, v_spec^)
case Simple: return _encode_simple(e.writer, v_spec)
case f16: return _encode_f16(e.writer, v_spec)
case f32: return _encode_f32(e, v_spec)
case f64: return _encode_f64(e, v_spec)
case bool: return _encode_bool(e.writer, v_spec)
case Nil: return _encode_nil(e.writer)
case Undefined: return _encode_undefined(e.writer)
case: return nil
}
}
_decode_header :: proc(r: io.Reader) -> (hdr: Header, err: io.Error) {
hdr = Header(_decode_u8(r) or_return)
return
}
_header_split :: proc(hdr: Header) -> (Major, Add) {
return Major(u8(hdr) >> 5), Add(u8(hdr) & 0x1f)
}
_decode_u8 :: proc(r: io.Reader) -> (v: u8, err: io.Error) {
byte: [1]byte = ---
io.read_full(r, byte[:]) or_return
return byte[0], nil
}
_encode_uint :: proc {
_encode_u8,
_encode_u16,
_encode_u32,
_encode_u64,
}
_encode_u8 :: proc(w: io.Writer, v: u8, major: Major = .Unsigned) -> (err: io.Error) {
header := u8(major) << 5
if v < u8(Add.One_Byte) {
header |= v
_, err = io.write_full(w, {header})
return
}
header |= u8(Add.One_Byte)
_, err = io.write_full(w, {header, v})
return
}
_decode_tiny_u8 :: proc(additional: Add) -> (u8, Decode_Data_Error) {
if additional < .One_Byte {
return u8(additional), nil
}
return 0, .Bad_Argument
}
_decode_u16 :: proc(r: io.Reader) -> (v: u16, err: io.Error) {
bytes: [2]byte = ---
io.read_full(r, bytes[:]) or_return
return endian.unchecked_get_u16be(bytes[:]), nil
}
_encode_u16 :: proc(e: Encoder, v: u16, major: Major = .Unsigned) -> Encode_Error {
if .Deterministic_Int_Size in e.flags {
return _encode_deterministic_uint(e.writer, v, major)
}
return _encode_u16_exact(e.writer, v, major)
}
_encode_u16_exact :: proc(w: io.Writer, v: u16, major: Major = .Unsigned) -> (err: io.Error) {
bytes: [3]byte = ---
bytes[0] = (u8(major) << 5) | u8(Add.Two_Bytes)
endian.unchecked_put_u16be(bytes[1:], v)
_, err = io.write_full(w, bytes[:])
return
}
_decode_u32 :: proc(r: io.Reader) -> (v: u32, err: io.Error) {
bytes: [4]byte = ---
io.read_full(r, bytes[:]) or_return
return endian.unchecked_get_u32be(bytes[:]), nil
}
_encode_u32 :: proc(e: Encoder, v: u32, major: Major = .Unsigned) -> Encode_Error {
if .Deterministic_Int_Size in e.flags {
return _encode_deterministic_uint(e.writer, v, major)
}
return _encode_u32_exact(e.writer, v, major)
}
_encode_u32_exact :: proc(w: io.Writer, v: u32, major: Major = .Unsigned) -> (err: io.Error) {
bytes: [5]byte = ---
bytes[0] = (u8(major) << 5) | u8(Add.Four_Bytes)
endian.unchecked_put_u32be(bytes[1:], v)
_, err = io.write_full(w, bytes[:])
return
}
_decode_u64 :: proc(r: io.Reader) -> (v: u64, err: io.Error) {
bytes: [8]byte = ---
io.read_full(r, bytes[:]) or_return
return endian.unchecked_get_u64be(bytes[:]), nil
}
_encode_u64 :: proc(e: Encoder, v: u64, major: Major = .Unsigned) -> Encode_Error {
if .Deterministic_Int_Size in e.flags {
return _encode_deterministic_uint(e.writer, v, major)
}
return _encode_u64_exact(e.writer, v, major)
}
_encode_u64_exact :: proc(w: io.Writer, v: u64, major: Major = .Unsigned) -> (err: io.Error) {
bytes: [9]byte = ---
bytes[0] = (u8(major) << 5) | u8(Add.Eight_Bytes)
endian.unchecked_put_u64be(bytes[1:], v)
_, err = io.write_full(w, bytes[:])
return
}
_decode_bytes_ptr :: proc(d: Decoder, add: Add, type: Major = .Bytes) -> (v: ^Bytes, err: Decode_Error) {
v = new(Bytes) or_return
defer if err != nil { free(v) }
v^ = _decode_bytes(d, add, type) or_return
return
}
_decode_bytes :: proc(d: Decoder, add: Add, type: Major = .Bytes, allocator := context.allocator) -> (v: Bytes, err: Decode_Error) {
context.allocator = allocator
add := add
n, scap := _decode_len_str(d, add) or_return
buf := strings.builder_make(0, scap) or_return
defer if err != nil { strings.builder_destroy(&buf) }
buf_stream := strings.to_stream(&buf)
if n == -1 {
indefinite_loop: for {
header := _decode_header(d.reader) or_return
maj: Major
maj, add = _header_split(header)
#partial switch maj {
case type:
iter_n, iter_cap := _decode_len_str(d, add) or_return
if iter_n == -1 {
return nil, .Nested_Indefinite_Length
}
reserve(&buf.buf, len(buf.buf) + iter_cap) or_return
io.copy_n(buf_stream, d.reader, i64(iter_n)) or_return
case .Other:
if add != .Break { return nil, .Bad_Argument }
break indefinite_loop
case:
return nil, .Bad_Major
}
}
} else {
io.copy_n(buf_stream, d.reader, i64(n)) or_return
}
v = buf.buf[:]
// Write zero byte so this can be converted to cstring.
strings.write_byte(&buf, 0)
if .Shrink_Excess in d.flags { shrink(&buf.buf) }
return
}
_encode_bytes :: proc(e: Encoder, val: Bytes, major: Major = .Bytes) -> (err: Encode_Error) {
assert(len(val) >= 0)
_encode_u64(e, u64(len(val)), major) or_return
_, err = io.write_full(e.writer, val[:])
return
}
_decode_text_ptr :: proc(d: Decoder, add: Add) -> (v: ^Text, err: Decode_Error) {
v = new(Text) or_return
defer if err != nil { free(v) }
v^ = _decode_text(d, add) or_return
return
}
_decode_text :: proc(d: Decoder, add: Add, allocator := context.allocator) -> (v: Text, err: Decode_Error) {
return (Text)(_decode_bytes(d, add, .Text, allocator) or_return), nil
}
_encode_text :: proc(e: Encoder, val: Text) -> Encode_Error {
return _encode_bytes(e, transmute([]byte)val, .Text)
}
_decode_array_ptr :: proc(d: Decoder, add: Add) -> (v: ^Array, err: Decode_Error) {
v = new(Array) or_return
defer if err != nil { free(v) }
v^ = _decode_array(d, add) or_return
return
}
_decode_array :: proc(d: Decoder, add: Add) -> (v: Array, err: Decode_Error) {
n, scap := _decode_len_container(d, add) or_return
array := make([dynamic]Value, 0, scap) or_return
defer if err != nil {
for entry in array { destroy(entry) }
delete(array)
}
for i := 0; n == -1 || i < n; i += 1 {
val, verr := _decode_from_decoder(d)
if n == -1 && verr == .Break {
break
} else if verr != nil {
err = verr
return
}
append(&array, val) or_return
}
if .Shrink_Excess in d.flags { shrink(&array) }
v = array[:]
return
}
_encode_array :: proc(e: Encoder, arr: Array) -> Encode_Error {
assert(len(arr) >= 0)
_encode_u64(e, u64(len(arr)), .Array)
for val in arr {
encode(e, val) or_return
}
return nil
}
_decode_map_ptr :: proc(d: Decoder, add: Add) -> (v: ^Map, err: Decode_Error) {
v = new(Map) or_return
defer if err != nil { free(v) }
v^ = _decode_map(d, add) or_return
return
}
_decode_map :: proc(d: Decoder, add: Add) -> (v: Map, err: Decode_Error) {
n, scap := _decode_len_container(d, add) or_return
items := make([dynamic]Map_Entry, 0, scap) or_return
defer if err != nil {
for entry in items {
destroy(entry.key)
destroy(entry.value)
}
delete(items)
}
for i := 0; n == -1 || i < n; i += 1 {
key, kerr := _decode_from_decoder(d)
if n == -1 && kerr == .Break {
break
} else if kerr != nil {
return nil, kerr
}
value := _decode_from_decoder(d) or_return
append(&items, Map_Entry{
key = key,
value = value,
}) or_return
}
if .Shrink_Excess in d.flags { shrink(&items) }
v = items[:]
return
}
_encode_map :: proc(e: Encoder, m: Map) -> (err: Encode_Error) {
assert(len(m) >= 0)
_encode_u64(e, u64(len(m)), .Map) or_return
if .Deterministic_Map_Sorting not_in e.flags {
for entry in m {
encode(e, entry.key) or_return
encode(e, entry.value) or_return
}
return
}
// Deterministic_Map_Sorting needs us to sort the entries by the byte contents of the
// encoded key.
//
// This means we have to store and sort them before writing incurring extra (temporary) allocations.
Map_Entry_With_Key :: struct {
encoded_key: []byte,
entry: Map_Entry,
}
entries := make([]Map_Entry_With_Key, len(m), e.temp_allocator) or_return
defer delete(entries, e.temp_allocator)
for &entry, i in entries {
entry.entry = m[i]
buf := strings.builder_make(e.temp_allocator) or_return
ke := e
ke.writer = strings.to_stream(&buf)
encode(ke, entry.entry.key) or_return
entry.encoded_key = buf.buf[:]
}
// Sort lexicographic on the bytes of the key.
slice.sort_by_cmp(entries, proc(a, b: Map_Entry_With_Key) -> slice.Ordering {
return slice.Ordering(bytes.compare(a.encoded_key, b.encoded_key))
})
for entry in entries {
io.write_full(e.writer, entry.encoded_key) or_return
delete(entry.encoded_key, e.temp_allocator)
encode(e, entry.entry.value) or_return
}
return nil
}
_decode_tag_ptr :: proc(d: Decoder, add: Add) -> (v: Value, err: Decode_Error) {
tag := _decode_tag(d, add) or_return
if t, ok := tag.?; ok {
defer if err != nil { destroy(t.value) }
tp := new(Tag) or_return
tp^ = t
return tp, nil
}
// no error, no tag, this was the self described CBOR tag, skip it.
return _decode_from_decoder(d)
}
_decode_tag :: proc(d: Decoder, add: Add) -> (v: Maybe(Tag), err: Decode_Error) {
num := _decode_uint_as_u64(d.reader, add) or_return
// CBOR can be wrapped in a tag that decoders can use to see/check if the binary data is CBOR.
// We can ignore it here.
if num == TAG_SELF_DESCRIBED_CBOR {
return
}
t := Tag{
number = num,
value = _decode_from_decoder(d) or_return,
}
if nested, ok := t.value.(^Tag); ok {
destroy(nested)
return nil, .Nested_Tag
}
return t, nil
}
_decode_uint_as_u64 :: proc(r: io.Reader, add: Add) -> (nr: u64, err: Decode_Error) {
#partial switch add {
case .One_Byte: return u64(_decode_u8(r) or_return), nil
case .Two_Bytes: return u64(_decode_u16(r) or_return), nil
case .Four_Bytes: return u64(_decode_u32(r) or_return), nil
case .Eight_Bytes: return u64(_decode_u64(r) or_return), nil
case: return u64(_decode_tiny_u8(add) or_return), nil
}
}
_encode_tag :: proc(e: Encoder, val: Tag) -> Encode_Error {
_encode_u64(e, val.number, .Tag) or_return
return encode(e, val.value)
}
_decode_simple :: proc(r: io.Reader) -> (v: Simple, err: io.Error) {
buf: [1]byte = ---
io.read_full(r, buf[:]) or_return
return Simple(buf[0]), nil
}
_encode_simple :: proc(w: io.Writer, v: Simple) -> (err: Encode_Error) {
header := u8(Major.Other) << 5
if v < Simple(Add.False) {
header |= u8(v)
_, err = io.write_full(w, {header})
return
} else if v <= Simple(Add.Break) {
return .Invalid_Simple
}
header |= u8(Add.One_Byte)
_, err = io.write_full(w, {header, u8(v)})
return
}
_decode_tiny_simple :: proc(add: Add) -> (Simple, Decode_Data_Error) {
if add < Add.False {
return Simple(add), nil
}
return 0, .Bad_Argument
}
_decode_f16 :: proc(r: io.Reader) -> (v: f16, err: io.Error) {
bytes: [2]byte = ---
io.read_full(r, bytes[:]) or_return
n := endian.unchecked_get_u16be(bytes[:])
return transmute(f16)n, nil
}
_encode_f16 :: proc(w: io.Writer, v: f16) -> (err: io.Error) {
bytes: [3]byte = ---
bytes[0] = u8(Header.F16)
endian.unchecked_put_u16be(bytes[1:], transmute(u16)v)
_, err = io.write_full(w, bytes[:])
return
}
_decode_f32 :: proc(r: io.Reader) -> (v: f32, err: io.Error) {
bytes: [4]byte = ---
io.read_full(r, bytes[:]) or_return
n := endian.unchecked_get_u32be(bytes[:])
return transmute(f32)n, nil
}
_encode_f32 :: proc(e: Encoder, v: f32) -> io.Error {
if .Deterministic_Float_Size in e.flags {
return _encode_deterministic_float(e.writer, v)
}
return _encode_f32_exact(e.writer, v)
}
_encode_f32_exact :: proc(w: io.Writer, v: f32) -> (err: io.Error) {
bytes: [5]byte = ---
bytes[0] = u8(Header.F32)
endian.unchecked_put_u32be(bytes[1:], transmute(u32)v)
_, err = io.write_full(w, bytes[:])
return
}
_decode_f64 :: proc(r: io.Reader) -> (v: f64, err: io.Error) {
bytes: [8]byte = ---
io.read_full(r, bytes[:]) or_return
n := endian.unchecked_get_u64be(bytes[:])
return transmute(f64)n, nil
}
_encode_f64 :: proc(e: Encoder, v: f64) -> io.Error {
if .Deterministic_Float_Size in e.flags {
return _encode_deterministic_float(e.writer, v)
}
return _encode_f64_exact(e.writer, v)
}
_encode_f64_exact :: proc(w: io.Writer, v: f64) -> (err: io.Error) {
bytes: [9]byte = ---
bytes[0] = u8(Header.F64)
endian.unchecked_put_u64be(bytes[1:], transmute(u64)v)
_, err = io.write_full(w, bytes[:])
return
}
_encode_bool :: proc(w: io.Writer, v: bool) -> (err: io.Error) {
switch v {
case true: _, err = io.write_full(w, {u8(Header.True )}); return
case false: _, err = io.write_full(w, {u8(Header.False)}); return
case: unreachable()
}
}
_encode_undefined :: proc(w: io.Writer) -> io.Error {
_, err := io.write_full(w, {u8(Header.Undefined)})
return err
}
_encode_nil :: proc(w: io.Writer) -> io.Error {
_, err := io.write_full(w, {u8(Header.Nil)})
return err
}
// Streaming
encode_stream_begin :: proc(w: io.Writer, major: Major) -> (err: io.Error) {
assert(major >= Major(.Bytes) && major <= Major(.Map), "illegal stream type")
header := (u8(major) << 5) | u8(Add.Length_Unknown)
_, err = io.write_full(w, {header})
return
}
encode_stream_end :: proc(w: io.Writer) -> io.Error {
header := (u8(Major.Other) << 5) | u8(Add.Break)
_, err := io.write_full(w, {header})
return err
}
encode_stream_bytes :: _encode_bytes
encode_stream_text :: _encode_text
encode_stream_array_item :: encode
encode_stream_map_entry :: proc(e: Encoder, key: Value, val: Value) -> Encode_Error {
encode(e, key) or_return
return encode(e, val)
}
// For `Bytes` and `Text` strings: Decodes the number of items the header says follows.
// If the number is not specified -1 is returned and streaming should be initiated.
// A suitable starting capacity is also returned for a buffer that is allocated up the stack.
_decode_len_str :: proc(d: Decoder, add: Add) -> (n: int, scap: int, err: Decode_Error) {
if add == .Length_Unknown {
if .Disallow_Streaming in d.flags {
return -1, -1, .Disallowed_Streaming
}
return -1, INITIAL_STREAMED_BYTES_CAPACITY, nil
}
_n := _decode_uint_as_u64(d.reader, add) or_return
if _n > u64(max(int)) { return -1, -1, .Length_Too_Big }
n = int(_n)
scap = n + 1 // Space for zero byte.
if .Trusted_Input not_in d.flags {
scap = min(d.max_pre_alloc, scap)
}
return
}
// For `Array` and `Map` types: Decodes the number of items the header says follows.
// If the number is not specified -1 is returned and streaming should be initiated.
// A suitable starting capacity is also returned for a buffer that is allocated up the stack.
_decode_len_container :: proc(d: Decoder, add: Add) -> (n: int, scap: int, err: Decode_Error) {
if add == .Length_Unknown {
if .Disallow_Streaming in d.flags {
return -1, -1, .Disallowed_Streaming
}
return -1, INITIAL_STREAMED_CONTAINER_CAPACITY, nil
}
_n := _decode_uint_as_u64(d.reader, add) or_return
if _n > u64(max(int)) { return -1, -1, .Length_Too_Big }
n = int(_n)
scap = n
if .Trusted_Input not_in d.flags {
// NOTE: if this is a map it will be twice this.
scap = min(d.max_pre_alloc / size_of(Value), scap)
}
return
}
// Deterministic encoding is (among other things) encoding all values into their smallest
// possible representation.
// See section 4 of RFC 8949.
_encode_deterministic_uint :: proc {
_encode_u8,
_encode_deterministic_u16,
_encode_deterministic_u32,
_encode_deterministic_u64,
_encode_deterministic_u128,
}
_encode_deterministic_u16 :: proc(w: io.Writer, v: u16, major: Major = .Unsigned) -> Encode_Error {
switch {
case v <= u16(max(u8)): return _encode_u8(w, u8(v), major)
case: return _encode_u16_exact(w, v, major)
}
}
_encode_deterministic_u32 :: proc(w: io.Writer, v: u32, major: Major = .Unsigned) -> Encode_Error {
switch {
case v <= u32(max(u8)): return _encode_u8(w, u8(v), major)
case v <= u32(max(u16)): return _encode_u16_exact(w, u16(v), major)
case: return _encode_u32_exact(w, u32(v), major)
}
}
_encode_deterministic_u64 :: proc(w: io.Writer, v: u64, major: Major = .Unsigned) -> Encode_Error {
switch {
case v <= u64(max(u8)): return _encode_u8(w, u8(v), major)
case v <= u64(max(u16)): return _encode_u16_exact(w, u16(v), major)
case v <= u64(max(u32)): return _encode_u32_exact(w, u32(v), major)
case: return _encode_u64_exact(w, u64(v), major)
}
}
_encode_deterministic_u128 :: proc(w: io.Writer, v: u128, major: Major = .Unsigned) -> Encode_Error {
switch {
case v <= u128(max(u8)): return _encode_u8(w, u8(v), major)
case v <= u128(max(u16)): return _encode_u16_exact(w, u16(v), major)
case v <= u128(max(u32)): return _encode_u32_exact(w, u32(v), major)
case v <= u128(max(u64)): return _encode_u64_exact(w, u64(v), major)
case: return .Int_Too_Big
}
}
_encode_deterministic_negative :: #force_inline proc(w: io.Writer, v: $T) -> Encode_Error
where T == Negative_U8 || T == Negative_U16 || T == Negative_U32 || T == Negative_U64 {
return _encode_deterministic_uint(w, v, .Negative)
}
// A Deterministic float is a float in the smallest type that stays the same after down casting.
_encode_deterministic_float :: proc {
_encode_f16,
_encode_deterministic_f32,
_encode_deterministic_f64,
}
_encode_deterministic_f32 :: proc(w: io.Writer, v: f32) -> io.Error {
if (f32(f16(v)) == v) {
return _encode_f16(w, f16(v))
}
return _encode_f32_exact(w, v)
}
_encode_deterministic_f64 :: proc(w: io.Writer, v: f64) -> io.Error {
if (f64(f16(v)) == v) {
return _encode_f16(w, f16(v))
}
if (f64(f32(v)) == v) {
return _encode_f32_exact(w, f32(v))
}
return _encode_f64_exact(w, v)
}
+141
View File
@@ -0,0 +1,141 @@
/*
Package cbor encodes, decodes, marshals and unmarshals types from/into RCF 8949 compatible CBOR binary.
Also provided are conversion to and from JSON and the CBOR diagnostic format.
**Allocations:**
In general, when in the following table it says allocations are done on the `temp_allocator`, these allocations
are still attempted to be deallocated.
This allows you to use an allocator with freeing implemented as the `temp_allocator` which is handy with big CBOR.
- *Encoding*: If the `.Deterministic_Map_Sorting` flag is set on the encoder, this allocates on the given `temp_allocator`
some space for the keys of maps in order to sort them and then write them.
Other than that there are no allocations (only for the final bytes if you use `cbor.encode_into_bytes`.
- *Decoding*: Allocates everything on the given allocator and input given can be deleted after decoding.
*No* temporary allocations are done.
- *Marshal*: Same allocation strategy as encoding.
- *Unmarshal*: Allocates everything on the given allocator and input given can be deleted after unmarshalling.
Some temporary allocations are done on the given `temp_allocator`.
**Determinism:**
CBOR defines a deterministic en/decoder, which among other things uses the smallest type possible for integers and floats,
and sorts map keys by their (encoded) lexical bytewise order.
You can enable this behaviour using a combination of flags, also available as the `cbor.ENCODE_FULLY_DETERMINISTIC` constant.
If you just want the small size that comes with this, but not the map sorting (which has a performance cost) you can use the
`cbor.ENCODE_SMALL` constant for the flags.
A deterministic float is a float in the smallest type (f16, f32, f64) that hasn't changed after conversion.
A deterministic integer is an integer in the smallest representation (u8, u16, u32, u64) it fits in.
**Untrusted Input:**
By default input is treated as untrusted, this means the sizes that are encoded in the CBOR are not blindly trusted.
If you were to trust these sizes, and allocate space for them an attacker would be able to cause massive allocations with small payloads.
The decoder has a `max_pre_alloc` field that specifies the maximum amount of bytes (roughly) to pre allocate, a KiB by default.
This does mean reallocations are more common though, you can, if you know the input is trusted, add the `.Trusted_Input` flag to the decoder.
**Tags:**
CBOR describes tags that you can wrap values with to assign a number to describe what type of data will follow.
More information and a list of default tags can be found here: [[RFC 8949 Section 3.4;https://www.rfc-editor.org/rfc/rfc8949.html#name-tagging-of-items]].
A list of registered extension types can be found here: [[IANA CBOR assignments;https://www.iana.org/assignments/cbor-tags/cbor-tags.xhtml]].
Tags can either be assigned to a distinct Odin type (used by default),
or be used with struct tags (`cbor_tag:"base64"`, or `cbor_tag:"1"` for example).
By default, the following tags are supported/provided by this implementation:
- *1/epoch*: Assign this tag to `time.Time` or integer fields to use the defined seconds since epoch format.
- *24/cbor*: Assign this tag to string or byte fields to store encoded CBOR (not decoding it).
- *34/base64*: Assign this tag to string or byte fields to store and decode the contents in base64.
- *2 & 3*: Used automatically by the implementation to encode and decode big numbers into/from `core:math/big`.
- *55799*: Self described CBOR, used when `.Self_Described_CBOR` flag is used to wrap the entire binary.
This shows other implementations that we are dealing with CBOR by just looking at the first byte of input.
- *1010*: An extension tag that defines a string type followed by its value, this is used by this implementation to support Odin's unions.
Users can provide their own tag implementations using the `cbor.tag_register_type(...)` to register a tag for a distinct Odin type
used automatically when it is encountered during marshal and unmarshal.
Or with `cbor.tag_register_number(...)` to register a tag number along with an identifier for convenience that can be used with struct tags,
e.g. `cbor_tag:"69"` or `cbor_tag:"my_tag"`.
You can look at the default tags provided for pointers on how these implementations work.
Example:
package main
import "core:encoding/cbor"
import "core:fmt"
import "core:time"
Possibilities :: union {
string,
int,
}
Data :: struct {
str: string,
neg: cbor.Negative_U16, // Store a CBOR value directly.
now: time.Time `cbor_tag:"epoch"`, // Wrapped in the epoch tag.
ignore_this: ^Data `cbor:"-"`, // Ignored by implementation.
renamed: f32 `cbor:"renamed :)"`, // Renamed when encoded.
my_union: Possibilities, // Union support.
}
main :: proc() {
now := time.Time{_nsec = 1701117968 * 1e9}
data := Data{
str = "Hello, World!",
neg = 300,
now = now,
ignore_this = &Data{},
renamed = 123123.125,
my_union = 3,
}
// Marshal the struct into binary CBOR.
binary, err := cbor.marshal(data, cbor.ENCODE_FULLY_DETERMINISTIC)
assert(err == nil)
defer delete(binary)
// Decode the binary data into a `cbor.Value`.
decoded, derr := cbor.decode(string(binary))
assert(derr == nil)
defer cbor.destroy(decoded)
// Turn the CBOR into a human readable representation defined as the diagnostic format in [[RFC 8949 Section 8;https://www.rfc-editor.org/rfc/rfc8949.html#name-diagnostic-notation]].
diagnosis, eerr := cbor.to_diagnostic_format(decoded)
assert(eerr == nil)
defer delete(diagnosis)
fmt.println(diagnosis)
}
Output:
{
"my_union": 1010([
"int",
3
]),
"neg": -301,
"now": 1(1701117968),
"renamed :)": 123123.12500000,
"str": "Hello, World!"
}
*/
package encoding_cbor
+575
View File
@@ -0,0 +1,575 @@
package encoding_cbor
import "base:intrinsics"
import "base:runtime"
import "core:bytes"
import "core:io"
import "core:mem"
import "core:reflect"
import "core:slice"
import "core:strconv"
import "core:strings"
import "core:unicode/utf8"
/*
Marshal a value into binary CBOR.
Flags can be used to control the output (mainly determinism, which coincidently affects size).
The default flags `ENCODE_SMALL` (`.Deterministic_Int_Size`, `.Deterministic_Float_Size`) will try
to put ints and floats into their smallest possible byte size without losing equality.
Adding the `.Self_Described_CBOR` flag will wrap the value in a tag that lets generic decoders know
the contents are CBOR from just reading the first byte.
Adding the `.Deterministic_Map_Sorting` flag will sort the encoded maps by the byte content of the
encoded key. This flag has a cost on performance and memory efficiency because all keys in a map
have to be precomputed, sorted and only then written to the output.
Empty flags will do nothing extra to the value.
The allocations for the `.Deterministic_Map_Sorting` flag are done using the given `temp_allocator`.
but are followed by the necessary `delete` and `free` calls if the allocator supports them.
This is helpful when the CBOR size is so big that you don't want to collect all the temporary
allocations until the end.
*/
marshal_into :: proc {
marshal_into_bytes,
marshal_into_builder,
marshal_into_writer,
marshal_into_encoder,
}
marshal :: marshal_into
// Marshals the given value into a CBOR byte stream (allocated using the given allocator).
// See docs on the `marshal_into` proc group for more info.
marshal_into_bytes :: proc(v: any, flags := ENCODE_SMALL, allocator := context.allocator, temp_allocator := context.temp_allocator) -> (bytes: []byte, err: Marshal_Error) {
b, alloc_err := strings.builder_make(allocator)
// The builder as a stream also returns .EOF if it ran out of memory so this is consistent.
if alloc_err != nil {
return nil, .EOF
}
defer if err != nil { strings.builder_destroy(&b) }
if err = marshal_into_builder(&b, v, flags, temp_allocator); err != nil {
return
}
return b.buf[:], nil
}
// Marshals the given value into a CBOR byte stream written to the given builder.
// See docs on the `marshal_into` proc group for more info.
marshal_into_builder :: proc(b: ^strings.Builder, v: any, flags := ENCODE_SMALL, temp_allocator := context.temp_allocator) -> Marshal_Error {
return marshal_into_writer(strings.to_writer(b), v, flags, temp_allocator)
}
// Marshals the given value into a CBOR byte stream written to the given writer.
// See docs on the `marshal_into` proc group for more info.
marshal_into_writer :: proc(w: io.Writer, v: any, flags := ENCODE_SMALL, temp_allocator := context.temp_allocator) -> Marshal_Error {
encoder := Encoder{flags, w, temp_allocator}
return marshal_into_encoder(encoder, v)
}
// Marshals the given value into a CBOR byte stream written to the given encoder.
// See docs on the `marshal_into` proc group for more info.
marshal_into_encoder :: proc(e: Encoder, v: any) -> (err: Marshal_Error) {
e := e
if e.temp_allocator.procedure == nil {
e.temp_allocator = context.temp_allocator
}
if .Self_Described_CBOR in e.flags {
err_conv(_encode_u64(e, TAG_SELF_DESCRIBED_CBOR, .Tag)) or_return
e.flags &~= { .Self_Described_CBOR }
}
if v == nil {
return _encode_nil(e.writer)
}
// Check if type has a tag implementation to use.
if impl, ok := _tag_implementations_type[v.id]; ok {
return impl->marshal(e, v)
}
ti := runtime.type_info_base(type_info_of(v.id))
a := any{v.data, ti.id}
#partial switch info in ti.variant {
case runtime.Type_Info_Named:
unreachable()
case runtime.Type_Info_Pointer:
switch vv in v {
case Undefined: return _encode_undefined(e.writer)
case Nil: return _encode_nil(e.writer)
}
case runtime.Type_Info_Integer:
switch vv in v {
case Simple: return err_conv(_encode_simple(e.writer, vv))
case Negative_U8: return _encode_u8(e.writer, u8(vv), .Negative)
case Negative_U16: return err_conv(_encode_u16(e, u16(vv), .Negative))
case Negative_U32: return err_conv(_encode_u32(e, u32(vv), .Negative))
case Negative_U64: return err_conv(_encode_u64(e, u64(vv), .Negative))
}
switch i in a {
case i8: return _encode_uint(e.writer, _int_to_uint(i))
case i16: return err_conv(_encode_uint(e, _int_to_uint(i)))
case i32: return err_conv(_encode_uint(e, _int_to_uint(i)))
case i64: return err_conv(_encode_uint(e, _int_to_uint(i)))
case i128: return err_conv(_encode_uint(e, _int_to_uint(i128(i)) or_return))
case int: return err_conv(_encode_uint(e, _int_to_uint(i64(i))))
case u8: return _encode_uint(e.writer, i)
case u16: return err_conv(_encode_uint(e, i))
case u32: return err_conv(_encode_uint(e, i))
case u64: return err_conv(_encode_uint(e, i))
case u128: return err_conv(_encode_uint(e, _u128_to_u64(u128(i)) or_return))
case uint: return err_conv(_encode_uint(e, u64(i)))
case uintptr: return err_conv(_encode_uint(e, u64(i)))
case i16le: return err_conv(_encode_uint(e, _int_to_uint(i16(i))))
case i32le: return err_conv(_encode_uint(e, _int_to_uint(i32(i))))
case i64le: return err_conv(_encode_uint(e, _int_to_uint(i64(i))))
case i128le: return err_conv(_encode_uint(e, _int_to_uint(i128(i)) or_return))
case u16le: return err_conv(_encode_uint(e, u16(i)))
case u32le: return err_conv(_encode_uint(e, u32(i)))
case u64le: return err_conv(_encode_uint(e, u64(i)))
case u128le: return err_conv(_encode_uint(e, _u128_to_u64(u128(i)) or_return))
case i16be: return err_conv(_encode_uint(e, _int_to_uint(i16(i))))
case i32be: return err_conv(_encode_uint(e, _int_to_uint(i32(i))))
case i64be: return err_conv(_encode_uint(e, _int_to_uint(i64(i))))
case i128be: return err_conv(_encode_uint(e, _int_to_uint(i128(i)) or_return))
case u16be: return err_conv(_encode_uint(e, u16(i)))
case u32be: return err_conv(_encode_uint(e, u32(i)))
case u64be: return err_conv(_encode_uint(e, u64(i)))
case u128be: return err_conv(_encode_uint(e, _u128_to_u64(u128(i)) or_return))
}
case runtime.Type_Info_Rune:
buf, w := utf8.encode_rune(a.(rune))
return err_conv(_encode_text(e, string(buf[:w])))
case runtime.Type_Info_Float:
switch f in a {
case f16: return _encode_f16(e.writer, f)
case f32: return _encode_f32(e, f)
case f64: return _encode_f64(e, f)
case f16le: return _encode_f16(e.writer, f16(f))
case f32le: return _encode_f32(e, f32(f))
case f64le: return _encode_f64(e, f64(f))
case f16be: return _encode_f16(e.writer, f16(f))
case f32be: return _encode_f32(e, f32(f))
case f64be: return _encode_f64(e, f64(f))
}
case runtime.Type_Info_Complex:
switch z in a {
case complex32:
arr: [2]Value = {real(z), imag(z)}
return err_conv(_encode_array(e, arr[:]))
case complex64:
arr: [2]Value = {real(z), imag(z)}
return err_conv(_encode_array(e, arr[:]))
case complex128:
arr: [2]Value = {real(z), imag(z)}
return err_conv(_encode_array(e, arr[:]))
}
case runtime.Type_Info_Quaternion:
switch q in a {
case quaternion64:
arr: [4]Value = {imag(q), jmag(q), kmag(q), real(q)}
return err_conv(_encode_array(e, arr[:]))
case quaternion128:
arr: [4]Value = {imag(q), jmag(q), kmag(q), real(q)}
return err_conv(_encode_array(e, arr[:]))
case quaternion256:
arr: [4]Value = {imag(q), jmag(q), kmag(q), real(q)}
return err_conv(_encode_array(e, arr[:]))
}
case runtime.Type_Info_String:
switch s in a {
case string: return err_conv(_encode_text(e, s))
case cstring: return err_conv(_encode_text(e, string(s)))
}
case runtime.Type_Info_Boolean:
switch b in a {
case bool: return _encode_bool(e.writer, b)
case b8: return _encode_bool(e.writer, bool(b))
case b16: return _encode_bool(e.writer, bool(b))
case b32: return _encode_bool(e.writer, bool(b))
case b64: return _encode_bool(e.writer, bool(b))
}
case runtime.Type_Info_Array:
if info.elem.id == byte {
raw := ([^]byte)(v.data)
return err_conv(_encode_bytes(e, raw[:info.count]))
}
err_conv(_encode_u64(e, u64(info.count), .Array)) or_return
for i in 0..<info.count {
data := uintptr(v.data) + uintptr(i*info.elem_size)
marshal_into(e, any{rawptr(data), info.elem.id}) or_return
}
return
case runtime.Type_Info_Enumerated_Array:
// index := runtime.type_info_base(info.index).variant.(runtime.Type_Info_Enum)
err_conv(_encode_u64(e, u64(info.count), .Array)) or_return
for i in 0..<info.count {
data := uintptr(v.data) + uintptr(i*info.elem_size)
marshal_into(e, any{rawptr(data), info.elem.id}) or_return
}
return
case runtime.Type_Info_Dynamic_Array:
if info.elem.id == byte {
raw := (^[dynamic]byte)(v.data)
return err_conv(_encode_bytes(e, raw[:]))
}
array := (^mem.Raw_Dynamic_Array)(v.data)
err_conv(_encode_u64(e, u64(array.len), .Array)) or_return
for i in 0..<array.len {
data := uintptr(array.data) + uintptr(i*info.elem_size)
marshal_into(e, any{rawptr(data), info.elem.id}) or_return
}
return
case runtime.Type_Info_Slice:
if info.elem.id == byte {
raw := (^[]byte)(v.data)
return err_conv(_encode_bytes(e, raw^))
}
array := (^mem.Raw_Slice)(v.data)
err_conv(_encode_u64(e, u64(array.len), .Array)) or_return
for i in 0..<array.len {
data := uintptr(array.data) + uintptr(i*info.elem_size)
marshal_into(e, any{rawptr(data), info.elem.id}) or_return
}
return
case runtime.Type_Info_Map:
m := (^mem.Raw_Map)(v.data)
err_conv(_encode_u64(e, u64(runtime.map_len(m^)), .Map)) or_return
if m != nil {
if info.map_info == nil {
return _unsupported(v.id, nil)
}
map_cap := uintptr(runtime.map_cap(m^))
ks, vs, hs, _, _ := runtime.map_kvh_data_dynamic(m^, info.map_info)
if .Deterministic_Map_Sorting not_in e.flags {
for bucket_index in 0..<map_cap {
runtime.map_hash_is_valid(hs[bucket_index]) or_continue
key := rawptr(runtime.map_cell_index_dynamic(ks, info.map_info.ks, bucket_index))
value := rawptr(runtime.map_cell_index_dynamic(vs, info.map_info.vs, bucket_index))
marshal_into(e, any{ key, info.key.id }) or_return
marshal_into(e, any{ value, info.value.id }) or_return
}
return
}
// Deterministic_Map_Sorting needs us to sort the entries by the byte contents of the
// encoded key.
//
// This means we have to store and sort them before writing incurring extra (temporary) allocations.
//
// If the map key is a `string` or `cstring` we only allocate space for a dynamic array of entries
// we sort.
//
// If the map key is of another type we also allocate space for encoding the key into.
// To sort a string/cstring we need to first sort by their encoded header/length.
// This fits in 9 bytes at most.
pre_key :: #force_inline proc(e: Encoder, str: string) -> (res: [10]byte) {
e := e
builder := strings.builder_from_slice(res[:])
e.writer = strings.to_stream(&builder)
assert(_encode_u64(e, u64(len(str)), .Text) == nil)
res[9] = u8(len(builder.buf))
assert(res[9] < 10)
return
}
Encoded_Entry_Fast :: struct($T: typeid) {
pre_key: [10]byte,
key: T,
val_idx: uintptr,
}
Encoded_Entry :: struct {
key: ^[dynamic]byte,
val_idx: uintptr,
}
switch info.key.id {
case string:
entries := make([dynamic]Encoded_Entry_Fast(^[]byte), 0, map_cap, e.temp_allocator) or_return
defer delete(entries)
for bucket_index in 0..<map_cap {
runtime.map_hash_is_valid(hs[bucket_index]) or_continue
key := (^[]byte)(runtime.map_cell_index_dynamic(ks, info.map_info.ks, bucket_index))
append(&entries, Encoded_Entry_Fast(^[]byte){
pre_key = pre_key(e, string(key^)),
key = key,
val_idx = bucket_index,
})
}
slice.sort_by_cmp(entries[:], proc(a, b: Encoded_Entry_Fast(^[]byte)) -> slice.Ordering {
a, b := a, b
pre_cmp := slice.Ordering(bytes.compare(a.pre_key[:a.pre_key[9]], b.pre_key[:b.pre_key[9]]))
if pre_cmp != .Equal {
return pre_cmp
}
return slice.Ordering(bytes.compare(a.key^, b.key^))
})
for &entry in entries {
io.write_full(e.writer, entry.pre_key[:entry.pre_key[9]]) or_return
io.write_full(e.writer, entry.key^) or_return
value := rawptr(runtime.map_cell_index_dynamic(vs, info.map_info.vs, entry.val_idx))
marshal_into(e, any{ value, info.value.id }) or_return
}
return
case cstring:
entries := make([dynamic]Encoded_Entry_Fast(^cstring), 0, map_cap, e.temp_allocator) or_return
defer delete(entries)
for bucket_index in 0..<map_cap {
runtime.map_hash_is_valid(hs[bucket_index]) or_continue
key := (^cstring)(runtime.map_cell_index_dynamic(ks, info.map_info.ks, bucket_index))
append(&entries, Encoded_Entry_Fast(^cstring){
pre_key = pre_key(e, string(key^)),
key = key,
val_idx = bucket_index,
})
}
slice.sort_by_cmp(entries[:], proc(a, b: Encoded_Entry_Fast(^cstring)) -> slice.Ordering {
a, b := a, b
pre_cmp := slice.Ordering(bytes.compare(a.pre_key[:a.pre_key[9]], b.pre_key[:b.pre_key[9]]))
if pre_cmp != .Equal {
return pre_cmp
}
ab := transmute([]byte)string(a.key^)
bb := transmute([]byte)string(b.key^)
return slice.Ordering(bytes.compare(ab, bb))
})
for &entry in entries {
io.write_full(e.writer, entry.pre_key[:entry.pre_key[9]]) or_return
io.write_full(e.writer, transmute([]byte)string(entry.key^)) or_return
value := rawptr(runtime.map_cell_index_dynamic(vs, info.map_info.vs, entry.val_idx))
marshal_into(e, any{ value, info.value.id }) or_return
}
return
case:
entries := make([dynamic]Encoded_Entry, 0, map_cap, e.temp_allocator) or_return
defer delete(entries)
for bucket_index in 0..<map_cap {
runtime.map_hash_is_valid(hs[bucket_index]) or_continue
key := rawptr(runtime.map_cell_index_dynamic(ks, info.map_info.ks, bucket_index))
key_builder := strings.builder_make(0, 8, e.temp_allocator) or_return
marshal_into(Encoder{e.flags, strings.to_stream(&key_builder), e.temp_allocator}, any{ key, info.key.id }) or_return
append(&entries, Encoded_Entry{ &key_builder.buf, bucket_index }) or_return
}
slice.sort_by_cmp(entries[:], proc(a, b: Encoded_Entry) -> slice.Ordering {
return slice.Ordering(bytes.compare(a.key[:], b.key[:]))
})
for entry in entries {
io.write_full(e.writer, entry.key[:]) or_return
delete(entry.key^)
value := rawptr(runtime.map_cell_index_dynamic(vs, info.map_info.vs, entry.val_idx))
marshal_into(e, any{ value, info.value.id }) or_return
}
return
}
}
case runtime.Type_Info_Struct:
switch vv in v {
case Tag: return err_conv(_encode_tag(e, vv))
}
field_name :: #force_inline proc(info: runtime.Type_Info_Struct, i: int) -> string {
if cbor_name := string(reflect.struct_tag_get(reflect.Struct_Tag(info.tags[i]), "cbor")); cbor_name != "" {
return cbor_name
} else {
return info.names[i]
}
}
marshal_entry :: #force_inline proc(e: Encoder, info: runtime.Type_Info_Struct, v: any, name: string, i: int) -> Marshal_Error {
err_conv(_encode_text(e, name)) or_return
id := info.types[i].id
data := rawptr(uintptr(v.data) + info.offsets[i])
field_any := any{data, id}
if tag := string(reflect.struct_tag_get(reflect.Struct_Tag(info.tags[i]), "cbor_tag")); tag != "" {
if impl, ok := _tag_implementations_id[tag]; ok {
return impl->marshal(e, field_any)
}
nr, ok := strconv.parse_u64_of_base(tag, 10)
if !ok { return .Invalid_CBOR_Tag }
if impl, nok := _tag_implementations_nr[nr]; nok {
return impl->marshal(e, field_any)
}
err_conv(_encode_u64(e, nr, .Tag)) or_return
}
return marshal_into(e, field_any)
}
n: u64; {
for _, i in info.names {
if field_name(info, i) != "-" {
n += 1
}
}
err_conv(_encode_u64(e, n, .Map)) or_return
}
if .Deterministic_Map_Sorting in e.flags {
Name :: struct {
name: string,
field: int,
}
entries := make([dynamic]Name, 0, n, e.temp_allocator) or_return
defer delete(entries)
for _, i in info.names {
fname := field_name(info, i)
if fname == "-" {
continue
}
append(&entries, Name{fname, i}) or_return
}
// Sort lexicographic on the bytes of the key.
slice.sort_by_cmp(entries[:], proc(a, b: Name) -> slice.Ordering {
return slice.Ordering(bytes.compare(transmute([]byte)a.name, transmute([]byte)b.name))
})
for entry in entries {
marshal_entry(e, info, v, entry.name, entry.field) or_return
}
} else {
for _, i in info.names {
fname := field_name(info, i)
if fname == "-" {
continue
}
marshal_entry(e, info, v, fname, i) or_return
}
}
return
case runtime.Type_Info_Union:
switch vv in v {
case Value: return err_conv(encode(e, vv))
}
id := reflect.union_variant_typeid(v)
if v.data == nil || id == nil {
return _encode_nil(e.writer)
}
if len(info.variants) == 1 {
return marshal_into(e, any{v.data, id})
}
// Encode a non-nil multi-variant union as the `TAG_OBJECT_TYPE`.
// Which is a tag of an array, where the first element is the textual id/type of the object
// that follows it.
err_conv(_encode_u16(e, TAG_OBJECT_TYPE, .Tag)) or_return
_encode_u8(e.writer, 2, .Array) or_return
vti := reflect.union_variant_type_info(v)
#partial switch vt in vti.variant {
case reflect.Type_Info_Named:
err_conv(_encode_text(e, vt.name)) or_return
case:
builder := strings.builder_make(e.temp_allocator) or_return
defer strings.builder_destroy(&builder)
reflect.write_type(&builder, vti)
err_conv(_encode_text(e, strings.to_string(builder))) or_return
}
return marshal_into(e, any{v.data, vti.id})
case runtime.Type_Info_Enum:
return marshal_into(e, any{v.data, info.base.id})
case runtime.Type_Info_Bit_Set:
// Store bit_set as big endian just like the protocol.
do_byte_swap := !reflect.bit_set_is_big_endian(v)
switch ti.size * 8 {
case 0:
return _encode_u8(e.writer, 0)
case 8:
x := (^u8)(v.data)^
return _encode_u8(e.writer, x)
case 16:
x := (^u16)(v.data)^
if do_byte_swap { x = intrinsics.byte_swap(x) }
return err_conv(_encode_u16(e, x))
case 32:
x := (^u32)(v.data)^
if do_byte_swap { x = intrinsics.byte_swap(x) }
return err_conv(_encode_u32(e, x))
case 64:
x := (^u64)(v.data)^
if do_byte_swap { x = intrinsics.byte_swap(x) }
return err_conv(_encode_u64(e, x))
case:
panic("unknown bit_size size")
}
}
return _unsupported(v.id, nil)
}
+381
View File
@@ -0,0 +1,381 @@
package encoding_cbor
import "base:runtime"
import "core:encoding/base64"
import "core:io"
import "core:math"
import "core:math/big"
import "core:mem"
import "core:reflect"
import "core:strings"
import "core:time"
// Tags defined in RFC 7049 that we provide implementations for.
// UTC time in seconds, unmarshalled into a `core:time` `time.Time` or integer.
// Use the struct tag `cbor_tag:"1"` or `cbor_tag:"epoch"` to have your `time.Time` field en/decoded as epoch time.
TAG_EPOCH_TIME_NR :: 1
TAG_EPOCH_TIME_ID :: "epoch"
// Using `core:math/big`, big integers are properly encoded and decoded during marshal and unmarshal.
// These fields use this tag by default, no struct tag required.
TAG_UNSIGNED_BIG_NR :: 2
// Using `core:math/big`, big integers are properly encoded and decoded during marshal and unmarshal.
// These fields use this tag by default, no struct tag required.
TAG_NEGATIVE_BIG_NR :: 3
// TAG_DECIMAL_FRACTION :: 4 // NOTE: We could probably implement this with `math/fixed`.
// Sometimes it is beneficial to carry an embedded CBOR data item that is not meant to be decoded
// immediately at the time the enclosing data item is being decoded. Tag number 24 (CBOR data item)
// can be used to tag the embedded byte string as a single data item encoded in CBOR format.
// Use the struct tag `cbor_tag:"24"` or `cbor_tag:"cbor"` to keep a non-decoded field (string or bytes) of raw CBOR.
TAG_CBOR_NR :: 24
TAG_CBOR_ID :: "cbor"
// The contents of this tag are base64 encoded during marshal and decoded during unmarshal.
// Use the struct tag `cbor_tag:"34"` or `cbor_tag:"base64"` to have your field string or bytes field en/decoded as base64.
TAG_BASE64_NR :: 34
TAG_BASE64_ID :: "base64"
// A tag that is used to detect the contents of a binary buffer (like a file) are CBOR.
// This tag would wrap everything else, decoders can then check for this header and see if the
// given content is definitely CBOR.
// Added by the encoder if it has the flag `.Self_Described_CBOR`, decoded by default.
TAG_SELF_DESCRIBED_CBOR :: 55799
// A tag that is used to assign a textual type to the object following it.
// The tag's value must be an array of 2 items, where the first is text (describing the following type)
// and the second is any valid CBOR value.
//
// See the registration: https://datatracker.ietf.org/doc/draft-rundgren-cotx/05/
//
// We use this in Odin to marshal and unmarshal unions.
TAG_OBJECT_TYPE :: 1010
// A tag implementation that handles marshals and unmarshals for the tag it is registered on.
Tag_Implementation :: struct {
data: rawptr,
unmarshal: Tag_Unmarshal_Proc,
marshal: Tag_Marshal_Proc,
}
// Procedure responsible for umarshalling the tag out of the reader into the given `any`.
Tag_Unmarshal_Proc :: #type proc(self: ^Tag_Implementation, d: Decoder, tag_nr: Tag_Number, v: any) -> Unmarshal_Error
// Procedure responsible for marshalling the tag in the given `any` into the given encoder.
Tag_Marshal_Proc :: #type proc(self: ^Tag_Implementation, e: Encoder, v: any) -> Marshal_Error
// When encountering a tag in the CBOR being unmarshalled, the implementation is used to unmarshal it.
// When encountering a struct tag like `cbor_tag:"Tag_Number"`, the implementation is used to marshal it.
_tag_implementations_nr: map[Tag_Number]Tag_Implementation
// Same as the number implementations but friendlier to use as a struct tag.
// Instead of `cbor_tag:"34"` you can use `cbor_tag:"base64"`.
_tag_implementations_id: map[string]Tag_Implementation
// Tag implementations that are always used by a type, if that type is encountered in marshal it
// will rely on the implementation to marshal it.
//
// This is good for types that don't make sense or can't marshal in its default form.
_tag_implementations_type: map[typeid]Tag_Implementation
// Register a custom tag implementation to be used when marshalling that type and unmarshalling that tag number.
tag_register_type :: proc(impl: Tag_Implementation, nr: Tag_Number, type: typeid) {
_tag_implementations_nr[nr] = impl
_tag_implementations_type[type] = impl
}
// Register a custom tag implementation to be used when marshalling that tag number or marshalling
// a field with the struct tag `cbor_tag:"nr"`.
tag_register_number :: proc(impl: Tag_Implementation, nr: Tag_Number, id: string) {
_tag_implementations_nr[nr] = impl
_tag_implementations_id[id] = impl
}
// Controls initialization of default tag implementations.
// JS and WASI default to a panic allocator so we don't want to do it on those.
INITIALIZE_DEFAULT_TAGS :: #config(CBOR_INITIALIZE_DEFAULT_TAGS, !ODIN_DEFAULT_TO_PANIC_ALLOCATOR && !ODIN_DEFAULT_TO_NIL_ALLOCATOR)
@(private, init, disabled=!INITIALIZE_DEFAULT_TAGS)
tags_initialize_defaults :: proc() {
tags_register_defaults()
}
// Registers tags that have implementations provided by this package.
// This is done by default and can be controlled with the `CBOR_INITIALIZE_DEFAULT_TAGS` define.
tags_register_defaults :: proc() {
tag_register_number({nil, tag_time_unmarshal, tag_time_marshal}, TAG_EPOCH_TIME_NR, TAG_EPOCH_TIME_ID)
tag_register_number({nil, tag_base64_unmarshal, tag_base64_marshal}, TAG_BASE64_NR, TAG_BASE64_ID)
tag_register_number({nil, tag_cbor_unmarshal, tag_cbor_marshal}, TAG_CBOR_NR, TAG_CBOR_ID)
// These following tags are registered at the type level and don't require an opt-in struct tag.
// Encoding these types on its own make no sense or no data is lost to encode it.
// En/Decoding of `big.Int` fields by default.
tag_register_type({nil, tag_big_unmarshal, tag_big_marshal}, TAG_UNSIGNED_BIG_NR, big.Int)
tag_register_type({nil, tag_big_unmarshal, tag_big_marshal}, TAG_NEGATIVE_BIG_NR, big.Int)
}
// Tag number 1 contains a numerical value counting the number of seconds from 1970-01-01T00:00Z
// in UTC time to the represented point in civil time.
//
// See RFC 8949 section 3.4.2.
@(private)
tag_time_unmarshal :: proc(_: ^Tag_Implementation, d: Decoder, _: Tag_Number, v: any) -> (err: Unmarshal_Error) {
hdr := _decode_header(d.reader) or_return
#partial switch hdr {
case .U8, .U16, .U32, .U64, .Neg_U8, .Neg_U16, .Neg_U32, .Neg_U64:
switch &dst in v {
case time.Time:
i: i64
_unmarshal_any_ptr(d, &i, hdr) or_return
dst = time.unix(i64(i), 0)
return
case:
return _unmarshal_value(d, v, hdr)
}
case .F16, .F32, .F64:
switch &dst in v {
case time.Time:
f: f64
_unmarshal_any_ptr(d, &f, hdr) or_return
whole, fract := math.modf(f)
dst = time.unix(i64(whole), i64(fract * 1e9))
return
case:
return _unmarshal_value(d, v, hdr)
}
case:
maj, add := _header_split(hdr)
if maj == .Other {
i := _decode_tiny_u8(add) or_return
switch &dst in v {
case time.Time:
dst = time.unix(i64(i), 0)
case:
if _assign_int(v, i) { return }
}
}
// Only numbers and floats are allowed in this tag.
return .Bad_Tag_Value
}
return _unsupported(v, hdr)
}
@(private)
tag_time_marshal :: proc(_: ^Tag_Implementation, e: Encoder, v: any) -> Marshal_Error {
switch vv in v {
case time.Time:
// NOTE: we lose precision here, which is one of the reasons for this tag being opt-in.
i := time.time_to_unix(vv)
_encode_u8(e.writer, TAG_EPOCH_TIME_NR, .Tag) or_return
return err_conv(_encode_uint(e, _int_to_uint(i)))
case:
unreachable()
}
}
@(private)
tag_big_unmarshal :: proc(_: ^Tag_Implementation, d: Decoder, tnr: Tag_Number, v: any) -> (err: Unmarshal_Error) {
hdr := _decode_header(d.reader) or_return
maj, add := _header_split(hdr)
if maj != .Bytes {
// Only bytes are supported in this tag.
return .Bad_Tag_Value
}
switch &dst in v {
case big.Int:
bytes := err_conv(_decode_bytes(d, add)) or_return
defer delete(bytes)
if err := big.int_from_bytes_big(&dst, bytes); err != nil {
return .Bad_Tag_Value
}
if tnr == TAG_NEGATIVE_BIG_NR {
dst.sign = .Negative
}
return
}
return _unsupported(v, hdr)
}
@(private)
tag_big_marshal :: proc(_: ^Tag_Implementation, e: Encoder, v: any) -> Marshal_Error {
switch &vv in v {
case big.Int:
if !big.int_is_initialized(&vv) {
_encode_u8(e.writer, TAG_UNSIGNED_BIG_NR, .Tag) or_return
return _encode_u8(e.writer, 0, .Bytes)
}
// NOTE: using the panic_allocator because all procedures should only allocate if the Int
// is uninitialized (which we checked).
is_neg, err := big.is_negative(&vv, mem.panic_allocator())
assert(err == nil, "should only error if not initialized, which has been checked")
tnr: u8 = TAG_NEGATIVE_BIG_NR if is_neg else TAG_UNSIGNED_BIG_NR
_encode_u8(e.writer, tnr, .Tag) or_return
size_in_bytes, berr := big.int_to_bytes_size(&vv, false, mem.panic_allocator())
assert(berr == nil, "should only error if not initialized, which has been checked")
assert(size_in_bytes >= 0)
err_conv(_encode_u64(e, u64(size_in_bytes), .Bytes)) or_return
for offset := (size_in_bytes*8)-8; offset >= 0; offset -= 8 {
bits, derr := big.int_bitfield_extract(&vv, offset, 8, mem.panic_allocator())
assert(derr == nil, "should only error if not initialized or invalid argument (offset and count), which won't happen")
io.write_full(e.writer, {u8(bits & 255)}) or_return
}
return nil
case: unreachable()
}
}
@(private)
tag_cbor_unmarshal :: proc(_: ^Tag_Implementation, d: Decoder, _: Tag_Number, v: any) -> Unmarshal_Error {
hdr := _decode_header(d.reader) or_return
major, add := _header_split(hdr)
#partial switch major {
case .Bytes:
ti := reflect.type_info_base(type_info_of(v.id))
return _unmarshal_bytes(d, v, ti, hdr, add)
case: return .Bad_Tag_Value
}
}
@(private)
tag_cbor_marshal :: proc(_: ^Tag_Implementation, e: Encoder, v: any) -> Marshal_Error {
_encode_u8(e.writer, TAG_CBOR_NR, .Tag) or_return
ti := runtime.type_info_base(type_info_of(v.id))
#partial switch t in ti.variant {
case runtime.Type_Info_String:
return marshal_into(e, v)
case runtime.Type_Info_Array:
elem_base := reflect.type_info_base(t.elem)
if elem_base.id != byte { return .Bad_Tag_Value }
return marshal_into(e, v)
case runtime.Type_Info_Slice:
elem_base := reflect.type_info_base(t.elem)
if elem_base.id != byte { return .Bad_Tag_Value }
return marshal_into(e, v)
case runtime.Type_Info_Dynamic_Array:
elem_base := reflect.type_info_base(t.elem)
if elem_base.id != byte { return .Bad_Tag_Value }
return marshal_into(e, v)
case:
return .Bad_Tag_Value
}
}
@(private)
tag_base64_unmarshal :: proc(_: ^Tag_Implementation, d: Decoder, _: Tag_Number, v: any) -> (err: Unmarshal_Error) {
hdr := _decode_header(d.reader) or_return
major, add := _header_split(hdr)
ti := reflect.type_info_base(type_info_of(v.id))
if major != .Text && major != .Bytes {
return .Bad_Tag_Value
}
bytes := string(err_conv(_decode_bytes(d, add, allocator=context.temp_allocator)) or_return)
defer delete(bytes, context.temp_allocator)
#partial switch t in ti.variant {
case reflect.Type_Info_String:
if t.is_cstring {
length := base64.decoded_len(bytes)
builder := strings.builder_make(0, length+1)
base64.decode_into(strings.to_stream(&builder), bytes) or_return
raw := (^cstring)(v.data)
raw^ = cstring(raw_data(builder.buf))
} else {
raw := (^string)(v.data)
raw^ = string(base64.decode(bytes) or_return)
}
return
case reflect.Type_Info_Slice:
elem_base := reflect.type_info_base(t.elem)
if elem_base.id != byte { return _unsupported(v, hdr) }
raw := (^[]byte)(v.data)
raw^ = base64.decode(bytes) or_return
return
case reflect.Type_Info_Dynamic_Array:
elem_base := reflect.type_info_base(t.elem)
if elem_base.id != byte { return _unsupported(v, hdr) }
decoded := base64.decode(bytes) or_return
raw := (^mem.Raw_Dynamic_Array)(v.data)
raw.data = raw_data(decoded)
raw.len = len(decoded)
raw.cap = len(decoded)
raw.allocator = context.allocator
return
case reflect.Type_Info_Array:
elem_base := reflect.type_info_base(t.elem)
if elem_base.id != byte { return _unsupported(v, hdr) }
if base64.decoded_len(bytes) > t.count { return _unsupported(v, hdr) }
slice := ([^]byte)(v.data)[:len(bytes)]
copy(slice, base64.decode(bytes) or_return)
return
}
return _unsupported(v, hdr)
}
@(private)
tag_base64_marshal :: proc(_: ^Tag_Implementation, e: Encoder, v: any) -> Marshal_Error {
_encode_u8(e.writer, TAG_BASE64_NR, .Tag) or_return
ti := runtime.type_info_base(type_info_of(v.id))
a := any{v.data, ti.id}
bytes: []byte
switch val in a {
case string: bytes = transmute([]byte)val
case cstring: bytes = transmute([]byte)string(val)
case []byte: bytes = val
case [dynamic]byte: bytes = val[:]
case:
#partial switch t in ti.variant {
case runtime.Type_Info_Array:
if t.elem.id != byte { return .Bad_Tag_Value }
bytes = ([^]byte)(v.data)[:t.count]
case:
return .Bad_Tag_Value
}
}
out_len := base64.encoded_len(bytes)
err_conv(_encode_u64(e, u64(out_len), .Text)) or_return
return base64.encode_into(e.writer, bytes)
}
+932
View File
@@ -0,0 +1,932 @@
package encoding_cbor
import "base:intrinsics"
import "base:runtime"
import "core:io"
import "core:mem"
import "core:reflect"
import "core:strings"
import "core:unicode/utf8"
/*
Unmarshals the given CBOR into the given pointer using reflection.
Types that require allocation are allocated using the given allocator.
Some temporary allocations are done on the given `temp_allocator`, but, if you want to,
this can be set to a "normal" allocator, because the necessary `delete` and `free` calls are still made.
This is helpful when the CBOR size is so big that you don't want to collect all the temporary allocations until the end.
Disable streaming/indeterminate lengths with the `.Disallow_Streaming` flag.
Shrink excess bytes in buffers and containers with the `.Shrink_Excess` flag.
Mark the input as trusted input with the `.Trusted_Input` flag, this turns off the safety feature
of not pre-allocating more than `max_pre_alloc` bytes before reading into the bytes. You should only
do this when you own both sides of the encoding and are sure there can't be malicious bytes used as
an input.
*/
unmarshal :: proc {
unmarshal_from_reader,
unmarshal_from_string,
}
unmarshal_from_reader :: proc(r: io.Reader, ptr: ^$T, flags := Decoder_Flags{}, allocator := context.allocator, temp_allocator := context.temp_allocator) -> (err: Unmarshal_Error) {
err = unmarshal_from_decoder(Decoder{ DEFAULT_MAX_PRE_ALLOC, flags, r }, ptr, allocator, temp_allocator)
// Normal EOF does not exist here, we try to read the exact amount that is said to be provided.
if err == .EOF { err = .Unexpected_EOF }
return
}
// Unmarshals from a string, see docs on the proc group `Unmarshal` for more info.
unmarshal_from_string :: proc(s: string, ptr: ^$T, flags := Decoder_Flags{}, allocator := context.allocator, temp_allocator := context.temp_allocator) -> (err: Unmarshal_Error) {
sr: strings.Reader
r := strings.to_reader(&sr, s)
err = unmarshal_from_reader(r, ptr, flags, allocator, temp_allocator)
// Normal EOF does not exist here, we try to read the exact amount that is said to be provided.
if err == .EOF { err = .Unexpected_EOF }
return
}
unmarshal_from_decoder :: proc(d: Decoder, ptr: ^$T, allocator := context.allocator, temp_allocator := context.temp_allocator) -> (err: Unmarshal_Error) {
d := d
err = _unmarshal_any_ptr(d, ptr, nil, allocator, temp_allocator)
// Normal EOF does not exist here, we try to read the exact amount that is said to be provided.
if err == .EOF { err = .Unexpected_EOF }
return
}
_unmarshal_any_ptr :: proc(d: Decoder, v: any, hdr: Maybe(Header) = nil, allocator := context.allocator, temp_allocator := context.temp_allocator) -> Unmarshal_Error {
context.allocator = allocator
context.temp_allocator = temp_allocator
v := v
if v == nil || v.id == nil {
return .Invalid_Parameter
}
v = reflect.any_base(v)
ti := type_info_of(v.id)
if !reflect.is_pointer(ti) || ti.id == rawptr {
return .Non_Pointer_Parameter
}
data := any{(^rawptr)(v.data)^, ti.variant.(reflect.Type_Info_Pointer).elem.id}
return _unmarshal_value(d, data, hdr.? or_else (_decode_header(d.reader) or_return))
}
_unmarshal_value :: proc(d: Decoder, v: any, hdr: Header) -> (err: Unmarshal_Error) {
v := v
ti := reflect.type_info_base(type_info_of(v.id))
r := d.reader
// If it's a union with only one variant, then treat it as that variant
if u, ok := ti.variant.(reflect.Type_Info_Union); ok && len(u.variants) == 1 {
#partial switch hdr {
case .Nil, .Undefined, nil: // no-op.
case:
variant := u.variants[0]
v.id = variant.id
ti = reflect.type_info_base(variant)
if !reflect.is_pointer_internally(variant) {
tag := any{rawptr(uintptr(v.data) + u.tag_offset), u.tag_type.id}
assert(_assign_int(tag, 1))
}
}
}
// Allow generic unmarshal by doing it into a `Value`.
switch &dst in v {
case Value:
dst = err_conv(_decode_from_decoder(d, hdr)) or_return
return
}
switch hdr {
case .U8:
decoded := _decode_u8(r) or_return
if !_assign_int(v, decoded) { return _unsupported(v, hdr) }
return
case .U16:
decoded := _decode_u16(r) or_return
if !_assign_int(v, decoded) { return _unsupported(v, hdr) }
return
case .U32:
decoded := _decode_u32(r) or_return
if !_assign_int(v, decoded) { return _unsupported(v, hdr) }
return
case .U64:
decoded := _decode_u64(r) or_return
if !_assign_int(v, decoded) { return _unsupported(v, hdr) }
return
case .Neg_U8:
decoded := Negative_U8(_decode_u8(r) or_return)
switch &dst in v {
case Negative_U8:
dst = decoded
return
case Negative_U16:
dst = Negative_U16(decoded)
return
case Negative_U32:
dst = Negative_U32(decoded)
return
case Negative_U64:
dst = Negative_U64(decoded)
return
}
if reflect.is_unsigned(ti) { return _unsupported(v, hdr) }
if !_assign_int(v, negative_to_int(decoded)) { return _unsupported(v, hdr) }
return
case .Neg_U16:
decoded := Negative_U16(_decode_u16(r) or_return)
switch &dst in v {
case Negative_U16:
dst = decoded
return
case Negative_U32:
dst = Negative_U32(decoded)
return
case Negative_U64:
dst = Negative_U64(decoded)
return
}
if reflect.is_unsigned(ti) { return _unsupported(v, hdr) }
if !_assign_int(v, negative_to_int(decoded)) { return _unsupported(v, hdr) }
return
case .Neg_U32:
decoded := Negative_U32(_decode_u32(r) or_return)
switch &dst in v {
case Negative_U32:
dst = decoded
return
case Negative_U64:
dst = Negative_U64(decoded)
return
}
if reflect.is_unsigned(ti) { return _unsupported(v, hdr) }
if !_assign_int(v, negative_to_int(decoded)) { return _unsupported(v, hdr) }
return
case .Neg_U64:
decoded := Negative_U64(_decode_u64(r) or_return)
switch &dst in v {
case Negative_U64:
dst = decoded
return
}
if reflect.is_unsigned(ti) { return _unsupported(v, hdr) }
if !_assign_int(v, negative_to_int(decoded)) { return _unsupported(v, hdr) }
return
case .Simple:
decoded := _decode_simple(r) or_return
// NOTE: Because this is a special type and not to be treated as a general integer,
// We only put the value of it in fields that are explicitly of type `Simple`.
switch &dst in v {
case Simple:
dst = decoded
return
case:
return _unsupported(v, hdr)
}
case .F16:
decoded := _decode_f16(r) or_return
if !_assign_float(v, decoded) { return _unsupported(v, hdr) }
return
case .F32:
decoded := _decode_f32(r) or_return
if !_assign_float(v, decoded) { return _unsupported(v, hdr) }
return
case .F64:
decoded := _decode_f64(r) or_return
if !_assign_float(v, decoded) { return _unsupported(v, hdr) }
return
case .True:
if !_assign_bool(v, true) { return _unsupported(v, hdr) }
return
case .False:
if !_assign_bool(v, false) { return _unsupported(v, hdr) }
return
case .Nil, .Undefined:
mem.zero(v.data, ti.size)
return
case .Break:
return .Break
}
maj, add := _header_split(hdr)
switch maj {
case .Unsigned:
decoded := _decode_tiny_u8(add) or_return
if !_assign_int(v, decoded) { return _unsupported(v, hdr, add) }
return
case .Negative:
decoded := Negative_U8(_decode_tiny_u8(add) or_return)
switch &dst in v {
case Negative_U8:
dst = decoded
return
}
if reflect.is_unsigned(ti) { return _unsupported(v, hdr, add) }
if !_assign_int(v, negative_to_int(decoded)) { return _unsupported(v, hdr, add) }
return
case .Other:
decoded := _decode_tiny_simple(add) or_return
// NOTE: Because this is a special type and not to be treated as a general integer,
// We only put the value of it in fields that are explicitly of type `Simple`.
switch &dst in v {
case Simple:
dst = decoded
return
case:
return _unsupported(v, hdr, add)
}
case .Tag:
switch &dst in v {
case ^Tag:
tval := err_conv(_decode_tag_ptr(d, add)) or_return
if t, is_tag := tval.(^Tag); is_tag {
dst = t
return
}
destroy(tval)
return .Bad_Tag_Value
case Tag:
t := err_conv(_decode_tag(d, add)) or_return
if t, is_tag := t.?; is_tag {
dst = t
return
}
return .Bad_Tag_Value
}
nr := err_conv(_decode_uint_as_u64(r, add)) or_return
// Custom tag implementations.
if impl, ok := _tag_implementations_nr[nr]; ok {
return impl->unmarshal(d, nr, v)
} else if nr == TAG_OBJECT_TYPE {
return _unmarshal_union(d, v, ti, hdr)
} else {
// Discard the tag info and unmarshal as its value.
return _unmarshal_value(d, v, _decode_header(r) or_return)
}
return _unsupported(v, hdr, add)
case .Bytes: return _unmarshal_bytes(d, v, ti, hdr, add)
case .Text: return _unmarshal_string(d, v, ti, hdr, add)
case .Array: return _unmarshal_array(d, v, ti, hdr, add)
case .Map: return _unmarshal_map(d, v, ti, hdr, add)
case: return .Bad_Major
}
}
_unmarshal_bytes :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header, add: Add) -> (err: Unmarshal_Error) {
#partial switch t in ti.variant {
case reflect.Type_Info_String:
bytes := err_conv(_decode_bytes(d, add)) or_return
if t.is_cstring {
raw := (^cstring)(v.data)
assert_safe_for_cstring(string(bytes))
raw^ = cstring(raw_data(bytes))
} else {
// String has same memory layout as a slice, so we can directly use it as a slice.
raw := (^mem.Raw_String)(v.data)
raw^ = transmute(mem.Raw_String)bytes
}
return
case reflect.Type_Info_Slice:
elem_base := reflect.type_info_base(t.elem)
if elem_base.id != byte { return _unsupported(v, hdr) }
bytes := err_conv(_decode_bytes(d, add)) or_return
raw := (^mem.Raw_Slice)(v.data)
raw^ = transmute(mem.Raw_Slice)bytes
return
case reflect.Type_Info_Dynamic_Array:
elem_base := reflect.type_info_base(t.elem)
if elem_base.id != byte { return _unsupported(v, hdr) }
bytes := err_conv(_decode_bytes(d, add)) or_return
raw := (^mem.Raw_Dynamic_Array)(v.data)
raw.data = raw_data(bytes)
raw.len = len(bytes)
raw.cap = len(bytes)
raw.allocator = context.allocator
return
case reflect.Type_Info_Array:
elem_base := reflect.type_info_base(t.elem)
if elem_base.id != byte { return _unsupported(v, hdr) }
bytes := err_conv(_decode_bytes(d, add, allocator=context.temp_allocator)) or_return
defer delete(bytes, context.temp_allocator)
if len(bytes) > t.count { return _unsupported(v, hdr) }
// Copy into array type, delete original.
slice := ([^]byte)(v.data)[:len(bytes)]
n := copy(slice, bytes)
assert(n == len(bytes))
return
}
return _unsupported(v, hdr)
}
_unmarshal_string :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header, add: Add) -> (err: Unmarshal_Error) {
#partial switch t in ti.variant {
case reflect.Type_Info_String:
text := err_conv(_decode_text(d, add)) or_return
if t.is_cstring {
raw := (^cstring)(v.data)
assert_safe_for_cstring(text)
raw^ = cstring(raw_data(text))
} else {
raw := (^string)(v.data)
raw^ = text
}
return
// Enum by its variant name.
case reflect.Type_Info_Enum:
text := err_conv(_decode_text(d, add, allocator=context.temp_allocator)) or_return
defer delete(text, context.temp_allocator)
for name, i in t.names {
if name == text {
if !_assign_int(any{v.data, ti.id}, t.values[i]) { return _unsupported(v, hdr) }
return
}
}
case reflect.Type_Info_Rune:
text := err_conv(_decode_text(d, add, allocator=context.temp_allocator)) or_return
defer delete(text, context.temp_allocator)
r := (^rune)(v.data)
dr, n := utf8.decode_rune(text)
if dr == utf8.RUNE_ERROR || n < len(text) {
return _unsupported(v, hdr)
}
r^ = dr
return
}
return _unsupported(v, hdr)
}
_unmarshal_array :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header, add: Add) -> (err: Unmarshal_Error) {
assign_array :: proc(
d: Decoder,
da: ^mem.Raw_Dynamic_Array,
elemt: ^reflect.Type_Info,
length: int,
growable := true,
) -> (out_of_space: bool, err: Unmarshal_Error) {
for idx: uintptr = 0; length == -1 || idx < uintptr(length); idx += 1 {
elem_ptr := rawptr(uintptr(da.data) + idx*uintptr(elemt.size))
elem := any{elem_ptr, elemt.id}
hdr := _decode_header(d.reader) or_return
// Double size if out of capacity.
if da.cap <= da.len {
// Not growable, error out.
if !growable { return true, .Out_Of_Memory }
cap := 2 * da.cap
ok := runtime.__dynamic_array_reserve(da, elemt.size, elemt.align, cap)
// NOTE: Might be lying here, but it is at least an allocator error.
if !ok { return false, .Out_Of_Memory }
}
err = _unmarshal_value(d, elem, hdr)
if length == -1 && err == .Break { break }
if err != nil { return }
da.len += 1
}
return false, nil
}
// Allow generically storing the values array.
switch &dst in v {
case ^Array:
dst = err_conv(_decode_array_ptr(d, add)) or_return
return
case Array:
dst = err_conv(_decode_array(d, add)) or_return
return
}
#partial switch t in ti.variant {
case reflect.Type_Info_Slice:
length, scap := err_conv(_decode_len_container(d, add)) or_return
data := mem.alloc_bytes_non_zeroed(t.elem.size * scap, t.elem.align) or_return
defer if err != nil { mem.free_bytes(data) }
da := mem.Raw_Dynamic_Array{raw_data(data), 0, length, context.allocator }
assign_array(d, &da, t.elem, length) or_return
if .Shrink_Excess in d.flags {
// Ignoring an error here, but this is not critical to succeed.
_ = runtime.__dynamic_array_shrink(&da, t.elem.size, t.elem.align, da.len)
}
raw := (^mem.Raw_Slice)(v.data)
raw.data = da.data
raw.len = da.len
return
case reflect.Type_Info_Dynamic_Array:
length, scap := err_conv(_decode_len_container(d, add)) or_return
data := mem.alloc_bytes_non_zeroed(t.elem.size * scap, t.elem.align) or_return
defer if err != nil { mem.free_bytes(data) }
raw := (^mem.Raw_Dynamic_Array)(v.data)
raw.data = raw_data(data)
raw.len = 0
raw.cap = length
raw.allocator = context.allocator
_ = assign_array(d, raw, t.elem, length) or_return
if .Shrink_Excess in d.flags {
// Ignoring an error here, but this is not critical to succeed.
_ = runtime.__dynamic_array_shrink(raw, t.elem.size, t.elem.align, raw.len)
}
return
case reflect.Type_Info_Array:
_, scap := err_conv(_decode_len_container(d, add)) or_return
length := min(scap, t.count)
if length > t.count {
return _unsupported(v, hdr)
}
da := mem.Raw_Dynamic_Array{rawptr(v.data), 0, length, context.allocator }
out_of_space := assign_array(d, &da, t.elem, length, growable=false) or_return
if out_of_space { return _unsupported(v, hdr) }
return
case reflect.Type_Info_Enumerated_Array:
_, scap := err_conv(_decode_len_container(d, add)) or_return
length := min(scap, t.count)
if length > t.count {
return _unsupported(v, hdr)
}
da := mem.Raw_Dynamic_Array{rawptr(v.data), 0, length, context.allocator }
out_of_space := assign_array(d, &da, t.elem, length, growable=false) or_return
if out_of_space { return _unsupported(v, hdr) }
return
case reflect.Type_Info_Complex:
_, scap := err_conv(_decode_len_container(d, add)) or_return
length := min(scap, 2)
if length > 2 {
return _unsupported(v, hdr)
}
da := mem.Raw_Dynamic_Array{rawptr(v.data), 0, 2, context.allocator }
info: ^runtime.Type_Info
switch ti.id {
case complex32: info = type_info_of(f16)
case complex64: info = type_info_of(f32)
case complex128: info = type_info_of(f64)
case: unreachable()
}
out_of_space := assign_array(d, &da, info, 2, growable=false) or_return
if out_of_space { return _unsupported(v, hdr) }
return
case reflect.Type_Info_Quaternion:
_, scap := err_conv(_decode_len_container(d, add)) or_return
length := min(scap, 4)
if length > 4 {
return _unsupported(v, hdr)
}
da := mem.Raw_Dynamic_Array{rawptr(v.data), 0, 4, context.allocator }
info: ^runtime.Type_Info
switch ti.id {
case quaternion64: info = type_info_of(f16)
case quaternion128: info = type_info_of(f32)
case quaternion256: info = type_info_of(f64)
case: unreachable()
}
out_of_space := assign_array(d, &da, info, 4, growable=false) or_return
if out_of_space { return _unsupported(v, hdr) }
return
case: return _unsupported(v, hdr)
}
}
_unmarshal_map :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header, add: Add) -> (err: Unmarshal_Error) {
r := d.reader
decode_key :: proc(d: Decoder, v: any, allocator := context.allocator) -> (k: string, err: Unmarshal_Error) {
entry_hdr := _decode_header(d.reader) or_return
entry_maj, entry_add := _header_split(entry_hdr)
#partial switch entry_maj {
case .Text:
k = err_conv(_decode_text(d, entry_add, allocator)) or_return
return
case .Bytes:
bytes := err_conv(_decode_bytes(d, entry_add, allocator=allocator)) or_return
k = string(bytes)
return
case:
err = _unsupported(v, entry_hdr)
return
}
}
// Allow generically storing the map array.
switch &dst in v {
case ^Map:
dst = err_conv(_decode_map_ptr(d, add)) or_return
return
case Map:
dst = err_conv(_decode_map(d, add)) or_return
return
}
#partial switch t in ti.variant {
case reflect.Type_Info_Struct:
if t.is_raw_union {
return _unsupported(v, hdr)
}
length, _ := err_conv(_decode_len_container(d, add)) or_return
unknown := length == -1
fields := reflect.struct_fields_zipped(ti.id)
for idx := 0; idx < len(fields) && (unknown || idx < length); idx += 1 {
// Decode key, keys can only be strings.
key: string
if keyv, kerr := decode_key(d, v, context.temp_allocator); unknown && kerr == .Break {
break
} else if kerr != nil {
err = kerr
return
} else {
key = keyv
}
defer delete(key, context.temp_allocator)
// Find matching field.
use_field_idx := -1
{
for field, field_idx in fields {
tag_value := string(reflect.struct_tag_get(field.tag, "cbor"))
if tag_value == "-" {
continue
}
if key == tag_value {
use_field_idx = field_idx
break
}
if key == field.name {
// No break because we want to still check remaining struct tags.
use_field_idx = field_idx
}
}
// Skips unused map entries.
if use_field_idx < 0 {
continue
}
}
field := fields[use_field_idx]
// name := field.name
ptr := rawptr(uintptr(v.data) + field.offset)
fany := any{ptr, field.type.id}
_unmarshal_value(d, fany, _decode_header(r) or_return) or_return
}
return
case reflect.Type_Info_Map:
if !reflect.is_string(t.key) {
return _unsupported(v, hdr)
}
raw_map := (^mem.Raw_Map)(v.data)
if raw_map.allocator.procedure == nil {
raw_map.allocator = context.allocator
}
defer if err != nil {
_ = runtime.map_free_dynamic(raw_map^, t.map_info)
}
length, scap := err_conv(_decode_len_container(d, add)) or_return
unknown := length == -1
if !unknown {
// Reserve space before setting so we can return allocation errors and be efficient on big maps.
new_len := uintptr(min(scap, runtime.map_len(raw_map^)+length))
runtime.map_reserve_dynamic(raw_map, t.map_info, new_len) or_return
}
// Temporary memory to unmarshal keys into before inserting them into the map.
elem_backing := mem.alloc_bytes_non_zeroed(t.value.size, t.value.align, context.temp_allocator) or_return
defer delete(elem_backing, context.temp_allocator)
map_backing_value := any{raw_data(elem_backing), t.value.id}
for idx := 0; unknown || idx < length; idx += 1 {
// Decode key, keys can only be strings.
key: string
if keyv, kerr := decode_key(d, v); unknown && kerr == .Break {
break
} else if kerr != nil {
err = kerr
return
} else {
key = keyv
}
if unknown || idx > scap {
// Reserve space for new element so we can return allocator errors.
new_len := uintptr(runtime.map_len(raw_map^)+1)
runtime.map_reserve_dynamic(raw_map, t.map_info, new_len) or_return
}
mem.zero_slice(elem_backing)
_unmarshal_value(d, map_backing_value, _decode_header(r) or_return) or_return
key_ptr := rawptr(&key)
key_cstr: cstring
if reflect.is_cstring(t.key) {
assert_safe_for_cstring(key)
key_cstr = cstring(raw_data(key))
key_ptr = &key_cstr
}
set_ptr := runtime.__dynamic_map_set_without_hash(raw_map, t.map_info, key_ptr, map_backing_value.data)
// We already reserved space for it, so this shouldn't fail.
assert(set_ptr != nil)
}
if .Shrink_Excess in d.flags {
_, _ = runtime.map_shrink_dynamic(raw_map, t.map_info)
}
return
case:
return _unsupported(v, hdr)
}
}
// Unmarshal into a union, based on the `TAG_OBJECT_TYPE` tag of the spec, it denotes a tag which
// contains an array of exactly two elements, the first is a textual representation of the following
// CBOR value's type.
_unmarshal_union :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header) -> (err: Unmarshal_Error) {
r := d.reader
#partial switch t in ti.variant {
case reflect.Type_Info_Union:
idhdr: Header
target_name: string
{
vhdr := _decode_header(r) or_return
vmaj, vadd := _header_split(vhdr)
if vmaj != .Array {
return .Bad_Tag_Value
}
n_items, _ := err_conv(_decode_len_container(d, vadd)) or_return
if n_items != 2 {
return .Bad_Tag_Value
}
idhdr = _decode_header(r) or_return
idmaj, idadd := _header_split(idhdr)
if idmaj != .Text {
return .Bad_Tag_Value
}
target_name = err_conv(_decode_text(d, idadd, context.temp_allocator)) or_return
}
defer delete(target_name, context.temp_allocator)
for variant, i in t.variants {
tag := i64(i)
if !t.no_nil {
tag += 1
}
#partial switch vti in variant.variant {
case reflect.Type_Info_Named:
if vti.name == target_name {
reflect.set_union_variant_raw_tag(v, tag)
return _unmarshal_value(d, any{v.data, variant.id}, _decode_header(r) or_return)
}
case:
builder := strings.builder_make(context.temp_allocator)
defer strings.builder_destroy(&builder)
reflect.write_type(&builder, variant)
variant_name := strings.to_string(builder)
if variant_name == target_name {
reflect.set_union_variant_raw_tag(v, tag)
return _unmarshal_value(d, any{v.data, variant.id}, _decode_header(r) or_return)
}
}
}
// No variant matched.
return _unsupported(v, idhdr)
case:
// Not a union.
return _unsupported(v, hdr)
}
}
_assign_int :: proc(val: any, i: $T) -> bool {
v := reflect.any_core(val)
// NOTE: should under/over flow be checked here? `encoding/json` doesn't, but maybe that is a
// less strict encoding?.
switch &dst in v {
case i8: dst = i8 (i)
case i16: dst = i16 (i)
case i16le: dst = i16le (i)
case i16be: dst = i16be (i)
case i32: dst = i32 (i)
case i32le: dst = i32le (i)
case i32be: dst = i32be (i)
case i64: dst = i64 (i)
case i64le: dst = i64le (i)
case i64be: dst = i64be (i)
case i128: dst = i128 (i)
case i128le: dst = i128le (i)
case i128be: dst = i128be (i)
case u8: dst = u8 (i)
case u16: dst = u16 (i)
case u16le: dst = u16le (i)
case u16be: dst = u16be (i)
case u32: dst = u32 (i)
case u32le: dst = u32le (i)
case u32be: dst = u32be (i)
case u64: dst = u64 (i)
case u64le: dst = u64le (i)
case u64be: dst = u64be (i)
case u128: dst = u128 (i)
case u128le: dst = u128le (i)
case u128be: dst = u128be (i)
case int: dst = int (i)
case uint: dst = uint (i)
case uintptr: dst = uintptr(i)
case:
ti := type_info_of(v.id)
if _, ok := ti.variant.(runtime.Type_Info_Bit_Set); ok {
do_byte_swap := !reflect.bit_set_is_big_endian(v)
switch ti.size * 8 {
case 0: // no-op.
case 8:
x := (^u8)(v.data)
x^ = u8(i)
case 16:
x := (^u16)(v.data)
x^ = do_byte_swap ? intrinsics.byte_swap(u16(i)) : u16(i)
case 32:
x := (^u32)(v.data)
x^ = do_byte_swap ? intrinsics.byte_swap(u32(i)) : u32(i)
case 64:
x := (^u64)(v.data)
x^ = do_byte_swap ? intrinsics.byte_swap(u64(i)) : u64(i)
case:
panic("unknown bit_size size")
}
return true
}
return false
}
return true
}
_assign_float :: proc(val: any, f: $T) -> bool {
v := reflect.any_core(val)
// NOTE: should under/over flow be checked here? `encoding/json` doesn't, but maybe that is a
// less strict encoding?.
switch &dst in v {
case f16: dst = f16 (f)
case f16le: dst = f16le(f)
case f16be: dst = f16be(f)
case f32: dst = f32 (f)
case f32le: dst = f32le(f)
case f32be: dst = f32be(f)
case f64: dst = f64 (f)
case f64le: dst = f64le(f)
case f64be: dst = f64be(f)
case complex32: dst = complex(f16(f), 0)
case complex64: dst = complex(f32(f), 0)
case complex128: dst = complex(f64(f), 0)
case quaternion64: dst = quaternion(w=f16(f), x=0, y=0, z=0)
case quaternion128: dst = quaternion(w=f32(f), x=0, y=0, z=0)
case quaternion256: dst = quaternion(w=f64(f), x=0, y=0, z=0)
case: return false
}
return true
}
_assign_bool :: proc(val: any, b: bool) -> bool {
v := reflect.any_core(val)
switch &dst in v {
case bool: dst = bool(b)
case b8: dst = b8 (b)
case b16: dst = b16 (b)
case b32: dst = b32 (b)
case b64: dst = b64 (b)
case: return false
}
return true
}
// Sanity check that the decoder added a nil byte to the end.
@(private, disabled=ODIN_DISABLE_ASSERT)
assert_safe_for_cstring :: proc(s: string, loc := #caller_location) {
assert(([^]byte)(raw_data(s))[len(s)] == 0, loc = loc)
}
+20 -6
View File
@@ -1,6 +1,6 @@
// package csv reads and writes comma-separated values (CSV) files.
// This package supports the format described in RFC 4180 <https://tools.ietf.org/html/rfc4180.html>
package csv
package encoding_csv
import "core:bufio"
import "core:bytes"
@@ -91,7 +91,10 @@ DEFAULT_RECORD_BUFFER_CAPACITY :: 256
// reader_init initializes a new Reader from r
reader_init :: proc(reader: ^Reader, r: io.Reader, buffer_allocator := context.allocator) {
reader.comma = ','
switch reader.comma {
case '\x00', '\n', '\r', 0xfffd:
reader.comma = ','
}
context.allocator = buffer_allocator
reserve(&reader.record_buffer, DEFAULT_RECORD_BUFFER_CAPACITY)
@@ -121,6 +124,7 @@ reader_destroy :: proc(r: ^Reader) {
// read reads a single record (a slice of fields) from r
//
// All \r\n sequences are normalized to \n, including multi-line field
@(require_results)
read :: proc(r: ^Reader, allocator := context.allocator) -> (record: []string, err: Error) {
if r.reuse_record {
record, err = _read_record(r, &r.last_record, allocator)
@@ -133,6 +137,7 @@ read :: proc(r: ^Reader, allocator := context.allocator) -> (record: []string, e
}
// is_io_error checks where an Error is a specific io.Error kind
@(require_results)
is_io_error :: proc(err: Error, io_err: io.Error) -> bool {
if v, ok := err.(io.Error); ok {
return v == io_err
@@ -140,10 +145,10 @@ is_io_error :: proc(err: Error, io_err: io.Error) -> bool {
return false
}
// read_all reads all the remaining records from r.
// Each record is a slice of fields.
// read_all is defined to read until an EOF, and does not treat, and does not treat EOF as an error
@(require_results)
read_all :: proc(r: ^Reader, allocator := context.allocator) -> ([][]string, Error) {
context.allocator = allocator
records: [dynamic][]string
@@ -153,13 +158,18 @@ read_all :: proc(r: ^Reader, allocator := context.allocator) -> ([][]string, Err
return records[:], nil
}
if rerr != nil {
return nil, rerr
// allow for a partial read
if record != nil {
append(&records, record)
}
return records[:], rerr
}
append(&records, record)
}
}
// read reads a single record (a slice of fields) from the provided input.
@(require_results)
read_from_string :: proc(input: string, record_allocator := context.allocator, buffer_allocator := context.allocator) -> (record: []string, n: int, err: Error) {
ir: strings.Reader
strings.reader_init(&ir, input)
@@ -175,6 +185,7 @@ read_from_string :: proc(input: string, record_allocator := context.allocator, b
// read_all reads all the remaining records from the provided input.
@(require_results)
read_all_from_string :: proc(input: string, records_allocator := context.allocator, buffer_allocator := context.allocator) -> ([][]string, Error) {
ir: strings.Reader
strings.reader_init(&ir, input)
@@ -186,7 +197,7 @@ read_all_from_string :: proc(input: string, records_allocator := context.allocat
return read_all(&r, records_allocator)
}
@private
@(private, require_results)
is_valid_delim :: proc(r: rune) -> bool {
switch r {
case 0, '"', '\r', '\n', utf8.RUNE_ERROR:
@@ -195,8 +206,9 @@ is_valid_delim :: proc(r: rune) -> bool {
return utf8.valid_rune(r)
}
@private
@(private, require_results)
_read_record :: proc(r: ^Reader, dst: ^[dynamic]string, allocator := context.allocator) -> ([]string, Error) {
@(require_results)
read_line :: proc(r: ^Reader) -> ([]byte, io.Error) {
if !r.multiline_fields {
line, err := bufio.reader_read_slice(&r.r, '\n')
@@ -266,6 +278,7 @@ _read_record :: proc(r: ^Reader, dst: ^[dynamic]string, allocator := context.all
unreachable()
}
@(require_results)
length_newline :: proc(b: []byte) -> int {
if len(b) > 0 && b[len(b)-1] == '\n' {
return 1
@@ -273,6 +286,7 @@ _read_record :: proc(r: ^Reader, dst: ^[dynamic]string, allocator := context.all
return 0
}
@(require_results)
next_rune :: proc(b: []byte) -> rune {
r, _ := utf8.decode_rune(b)
return r
+5 -2
View File
@@ -1,4 +1,4 @@
package csv
package encoding_csv
import "core:io"
import "core:strings"
@@ -17,7 +17,10 @@ Writer :: struct {
// writer_init initializes a Writer that writes to w
writer_init :: proc(writer: ^Writer, w: io.Writer) {
writer.comma = ','
switch writer.comma {
case '\x00', '\n', '\r', 0xfffd:
writer.comma = ','
}
writer.w = w
}
+1 -1
View File
@@ -1,4 +1,4 @@
package unicode_entity
package encoding_unicode_entity
/*
A unicode entity encoder/decoder
+1 -1
View File
@@ -1,4 +1,4 @@
package unicode_entity
package encoding_unicode_entity
/*
------ GENERATED ------ DO NOT EDIT ------ GENERATED ------ DO NOT EDIT ------ GENERATED ------
+1 -1
View File
@@ -1,4 +1,4 @@
package hex
package encoding_hex
import "core:strings"
+2 -2
View File
@@ -1,4 +1,4 @@
package json
package encoding_json
import "core:mem"
import "core:math/bits"
@@ -420,7 +420,7 @@ marshal_to_writer :: proc(w: io.Writer, v: any, opt: ^Marshal_Options) -> (err:
data := rawptr(uintptr(v.data) + info.offsets[i])
the_value := any{data, id}
if is_omitempty(the_value) {
if omitempty && is_omitempty(the_value) {
continue
}
+1 -1
View File
@@ -1,4 +1,4 @@
package json
package encoding_json
import "core:mem"
import "core:unicode/utf8"
+1 -1
View File
@@ -1,4 +1,4 @@
package json
package encoding_json
import "core:unicode/utf8"
+1 -1
View File
@@ -1,4 +1,4 @@
package json
package encoding_json
import "core:strings"
+2 -2
View File
@@ -1,4 +1,4 @@
package json
package encoding_json
import "core:mem"
import "core:math"
@@ -348,7 +348,7 @@ json_name_from_tag_value :: proc(value: string) -> (json_name, extra: string) {
json_name = value
if comma_index := strings.index_byte(json_name, ','); comma_index >= 0 {
json_name = json_name[:comma_index]
extra = json_name[comma_index:]
extra = value[1 + comma_index:]
}
return
}
+1 -1
View File
@@ -1,4 +1,4 @@
package json
package encoding_json
import "core:mem"
+1 -1
View File
@@ -25,4 +25,4 @@
```
*/
package varint
package encoding_varint
+1 -1
View File
@@ -8,7 +8,7 @@
// package varint implements variable length integer encoding and decoding using
// the LEB128 format as used by DWARF debug info, Android .dex and other file formats.
package varint
package encoding_varint
// In theory we should use the bigint package. In practice, varints bigger than this indicate a corrupted file.
// Instead we'll set limits on the values we'll encode/decode
+1 -1
View File
@@ -1,4 +1,4 @@
package xml
package encoding_xml
/*
An XML 1.0 / 1.1 parser
+1 -1
View File
@@ -1,4 +1,4 @@
package xml
package encoding_xml
/*
An XML 1.0 / 1.1 parser
+1 -1
View File
@@ -1,4 +1,4 @@
package xml
package encoding_xml
/*
An XML 1.0 / 1.1 parser
+1 -1
View File
@@ -24,7 +24,7 @@ MAYBE:
List of contributors:
- Jeroen van Rijn: Initial implementation.
*/
package xml
package encoding_xml
// An XML 1.0 / 1.1 parser
import "core:bytes"
+18 -15
View File
@@ -1,5 +1,5 @@
/*
package fmt implemented formatted I/O with procedures similar to C's printf and Python's format.
package fmt implements formatted I/O with procedures similar to C's printf and Python's format.
The format 'verbs' are derived from C's but simpler.
Printing
@@ -33,6 +33,8 @@ Floating-point, complex numbers, and quaternions:
%E scientific notation, e.g. -1.23456E+78
%f decimal point but no exponent, e.g. 123.456
%F synonym for %f
%g synonym for %f with default maximum precision
%G synonym for %g
%h hexadecimal (lower-case) representation with 0h prefix (0h01234abcd)
%H hexadecimal (upper-case) representation with 0H prefix (0h01234ABCD)
%m number of bytes in the best unit of measurement, e.g. 123.45mib
@@ -61,9 +63,9 @@ For compound values, the elements are printed using these rules recursively; lai
bit sets {key0 = elem0, key1 = elem1, ...}
pointer to above: &{}, &[], &map[]
Width is specified by an optional decimal number immediately preceding the verb.
Width is specified by an optional decimal number immediately after the '%'.
If not present, the width is whatever is necessary to represent the value.
Precision is specified after the (optional) width followed by a period followed by a decimal number.
Precision is specified after the (optional) width by a period followed by a decimal number.
If no period is present, a default precision is used.
A period with no following number specifies a precision of 0.
@@ -75,7 +77,7 @@ Examples:
%8.f width 8, precision 0
Width and precision are measured in units of Unicode code points (runes).
n.b. C's printf uses units of bytes
n.b. C's printf uses units of bytes.
Other flags:
@@ -92,7 +94,7 @@ Other flags:
0 pad with leading zeros rather than spaces
Flags are ignored by verbs that don't expect them
Flags are ignored by verbs that don't expect them.
For each printf-like procedure, there is a print function that takes no
@@ -105,19 +107,20 @@ Explicit argument indices:
In printf-like procedures, the default behaviour is for each formatting verb to format successive
arguments passed in the call. However, the notation [n] immediately before the verb indicates that
the nth zero-index argument is to be formatted instead.
The same notation before an '*' for a width or precision selecting the argument index holding the value.
Python-like syntax with argument indices differs for the selecting the argument index: {N:v}
The same notation before an '*' for a width or precision specifier selects the argument index
holding the value.
Python-like syntax with argument indices differs for selecting the argument index: {n:v}
Examples:
fmt.printf("%[1]d %[0]d\n", 13, 37); // C-like syntax
fmt.printf("{1:d} {0:d}\n", 13, 37); // Python-like syntax
fmt.printfln("%[1]d %[0]d", 13, 37) // C-like syntax
fmt.printfln("{1:d} {0:d}", 13, 37) // Python-like syntax
prints "37 13", whilst:
fmt.printf("%[2]*.[1]*[0]f\n", 17.0, 2, 6); // C-like syntax
fmt.printf("%{0:[2]*.[1]*f}\n", 17.0, 2, 6); // Python-like syntax
equivalent to:
fmt.printf("%6.2f\n", 17.0, 2, 6); // C-like syntax
fmt.printf("{:6.2f}\n", 17.0, 2, 6); // Python-like syntax
prints "17.00"
fmt.printfln("%*[2].*[1][0]f", 17.0, 2, 6) // C-like syntax
fmt.printfln("{0:*[2].*[1]f}", 17.0, 2, 6) // Python-like syntax
is equivalent to:
fmt.printfln("%6.2f", 17.0) // C-like syntax
fmt.printfln("{:6.2f}", 17.0) // Python-like syntax
and prints "17.00".
Format errors:
+205 -210
View File
@@ -25,8 +25,6 @@ Info :: struct {
prec: int,
indent: int,
reordered: bool,
good_arg_index: bool,
ignore_user_formatters: bool,
in_bad: bool,
@@ -120,11 +118,11 @@ register_user_formatter :: proc(id: typeid, formatter: User_Formatter) -> Regist
//
// Returns: A formatted string.
//
@(require_results)
aprint :: proc(args: ..any, sep := " ", allocator := context.allocator) -> string {
str: strings.Builder
strings.builder_init(&str, allocator)
sbprint(&str, ..args, sep=sep)
return strings.to_string(str)
return sbprint(&str, ..args, sep=sep)
}
// Creates a formatted string with a newline character at the end
//
@@ -136,11 +134,11 @@ aprint :: proc(args: ..any, sep := " ", allocator := context.allocator) -> strin
//
// Returns: A formatted string with a newline character at the end.
//
@(require_results)
aprintln :: proc(args: ..any, sep := " ", allocator := context.allocator) -> string {
str: strings.Builder
strings.builder_init(&str, allocator)
sbprintln(&str, ..args, sep=sep)
return strings.to_string(str)
return sbprintln(&str, ..args, sep=sep)
}
// Creates a formatted string using a format string and arguments
//
@@ -153,11 +151,11 @@ aprintln :: proc(args: ..any, sep := " ", allocator := context.allocator) -> str
//
// Returns: A formatted string. The returned string must be freed accordingly.
//
@(require_results)
aprintf :: proc(fmt: string, args: ..any, allocator := context.allocator, newline := false) -> string {
str: strings.Builder
strings.builder_init(&str, allocator)
sbprintf(&str, fmt, ..args, newline=newline)
return strings.to_string(str)
return sbprintf(&str, fmt, ..args, newline=newline)
}
// Creates a formatted string using a format string and arguments, followed by a newline.
//
@@ -169,6 +167,7 @@ aprintf :: proc(fmt: string, args: ..any, allocator := context.allocator, newlin
//
// Returns: A formatted string. The returned string must be freed accordingly.
//
@(require_results)
aprintfln :: proc(fmt: string, args: ..any, allocator := context.allocator) -> string {
return aprintf(fmt, ..args, allocator=allocator, newline=true)
}
@@ -182,11 +181,11 @@ aprintfln :: proc(fmt: string, args: ..any, allocator := context.allocator) -> s
//
// Returns: A formatted string.
//
@(require_results)
tprint :: proc(args: ..any, sep := " ") -> string {
str: strings.Builder
strings.builder_init(&str, context.temp_allocator)
sbprint(&str, ..args, sep=sep)
return strings.to_string(str)
return sbprint(&str, ..args, sep=sep)
}
// Creates a formatted string with a newline character at the end
//
@@ -198,11 +197,11 @@ tprint :: proc(args: ..any, sep := " ") -> string {
//
// Returns: A formatted string with a newline character at the end.
//
@(require_results)
tprintln :: proc(args: ..any, sep := " ") -> string {
str: strings.Builder
strings.builder_init(&str, context.temp_allocator)
sbprintln(&str, ..args, sep=sep)
return strings.to_string(str)
return sbprintln(&str, ..args, sep=sep)
}
// Creates a formatted string using a format string and arguments
//
@@ -215,11 +214,11 @@ tprintln :: proc(args: ..any, sep := " ") -> string {
//
// Returns: A formatted string.
//
@(require_results)
tprintf :: proc(fmt: string, args: ..any, newline := false) -> string {
str: strings.Builder
strings.builder_init(&str, context.temp_allocator)
sbprintf(&str, fmt, ..args, newline=newline)
return strings.to_string(str)
return sbprintf(&str, fmt, ..args, newline=newline)
}
// Creates a formatted string using a format string and arguments, followed by a newline.
//
@@ -231,6 +230,7 @@ tprintf :: proc(fmt: string, args: ..any, newline := false) -> string {
//
// Returns: A formatted string.
//
@(require_results)
tprintfln :: proc(fmt: string, args: ..any) -> string {
return tprintf(fmt, ..args, newline=true)
}
@@ -339,6 +339,7 @@ panicf :: proc(fmt: string, args: ..any, loc := #caller_location) -> ! {
//
// Returns: A formatted C string
//
@(require_results)
caprintf :: proc(format: string, args: ..any, newline := false) -> cstring {
str: strings.Builder
strings.builder_init(&str)
@@ -357,6 +358,7 @@ caprintf :: proc(format: string, args: ..any, newline := false) -> cstring {
//
// Returns: A formatted C string
//
@(require_results)
caprintfln :: proc(format: string, args: ..any) -> cstring {
return caprintf(format, ..args, newline=true)
}
@@ -371,6 +373,7 @@ caprintfln :: proc(format: string, args: ..any) -> cstring {
//
// Returns: A formatted C string
//
@(require_results)
ctprintf :: proc(format: string, args: ..any, newline := false) -> cstring {
str: strings.Builder
strings.builder_init(&str, context.temp_allocator)
@@ -389,6 +392,7 @@ ctprintf :: proc(format: string, args: ..any, newline := false) -> cstring {
//
// Returns: A formatted C string
//
@(require_results)
ctprintfln :: proc(format: string, args: ..any) -> cstring {
return ctprintf(format, ..args, newline=true)
}
@@ -521,13 +525,107 @@ wprintln :: proc(w: io.Writer, args: ..any, sep := " ", flush := true) -> int {
// Returns: The number of bytes written
//
wprintf :: proc(w: io.Writer, fmt: string, args: ..any, flush := true, newline := false) -> int {
MAX_CHECKED_ARGS :: 64
assert(len(args) <= MAX_CHECKED_ARGS, "number of args > 64 is unsupported")
parse_options :: proc(fi: ^Info, fmt: string, index, end: int, unused_args: ^bit_set[0 ..< MAX_CHECKED_ARGS], args: ..any) -> int {
i := index
// Prefix
prefix_loop: for ; i < end; i += 1 {
switch fmt[i] {
case '+':
fi.plus = true
case '-':
fi.minus = true
fi.zero = false
case ' ':
fi.space = true
case '#':
fi.hash = true
case '0':
fi.zero = !fi.minus
case:
break prefix_loop
}
}
// Width
if i < end && fmt[i] == '*' {
i += 1
width_index, _, index_ok := _arg_number(fmt, &i, len(args))
if index_ok {
unused_args^ -= {width_index}
fi.width, _, fi.width_set = int_from_arg(args, width_index)
if !fi.width_set {
io.write_string(fi.writer, "%!(BAD WIDTH)", &fi.n)
}
if fi.width < 0 {
fi.width = -fi.width
fi.minus = true
fi.zero = false
}
}
} else {
fi.width, i, fi.width_set = _parse_int(fmt, i)
}
// Precision
if i < end && fmt[i] == '.' {
i += 1
if i < end && fmt[i] == '*' {
i += 1
precision_index, _, index_ok := _arg_number(fmt, &i, len(args))
if index_ok {
unused_args^ -= {precision_index}
fi.prec, _, fi.prec_set = int_from_arg(args, precision_index)
if fi.prec < 0 {
fi.prec = 0
fi.prec_set = false
}
if !fi.prec_set {
io.write_string(fi.writer, "%!(BAD PRECISION)", &fi.n)
}
}
} else {
prev_i := i
fi.prec, i, fi.prec_set = _parse_int(fmt, i)
if i == prev_i {
fi.prec = 0
fi.prec_set = true
}
}
}
return i
}
error_check_arg :: proc(fi: ^Info, arg_parsed: bool, unused_args: bit_set[0 ..< MAX_CHECKED_ARGS]) -> (int, bool) {
if !arg_parsed {
for index in unused_args {
return index, true
}
io.write_string(fi.writer, "%!(MISSING ARGUMENT)", &fi.n)
} else {
io.write_string(fi.writer, "%!(BAD ARGUMENT NUMBER)", &fi.n)
}
return 0, false
}
fi: Info
arg_index: int = 0
end := len(fmt)
was_prev_index := false
unused_args: bit_set[0 ..< MAX_CHECKED_ARGS]
for i in 0 ..< len(args) {
unused_args += {i}
}
loop: for i := 0; i < end; /**/ {
fi = Info{writer = w, good_arg_index = true, reordered = fi.reordered, n = fi.n}
fi = Info{writer = w, n = fi.n}
prev_i := i
for i < end && !(fmt[i] == '%' || fmt[i] == '{' || fmt[i] == '}') {
@@ -561,191 +659,65 @@ wprintf :: proc(w: io.Writer, fmt: string, args: ..any, flush := true, newline :
}
if char == '%' {
prefix_loop: for ; i < end; i += 1 {
switch fmt[i] {
case '+':
fi.plus = true
case '-':
fi.minus = true
fi.zero = false
case ' ':
fi.space = true
case '#':
fi.hash = true
case '0':
fi.zero = !fi.minus
case:
break prefix_loop
}
}
arg_index, i, was_prev_index = _arg_number(&fi, arg_index, fmt, i, len(args))
// Width
if i < end && fmt[i] == '*' {
if i < end && fmt[i] == '%' {
io.write_byte(fi.writer, '%', &fi.n)
i += 1
fi.width, arg_index, fi.width_set = int_from_arg(args, arg_index)
if !fi.width_set {
io.write_string(w, "%!(BAD WIDTH)", &fi.n)
}
if fi.width < 0 {
fi.width = -fi.width
fi.minus = true
fi.zero = false
}
was_prev_index = false
} else {
fi.width, i, fi.width_set = _parse_int(fmt, i)
if was_prev_index && fi.width_set { // %[6]2d
fi.good_arg_index = false
}
continue loop
}
// Precision
if i < end && fmt[i] == '.' {
i += 1
if was_prev_index { // %[6].2d
fi.good_arg_index = false
}
if i < end && fmt[i] == '*' {
arg_index, i, was_prev_index = _arg_number(&fi, arg_index, fmt, i, len(args))
i += 1
fi.prec, arg_index, fi.prec_set = int_from_arg(args, arg_index)
if fi.prec < 0 {
fi.prec = 0
fi.prec_set = false
}
if !fi.prec_set {
io.write_string(fi.writer, "%!(BAD PRECISION)", &fi.n)
}
was_prev_index = false
} else {
fi.prec, i, fi.prec_set = _parse_int(fmt, i)
}
}
i = parse_options(&fi, fmt, i, end, &unused_args, ..args)
if !was_prev_index {
arg_index, i, was_prev_index = _arg_number(&fi, arg_index, fmt, i, len(args))
arg_index, arg_parsed, index_ok := _arg_number(fmt, &i, len(args))
if !index_ok {
arg_index, index_ok = error_check_arg(&fi, arg_parsed, unused_args)
}
if i >= end {
io.write_string(fi.writer, "%!(NO VERB)", &fi.n)
break loop
} else if fmt[i] == ' ' {
io.write_string(fi.writer, "%!(NO VERB)", &fi.n)
continue loop
}
verb, w := utf8.decode_rune_in_string(fmt[i:])
i += w
switch {
case verb == '%':
io.write_byte(fi.writer, '%', &fi.n)
case !fi.good_arg_index:
io.write_string(fi.writer, "%!(BAD ARGUMENT NUMBER)", &fi.n)
case arg_index >= len(args):
io.write_string(fi.writer, "%!(MISSING ARGUMENT)", &fi.n)
case:
if index_ok {
unused_args -= {arg_index}
fmt_arg(&fi, args[arg_index], verb)
arg_index += 1
}
} else if char == '{' {
arg_index: int
arg_parsed, index_ok: bool
if i < end && fmt[i] != '}' && fmt[i] != ':' {
new_arg_index, new_i, ok := _parse_int(fmt, i)
if ok {
fi.reordered = true
was_prev_index = true
arg_index = new_arg_index
i = new_i
} else {
io.write_string(fi.writer, "%!(BAD ARGUMENT NUMBER ", &fi.n)
// Skip over the bad argument
start_index := i
for i < end && fmt[i] != '}' && fmt[i] != ':' {
i += 1
}
fmt_arg(&fi, fmt[start_index:i], 'v')
io.write_string(fi.writer, ")", &fi.n)
arg_index, i, arg_parsed = _parse_int(fmt, i)
if arg_parsed {
index_ok = 0 <= arg_index && arg_index < len(args)
}
}
if !index_ok {
arg_index, index_ok = error_check_arg(&fi, arg_parsed, unused_args)
}
verb: rune = 'v'
if i < end && fmt[i] == ':' {
i += 1
prefix_loop_percent: for ; i < end; i += 1 {
switch fmt[i] {
case '+':
fi.plus = true
case '-':
fi.minus = true
fi.zero = false
case ' ':
fi.space = true
case '#':
fi.hash = true
case '0':
fi.zero = !fi.minus
case:
break prefix_loop_percent
}
}
arg_index, i, was_prev_index = _arg_number(&fi, arg_index, fmt, i, len(args))
// Width
if i < end && fmt[i] == '*' {
i += 1
fi.width, arg_index, fi.width_set = int_from_arg(args, arg_index)
if !fi.width_set {
io.write_string(fi.writer, "%!(BAD WIDTH)", &fi.n)
}
if fi.width < 0 {
fi.width = -fi.width
fi.minus = true
fi.zero = false
}
was_prev_index = false
} else {
fi.width, i, fi.width_set = _parse_int(fmt, i)
if was_prev_index && fi.width_set { // %[6]2d
fi.good_arg_index = false
}
}
// Precision
if i < end && fmt[i] == '.' {
i += 1
if was_prev_index { // %[6].2d
fi.good_arg_index = false
}
if i < end && fmt[i] == '*' {
arg_index, i, was_prev_index = _arg_number(&fi, arg_index, fmt, i, len(args))
i += 1
fi.prec, arg_index, fi.prec_set = int_from_arg(args, arg_index)
if fi.prec < 0 {
fi.prec = 0
fi.prec_set = false
}
if !fi.prec_set {
io.write_string(fi.writer, "%!(BAD PRECISION)", &fi.n)
}
was_prev_index = false
} else {
fi.prec, i, fi.prec_set = _parse_int(fmt, i)
}
}
if !was_prev_index {
arg_index, i, was_prev_index = _arg_number(&fi, arg_index, fmt, i, len(args))
}
i = parse_options(&fi, fmt, i, end, &unused_args, ..args)
if i >= end {
io.write_string(fi.writer, "%!(NO VERB)", &fi.n)
break loop
} else if fmt[i] == '}' {
i += 1
io.write_string(fi.writer, "%!(NO VERB)", &fi.n)
continue
}
w: int = 1
@@ -764,31 +736,35 @@ wprintf :: proc(w: io.Writer, fmt: string, args: ..any, flush := true, newline :
switch {
case brace != '}':
io.write_string(fi.writer, "%!(MISSING CLOSE BRACE)", &fi.n)
case !fi.good_arg_index:
io.write_string(fi.writer, "%!(BAD ARGUMENT NUMBER)", &fi.n)
case arg_index >= len(args):
io.write_string(fi.writer, "%!(MISSING ARGUMENT)", &fi.n)
case:
case index_ok:
fmt_arg(&fi, args[arg_index], verb)
arg_index += 1
unused_args -= {arg_index}
}
}
}
if !fi.reordered && arg_index < len(args) {
io.write_string(fi.writer, "%!(EXTRA ", &fi.n)
for arg, index in args[arg_index:] {
if index > 0 {
io.write_string(fi.writer, ", ", &fi.n)
if unused_args != {} {
// Use default options when formatting extra arguments.
extra_fi := Info { writer = fi.writer, n = fi.n }
io.write_string(extra_fi.writer, "%!(EXTRA ", &extra_fi.n)
first_printed := false
for index in unused_args {
if first_printed {
io.write_string(extra_fi.writer, ", ", &extra_fi.n)
}
arg := args[index]
if arg == nil {
io.write_string(fi.writer, "<nil>", &fi.n)
io.write_string(extra_fi.writer, "<nil>", &extra_fi.n)
} else {
fmt_arg(&fi, args[index], 'v')
fmt_arg(&extra_fi, arg, 'v')
}
first_printed = true
}
io.write_string(fi.writer, ")", &fi.n)
io.write_byte(extra_fi.writer, ')', &extra_fi.n)
fi.n = extra_fi.n
}
if newline {
@@ -871,18 +847,16 @@ _parse_int :: proc(s: string, offset: int) -> (result: int, new_offset: int, ok:
// Parses an argument number from a format string and determines if it's valid
//
// Inputs:
// - fi: A pointer to an Info structure
// - arg_index: The current argument index
// - format: The format string to parse
// - offset: The current position in the format string
// - offset: A pointer to the current position in the format string
// - arg_count: The total number of arguments
//
// Returns:
// - index: The parsed argument index
// - new_offset: The new position in the format string
// - ok: A boolean indicating if the parsed argument number is valid
// - parsed: A boolean indicating if an argument number was parsed
// - ok: A boolean indicating if the parsed argument number is within arg_count
//
_arg_number :: proc(fi: ^Info, arg_index: int, format: string, offset, arg_count: int) -> (index, new_offset: int, ok: bool) {
_arg_number :: proc(format: string, offset: ^int, arg_count: int) -> (index: int, parsed, ok: bool) {
parse_arg_number :: proc(format: string) -> (int, int, bool) {
if len(format) < 3 {
return 0, 1, false
@@ -890,30 +864,28 @@ _arg_number :: proc(fi: ^Info, arg_index: int, format: string, offset, arg_count
for i in 1..<len(format) {
if format[i] == ']' {
width, new_index, ok := _parse_int(format, 1)
value, new_index, ok := _parse_int(format, 1)
if !ok || new_index != i {
return 0, i+1, false
}
return width-1, i+1, true
return value, i+1, true
}
}
return 0, 1, false
}
i := offset^
if len(format) <= offset || format[offset] != '[' {
return arg_index, offset, false
if len(format) <= i || format[i] != '[' {
return 0, false, false
}
fi.reordered = true
width: int
index, width, ok = parse_arg_number(format[offset:])
if ok && 0 <= index && index < arg_count {
return index, offset+width, true
}
fi.good_arg_index = false
return arg_index, offset+width, false
index, width, parsed = parse_arg_number(format[i:])
offset^ = i + width
ok = parsed && 0 <= index && index < arg_count
return
}
// Retrieves an integer from a list of any type at the specified index
//
@@ -1900,7 +1872,7 @@ fmt_struct :: proc(fi: ^Info, v: any, the_verb: rune, info: runtime.Type_Info_St
// fi.hash = false;
fi.indent += 1
if hash {
if !is_soa && hash {
io.write_byte(fi.writer, '\n', &fi.n)
}
defer {
@@ -1934,6 +1906,9 @@ fmt_struct :: proc(fi: ^Info, v: any, the_verb: rune, info: runtime.Type_Info_St
n = uintptr((^int)(uintptr(v.data) + info.offsets[actual_field_count])^)
}
if hash && n > 0 {
io.write_byte(fi.writer, '\n', &fi.n)
}
for index in 0..<n {
if !hash && index > 0 { io.write_string(fi.writer, ", ", &fi.n) }
@@ -1942,9 +1917,23 @@ fmt_struct :: proc(fi: ^Info, v: any, the_verb: rune, info: runtime.Type_Info_St
if !hash && field_count > 0 { io.write_string(fi.writer, ", ", &fi.n) }
if hash {
fi.indent -= 1
fmt_write_indent(fi)
fi.indent += 1
}
io.write_string(fi.writer, base_type_name, &fi.n)
io.write_byte(fi.writer, '{', &fi.n)
defer io.write_byte(fi.writer, '}', &fi.n)
if hash { io.write_byte(fi.writer, '\n', &fi.n) }
defer {
if hash {
fi.indent -= 1
fmt_write_indent(fi)
fi.indent += 1
}
io.write_byte(fi.writer, '}', &fi.n)
if hash { io.write_string(fi.writer, ",\n", &fi.n) }
}
fi.record_level += 1
defer fi.record_level -= 1
@@ -2156,14 +2145,18 @@ fmt_named :: proc(fi: ^Info, v: any, verb: rune, info: runtime.Type_Info_Named)
when ODIN_ERROR_POS_STYLE == .Default {
io.write_byte(fi.writer, '(', &fi.n)
io.write_int(fi.writer, int(a.line), 10, &fi.n)
io.write_byte(fi.writer, ':', &fi.n)
io.write_int(fi.writer, int(a.column), 10, &fi.n)
if a.column != 0 {
io.write_byte(fi.writer, ':', &fi.n)
io.write_int(fi.writer, int(a.column), 10, &fi.n)
}
io.write_byte(fi.writer, ')', &fi.n)
} else when ODIN_ERROR_POS_STYLE == .Unix {
io.write_byte(fi.writer, ':', &fi.n)
io.write_int(fi.writer, int(a.line), 10, &fi.n)
io.write_byte(fi.writer, ':', &fi.n)
io.write_int(fi.writer, int(a.column), 10, &fi.n)
if a.column != 0 {
io.write_byte(fi.writer, ':', &fi.n)
io.write_int(fi.writer, int(a.column), 10, &fi.n)
}
io.write_byte(fi.writer, ':', &fi.n)
} else {
#panic("Unhandled ODIN_ERROR_POS_STYLE")
@@ -2505,8 +2498,11 @@ fmt_bit_field :: proc(fi: ^Info, v: any, verb: rune, info: runtime.Type_Info_Bit
bit_offset := info.bit_offsets[i]
bit_size := info.bit_sizes[i]
value := read_bits(([^]byte)(v.data), bit_offset, bit_size)
type := info.types[i]
value := read_bits(([^]byte)(v.data), bit_offset, bit_size)
if reflect.is_endian_big(type) {
value <<= u64(8*type.size) - u64(bit_size)
}
if !reflect.is_unsigned(runtime.type_info_core(type)) {
// Sign Extension
@@ -2540,7 +2536,6 @@ fmt_value :: proc(fi: ^Info, v: any, verb: rune) {
if _user_formatters != nil && !fi.ignore_user_formatters {
formatter := _user_formatters[v.id]
if formatter != nil {
fi.ignore_user_formatters = false
if ok := formatter(fi, v, verb); !ok {
fi.ignore_user_formatters = true
fmt_bad_verb(fi, verb)
+55
View File
@@ -1,7 +1,9 @@
//+build js
package fmt
import "core:bufio"
import "core:io"
import "core:os"
foreign import "odin_env"
@@ -31,12 +33,63 @@ stderr := io.Writer{
data = rawptr(uintptr(2)),
}
@(private="file")
fd_to_writer :: proc(fd: os.Handle, loc := #caller_location) -> io.Writer {
switch fd {
case 1: return stdout
case 2: return stderr
case: panic("`fmt.fprint` variant called with invalid file descriptor for JS, only 1 (stdout) and 2 (stderr) are supported", loc)
}
}
// fprint formats using the default print settings and writes to fd
fprint :: proc(fd: os.Handle, args: ..any, sep := " ", flush := true, loc := #caller_location) -> int {
buf: [1024]byte
b: bufio.Writer
defer bufio.writer_flush(&b)
bufio.writer_init_with_buf(&b, fd_to_writer(fd, loc), buf[:])
w := bufio.writer_to_writer(&b)
return wprint(w, ..args, sep=sep, flush=flush)
}
// fprintln formats using the default print settings and writes to fd
fprintln :: proc(fd: os.Handle, args: ..any, sep := " ", flush := true, loc := #caller_location) -> int {
buf: [1024]byte
b: bufio.Writer
defer bufio.writer_flush(&b)
bufio.writer_init_with_buf(&b, fd_to_writer(fd, loc), buf[:])
w := bufio.writer_to_writer(&b)
return wprintln(w, ..args, sep=sep, flush=flush)
}
// fprintf formats according to the specified format string and writes to fd
fprintf :: proc(fd: os.Handle, fmt: string, args: ..any, flush := true, newline := false, loc := #caller_location) -> int {
buf: [1024]byte
b: bufio.Writer
defer bufio.writer_flush(&b)
bufio.writer_init_with_buf(&b, fd_to_writer(fd, loc), buf[:])
w := bufio.writer_to_writer(&b)
return wprintf(w, fmt, ..args, flush=flush, newline=newline)
}
// fprintfln formats according to the specified format string and writes to fd, followed by a newline.
fprintfln :: proc(fd: os.Handle, fmt: string, args: ..any, flush := true, loc := #caller_location) -> int {
return fprintf(fd, fmt, ..args, flush=flush, newline=true, loc=loc)
}
// print formats using the default print settings and writes to stdout
print :: proc(args: ..any, sep := " ", flush := true) -> int { return wprint(w=stdout, args=args, sep=sep, flush=flush) }
// println formats using the default print settings and writes to stdout
println :: proc(args: ..any, sep := " ", flush := true) -> int { return wprintln(w=stdout, args=args, sep=sep, flush=flush) }
// printf formats according to the specififed format string and writes to stdout
printf :: proc(fmt: string, args: ..any, flush := true) -> int { return wprintf(stdout, fmt, ..args, flush=flush) }
// printfln formats according to the specified format string and writes to stdout, followed by a newline.
printfln :: proc(fmt: string, args: ..any, flush := true) -> int { return wprintf(stdout, fmt, ..args, flush=flush, newline=true) }
// eprint formats using the default print settings and writes to stderr
eprint :: proc(args: ..any, sep := " ", flush := true) -> int { return wprint(w=stderr, args=args, sep=sep, flush=flush) }
@@ -44,3 +97,5 @@ eprint :: proc(args: ..any, sep := " ", flush := true) -> int { return wprint(
eprintln :: proc(args: ..any, sep := " ", flush := true) -> int { return wprintln(w=stderr, args=args, sep=sep, flush=flush) }
// eprintf formats according to the specififed format string and writes to stderr
eprintf :: proc(fmt: string, args: ..any, flush := true) -> int { return wprintf(stderr, fmt, ..args, flush=flush) }
// eprintfln formats according to the specified format string and writes to stderr, followed by a newline.
eprintfln :: proc(fmt: string, args: ..any, flush := true) -> int { return wprintf(stdout, fmt, ..args, flush=flush, newline=true) }
+21 -18
View File
@@ -1,5 +1,6 @@
/*
Formats:
PBM (P1, P4): Portable Bit Map, stores black and white images (1 channel)
PGM (P2, P5): Portable Gray Map, stores greyscale images (1 channel, 1 or 2 bytes per value)
PPM (P3, P6): Portable Pixel Map, stores colour images (3 channel, 1 or 2 bytes per value)
@@ -7,27 +8,29 @@ Formats:
PFM (Pf, PF): Portable Float Map, stores floating-point images (Pf: 1 channel, PF: 3 channel)
Reading:
All formats fill out header fields `format`, `width`, `height`, `channels`, `depth`
Specific formats use more fields
PGM, PPM, and PAM set `maxval` (maximum of 65535)
PAM sets `tupltype` if there is one, and can set `channels` to any value (not just 1 or 3)
PFM sets `scale` (float equivalent of `maxval`) and `little_endian` (endianness of stored floats)
Currently doesn't support reading multiple images from one binary-format file
- All formats fill out header fields `format`, `width`, `height`, `channels`, `depth`.
- Specific formats use more fields:
PGM, PPM, and PAM set `maxval` (maximum of 65535)
PAM sets `tupltype` if there is one, and can set `channels` to any value (not just 1 or 3)
PFM sets `scale` (float equivalent of `maxval`) and `little_endian` (endianness of stored floats)
- Currently doesn't support reading multiple images from one binary-format file.
Writing:
You can use your own `Netpbm_Info` struct to control how images are written
All formats require the header field `format` to be specified
Additional header fields are required for specific formats
PGM, PPM, and PAM require `maxval` (maximum of 65535)
PAM also uses `tupltype`, though it may be left as default (empty or nil string)
PFM requires `scale`, and optionally `little_endian`
- You can use your own `Netpbm_Info` struct to control how images are written.
- All formats require the header field `format` to be specified.
- Additional header fields are required for specific formats:
PGM, PPM, and PAM require `maxval` (maximum of 65535)
PAM also uses `tupltype`, though it may be left as default (empty or nil string)
PFM requires `scale`, and optionally `little_endian`
Some syntax differences from the specifications:
`channels` stores the number of values per pixel, what the PAM specification calls `depth`
`depth` instead is the number of bits for a single value (32 for PFM, 16 or 8 otherwise)
`scale` and `little_endian` are separated, so the `header` will always store a positive `scale`
`little_endian` will only be true for a negative `scale` PFM, every other format will be false
`little_endian` only describes the netpbm data being read/written, the image buffer will be native
*/
- `channels` stores the number of values per pixel, what the PAM specification calls `depth`
- `depth` instead is the number of bits for a single value (32 for PFM, 16 or 8 otherwise)
- `scale` and `little_endian` are separated, so the `header` will always store a positive `scale`
- `little_endian` will only be true for a negative `scale` PFM, every other format will be false
- `little_endian` only describes the netpbm data being read/written, the image buffer will be native
*/
package netpbm
+24 -1
View File
@@ -29,7 +29,7 @@ Error :: enum i32 {
// Invalid_Write means that a write returned an impossible count
Invalid_Write,
// Short_Buffer means that a read required a longer buffer than was provided
// Short_Buffer means that a read/write required a longer buffer than was provided
Short_Buffer,
// No_Progress is returned by some implementations of `io.Reader` when many calls
@@ -359,6 +359,29 @@ read_at_least :: proc(r: Reader, buf: []byte, min: int) -> (n: int, err: Error)
return
}
// write_full writes until the entire contents of `buf` has been written or an error occurs.
write_full :: proc(w: Writer, buf: []byte) -> (n: int, err: Error) {
return write_at_least(w, buf, len(buf))
}
// write_at_least writes at least `buf[:min]` to the writer and returns the amount written.
// If an error occurs before writing everything it is returned.
write_at_least :: proc(w: Writer, buf: []byte, min: int) -> (n: int, err: Error) {
if len(buf) < min {
return 0, .Short_Buffer
}
for n < min && err == nil {
nn: int
nn, err = write(w, buf[n:])
n += nn
}
if err == nil && n < min {
err = .Short_Write
}
return
}
// copy copies from src to dst till either EOF is reached on src or an error occurs
// It returns the number of bytes copied and the first error that occurred whilst copying, if any.
copy :: proc(dst: Writer, src: Reader) -> (written: i64, err: Error) {
+1 -1
View File
@@ -356,7 +356,7 @@ int_count_lsb :: proc(a: ^Int, allocator := context.allocator) -> (count: int, e
}
platform_count_lsb :: #force_inline proc(a: $T) -> (count: int)
where intrinsics.type_is_integer(T) && intrinsics.type_is_unsigned(T) {
where intrinsics.type_is_integer(T), intrinsics.type_is_unsigned(T) {
return int(intrinsics.count_trailing_zeros(a)) if a > 0 else 0
}
+2 -2
View File
@@ -546,7 +546,7 @@ internal_int_shl1 :: proc(dest, src: ^Int, allocator := context.allocator) -> (e
Like `internal_int_mul_digit` but with an integer as the small input.
*/
internal_int_mul_integer :: proc(dest, a: ^Int, b: $T, allocator := context.allocator) -> (err: Error)
where intrinsics.type_is_integer(T) && T != DIGIT {
where intrinsics.type_is_integer(T), T != DIGIT {
context.allocator = allocator
t := &Int{}
@@ -2806,7 +2806,7 @@ internal_int_count_lsb :: proc(a: ^Int) -> (count: int, err: Error) {
}
internal_platform_count_lsb :: #force_inline proc(a: $T) -> (count: int)
where intrinsics.type_is_integer(T) && intrinsics.type_is_unsigned(T) {
where intrinsics.type_is_integer(T), intrinsics.type_is_unsigned(T) {
return int(intrinsics.count_trailing_zeros(a)) if a > 0 else 0
}
+14
View File
@@ -1247,6 +1247,20 @@ internal_random_prime :: proc(a: ^Int, size_in_bits: int, trials: int, flags :=
a.digit[0] |= 3
}
if .Second_MSB_On in flags {
/*
Ensure there's enough space for the bit to be set.
*/
if a.used * _DIGIT_BITS < size_in_bits - 1 {
new_size := (size_in_bits - 1) / _DIGIT_BITS
if new_size % _DIGIT_BITS > 0 {
new_size += 1
}
internal_grow(a, new_size) or_return
a.used = new_size
}
internal_int_bitfield_set_single(a, size_in_bits - 2) or_return
}
+2 -2
View File
@@ -469,7 +469,7 @@ internal_int_pack_count :: proc(a: ^Int, $T: typeid, nails := 0) -> (size_needed
Assumes `a` not to be `nil` and to have been initialized.
*/
internal_int_pack :: proc(a: ^Int, buf: []$T, nails := 0, order := Order.LSB_First) -> (written: int, err: Error)
where intrinsics.type_is_integer(T) && intrinsics.type_is_unsigned(T) && size_of(T) <= 16 {
where intrinsics.type_is_integer(T), intrinsics.type_is_unsigned(T), size_of(T) <= 16 {
assert(nails >= 0 && nails < (size_of(T) * 8))
@@ -505,7 +505,7 @@ internal_int_pack :: proc(a: ^Int, buf: []$T, nails := 0, order := Order.LSB_Fir
internal_int_unpack :: proc(a: ^Int, buf: []$T, nails := 0, order := Order.LSB_First, allocator := context.allocator) -> (err: Error)
where intrinsics.type_is_integer(T) && intrinsics.type_is_unsigned(T) && size_of(T) <= 16 {
where intrinsics.type_is_integer(T), intrinsics.type_is_unsigned(T), size_of(T) <= 16 {
assert(nails >= 0 && nails < (size_of(T) * 8))
context.allocator = allocator
+2 -2
View File
@@ -1724,7 +1724,7 @@ quatFromMat4 :: proc "c" (m: mat4) -> (q: quat) {
@(require_results)
quatMulVec3 :: proc "c" (q: quat, v: vec3) -> vec3 {
xyz := vec3{q.x, q.y, q.z}
t := cross(xyz, v)
t := cross(2.0 * xyz, v)
return v + q.w*t + cross(xyz, t)
}
@@ -1832,7 +1832,7 @@ dquatFromDmat4 :: proc "c" (m: dmat4) -> (q: dquat) {
@(require_results)
dquatMulDvec3 :: proc "c" (q: dquat, v: dvec3) -> dvec3 {
xyz := dvec3{q.x, q.y, q.z}
t := cross(xyz, v)
t := cross(2.0 * xyz, v)
return v + q.w*t + cross(xyz, t)
}
+27
View File
@@ -60,6 +60,7 @@ sqrt :: proc{
@(require_results) sin_f32be :: proc "contextless" (θ: f32be) -> f32be { return #force_inline f32be(sin_f32(f32(θ))) }
@(require_results) sin_f64le :: proc "contextless" (θ: f64le) -> f64le { return #force_inline f64le(sin_f64(f64(θ))) }
@(require_results) sin_f64be :: proc "contextless" (θ: f64be) -> f64be { return #force_inline f64be(sin_f64(f64(θ))) }
// Return the sine of θ in radians.
sin :: proc{
sin_f16, sin_f16le, sin_f16be,
sin_f32, sin_f32le, sin_f32be,
@@ -72,6 +73,7 @@ sin :: proc{
@(require_results) cos_f32be :: proc "contextless" (θ: f32be) -> f32be { return #force_inline f32be(cos_f32(f32(θ))) }
@(require_results) cos_f64le :: proc "contextless" (θ: f64le) -> f64le { return #force_inline f64le(cos_f64(f64(θ))) }
@(require_results) cos_f64be :: proc "contextless" (θ: f64be) -> f64be { return #force_inline f64be(cos_f64(f64(θ))) }
// Return the cosine of θ in radians.
cos :: proc{
cos_f16, cos_f16le, cos_f16be,
cos_f32, cos_f32le, cos_f32be,
@@ -378,6 +380,7 @@ log10 :: proc{
@(require_results) tan_f64 :: proc "contextless" (θ: f64) -> f64 { return sin(θ)/cos(θ) }
@(require_results) tan_f64le :: proc "contextless" (θ: f64le) -> f64le { return f64le(tan_f64(f64(θ))) }
@(require_results) tan_f64be :: proc "contextless" (θ: f64be) -> f64be { return f64be(tan_f64(f64(θ))) }
// Return the tangent of θ in radians.
tan :: proc{
tan_f16, tan_f16le, tan_f16be,
tan_f32, tan_f32le, tan_f32be,
@@ -1752,7 +1755,28 @@ atan2_f64be :: proc "contextless" (y, x: f64be) -> f64be {
// TODO(bill): Better atan2_f32
return f64be(atan2_f64(f64(y), f64(x)))
}
/*
Return the arc tangent of y/x in radians. Defined on the domain [-, ] for x and y with a range of [-π, π]
Special cases:
atan2(y, NaN) = NaN
atan2(NaN, x) = NaN
atan2(+0, x>=0) = + 0
atan2(-0, x>=0) = - 0
atan2(+0, x<=-0) = + π
atan2(-0, x<=-0) = - π
atan2(y>0, 0) = + π/2
atan2(y<0, 0) = - π/2
atan2(+, +) = + π/4
atan2(-, +) = - π/4
atan2(+, -) = /4
atan2(-, -) = - /4
atan2(y, +) = 0
atan2(y>0, -) = + π
atan2(y<0, -) = - π
atan2(+, x) = + π/2
atan2(-, x) = - π/2
*/
atan2 :: proc{
atan2_f64, atan2_f32, atan2_f16,
atan2_f64le, atan2_f64be,
@@ -1760,6 +1784,7 @@ atan2 :: proc{
atan2_f16le, atan2_f16be,
}
// Return the arc tangent of x, in radians. Defined on the domain of [-, ] with a range of [-π/2, π/2]
@(require_results)
atan :: proc "contextless" (x: $T) -> T where intrinsics.type_is_float(T) {
return atan2(x, 1)
@@ -1871,6 +1896,7 @@ asin_f16le :: proc "contextless" (x: f16le) -> f16le {
asin_f16be :: proc "contextless" (x: f16be) -> f16be {
return f16be(asin_f64(f64(x)))
}
// Return the arc sine of x, in radians. Defined on the domain of [-1, 1] with a range of [-π/2, π/2]
asin :: proc{
asin_f64, asin_f32, asin_f16,
asin_f64le, asin_f64be,
@@ -1985,6 +2011,7 @@ acos_f16le :: proc "contextless" (x: f16le) -> f16le {
acos_f16be :: proc "contextless" (x: f16be) -> f16be {
return f16be(acos_f64(f64(x)))
}
// Return the arc cosine of x, in radians. Defined on the domain of [-1, 1] with a range of [0, π].
acos :: proc{
acos_f64, acos_f32, acos_f16,
acos_f64le, acos_f64be,
+23 -20
View File
@@ -5,6 +5,7 @@ Package core:math/rand implements various random number generators
package rand
import "base:intrinsics"
import "core:crypto"
import "core:math"
import "core:mem"
@@ -104,27 +105,30 @@ init :: proc(r: ^Rand, seed: u64) {
}
/*
Initialises a random number generator to use the system random number generator.
The system random number generator is platform specific.
On `linux` refer to the `getrandom` syscall.
On `darwin` refer to `getentropy`.
On `windows` refer to `BCryptGenRandom`.
All other platforms are not supported
Initialises a random number generator to use the system random number generator.
The system random number generator is platform specific, and not supported
on all targets.
Inputs:
- r: The random number generator to use the system random number generator
WARNING: Panics if the system is not either `windows`, `darwin` or `linux`
WARNING: Panics if the system random number generator is not supported.
Support can be determined via the `core:crypto.HAS_RAND_BYTES` constant.
Example:
import "core:crypto"
import "core:math/rand"
import "core:fmt"
init_as_system_example :: proc() {
my_rand: rand.Rand
rand.init_as_system(&my_rand)
fmt.println(rand.uint64(&my_rand))
switch crypto.HAS_RAND_BYTES {
case true:
rand.init_as_system(&my_rand)
fmt.println(rand.uint64(&my_rand))
case false:
fmt.println("system random not supported!")
}
}
Possible Output:
@@ -133,7 +137,7 @@ Possible Output:
*/
init_as_system :: proc(r: ^Rand) {
if !#defined(_system_random) {
if !crypto.HAS_RAND_BYTES {
panic(#procedure + " is not supported on this platform yet")
}
r.state = 0
@@ -144,15 +148,14 @@ init_as_system :: proc(r: ^Rand) {
@(private)
_random_u64 :: proc(r: ^Rand) -> u64 {
r := r
if r == nil {
switch {
case r == nil:
r = &global_rand
case r.is_system:
value: u64
crypto.rand_bytes((cast([^]u8)&value)[:size_of(u64)])
return value
}
when #defined(_system_random) {
if r.is_system {
return _system_random()
}
}
old_state := r.state
r.state = old_state * 6364136223846793005 + (r.inc|1)
@@ -789,8 +792,8 @@ shuffle :: proc(array: $T/[]$E, r: ^Rand = nil) {
return
}
for i := i64(0); i < n; i += 1 {
j := int63_max(n, r)
for i := i64(n - 1); i > 0; i -= 1 {
j := int63_max(i + 1, r)
array[i], array[j] = array[j], array[i]
}
}
-22
View File
@@ -1,22 +0,0 @@
package rand
import "core:sys/darwin"
@(require_results)
_system_random :: proc() -> u64 {
for {
value: u64
ret := darwin.syscall_getentropy(([^]u8)(&value), size_of(value))
if ret < 0 {
switch ret {
case -4: // EINTR
continue
case -78: // ENOSYS
panic("getentropy not available in kernel")
case:
panic("getentropy failed")
}
}
return value
}
}
-14
View File
@@ -1,14 +0,0 @@
package rand
foreign import "odin_env"
foreign odin_env {
@(link_name = "rand_bytes")
env_rand_bytes :: proc "contextless" (buf: []byte) ---
}
@(require_results)
_system_random :: proc() -> u64 {
buf: [8]u8
env_rand_bytes(buf[:])
return transmute(u64)buf
}
-29
View File
@@ -1,29 +0,0 @@
package rand
import "core:sys/linux"
@(require_results)
_system_random :: proc() -> u64 {
for {
value: u64
value_buf := (cast([^]u8)&value)[:size_of(u64)]
_, errno := linux.getrandom(value_buf, {})
#partial switch errno {
case .NONE:
// Do nothing
case .EINTR:
// Call interupted by a signal handler, just retry the request.
continue
case .ENOSYS:
// The kernel is apparently prehistoric (< 3.17 circa 2014)
// and does not support getrandom.
panic("getrandom not available in kernel")
case:
// All other failures are things that should NEVER happen
// unless the kernel interface changes (ie: the Linux
// developers break userland).
panic("getrandom failed")
}
return value
}
}
-13
View File
@@ -1,13 +0,0 @@
package rand
import win32 "core:sys/windows"
@(require_results)
_system_random :: proc() -> u64 {
value: u64
status := win32.BCryptGenRandom(nil, ([^]u8)(&value), size_of(value), win32.BCRYPT_USE_SYSTEM_PREFERRED_RNG)
if status < 0 {
panic("BCryptGenRandom failed")
}
return value
}
+1 -1
View File
@@ -1124,7 +1124,7 @@ buddy_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode,
case .Query_Info:
info := (^Allocator_Query_Info)(old_memory)
if info != nil && info.pointer != nil {
ptr := old_memory
ptr := info.pointer
if !(b.head <= ptr && ptr <= b.tail) {
return nil, .Invalid_Pointer
}
+32
View File
@@ -22,6 +22,13 @@ Tracking_Allocator :: struct {
bad_free_array: [dynamic]Tracking_Allocator_Bad_Free_Entry,
mutex: sync.Mutex,
clear_on_free_all: bool,
total_memory_allocated: i64,
total_allocation_count: i64,
total_memory_freed: i64,
total_free_count: i64,
peak_memory_allocated: i64,
current_memory_allocated: i64,
}
tracking_allocator_init :: proc(t: ^Tracking_Allocator, backing_allocator: Allocator, internals_allocator := context.allocator) {
@@ -44,6 +51,7 @@ tracking_allocator_clear :: proc(t: ^Tracking_Allocator) {
sync.mutex_lock(&t.mutex)
clear(&t.allocation_map)
clear(&t.bad_free_array)
t.current_memory_allocated = 0
sync.mutex_unlock(&t.mutex)
}
@@ -59,6 +67,21 @@ tracking_allocator :: proc(data: ^Tracking_Allocator) -> Allocator {
tracking_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode,
size, alignment: int,
old_memory: rawptr, old_size: int, loc := #caller_location) -> (result: []byte, err: Allocator_Error) {
track_alloc :: proc(data: ^Tracking_Allocator, entry: ^Tracking_Allocator_Entry) {
data.total_memory_allocated += i64(entry.size)
data.total_allocation_count += 1
data.current_memory_allocated += i64(entry.size)
if data.current_memory_allocated > data.peak_memory_allocated {
data.peak_memory_allocated = data.current_memory_allocated
}
}
track_free :: proc(data: ^Tracking_Allocator, entry: ^Tracking_Allocator_Entry) {
data.total_memory_freed += i64(entry.size)
data.total_free_count += 1
data.current_memory_allocated -= i64(entry.size)
}
data := (^Tracking_Allocator)(allocator_data)
sync.mutex_guard(&data.mutex)
@@ -100,13 +123,21 @@ tracking_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode,
err = err,
location = loc,
}
track_alloc(data, &data.allocation_map[result_ptr])
case .Free:
if old_memory != nil && old_memory in data.allocation_map {
track_free(data, &data.allocation_map[old_memory])
}
delete_key(&data.allocation_map, old_memory)
case .Free_All:
if data.clear_on_free_all {
clear_map(&data.allocation_map)
data.current_memory_allocated = 0
}
case .Resize, .Resize_Non_Zeroed:
if old_memory != nil && old_memory in data.allocation_map {
track_free(data, &data.allocation_map[old_memory])
}
if old_memory != result_ptr {
delete_key(&data.allocation_map, old_memory)
}
@@ -118,6 +149,7 @@ tracking_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode,
err = err,
location = loc,
}
track_alloc(data, &data.allocation_map[result_ptr])
case .Query_Features:
set := (^Allocator_Mode_Set)(old_memory)
+6 -2
View File
@@ -258,8 +258,12 @@ _send_tcp :: proc(tcp_sock: TCP_Socket, buf: []byte) -> (int, Network_Error) {
for total_written < len(buf) {
limit := min(int(max(i32)), len(buf) - total_written)
remaining := buf[total_written:][:limit]
res, errno := linux.send(linux.Fd(tcp_sock), remaining, {})
if errno != .NONE {
res, errno := linux.send(linux.Fd(tcp_sock), remaining, {.NOSIGNAL})
if errno == .EPIPE {
// If the peer is disconnected when we are trying to send we will get an `EPIPE` error,
// so we turn that into a clearer error
return total_written, TCP_Send_Error.Connection_Closed
} else if errno != .NONE {
return total_written, TCP_Send_Error(errno)
}
total_written += int(res)
+15 -2
View File
@@ -21,7 +21,7 @@ import "core:strconv"
import "core:unicode/utf8"
import "core:encoding/hex"
split_url :: proc(url: string, allocator := context.allocator) -> (scheme, host, path: string, queries: map[string]string) {
split_url :: proc(url: string, allocator := context.allocator) -> (scheme, host, path: string, queries: map[string]string, fragment: string) {
s := url
i := strings.index(s, "://")
@@ -30,6 +30,12 @@ split_url :: proc(url: string, allocator := context.allocator) -> (scheme, host,
s = s[i+3:]
}
i = strings.index(s, "#")
if i != -1 {
fragment = s[i+1:]
s = s[:i]
}
i = strings.index(s, "?")
if i != -1 {
query_str := s[i+1:]
@@ -62,7 +68,7 @@ split_url :: proc(url: string, allocator := context.allocator) -> (scheme, host,
return
}
join_url :: proc(scheme, host, path: string, queries: map[string]string, allocator := context.allocator) -> string {
join_url :: proc(scheme, host, path: string, queries: map[string]string, fragment: string, allocator := context.allocator) -> string {
b := strings.builder_make(allocator)
strings.builder_grow(&b, len(scheme) + 3 + len(host) + 1 + len(path))
@@ -95,6 +101,13 @@ join_url :: proc(scheme, host, path: string, queries: map[string]string, allocat
i += 1
}
if fragment != "" {
if fragment[0] != '#' {
strings.write_string(&b, "#")
}
strings.write_string(&b, strings.trim_space(fragment))
}
return strings.to_string(b)
}
+11 -1
View File
@@ -6,6 +6,7 @@ import "core:path/filepath"
import "core:fmt"
import "core:os"
import "core:slice"
import "core:strings"
collect_package :: proc(path: string) -> (pkg: ^ast.Package, success: bool) {
NO_POS :: tokenizer.Pos{}
@@ -32,11 +33,18 @@ collect_package :: proc(path: string) -> (pkg: ^ast.Package, success: bool) {
if !ok {
return
}
src, ok = os.read_entire_file(fullpath)
if !ok {
delete(fullpath)
return
}
if strings.trim_space(string(src)) == "" {
delete(fullpath)
delete(src)
continue
}
file := ast.new(ast.File, NO_POS, NO_POS)
file.pkg = pkg
file.src = string(src)
@@ -69,7 +77,9 @@ parse_package :: proc(pkg: ^ast.Package, p: ^Parser = nil) -> bool {
if !parse_file(p, file) {
ok = false
}
if pkg.name == "" {
if file.pkg_decl == nil {
error(p, p.curr_tok.pos, "Expected a package declaration at the start of the file")
} else if pkg.name == "" {
pkg.name = file.pkg_decl.name
} else if pkg.name != file.pkg_decl.name {
error(p, file.pkg_decl.pos, "different package name, expected '%s', got '%s'", pkg.name, file.pkg_decl.name)
+2
View File
@@ -2117,7 +2117,9 @@ parse_proc_type :: proc(p: ^Parser, tok: tokenizer.Token) -> ^ast.Proc_Type {
}
expect_token(p, .Open_Paren)
p.expr_level += 1
params, _ := parse_field_list(p, .Close_Paren, ast.Field_Flags_Signature_Params)
p.expr_level -= 1
expect_closing_parentheses_of_field_list(p)
results, diverging := parse_results(p)
+1 -1
View File
@@ -159,7 +159,7 @@ blkcnt_t :: i64
blksize_t :: i32
fflags_t :: u32
when ODIN_ARCH == .amd64 /* LP64 */ {
when ODIN_ARCH == .amd64 || ODIN_ARCH == .arm64 /* LP64 */ {
time_t :: i64
} else {
time_t :: i32
+2 -9
View File
@@ -1,9 +1,7 @@
//+build js
package os
import "base:intrinsics"
import "base:runtime"
import "core:unicode/utf16"
is_path_separator :: proc(c: byte) -> bool {
return c == '/' || c == '\\'
@@ -64,13 +62,8 @@ 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))
stdout: Handle = 1
stderr: Handle = 2
get_std_handle :: proc "contextless" (h: uint) -> Handle {
context = runtime.default_context()
+2 -2
View File
@@ -3,8 +3,8 @@ package os
import "core:time"
File_Info :: struct {
fullpath: string,
name: string,
fullpath: string, // allocated
name: string, // uses `fullpath` as underlying data
size: i64,
mode: File_Mode,
is_dir: bool,
+21
View File
@@ -934,6 +934,27 @@ set_union_value :: proc(dst: any, value: any) -> bool {
panic("expected a union to reflect.set_union_variant_typeid")
}
@(require_results)
bit_set_is_big_endian :: proc(value: any, loc := #caller_location) -> bool {
if value == nil { return ODIN_ENDIAN == .Big }
ti := runtime.type_info_base(type_info_of(value.id))
if info, ok := ti.variant.(runtime.Type_Info_Bit_Set); ok {
if info.underlying == nil { return ODIN_ENDIAN == .Big }
underlying_ti := runtime.type_info_base(info.underlying)
if underlying_info, uok := underlying_ti.variant.(runtime.Type_Info_Integer); uok {
switch underlying_info.endianness {
case .Platform: return ODIN_ENDIAN == .Big
case .Little: return false
case .Big: return true
}
}
return ODIN_ENDIAN == .Big
}
panic("expected a bit_set to reflect.bit_set_is_big_endian", loc)
}
@(require_results)
+61
View File
@@ -408,7 +408,68 @@ is_relative_multi_pointer :: proc(info: ^Type_Info) -> bool {
}
@(require_results)
is_endian_platform :: proc(info: ^Type_Info) -> bool {
if info == nil { return false}
info := info
info = type_info_core(info)
#partial switch v in info.variant {
case Type_Info_Integer:
return v.endianness == .Platform
case Type_Info_Bit_Set:
if v.underlying != nil {
return is_endian_platform(v.underlying)
}
return true
case Type_Info_Pointer:
return true
}
return false
}
@(require_results)
is_endian_little :: proc(info: ^Type_Info) -> bool {
if info == nil { return false}
info := info
info = type_info_core(info)
#partial switch v in info.variant {
case Type_Info_Integer:
if v.endianness == .Platform {
return ODIN_ENDIAN == .Little
}
return v.endianness == .Little
case Type_Info_Bit_Set:
if v.underlying != nil {
return is_endian_platform(v.underlying)
}
return ODIN_ENDIAN == .Little
case Type_Info_Pointer:
return ODIN_ENDIAN == .Little
}
return ODIN_ENDIAN == .Little
}
@(require_results)
is_endian_big :: proc(info: ^Type_Info) -> bool {
if info == nil { return false}
info := info
info = type_info_core(info)
#partial switch v in info.variant {
case Type_Info_Integer:
if v.endianness == .Platform {
return ODIN_ENDIAN == .Big
}
return v.endianness == .Big
case Type_Info_Bit_Set:
if v.underlying != nil {
return is_endian_platform(v.underlying)
}
return ODIN_ENDIAN == .Big
case Type_Info_Pointer:
return ODIN_ENDIAN == .Big
}
return ODIN_ENDIAN == .Big
}
+2 -2
View File
@@ -36,7 +36,7 @@ _mm_lddqu_si128 :: #force_inline proc "c" (mem_addr: ^__m128i) -> __m128i {
_mm_movedup_pd :: #force_inline proc "c" (a: __m128d) -> __m128d {
return simd.shuffle(a, a, 0, 0)
}
@(require_results, enable_target_feature="sse3")
@(require_results, enable_target_feature="sse2,sse3")
_mm_loaddup_pd :: #force_inline proc "c" (mem_addr: [^]f64) -> __m128d {
return _mm_load1_pd(mem_addr)
}
@@ -65,4 +65,4 @@ foreign _ {
hsubps :: proc(a, b: __m128) -> __m128 ---
@(link_name = "llvm.x86.sse3.ldu.dq")
lddqu :: proc(mem_addr: rawptr) -> i8x16 ---
}
}
+2 -2
View File
@@ -268,7 +268,7 @@ _mm_testnzc_si128 :: #force_inline proc "c" (a: __m128i, mask: __m128i) -> i32 {
_mm_test_all_zeros :: #force_inline proc "c" (a: __m128i, mask: __m128i) -> i32 {
return _mm_testz_si128(a, mask)
}
@(require_results, enable_target_feature="sse4.1")
@(require_results, enable_target_feature="sse2,sse4.1")
_mm_test_all_ones :: #force_inline proc "c" (a: __m128i) -> i32 {
return _mm_testc_si128(a, _mm_cmpeq_epi32(a, a))
}
@@ -349,4 +349,4 @@ foreign _ {
ptestc :: proc(a, mask: i64x2) -> i32 ---
@(link_name = "llvm.x86.sse41.ptestnzc")
ptestnzc :: proc(a, mask: i64x2) -> i32 ---
}
}
+36
View File
@@ -701,3 +701,39 @@ enumerated_array :: proc(ptr: ^$T) -> []intrinsics.type_elem_type(T)
where intrinsics.type_is_enumerated_array(T) {
return ([^]intrinsics.type_elem_type(T))(ptr)[:len(T)]
}
// Turn a `[]E` into `bit_set[E]`
// e.g.:
// bs := slice.enum_slice_to_bitset(my_flag_slice, rl.ConfigFlags)
@(require_results)
enum_slice_to_bitset :: proc(enums: []$E, $T: typeid/bit_set[E]) -> (bits: T) where intrinsics.type_is_enum(E), intrinsics.type_bit_set_elem_type(T) == E {
for v in enums {
bits |= {v}
}
return
}
// Turn a `bit_set[E]` into a `[]E`
// e.g.:
// sl := slice.bitset_to_enum_slice(flag_buf[:], bs)
@(require_results)
bitset_to_enum_slice_with_buffer :: proc(buf: []$E, bs: $T) -> (slice: []E) where intrinsics.type_is_enum(E), intrinsics.type_bit_set_elem_type(T) == E {
count := 0
for v in bs {
buf[count] = v
count += 1
}
return buf[:count]
}
// Turn a `bit_set[E]` into a `[]E`, allocates
// e.g.:
// sl := slice.bitset_to_enum_slice(bs)
@(require_results)
bitset_to_enum_slice_with_make :: proc(bs: $T, $E: typeid, allocator := context.allocator) -> (slice: []E) where intrinsics.type_is_enum(E), intrinsics.type_bit_set_elem_type(T) == E {
ones := intrinsics.count_ones(transmute(E)bs)
buf := make([]E, int(ones), allocator)
return bitset_to_enum_slice(buf, bs)
}
bitset_to_enum_slice :: proc{bitset_to_enum_slice_with_make, bitset_to_enum_slice_with_buffer}
+1 -2
View File
@@ -104,8 +104,7 @@ generic_ftoa :: proc(buf: []byte, val: f64, fmt: byte, precision, bit_size: int)
} else {
switch fmt {
case 'e', 'E':
prec += 1
decimal.round(d, prec)
decimal.round(d, prec + 1)
case 'f', 'F':
decimal.round(d, d.decimal_point+prec)
case 'g', 'G':
+2
View File
@@ -75,6 +75,7 @@ create_raw_unbuffered :: proc(#any_int msg_size, msg_alignment: int, allocator:
ptr := mem.alloc(size, align, allocator) or_return
c = (^Raw_Chan)(ptr)
c.allocator = allocator
c.allocation_size = size
c.unbuffered_data = ([^]byte)(ptr)[offset:]
c.msg_size = u16(msg_size)
@@ -99,6 +100,7 @@ create_raw_buffered :: proc(#any_int msg_size, msg_alignment: int, #any_int cap:
ptr := mem.alloc(size, align, allocator) or_return
c = (^Raw_Chan)(ptr)
c.allocator = allocator
c.allocation_size = size
bptr := ([^]byte)(ptr)
+1 -1
View File
@@ -433,7 +433,7 @@ One_Shot_Event :: struct #no_copy {
// Blocks the current thread until the event is made available with `one_shot_event_signal`.
one_shot_event_wait :: proc "contextless" (e: ^One_Shot_Event) {
for atomic_load_explicit(&e.state, .Acquire) == 0 {
futex_wait(&e.state, 1)
futex_wait(&e.state, 0)
}
}
+31 -13
View File
@@ -5,31 +5,49 @@ package sync
import "base:intrinsics"
import "core:time"
// NOTE: because `core:sync` is in the dependency chain of a lot of the core packages (mostly through `core:mem`)
// without actually calling into it much, I opted for a runtime panic instead of a compile error here.
_futex_wait :: proc "contextless" (f: ^Futex, expected: u32) -> bool {
s := intrinsics.wasm_memory_atomic_wait32((^u32)(f), expected, -1)
return s != 0
when !intrinsics.has_target_feature("atomics") {
_panic("usage of `core:sync` requires the `-target-feature:\"atomics\"` or a `-microarch` that supports it")
} else {
s := intrinsics.wasm_memory_atomic_wait32((^u32)(f), expected, -1)
return s != 0
}
}
_futex_wait_with_timeout :: proc "contextless" (f: ^Futex, expected: u32, duration: time.Duration) -> bool {
s := intrinsics.wasm_memory_atomic_wait32((^u32)(f), expected, i64(duration))
return s != 0
when !intrinsics.has_target_feature("atomics") {
_panic("usage of `core:sync` requires the `-target-feature:\"atomics\"` or a `-microarch` that supports it")
} else {
s := intrinsics.wasm_memory_atomic_wait32((^u32)(f), expected, i64(duration))
return s != 0
}
}
_futex_signal :: proc "contextless" (f: ^Futex) {
loop: for {
s := intrinsics.wasm_memory_atomic_notify32((^u32)(f), 1)
if s >= 1 {
return
when !intrinsics.has_target_feature("atomics") {
_panic("usage of `core:sync` requires the `-target-feature:\"atomics\"` or a `-microarch` that supports it")
} else {
loop: for {
s := intrinsics.wasm_memory_atomic_notify32((^u32)(f), 1)
if s >= 1 {
return
}
}
}
}
_futex_broadcast :: proc "contextless" (f: ^Futex) {
loop: for {
s := intrinsics.wasm_memory_atomic_notify32((^u32)(f), ~u32(0))
if s >= 0 {
return
when !intrinsics.has_target_feature("atomics") {
_panic("usage of `core:sync` requires the `-target-feature:\"atomics\"` or a `-microarch` that supports it")
} else {
loop: for {
s := intrinsics.wasm_memory_atomic_notify32((^u32)(f), ~u32(0))
if s >= 0 {
return
}
}
}
}
@@ -0,0 +1,34 @@
package CoreFoundation
foreign import CoreFoundation "system:CoreFoundation.framework"
TypeID :: distinct uint
OptionFlags :: distinct uint
HashCode :: distinct uint
Index :: distinct int
TypeRef :: distinct rawptr
Range :: struct {
location: Index,
length: Index,
}
foreign CoreFoundation {
// Releases a Core Foundation object.
CFRelease :: proc(cf: TypeRef) ---
}
// Releases a Core Foundation object.
Release :: proc {
ReleaseObject,
ReleaseString,
}
ReleaseObject :: #force_inline proc(cf: TypeRef) {
CFRelease(cf)
}
// Releases a Core Foundation string.
ReleaseString :: #force_inline proc(theString: String) {
CFRelease(TypeRef(theString))
}
@@ -0,0 +1,203 @@
package CoreFoundation
import "base:runtime"
foreign import CoreFoundation "system:CoreFoundation.framework"
String :: distinct TypeRef // same as CFStringRef
StringEncoding :: distinct u32
StringBuiltInEncodings :: enum StringEncoding {
MacRoman = 0,
WindowsLatin1 = 0x0500,
ISOLatin1 = 0x0201,
NextStepLatin = 0x0B01,
ASCII = 0x0600,
Unicode = 0x0100,
UTF8 = 0x08000100,
NonLossyASCII = 0x0BFF,
UTF16 = 0x0100,
UTF16BE = 0x10000100,
UTF16LE = 0x14000100,
UTF32 = 0x0c000100,
UTF32BE = 0x18000100,
UTF32LE = 0x1c000100,
}
StringEncodings :: enum Index {
MacJapanese = 1,
MacChineseTrad = 2,
MacKorean = 3,
MacArabic = 4,
MacHebrew = 5,
MacGreek = 6,
MacCyrillic = 7,
MacDevanagari = 9,
MacGurmukhi = 10,
MacGujarati = 11,
MacOriya = 12,
MacBengali = 13,
MacTamil = 14,
MacTelugu = 15,
MacKannada = 16,
MacMalayalam = 17,
MacSinhalese = 18,
MacBurmese = 19,
MacKhmer = 20,
MacThai = 21,
MacLaotian = 22,
MacGeorgian = 23,
MacArmenian = 24,
MacChineseSimp = 25,
MacTibetan = 26,
MacMongolian = 27,
MacEthiopic = 28,
MacCentralEurRoman = 29,
MacVietnamese = 30,
MacExtArabic = 31,
MacSymbol = 33,
MacDingbats = 34,
MacTurkish = 35,
MacCroatian = 36,
MacIcelandic = 37,
MacRomanian = 38,
MacCeltic = 39,
MacGaelic = 40,
MacFarsi = 0x8C,
MacUkrainian = 0x98,
MacInuit = 0xEC,
MacVT100 = 0xFC,
MacHFS = 0xFF,
ISOLatin2 = 0x0202,
ISOLatin3 = 0x0203,
ISOLatin4 = 0x0204,
ISOLatinCyrillic = 0x0205,
ISOLatinArabic = 0x0206,
ISOLatinGreek = 0x0207,
ISOLatinHebrew = 0x0208,
ISOLatin5 = 0x0209,
ISOLatin6 = 0x020A,
ISOLatinThai = 0x020B,
ISOLatin7 = 0x020D,
ISOLatin8 = 0x020E,
ISOLatin9 = 0x020F,
ISOLatin10 = 0x0210,
DOSLatinUS = 0x0400,
DOSGreek = 0x0405,
DOSBalticRim = 0x0406,
DOSLatin1 = 0x0410,
DOSGreek1 = 0x0411,
DOSLatin2 = 0x0412,
DOSCyrillic = 0x0413,
DOSTurkish = 0x0414,
DOSPortuguese = 0x0415,
DOSIcelandic = 0x0416,
DOSHebrew = 0x0417,
DOSCanadianFrench = 0x0418,
DOSArabic = 0x0419,
DOSNordic = 0x041A,
DOSRussian = 0x041B,
DOSGreek2 = 0x041C,
DOSThai = 0x041D,
DOSJapanese = 0x0420,
DOSChineseSimplif = 0x0421,
DOSKorean = 0x0422,
DOSChineseTrad = 0x0423,
WindowsLatin2 = 0x0501,
WindowsCyrillic = 0x0502,
WindowsGreek = 0x0503,
WindowsLatin5 = 0x0504,
WindowsHebrew = 0x0505,
WindowsArabic = 0x0506,
WindowsBalticRim = 0x0507,
WindowsVietnamese = 0x0508,
WindowsKoreanJohab = 0x0510,
ANSEL = 0x0601,
JIS_X0201_76 = 0x0620,
JIS_X0208_83 = 0x0621,
JIS_X0208_90 = 0x0622,
JIS_X0212_90 = 0x0623,
JIS_C6226_78 = 0x0624,
ShiftJIS_X0213 = 0x0628,
ShiftJIS_X0213_MenKuTen = 0x0629,
GB_2312_80 = 0x0630,
GBK_95 = 0x0631,
GB_18030_2000 = 0x0632,
KSC_5601_87 = 0x0640,
KSC_5601_92_Johab = 0x0641,
CNS_11643_92_P1 = 0x0651,
CNS_11643_92_P2 = 0x0652,
CNS_11643_92_P3 = 0x0653,
ISO_2022_JP = 0x0820,
ISO_2022_JP_2 = 0x0821,
ISO_2022_JP_1 = 0x0822,
ISO_2022_JP_3 = 0x0823,
ISO_2022_CN = 0x0830,
ISO_2022_CN_EXT = 0x0831,
ISO_2022_KR = 0x0840,
EUC_JP = 0x0920,
EUC_CN = 0x0930,
EUC_TW = 0x0931,
EUC_KR = 0x0940,
ShiftJIS = 0x0A01,
KOI8_R = 0x0A02,
Big5 = 0x0A03,
MacRomanLatin1 = 0x0A04,
HZ_GB_2312 = 0x0A05,
Big5_HKSCS_1999 = 0x0A06,
VISCII = 0x0A07,
KOI8_U = 0x0A08,
Big5_E = 0x0A09,
NextStepJapanese = 0x0B02,
EBCDIC_US = 0x0C01,
EBCDIC_CP037 = 0x0C02,
UTF7 = 0x04000100,
UTF7_IMAP = 0x0A10,
ShiftJIS_X0213_00 = 0x0628, // Deprecated. Use `ShiftJIS_X0213` instead.
}
@(link_prefix = "CF", default_calling_convention = "c")
foreign CoreFoundation {
// Copies the character contents of a string to a local C string buffer after converting the characters to a given encoding.
StringGetCString :: proc(theString: String, buffer: [^]byte, bufferSize: Index, encoding: StringEncoding) -> b8 ---
// Returns the number (in terms of UTF-16 code pairs) of Unicode characters in a string.
StringGetLength :: proc(theString: String) -> Index ---
// Returns the maximum number of bytes a string of a specified length (in Unicode characters) will take up if encoded in a specified encoding.
StringGetMaximumSizeForEncoding :: proc(length: Index, encoding: StringEncoding) -> Index ---
// Fetches a range of the characters from a string into a byte buffer after converting the characters to a specified encoding.
StringGetBytes :: proc(thestring: String, range: Range, encoding: StringEncoding, lossByte: u8, isExternalRepresentation: b8, buffer: [^]byte, maxBufLen: Index, usedBufLen: ^Index) -> Index ---
StringIsEncodingAvailable :: proc(encoding: StringEncoding) -> bool ---
@(link_name = "__CFStringMakeConstantString")
StringMakeConstantString :: proc "c" (#const c: cstring) -> String ---
}
STR :: StringMakeConstantString
StringCopyToOdinString :: proc(
theString: String,
allocator := context.allocator,
) -> (
str: string,
ok: bool,
) #optional_ok {
length := StringGetLength(theString)
max := StringGetMaximumSizeForEncoding(length, StringEncoding(StringBuiltInEncodings.UTF8))
buf, err := make([]byte, max, allocator)
if err != nil do return
raw_str := runtime.Raw_String {
data = raw_data(buf),
}
StringGetBytes(theString, {0, length}, StringEncoding(StringBuiltInEncodings.UTF8), 0, false, raw_data(buf), max, (^Index)(&raw_str.len))
return transmute(string)raw_str, true
}
@@ -132,7 +132,7 @@ Application_nextEventMatchingMask :: proc "c" (self: ^Application, mask: EventMa
@(objc_type=Application, objc_name="sendEvent")
Application_sendEvent :: proc "c" (self: ^Application, event: ^Event) {
msgSend(Event, self, "sendEvent:", event)
msgSend(nil, self, "sendEvent:", event)
}
@(objc_type=Application, objc_name="updateWindows")
Application_updateWindows :: proc "c" (self: ^Application) {
+6 -9
View File
@@ -23,12 +23,9 @@ StringEncoding :: enum UInteger {
WindowsCP1250 = 15,
ISO2022JP = 21,
MacOSRoman = 30,
UTF16 = Unicode,
UTF16BigEndian = 0x90000100,
UTF16LittleEndian = 0x94000100,
UTF32 = 0x8c000100,
UTF32BigEndian = 0x98000100,
UTF32LittleEndian = 0x9c000100,
@@ -49,12 +46,9 @@ StringCompareOption :: enum UInteger {
unichar :: distinct u16
@(link_prefix="NS", default_calling_convention="c")
foreign Foundation {
StringFromClass :: proc(cls: Class) -> ^String ---
}
AT :: MakeConstantString
// CFString is 'toll-free bridged' with its Cocoa Foundation counterpart, NSString.
MakeConstantString :: proc "c" (#const c: cstring) -> ^String {
foreign Foundation {
__CFStringMakeConstantString :: proc "c" (c: cstring) -> ^String ---
@@ -62,6 +56,10 @@ MakeConstantString :: proc "c" (#const c: cstring) -> ^String {
return __CFStringMakeConstantString(c)
}
@(link_prefix="NS", default_calling_convention="c")
foreign Foundation {
StringFromClass :: proc(cls: Class) -> ^String ---
}
@(objc_type=String, objc_name="alloc", objc_is_class_method=true)
String_alloc :: proc "c" () -> ^String {
@@ -73,7 +71,6 @@ String_init :: proc "c" (self: ^String) -> ^String {
return msgSend(^String, self, "init")
}
@(objc_type=String, objc_name="initWithString")
String_initWithString :: proc "c" (self: ^String, other: ^String) -> ^String {
return msgSend(^String, self, "initWithString:", other)

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