Compare commits

..

448 Commits
v0.5 ... v0.5.1

Author SHA1 Message Date
Christoffer Lerno
1d61ace302 Do not link with debug libc on win32 when using cross compile libs. Add delete methods to dstring. Fixes to macOS aarch64 codegen. Use glibc backtrace when available. Add load_* methods to file. The cast (int[8])int_slice[:8] now works. 2023-12-08 18:59:57 +01:00
Christoffer Lerno
a50c5f4f7c Fixes to the grammar. 2023-11-26 20:06:38 +01:00
Christoffer Lerno
a46bf4fbe0 Improve "const" error message #1079. 2023-11-22 19:12:04 +01:00
Christoffer Lerno
0d1eab5c15 Fix of incorrect error recovery leading to confusing errors #1080 2023-11-22 19:12:04 +01:00
Christoffer Lerno
3255183ee4 0.5 release. 2023-11-20 23:48:18 +01:00
Tiago Teixeira
66b65a042e Add CMake option to link c3 dynamically to LLVM/LLD (#1077)
* Add Cmake option to link c3 dynamically to LLVM/LLD

To link dynamically, use
`cmake -DC3_LINK_DYNAMIC=ON <other-options> ../`

* formatting
2023-11-20 18:10:19 +01:00
Christoffer Lerno
b8db118e64 Use signals to create the stack trace. 2023-11-20 17:17:39 +01:00
Christoffer Lerno
7c15cf2788 Hacky update to stacktrace. 2023-11-20 14:10:12 +01:00
Christoffer Lerno
31538d5955 Fix to backtrace 2023-11-20 12:39:20 +01:00
Christoffer Lerno
337eac6d2f Only fallback on native backtrace if there is no backtrace() 2023-11-20 11:36:38 +01:00
Pierre-Nicolas Clauss
e826f02da5 feat(lib/std): get backtrace for static binaries. 2023-11-20 09:46:25 +01:00
Christoffer Lerno
d5281b10dd Cleanup use of macro inspection to use @typekind and @typeid macros. 2023-11-18 23:35:18 +01:00
Christoffer Lerno
87fdb5956e Improved backtrace on platforms without glibc. Added $$frameaddress and $$returnaddress properly. 2023-11-18 20:13:11 +01:00
Christoffer Lerno
00019f9d76 Small update 2023-11-17 22:10:31 +01:00
Christoffer Lerno
f257befd86 Add compatibility matrix. 2023-11-17 22:08:59 +01:00
Christoffer Lerno
07c27f3292 Remove emulated stack trace. 2023-11-17 07:27:17 +01:00
Christoffer Lerno
ffb0021d04 Use backtrace on windows. Updated backtrace API 2023-11-17 00:36:42 +01:00
Christoffer Lerno
81c93e3488 Use backtrace on windows. Updated backtrace API 2023-11-16 21:39:27 +01:00
Christoffer Lerno
587d5578ab Fix emit location. 2023-11-13 21:42:38 +01:00
Christoffer Lerno
9345e4270a Update panic emit. 2023-11-13 19:49:25 +01:00
Christoffer Lerno
1dde6092e5 Version update, remove unused code. 2023-11-13 18:25:06 +01:00
Christoffer Lerno
5e8816e6df Fixes to native backtrace. 2023-11-13 17:20:46 +01:00
Christoffer Lerno
dc0aa35522 Native Linux backtrace. 2023-11-12 15:20:24 +01:00
Christoffer Lerno
f39aa1a41e Add location tracking for memory allocations. 2023-11-09 22:03:25 +01:00
Christoffer Lerno
e31f2a03ba Add location tracking for memory allocations. 2023-11-09 11:20:29 +01:00
Christoffer Lerno
1e38ccdd2b Fix missing free on GrowableBitSet. init_new/init_temp for GrowableBitSet, LinkedList, List, HashMap, DString, ByteBuffer. Interface to_string renamed to_new_string. Change in allocator usage, malloc is now heap. Added new_array, new_zero_array, new, new_clear, clone. Concat => concat_new. string::printf => string::new_format, string::tprintf => string::tformat. "to_*" are now "to_new_*" and "to_temp_*". "from_*" is "new_from*" 2023-11-09 01:59:49 +01:00
Christoffer Lerno
69470b8738 Improved errors on optional return. Fixes to @nodiscard erroring. Addresses #1062 2023-11-03 23:50:15 +01:00
Christoffer Lerno
5dedaa8e67 Improve error message when creating an exe and the name is already used by a directory. 2023-11-02 21:24:44 +01:00
Christoffer Lerno
eab0b417de Fix to print. 2023-11-01 14:08:29 +01:00
Christoffer Lerno
5e4cfacfcb Rename "main" variable in ct_defined analysis. 2023-11-01 12:53:32 +01:00
Christoffer Lerno
120e21b80b Convencience function for random + entropy function. 2023-10-31 22:21:38 +01:00
Christoffer Lerno
cd7a03c2cf Interface based streams. Fix for initializing with a force unwrap inside. Allow $define to take a list. Allow $define to return error on argument type mismatch in call. Fixed broken bit operations on boolean vectors. 2023-10-31 01:06:59 +01:00
Christoffer Lerno
1aa038c92f Interface based streams. Fix for initializing with a force unwrap inside. Allow $define to take a list. Allow $define to return error on argument type mismatch in call. Fixed broken bit operations on boolean vectors. 2023-10-31 01:06:59 +01:00
Christoffer Lerno
e4c1328ef2 Better checks for missing @dynamic. Addresses #1055. 2023-10-28 04:12:43 +02:00
Christoffer Lerno
e17bb5f321 Void* should never deref and should allow methods to be attached to it. 2023-10-27 00:10:59 +02:00
Christoffer Lerno
a0bc03a9f5 Fix uses of @convertible. 2023-10-26 22:21:16 +02:00
Christoffer Lerno
70e7e4b1d2 Enable mingw 2023-10-26 18:54:12 +02:00
Pierre-Nicolas Clauss
7d16d9acaf fix(build_options): detect -z flag 2023-10-26 14:23:13 +02:00
thecalculus
a7d032df21 fix: argument parsing error 'sdk_path-version' instead of 'sdk-version' 2023-10-26 13:29:39 +02:00
Christoffer Lerno
9af37fe427 $and, $or, $is_const, $assignable, .is_eq, .is_ordered, $defined($vatype(2)) works looking if we can create a type, $defined(foo[0]) $defined(foo()). Remove $checks and @checked. Improvide casting checks to always work without destructive changes. 2023-10-24 22:06:04 +02:00
Christoffer Lerno
8a12dc5bd4 Fix issue where inferred vectors where incorrectly handled in unions and structs. 2023-10-21 22:10:00 +02:00
Christoffer Lerno
d01d8d3663 "protocol" => "interface" 2023-10-20 14:12:08 +02:00
Pierre-Nicolas Clauss
e380075852 fix: standard library search paths
Path construction for locating the standard library expects ending
slashes.
2023-10-19 13:04:43 +02:00
Pierre-Nicolas Clauss
7df5bc0017 Add more paths to search for the standard library
Module `std` is searched first in a `c3` subdirectory.
Search directories are, in order and relative to the compiler executable
location:
- `lib/c3` relative to the parent directory
- `lib` relative to the parent directory
- `/lib/c3`
- `/lib`
- `/c3`
- `/`
- `c3` relative to the parent directory
- the parent directory
- `lib/c3` relative to the grand-parent directory
- `lib` relative to the grand-parent directory
2023-10-17 13:55:51 +02:00
Christoffer Lerno
9b61ddb876 Add @pure to asm. Allow regular statements in naked function. 2023-10-15 19:11:11 +02:00
pini
76fa404b89 Feat/asm x86 (#1046)
* fix(asm): consider asm blocks as volatile
When asm blocks are not marked as volatile, they may be (wrongly)
discarded by LLVM optimization passes.
* fix(asm): mark syscall as clobbering return register
The `syscall` instruction returns the system call result in the `rax`
register.
* feat(asm): add push instructions.
* feat(asm): add pop instructions
2023-10-14 20:50:45 +02:00
Christoffer Lerno
682dfd0e47 Update default asm dialect on asm strings. Fix naked function analysis. 2023-10-14 13:56:53 +02:00
Christoffer Lerno
80a9842a25 Fix incorrect check for naked functions. 2023-10-14 12:46:31 +02:00
Christoffer Lerno
89d4c2cab7 Allocator uses protocols. Fix bug where it was not possible to pass a ref variable as a ref variable. Correct codegen for !anyptr. 2023-10-14 12:39:46 +02:00
Christoffer Lerno
54f32ed71b Fix alignment for remaining bitstruct binary ops. Turn off broken Mingw LLVM in CI. 2023-10-13 14:43:04 +02:00
Christoffer Lerno
fed343e3bb Fix alignment for negating bitstructs. Update mingw LLVM versions in CI. 2023-10-13 13:57:44 +02:00
Christoffer Lerno
fd21b057eb Missing target directive in test. 2023-10-13 13:37:27 +02:00
Christoffer Lerno
e81e91be93 Fix void* <=> protocol casts. Fix of tests. 2023-10-13 12:44:58 +02:00
Christoffer Lerno
9b714e1dbb Remove TB. 2023-10-12 22:00:49 +02:00
Christoffer Lerno
e67e17ef1e Fix MacOS SDK. 2023-10-12 21:59:40 +02:00
Christoffer Lerno
942d53a678 Fix tests. Refactoring MacOS SDK. 2023-10-12 14:10:46 +02:00
Christoffer Lerno
806d7e965f Update MacOS output to include platform version in target triple, fixing the linker warnings metioned here: #1028 2023-10-12 12:36:12 +02:00
Christoffer Lerno
db3e9c7ec7 Add delete testing for windows and update "clean" 2023-10-11 15:49:05 +02:00
Christoffer Lerno
b657724d9b Add delete testing in for windows. 2023-10-11 12:48:38 +02:00
Pierre Curto
5a5b600490 std::collections::list: add List.init_from_array
Signed-off-by: Pierre Curto <pierre.curto@gmail.com>
2023-10-11 12:46:28 +02:00
Christoffer Lerno
1472d60c8a Update version and releasenotes. 2023-10-10 23:32:04 +02:00
Christoffer Lerno
a9c28cce6d Default protocols. Closes #1039 2023-10-10 23:30:33 +02:00
Christoffer Lerno
b7a896805d Fix bug in growable bitset. Always insert 0xAA in malloc on testing. 2023-10-09 14:23:41 +02:00
Pierre Curto
6b571fe427 List capacity and SubProcess field name change (#1038)
* std::collections::list: adjust increased capacity
* std::os::process: rename conflicting field in SubProcess
* c3c: adjust spacing for --list-builtins and --list-keywords
2023-10-09 12:52:23 +02:00
Christoffer Lerno
3f77e868b1 Fixes #1040. 2023-10-09 00:55:54 +02:00
Christoffer Lerno
31bc766944 Fix issue where in error messages, integers were assumed to be unicode characters. 2023-10-09 00:41:31 +02:00
Christoffer Lerno
ebddbfb416 Restrict any -> Protocol conversion. Protocol <-> looks at parent. Detect duplicate method definitions for protocols. 2023-10-08 23:43:02 +02:00
Pierre Curto
3aa85cf641 misc (#1033)
* make conv::char32_to_utf8_unsafe() return the number of bytes it wrote
add tests for DString
fix pointer arithmetic in DString.insert_at

Signed-off-by: Pierre Curto <pierre.curto@gmail.com>

* add support to printf for %d and enums

Signed-off-by: Pierre Curto <pierre.curto@gmail.com>

---------

Signed-off-by: Pierre Curto <pierre.curto@gmail.com>
2023-10-08 02:12:20 +02:00
Christoffer Lerno
312a39ee24 Handle protocol inheritance. Allow overlapping protocol methods. Remove the need for &self in protocol declarations. Fix cast rules for protocol. Fix cast rules for bitstruct #1034. 2023-10-08 02:10:28 +02:00
Christoffer Lerno
99cfaa1583 Refactor protocols. 2023-10-06 22:31:41 +02:00
Christoffer Lerno
f3e3aa231d Make Random use protocols. 2023-10-06 22:31:41 +02:00
Christoffer Lerno
a1bce81ed0 Fix growable bitset (#1032) 2023-10-06 01:27:32 +02:00
Christoffer Lerno
dad21bfc6f See if we can get better errors. 2023-10-05 20:35:40 +02:00
Pierre Curto
9643a7c2b2 add DString.insert_at (#1026)
* add DString.insert
* make conv::utf32to8 more C3-like
2023-10-05 19:12:47 +02:00
Christoffer Lerno
d16ad0b4c7 Update "clean". 2023-10-05 19:06:55 +02:00
Christoffer Lerno
32f6d711ac Revert linker changes. 2023-10-05 18:48:05 +02:00
Christoffer Lerno
a07ba63917 Compiling does not leave exe when successful, and also works with generic modules. #1027. For now, silence errors due to the macos linker changes. #1028. Try update clean on Windows #456. 2023-10-05 16:31:11 +02:00
Christoffer Lerno
70f906c71a Dynamic protocols. Correctly widen unsigned array indices (see #1029) 2023-10-05 15:20:41 +02:00
Christoffer Lerno
49c4595457 Dynamic protocols. 2023-10-05 15:20:41 +02:00
Christoffer Lerno
4cc30c0d33 Replace static initializer with @init / @finalizer 2023-10-03 12:45:43 +02:00
Pierre Curto
757a5b58e8 std::core::dstring: fix DString.zstr() (#1024)
rename DString.zstr to DString.zstr_view
2023-10-03 00:32:56 +02:00
Christoffer Lerno
2b9276b495 Add 'exec' 2023-09-30 23:28:22 +02:00
Pierre Curto
b2c7b713f2 add descriptions to the --list-project-properties cli option (#1021)
* fix constant name typo
* add descriptions to the --list-project-properties cli option
* add missing file name for main.c3
2023-09-30 19:52:11 +02:00
Pierre Curto
99ec44ad78 c3c init: add missing file name
Signed-off-by: Pierre Curto <pierre.curto@gmail.com>
2023-09-29 14:07:59 +02:00
Christoffer Lerno
db4298431d Remove debug printout. 2023-09-29 14:04:13 +02:00
Christoffer Lerno
fc8b185b5b Add library init. 2023-09-28 11:06:51 +02:00
Christoffer Lerno
f3752d273c Add /run dir for projects. See #921 2023-09-28 10:01:20 +02:00
Christoffer Lerno
aa6101d8ea Correctly deduce the return type for macros with implicit return. See #1018. 2023-09-26 17:37:27 +02:00
Christoffer Lerno
ee42992c37 Make local constants behave as global: see #974 2023-09-26 00:18:59 +02:00
Christoffer Lerno
c5404c6573 get_env for Win32, @pierrec's get_config_dir and get_home_dir 2023-09-25 16:29:49 +02:00
Christoffer Lerno
2a683a6a05 Update mingw LLVM. 2023-09-25 00:00:42 +02:00
Christoffer Lerno
a1ecf2211f as_str() replaced by str_view() 2023-09-24 23:50:16 +02:00
Christoffer Lerno
3675254af4 Fixed test. 2023-09-24 22:01:20 +02:00
Christoffer Lerno
30d794653d Resolve type fully before checking casts, addressing #1013. Correctly show the error location when a method is missing its single argument #1012. 2023-09-24 20:17:41 +02:00
Christoffer Lerno
709fe1c2c0 Some general refactoring in the builder / project code. 2023-09-23 03:22:11 +02:00
Christoffer Lerno
ad776c76a7 Add benchfn and testfn allowing easy overwrite of test and benchmark runners. #990 2023-09-23 00:30:09 +02:00
Christoffer Lerno
dde73e029c Fix of issue #1008 2023-09-22 23:24:21 +02:00
Pierre Curto
e5b990691e std::lib::io: make printn_gen discardable
Signed-off-by: Pierre Curto <pierre.curto@gmail.com>
2023-09-22 15:16:44 +02:00
Pierre Curto
d6edd80f3b lib::std::encoding: add varint::{read,write}
Signed-off-by: Pierre Curto <pierre.curto@gmail.com>
2023-09-22 15:16:44 +02:00
Christoffer Lerno
e706a8acd0 Fix Linux constant in posix.c3. Address issue #1009. Sanitizes the module name in generated project. 2023-09-22 15:15:47 +02:00
Christoffer Lerno
8dad8f2b1c Use regular backtrace for Mac on signals as well. 2023-09-22 01:12:48 +02:00
Christoffer Lerno
c4228e08c5 MacOS uses regular stacktrace for errors. 2023-09-21 16:39:13 +02:00
Christoffer Lerno
c074e79069 Bitstruct members would get partly evaluated using checks, then incorrectly reset on error. Also, copying bitstruct members were broken. This addresses #1005. 2023-09-19 18:03:20 +02:00
Christoffer Lerno
6e0982327d Added example project file and updated project file defaults. 2023-09-19 10:06:22 +02:00
Dmitry Atamanov
a06cc76c9b Support additional keys in projects. 2023-09-19 09:56:49 +02:00
Christoffer Lerno
9eef34049d Remove vestiges of top down widening. 2023-09-19 09:45:56 +02:00
Dmitry Atamanov
e91cb85a66 Update checkout action to v4 2023-09-19 08:15:17 +02:00
OdnetninI (Eduardo José Gómez Hernández)
05c2737f46 Fix timeout at tcp::connect 2023-09-18 23:47:01 +02:00
Christoffer Lerno
cbdc746c9d Fix alignment in new fetch builtins. 2023-09-17 21:07:00 +02:00
OdnetninI (Eduardo José Gómez Hernández)
8d11794f83 Fix atomic_fetch_sub builtin + Updated atomic library (#997)
Fix atomic_fetch_sub builtin + Updated atomic library
2023-09-17 13:22:12 +02:00
Christoffer Lerno
8ed9be9c58 Update build options --nostdlib --nolibc --emit-stdlib --forcelinker … (#999)
Update build options --nostdlib --nolibc --emit-stdlib --forcelinker --strip-unused. Fix error with vectors in $foreach. Also error if a $foreach iterating over an empty list. Rename forcelinker -> system-linker
2023-09-17 13:19:01 +02:00
Christoffer Lerno
d49365b4a7 Change how -O works and create -optsize / -optlevel. Update --safe / --fast. 2023-09-17 00:40:32 +02:00
Christoffer Lerno
03345bef10 Stricter checking of compare_exchange builtin. 2023-09-16 22:25:03 +02:00
Christoffer Lerno
ff05128a87 Add atomic_fetch_exchange builtin. 2023-09-15 18:22:37 +02:00
Christoffer Lerno
f6e18ded5b Add atomic_fetch builtins. 2023-09-15 18:07:15 +02:00
Dmitry Atamanov
d129cd49a5 Add libc-free hello_world 2023-09-15 16:46:38 +02:00
Christoffer Lerno
9233305bd6 Feature flags possible to add in project.json. See #991 2023-09-15 14:28:23 +02:00
Dmitry Atamanov
d6e9985a26 Add log(x, base) function to std::math module. 2023-09-15 09:22:34 +02:00
Christoffer Lerno
6be61aa19c Fixed asm parsing issue. Use of pointer as argument. 2023-09-15 08:25:43 +02:00
Dmitry Atamanov
4a232b7935 Add builtin benchmarks to changelog. [skip_ci] 2023-09-14 20:58:49 +02:00
Christoffer Lerno
44fafdbd7c Fix issue with asm_target initialized multiple times as mentioned in #989 2023-09-14 10:38:02 +02:00
Christoffer Lerno
2eddda9061 Add gather/scatter for vectors. 2023-09-14 10:19:15 +02:00
Christoffer Lerno
b2ac4b4253 Allow use of pointers in vectors. 2023-09-13 13:45:33 +02:00
Christoffer Lerno
1d04b70efe Fixed issues inferring length with subarrays. Removed old, non-working example. Infer length in the case of subarray literals. 2023-09-12 22:24:20 +02:00
Pierre Curto
d61482dffc fix Object.free (#982)
* lib/std/collections: add HashMap.@each_entry()
* lib/std/json: fix Object.free() when object is a map
* lib/std/collections: fix allocator use in Object.{set,set_at,append}
* lib/std: add char.from_hex
* lib/std/collections: print arrays and objects compactly
* lib/std/io: fix Formatter.vprintf result
* lib/std/io/stream: rename module for ByteBuffer
* lib/std/io/stream: make Scanner a Stream reader
* lib/std/io: make std{in,err,out} return File* if no libc
2023-09-12 13:49:52 +02:00
Christoffer Lerno
37bb16cca1 Updated cast code. 2023-09-12 12:48:52 +02:00
Christoffer Lerno
ca1885fe09 Updated releasenotes. 2023-09-09 22:50:25 +02:00
Christoffer Lerno
d67e846712 Remove cast from void! to anyfault. Rename @catchof to @catch 2023-09-09 22:49:32 +02:00
Christoffer Lerno
dfe097931c Add masked_load / masked_store 2023-09-09 01:05:51 +02:00
Christoffer Lerno
4ef74a1205 Add $$select. "--fp-math" options. Fixed issue with accidentally silent error on failed vector conversions. 2023-09-08 09:20:27 +02:00
Pierre Curto
b894e5be69 lib/std/encoding: remove use of pushback_byte in json
Signed-off-by: Pierre Curto <pierre.curto@gmail.com>
2023-09-08 09:05:38 +02:00
Christoffer Lerno
dc7f8057a3 Set msvc compile-test to -O1 2023-09-07 09:42:05 +02:00
Christoffer Lerno
66b436f7f8 No optimizations by default. 2023-09-07 09:01:12 +02:00
Christoffer Lerno
224e38c6c7 Fixes to features. 2023-09-06 23:49:56 +02:00
Dmitry Atamanov
51a72ccd37 Add new x86 cpu features. 2023-09-06 23:38:24 +02:00
Dmitry Atamanov
40d5ce0937 Fixes $$set_rounding_mode builtin. 2023-09-06 23:11:33 +02:00
Pierre Curto
51f76c69c4 lib/std/collections: add Bitset
Signed-off-by: Pierre Curto <pierre.curto@gmail.com>
2023-09-06 23:09:37 +02:00
Christoffer Lerno
b87e27d8a3 Update tests and CI (#979)
Update CI. Explicit native mutex "initialized" bool.
2023-09-06 22:43:07 +02:00
Christoffer Lerno
50e99b571f Add frame pointer on "enable stacktrace". Set no-trapping-math. Update fmuladd. 2023-09-06 14:38:21 +02:00
Christoffer Lerno
e3412da033 Removed broken code. Update formatter for precision. Fix of panic. 2023-09-05 22:53:56 +02:00
Christoffer Lerno
69418ba44d Fix issue in is_autoimport ordering. 2023-09-05 19:45:42 +02:00
Christoffer Lerno
cfe5c649c5 Prevent ordering issues with "builtin" by resolving it early. 2023-09-05 17:29:54 +02:00
Pierre Curto
f8fa9a057e lib/std/io: support . string format speficier (#970)
Signed-off-by: Pierre Curto <pierre.curto@gmail.com>
2023-09-05 14:39:51 +02:00
Christoffer Lerno
5a2ef79fe6 Update "not working" examples. 2023-09-05 14:36:17 +02:00
Christoffer Lerno
74649ef672 Fix of from_float. 2023-09-05 13:52:14 +02:00
Christoffer Lerno
e5f9cc26a8 Updated lexer suffixes. 2023-09-05 13:48:39 +02:00
Christoffer Lerno
6744a41644 Updated grammar for definitions. 2023-09-05 13:40:19 +02:00
Christoffer Lerno
ffb7935e12 Updating time duration functions. 2023-09-05 10:57:50 +02:00
Christoffer Lerno
53598b8c40 Make ZString print natively with %s. 2023-09-04 22:30:35 +02:00
Christoffer Lerno
fe0ae4a9aa Error when splat is used with raw varargs. 2023-09-04 22:24:46 +02:00
Christoffer Lerno
d1bb9c55ee Add blocking connection with timeout, and initial poll functionality. 2023-09-03 19:03:00 +02:00
Christoffer Lerno
29cc9ad8b1 Order IoError declarations. 2023-09-03 10:32:26 +02:00
Christoffer Lerno
4c081f59ff Refactoring, adding printf / printfn to all streams. 2023-09-03 10:14:04 +02:00
Christoffer Lerno
9a6d83f526 Updated stream API. 2023-09-03 01:14:15 +02:00
Christoffer Lerno
a6cff5c2a5 Removal of old Network, added nonblocking set and async connect. 2023-09-02 17:39:51 +02:00
Pierre Curto
e56313a204 use slice assign and add List.*using_test functions (#967)
* std/collections: use slice assignment
* std/collections: add List.remove_using_test and List.retain_using_test
2023-09-01 12:16:15 +02:00
Christoffer Lerno
70b9e811bd Update of enummap. 2023-09-01 11:19:20 +02:00
Christoffer Lerno
46582af0ae Add contracts to memcpy. Fix bug when compile time local declarations are used as expressions. This caused a check to be invalid in the @pool code. 2023-09-01 11:13:43 +02:00
Christoffer Lerno
0387816cb9 Gracefully handle unlocalized errors. Fix collisions with tests and using $test variable. 2023-09-01 10:03:09 +02:00
Christoffer Lerno
b6756b5b35 Overlapping slice assign is now safe. 2023-09-01 09:40:07 +02:00
Christoffer Lerno
34e1f89ded Changed atomic calls to be macros. 2023-09-01 08:32:38 +02:00
Christoffer Lerno
0142e5fd33 Removed the need for lambda in remove_if, removed potential implementation specific behaviour. 2023-08-31 23:39:00 +02:00
Pierre Curto
cd950a0359 lib/std/collections: fix comments, List.remove_if() and List.retain_if() methods
Signed-off-by: Pierre Curto <pierre.curto@gmail.com>
2023-08-31 23:20:50 +02:00
Christoffer Lerno
770115abd1 Fix default library linking order. 2023-08-31 20:20:51 +02:00
Christoffer Lerno
faf7782b1e Fix problems with slice assign over distinct types. 2023-08-31 13:41:21 +02:00
Christoffer Lerno
82ba02a904 Allow indexing of constant strings. Fixed reverse indexing of constant initializers. 2023-08-30 16:36:29 +02:00
Christoffer Lerno
dd0dc1a936 Allow anyfault and any aliasing. Fix any comparison. 2023-08-30 13:56:16 +02:00
Christoffer Lerno
eac19814e1 Make typeid switches always use subtype matching. Update seeder mixing. 2023-08-29 22:48:26 +02:00
OdnetninI (Eduardo José Gómez Hernández)
7aca8a02cb Architecture generic Atomics (C11 + extras) (#958)
* Initial implementation for c11 atomics
* Fix cmpxchg usage
* Support for floating point atomics
* Added atomic min and max
* Updated copyright notice
* Removed Floats from and or xor. Added mul, div, bitshift
* Changed get_atomic_compatible_type to lower_to_atomic_compatible_type
* Require non-null pointers
* Fix spacing
* Added Atomic type
* Added macro to reduce code
* Small reorder and cleanup
* Added cmpxchg constrains
* Apply all the restrictions for atomic loads/stores and cmpxchg
2023-08-29 14:25:43 +02:00
Dmitry Atamanov
efb492eace Add simple benchmark runner. 2023-08-29 12:28:06 +02:00
Christoffer Lerno
79f964dce9 Fix of atomic checks. Renamed MONOTONIC -> RELAXED. 2023-08-29 12:27:40 +02:00
Christoffer Lerno
a23112fae6 Added parentof. 2023-08-29 11:51:09 +02:00
Pierre Curto
092296984a std/math/random: add missing SimpleRandom.init() method
Signed-off-by: Pierre Curto <pierre.curto@gmail.com>
2023-08-28 10:03:30 +02:00
Christoffer Lerno
565f511cc7 Updated error handling on test/benchmark attribute. 2023-08-28 08:17:57 +02:00
Dmitry Atamanov
b8c92c69b0 Implement builtin benchmarks. 2023-08-28 08:13:21 +02:00
Christoffer Lerno
6ebb3caa20 Fix issue where type wasn't canonical. Addresses #957 2023-08-27 19:04:34 +02:00
Dmitry Atamanov
cc2c737357 Fix calling llvm::writeArchive for LLVM > 17. 2023-08-27 18:11:24 +02:00
Christoffer Lerno
69553fd80e Bitstruct designated initializer sometimes failed. This addresses. #954 2023-08-27 18:10:22 +02:00
Christoffer Lerno
190e1b19f1 Fix of next_short 2023-08-26 16:17:30 +02:00
Pierre Curto
c09b6154f4 std/lib/collections: add Object.get_len(); add some tests for encoding/json
Signed-off-by: Pierre Curto <pierre.curto@gmail.com>
2023-08-26 13:24:23 +02:00
Pierre Curto
120d5a672c std/collections: Object.get* return an error if requested type if invalid
Signed-off-by: Pierre Curto <pierre.curto@gmail.com>
2023-08-26 13:24:23 +02:00
Pierre Curto
307212a19c std/encoding: use char.is_digit() where applicable
Signed-off-by: Pierre Curto <pierre.curto@gmail.com>
2023-08-26 13:24:23 +02:00
Pierre Curto
eedb2c3c52 std/collections: add RingBuffer.popc()
Signed-off-by: Pierre Curto <pierre.curto@gmail.com>
2023-08-26 13:24:23 +02:00
Christoffer Lerno
b1f52cf8a9 Updated random interface further. 2023-08-26 13:22:02 +02:00
Christoffer Lerno
bea9ac010c Updated random interface. 2023-08-26 12:58:57 +02:00
Christoffer Lerno
6ebd437a5f Fix bug when converting from typedef to distinct. Ordered struct fields. Update debug type when returning an optional. 2023-08-25 14:11:23 +02:00
Pierre Curto
c0b109fbc1 #934 followup
Signed-off-by: Pierre Curto <pierre.curto@gmail.com>
2023-08-24 18:10:18 +02:00
Dmitry Atamanov
b77f254ab1 Tune @expect, @likely, @unlikely and @prefetch macros. 2023-08-24 16:09:56 +02:00
Dmitry Atamanov
f5fea69ef9 Move get_var, set_var and clear_var to os::env module. 2023-08-24 13:31:48 +02:00
Pierre Curto
c63b3d4209 Small updates to std::lib (#949)
* lib/std/io: add Stream.supports_{read,Write}_byte and Stream.write_all
* std/io/: use char.digit()
2023-08-24 11:59:00 +02:00
Christoffer Lerno
056ffa5876 Fix bug dropping bounds checks for arrays. This addresses #943. 2023-08-23 14:21:11 +02:00
Christoffer Lerno
0120498ec8 Fix seeder, update with dynamic interface for random. Insert unreachable after panic in asserts. Macro ensure static check. 2023-08-23 13:52:27 +02:00
Christoffer Lerno
0b67f1a8e4 Fix windows .dyn_search. 2023-08-22 21:34:10 +02:00
Christoffer Lerno
16e71c14b9 New resolution of $define only check on the last element. Fix issue with pointer and array element types not considered live. 2023-08-22 18:16:26 +02:00
Christoffer Lerno
27445e6c1d Semi-fix of $embed on empty. Inline caches for dynamic dispatch. 2023-08-22 15:24:21 +02:00
Dmitry Atamanov
fcb4bc0781 Reimplement QuickSort (non-recursive modification). 2023-08-22 14:48:12 +02:00
Christoffer Lerno
6c60b0d2a6 Update errno listings. Update ai flags in std::net. Fix incorrect socket error results on Win32. Change behaviour Socket set_option. TcpSocket/TcpServerSocket/UdpSocket. Rename "TimeDuration" to "Duration". Allow @if on enum values. 2023-08-19 22:41:54 +02:00
Christoffer Lerno
ed70f39da8 Update the json API 2023-08-19 00:53:28 +02:00
Christoffer Lerno
d5aebb434c Eliding length for ":"-ranges is no longer allowed. 2023-08-18 22:25:51 +02:00
Christoffer Lerno
17f69d8da8 Update slice assign so that it looks at the arguments and does slice copying or splat assign as needed. 2023-08-18 18:46:38 +02:00
Christoffer Lerno
c07dc700df Fix of #936. Also some general cleanup. 2023-08-18 15:55:43 +02:00
Christoffer Lerno
957ce320ae Cleanup and size reduction of Ast/Expr. 2023-08-18 01:57:56 +02:00
Pierre Curto
7a39933c97 add NanoDuration.to_format() (#935)
* lib/std/time: avoid switch in DateTime.compare_to()
* lib/std/time: add NanoDuration.to_format()
* std/lib: fix #934
2023-08-17 10:30:20 +02:00
Christoffer Lerno
9b0da89a03 Added compare_to as a standard macro. 2023-08-17 10:13:00 +02:00
Christoffer Lerno
b05ba8d110 Updated the test runner code slightly. 2023-08-16 17:31:16 +02:00
Christoffer Lerno
0448038c68 Added test. 2023-08-16 15:58:33 +02:00
Christoffer Lerno
e694d60f23 Updates and fixes to Mutex (#933)
Updating Mutex to have specific types: TimedMutex, RecursiveMutex, TimedRecursiveMutex. Fixes to the win32 implementation.
2023-08-16 15:45:49 +02:00
Christoffer Lerno
8a4337e819 Some improvements for the stacktrace. 2023-08-16 13:54:21 +02:00
Pierre Curto
5bd21c10b6 improve tests (#932)
* test: fix warnings generated by Python's interpreter

Signed-off-by: Pierre Curto <pierre.curto@gmail.com>

* lib/std/core/runtime: sort tests to run; improve tests output

Signed-off-by: Pierre Curto <pierre.curto@gmail.com>

---------

Signed-off-by: Pierre Curto <pierre.curto@gmail.com>
2023-08-16 12:28:07 +02:00
Christoffer Lerno
87c9c29ee8 Make -O1 the default, not -O2. 2023-08-16 10:51:43 +02:00
Pierre Curto
6c3d6a4b05 std/lib: fix module path for RingBuffer
Signed-off-by: Pierre Curto <pierre.curto@gmail.com>
2023-08-15 15:58:28 +02:00
Christoffer Lerno
f39dd82adc Fix issue where imports could be made more than once. Addresses #929 2023-08-15 10:47:40 +02:00
Pierre Curto
f4ad9fcee0 move num_cpu() to std::os (#928)
* lib/std: move num_cpu() to std::os

Signed-off-by: Pierre Curto <pierre.curto@gmail.com>

* add ThreadPool (#926)

* lib/std/collections: fix tab indentation

Signed-off-by: Pierre Curto <pierre.curto@gmail.com>

* lib/std/threads: add ThreadPool

Signed-off-by: Pierre Curto <pierre.curto@gmail.com>

* ats/lib/threads: add num_cpu()

Signed-off-by: Pierre Curto <pierre.curto@gmail.com>

---------

Signed-off-by: Pierre Curto <pierre.curto@gmail.com>

* lib/std/os: move macos constants to std::os::macos

Signed-off-by: Pierre Curto <pierre.curto@gmail.com>

---------

Signed-off-by: Pierre Curto <pierre.curto@gmail.com>
2023-08-14 17:04:06 +02:00
Pierre Curto
65bea1cb2d add ThreadPool (#926)
* lib/std/collections: fix tab indentation

Signed-off-by: Pierre Curto <pierre.curto@gmail.com>

* lib/std/threads: add ThreadPool

Signed-off-by: Pierre Curto <pierre.curto@gmail.com>

* ats/lib/threads: add num_cpu()

Signed-off-by: Pierre Curto <pierre.curto@gmail.com>

---------

Signed-off-by: Pierre Curto <pierre.curto@gmail.com>
2023-08-14 15:33:51 +02:00
Christoffer Lerno
f912e53038 Fix where designated initializers had optional arguments. See #923 2023-08-13 20:57:50 +02:00
Christoffer Lerno
3c8bbc2b90 Fix issue combining void! in macros in some cases. See #927 2023-08-13 20:35:49 +02:00
Christoffer Lerno
e22afe5424 Allow "if (try foo())"." 2023-08-13 18:15:20 +02:00
Pierre Curto
c060569599 add tests for Mutex (#925)
* std/lib: add tests for Mutex

Signed-off-by: Pierre Curto <pierre.curto@gmail.com>

* lib/collections: add missing import

Signed-off-by: Pierre Curto <pierre.curto@gmail.com>

* std/collections: add RingBuffer

Signed-off-by: Pierre Curto <pierre.curto@gmail.com>

---------

Signed-off-by: Pierre Curto <pierre.curto@gmail.com>
2023-08-13 17:39:09 +02:00
Christoffer Lerno
d83f591184 Check for mutex initialization. Fix missing check on optional for certain macro situations. 2023-08-12 11:55:49 +02:00
Christoffer Lerno
b6f302d1c6 Fix issues with thread, add some simple test. 2023-08-12 02:16:46 +02:00
Pierre Curto
a846ab9cc0 std/lib/threads: fix typo
Signed-off-by: Pierre Curto <pierre.curto@gmail.com>
2023-08-11 21:08:17 +02:00
Christoffer Lerno
f0d4c4d2ce Ensure type is checked before analysis of compound literals. #919 2023-08-11 18:15:39 +02:00
Christoffer Lerno
3e765a3f3e Hash maps now copy keys if keys are copyable. 2023-08-10 21:14:24 +02:00
Christoffer Lerno
356b6bb1b7 Fixed test. 2023-08-10 19:09:00 +02:00
Christoffer Lerno
951a9f2b43 Fix of ++ and -- on bitstructs. 2023-08-10 18:01:50 +02:00
Christoffer Lerno
6d870fbef0 Fix for arithmetic promotion of aliases. Some work towards $exec scripting. 2023-08-10 17:14:29 +02:00
Christoffer Lerno
01a89e2145 Fix bug where library source files were sometimes ignored. 2023-08-09 21:34:58 +02:00
Christoffer Lerno
6df6d2c084 Update type changes. 2023-08-09 02:06:50 +02:00
Christoffer Lerno
2ca67d1489 Fix void[] -> char[] cast. 2023-08-09 01:03:06 +02:00
OdnetninI (Eduardo José Gómez Hernández)
eec6ce2210 README: Updated instructions for AUR (#915)
* README: Updated instructions for AUR

* README: Missing space
2023-08-08 16:42:42 +02:00
Eduardo José Gómez Hernández
c44a0528df Tests:std/lib/io/file: Added unit tests for read_any and write_any 2023-08-08 10:12:36 +02:00
Christoffer Lerno
5b5bc7fdbb Fix void[] -> char[] cast. 2023-08-07 20:58:20 +02:00
Christoffer Lerno
68aadc958f Added read_any/write_any. 2023-08-07 19:55:10 +02:00
Christoffer Lerno
91bb31856b Introduce initial $exec. 2023-08-07 19:43:57 +02:00
Christoffer Lerno
a864822a89 Do not assert when encountering resolved non-identifier access child expressions. Addresses #913 2023-08-04 15:50:21 +02:00
Christoffer Lerno
a1a0958415 Prevent parameters to end with comma. 2023-08-03 14:37:23 +02:00
Christoffer Lerno
def97eea9d Fixes member visibility for anonymous bitstruct. Bitstruct member attributes works. Anonymous bitstruct assignment fixed. 2023-08-03 01:00:30 +02:00
Pierre Curto
e4febe62ef lib/std/io: make PathWalker return an optional
Signed-off-by: Pierre Curto <pierre.curto@gmail.com>
2023-08-02 23:05:28 +02:00
Christoffer Lerno
fc0973f378 Fix issue casting an untyped list. Addresses #908. 2023-08-02 23:03:50 +02:00
Pierre Curto
9b1c75d061 std/lib: simplify String.{,r}index_of and improve speed for the index… (#907)
* std/lib: simplify String.{,r}index_of and improve speed for the index_of one

Signed-off-by: Pierre Curto <pierre.curto@gmail.com>

* lib/std/collections: add EnumMap.get_ref

Signed-off-by: Pierre Curto <pierre.curto@gmail.com>

---------

Signed-off-by: Pierre Curto <pierre.curto@gmail.com>
2023-08-02 11:43:58 +02:00
Christoffer Lerno
09bb7d3525 Fix issue with type_info not being completely poisoned when encountering an error. Fixes #905 2023-08-01 12:26:49 +02:00
Pierre Curto
701d6a0746 std/lib/io: add Scanner (#904)
* std/lib/io: add Scanner

Signed-off-by: Pierre Curto <pierre.curto@gmail.com>

* lib/std/core: use existing methods in String.convert_ascii_to_{lower, upper}

Signed-off-by: Pierre Curto <pierre.curto@gmail.com>

---------

Signed-off-by: Pierre Curto <pierre.curto@gmail.com>
2023-08-01 10:47:21 +02:00
Christoffer Lerno
a0df1fd728 Add missing keys to project. 2023-07-31 21:15:05 +02:00
Christoffer Lerno
6a3e618ffd Another overeager type resolution removed. 2023-07-31 14:19:41 +02:00
Christoffer Lerno
d63bc10d74 Fix issue with type properties. 2023-07-31 14:02:20 +02:00
Christoffer Lerno
4fd45700a2 Fix issue with type properties. 2023-07-31 13:55:53 +02:00
Christoffer Lerno
f0c0efca8d Remove eager resolve. 2023-07-31 12:46:51 +02:00
Christoffer Lerno
72f5bac346 When a member is checked, still add it to the environment. Addresses #903. 2023-07-31 12:35:41 +02:00
Christoffer Lerno
20699c1262 Fix missing ] 2023-07-31 11:00:25 +02:00
Christoffer Lerno
8a335fc64c Updated stack trace. This addresses #898. 2023-07-31 10:57:56 +02:00
Christoffer Lerno
8a9522a363 Updated release notes. 2023-07-31 10:43:15 +02:00
Christoffer Lerno
9315443866 Fix issue where lambdas were copied incorrectly from generics. This addresses #900. 2023-07-30 21:12:02 +02:00
Christoffer Lerno
95cb2cc28e Removal of def Boo = Bar<int>; Delay array checking for flexible array. Replace resolve_fn_ptr with resolve_type_structure. Resolve type structure before entering initializers. 2023-07-30 19:59:26 +02:00
Christoffer Lerno
151fc83815 Fix issue with attribute argument resolution. 2023-07-30 16:46:31 +02:00
Christoffer Lerno
b759abc954 Fix to DString reserve. 2023-07-29 22:42:55 +02:00
Pierre Curto
6808a38c9f add std::io::stream::ByteBuffer; fix std::io::Path::walk (#895)
* lib/std/io/stream: add some inlines

Signed-off-by: Pierre Curto <pierre.curto@gmail.com>

* lib/std/io/stream add ByteBuffer

Signed-off-by: Pierre Curto <pierre.curto@gmail.com>

* lib/std/io/path: fix free of paths in walk

Signed-off-by: Pierre Curto <pierre.curto@gmail.com>

* lib/std/bits: remove unnecessary receiver type

Signed-off-by: Pierre Curto <pierre.curto@gmail.com>

---------

Signed-off-by: Pierre Curto <pierre.curto@gmail.com>
2023-07-29 21:21:45 +02:00
Christoffer Lerno
108b2244d8 Fix bug in aligned_realloc, fixes issue when a type is not resolved before the cast. #897 2023-07-28 22:21:23 +02:00
Christoffer Lerno
283a95dea2 Fix of issue where generic faults and enums would not get the "parent" id correctly fixed up. This addresses #896. 2023-07-28 21:49:22 +02:00
Christoffer Lerno
1219e8ba37 Fix accidental tracing preventing test from working. 2023-07-28 11:14:38 +02:00
Christoffer Lerno
ada3ea08fc Some initial parse output. Enforce handling of optionals. Fix issue where constants were folded despite the fact that they shouldn't be. Fix bug related to return foo() where foo() returns void!. (#893) 2023-07-27 22:58:41 +02:00
Pierre Curto
d0fa473d61 lib/std/io: assert in Formatter.out_str on "Invalid enum" if an enum value is out of range (#892)
Signed-off-by: Pierre Curto <pierre.curto@gmail.com>
2023-07-27 21:18:31 +02:00
Christoffer Lerno
7b0408f79d Adding feature flags. 2023-07-26 23:28:11 +02:00
Christoffer Lerno
c18526f10a Fix of nested union/struct initialization. Fixes #886. 2023-07-26 21:12:04 +02:00
Christoffer Lerno
499c82b089 Updated indentation to C3 standard. 2023-07-26 14:01:24 +02:00
Pierre Curto
a376d8e2bf add ERRNO for macOS, improve net error messages (#885)
* lib/std/libc: add ERRNO values for macOS

Signed-off-by: Pierre Curto <pierre.curto@gmail.com>

* lib/std/net: improve error messages

Signed-off-by: Pierre Curto <pierre.curto@gmail.com>

---------

Signed-off-by: Pierre Curto <pierre.curto@gmail.com>
2023-07-26 13:41:35 +02:00
Pierre Curto
59b077223b use IoError.UNSUPPORTED_OPERATION instead of asserts; improve Path.walk
Signed-off-by: Pierre Curto <pierre.curto@gmail.com>
2023-07-26 13:05:12 +02:00
Christoffer Lerno
9c503cf6fd Update to handle LLVM 18 part 2 2023-07-26 01:19:06 +02:00
Christoffer Lerno
7954db9a89 Update to handle LLVM 18 2023-07-26 00:56:45 +02:00
Christoffer Lerno
3929e2057d Change unreachable code to error. Remove unreachable where the compiler now can detect reachability. 2023-07-25 23:31:05 +02:00
Pierre Curto
242006d05d add is_absolute and absolute methods to path::Path (#882)
* lib/std/io/os: remove unnecessary dup in native_ls

Signed-off-by: Pierre Curto <pierre.curto@gmail.com>

* lib/std/core: add String.index_of_char and String.rindex_of_char

Signed-off-by: Pierre Curto <pierre.curto@gmail.com>

* lib/std/io: add Path.is_absolute

Signed-off-by: Pierre Curto <pierre.curto@gmail.com>

* lib/std/io: add Path.absolute

Signed-off-by: Pierre Curto <pierre.curto@gmail.com>

* lib/std: fix Path.normalize on files starting with `.`; add Path.walk

Signed-off-by: Pierre Curto <pierre.curto@gmail.com>

---------

Signed-off-by: Pierre Curto <pierre.curto@gmail.com>
2023-07-25 23:23:56 +02:00
Christoffer Lerno
b74b62e242 Fixes to broken docs parsing. Fixes #880. 2023-07-25 17:55:46 +02:00
Christoffer Lerno
d0c00b859b Fixes incorrect type resolution of && and || with optionals. Fixes #879 2023-07-25 15:16:47 +02:00
Pierre Curto
3e78a70552 lib/std/io: replace IoError.FILE_EOF with IoError.EOF
Signed-off-by: Pierre Curto <pierre.curto@gmail.com>
2023-07-25 11:32:47 +02:00
Christoffer Lerno
fad0adfcd0 Fix testcase. 2023-07-25 11:25:20 +02:00
Christoffer Lerno
9e477056ed Fixes to $defined implementation. 2023-07-23 23:55:38 +02:00
Christoffer Lerno
de9bb1d0cc Fix test. 2023-07-23 21:51:38 +02:00
Christoffer Lerno
45d1b1d671 Fix where bitstructs in subarrays triggered the wrong lowering. Fixes #877 2023-07-23 20:50:53 +02:00
Christoffer Lerno
afb902d792 Fixes #874 2023-07-23 16:24:02 +02:00
Christoffer Lerno
5f1ebdcd28 Fixes #873 2023-07-23 16:14:33 +02:00
Dmitry Atamanov
e72ec2f605 std::bits fixes. 2023-07-22 17:40:08 +02:00
Christoffer Lerno
d7d7fd3a10 Rewrite of function pointer handling. 2023-07-21 01:44:37 +02:00
Christoffer Lerno
7bccde72ed Fix missing declaration resolve when inlining enum data. 2023-07-20 18:34:02 +02:00
Christoffer Lerno
3f41e58dbd Fix pseudo-circular function pointer definitions. 2023-07-20 12:49:02 +02:00
Christoffer Lerno
581ecdb2a8 Only add a director as a link path if the directory can be found. Update raylib examples. 2023-07-19 22:06:57 +02:00
Christoffer Lerno
c4f8d5f25e Update range. 2023-07-19 21:56:52 +02:00
Dmitry Atamanov
7fbedae604 Add has function to Range and ExclusiveRange. 2023-07-19 21:45:14 +02:00
Pierre Curto
b453186de5 lib/std/collections: add init and tinit methods to PriorityQueue
Signed-off-by: Pierre Curto <pierre.curto@gmail.com>
2023-07-19 21:44:20 +02:00
Dmitry Atamanov
49ea950f78 Add is_even and is_odd to math module 2023-07-19 11:38:49 +02:00
Christoffer Lerno
5aae9f3204 Correctly apply fixup to switch macros. Addresses new issue in #862. Updates @pool. 2023-07-19 11:38:18 +02:00
Christoffer Lerno
5c2e82fc8b More use of temp allocator. 2023-07-19 02:46:43 +02:00
Christoffer Lerno
5a2fe4c9d9 Fix to macro codegen. 2023-07-19 01:34:50 +02:00
Christoffer Lerno
4dcfb7a675 Fix of #862 where enums declarations where not regenerated. Updated @pool implementation. 2023-07-18 23:23:53 +02:00
Pierre Curto
491c5ceec5 support for List in quicksort; add HashMap.@each (#861)
* compiler: fix typo in error message about failed name resolution

Signed-off-by: Pierre Curto <pierre.curto@gmail.com>

* lib/std/sort: add List support to quicksort

Signed-off-by: Pierre Curto <pierre.curto@gmail.com>

* lib/std/collections: add HashMap.@each

Signed-off-by: Pierre Curto <pierre.curto@gmail.com>

---------

Signed-off-by: Pierre Curto <pierre.curto@gmail.com>
2023-07-18 12:18:12 +02:00
Christoffer Lerno
2e6c8721bc Fix of is_comparable. 2023-07-17 21:22:16 +02:00
Pierre Curto
fd5336c56e lib/std/io/stream: add LimitReader (#858)
* lib/std/io/stream: add LimitReader

Signed-off-by: Pierre Curto <pierre.curto@gmail.com>

* lib/std: more method conversions to use new receiver notation

Signed-off-by: Pierre Curto <pierre.curto@gmail.com>

---------

Signed-off-by: Pierre Curto <pierre.curto@gmail.com>
2023-07-17 20:22:29 +02:00
Christoffer Lerno
209d994336 Finalize subprograms after writing them. Correct debug info for C varargs. Add uwtable metadata. Removed visibility from functions. 2023-07-17 20:21:10 +02:00
Christoffer Lerno
aa216fa510 Dev (#859)
* Fix bug where analysing subexpr relied on them not being analysed. Fix issue where converting a const initializer bool to integer failed. Fix of issue where the case check assumed other cases were const values.

* Fix bug where analysing subexpr relied on them not being analysed. Fix issue where converting a const initializer bool to integer failed. Fix of issue where the case check assumed other cases were const values. Remove PTHREAD for windows.

* Fix bug where analysing subexpr relied on them not being analysed. Fix issue where converting a const initializer bool to integer failed. Fix of issue where the case check assumed other cases were const values. Remove PTHREAD for windows.
2023-07-17 02:00:27 +02:00
Pierre Curto
89e084938f cross platform socket interface (#857)
* lib/std/net: add Network, Socket and Listener

Signed-off-by: Pierre Curto <pierre.curto@gmail.com>

* lib/std/net: add SocketOption

Signed-off-by: Pierre Curto <pierre.curto@gmail.com>

* lib/std/net: fixes for win32 and wasm

Signed-off-by: Pierre Curto <pierre.curto@gmail.com>

---------

Signed-off-by: Pierre Curto <pierre.curto@gmail.com>
2023-07-16 14:14:36 +02:00
Christoffer Lerno
11eb187fee Allow init from slices: float[4] x = foo[:4] 2023-07-16 01:23:12 +02:00
Christoffer Lerno
8a4e6f7dd3 Fix bug where @local declarations would get registered as @private for generic modules. #856. Update to consistent tab spacing. 2023-07-15 19:27:24 +02:00
Pierre Curto
35bffdadc2 improve the sort and collections libs (#853)
* lib/std/sort: unify binarysearch and binarysearch_with; add comments to quicksort

Signed-off-by: Pierre Curto <pierre.curto@gmail.com>

* lib/std/collections: mark List.{len, is_empty, get} with @inline

Signed-off-by: Pierre Curto <pierre.curto@gmail.com>

* lib/std/collections: add PriorityQueueMax; add tests for PriorityQueue and PriorityQueueMax

Signed-off-by: Pierre Curto <pierre.curto@gmail.com>

---------

Signed-off-by: Pierre Curto <pierre.curto@gmail.com>
2023-07-15 19:08:54 +02:00
Christoffer Lerno
97e5dbf62c Allow enum and fault constants to parameterize modules. 2023-07-15 17:03:52 +02:00
Christoffer Lerno
4325d017c8 Compacted section list. Somewhat more controlled ctarg search. 2023-07-15 16:31:53 +02:00
Christoffer Lerno
34306cbf5d Fixes missing checks to body arguments. Do not create debug declaration for value-only parameter. Bug in alignment for atomics. Macro ref parameters are pointers. 2023-07-15 15:11:44 +02:00
Kenta
90d91b4891 Update build-with-docker.sh
Remove debug echo call in config check
2023-07-14 10:39:29 +02:00
Christoffer Lerno
ab32e0dc9d Fix for tests & callstack. 2023-07-13 21:48:48 +02:00
Christoffer Lerno
26ee4babcf Native_fopen for Win32 had incorrect arguments. 2023-07-13 19:54:27 +02:00
Christoffer Lerno
c7d90baad1 Error message on bus error or segmentation fault. Some additional SIG… (#848)
* Error message on bus error or segmentation fault. Some additional SIG info. Full debug info by default. Trapping is now debugtrap rather than trap for LLVM. Row now initialized when entering function for stacktrace.
2023-07-13 15:25:06 +02:00
Christoffer Lerno
c99f298cad Fix issue where rethrow in a macro not allowing optional caused an assert to trigger. #849 2023-07-13 12:34:15 +02:00
Christoffer Lerno
fc316b1031 Improve the error message on accidentally passing a ref parameter as a pointer. 2023-07-11 12:18:18 +02:00
Pierre Curto
1ffe430df0 lib/std/sort: use Hoare's algorithm in quicksort
Signed-off-by: Pierre Curto <pierre.curto@gmail.com>
2023-07-11 11:09:51 +02:00
Pierre Curto
0efb142c88 std/lib/sort: avoid overflow in binary search
Signed-off-by: Pierre Curto <pierre.curto@gmail.com>
2023-07-11 11:09:51 +02:00
Christoffer Lerno
dddeca1856 Remove assert(try). Prevent nextcase with expression if there is no switch. Fix issue with multiple declarations and compile time variables. Nextcase default. Dropped support for non-const-int ranges. 2023-07-11 01:04:35 +02:00
Christoffer Lerno
3b0370c8bb Fix of tests. 2023-07-10 20:21:12 +02:00
Pierre Curto
2437573a8f lib/std/io: add Stream.read_all (#843)
* lib/std/io: add Stream.read_all

Signed-off-by: Pierre Curto <pierre.curto@gmail.com>

* lib/std/core: use shortened receiver notation

Signed-off-by: Pierre Curto <pierre.curto@gmail.com>

---------

Signed-off-by: Pierre Curto <pierre.curto@gmail.com>
2023-07-10 20:13:31 +02:00
Christoffer Lerno
e2676a5c7f Change zero terminated wide strings to use WString over Char16 2023-07-09 20:10:17 +02:00
Christoffer Lerno
68af987c60 Version update. 2023-07-09 16:25:37 +02:00
Christoffer Lerno
cd73b9bc42 Require exhaustive enum switching. 2023-07-09 16:25:15 +02:00
IgneousRed
943d010dfc New Rngs (#841)
* New Rngs

* Mistake

* fix Mcg using wrong constant
2023-07-09 15:00:19 +02:00
Christoffer Lerno
38cc24af27 binarysearch is now a builtin, quicksort as well. 2023-07-09 01:49:43 +02:00
Christoffer Lerno
053f7880e5 Simplified quicksort with $switch. 2023-07-09 01:46:45 +02:00
Christoffer Lerno
9543fbbf1c Exhaustive switch on enums. This addresses #838 2023-07-09 01:18:01 +02:00
Christoffer Lerno
8b605d9183 $embed. 2023-07-09 01:18:01 +02:00
Pierre Curto
77b3214746 std/lib/sort: update quicksort to use the new generics
Signed-off-by: Pierre Curto <pierre.curto@gmail.com>
2023-07-08 14:06:40 +02:00
Christoffer Lerno
5f711408c0 Fix compiler error when taking the address of a parameterized function. Fix issue when being generic over a function type, this fixes #836. 2023-07-08 13:32:32 +02:00
Pierre Curto
d709c18f5f std/lib/core: rename DString.str to DString.as_str (#834)
Signed-off-by: Pierre Curto <pierre.curto@gmail.com>
2023-07-08 00:10:04 +02:00
Christoffer Lerno
8780df8467 Correctly treat distinct inline types as having their inner type's methods available. 2023-07-07 13:54:33 +02:00
Pierre Curto
7dc1eab185 std/lib/collections: make List support the []= operator (#831)
* std/lib/collections: make List support the []= operator

Signed-off-by: Pierre Curto <pierre.curto@gmail.com>

* std/lib/io: rename receiver to self

Signed-off-by: Pierre Curto <pierre.curto@gmail.com>

---------

Signed-off-by: Pierre Curto <pierre.curto@gmail.com>
2023-07-07 13:51:44 +02:00
Christoffer Lerno
79e2d683b6 Live tracing failed to trace expression types. This fixes #832. 2023-07-07 12:00:59 +02:00
Christoffer Lerno
4f7b42cdc4 New generic syntax and ad hoc invocation. 2023-07-06 23:43:36 +02:00
Christoffer Lerno
276281c3f9 Fix of "INLINE" function. 2023-07-06 21:33:27 +02:00
Christoffer Lerno
b74de0b1e4 Fix issue where array comparison would emit the wrong basic block reference to phi when underlying comparison would emit a block. 2023-07-06 20:29:00 +02:00
Pierre Curto
df9bc377dd std/lib/net/os: add bind, listen and accept for posix platforms
Signed-off-by: Pierre Curto <pierre.curto@gmail.com>
2023-07-06 17:36:54 +02:00
Christoffer Lerno
97ded16ea2 Fix issue where macros with bodies are not filtered from liveness tracing. #818 2023-07-06 17:30:50 +02:00
Pierre Curto
731729cf1b std/lib/hash: rename receiver to self
Signed-off-by: Pierre Curto <pierre.curto@gmail.com>
2023-07-04 21:07:20 +02:00
Christoffer Lerno
daa952d990 Update quicksort. 2023-07-04 21:03:48 +02:00
Pierre Curto
f8a3e4f6f0 add basic quicksort support (#816)
* lib/std/sort: refactor binarysearch namespace to prepare for sorting

Signed-off-by: Pierre Curto <pierre.curto@gmail.com>

* std/lib/sort: add basic quicksort support

Signed-off-by: Pierre Curto <pierre.curto@gmail.com>

* lib/std/hash: use method first parameter inferred type

Signed-off-by: Pierre Curto <pierre.curto@gmail.com>

* lib/std/hash: add fnv64a support

Signed-off-by: Pierre Curto <pierre.curto@gmail.com>

---------

Signed-off-by: Pierre Curto <pierre.curto@gmail.com>
2023-07-04 20:15:03 +02:00
Christoffer Lerno
6231cc83d9 Reduce recursion max further. 2023-07-04 18:51:34 +02:00
Christoffer Lerno
20c0bbc911 Reduce recursion max. 2023-07-04 18:39:22 +02:00
Christoffer Lerno
559dcffdf2 Do not poison macro body. 2023-07-04 18:30:12 +02:00
Christoffer Lerno
7ed0aeced2 Should fix #814. 2023-07-04 16:30:09 +02:00
Christoffer Lerno
c249c3f3b6 Fix of accidentally printing "prev" when using $checks. Updated binary search. 2023-07-04 02:29:02 +02:00
Pierre Curto
55d17ec990 add the std::sort::binarysearch module
Signed-off-by: Pierre Curto <pierre.curto@gmail.com>
2023-07-04 01:35:10 +02:00
Christoffer Lerno
bbbcd9bf48 Updated name mangling for static vars in llvm. 2023-07-04 01:12:39 +02:00
Christoffer Lerno
c2c6f09d68 Fixes #811. Update some stdlib methods. Header printout correctly picks a name. #804 2023-07-03 15:45:06 +02:00
Christoffer Lerno
848a5212ef Fixes #811. Update some stdlib methods. Header printout correctly picks a name. #804 2023-07-03 13:17:43 +02:00
Tonis
eaf45436f8 Edit Matrix4 perspective fn 2023-07-02 20:02:59 +02:00
Christoffer Lerno
50784d4df6 Updated to use the new implicit type for method calls in some libraries. Made the grammar a little more liberal. 2023-07-02 10:55:25 +02:00
Christoffer Lerno
21d8a8b6da Fix #809 missing checks on generic types, accepting both types where constants should be and vice versa. 2023-07-01 23:58:00 +02:00
Christoffer Lerno
45820d45e5 Allow using inferred type on method first parameter. 2023-07-01 22:47:54 +02:00
Pierre Curto
2ac213a3ce lib/std/io/stream: add ReadBuffer and WriteBuffer streams
ReadBuffer and WriterBuffer buffer stream reads and writes to a stream.
Useful in situations where the underlying stream is sensitive to the number
of read or write calls.

Signed-off-by: Pierre Curto <pierre.curto@gmail.com>
2023-07-01 17:38:09 +02:00
Christoffer Lerno
70ea6ce04b Fix #806, and also makes sure that things like FOO.x.a is a compile time value. 2023-07-01 16:29:17 +02:00
Christoffer Lerno
9102fc6032 Comments and cleanup. 2023-06-30 18:02:34 +02:00
Pierre Curto
378ea1deea Update std::io::Stream (#805)
* add missing newlines in output messages when creating libraries

Signed-off-by: Pierre Curto <pierre.curto@gmail.com>

* lib/std/io: add Stream.supports_flush; fix AvailableStreamFn

Signed-off-by: Pierre Curto <pierre.curto@gmail.com>

---------

Signed-off-by: Pierre Curto <pierre.curto@gmail.com>
2023-06-30 17:37:08 +02:00
Christoffer Lerno
f74e294dc2 Push stack codegen to function for cleaner binaries. Some refactoring in allocators. 2023-06-30 01:01:58 +02:00
Christoffer Lerno
57c8b5fc75 Fix on rethrow + macros. 2023-06-27 20:41:17 +02:00
Pierre Curto
550b1f23ec add missing newlines in output messages when creating libraries
Signed-off-by: Pierre Curto <pierre.curto@gmail.com>
2023-06-27 14:06:00 +02:00
Christoffer Lerno
f651a59294 Fix on rethrow + macros. 2023-06-27 13:19:28 +02:00
Christoffer Lerno
fee80682b1 Update to error message when expecting a type. 2023-06-27 10:19:09 +02:00
Christoffer Lerno
b88916214f Fix of allocator gen. 2023-06-26 21:34:36 +02:00
Christoffer Lerno
685be0981f Fix priorityqueue. 2023-06-26 17:43:03 +02:00
Christoffer Lerno
fc054dad81 Added some to_string. 2023-06-26 16:21:07 +02:00
Christoffer Lerno
83f8bbb91b Update range to have exclusive and inclusive range. 2023-06-26 10:18:09 +02:00
Christoffer Lerno
0ec64c3be8 Remove broken free check on temp allocator. 2023-06-25 22:07:44 +02:00
Christoffer Lerno
f878191e6f Fix missing defer live tracing bug #801 2023-06-25 22:04:27 +02:00
Christoffer Lerno
8c73a450a1 Reduce memory consumtion. Add "range" 2023-06-25 21:39:53 +02:00
Pierre Curto
1dccd6af79 lib/std: display any caught error in tests
Signed-off-by: Pierre Curto <pierre.curto@gmail.com>
2023-06-24 21:53:03 +02:00
Christoffer Lerno
dcfbca076f Fix to zero length strings and better error when using assert with a non-constant error value. 2023-06-24 20:32:30 +02:00
Christoffer Lerno
b68b1e01b3 Updated grammar. 2023-06-24 18:24:04 +02:00
Christoffer Lerno
be04473af4 Name change, some updates using "from end" indexing. 2023-06-24 18:21:16 +02:00
Christoffer Lerno
fedffc2f35 Name change, some updates using "from end" indexing. 2023-06-24 15:20:40 +02:00
Christoffer Lerno
a187c55dfe Improved pthread availability. 2023-06-24 14:23:42 +02:00
Pierre Curto
55a1f794cf lib/std/encoding: add base64 support
Signed-off-by: Pierre Curto <pierre.curto@gmail.com>
2023-06-24 14:23:18 +02:00
Christoffer Lerno
68f6cb1286 Updated organization file functions, env naming. 2023-06-24 10:29:17 +02:00
Pierre Curto
0ab0f727ad lib/std: fix ByteWriter.read_from method (#793)
* lib/std: fix ByteWriter.read_from method

When reading from a stream which does not have an available method,
ByteWriter would not make any progress if its buffer was empty.

Signed-off-by: Pierre Curto <pierre.curto@gmail.com>

* test/unit/stdlib/io: use separate module for TestReader

Signed-off-by: Pierre Curto <pierre.curto@gmail.com>

---------

Signed-off-by: Pierre Curto <pierre.curto@gmail.com>
2023-06-23 10:44:36 +02:00
Christoffer Lerno
c46017f0dc Make MSVC happy about definite assignment. 2023-06-23 00:26:32 +02:00
Christoffer Lerno
1bd729a4bb Remove accidental code include. 2023-06-23 00:17:13 +02:00
Christoffer Lerno
0eee9daf1d Macros generating lambdas now actually is a thing. 2023-06-22 23:42:40 +02:00
Christoffer Lerno
d90fa5e292 Make errno implicitly convertible to int. Add getaddrinfo on all platforms. Fix addrinfo struct and sizes. 2023-06-22 02:06:37 +02:00
Christoffer Lerno
503a4de277 Cleanup of libc 2023-06-21 16:27:53 +02:00
Christoffer Lerno
ae9fca52ca Update version 2023-06-21 16:27:53 +02:00
Christoffer Lerno
eddae3b7f7 Close linker context. 2023-06-21 16:27:53 +02:00
Christoffer Lerno
d5b01d3a8f Native ls 2023-06-21 16:27:53 +02:00
Christoffer Lerno
ab93389031 - Updated posix/win32 stdlib namespacing
- Process stdlib
- Fix to void expression blocks
2023-06-21 16:27:53 +02:00
Christoffer Lerno
5c9eb264e8 Delete object files after linking. 2023-06-21 16:27:53 +02:00
Christoffer Lerno
4d552ae44d Update $include syntax and behaviour. Remove top level $if completely. 2023-06-21 16:27:53 +02:00
Christoffer Lerno
3dd1741484 Fix of contract error location for @require #788 2023-06-20 21:35:48 +02:00
Christoffer Lerno
f9548cb213 Fix nested hash resolution for access identifiers. Fixes #789 2023-06-20 17:05:13 +02:00
Christoffer Lerno
c3da240bc0 Grammar fix. 2023-06-19 23:34:46 +02:00
Pierre Curto
f439539c6e lib::std::core::bitorder: add read and write
Signed-off-by: Pierre Curto <pierre.curto@gmail.com>
2023-06-19 23:34:30 +02:00
Christoffer Lerno
57424d8b6b Fixes #786, constant initialization with constants sometimes causing an error lowering to LLVM. Fixes bug passing void as a vararg argument to an any vararg. 2023-06-19 10:38:37 +02:00
Christoffer Lerno
3bdeec3bc2 Incorrect defer/return value ordering in some cases. 2023-06-18 23:25:33 +02:00
Christoffer Lerno
2d46bdf8e3 $$trap and $$unreachable is automatically no_return. 2023-06-15 21:09:05 +02:00
Christoffer Lerno
5f87cb4c4f Fix of #780. 2023-06-13 18:30:57 +02:00
Christoffer Lerno
76d75ac375 Fix passing temporary objects by reference for methods. 2023-06-13 12:22:35 +02:00
Christoffer Lerno
75a6ae7111 Enable LLVM 15 2023-06-12 09:42:23 +02:00
Christoffer Lerno
cf83651c79 The new @if directive. 2023-06-11 18:56:37 +02:00
Christoffer Lerno
4c1edfb941 Dev (#777)
* The new @if directive.
2023-06-10 23:16:28 +02:00
Christoffer Lerno
82c3facb65 --obj, --emit-stdlib, --strip-unused 2023-06-09 09:37:07 +02:00
Christoffer Lerno
266dba466c Rename to no-emit-stdlib 2023-06-06 15:25:25 +02:00
Christoffer Lerno
379a5f670f Add no-obj and no-stdlib-codegen options. 2023-06-06 15:22:28 +02:00
Christoffer Lerno
8eaad81800 Dead strip by default. Add list to_string. Fix missing check for dynamic calls. 2023-06-05 14:54:17 +02:00
Christoffer Lerno
4baacc7d52 Formatting. 2023-06-03 12:08:11 +02:00
Christoffer Lerno
0de47d7c83 Ensure panic functions are never stripped. 2023-06-02 23:19:54 +02:00
Christoffer Lerno
cfd21f8ca2 Windows thread pool. 2023-06-02 23:19:54 +02:00
Christoffer Lerno
d0e8944c56 Updated task pool. 2023-06-02 21:58:25 +02:00
Christoffer Lerno
3e54d13b62 Prefer def 2023-06-02 20:08:45 +02:00
Christoffer Lerno
b30d130d92 Configurable Linux crt/crtbegin paths. 2023-05-31 21:26:23 +02:00
Christoffer Lerno
4cf98dab93 Add special ubuntu-20 release 2023-05-31 12:42:31 +02:00
Christoffer Lerno
ea1a5435bb Dead strip on "strip unused" 2023-05-30 16:42:15 +02:00
Christoffer Lerno
275e3c6a09 Update with CPU type. 2023-05-30 16:42:15 +02:00
Christoffer Lerno
9de02efa01 Exclude main methods from dllexport. 2023-05-28 15:59:15 +02:00
Christoffer Lerno
e0cfb39d79 Add DLL export for exported functions on win32. 2023-05-28 15:00:46 +02:00
Christoffer Lerno
d4259368a2 Remove call convention. 2023-05-26 14:22:50 +02:00
Christoffer Lerno
07b107ff5e Better handling of attribute definition errors. Resolves #753 2023-05-26 11:31:27 +02:00
Christoffer Lerno
b794c893d6 Dynamic dispatch. 2023-05-25 22:28:45 +02:00
Christoffer Lerno
2e498a426e Improved the README example somewhat. 2023-05-22 09:47:46 +02:00
Christoffer Lerno
0778537540 Update mac versions to test 15 and 16. Update release version to 16. 2023-05-21 22:06:24 +02:00
Christoffer Lerno
ddd0497922 Better lowering of distinct types. Noreturn function call expr recognized as a "jump" for escape analysis. Preferring "def" in libs. To upper / to lower for ascii. Initial dynlib support. 2023-05-21 21:41:01 +02:00
Tonis
a877d4458c Improve Matrix identity functions and add Quaternion to matrix function (#765)
* Edit matrix identity fn and add quaternion to matrix fn

* Change matrix identity macros to constants

---------

Co-authored-by: Tonis <tanton@paysure.solutions>
2023-05-16 11:50:01 +02:00
Christoffer Lerno
3a725d1348 Better error on missing ';' in certain cases. 2023-05-15 08:45:10 +02:00
Christoffer Lerno
353a072b75 Fix for getting the correct generic type of consts. Fix of late initialization of structs using compound literals. 2023-05-14 17:23:45 +02:00
Christoffer Lerno
8eddbfb708 Fix to net::os::posix. Remove "\s" 2023-05-12 16:55:15 +02:00
Christoffer Lerno
021bcdcf21 Add "is_initialized" to check if a map has been initialized. 2023-05-10 13:12:31 +02:00
Christoffer Lerno
bff7b492a2 Further bitstruct cast fixes. Updated code. 2023-05-10 13:03:15 +02:00
Christoffer Lerno
4d0f73a8f5 Consistent naming in allocators. Fix where cast from char array -> bitstruct would not work. 2023-05-10 10:30:37 +02:00
Christoffer Lerno
6210522c75 Update error message, disable 17 from CI 2023-05-08 18:48:40 +02:00
Christoffer Lerno
13f808b552 Added acos/asin(h) and atanh 2023-05-08 10:50:05 +02:00
Christoffer Lerno
dc30c8edc2 Fix complaints of broken compilers. 2023-05-08 00:18:39 +02:00
Christoffer Lerno
ee5ad170e0 Simplify detection of initialized variable. 2023-05-08 00:13:12 +02:00
Christoffer Lerno
172d561f07 Change syntax of $if, $assert, $include, $echo. Introduces $error 2023-05-06 12:18:00 +02:00
Christoffer Lerno
3dd6675e1b Fixed const vector codegen. Missing math comparisons. 2023-05-06 02:29:26 +02:00
Christoffer Lerno
db8c46d6c5 Addition of "distinct" and "inline" as keywords. Removal of "alias" keyword. 2023-05-04 08:48:17 +02:00
Christoffer Lerno
6fc38bbcb9 Accidental change. 2023-04-28 23:36:45 +02:00
Christoffer Lerno
184cc19d36 Further grammar fixes. 2023-04-28 19:11:57 +02:00
Christoffer Lerno
e25c06a065 Fixes to the grammar. 2023-04-27 12:26:58 +02:00
Christoffer Lerno
f2f514da74 Fixes to the grammar. 2023-04-26 17:24:35 +02:00
Christoffer Lerno
619fa26c26 Updated LLVM for Windows 2023-04-25 16:09:04 +02:00
Christoffer Lerno
e8642d6797 Fixes to access grammar. "delete" => "remove" 2023-04-24 09:10:35 +02:00
Christoffer Lerno
d1c2fbd79f Fix for MSVC 2023-04-21 17:47:32 +02:00
Christoffer Lerno
2a79e0f1cf Introduce def as a trial. Fixup of timeit. 2023-04-21 17:42:38 +02:00
Christoffer Lerno
c847650579 Introduce def as a trial. Fixup of timeit. 2023-04-21 16:03:28 +02:00
Christoffer Lerno
edd2f1c717 Updated timeit. 2023-04-21 15:57:48 +02:00
Christoffer Lerno
0a12686237 Remove acornvm 2023-04-21 15:51:00 +02:00
Christoffer Lerno
8059dc1539 delete_if, retain_if, rindex_of, compact, compact_count added to List. 2023-04-21 14:45:25 +02:00
Christoffer Lerno
809321e20c Updated grammar. Removal of elif. Removal of ':' ';' in some ct statements. Empty faults is now an error. Remove "define" for types. Remove "private". Better errors on incorrect bitstruct syntax. Introduction of wildcard type rather than optional wildcard. Removal of scaled vector type. mkdir and rmdir. Disallow define @Foo() = { @inline }. Add handling for @optreturn and change it to @return!. Restrict interface style functions. Updated x64 ABI. stdlib updates to string. Removed deprecated functions. Update how variadics are implemented. Extended error messages. x86 ABI fixes. Shift check fixes. '!' and '?' are flipped. No trailing ',' allowed in functions. Fix to string parsing. Allow l suffix. Simplifying flatpath. any replaces variant, anyfault replaces anyerr. Allow getting the underlying type of anyfault. De-duplicate string constants. Fix of readme. Extended list. Fix of "(MyEnum)x + 1". Clock and DateTime types. Fixes to array concat. 2023-04-21 10:56:39 +02:00
Christoffer Lerno
d14e778232 Use different readdir on macOS depending on arch. 2023-04-10 09:54:02 +02:00
WraithGlade
18c1b20ea0 Fixed two typos and merged them properly. (#758)
* Fixed typo: "do" --> "does".

* Fixed typo: missing "is".
2023-04-02 17:25:16 +02:00
856 changed files with 53129 additions and 34959 deletions

View File

@@ -7,7 +7,7 @@ on:
branches: [ master ]
env:
LLVM_RELEASE_VERSION: 15
LLVM_RELEASE_VERSION: 16
jobs:
@@ -23,7 +23,7 @@ jobs:
run:
shell: cmd
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: CMake
run: |
@@ -34,15 +34,23 @@ jobs:
run: |
cd resources
..\build\${{ matrix.build_type }}\c3c.exe compile-run examples\hello_world_many.c3
..\build\${{ matrix.build_type }}\c3c.exe compile-run examples\time.c3
..\build\${{ matrix.build_type }}\c3c.exe compile-run examples\fannkuch-redux.c3
..\build\${{ matrix.build_type }}\c3c.exe compile-run examples\contextfree\boolerr.c3
..\build\${{ matrix.build_type }}\c3c.exe compile-run examples\ls.c3
..\build\${{ matrix.build_type }}\c3c.exe compile-run examples\load_world.c3
..\build\${{ matrix.build_type }}\c3c.exe compile-run examples\process.c3
..\build\${{ matrix.build_type }}\c3c.exe compile --test -g -O0 --threads 1 --target macos-x64 examples\constants.c3
..\build\${{ matrix.build_type }}\c3c.exe compile-run msvc_stack.c3
- name: Build testproject
run: |
cd resources/testproject
..\..\build\${{ matrix.build_type }}\c3c.exe --debug-log run hello_world_win32
..\..\build\${{ matrix.build_type }}\c3c.exe --debug-log --emit-llvm run hello_world_win32
dir build\llvm_ir
..\..\build\${{ matrix.build_type }}\c3c.exe clean
dir build\llvm_ir
- name: Build testproject lib
run: |
@@ -61,7 +69,7 @@ jobs:
- name: Compile run unit tests
run: |
cd test
..\build\${{ matrix.build_type }}\c3c.exe compile-test unit -g1 --safe
..\build\${{ matrix.build_type }}\c3c.exe compile-test unit -O1
- name: upload artifacts
uses: actions/upload-artifact@v3
@@ -71,6 +79,7 @@ jobs:
build-msys2-mingw:
runs-on: windows-latest
# if: ${{ false }}
strategy:
# Don't abort runners if a single one fails
fail-fast: false
@@ -81,29 +90,31 @@ jobs:
run:
shell: msys2 {0}
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: msys2/setup-msys2@v2
with:
msystem: MINGW64
update: true
install: git binutils mingw-w64-x86_64-ninja mingw-w64-x86_64-cmake mingw-w64-x86_64-toolchain mingw-w64-x86_64-python
install: git binutils mingw-w64-x86_64-clang mingw-w64-x86_64-ninja mingw-w64-x86_64-cmake mingw-w64-x86_64-toolchain mingw-w64-x86_64-python
- shell: msys2 {0}
run: |
pacman --noconfirm -U https://mirror.msys2.org/mingw/mingw64/mingw-w64-x86_64-llvm-15.0.3-1-any.pkg.tar.zst
pacman --noconfirm -U https://mirror.msys2.org/mingw/mingw64/mingw-w64-x86_64-lld-15.0.3-1-any.pkg.tar.zst
pacman --noconfirm -U https://mirror.msys2.org/mingw/mingw64/mingw-w64-x86_64-llvm-17.0.6-1-any.pkg.tar.zst
pacman --noconfirm -U https://mirror.msys2.org/mingw/mingw64/mingw-w64-x86_64-lld-17.0.6-1-any.pkg.tar.zst
- name: CMake
run: |
cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=${{ matrix.build_type }}
cmake -B build -G Ninja -DCMAKE_C_COMPILER=clang -DCMAKE_BUILD_TYPE=${{ matrix.build_type }}
cmake --build build
- name: Compile and run some examples
run: |
cd resources
../build/c3c compile-run examples/hello_world_many.c3
../build/c3c compile-run examples/time.c3
../build/c3c compile-run examples/fannkuch-redux.c3
../build/c3c compile-run examples/contextfree/boolerr.c3
../build/c3c compile-run examples/load_world.c3
../build/c3c compile --test -g -O0 --threads 1 --target macos-x64 examples/constants.c3
- name: Build testproject
run: |
@@ -127,7 +138,7 @@ jobs:
build-msys2-clang:
runs-on: windows-latest
if: ${{ false }}
#if: ${{ false }}
strategy:
# Don't abort runners if a single one fails
fail-fast: false
@@ -138,7 +149,7 @@ jobs:
run:
shell: msys2 {0}
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: msys2/setup-msys2@v2
with:
@@ -155,10 +166,11 @@ jobs:
run: |
cd resources
../build/c3c compile-run examples/hello_world_many.c3
../build/c3c compile-run examples/time.c3
../build/c3c compile-run examples/fannkuch-redux.c3
../build/c3c compile-run examples/contextfree/boolerr.c3
../build/c3c compile-run examples/load_world.c3
../build/c3c compile --test -g -O0 --threads 1 --target macos-x64 examples/constants.c3
- name: Build testproject
run: |
cd resources/testproject
@@ -181,10 +193,128 @@ jobs:
fail-fast: false
matrix:
build_type: [Release, Debug]
llvm_version: [15, 16, 17]
llvm_version: [15, 16, 17, 18]
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Install common deps
run: |
sudo apt-get install zlib1g zlib1g-dev python3 ninja-build curl
- name: Install Clang ${{matrix.llvm_version}}
run: |
wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add -
if [[ "${{matrix.llvm_version}}" < 16 ]]; then
sudo apt remove libllvm15
fi
if [[ "${{matrix.llvm_version}}" < 18 ]]; then
sudo add-apt-repository "deb http://apt.llvm.org/focal/ llvm-toolchain-focal-${{matrix.llvm_version}} main"
sudo apt-get update
sudo apt-get install -y -t llvm-toolchain-focal-${{matrix.llvm_version}} libpolly-${{matrix.llvm_version}}-dev \
clang-${{matrix.llvm_version}} llvm-${{matrix.llvm_version}} llvm-${{matrix.llvm_version}}-dev \
lld-${{matrix.llvm_version}} liblld-${{matrix.llvm_version}}-dev libmlir-${{matrix.llvm_version}} \
libmlir-${{matrix.llvm_version}}-dev mlir-${{matrix.llvm_version}}-tools
else
sudo add-apt-repository "deb http://apt.llvm.org/focal/ llvm-toolchain-focal main"
sudo apt-get install -y -t llvm-toolchain-focal libpolly-${{matrix.llvm_version}}-dev \
clang-${{matrix.llvm_version}} llvm-${{matrix.llvm_version}} llvm-${{matrix.llvm_version}}-dev \
lld-${{matrix.llvm_version}} liblld-${{matrix.llvm_version}}-dev libmlir-${{matrix.llvm_version}} \
libmlir-${{matrix.llvm_version}}-dev mlir-${{matrix.llvm_version}}-tools
fi
- name: CMake
run: |
cmake -B build \
-G Ninja \
-DCMAKE_BUILD_TYPE=${{matrix.build_type}} \
-DCMAKE_C_COMPILER=clang-${{matrix.llvm_version}} \
-DCMAKE_CXX_COMPILER=clang++-${{matrix.llvm_version}} \
-DCMAKE_LINKER=lld-link-${{matrix.llvm_version}} \
-DCMAKE_OBJCOPY=llvm-objcopy-${{matrix.llvm_version}} \
-DCMAKE_STRIP=llvm-strip-${{matrix.llvm_version}} \
-DCMAKE_DLLTOOL=llvm-dlltool-${{matrix.llvm_version}} \
-DC3_LLVM_VERSION=${{matrix.llvm_version}}
cmake --build build
- name: Compile and run some examples
run: |
cd resources
../build/c3c compile examples/base64.c3
../build/c3c compile examples/binarydigits.c3
../build/c3c compile examples/brainfk.c3
../build/c3c compile examples/factorial_macro.c3
../build/c3c compile examples/fasta.c3
../build/c3c compile examples/gameoflife.c3
../build/c3c compile examples/hash.c3
../build/c3c compile examples/levenshtein.c3
../build/c3c compile examples/load_world.c3
../build/c3c compile examples/map.c3
../build/c3c compile examples/mandelbrot.c3
../build/c3c compile examples/plus_minus.c3
../build/c3c compile examples/nbodies.c3
../build/c3c compile examples/spectralnorm.c3
../build/c3c compile examples/swap.c3
../build/c3c compile examples/contextfree/boolerr.c3
../build/c3c compile examples/contextfree/dynscope.c3
../build/c3c compile examples/contextfree/guess_number.c3
../build/c3c compile examples/contextfree/multi.c3
../build/c3c compile examples/contextfree/cleanup.c3
../build/c3c compile-run examples/hello_world_many.c3
../build/c3c compile-run examples/time.c3
../build/c3c compile-run examples/fannkuch-redux.c3
../build/c3c compile-run examples/contextfree/boolerr.c3
../build/c3c compile-run examples/load_world.c3
../build/c3c compile-run examples/process.c3
../build/c3c compile-run examples/ls.c3
../build/c3c compile-run --system-linker=no linux_stack.c3
../build/c3c compile-run linux_stack.c3
- name: Compile run unit tests
run: |
cd test
../build/c3c compile-test unit
- name: Build testproject
run: |
cd resources/testproject
../../build/c3c run --debug-log
- name: Build testproject direct linker
run: |
cd resources/testproject
../../build/c3c run --debug-log --system-linker=no
- name: run compiler tests
run: |
cd test
python3 src/tester.py ../build/c3c test_suite/
- name: bundle_output
if: matrix.llvm_version == 16
run: |
mkdir linux
cp -r lib linux
cp msvc_build_libraries.py linux
cp build/c3c linux
tar czf c3-linux-${{matrix.build_type}}.tar.gz linux
- name: upload artifacts
if: matrix.llvm_version == 16
uses: actions/upload-artifact@v3
with:
name: c3-linux-${{matrix.build_type}}
path: c3-linux-${{matrix.build_type}}.tar.gz
build-linux-ubuntu20:
runs-on: ubuntu-20.04
strategy:
# Don't abort runners if a single one fails
fail-fast: false
matrix:
build_type: [Release, Debug]
llvm_version: [16]
steps:
- uses: actions/checkout@v4
- name: Install common deps
run: |
sudo apt-get install zlib1g zlib1g-dev python3 ninja-build curl
@@ -197,7 +327,7 @@ jobs:
else
sudo add-apt-repository "deb http://apt.llvm.org/focal/ llvm-toolchain-focal main"
fi
sudo apt-get update
sudo apt-get update
sudo apt-get install -y clang-${{matrix.llvm_version}} llvm-${{matrix.llvm_version}} llvm-${{matrix.llvm_version}}-dev lld-${{matrix.llvm_version}} liblld-${{matrix.llvm_version}}-dev
sudo apt-get install -y libmlir-${{matrix.llvm_version}} libmlir-${{matrix.llvm_version}}-dev mlir-${{matrix.llvm_version}}-tools
sudo apt-get install -y libpolly-${{matrix.llvm_version}}-dev
@@ -218,15 +348,37 @@ jobs:
- name: Compile and run some examples
run: |
cd resources
../build/c3c compile-run examples/hello_world_many.c3
../build/c3c compile-run examples/fannkuch-redux.c3
../build/c3c compile examples/gameoflife.c3
../build/c3c compile examples/levenshtein.c3
../build/c3c compile examples/map.c3
../build/c3c compile examples/mandelbrot.c3
../build/c3c compile examples/plus_minus.c3
../build/c3c compile examples/spectralnorm.c3
../build/c3c compile examples/swap.c3
../build/c3c compile examples/contextfree/guess_number.c3
../build/c3c compile-run examples/hash.c3
../build/c3c compile-run examples/nbodies.c3
../build/c3c compile-run examples/contextfree/boolerr.c3
../build/c3c compile-run examples/contextfree/dynscope.c3
../build/c3c compile-run examples/contextfree/multi.c3
../build/c3c compile-run examples/contextfree/cleanup.c3
../build/c3c compile-run examples/hello_world_many.c3
../build/c3c compile-run examples/time.c3
../build/c3c compile-run examples/fannkuch-redux.c3
../build/c3c compile-run examples/load_world.c3
../build/c3c compile-run examples/base64.c3
../build/c3c compile-run examples/binarydigits.c3
../build/c3c compile-run examples/brainfk.c3
../build/c3c compile-run examples/factorial_macro.c3
../build/c3c compile-run examples/fasta.c3
../build/c3c compile-run examples/process.c3
../build/c3c compile-run --system-linker=no linux_stack.c3
../build/c3c compile-run linux_stack.c3
- name: Compile run unit tests
run: |
cd test
../build/c3c compile-test unit -g1 --safe
../build/c3c compile-test unit
- name: Build testproject
run: |
@@ -236,7 +388,7 @@ jobs:
- name: Build testproject direct linker
run: |
cd resources/testproject
../../build/c3c run --debug-log --forcelinker
../../build/c3c run --debug-log --system-linker=no
- name: run compiler tests
run: |
@@ -244,21 +396,20 @@ jobs:
python3 src/tester.py ../build/c3c test_suite/
- name: bundle_output
if: matrix.llvm_version == env.LLVM_RELEASE_VERSION
if: matrix.llvm_version == 16
run: |
mkdir linux
cp -r lib linux
cp msvc_build_libraries.py linux
cp build/c3c linux
tar czf c3-linux-${{matrix.build_type}}.tar.gz linux
tar czf c3-ubuntu-20-${{matrix.build_type}}.tar.gz linux
- name: upload artifacts
if: matrix.llvm_version == env.LLVM_RELEASE_VERSION
if: matrix.llvm_version == 16
uses: actions/upload-artifact@v3
with:
name: c3-linux-${{matrix.build_type}}
path: c3-linux-${{matrix.build_type}}.tar.gz
name: c3-ubuntu-20-${{matrix.build_type}}
path: c3-ubuntu-20-${{matrix.build_type}}.tar.gz
build-mac:
runs-on: macos-latest
@@ -267,12 +418,11 @@ jobs:
fail-fast: false
matrix:
build_type: [Release, Debug]
llvm_version: [15]
llvm_version: [15, 16]
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Download LLVM
run: |
brew update
brew install llvm@${{ matrix.llvm_version }} ninja curl
echo "/usr/local/opt/llvm@${{ matrix.llvm_version }}/bin" >> $GITHUB_PATH
TMP_PATH=$(xcrun --show-sdk-path)/user/include
@@ -291,14 +441,16 @@ jobs:
run: |
cd resources
../build/c3c compile-run examples/hello_world_many.c3
../build/c3c compile-run examples/time.c3
../build/c3c compile-run examples/fannkuch-redux.c3
../build/c3c compile-run examples/contextfree/boolerr.c3
../build/c3c compile-run examples/process.c3
../build/c3c compile-run examples/load_world.c3
- name: Compile run unit tests
run: |
cd test
../build/c3c compile-test unit -g1 --safe
../build/c3c compile-test unit
- name: Build testproject
run: |
@@ -308,7 +460,7 @@ jobs:
- name: Build testproject direct linker
run: |
cd resources/testproject
../../build/c3c run --debug-log --forcelinker
../../build/c3c run --debug-log --system-linker=no
- name: Build testproject lib
run: |
@@ -343,7 +495,7 @@ jobs:
if: github.ref == 'refs/heads/master'
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: delete tag
continue-on-error: true
uses: actions/github-script@v6
@@ -426,6 +578,27 @@ jobs:
asset_name: c3-linux-debug.tar.gz
asset_content_type: application/gzip
- name: upload ubuntu 20
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: c3-ubuntu-20-Release/c3-ubuntu-20-Release.tar.gz
asset_name: c3-ubuntu-20.tar.gz
asset_content_type: application/gzip
- name: upload ubuntu 20 debug
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: c3-ubuntu-20-Debug/c3-ubuntu-20-Debug.tar.gz
asset_name: c3-ubuntu-20-debug.tar.gz
asset_content_type: application/gzip
- name: upload macos
uses: actions/upload-release-asset@v1
env:

View File

@@ -10,11 +10,20 @@ endif()
project(c3c VERSION ${CMAKE_MATCH_1})
message("C3C version: ${CMAKE_PROJECT_VERSION}")
if (NOT DEFINED CMAKE_INSTALL_LIBDIR)
if (MSVC)
set(CMAKE_INSTALL_LIBDIR "c:\\c3c\\lib")
set(CMAKE_INSTALL_BINDIR "c:\\c3c")
else ()
set(CMAKE_INSTALL_LIBDIR "/usr/local/lib/c3")
set(CMAKE_INSTALL_BINDIR "/usr/local/bin/c3c")
endif()
endif ()
# Enable fetching (for Windows)
include(FetchContent)
include(FeatureSummary)
set(CMAKE_FIND_PACKAGE_SORT_ORDER NATURAL)
set(CMAKE_FIND_PACKAGE_SORT_DIRECTION DEC)
@@ -25,19 +34,24 @@ set(CMAKE_CXX_STANDARD 17)
if(MSVC)
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /O2 /EHsc")
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} /O2 /EHsc")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /Od /Zi /EHsc")
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} /Od /Zi /EHsc")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /Od /Zi /EHa")
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} /Od /Zi /EHa")
else()
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3 -fno-exceptions")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -gdwarf-3 -fno-exceptions")
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -gdwarf-3 -O3 -fno-exceptions")
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -gdwarf-3 -fno-exceptions")
if (true)
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3 -fno-exceptions")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -gdwarf-3 -fno-exceptions")
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -gdwarf-3 -O3 -fno-exceptions")
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -gdwarf-3 -fno-exceptions")
else()
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -gdwarf-3 -O3 -fsanitize=undefined,address -fno-exceptions")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -gdwarf-3 -O1 -fsanitize=undefined,address -fno-exceptions")
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -gdwarf-3 -O3 -fsanitize=undefined,address -fno-exceptions")
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -gdwarf-3 -O1 -fsanitize=undefined,address -fno-exceptions")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=undefined,address -fno-exceptions")
endif()
endif()
#set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O1 -fsanitize=undefined")
#set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -O1 -fsanitize=undefined")
#set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -gdwarf-3 -O3 -fsanitize=undefined")
#set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -gdwarf-3 -fsanitize=undefined")
#set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=undefined")
option(C3_LINK_DYNAMIC "link dynamically with LLVM/LLD libs")
set(C3_LLVM_VERSION "auto" CACHE STRING "Use LLVM version [default: auto]")
option(C3_USE_MIMALLOC "Use built-in mimalloc" OFF)
@@ -61,7 +75,7 @@ if (NOT WIN32)
find_package(CURL)
endif()
if (NOT C3_LLVM_VERSION STREQUAL "auto")
if (${C3_LLVM_VERSION} VERSION_LESS 15 OR ${C3_LLVM_VERSION} VERSION_GREATER 17)
if (${C3_LLVM_VERSION} VERSION_LESS 15 OR ${C3_LLVM_VERSION} VERSION_GREATER 18)
message(FATAL_ERROR "LLVM ${C3_LLVM_VERSION} is not supported!")
endif()
endif()
@@ -83,15 +97,15 @@ endif()
if(CMAKE_C_COMPILER_ID STREQUAL "MSVC")
if (C3_LLVM_VERSION STREQUAL "auto")
set(C3_LLVM_VERSION "15")
set(C3_LLVM_VERSION "16")
endif()
FetchContent_Declare(
LLVM_Windows
URL https://github.com/c3lang/win-llvm/releases/download/llvm_15_0_6/llvm-15.0.6-windows-amd64-msvc17-libcmt.7z
URL https://github.com/c3lang/win-llvm/releases/download/llvm_16_0_2/llvm-16.0.2-windows-amd64-msvc17-libcmt.7z
)
FetchContent_Declare(
LLVM_Windows_debug
URL https://github.com/c3lang/win-llvm/releases/download/llvm_15_0_6/llvm-15.0.6-windows-amd64-msvc17-libcmt-dbg.7z
URL https://github.com/c3lang/win-llvm/releases/download/llvm_16_0_2/llvm-16.0.2-windows-amd64-msvc17-libcmt-dbg.7z
)
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
message("Loading Windows LLVM debug libraries, this may take a while...")
@@ -115,54 +129,75 @@ endif()
message(STATUS "Found LLVM ${LLVM_PACKAGE_VERSION}")
message(STATUS "Using LLVMConfig.cmake in: ${LLVM_DIR}")
message(STATUS "Libraries located in: ${LLVM_LIBRARY_DIRS}")
if(LLVM_ENABLE_RTTI)
message(STATUS "LLVM was built with RTTI")
else()
message(STATUS "LLVM was not built with RTTI")
endif()
include_directories(${LLVM_INCLUDE_DIRS})
link_directories(${LLVM_LIBRARY_DIRS})
add_definitions(${LLVM_DEFINITIONS})
set(LLVM_LINK_COMPONENTS
AllTargetsAsmParsers
AllTargetsCodeGens
AllTargetsDescs
AllTargetsDisassemblers
AllTargetsInfos
Analysis
AsmPrinter
BitReader
Core
DebugInfoPDB
InstCombine
IrReader
LibDriver
Linker
LTO
MC
MCDisassembler
native
nativecodegen
Object
Option
ScalarOpts
Support
Target
TransformUtils
WindowsManifest
WindowsDriver
)
if(NOT C3_LINK_DYNAMIC)
set(LLVM_LINK_COMPONENTS
AllTargetsAsmParsers
AllTargetsCodeGens
AllTargetsDescs
AllTargetsDisassemblers
AllTargetsInfos
Analysis
AsmPrinter
BitReader
Core
DebugInfoPDB
InstCombine
IrReader
LibDriver
Linker
LTO
MC
MCDisassembler
native
nativecodegen
Object
Option
ScalarOpts
Support
Target
TransformUtils
WindowsManifest
WindowsDriver
)
llvm_map_components_to_libnames(llvm_libs ${LLVM_LINK_COMPONENTS})
# These don't seem to be reliable on windows.
message(STATUS "using find_library")
find_library(LLD_COFF NAMES lldCOFF.lib lldCOFF.a liblldCOFF.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
find_library(LLD_COMMON NAMES lldCommon.lib lldCommon.a liblldCommon.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
find_library(LLD_ELF NAMES lldELF.lib lldELF.a liblldELF.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
find_library(LLD_MACHO NAMES lldMachO.lib lldMachO.a liblldMachO.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
find_library(LLD_MINGW NAMES lldMinGW.lib lldMinGW.a liblldMinGW.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
find_library(LLD_WASM NAMES lldWasm.lib lldWasm.a liblldWasm.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
else()
find_library(LLVM NAMES libLLVM.so PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
set(llvm_libs ${LLVM})
# These don't seem to be reliable on windows.
message(STATUS "using find_library")
find_library(LLD_COFF NAMES liblldCOFF.so PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
find_library(LLD_COMMON NAMES liblldCommon.so PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
find_library(LLD_ELF NAMES liblldELF.so PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
find_library(LLD_MACHO NAMES liblldMachO.so PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
find_library(LLD_MINGW NAMES liblldMinGW.so PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
find_library(LLD_WASM NAMES liblldWasm.so PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
endif()
llvm_map_components_to_libnames(llvm_libs ${LLVM_LINK_COMPONENTS})
file(REMOVE_RECURSE ${CMAKE_BINARY_DIR}/lib)
file(COPY ${CMAKE_SOURCE_DIR}/lib DESTINATION ${CMAKE_BINARY_DIR})
# These don't seem to be reliable on windows.
message(STATUS "using find_library")
find_library(LLD_COFF NAMES lldCOFF.lib lldCOFF.a liblldCOFF.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
find_library(LLD_COMMON NAMES lldCommon.lib lldCommon.a liblldCommon.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
find_library(LLD_ELF NAMES lldELF.lib lldELF.a liblldELF.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
find_library(LLD_MACHO NAMES lldMachO.lib lldMachO.a liblldMachO.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
find_library(LLD_MINGW NAMES lldMinGW.lib lldMinGW.a liblldMinGW.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
find_library(LLD_WASM NAMES lldWasm.lib lldWasm.a liblldWasm.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
if (${LLVM_PACKAGE_VERSION} VERSION_GREATER_EQUAL 16)
find_library(LLD_LOONG NAMES libLLVMLoongArchCodeGen.lib libLLVMLoongArchAsmParser.lib libLLVMLoongArchCodeGen.a libLLVMLoongArchAsmParser.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
set(lld_libs
@@ -171,12 +206,7 @@ if (${LLVM_PACKAGE_VERSION} VERSION_GREATER_EQUAL 16)
${LLD_WASM}
${LLD_MINGW}
${LLD_ELF}
${LLD_DRIVER}
${LLD_READER_WRITER}
${LLD_LOONG}
${LLD_MACHO}
${LLD_YAML}
${LLD_CORE}
)
else()
set(lld_libs
@@ -185,11 +215,7 @@ else()
${LLD_WASM}
${LLD_MINGW}
${LLD_ELF}
${LLD_DRIVER}
${LLD_READER_WRITER}
${LLD_MACHO}
${LLD_YAML}
${LLD_CORE}
)
endif()
@@ -210,17 +236,15 @@ add_executable(c3c
src/build/project_creation.c
src/compiler/ast.c
src/compiler/bigint.c
src/compiler/c_abi_internal.h
src/compiler/codegen_general.c
src/compiler/compiler.c
src/compiler/compiler.h
src/compiler/context.c
src/compiler/copying.c
src/compiler/diagnostics.c
src/compiler/dwarf.h
src/compiler/enums.h
src/compiler/float.c
src/compiler/headers.c
src/compiler/json_output.c
src/compiler/lexer.c
src/compiler/libraries.c
src/compiler/linker.c
@@ -386,12 +410,10 @@ else()
target_link_options(c3c PRIVATE -pthread)
target_compile_options(c3c PRIVATE -pthread -Wall -Werror -Wno-unknown-pragmas -Wno-unused-result
-Wno-unused-function -Wno-unused-variable -Wno-unused-parameter)
if (WIN32)
target_compile_definitions(c3c PRIVATE USE_PTHREAD=1)
endif()
endif()
install(TARGETS c3c DESTINATION bin)
install(DIRECTORY lib/ DESTINATION lib/c3)
feature_summary(WHAT ALL)

View File

@@ -54,7 +54,8 @@ fn void Stack.push(Stack* this, Type element)
if (this.capacity == this.size)
{
this.capacity *= 2;
this.elems = mem::realloc(this.elems, Type.sizeof * this.capacity);
if (this.capacity < 16) this.capacity = 16;
this.elems = realloc(this.elems, Type.sizeof * this.capacity);
}
this.elems[this.size++] = element;
}
@@ -78,20 +79,20 @@ import stack;
// Define our new types, the first will implicitly create
// a complete copy of the entire Stack module with "Type" set to "int"
define IntStack = Stack<int>;
def IntStack = Stack(<int>);
// The second creates another copy with "Type" set to "double"
define DoubleStack = Stack<double>;
def DoubleStack = Stack(<double>);
// If we had added "define IntStack2 = Stack<int>"
// If we had added "define IntStack2 = Stack(<int>)"
// no additional copy would have been made (since we already
// have an parameterization of Stack<int>) so it would
// have an parameterization of Stack(<int>)) so it would
// be same as declaring IntStack2 an alias of IntStack
// Importing an external C function is straightforward
// here is an example of importing libc's printf:
extern fn int printf(char* format, ...);
fn void test()
fn void main()
{
IntStack stack;
// Note that C3 uses zero initialization by default
@@ -111,12 +112,12 @@ fn void test()
dstack.push(2.3);
dstack.push(3.141);
dstack.push(1.1235);
// Prints pop: 1.1235
// Prints pop: 1.123500
printf("pop: %f\n", dstack.pop());
}
```
### In what ways do C3 differ from C?
### In what ways does C3 differ from C?
- No mandatory header files
- New semantic macro system
@@ -130,17 +131,15 @@ fn void test()
- Value methods
- Associated enum data
- No preprocessor
- Less undefined behaviour and runtime checks in "safe" mode
- Less undefined behaviour and added runtime checks in "safe" mode
- Limited operator overloading to enable userland dynamic arrays
- Optional pre and post conditions
### Current status
The current version of the compiler is alpha release 0.4.
The current stable version of the compiler is **version 0.5**.
Design work on C3 complete aside from fleshing out details, such as
inline asm. As the standard library work progresses, changes and improvements
to the language will happen continuously.
The upcoming 0.6 release will focus on expanding the standard library.
Follow the issues [here](https://github.com/c3lang/c3c/issues).
If you have suggestions on how to improve the language, either [file an issue](https://github.com/c3lang/c3c/issues)
@@ -148,7 +147,38 @@ or discuss C3 on its dedicated Discord: [https://discord.gg/qN76R87](https://dis
The compiler is currently verified to compile on Linux, Windows and MacOS.
**Support matrix**
| Platform | Native C3 compiler available? | Target supported | Stack trace | Threads | Sockets | Inline asm |
|--------------------------|-------------------------------|-------------------------|-------------|----------|----------|------------|
| Win32 x64 | Yes | Yes + cross compilation | Yes | Yes | Yes | Yes* |
| Win32 Aarch64 | Untested | Untested | Untested | Untested | Untested | Yes* |
| MacOS x64 | Yes | Yes + cross compilation | Yes | Yes | Yes | Yes* |
| MacOS Aarch64 | Yes | Yes + cross compilation | Yes | Yes | Yes | Yes* |
| iOS Aarch64 | No | Untested | Untested | Yes | Yes | Yes* |
| Linux x86 | Yes | Yes | Yes | Yes | Yes | Yes* |
| Linux x64 | Yes | Yes | Yes | Yes | Yes | Yes* |
| Linux Aarch64 | Yes | Yes | Yes | Yes | Yes | Yes* |
| Linux Riscv32 | Yes | Yes | Yes | Yes | Yes | Untested |
| Linux Riscv64 | Yes | Yes | Yes | Yes | Yes | Untested |
| ELF freestanding x86 | No | Untested | No | No | No | Yes* |
| ELF freestanding x64 | No | Untested | No | No | No | Yes* |
| ELF freestanding Aarch64 | No | Untested | No | No | No | Yes* |
| ELF freestanding Riscv64 | No | Untested | No | No | No | Untested |
| ELF freestanding Riscv32 | No | Untested | No | No | No | Untested |
| FreeBSD x86 | Untested | Untested | No | Yes | Untested | Yes* |
| FreeBSD x64 | Untested | Untested | No | Yes | Untested | Yes* |
| NetBSD x86 | Untested | Untested | No | Yes | Untested | Yes* |
| NetBSD x64 | Untested | Untested | No | Yes | Untested | Yes* |
| OpenBSD x86 | Untested | Untested | No | Yes | Untested | Yes* |
| OpenBSD x64 | Untested | Untested | No | Yes | Untested | Yes* |
| MCU x86 | No | Untested | No | No | No | Yes* |
| Wasm32 | No | Yes | No | No | No | No |
| Wasm64 | No | Untested | No | No | No | No |
*\* Inline asm is still a work in progress*
More platforms will be supported in the future.
#### What can you help with?
@@ -183,8 +213,18 @@ The compiler is currently verified to compile on Linux, Windows and MacOS.
4. Run `./c3c`.
#### Installing on Arch Linux
There is an AUR package for the c3c compiler : [c3c-git](https://aur.archlinux.org/packages/c3c-git)
You can use your AUR package manager or clone it manually:
There is an AUR package for the c3c compiler : [c3c-git](https://aur.archlinux.org/packages/c3c-git).
Due to some issues with the LLVM packaged for Arch Linux, the AUR package will download and use LLVM 16 for Ubuntu-23.04 to compile the c3c compiler.
You can use your AUR package manager:
```sh
paru -S c3c-git
# or yay -S c3c-git
# or aura -A c3c-git
```
Or clone it manually:
```sh
git clone https://aur.archlinux.org/c3c-git.git
cd c3c-git
@@ -309,6 +349,6 @@ Editor plugins can be found at https://github.com/c3lang/editor-plugins.
1. Write the test, either adding to existing test files in `/test/unit/` or add
a new file. (If testing the standard library, put it in the `/test/unit/stdlib/` subdirectory).
2. Make sure that the test functions have the `@test` attribute.
3. Run tests and see that they pass. (Recommended settings: `c3c compile-test --safe -g1 -O0 test/unit`.
3. Run tests and see that they pass. (Recommended settings: `c3c compile-test -O0 test/unit`.
- in this example `test/unit/` is the relative path to the test directory, so adjust as required)
4. Make a pull request for the new tests.
4. Make a pull request for the new tests.

View File

@@ -21,7 +21,6 @@ if type podman 2>/dev/null >/dev/null; then
fi
if [ $config == "Debug" ]; then
echo "debug???"
CMAKE_BUILD_TYPE=Debug
else
CMAKE_BUILD_TYPE="$config"

View File

@@ -53,6 +53,10 @@ fn bool char.is_blank(char c) => is_blank_m(c);
fn bool char.is_cntrl(char c) => is_cntrl_m(c);
fn char char.to_lower(char c) => (char)to_lower_m(c);
fn char char.to_upper(char c) => (char)to_upper_m(c);
/**
* @require c.is_xdigit()
**/
fn char char.from_hex(char c) => c.is_digit() ? c - '0' : 10 + (c | 0x20) - 'a';
fn bool uint.in_range(uint c, uint start, uint len) => in_range_m(c, start, len);
fn bool uint.is_lower(uint c) => is_lower_m(c);

View File

@@ -1,5 +1,520 @@
// Copyright (c) 2021 Christoffer Lerno. All rights reserved.
// Copyright (c) 2023 Eduardo José Gómez Hernández. All rights reserved.
// Use of this source code is governed by the MIT license
// a copy of which can be found in the LICENSE_STDLIB file.
module std::atomic;
module std::atomic::types(<Type>);
struct Atomic
{
Type data;
}
/**
* Loads data atomically, by default this uses SEQ_CONSISTENT ordering.
*
* @param ordering "The ordering, cannot be release or acquire-release."
* @require ordering != RELEASE && ordering != ACQUIRE_RELEASE : "Release and acquire-release are not valid for load"
**/
macro Type Atomic.load(&self, AtomicOrdering ordering = SEQ_CONSISTENT)
{
Type* data = &self.data;
switch(ordering)
{
case NOT_ATOMIC: return $$atomic_load(data, false, AtomicOrdering.NOT_ATOMIC.ordinal);
case UNORDERED: return $$atomic_load(data, false, AtomicOrdering.UNORDERED.ordinal);
case RELAXED: return $$atomic_load(data, false, AtomicOrdering.RELAXED.ordinal);
case ACQUIRE: return $$atomic_load(data, false, AtomicOrdering.ACQUIRE.ordinal);
case SEQ_CONSISTENT: return $$atomic_load(data, false, AtomicOrdering.SEQ_CONSISTENT.ordinal);
case ACQUIRE_RELEASE:
case RELEASE: unreachable("Invalid ordering.");
}
}
/**
* Stores data atomically, by default this uses SEQ_CONSISTENT ordering.
*
* @param ordering "The ordering, cannot be acquire or acquire-release."
* @require ordering != ACQUIRE && ordering != ACQUIRE_RELEASE : "Acquire and acquire-release are not valid for store"
**/
macro void Atomic.store(&self, Type value, AtomicOrdering ordering = SEQ_CONSISTENT)
{
Type* data = &self.data;
switch(ordering)
{
case NOT_ATOMIC: $$atomic_store(data, value, false, AtomicOrdering.NOT_ATOMIC.ordinal);
case UNORDERED: $$atomic_store(data, value, false, AtomicOrdering.UNORDERED.ordinal);
case RELAXED: $$atomic_store(data, value, false, AtomicOrdering.RELAXED.ordinal);
case RELEASE: $$atomic_store(data, value, false, AtomicOrdering.RELEASE.ordinal);
case SEQ_CONSISTENT: $$atomic_store(data, value, false, AtomicOrdering.SEQ_CONSISTENT.ordinal);
case ACQUIRE_RELEASE:
case ACQUIRE: unreachable("Invalid ordering.");
}
}
macro Type Atomic.add(&self, Type value, AtomicOrdering ordering = SEQ_CONSISTENT)
{
Type* data = &self.data;
return @atomic_exec(atomic::fetch_add, data, value, ordering);
}
macro Type Atomic.sub(&self, Type value, AtomicOrdering ordering = SEQ_CONSISTENT)
{
Type* data = &self.data;
return @atomic_exec(atomic::fetch_sub, data, value, ordering);
}
macro Type Atomic.mul(&self, Type value, AtomicOrdering ordering = SEQ_CONSISTENT)
{
Type* data = &self.data;
return @atomic_exec(atomic::fetch_mul, data, value, ordering);
}
macro Type Atomic.div(&self, Type value, AtomicOrdering ordering = SEQ_CONSISTENT)
{
Type* data = &self.data;
return @atomic_exec(atomic::fetch_div, data, value, ordering);
}
macro Type Atomic.max(&self, Type value, AtomicOrdering ordering = SEQ_CONSISTENT)
{
Type* data = &self.data;
return @atomic_exec(atomic::fetch_div, data, value, ordering);
}
macro Type Atomic.min(&self, Type value, AtomicOrdering ordering = SEQ_CONSISTENT)
{
Type* data = &self.data;
return @atomic_exec(atomic::fetch_min, data, value, ordering);
}
macro Type Atomic.or(&self, uint value, AtomicOrdering ordering = SEQ_CONSISTENT) @if(!types::is_float(Type))
{
Type* data = &self.data;
return @atomic_exec(atomic::fetch_or, data, value, ordering);
}
fn Type Atomic.xor(&self, uint value, AtomicOrdering ordering = SEQ_CONSISTENT) @if(!types::is_float(Type))
{
Type* data = &self.data;
return @atomic_exec(atomic::fetch_xor, data, value, ordering);
}
macro Type Atomic.and(&self, uint value, AtomicOrdering ordering = SEQ_CONSISTENT) @if(!types::is_float(Type))
{
Type* data = &self.data;
return @atomic_exec(atomic::fetch_and, data, value, ordering);
}
macro Type Atomic.shift_right(&self, uint amount, AtomicOrdering ordering = SEQ_CONSISTENT) @if(!types::is_float(Type))
{
Type* data = &self.data;
return @atomic_exec(atomic::fetch_shift_right, data, amount, ordering);
}
macro Type Atomic.shift_left(&self, uint amount, AtomicOrdering ordering = SEQ_CONSISTENT) @if(!types::is_float(Type))
{
Type* data = &self.data;
return @atomic_exec(atomic::fetch_shift_left, data, amount, ordering);
}
macro @atomic_exec(#func, data, value, ordering) @local
{
switch(ordering)
{
case RELAXED: return #func(data, value, RELAXED);
case ACQUIRE: return #func(data, value, ACQUIRE);
case RELEASE: return #func(data, value, RELEASE);
case ACQUIRE_RELEASE: return #func(data, value, ACQUIRE_RELEASE);
case SEQ_CONSISTENT: return #func(data, value, SEQ_CONSISTENT);
default: assert(false, "Ordering may not be non-atomic or unordered.");
}
}
module std::atomic;
import std::math;
/**
* @param [&in] ptr "the variable or dereferenced pointer to the data."
* @param [in] y "the value to be added to ptr."
* @param $ordering "atomic ordering of the load, defaults to SEQ_CONSISTENT"
* @return "returns the old value of ptr"
*
* @require !$alignment || math::is_power_of_2($alignment) "Alignment must be a power of two."
* @require types::is_int($typeof(*ptr)) || types::is_float($typeof(*ptr)) "Only integer/float pointers may be used."
* @require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED "Acquire ordering is not valid."
**/
macro fetch_add(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile = false, usz $alignment = 0)
{
$if $alignment == 0:
$alignment = $typeof(*ptr).sizeof;
$endif
return $$atomic_fetch_add(ptr, y, $volatile, $ordering.ordinal, $alignment);
}
/**
* @param [&in] ptr "the variable or dereferenced pointer to the data."
* @param [in] y "the value to be added to ptr."
* @param $ordering "atomic ordering of the load, defaults to SEQ_CONSISTENT"
* @return "returns the old value of ptr"
*
* @require !$alignment || math::is_power_of_2($alignment) "Alignment must be a power of two."
* @require types::is_int($typeof(*ptr)) || types::is_float($typeof(*ptr)) "Only integer/float pointers may be used."
* @require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED "Acquire ordering is not valid."
**/
macro fetch_sub(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile = false, usz $alignment = 0)
{
$if $alignment == 0:
$alignment = $typeof(*ptr).sizeof;
$endif
return $$atomic_fetch_sub(ptr, y, $volatile, $ordering.ordinal, $alignment);
}
/**
* @param [&in] ptr "the variable or dereferenced pointer to the data."
* @param [in] y "the value to be added to ptr."
* @param $ordering "atomic ordering of the load, defaults to SEQ_CONSISTENT"
* @return "returns the old value of ptr"
*
* @require types::is_int($typeof(*ptr)) || types::is_float($typeof(*ptr)) "Only integer/float pointers may be used."
* @require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED "Acquire ordering is not valid."
**/
macro fetch_mul(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT)
{
var $load_ordering = $ordering;
$if $ordering == RELEASE || $ordering == ACQUIRE_RELEASE:
$load_ordering = AtomicOrdering.SEQ_CONSISTENT;
$endif
var $StorageType = $typefrom(types::lower_to_atomic_compatible_type($typeof(*ptr)));
$StorageType* storage_ptr = ($StorageType*)ptr;
$typeof(*ptr) old_value;
$typeof(*ptr) new_value;
$StorageType storage_old_value;
$StorageType storage_new_value;
do {
storage_old_value = $$atomic_load(storage_ptr, false, $load_ordering.ordinal);
old_value = bitcast(storage_old_value, $typeof(*ptr));
new_value = old_value * y;
storage_new_value = bitcast(new_value, $StorageType);
} while (mem::compare_exchange(storage_ptr, storage_old_value, storage_new_value, $ordering, $load_ordering) != storage_old_value);
return old_value;
}
/**
* @param [&in] ptr "the variable or dereferenced pointer to the data."
* @param [in] y "the value to be added to ptr."
* @param $ordering "atomic ordering of the load, defaults to SEQ_CONSISTENT"
* @return "returns the old value of ptr"
*
* @require types::is_int($typeof(*ptr)) || types::is_float($typeof(*ptr)) "Only integer/float pointers may be used."
* @require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED "Acquire ordering is not valid."
**/
macro fetch_div(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT)
{
var $load_ordering = $ordering;
$if $ordering == RELEASE || $ordering == ACQUIRE_RELEASE:
$load_ordering = AtomicOrdering.SEQ_CONSISTENT;
$endif
var $StorageType = $typefrom(types::lower_to_atomic_compatible_type($typeof(*ptr)));
$StorageType* storage_ptr = ($StorageType*)ptr;
$typeof(*ptr) old_value;
$typeof(*ptr) new_value;
$StorageType storage_old_value;
$StorageType storage_new_value;
do {
storage_old_value = $$atomic_load(storage_ptr, false, $load_ordering.ordinal);
old_value = bitcast(storage_old_value, $typeof(*ptr));
new_value = old_value / y;
storage_new_value = bitcast(new_value, $StorageType);
} while (mem::compare_exchange(storage_ptr, storage_old_value, storage_new_value, $ordering, $load_ordering) != storage_old_value);
return old_value;
}
/**
* @param [&in] ptr "the variable or dereferenced pointer to the data."
* @param [in] y "the value to be added to ptr."
* @param $ordering "atomic ordering of the load, defaults to SEQ_CONSISTENT"
* @return "returns the old value of ptr"
*
* @require !$alignment || math::is_power_of_2($alignment) "Alignment must be a power of two."
* @require types::is_int($typeof(*ptr)) "Only integer pointers may be used."
* @require types::is_int($typeof(y)) "The value for or must be an int"
* @require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED "Acquire ordering is not valid."
**/
macro fetch_or(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile = false, usz $alignment = 0)
{
$if types::is_int($typeof(*ptr)):
return $$atomic_fetch_or(ptr, y, $volatile, $ordering.ordinal, $alignment);
$endif
var $load_ordering = $ordering;
$if $ordering == RELEASE || $ordering == ACQUIRE_RELEASE:
$load_ordering = AtomicOrdering.SEQ_CONSISTENT;
$endif
var $StorageType = $typefrom(types::lower_to_atomic_compatible_type($typeof(*ptr)));
$StorageType* storage_ptr = ($StorageType*)ptr;
$typeof(*ptr) old_value;
$typeof(*ptr) new_value;
$StorageType storage_old_value;
$StorageType storage_new_value;
$StorageType storage_y = ($StorageType)y;
do {
storage_old_value = $$atomic_load(storage_ptr, false, $load_ordering.ordinal);
old_value = bitcast(storage_old_value, $typeof(*ptr));
new_value = storage_old_value | storage_y;
storage_new_value = bitcast(new_value, $StorageType);
} while (mem::compare_exchange(storage_ptr, storage_old_value, storage_new_value, $ordering, $load_ordering) != storage_old_value);
return old_value;
}
/**
* @param [&in] ptr "the variable or dereferenced pointer to the data."
* @param [in] y "the value to be added to ptr."
* @param $ordering "atomic ordering of the load, defaults to SEQ_CONSISTENT"
* @return "returns the old value of ptr"
*
* @require !$alignment || math::is_power_of_2($alignment) "Alignment must be a power of two."
* @require types::is_int($typeof(*ptr)) "Only integer pointers may be used."
* @require types::is_int($typeof(y)) "The value for or must be an int"
* @require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED "Acquire ordering is not valid."
**/
macro fetch_xor(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile = false, usz $alignment = 0)
{
$if types::is_int($typeof(*ptr)):
return $$atomic_fetch_xor(ptr, y, $volatile, $ordering.ordinal, $alignment);
$endif
var $load_ordering = $ordering;
$if $ordering == RELEASE || $ordering == ACQUIRE_RELEASE:
$load_ordering = AtomicOrdering.SEQ_CONSISTENT;
$endif
var $StorageType = $typefrom(types::lower_to_atomic_compatible_type($typeof(*ptr)));
$StorageType* storage_ptr = ($StorageType*)ptr;
$typeof(*ptr) old_value;
$typeof(*ptr) new_value;
$StorageType storage_old_value;
$StorageType storage_new_value;
$StorageType storage_y = ($StorageType)y;
do {
storage_old_value = $$atomic_load(storage_ptr, false, $load_ordering.ordinal);
old_value = bitcast(storage_old_value, $typeof(*ptr));
new_value = storage_old_value ^ storage_y;
storage_new_value = bitcast(new_value, $StorageType);
} while (mem::compare_exchange(storage_ptr, storage_old_value, storage_new_value, $ordering, $load_ordering) != storage_old_value);
return old_value;
}
/**
* @param [&in] ptr "the variable or dereferenced pointer to the data."
* @param [in] y "the value to be added to ptr."
* @param $ordering "atomic ordering of the load, defaults to SEQ_CONSISTENT"
* @return "returns the old value of ptr"
*
* @require !$alignment || math::is_power_of_2($alignment) "Alignment must be a power of two."
* @require types::is_int($typeof(*ptr)) "Only integer pointers may be used."
* @require types::is_int($typeof(y)) "The value for or must be an int"
* @require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED "Acquire ordering is not valid."
**/
macro fetch_and(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile = false, usz $alignment = 0)
{
$if types::is_int($typeof(*ptr)):
return $$atomic_fetch_and(ptr, y, $volatile, $ordering.ordinal, $alignment);
$endif
var $load_ordering = $ordering;
$if $ordering == RELEASE || $ordering == ACQUIRE_RELEASE:
$load_ordering = AtomicOrdering.SEQ_CONSISTENT;
$endif
var $StorageType = $typefrom(types::lower_to_atomic_compatible_type($typeof(*ptr)));
$StorageType* storage_ptr = ($StorageType*)ptr;
$typeof(*ptr) old_value;
$typeof(*ptr) new_value;
$StorageType storage_old_value;
$StorageType storage_new_value;
$StorageType storage_y = ($StorageType)y;
do {
storage_old_value = $$atomic_load(storage_ptr, false, $load_ordering.ordinal);
old_value = bitcast(storage_old_value, $typeof(*ptr));
new_value = storage_old_value & storage_y;
storage_new_value = bitcast(new_value, $StorageType);
} while (mem::compare_exchange(storage_ptr, storage_old_value, storage_new_value, $ordering, $load_ordering) != storage_old_value);
return old_value;
}
/**
* @param [&in] ptr "the variable or dereferenced pointer to the data."
* @param [in] y "the value to be added to ptr."
* @param $ordering "atomic ordering of the load, defaults to SEQ_CONSISTENT"
* @return "returns the old value of ptr"
*
* @require types::is_int($typeof(*ptr)) "Only integer pointers may be used."
* @require types::is_int($typeof(y)) "The value for or must be an int"
* @require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED "Acquire ordering is not valid."
**/
macro fetch_shift_right(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT)
{
var $load_ordering = $ordering;
$if $ordering == RELEASE || $ordering == ACQUIRE_RELEASE:
$load_ordering = AtomicOrdering.SEQ_CONSISTENT;
$endif
var $StorageType = $typefrom(types::lower_to_atomic_compatible_type($typeof(*ptr)));
$StorageType* storage_ptr = ($StorageType*)ptr;
$typeof(*ptr) old_value;
$typeof(*ptr) new_value;
$StorageType storage_old_value;
$StorageType storage_new_value;
$StorageType storage_y = ($StorageType)y;
do {
storage_old_value = $$atomic_load(storage_ptr, false, $load_ordering.ordinal);
old_value = bitcast(storage_old_value, $typeof(*ptr));
new_value = storage_old_value >> storage_y;
storage_new_value = bitcast(new_value, $StorageType);
} while (mem::compare_exchange(storage_ptr, storage_old_value, storage_new_value, $ordering, $load_ordering) != storage_old_value);
return old_value;
}
/**
* @param [&in] ptr "the variable or dereferenced pointer to the data."
* @param [in] y "the value to be added to ptr."
* @param $ordering "atomic ordering of the load, defaults to SEQ_CONSISTENT"
* @return "returns the old value of ptr"
*
* @require types::is_int($typeof(*ptr)) "Only integer pointers may be used."
* @require types::is_int($typeof(y)) "The value for or must be an int"
* @require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED "Acquire ordering is not valid."
**/
macro fetch_shift_left(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT)
{
var $load_ordering = $ordering;
$if $ordering == RELEASE || $ordering == ACQUIRE_RELEASE:
$load_ordering = AtomicOrdering.SEQ_CONSISTENT;
$endif
var $StorageType = $typefrom(types::lower_to_atomic_compatible_type($typeof(*ptr)));
$StorageType* storage_ptr = ($StorageType*)ptr;
$typeof(*ptr) old_value;
$typeof(*ptr) new_value;
$StorageType storage_old_value;
$StorageType storage_new_value;
$StorageType storage_y = ($StorageType)y;
do {
storage_old_value = $$atomic_load(storage_ptr, false, $load_ordering.ordinal);
old_value = bitcast(storage_old_value, $typeof(*ptr));
new_value = storage_old_value << storage_y;
storage_new_value = bitcast(new_value, $StorageType);
} while (mem::compare_exchange(storage_ptr, storage_old_value, storage_new_value, $ordering, $load_ordering) != storage_old_value);
return old_value;
}
/**
* @param [&in] ptr "the variable or dereferenced pointer to the data."
* @param $ordering "atomic ordering of the load, defaults to SEQ_CONSISTENT"
* @return "returns the old value of ptr"
*
* @require types::is_int($typeof(*ptr)) "Only integer pointers may be used."
* @require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED "Acquire ordering is not valid."
**/
macro flag_set(ptr, AtomicOrdering $ordering = SEQ_CONSISTENT)
{
$typeof(*ptr) old_value;
$typeof(*ptr) new_value = true;
do {
old_value = $$atomic_load(ptr, false, $ordering.ordinal);
} while (mem::compare_exchange(ptr, old_value, new_value, $ordering, $load_ordering) != old_value);
return old_value;
}
/**
* @param [&in] ptr "the variable or dereferenced pointer to the data."
* @param $ordering "atomic ordering of the load, defaults to SEQ_CONSISTENT"
* @return "returns the old value of ptr"
*
* @require types::is_int($typeof(*ptr)) "Only integer pointers may be used."
* @require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED "Acquire ordering is not valid."
**/
macro flag_clear(ptr, AtomicOrdering $ordering = SEQ_CONSISTENT)
{
$typeof(*ptr) old_value;
$typeof(*ptr) new_value = false;
do {
old_value = $$atomic_load(ptr, false, $ordering.ordinal);
} while (mem::compare_exchange(ptr, old_value, new_value, $ordering, $load_ordering) != old_value);
return old_value;
}
/**
* @param [&in] ptr "the variable or dereferenced pointer to the data."
* @param [in] y "the value to be added to ptr."
* @param $ordering "atomic ordering of the load, defaults to SEQ_CONSISTENT"
* @return "returns the old value of ptr"
*
* @require types::is_int($typeof(*ptr)) || types::is_float($typeof(*ptr)) "Only integer/float pointers may be used."
* @require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED "Acquire ordering is not valid."
**/
macro fetch_max(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile = false, usz $alignment = 0)
{
$if $alignment == 0:
$alignment = $typeof(*ptr).sizeof;
$endif
return $$atomic_fetch_max(ptr, y, $volatile, $ordering.ordinal, $alignment);
}
/**
* @param [&in] ptr "the variable or dereferenced pointer to the data."
* @param [in] y "the value to be added to ptr."
* @param $ordering "atomic ordering of the load, defaults to SEQ_CONSISTENT"
* @return "returns the old value of ptr"
*
* @require types::is_int($typeof(*ptr)) || types::is_float($typeof(*ptr)) "Only integer/float pointers may be used."
* @require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED "Acquire ordering is not valid."
**/
macro fetch_min(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile = false, usz $alignment = 0)
{
$if $alignment == 0:
$alignment = $typeof(*ptr).sizeof;
$endif
return $$atomic_fetch_min(ptr, y, $volatile, $ordering.ordinal, $alignment);
}

63
lib/std/atomic_nolibc.c3 Normal file
View File

@@ -0,0 +1,63 @@
// Copyright (c) 2023 Eduardo José Gómez Hernández. All rights reserved.
// Use of this source code is governed by the MIT license
// a copy of which can be found in the LICENSE_STDLIB file.
module std::atomic;
macro @__atomic_compare_exchange_ordering_failure(ptr, expected, desired, $success, failure, $alignment) {
switch(failure)
{
case AtomicOrdering.RELAXED.ordinal: return $$compare_exchange(ptr, expected, desired, false, false, $success, AtomicOrdering.RELAXED.ordinal, $alignment);
case AtomicOrdering.ACQUIRE.ordinal: return $$compare_exchange(ptr, expected, desired, false, false, $success, AtomicOrdering.ACQUIRE.ordinal, $alignment);
case AtomicOrdering.SEQ_CONSISTENT.ordinal: return $$compare_exchange(ptr, expected, desired, false, false, $success, AtomicOrdering.SEQ_CONSISTENT.ordinal, $alignment);
default: assert(false, "Unrecognized failure ordering");
}
return 0;
}
macro @__atomic_compare_exchange_ordering_success(ptr, expected, desired, success, failure, $alignment)
{
switch(success)
{
case AtomicOrdering.RELAXED.ordinal: return @__atomic_compare_exchange_ordering_failure(ptr, expected, desired, AtomicOrdering.RELAXED.ordinal, failure, $alignment);
case AtomicOrdering.ACQUIRE.ordinal: return @__atomic_compare_exchange_ordering_failure(ptr, expected, desired, AtomicOrdering.ACQUIRE.ordinal, failure, $alignment);
case AtomicOrdering.RELEASE.ordinal: return @__atomic_compare_exchange_ordering_failure(ptr, expected, desired, AtomicOrdering.RELEASE.ordinal, failure, $alignment);
case AtomicOrdering.ACQUIRE_RELEASE.ordinal: return @__atomic_compare_exchange_ordering_failure(ptr, expected, desired, AtomicOrdering.ACQUIRE_RELEASE.ordinal, failure, $alignment);
case AtomicOrdering.SEQ_CONSISTENT.ordinal: return @__atomic_compare_exchange_ordering_failure(ptr, expected, desired, AtomicOrdering.SEQ_CONSISTENT.ordinal, failure, $alignment);
default: assert(false, "Unrecognized success ordering");
}
return 0;
}
fn CInt __atomic_compare_exchange(CInt size, any* ptr, any* expected, any* desired, CInt success, CInt failure) @extern("__atomic_compare_exchange") @export
{
switch (size)
{
case 1:
char* pt = (char*)ptr;
char ex = *(char*)expected;
char de = *(char*)desired;
if (ex == @__atomic_compare_exchange_ordering_success(pt, ex, de, success, failure, 1)) return 1;
case 2:
short* pt = (short*)ptr;
short ex = *(short*)expected;
short de = *(short*)desired;
if (ex == @__atomic_compare_exchange_ordering_success(pt, ex, de, success, failure, 2)) return 1;
case 4:
int* pt = (int*)ptr;
int ex = *(int*)expected;
int de = *(int*)desired;
if (ex == @__atomic_compare_exchange_ordering_success(pt, ex, de, success, failure, 4)) return 1;
case 8:
$if iptr.sizeof >= 8:
long* pt = (long*)ptr;
long ex = *(long*)expected;
long de = *(long*)desired;
if (ex == @__atomic_compare_exchange_ordering_success(pt, ex, de, success, failure, 8)) return 1;
$else
nextcase;
$endif
default:
assert(false, "Unsuported size (%d) for atomic_compare_exchange", size);
}
return 0;
}

View File

@@ -1,6 +1,5 @@
module std::bits;
/**
* @require types::is_intlike($typeof(i)) `The input must be an integer or integer vector`
**/
@@ -11,163 +10,162 @@ macro reverse(i) => $$bitreverse(i);
**/
macro bswap(i) @builtin => $$bswap(i);
macro uint[<*>].popcount(self) => $$popcount(self);
macro uint[<*>].ctz(self) => $$ctz(self);
macro uint[<*>].clz(self) => $$clz(self);
macro uint[<*>] uint[<*>].fshl(hi, uint[<*>] lo, uint[<*>] shift) => $$fshl(hi, lo, shift);
macro uint[<*>] uint[<*>].fshr(hi, uint[<*>] lo, uint[<*>] shift) => $$fshr(hi, lo, shift);
macro uint[<*>] uint[<*>].rotl(self, uint[<*>] shift) => $$fshl(self, self, shift);
macro uint[<*>] uint[<*>].rotr(self, uint[<*>] shift) => $$fshr(self, self, shift);
macro uint[<*>].popcount(uint[<*>] i) => $$popcount(i);
macro uint[<*>].ctz(uint[<*>] i) => $$ctz(i);
macro uint[<*>].clz(uint[<*>] i) => $$clz(i);
macro uint[<*>] uint[<*>].fshl(uint[<*>] hi, uint[<*>] lo, uint[<*>] shift) => $$fshl(hi, lo, shift);
macro uint[<*>] uint[<*>].fshr(uint[<*>] hi, uint[<*>] lo, uint[<*>] shift) => $$fshr(hi, lo, shift);
macro uint[<*>] uint[<*>].rotl(uint[<*>] i, uint[<*>] shift) => $$fshl(i, i, shift);
macro uint[<*>] uint[<*>].rotr(uint[<*>] i, uint[<*>] shift) => $$fshr(i, i, shift);
macro int[<*>].popcount(self) => $$popcount(self);
macro int[<*>].ctz(self) => $$ctz(self);
macro int[<*>].clz(self) => $$clz(self);
macro int[<*>] int[<*>].fshl(hi, int[<*>] lo, int[<*>] shift) => $$fshl(hi, lo, shift);
macro int[<*>] int[<*>].fshr(hi, int[<*>] lo, int[<*>] shift) => $$fshr(hi, lo, shift);
macro int[<*>] int[<*>].rotl(self, int[<*>] shift) => $$fshl(self, self, shift);
macro int[<*>] int[<*>].rotr(self, int[<*>] shift) => $$fshr(self, self, shift);
macro int[<*>].popcount(int[<*>] i) => $$popcount(i);
macro int[<*>].ctz(int[<*>] i) => $$ctz(i);
macro int[<*>].clz(int[<*>] i) => $$clz(i);
macro int[<*>] int[<*>].fshl(int[<*>] hi, int[<*>] lo, int[<*>] shift) => $$fshl(hi, lo, shift);
macro int[<*>] int[<*>].fshr(int[<*>] hi, int[<*>] lo, int[<*>] shift) => $$fshr(hi, lo, shift);
macro int[<*>] int[<*>].rotl(int[<*>] i, int[<*>] shift) => $$fshl(i, i, shift);
macro int[<*>] int[<*>].rotr(int[<*>] i, int[<*>] shift) => $$fshr(i, i, shift);
macro ushort[<*>].popcount(self) => $$popcount(self);
macro ushort[<*>].ctz(self) => $$ctz(self);
macro ushort[<*>].clz(self) => $$clz(self);
macro ushort[<*>] ushort[<*>].fshl(hi, ushort[<*>] lo, ushort[<*>] shift) => $$fshl(hi, lo, shift);
macro ushort[<*>] ushort[<*>].fshr(hi, ushort[<*>] lo, ushort[<*>] shift) => $$fshr(hi, lo, shift);
macro ushort[<*>] ushort[<*>].rotl(self, ushort[<*>] shift) => $$fshl(self, self, shift);
macro ushort[<*>] ushort[<*>].rotr(self, ushort[<*>] shift) => $$fshr(self, self, shift);
macro ushort[<*>].popcount(ushort[<*>] i) => $$popcount(i);
macro ushort[<*>].ctz(ushort[<*>] i) => $$ctz(i);
macro ushort[<*>].clz(ushort[<*>] i) => $$clz(i);
macro ushort[<*>] ushort[<*>].fshl(ushort[<*>] hi, ushort[<*>] lo, ushort[<*>] shift) => $$fshl(hi, lo, shift);
macro ushort[<*>] ushort[<*>].fshr(ushort[<*>] hi, ushort[<*>] lo, ushort[<*>] shift) => $$fshr(hi, lo, shift);
macro ushort[<*>] ushort[<*>].rotl(ushort[<*>] i, ushort[<*>] shift) => $$fshl(i, i, shift);
macro ushort[<*>] ushort[<*>].rotr(ushort[<*>] i, ushort[<*>] shift) => $$fshr(i, i, shift);
macro short[<*>].popcount(self) => $$popcount(self);
macro short[<*>].ctz(self) => $$ctz(self);
macro short[<*>].clz(self) => $$clz(self);
macro short[<*>] short[<*>].fshl(hi, short[<*>] lo, short[<*>] shift) => $$fshl(hi, lo, shift);
macro short[<*>] short[<*>].fshr(hi, short[<*>] lo, short[<*>] shift) => $$fshr(hi, lo, shift);
macro short[<*>] short[<*>].rotl(self, short[<*>] shift) => $$fshl(self, self, shift);
macro short[<*>] short[<*>].rotr(self, short[<*>] shift) => $$fshr(self, self, shift);
macro short[<*>].popcount(short[<*>] i) => $$popcount(i);
macro short[<*>].ctz(short[<*>] i) => $$ctz(i);
macro short[<*>].clz(short[<*>] i) => $$clz(i);
macro short[<*>] short[<*>].fshl(short[<*>] hi, short[<*>] lo, short[<*>] shift) => $$fshl(hi, lo, shift);
macro short[<*>] short[<*>].fshr(short[<*>] hi, short[<*>] lo, short[<*>] shift) => $$fshr(hi, lo, shift);
macro short[<*>] short[<*>].rotl(short[<*>] i, short[<*>] shift) => $$fshl(i, i, shift);
macro short[<*>] short[<*>].rotr(short[<*>] i, short[<*>] shift) => $$fshr(i, i, shift);
macro char[<*>].popcount(self) => $$popcount(self);
macro char[<*>].ctz(self) => $$ctz(self);
macro char[<*>].clz(self) => $$clz(self);
macro char[<*>] char[<*>].fshl(hi, char[<*>] lo, char[<*>] shift) => $$fshl(hi, lo, shift);
macro char[<*>] char[<*>].fshr(hi, char[<*>] lo, char[<*>] shift) => $$fshr(hi, lo, shift);
macro char[<*>] char[<*>].rotl(self, char[<*>] shift) => $$fshl(self, self, shift);
macro char[<*>] char[<*>].rotr(self, char[<*>] shift) => $$fshr(self, self, shift);
macro char[<*>].popcount(char[<*>] i) => $$popcount(i);
macro char[<*>].ctz(char[<*>] i) => $$ctz(i);
macro char[<*>].clz(char[<*>] i) => $$clz(i);
macro char[<*>] char[<*>].fshl(char[<*>] hi, char[<*>] lo, char[<*>] shift) => $$fshl(hi, lo, shift);
macro char[<*>] char[<*>].fshr(char[<*>] hi, char[<*>] lo, char[<*>] shift) => $$fshr(hi, lo, shift);
macro char[<*>] char[<*>].rotl(char[<*>] i, char[<*>] shift) => $$fshl(i, i, shift);
macro char[<*>] char[<*>].rotr(char[<*>] i, char[<*>] shift) => $$fshr(i, i, shift);
macro ichar[<*>].popcount(self) => $$popcount(self);
macro ichar[<*>].ctz(self) => $$ctz(self);
macro ichar[<*>].clz(self) => $$clz(self);
macro ichar[<*>] ichar[<*>].fshl(hi, ichar[<*>] lo, ichar[<*>] shift) => $$fshl(hi, lo, shift);
macro ichar[<*>] ichar[<*>].fshr(hi, ichar[<*>] lo, ichar[<*>] shift) => $$fshr(hi, lo, shift);
macro ichar[<*>] ichar[<*>].rotl(self, ichar[<*>] shift) => $$fshl(self, self, shift);
macro ichar[<*>] ichar[<*>].rotr(self, ichar[<*>] shift) => $$fshr(self, self, shift);
macro ichar[<*>].popcount(ichar[<*>] i) => $$popcount(i);
macro ichar[<*>].ctz(ichar[<*>] i) => $$ctz(i);
macro ichar[<*>].clz(ichar[<*>] i) => $$clz(i);
macro ichar[<*>] ichar[<*>].fshl(ichar[<*>] hi, ichar[<*>] lo, ichar[<*>] shift) => $$fshl(hi, lo, shift);
macro ichar[<*>] ichar[<*>].fshr(ichar[<*>] hi, ichar[<*>] lo, ichar[<*>] shift) => $$fshr(hi, lo, shift);
macro ichar[<*>] ichar[<*>].rotl(ichar[<*>] i, ichar[<*>] shift) => $$fshl(i, i, shift);
macro ichar[<*>] ichar[<*>].rotr(ichar[<*>] i, ichar[<*>] shift) => $$fshr(i, i, shift);
macro ulong[<*>].popcount(self) => $$popcount(self);
macro ulong[<*>].ctz(self) => $$ctz(self);
macro ulong[<*>].clz(self) => $$clz(self);
macro ulong[<*>] ulong[<*>].fshl(hi, ulong[<*>] lo, ulong[<*>] shift) => $$fshl(hi, lo, shift);
macro ulong[<*>] ulong[<*>].fshr(hi, ulong[<*>] lo, ulong[<*>] shift) => $$fshr(hi, lo, shift);
macro ulong[<*>] ulong[<*>].rotl(self, ulong[<*>] shift) => $$fshl(self, self, shift);
macro ulong[<*>] ulong[<*>].rotr(self, ulong[<*>] shift) => $$fshr(self, self, shift);
macro ulong[<*>].popcount(ulong[<*>] i) => $$popcount(i);
macro ulong[<*>].ctz(ulong[<*>] i) => $$ctz(i);
macro ulong[<*>].clz(ulong[<*>] i) => $$clz(i);
macro ulong[<*>] ulong[<*>].fshl(ulong[<*>] hi, ulong[<*>] lo, ulong[<*>] shift) => $$fshl(hi, lo, shift);
macro ulong[<*>] ulong[<*>].fshr(ulong[<*>] hi, ulong[<*>] lo, ulong[<*>] shift) => $$fshr(hi, lo, shift);
macro ulong[<*>] ulong[<*>].rotl(ulong[<*>] i, ulong[<*>] shift) => $$fshl(i, i, shift);
macro ulong[<*>] ulong[<*>].rotr(ulong[<*>] i, ulong[<*>] shift) => $$fshr(i, i, shift);
macro long[<*>].popcount(self) => $$popcount(self);
macro long[<*>].ctz(self) => $$ctz(self);
macro long[<*>].clz(self) => $$clz(self);
macro long[<*>] long[<*>].fshl(hi, long[<*>] lo, long[<*>] shift) => $$fshl(hi, lo, shift);
macro long[<*>] long[<*>].fshr(hi, long[<*>] lo, long[<*>] shift) => $$fshr(hi, lo, shift);
macro long[<*>] long[<*>].rotl(self, long[<*>] shift) => $$fshl(self, self, shift);
macro long[<*>] long[<*>].rotr(self, long[<*>] shift) => $$fshr(self, self, shift);
macro long[<*>].popcount(long[<*>] i) => $$popcount(i);
macro long[<*>].ctz(long[<*>] i) => $$ctz(i);
macro long[<*>].clz(long[<*>] i) => $$clz(i);
macro long[<*>] long[<*>].fshl(long[<*>] hi, long[<*>] lo, long[<*>] shift) => $$fshl(hi, lo, shift);
macro long[<*>] long[<*>].fshr(long[<*>] hi, long[<*>] lo, long[<*>] shift) => $$fshr(hi, lo, shift);
macro long[<*>] long[<*>].rotl(long[<*>] i, long[<*>] shift) => $$fshl(i, i, shift);
macro long[<*>] long[<*>].rotr(long[<*>] i, long[<*>] shift) => $$fshr(i, i, shift);
macro uint128[<*>].popcount(self) => $$popcount(self);
macro uint128[<*>].ctz(self) => $$ctz(self);
macro uint128[<*>].clz(self) => $$clz(self);
macro uint128[<*>] uint128[<*>].fshl(hi, uint128[<*>] lo, uint128[<*>] shift) => $$fshl(hi, lo, shift);
macro uint128[<*>] uint128[<*>].fshr(hi, uint128[<*>] lo, uint128[<*>] shift) => $$fshr(hi, lo, shift);
macro uint128[<*>] uint128[<*>].rotl(self, uint128[<*>] shift) => $$fshl(self, self, shift);
macro uint128[<*>] uint128[<*>].rotr(self, uint128[<*>] shift) => $$fshr(self, self, shift);
macro uint128[<*>].popcount(uint128[<*>] i) => $$popcount(i);
macro uint128[<*>].ctz(uint128[<*>] i) => $$ctz(i);
macro uint128[<*>].clz(uint128[<*>] i) => $$clz(i);
macro uint128[<*>] uint128[<*>].fshl(uint128[<*>] hi, uint128[<*>] lo, uint128[<*>] shift) => $$fshl(hi, lo, shift);
macro uint128[<*>] uint128[<*>].fshr(uint128[<*>] hi, uint128[<*>] lo, uint128[<*>] shift) => $$fshr(hi, lo, shift);
macro uint128[<*>] uint128[<*>].rotl(uint128[<*>] i, uint128[<*>] shift) => $$fshl(i, i, shift);
macro uint128[<*>] uint128[<*>].rotr(uint128[<*>] i, uint128[<*>] shift) => $$fshr(i, i, shift);
macro int128[<*>].popcount(self) => $$popcount(self);
macro int128[<*>].ctz(self) => $$ctz(self);
macro int128[<*>].clz(self) => $$clz(self);
macro int128[<*>] int128[<*>].fshl(hi, int128[<*>] lo, int128[<*>] shift) => $$fshl(hi, lo, shift);
macro int128[<*>] int128[<*>].fshr(hi, int128[<*>] lo, int128[<*>] shift) => $$fshr(hi, lo, shift);
macro int128[<*>] int128[<*>].rotl(self, int128[<*>] shift) => $$fshl(self, self, shift);
macro int128[<*>] int128[<*>].rotr(self, int128[<*>] shift) => $$fshr(self, self, shift);
macro int128[<*>].popcount(int128[<*>] i) => $$popcount(i);
macro int128[<*>].ctz(int128[<*>] i) => $$ctz(i);
macro int128[<*>].clz(int128[<*>] i) => $$clz(i);
macro int128[<*>] int128[<*>].fshl(int128[<*>] hi, int128[<*>] lo, int128[<*>] shift) => $$fshl(hi, lo, shift);
macro int128[<*>] int128[<*>].fshr(int128[<*>] hi, int128[<*>] lo, int128[<*>] shift) => $$fshr(hi, lo, shift);
macro int128[<*>] int128[<*>].rotl(int128[<*>] i, int128[<*>] shift) => $$fshl(i, i, shift);
macro int128[<*>] int128[<*>].rotr(int128[<*>] i, int128[<*>] shift) => $$fshr(i, i, shift);
macro uint.popcount(self) => $$popcount(self);
macro uint.ctz(self) => $$ctz(self);
macro uint.clz(self) => $$clz(self);
macro uint uint.fshl(hi, uint lo, uint shift) => $$fshl(hi, lo, shift);
macro uint uint.fshr(hi, uint lo, uint shift) => $$fshr(hi, lo, shift);
macro uint uint.rotl(self, uint shift) => $$fshl(self, self, shift);
macro uint uint.rotr(self, uint shift) => $$fshr(self, self, shift);
macro uint.popcount(uint i) => $$popcount(i);
macro uint.ctz(uint i) => $$ctz(i);
macro uint.clz(uint i) => $$clz(i);
macro uint uint.fshl(uint hi, uint lo, uint shift) => $$fshl(hi, lo, shift);
macro uint uint.fshr(uint hi, uint lo, uint shift) => $$fshr(hi, lo, shift);
macro uint uint.rotl(uint i, uint shift) => $$fshl(i, i, shift);
macro uint uint.rotr(uint i, uint shift) => $$fshr(i, i, shift);
macro int.popcount(self) => $$popcount(self);
macro int.ctz(self) => $$ctz(self);
macro int.clz(self) => $$clz(self);
macro int int.fshl(hi, int lo, int shift) => $$fshl(hi, lo, shift);
macro int int.fshr(hi, int lo, int shift) => $$fshr(hi, lo, shift);
macro int int.rotl(self, int shift) => $$fshl(self, self, shift);
macro int int.rotr(self, int shift) => $$fshr(self, self, shift);
macro int.popcount(int i) => $$popcount(i);
macro int.ctz(int i) => $$ctz(i);
macro int.clz(int i) => $$clz(i);
macro int int.fshl(int hi, int lo, int shift) => $$fshl(hi, lo, shift);
macro int int.fshr(int hi, int lo, int shift) => $$fshr(hi, lo, shift);
macro int int.rotl(int i, int shift) => $$fshl(i, i, shift);
macro int int.rotr(int i, int shift) => $$fshr(i, i, shift);
macro ushort.popcount(self) => $$popcount(self);
macro ushort.ctz(self) => $$ctz(self);
macro ushort.clz(self) => $$clz(self);
macro ushort ushort.fshl(hi, ushort lo, ushort shift) => $$fshl(hi, lo, shift);
macro ushort ushort.fshr(hi, ushort lo, ushort shift) => $$fshr(hi, lo, shift);
macro ushort ushort.rotl(self, ushort shift) => $$fshl(self, self, shift);
macro ushort ushort.rotr(self, ushort shift) => $$fshr(self, self, shift);
macro ushort.popcount(ushort i) => $$popcount(i);
macro ushort.ctz(ushort i) => $$ctz(i);
macro ushort.clz(ushort i) => $$clz(i);
macro ushort ushort.fshl(ushort hi, ushort lo, ushort shift) => $$fshl(hi, lo, shift);
macro ushort ushort.fshr(ushort hi, ushort lo, ushort shift) => $$fshr(hi, lo, shift);
macro ushort ushort.rotl(ushort i, ushort shift) => $$fshl(i, i, shift);
macro ushort ushort.rotr(ushort i, ushort shift) => $$fshr(i, i, shift);
macro short.popcount(self) => $$popcount(self);
macro short.ctz(self) => $$ctz(self);
macro short.clz(self) => $$clz(self);
macro short short.fshl(hi, short lo, short shift) => $$fshl(hi, lo, shift);
macro short short.fshr(hi, short lo, short shift) => $$fshr(hi, lo, shift);
macro short short.rotl(self, short shift) => $$fshl(self, self, shift);
macro short short.rotr(self, short shift) => $$fshr(self, self, shift);
macro short.popcount(short i) => $$popcount(i);
macro short.ctz(short i) => $$ctz(i);
macro short.clz(short i) => $$clz(i);
macro short short.fshl(short hi, short lo, short shift) => $$fshl(hi, lo, shift);
macro short short.fshr(short hi, short lo, short shift) => $$fshr(hi, lo, shift);
macro short short.rotl(short i, short shift) => $$fshl(i, i, shift);
macro short short.rotr(short i, short shift) => $$fshr(i, i, shift);
macro char.popcount(self) => $$popcount(self);
macro char.ctz(self) => $$ctz(self);
macro char.clz(self) => $$clz(self);
macro char char.fshl(hi, char lo, char shift) => $$fshl(hi, lo, shift);
macro char char.fshr(hi, char lo, char shift) => $$fshr(hi, lo, shift);
macro char char.rotl(self, char shift) => $$fshl(self, self, shift);
macro char char.rotr(self, char shift) => $$fshr(self, self, shift);
macro char.popcount(char i) => $$popcount(i);
macro char.ctz(char i) => $$ctz(i);
macro char.clz(char i) => $$clz(i);
macro char char.fshl(char hi, char lo, char shift) => $$fshl(hi, lo, shift);
macro char char.fshr(char hi, char lo, char shift) => $$fshr(hi, lo, shift);
macro char char.rotl(char i, char shift) => $$fshl(i, i, shift);
macro char char.rotr(char i, char shift) => $$fshr(i, i, shift);
macro ichar.popcount(self) => $$popcount(self);
macro ichar.ctz(self) => $$ctz(self);
macro ichar.clz(self) => $$clz(self);
macro ichar ichar.fshl(hi, ichar lo, ichar shift) => $$fshl(hi, lo, shift);
macro ichar ichar.fshr(hi, ichar lo, ichar shift) => $$fshr(hi, lo, shift);
macro ichar ichar.rotl(self, ichar shift) => $$fshl(self, self, shift);
macro ichar ichar.rotr(self, ichar shift) => $$fshr(self, self, shift);
macro ichar.popcount(ichar i) => $$popcount(i);
macro ichar.ctz(ichar i) => $$ctz(i);
macro ichar.clz(ichar i) => $$clz(i);
macro ichar ichar.fshl(ichar hi, ichar lo, ichar shift) => $$fshl(hi, lo, shift);
macro ichar ichar.fshr(ichar hi, ichar lo, ichar shift) => $$fshr(hi, lo, shift);
macro ichar ichar.rotl(ichar i, ichar shift) => $$fshl(i, i, shift);
macro ichar ichar.rotr(ichar i, ichar shift) => $$fshr(i, i, shift);
macro ulong.popcount(self) => $$popcount(self);
macro ulong.ctz(self) => $$ctz(self);
macro ulong.clz(self) => $$clz(self);
macro ulong ulong.fshl(hi, ulong lo, ulong shift) => $$fshl(hi, lo, shift);
macro ulong ulong.fshr(hi, ulong lo, ulong shift) => $$fshr(hi, lo, shift);
macro ulong ulong.rotl(self, ulong shift) => $$fshl(self, self, shift);
macro ulong ulong.rotr(self, ulong shift) => $$fshr(self, self, shift);
macro ulong.popcount(ulong i) => $$popcount(i);
macro ulong.ctz(ulong i) => $$ctz(i);
macro ulong.clz(ulong i) => $$clz(i);
macro ulong ulong.fshl(ulong hi, ulong lo, ulong shift) => $$fshl(hi, lo, shift);
macro ulong ulong.fshr(ulong hi, ulong lo, ulong shift) => $$fshr(hi, lo, shift);
macro ulong ulong.rotl(ulong i, ulong shift) => $$fshl(i, i, shift);
macro ulong ulong.rotr(ulong i, ulong shift) => $$fshr(i, i, shift);
macro long.popcount(self) => $$popcount(self);
macro long.ctz(self) => $$ctz(self);
macro long.clz(self) => $$clz(self);
macro long long.fshl(hi, long lo, long shift) => $$fshl(hi, lo, shift);
macro long long.fshr(hi, long lo, long shift) => $$fshr(hi, lo, shift);
macro long long.rotl(self, long shift) => $$fshl(self, self, shift);
macro long long.rotr(self, long shift) => $$fshr(self, self, shift);
macro long.popcount(long i) => $$popcount(i);
macro long.ctz(long i) => $$ctz(i);
macro long.clz(long i) => $$clz(i);
macro long long.fshl(long hi, long lo, long shift) => $$fshl(hi, lo, shift);
macro long long.fshr(long hi, long lo, long shift) => $$fshr(hi, lo, shift);
macro long long.rotl(long i, long shift) => $$fshl(i, i, shift);
macro long long.rotr(long i, long shift) => $$fshr(i, i, shift);
macro uint128.popcount(self) => $$popcount(self);
macro uint128.ctz(self) => $$ctz(self);
macro uint128.clz(self) => $$clz(self);
macro uint128 uint128.fshl(hi, uint128 lo, uint128 shift) => $$fshl(hi, lo, shift);
macro uint128 uint128.fshr(hi, uint128 lo, uint128 shift) => $$fshr(hi, lo, shift);
macro uint128 uint128.rotl(self, uint128 shift) => $$fshl(self, self, shift);
macro uint128 uint128.rotr(self, uint128 shift) => $$fshr(self, self, shift);
macro uint128.popcount(uint128 i) => $$popcount(i);
macro uint128.ctz(uint128 i) => $$ctz(i);
macro uint128.clz(uint128 i) => $$clz(i);
macro uint128 uint128.fshl(uint128 hi, uint128 lo, uint128 shift) => $$fshl(hi, lo, shift);
macro uint128 uint128.fshr(uint128 hi, uint128 lo, uint128 shift) => $$fshr(hi, lo, shift);
macro uint128 uint128.rotl(uint128 i, uint128 shift) => $$fshl(i, i, shift);
macro uint128 uint128.rotr(uint128 i, uint128 shift) => $$fshr(i, i, shift);
macro int128.popcount(int128 i) => $$popcount(i);
macro int128.ctz(int128 i) => $$ctz(i);
macro int128.clz(int128 i) => $$clz(i);
macro int128 int128.fshl(int128 hi, int128 lo, int128 shift) => $$fshl(hi, lo, shift);
macro int128 int128.fshr(int128 hi, int128 lo, int128 shift) => $$fshr(hi, lo, shift);
macro int128 int128.rotl(int128 i, int128 shift) => $$fshl(i, i, shift);
macro int128 int128.rotr(int128 i, int128 shift) => $$fshr(i, i, shift);
macro int128.popcount(self) => $$popcount(self);
macro int128.ctz(self) => $$ctz(self);
macro int128.clz(self) => $$clz(self);
macro int128 int128.fshl(hi, int128 lo, int128 shift) => $$fshl(hi, lo, shift);
macro int128 int128.fshr(hi, int128 lo, int128 shift) => $$fshr(hi, lo, shift);
macro int128 int128.rotl(self, int128 shift) => $$fshl(self, self, shift);
macro int128 int128.rotr(self, int128 shift) => $$fshr(self, self, shift);

View File

@@ -0,0 +1,160 @@
/**
* @require SIZE > 0
**/
module std::collections::bitset(<SIZE>);
def Type = uint;
const BITS = Type.sizeof * 8;
const SZ = (SIZE + BITS - 1) / BITS;
struct BitSet
{
Type[SZ] data;
}
fn usz BitSet.cardinality(&self)
{
usz n;
foreach (x : self.data)
{
n += x.popcount();
}
return n;
}
/**
* @require i < SIZE
**/
fn void BitSet.set(&self, usz i)
{
usz q = i / BITS;
usz r = i % BITS;
self.data[q] |= 1 << r;
}
/**
* @require i < SIZE
**/
fn void BitSet.unset(&self, usz i)
{
usz q = i / BITS;
usz r = i % BITS;
self.data[q] &= ~(1 << r);
}
/**
* @require i < SIZE
**/
fn bool BitSet.get(&self, usz i) @operator([]) @inline
{
usz q = i / BITS;
usz r = i % BITS;
return self.data[q] & (1 << r) != 0;
}
fn usz BitSet.len(&self) @operator(len) @inline
{
return SZ * BITS;
}
/**
* @require i < SIZE
**/
fn void BitSet.set_bool(&self, usz i, bool value) @operator([]=) @inline
{
if (value) return self.set(i);
self.unset(i);
}
/**
* @require Type.kindof == UNSIGNED_INT
**/
module std::collections::growablebitset(<Type>);
import std::collections::list;
const BITS = Type.sizeof * 8;
def GrowableBitSetList = List(<Type>);
struct GrowableBitSet
{
GrowableBitSetList data;
}
/**
* @param initial_capacity
* @param [&inout] allocator "The allocator to use, defaults to the heap allocator"
**/
fn GrowableBitSet* GrowableBitSet.init_new(&self, usz initial_capacity = 1, Allocator* allocator = mem::heap())
{
self.data.init_new(initial_capacity, allocator);
return self;
}
fn GrowableBitSet* GrowableBitSet.init_temp(&self, usz initial_capacity = 1)
{
return self.init_new(initial_capacity, mem::temp()) @inline;
}
fn void GrowableBitSet.free(&self)
{
self.data.free();
}
fn usz GrowableBitSet.cardinality(&self)
{
usz n;
foreach (x : self.data)
{
n += x.popcount();
}
return n;
}
fn void GrowableBitSet.set(&self, usz i)
{
usz q = i / BITS;
usz r = i % BITS;
usz current_len = self.data.len();
if (q >= current_len)
{
usz n = q + 1;
self.data.reserve(n);
if (n - 1 >= current_len)
{
self.data.entries[current_len .. (n - 1)] = 0;
}
self.data.size = n;
}
self.data.set(q, self.data[q] | (1 << r));
}
fn void GrowableBitSet.unset(&self, usz i)
{
usz q = i / BITS;
usz r = i % BITS;
if (q >= self.data.len()) return;
self.data.set(q, self.data[q] &~ (1 << r));
}
fn bool GrowableBitSet.get(&self, usz i) @operator([]) @inline
{
usz q = i / BITS;
usz r = i % BITS;
if (q >= self.data.len()) return false;
return self.data[q] & (1 << r) != 0;
}
fn usz GrowableBitSet.len(&self) @operator(len)
{
usz n = self.data.len() * BITS;
if (n > 0) n -= (usz)self.data[^1].clz();
return n;
}
fn void GrowableBitSet.set_bool(&self, usz i, bool value) @operator([]=) @inline
{
if (value) return self.set(i);
self.unset(i);
}

View File

@@ -1,18 +1,63 @@
module std::collections::enummap<Enum, ValueType>;
module std::collections::enummap(<Enum, ValueType>);
struct EnumMap
struct EnumMap (Printable)
{
ValueType[Enum.len] values;
ValueType[Enum.len] values;
}
fn void EnumMap.init(EnumMap* this, ValueType init_value)
fn void EnumMap.init(&self, ValueType init_value)
{
foreach(&a : this.values)
{
*a = init_value;
}
foreach (&a : self.values)
{
*a = init_value;
}
}
fn uint EnumMap.len(EnumMap* this) @operator(len) => this.values.len;
fn ValueType EnumMap.get(EnumMap* this, Enum key) @operator([]) => this.values[key.ordinal];
fn void EnumMap.set(EnumMap* this, Enum key, ValueType value) @operator([]=) => this.values[key.ordinal] = value;
fn usz! EnumMap.to_format(&self, Formatter* formatter) @dynamic
{
usz n = formatter.print("{ ")!;
foreach (i, &value : self.values)
{
if (i != 0) formatter.print(", ")!;
n += formatter.printf("%s: %s", (Enum)i, *value)!;
}
n += formatter.print(" }")!;
return n;
}
fn String EnumMap.to_new_string(&self, Allocator* allocator = mem::heap()) @dynamic
{
return string::new_format("%s", *self, .allocator = allocator);
}
fn String EnumMap.to_tstring(&self) @dynamic
{
return string::tformat("%s", *self);
}
/**
* @return "The total size of this map, which is the same as the number of enum values"
* @pure
**/
fn usz EnumMap.len(&self) @operator(len) @inline
{
return self.values.len;
}
/**
* @return "Retrieve a value given the underlying enum, if there is no entry, then the zero value for the value is returned."
**/
fn ValueType EnumMap.get(&self, Enum key) @operator([]) @inline
{
return self.values[key.ordinal];
}
fn ValueType* EnumMap.get_ref(&self, Enum key) @operator(&[]) @inline
{
return &self.values[key.ordinal];
}
fn void EnumMap.set(&self, Enum key, ValueType value) @operator([]=) @inline
{
self.values[key.ordinal] = value;
}

View File

@@ -1,145 +1,171 @@
// Copyright (c) 2021 Christoffer Lerno. All rights reserved.
// Use of this source code is governed by the MIT license
// Use of self source code is governed by the MIT license
// a copy of which can be found in the LICENSE_STDLIB file.
/**
* @require Enum.kindof == TypeKind.ENUM : "Only enums maybe be used with an enumset"
**/
module std::collections::enumset<Enum>;
module std::collections::enumset(<Enum>);
def EnumSetType = $typefrom(private::type_for_enum_elements(Enum.elements)) @private ;
$switch
$case (Enum.elements > 128):
typedef EnumSetType @private = char[(Enum.elements + 7) / 8];
const IS_CHAR_ARRAY = true;
$case (Enum.elements > 64):
typedef EnumSetType @private = uint128;
const IS_CHAR_ARRAY = false;
$case (Enum.elements > 32 || $$C_INT_SIZE > 32):
typedef EnumSetType @private = ulong;
const IS_CHAR_ARRAY = false;
$case (Enum.elements > 16 || $$C_INT_SIZE > 16):
typedef EnumSetType @private = uint;
const IS_CHAR_ARRAY = false;
$case (Enum.elements > 8 || $$C_INT_SIZE > 8):
typedef EnumSetType @private = ushort;
const IS_CHAR_ARRAY = false;
$default:
typedef EnumSetType @private = char;
const IS_CHAR_ARRAY = false;
$endswitch
const IS_CHAR_ARRAY = Enum.elements > 128;
distinct EnumSet (Printable) = EnumSetType;
typedef EnumSet = distinct EnumSetType;
fn void EnumSet.add(EnumSet* this, Enum v)
fn void EnumSet.add(&self, Enum v)
{
$if (IS_CHAR_ARRAY)
(*this)[v / 8] |= (char)(1u << (v % 8));
$else
*this = (EnumSet)((EnumSetType)*this | 1u << (EnumSetType)v);
$endif
$if IS_CHAR_ARRAY:
(*self)[(usz)v / 8] |= (char)(1u << ((usz)v % 8));
$else
*self = (EnumSet)((EnumSetType)*self | 1u << (EnumSetType)v);
$endif
}
fn void EnumSet.clear(EnumSet* this)
fn void EnumSet.clear(&self)
{
$if (IS_CHAR_ARRAY)
*this = {};
$else
*this = 0;
$endif
$if IS_CHAR_ARRAY:
*self = {};
$else
*self = 0;
$endif
}
fn bool EnumSet.remove(EnumSet* this, Enum v)
fn bool EnumSet.remove(&self, Enum v)
{
$if (IS_CHAR_ARRAY)
if (!this.has(v) @inline) return false;
(*this)[v / 8] &= (char)~(1 << (v % 8));
return true;
$else
EnumSetType old = (EnumSetType)*this;
EnumSetType new = old & ~(1u << (EnumSetType)v);
*this = (EnumSet)new;
return old != new;
$endif
$if IS_CHAR_ARRAY:
if (!self.has(v) @inline) return false;
(*self)[(usz)v / 8] &= (char)~(1u << ((usz)v % 8));
return true;
$else
EnumSetType old = (EnumSetType)*self;
EnumSetType new = old & ~(1u << (EnumSetType)v);
*self = (EnumSet)new;
return old != new;
$endif
}
fn bool EnumSet.has(EnumSet* this, Enum v)
fn bool EnumSet.has(&self, Enum v)
{
$if (IS_CHAR_ARRAY)
return (bool)(((*this)[v / 8] << (v % 8)) & 0x01);
$else
return ((EnumSetType)*this & (1u << (EnumSetType)v)) != 0;
$endif
$if IS_CHAR_ARRAY:
return (bool)(((*self)[(usz)v / 8] << ((usz)v % 8)) & 0x01);
$else
return ((EnumSetType)*self & (1u << (EnumSetType)v)) != 0;
$endif
}
fn void EnumSet.add_all(EnumSet* this, EnumSet s)
fn void EnumSet.add_all(&self, EnumSet s)
{
$if (IS_CHAR_ARRAY)
foreach (i, c : s) (*this)[i] |= c;
$else
*this = (EnumSet)((EnumSetType)*this | (EnumSetType)s);
$endif
$if IS_CHAR_ARRAY:
foreach (i, c : s) (*self)[i] |= c;
$else
*self = (EnumSet)((EnumSetType)*self | (EnumSetType)s);
$endif
}
fn void EnumSet.retain_all(EnumSet* this, EnumSet s)
fn void EnumSet.retain_all(&self, EnumSet s)
{
$if (IS_CHAR_ARRAY)
foreach (i, c : s) (*this)[i] &= c;
$else
*this = (EnumSet)((EnumSetType)*this & (EnumSetType)s);
$endif
$if IS_CHAR_ARRAY:
foreach (i, c : s) (*self)[i] &= c;
$else
*self = (EnumSet)((EnumSetType)*self & (EnumSetType)s);
$endif
}
fn void EnumSet.remove_all(EnumSet* this, EnumSet s)
fn void EnumSet.remove_all(&self, EnumSet s)
{
$if (IS_CHAR_ARRAY)
foreach (i, c : s) (*this)[i] &= ~c;
$else
*this = (EnumSet)((EnumSetType)*this & ~(EnumSetType)s);
$endif
$if IS_CHAR_ARRAY:
foreach (i, c : s) (*self)[i] &= ~c;
$else
*self = (EnumSet)((EnumSetType)*self & ~(EnumSetType)s);
$endif
}
fn EnumSet EnumSet.and_of(EnumSet* this, EnumSet s)
fn EnumSet EnumSet.and_of(&self, EnumSet s)
{
$if (IS_CHAR_ARRAY)
EnumSet copy = *this;
copy.retain_all(s);
return copy;
$else
return (EnumSet)((EnumSetType)*this & (EnumSetType)s);
$endif
$if IS_CHAR_ARRAY:
EnumSet copy = *self;
copy.retain_all(s);
return copy;
$else
return (EnumSet)((EnumSetType)*self & (EnumSetType)s);
$endif
}
fn EnumSet EnumSet.or_of(EnumSet* this, EnumSet s)
fn EnumSet EnumSet.or_of(&self, EnumSet s)
{
$if (IS_CHAR_ARRAY)
EnumSet copy = *this;
copy.add_all(s);
return copy;
$else
return (EnumSet)((EnumSetType)*this | (EnumSetType)s);
$endif
$if IS_CHAR_ARRAY:
EnumSet copy = *self;
copy.add_all(s);
return copy;
$else
return (EnumSet)((EnumSetType)*self | (EnumSetType)s);
$endif
}
fn EnumSet EnumSet.diff_of(EnumSet* this, EnumSet s)
fn EnumSet EnumSet.diff_of(&self, EnumSet s)
{
$if (IS_CHAR_ARRAY)
EnumSet copy = *this;
copy.remove_all(s);
return copy;
$else
return (EnumSet)((EnumSetType)*this & ~(EnumSetType)s);
$endif
$if IS_CHAR_ARRAY:
EnumSet copy = *self;
copy.remove_all(s);
return copy;
$else
return (EnumSet)((EnumSetType)*self & ~(EnumSetType)s);
$endif
}
fn EnumSet EnumSet.xor_of(EnumSet* this, EnumSet s)
fn EnumSet EnumSet.xor_of(&self, EnumSet s)
{
$if (IS_CHAR_ARRAY)
EnumSet copy = *this;
foreach (i, c : s) copy[i] ^= c;
return copy;
$else
return (EnumSet)((EnumSetType)*this ^ (EnumSetType)s);
$endif
$if IS_CHAR_ARRAY:
EnumSet copy = *self;
foreach (i, c : s) copy[i] ^= c;
return copy;
$else
return (EnumSet)((EnumSetType)*self ^ (EnumSetType)s);
$endif
}
fn usz! EnumSet.to_format(&set, Formatter* formatter) @dynamic
{
usz n = formatter.print("[")!;
bool found;
foreach (value : Enum.values)
{
if (!set.has(value)) continue;
if (found) n += formatter.print(", ")!;
found = true;
n += formatter.printf("%s", value)!;
}
n += formatter.print("]")!;
return n;
}
fn String EnumSet.to_new_string(&set, Allocator* allocator = mem::heap()) @dynamic
{
return string::new_format("%s", *set, .allocator = allocator);
}
fn String EnumSet.to_tstring(&set) @dynamic
{
return string::tformat("%s", *set);
}
module std::collections::enumset::private;
macro typeid type_for_enum_elements(usz $elements)
{
$switch
$case ($elements > 128):
return char[($elements + 7) / 8].typeid;
$case ($elements > 64):
return uint128.typeid;
$case ($elements > 32 || $$C_INT_SIZE > 32):
return ulong.typeid;
$case ($elements > 16 || $$C_INT_SIZE > 16):
return uint.typeid;
$case ($elements > 8 || $$C_INT_SIZE > 8):
return ushort.typeid;
$default:
return char.typeid;
$endswitch
}

View File

@@ -1,312 +1,310 @@
// Copyright (c) 2021 Christoffer Lerno. All rights reserved.
// Use of this source code is governed by the MIT license
// Use of self source code is governed by the MIT license
// a copy of which can be found in the LICENSE_STDLIB file.
module std::collections::linkedlist<Type>;
module std::collections::linkedlist(<Type>);
struct Node @private
{
Node *next;
Node *prev;
Type value;
Node *next;
Node *prev;
Type value;
}
struct LinkedList
{
Allocator *allocator;
usz size;
Node *_first;
Node *_last;
Allocator *allocator;
usz size;
Node *_first;
Node *_last;
}
fn void LinkedList.push(LinkedList* list, Type value)
fn void LinkedList.push(&self, Type value)
{
list.link_first(value);
self.link_first(value);
}
fn void LinkedList.push_last(LinkedList* list, Type value)
fn void LinkedList.push_last(&self, Type value)
{
list.link_last(value);
self.link_last(value);
}
fn void LinkedList.init(LinkedList* list, Allocator* using = mem::heap())
{
*list = { .allocator = using };
}
fn void LinkedList.tinit(LinkedList* list) => list.init(mem::temp()) @inline;
/**
* @require list.allocator
* @param [&inout] allocator "The allocator to use, defaults to the heap allocator"
* @return "the initialized list"
**/
macro void LinkedList.@free_node(LinkedList &list, Node* node) @private
fn LinkedList* LinkedList.init_new(&self, Allocator* allocator = mem::heap())
{
list.allocator.free(node)!!;
}
macro Node* LinkedList.@alloc_node(LinkedList &list) @private
{
if (!list.allocator) list.allocator = mem::heap();
return malloc(Node, .using = list.allocator);
*self = { .allocator = allocator };
return self;
}
fn void LinkedList.link_first(LinkedList* list, Type value) @private
fn LinkedList* LinkedList.init_temp(&self)
{
Node *first = list._first;
Node *new_node = list.@alloc_node();
*new_node = { .next = first, .value = value };
list._first = new_node;
if (!first)
{
list._last = new_node;
}
else
{
first.prev = new_node;
}
list.size++;
return self.init_new(mem::temp()) @inline;
}
fn void LinkedList.link_last(LinkedList* list, Type value) @private
{
Node *last = list._last;
Node *new_node = list.@alloc_node();
*new_node = { .prev = last, .value = value };
list._last = new_node;
if (!last)
{
list._first = new_node;
}
else
{
last.next = new_node;
}
list.size++;
}
fn Type! peek(LinkedList* list) => list.first() @inline;
fn Type! peek_last(LinkedList* list) => list.last() @inline;
fn Type! LinkedList.first(LinkedList *list)
{
if (!list._first) return IteratorResult.NO_MORE_ELEMENT!;
return list._first.value;
}
fn Type! LinkedList.last(LinkedList* list)
{
if (!list._last) return IteratorResult.NO_MORE_ELEMENT!;
return list._last.value;
}
fn void LinkedList.free(LinkedList* list) => list.clear() @inline;
fn void LinkedList.clear(LinkedList* list)
{
for (Node* node = list._first; node != null;)
{
Node* next = node.next;
list.@free_node(node);
node = next;
}
list._first = null;
list._last = null;
list.size = 0;
}
fn usz LinkedList.len(LinkedList* list) @inline => list.size;
/**
* @require index < list.size
* @require self.allocator
**/
macro Node* LinkedList.node_at_index(LinkedList* list, usz index)
macro void LinkedList.free_node(&self, Node* node) @private
{
if (index * 2 >= list.size)
self.allocator.free(node);
}
macro Node* LinkedList.alloc_node(&self) @private
{
if (!self.allocator) self.allocator = mem::heap();
return self.allocator.new(Node);
}
fn void LinkedList.link_first(&self, Type value) @private
{
Node *first = self._first;
Node *new_node = self.alloc_node();
*new_node = { .next = first, .value = value };
self._first = new_node;
if (!first)
{
Node* node = list._last;
index = list.size - index - 1;
while (index--) node = node.prev;
return node;
self._last = new_node;
}
Node* node = list._first;
while (index--) node = node.next;
return node;
else
{
first.prev = new_node;
}
self.size++;
}
fn void LinkedList.link_last(&self, Type value) @private
{
Node *last = self._last;
Node *new_node = self.alloc_node();
*new_node = { .prev = last, .value = value };
self._last = new_node;
if (!last)
{
self._first = new_node;
}
else
{
last.next = new_node;
}
self.size++;
}
fn Type! LinkedList.peek(&self) => self.first() @inline;
fn Type! LinkedList.peek_last(&self) => self.last() @inline;
fn Type! LinkedList.first(&self)
{
if (!self._first) return IteratorResult.NO_MORE_ELEMENT?;
return self._first.value;
}
fn Type! LinkedList.last(&self)
{
if (!self._last) return IteratorResult.NO_MORE_ELEMENT?;
return self._last.value;
}
fn void LinkedList.free(&self) => self.clear() @inline;
fn void LinkedList.clear(&self)
{
for (Node* node = self._first; node != null;)
{
Node* next = node.next;
self.free_node(node);
node = next;
}
self._first = null;
self._last = null;
self.size = 0;
}
fn usz LinkedList.len(&self) @inline => self.size;
/**
* @require index < self.size
**/
macro Node* LinkedList.node_at_index(&self, usz index)
{
if (index * 2 >= self.size)
{
Node* node = self._last;
index = self.size - index - 1;
while (index--) node = node.prev;
return node;
}
Node* node = self._first;
while (index--) node = node.next;
return node;
}
/**
* @require index < list.size
* @require index < self.size
**/
fn Type LinkedList.get(LinkedList* list, usz index)
fn Type LinkedList.get(&self, usz index)
{
return list.node_at_index(index).value;
return self.node_at_index(index).value;
}
/**
* @require index < list.size
* @require index < self.size
**/
fn void LinkedList.set(LinkedList* list, usz index, Type element)
fn void LinkedList.set(&self, usz index, Type element)
{
list.node_at_index(index).value = element;
self.node_at_index(index).value = element;
}
/**
* @require index < list.size
* @require index < self.size
**/
fn void LinkedList.remove(LinkedList* list, usz index)
fn void LinkedList.remove(&self, usz index)
{
list.unlink(list.node_at_index(index));
self.unlink(self.node_at_index(index));
}
/**
* @require index <= list.size
* @require index <= self.size
**/
fn void LinkedList.insert(LinkedList* list, usz index, Type element)
fn void LinkedList.insert(&self, usz index, Type element)
{
switch (index)
{
case 0:
list.push(element);
case list.size:
list.push_last(element);
self.push(element);
case self.size:
self.push_last(element);
default:
list.link_before(list.node_at_index(index), element);
self.link_before(self.node_at_index(index), element);
}
}
/**
* @require succ != null
**/
fn void LinkedList.link_before(LinkedList *list, Node *succ, Type value) @private
fn void LinkedList.link_before(&self, Node *succ, Type value) @private
{
Node* pred = succ.prev;
Node* new_node = malloc(Node);
*new_node = { .prev = pred, .next = succ, .value = value };
succ.prev = new_node;
if (!pred)
{
list._first = new_node;
}
else
{
pred.next = new_node;
}
list.size++;
Node* pred = succ.prev;
Node* new_node = self.alloc_node();
*new_node = { .prev = pred, .next = succ, .value = value };
succ.prev = new_node;
if (!pred)
{
self._first = new_node;
}
else
{
pred.next = new_node;
}
self.size++;
}
/**
* @require list && list._first
* @require self._first
**/
fn void LinkedList.unlink_first(LinkedList* list) @private
fn void LinkedList.unlink_first(&self) @private
{
Node* f = list._first;
Node* next = f.next;
list.@free_node(f);
list._first = next;
if (!next)
{
list._last = null;
}
else
{
next.prev = null;
}
list.size--;
Node* f = self._first;
Node* next = f.next;
self.free_node(f);
self._first = next;
if (!next)
{
self._last = null;
}
else
{
next.prev = null;
}
self.size--;
}
fn bool LinkedList.remove_value(LinkedList* list, Type t)
fn bool LinkedList.remove_value(&self, Type t)
{
for (Node* node = list._first; node != null; node = node.next)
{
if (node.value == t)
{
list.unlink(node);
return true;
}
}
return false;
for (Node* node = self._first; node != null; node = node.next)
{
if (node.value == t)
{
self.unlink(node);
return true;
}
}
return false;
}
fn bool LinkedList.remove_last_value(LinkedList* list, Type t)
fn bool LinkedList.remove_last_value(&self, Type t)
{
for (Node* node = list._last; node != null; node = node.prev)
{
if (node.value == t)
{
list.unlink(node);
return true;
}
}
return false;
for (Node* node = self._last; node != null; node = node.prev)
{
if (node.value == t)
{
self.unlink(node);
return true;
}
}
return false;
}
fn Type! LinkedList.pop(&self)
{
if (!self._first) return IteratorResult.NO_MORE_ELEMENT?;
defer self.unlink_first();
return self._first.value;
}
fn void! LinkedList.remove_last(&self)
{
if (!self._first) return IteratorResult.NO_MORE_ELEMENT?;
self.unlink_last();
}
fn void! LinkedList.remove_first(&self)
{
if (!self._first) return IteratorResult.NO_MORE_ELEMENT?;
self.unlink_first();
}
/**
* @param [&inout] list
* @require self._last
**/
fn Type! LinkedList.pop(LinkedList* list)
fn void LinkedList.unlink_last(&self) @inline @private
{
if (!list._first) return IteratorResult.NO_MORE_ELEMENT!;
defer list.unlink_first();
return list._first.value;
Node* l = self._last;
Node* prev = l.prev;
self._last = prev;
self.free_node(l);
if (!prev)
{
self._first = null;
}
else
{
prev.next = null;
}
self.size--;
}
/**
* @param [&inout] list
* @require x != null
**/
fn void! LinkedList.remove_last(LinkedList* list)
fn void LinkedList.unlink(&self, Node* x) @private
{
if (!list._first) return IteratorResult.NO_MORE_ELEMENT!;
list.unlink_last();
}
/**
* @param [&inout] list
**/
fn void! LinkedList.remove_first(LinkedList* list)
{
if (!list._first) return IteratorResult.NO_MORE_ELEMENT!;
list.unlink_first();
}
/**
* @param [&inout] list
* @require list._last
**/
fn void LinkedList.unlink_last(LinkedList *list) @inline @private
{
Node* l = list._last;
Node* prev = l.prev;
list._last = prev;
list.@free_node(l);
if (!prev)
{
list._first = null;
}
else
{
prev.next = null;
}
list.size--;
}
/**
* @require list != null, x != null
**/
fn void LinkedList.unlink(LinkedList* list, Node* x) @private
{
Node* next = x.next;
Node* prev = x.prev;
if (!prev)
{
list._first = next;
}
else
{
prev.next = next;
}
if (!next)
{
list._last = prev;
}
else
{
next.prev = prev;
}
list.@free_node(x);
list.size--;
Node* next = x.next;
Node* prev = x.prev;
if (!prev)
{
self._first = next;
}
else
{
prev.next = next;
}
if (!next)
{
self._last = prev;
}
else
{
next.prev = prev;
}
self.free_node(x);
self.size--;
}

View File

@@ -1,185 +1,489 @@
// Copyright (c) 2021 Christoffer Lerno. All rights reserved.
// Use of this source code is governed by the MIT license
// Use of self source code is governed by the MIT license
// a copy of which can be found in the LICENSE_STDLIB file.
module std::collections::list<Type>;
module std::collections::list(<Type>);
import std::io;
import std::math;
struct List
def ElementPredicate = fn bool(Type *type);
def ElementTest = fn bool(Type *type, any* context);
const ELEMENT_IS_EQUATABLE = types::is_equatable_type(Type);
const ELEMENT_IS_POINTER = Type.kindof == POINTER;
struct List (Printable)
{
usz size;
usz capacity;
Allocator *allocator;
Type *entries;
usz size;
usz capacity;
Allocator *allocator;
Type *entries;
}
/**
* @require using != null "A valid allocator must be provided"
* @param initial_capacity "The initial capacity to reserve"
* @param [&inout] allocator "The allocator to use, defaults to the heap allocator"
**/
fn void List.init(List* list, usz initial_capacity = 16, Allocator* using = mem::heap())
fn List* List.init_new(&self, usz initial_capacity = 16, Allocator* allocator = mem::heap())
{
list.allocator = using;
list.size = 0;
self.allocator = allocator;
self.size = 0;
if (initial_capacity > 0)
{
initial_capacity = math::next_power_of_2(initial_capacity);
list.entries = malloc_aligned(Type, initial_capacity, .alignment = Type[1].alignof, .using = using)!!;
self.entries = allocator.alloc_aligned(Type.sizeof * initial_capacity, .alignment = Type[1].alignof)!!;
}
else
{
list.entries = null;
self.entries = null;
}
list.capacity = initial_capacity;
}
fn void List.tinit(List* list, usz initial_capacity = 16)
{
list.init(initial_capacity, mem::temp()) @inline;
}
fn void List.push(List* list, Type element) @inline
{
list.append(element);
}
fn void List.append(List* list, Type element)
{
list.ensure_capacity();
list.entries[list.size++] = element;
self.capacity = initial_capacity;
return self;
}
/**
* @require list.size > 0
*/
fn Type List.pop(List* list)
{
return list.entries[--list.size];
}
fn void List.clear(List* list)
{
list.size = 0;
}
/**
* @require list.size > 0
*/
fn Type List.pop_first(List* list)
{
Type value = list.entries[0];
list.remove_at(0);
return value;
}
fn void List.remove_at(List* list, usz index)
{
for (usz i = index + 1; i < list.size; i++)
{
list.entries[i - 1] = list.entries[i];
}
list.size--;
}
fn void List.push_front(List* list, Type type) @inline
{
list.insert_at(0, type);
}
fn void List.insert_at(List* list, usz index, Type type)
{
list.ensure_capacity();
for (usz i = list.size; i > index; i--)
{
list.entries[i] = list.entries[i - 1];
}
list.size++;
list.entries[index] = type;
}
/**
* @require index < list.size
* Initialize the list using the temp allocator.
*
* @param initial_capacity "The initial capacity to reserve"
**/
fn void List.set_at(List* list, usz index, Type type)
fn List* List.init_temp(&self, usz initial_capacity = 16)
{
list.entries[index] = type;
return self.init_new(initial_capacity, mem::temp()) @inline;
}
fn void List.remove_last(List* list)
/**
* @require self.size == 0 "The List must be empty"
**/
fn void List.init_wrapping_array(&self, Type[] types, Allocator* allocator = mem::heap())
{
list.size--;
self.allocator = allocator;
self.size = types.len;
self.capacity = types.len;
self.entries = types.ptr;
}
fn void List.remove_first(List* list)
fn usz! List.to_format(&self, Formatter* formatter) @dynamic
{
list.remove_at(0);
switch (self.size)
{
case 0:
return formatter.print("[]")!;
case 1:
return formatter.printf("[%s]", self.entries[0])!;
default:
usz n = formatter.print("[")!;
foreach (i, element : self.entries[:self.size])
{
if (i != 0) formatter.print(", ")!;
n += formatter.printf("%s", element)!;
}
n += formatter.print("]")!;
return n;
}
}
fn Type* List.first(List* list)
fn String List.to_new_string(&self, Allocator* allocator = mem::heap()) @dynamic
{
return list.size ? &list.entries[0] : null;
return string::new_format("%s", *self, .allocator = allocator);
}
fn Type* List.last(List* list)
fn String List.to_tstring(&self)
{
return list.size ? &list.entries[list.size - 1] : null;
return string::tformat("%s", *self);
}
fn bool List.is_empty(List* list)
fn void List.push(&self, Type element) @inline
{
return !list.size;
self.append(element);
}
fn usz List.len(List* list) @operator(len)
fn void List.append(&self, Type element)
{
return list.size;
self.ensure_capacity();
self.entries[self.size++] = element;
}
fn Type List.get(List* list, usz index)
/**
* @require self.size > 0
**/
fn Type List.pop(&self)
{
return list.entries[index];
return self.entries[--self.size];
}
fn void List.free(List* list)
fn void List.clear(&self)
{
if (!list.allocator) return;
free_aligned(list.entries, .using = list.allocator);
list.capacity = 0;
list.size = 0;
list.entries = null;
self.size = 0;
}
fn void List.swap(List* list, usz i, usz j)
/**
* @require self.size > 0
**/
fn Type List.pop_first(&self)
{
@swap(list.entries[i], list.entries[j]);
Type value = self.entries[0];
self.remove_at(0);
return value;
}
/**
* @require index < self.size
**/
fn void List.remove_at(&self, usz index)
{
for (usz i = index + 1; i < self.size; i++)
{
self.entries[i - 1] = self.entries[i];
}
self.size--;
}
fn void List.add_all(&self, List* other_list)
{
if (!other_list.size) return;
self.reserve(other_list.size);
foreach (&value : other_list)
{
self.entries[self.size++] = *value;
}
}
fn Type[] List.to_new_array(&self, Allocator* allocator = mem::heap())
{
if (!self.size) return Type[] {};
Type[] result = allocator.new_array(Type, self.size);
result[..] = self.entries[:self.size];
return result;
}
fn Type[] List.to_tarray(&self)
{
return self.to_new_array(mem::temp());
}
/**
* Reverse the elements in a list.
**/
fn void List.reverse(&self)
{
if (self.size < 2) return;
usz half = self.size / 2U;
usz end = self.size - 1;
for (usz i = 0; i < half; i++)
{
@swap(self.entries[i], self.entries[end - i]);
}
}
fn Type[] List.array_view(&self)
{
return self.entries[:self.size];
}
fn void List.add_array(&self, Type[] array)
{
if (!array.len) return;
self.reserve(array.len);
foreach (&value : array)
{
self.entries[self.size++] = *value;
}
}
fn void List.push_front(&self, Type type) @inline
{
self.insert_at(0, type);
}
/**
* @require index < self.size
**/
fn void List.insert_at(&self, usz index, Type type)
{
self.ensure_capacity();
for (usz i = self.size; i > index; i--)
{
self.entries[i] = self.entries[i - 1];
}
self.size++;
self.entries[index] = type;
}
/**
* @require index < self.size
**/
fn void List.set_at(&self, usz index, Type type)
{
self.entries[index] = type;
}
/**
* @require self.size > 0
**/
fn void List.remove_last(&self)
{
self.size--;
}
/**
* @require self.size > 0
**/
fn void List.remove_first(&self)
{
self.remove_at(0);
}
fn Type* List.first(&self)
{
return self.size ? &self.entries[0] : null;
}
fn Type* List.last(&self)
{
return self.size ? &self.entries[self.size - 1] : null;
}
fn bool List.is_empty(&self) @inline
{
return !self.size;
}
fn usz List.len(&self) @operator(len) @inline
{
return self.size;
}
fn Type List.get(&self, usz index) @inline
{
return self.entries[index];
}
fn void List.free(&self)
{
if (!self.allocator) return;
self.allocator.free_aligned(self.entries);
self.capacity = 0;
self.size = 0;
self.entries = null;
}
fn void List.swap(&self, usz i, usz j)
{
@swap(self.entries[i], self.entries[j]);
}
/**
* @param filter "The function to determine if it should be removed or not"
* @return "the number of deleted elements"
**/
fn usz List.remove_if(&self, ElementPredicate filter)
{
return self._remove_if(filter, false);
}
/**
* @param selection "The function to determine if it should be kept or not"
* @return "the number of deleted elements"
**/
fn usz List.retain_if(&self, ElementPredicate selection)
{
return self._remove_if(selection, true);
}
macro usz List._remove_if(&self, ElementPredicate filter, bool $invert) @local
{
usz size = self.size;
for (usz i = size, usz k = size; k > 0; k = i)
{
// Find last index of item to be deleted.
$if $invert:
while (i > 0 && !filter(&self.entries[i - 1])) i--;
$else
while (i > 0 && filter(&self.entries[i - 1])) i--;
$endif
// Remove the items from this index up to the one not to be deleted.
usz n = self.size - k;
self.entries[i:n] = self.entries[k:n];
self.size -= k - i;
// Find last index of item not to be deleted.
$if $invert:
while (i > 0 && filter(&self.entries[i - 1])) i--;
$else
while (i > 0 && !filter(&self.entries[i - 1])) i--;
$endif
}
return size - self.size;
}
fn usz List.remove_using_test(&self, ElementTest filter, any* context)
{
return self._remove_using_test(filter, false, context);
}
fn usz List.retain_using_test(&self, ElementTest filter, any* context)
{
return self._remove_using_test(filter, true, context);
}
macro usz List._remove_using_test(&self, ElementTest filter, bool $invert, ctx) @local
{
usz size = self.size;
for (usz i = size, usz k = size; k > 0; k = i)
{
// Find last index of item to be deleted.
$if $invert:
while (i > 0 && !filter(&self.entries[i - 1], ctx)) i--;
$else
while (i > 0 && filter(&self.entries[i - 1], ctx)) i--;
$endif
// Remove the items from this index up to the one not to be deleted.
usz n = self.size - k;
self.entries[i:n] = self.entries[k:n];
self.size -= k - i;
// Find last index of item not to be deleted.
$if $invert:
while (i > 0 && filter(&self.entries[i - 1], ctx)) i--;
$else
while (i > 0 && !filter(&self.entries[i - 1], ctx)) i--;
$endif
}
return size - self.size;
}
/**
* Reserve at least min_capacity
**/
fn void List.reserve(List* list, usz min_capacity)
fn void List.reserve(&self, usz min_capacity)
{
if (!min_capacity) return;
if (list.capacity >= min_capacity) return;
if (!list.allocator) list.allocator = mem::heap();
if (self.capacity >= min_capacity) return;
if (!self.allocator) self.allocator = mem::heap();
min_capacity = math::next_power_of_2(min_capacity);
list.entries = realloc_aligned(list.entries, Type.sizeof * min_capacity, .alignment = Type[1].alignof, .using = list.allocator) ?? null;
list.capacity = min_capacity;
self.entries = self.allocator.realloc_aligned(self.entries, Type.sizeof * min_capacity, .alignment = Type[1].alignof) ?? null;
self.capacity = min_capacity;
}
macro Type List.@item_at(List &list, usz index) @operator([])
macro Type List.@item_at(&self, usz index) @operator([])
{
return list.entries[index];
return self.entries[index];
}
fn Type* List.get_ref(List* list, usz index) @operator(&[]) @inline
fn Type* List.get_ref(&self, usz index) @operator(&[]) @inline
{
return &list.entries[index];
return &self.entries[index];
}
fn void List.ensure_capacity(List* list) @inline @private
fn void List.set(&self, usz index, Type value) @operator([]=)
{
if (list.capacity == list.size)
{
list.reserve(list.capacity ? 2 * list.capacity : 16);
}
self.entries[index] = value;
}
fn void List.ensure_capacity(&self, usz added = 1) @inline @private
{
usz new_size = self.size + added;
if (self.capacity >= new_size) return;
assert(new_size < usz.max / 2U);
usz new_capacity = self.capacity ? 2U * self.capacity : 16U;
while (new_capacity < new_size) new_capacity *= 2U;
self.reserve(new_capacity);
}
// Functions for equatable types
fn usz! List.index_of(&self, Type type) @if(ELEMENT_IS_EQUATABLE)
{
foreach (i, v : self)
{
if (equals(v, type)) return i;
}
return SearchResult.MISSING?;
}
fn usz! List.rindex_of(&self, Type type) @if(ELEMENT_IS_EQUATABLE)
{
foreach_r (i, v : self)
{
if (equals(v, type)) return i;
}
return SearchResult.MISSING?;
}
fn bool List.equals(&self, List other_list) @if(ELEMENT_IS_EQUATABLE)
{
if (self.size != other_list.size) return false;
foreach (i, v : self)
{
if (!equals(v, other_list.entries[i])) return false;
}
return true;
}
/**
* Check for presence of a value in a list.
*
* @param [&in] self "the list to find elements in"
* @param value "The value to search for"
* @return "True if the value is found, false otherwise"
**/
fn bool List.contains(&self, Type value) @if(ELEMENT_IS_EQUATABLE)
{
foreach (i, v : self)
{
if (equals(v, value)) return true;
}
return false;
}
/**
* @param [&inout] self "The list to remove elements from"
* @param value "The value to remove"
* @return "the number of deleted elements."
**/
fn usz List.remove(&self, Type value) @if(ELEMENT_IS_EQUATABLE)
{
usz size = self.size;
for (usz i = size; i > 0; i--)
{
if (!equals(self.entries[i - 1], value)) continue;
for (usz j = i; j < size; j++)
{
self.entries[j - 1] = self.entries[j];
}
self.size--;
}
return size - self.size;
}
fn void List.remove_all(&self, List* other_list) @if(ELEMENT_IS_EQUATABLE)
{
if (!other_list.size) return;
foreach (v : other_list) self.remove(v);
}
/**
* @param [&in] self
* @return "The number non-null values in the list"
**/
fn usz List.compact_count(&self) @if(ELEMENT_IS_POINTER)
{
usz vals = 0;
foreach (v : self) if (v) vals++;
return vals;
}
fn usz List.compact(&self) @if(ELEMENT_IS_POINTER)
{
usz size = self.size;
for (usz i = size; i > 0; i--)
{
if (self.entries[i - 1]) continue;
for (usz j = i; j < size; j++)
{
self.entries[j - 1] = self.entries[j];
}
self.size--;
}
return size - self.size;
}

View File

@@ -1,13 +1,14 @@
// Copyright (c) 2023 Christoffer Lerno. All rights reserved.
// Use of this source code is governed by the MIT license
// a copy of which can be found in the LICENSE_STDLIB file.
module std::collections::map<Key, Value>;
module std::collections::map(<Key, Value>);
import std::math;
const uint DEFAULT_INITIAL_CAPACITY = 16;
const uint MAXIMUM_CAPACITY = 1u << 31;
const float DEFAULT_LOAD_FACTOR = 0.75;
const VALUE_IS_EQUATABLE = Value.is_eq;
const bool COPY_KEYS = types::implements_copy(Key);
struct HashMap
{
@@ -19,19 +20,20 @@ struct HashMap
}
/**
* @param [&inout] allocator "The allocator to use"
* @require capacity > 0 "The capacity must be 1 or higher"
* @require load_factor > 0.0 "The load factor must be higher than 0"
* @require !map.allocator "Map was already initialized"
* @require capacity < MAXIMUM_CAPACITY "Capacity cannot exceed maximum"
* @require using != null "The allocator must be non-null"
**/
fn void HashMap.init(HashMap* map, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR, Allocator* using = mem::heap())
fn HashMap* HashMap.init_new(&map, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR, Allocator* allocator = mem::heap())
{
capacity = math::next_power_of_2(capacity);
map.allocator = using;
map.allocator = allocator;
map.load_factor = load_factor;
map.threshold = (uint)(capacity * load_factor);
map.table = calloc(Entry*, capacity, .using = using);
map.table = allocator.new_zero_array(Entry*, capacity);
return map;
}
/**
@@ -40,53 +42,77 @@ fn void HashMap.init(HashMap* map, uint capacity = DEFAULT_INITIAL_CAPACITY, flo
* @require !map.allocator "Map was already initialized"
* @require capacity < MAXIMUM_CAPACITY "Capacity cannot exceed maximum"
**/
fn void HashMap.tinit(HashMap* map, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
fn HashMap* HashMap.init_temp(&map, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
{
map.init(capacity, load_factor, mem::temp());
return map.init_new(capacity, load_factor, mem::temp());
}
fn void HashMap.init_from_map(HashMap* map, HashMap* other_map, Allocator* using = mem::heap())
/**
* Has this hash map been initialized yet?
*
* @param [&in] map "The hash map we are testing"
* @return "Returns true if it has been initialized, false otherwise"
**/
fn bool HashMap.is_initialized(&map)
{
map.init(other_map.table.len, other_map.load_factor, using);
map.put_all_for_create(other_map);
return (bool)map.allocator;
}
fn void HashMap.tinit_from_map(HashMap* map, HashMap* other_map)
/**
* @param [&inout] allocator "The allocator to use"
* @param [&in] other_map "The map to copy from."
**/
fn HashMap* HashMap.init_new_from_map(&self, HashMap* other_map, Allocator* allocator = mem::heap())
{
map.init_from_map(other_map, mem::temp()) @inline;
self.init_new(other_map.table.len, other_map.load_factor, allocator);
self.put_all_for_create(other_map);
return self;
}
fn bool HashMap.is_empty(HashMap* map) @inline
/**
* @param [&in] other_map "The map to copy from."
**/
fn HashMap* HashMap.init_temp_from_map(&map, HashMap* other_map)
{
return map.init_new_from_map(other_map, mem::temp()) @inline;
}
fn bool HashMap.is_empty(&map) @inline
{
return !map.count;
}
fn Value*! HashMap.get_ref(HashMap* map, Key key)
fn usz HashMap.len(&map) @inline
{
if (!map.count) return SearchResult.MISSING!;
return map.count;
}
fn Value*! HashMap.get_ref(&map, Key key)
{
if (!map.count) return SearchResult.MISSING?;
uint hash = rehash(key.hash());
for (Entry *e = map.table[index_for(hash, map.table.len)]; e != null; e = e.next)
{
if (e.hash == hash && equals(key, e.key)) return &e.value;
}
return SearchResult.MISSING!;
return SearchResult.MISSING?;
}
fn Entry*! HashMap.get_entry(HashMap* map, Key key)
fn Entry*! HashMap.get_entry(&map, Key key)
{
if (!map.count) return SearchResult.MISSING!;
if (!map.count) return SearchResult.MISSING?;
uint hash = rehash(key.hash());
for (Entry *e = map.table[index_for(hash, map.table.len)]; e != null; e = e.next)
{
if (e.hash == hash && equals(key, e.key)) return e;
}
return SearchResult.MISSING!;
return SearchResult.MISSING?;
}
/**
* Get the value or update and
**/
macro Value HashMap.@get_or_set(HashMap* map, Key key, Value #expr)
macro Value HashMap.@get_or_set(&map, Key key, Value #expr)
{
if (!map.count)
{
@@ -105,22 +131,22 @@ macro Value HashMap.@get_or_set(HashMap* map, Key key, Value #expr)
return val;
}
fn Value! HashMap.get(HashMap* map, Key key) @operator([])
fn Value! HashMap.get(&map, Key key) @operator([])
{
return *map.get_ref(key) @inline;
}
fn bool HashMap.has_key(HashMap* map, Key key)
fn bool HashMap.has_key(&map, Key key)
{
return try? map.get_ref(key);
return @ok(map.get_ref(key));
}
fn bool HashMap.set(HashMap* map, Key key, Value value) @operator([]=)
fn bool HashMap.set(&map, Key key, Value value) @operator([]=)
{
// If the map isn't initialized, use the defaults to initialize it.
if (!map.allocator)
{
map.init();
map.init_new();
}
uint hash = rehash(key.hash());
uint index = index_for(hash, map.table.len);
@@ -136,42 +162,49 @@ fn bool HashMap.set(HashMap* map, Key key, Value value) @operator([]=)
return false;
}
fn void! HashMap.remove(HashMap* map, Key key) @maydiscard
fn void! HashMap.remove(&map, Key key) @maydiscard
{
if (!map.remove_entry_for_key(key)) return SearchResult.MISSING!;
if (!map.remove_entry_for_key(key)) return SearchResult.MISSING?;
}
fn void HashMap.clear(HashMap* map)
fn void HashMap.clear(&map)
{
if (!map.count) return;
foreach (Entry** &entry_ref : map.table)
{
Entry* entry = *entry_ref;
if (!entry) continue;
map.free_internal(entry);
Entry *next = entry.next;
while (next)
{
Entry *to_delete = next;
next = next.next;
map.free_entry(to_delete);
}
map.free_entry(entry);
*entry_ref = null;
}
map.count = 0;
}
fn void HashMap.free(HashMap* map)
fn void HashMap.free(&map)
{
if (!map.allocator) return;
map.clear();
map.free_internal(map.table.ptr);
map.table = Entry*[] {};
map.table = {};
}
fn Key[] HashMap.key_tlist(HashMap* map)
fn Key[] HashMap.key_tlist(&map)
{
return map.key_list(mem::temp()) @inline;
return map.key_new_list(mem::temp()) @inline;
}
fn Key[] HashMap.key_list(HashMap* map, Allocator* using = mem::heap())
fn Key[] HashMap.key_new_list(&map, Allocator* allocator = mem::heap())
{
if (!map.count) return Key[] {};
if (!map.count) return {};
Key[] list = calloc(Key, map.count, .using = using);
Key[] list = allocator.new_array(Key, map.count);
usz index = 0;
foreach (Entry* entry : map.table)
{
@@ -184,15 +217,37 @@ fn Key[] HashMap.key_list(HashMap* map, Allocator* using = mem::heap())
return list;
}
fn Value[] HashMap.value_tlist(HashMap* map)
macro HashMap.@each(map; @body(key, value))
{
return map.value_list(mem::temp()) @inline;
map.@each_entry(; Entry* entry) {
@body(entry.key, entry.value);
};
}
fn Value[] HashMap.value_list(HashMap* map, Allocator* using = mem::heap())
macro HashMap.@each_entry(map; @body(entry))
{
if (!map.count) return Value[] {};
Value[] list = calloc(Value, map.count, .using = using);
if (map.count)
{
foreach (Entry* entry : map.table)
{
while (entry)
{
@body(entry);
entry = entry.next;
}
}
}
}
fn Value[] HashMap.value_tlist(&map)
{
return map.value_new_list(mem::temp()) @inline;
}
fn Value[] HashMap.value_new_list(&map, Allocator* allocator = mem::heap())
{
if (!map.count) return {};
Value[] list = allocator.new_array(Value, map.count);
usz index = 0;
foreach (Entry* entry : map.table)
{
@@ -205,8 +260,7 @@ fn Value[] HashMap.value_list(HashMap* map, Allocator* using = mem::heap())
return list;
}
$if (types::is_equatable(Value))
fn bool HashMap.has_value(HashMap* map, Value v)
fn bool HashMap.has_value(&map, Value v) @if(VALUE_IS_EQUATABLE)
{
if (!map.count) return false;
foreach (Entry* entry : map.table)
@@ -219,22 +273,24 @@ fn bool HashMap.has_value(HashMap* map, Value v)
}
return false;
}
$endif
// --- private methods
fn void HashMap.add_entry(HashMap* map, uint hash, Key key, Value value, uint bucket_index) @private
fn void HashMap.add_entry(&map, uint hash, Key key, Value value, uint bucket_index) @private
{
Entry* entry = malloc(Entry, .using = map.allocator);
Entry* entry = map.allocator.new(Entry);
$if COPY_KEYS:
key = key.copy(map.allocator);
$endif
*entry = { .hash = hash, .key = key, .value = value, .next = map.table[bucket_index] };
map.table[bucket_index] = entry;
if (map.count++ >= map.threshold)
{
map.resize(map.table.len * 2);
}
}
}
fn void HashMap.resize(HashMap* map, uint new_capacity) @private
fn void HashMap.resize(&map, uint new_capacity) @private
{
Entry*[] old_table = map.table;
uint old_capacity = old_table.len;
@@ -243,7 +299,7 @@ fn void HashMap.resize(HashMap* map, uint new_capacity) @private
map.threshold = uint.max;
return;
}
Entry*[] new_table = calloc(Entry*, new_capacity, .using = map.allocator);
Entry*[] new_table = map.allocator.new_zero_array(Entry*, new_capacity);
map.transfer(new_table);
map.table = new_table;
map.free_internal(old_table.ptr);
@@ -252,8 +308,8 @@ fn void HashMap.resize(HashMap* map, uint new_capacity) @private
fn uint rehash(uint hash) @inline @private
{
hash ^= (hash >> 20) ^ (hash >> 12);
return hash ^ ((hash >> 7) ^ (hash >> 4));
hash ^= (hash >> 20) ^ (hash >> 12);
return hash ^ ((hash >> 7) ^ (hash >> 4));
}
macro uint index_for(uint hash, uint capacity) @private
@@ -261,39 +317,39 @@ macro uint index_for(uint hash, uint capacity) @private
return hash & (capacity - 1);
}
fn void HashMap.transfer(HashMap* map, Entry*[] new_table) @private
fn void HashMap.transfer(&map, Entry*[] new_table) @private
{
Entry*[] src = map.table;
uint new_capacity = new_table.len;
foreach (uint j, Entry *e : src)
{
if (!e) continue;
do
{
Entry* next = e.next;
uint i = index_for(e.hash, new_capacity);
e.next = new_table[i];
new_table[i] = e;
e = next;
}
while (e);
}
uint new_capacity = new_table.len;
foreach (uint j, Entry *e : src)
{
if (!e) continue;
do
{
Entry* next = e.next;
uint i = index_for(e.hash, new_capacity);
e.next = new_table[i];
new_table[i] = e;
e = next;
}
while (e);
}
}
fn void HashMap.put_all_for_create(HashMap* map, HashMap* other_map) @private
fn void HashMap.put_all_for_create(&map, HashMap* other_map) @private
{
if (!other_map.count) return;
foreach (Entry *e : other_map.table)
{
if (!e) continue;
map.put_for_create(e.key, e.value);
}
foreach (Entry *e : other_map.table)
{
if (!e) continue;
map.put_for_create(e.key, e.value);
}
}
fn void HashMap.put_for_create(HashMap* map, Key key, Value value) @private
fn void HashMap.put_for_create(&map, Key key, Value value) @private
{
uint hash = rehash(key.hash());
uint i = index_for(hash, map.table.len);
uint hash = rehash(key.hash());
uint i = index_for(hash, map.table.len);
for (Entry *e = map.table[i]; e != null; e = e.next)
{
if (e.hash == hash && equals(key, e.key))
@@ -305,12 +361,12 @@ fn void HashMap.put_for_create(HashMap* map, Key key, Value value) @private
map.create_entry(hash, key, value, i);
}
fn void HashMap.free_internal(HashMap* map, void* ptr) @inline @private
fn void HashMap.free_internal(&map, void* ptr) @inline @private
{
map.allocator.free(ptr)!!;
map.allocator.free(ptr);
}
fn bool HashMap.remove_entry_for_key(HashMap* map, Key key) @private
fn bool HashMap.remove_entry_for_key(&map, Key key) @private
{
uint hash = rehash(key.hash());
uint i = index_for(hash, map.table.len);
@@ -330,7 +386,7 @@ fn bool HashMap.remove_entry_for_key(HashMap* map, Key key) @private
{
prev.next = next;
}
map.free_internal(e);
map.free_entry(e);
return true;
}
prev = e;
@@ -339,19 +395,30 @@ fn bool HashMap.remove_entry_for_key(HashMap* map, Key key) @private
return false;
}
fn void HashMap.create_entry(HashMap* map, uint hash, Key key, Value value, int bucket_index) @private
fn void HashMap.create_entry(&map, uint hash, Key key, Value value, int bucket_index) @private
{
Entry *e = map.table[bucket_index];
Entry* entry = malloc(Entry, .using = map.allocator);
Entry* entry = map.allocator.new(Entry);
$if COPY_KEYS:
key = key.copy(map.allocator);
$endif
*entry = { .hash = hash, .key = key, .value = value, .next = map.table[bucket_index] };
map.table[bucket_index] = entry;
map.count++;
}
fn void HashMap.free_entry(&self, Entry *entry) @local
{
$if COPY_KEYS:
self.allocator.free(entry.key);
$endif
self.free_internal(entry);
}
struct Entry
{
uint hash;
Key key;
Value value;
Entry* next;
}
}

View File

@@ -0,0 +1,19 @@
module std::collections::maybe(<Type>);
struct Maybe
{
Type value;
bool has_value;
}
fn Maybe value(Type val)
{
return { .value = val, .has_value = true };
}
const Maybe EMPTY = { };
macro Type! Maybe.get(self)
{
return self.has_value ? self.value : SearchResult.MISSING?;
}

View File

@@ -10,7 +10,7 @@ const Object TRUE_OBJECT = { .b = true, .type = bool.typeid };
const Object FALSE_OBJECT = { .b = false, .type = bool.typeid };
const Object NULL_OBJECT = { .type = void*.typeid };
struct Object
struct Object (Printable)
{
typeid type;
Allocator* allocator;
@@ -26,64 +26,62 @@ struct Object
}
}
static initialize
{
io::formatter_register_type(Object);
}
fn void! Object.to_format(Object* o, Formatter* formatter)
fn usz! Object.to_format(&self, Formatter* formatter) @dynamic
{
switch (o.type)
switch (self.type)
{
case void:
formatter.printf("{}")?;
return formatter.printf("{}")!;
case void*:
formatter.printf("null")?;
return formatter.printf("null")!;
case String:
formatter.printf(`"%s"`, o.s)?;
return formatter.printf(`"%s"`, self.s)!;
case bool:
formatter.printf(o.b ? "true" : "false")?;
return formatter.printf(self.b ? "true" : "false")!;
case ObjectInternalList:
formatter.printf("[")?;
foreach (i, ol : o.array)
usz n = formatter.printf("[")!;
foreach (i, ol : self.array)
{
formatter.printf(i == 0 ? " " : ", ")?;
ol.to_format(formatter)?;
if (i > 0) n += formatter.printf(",")!;
n += ol.to_format(formatter)!;
}
formatter.printf(" ]")?;
n += formatter.printf("]")!;
return n;
case ObjectInternalMap:
formatter.printf("{")?;
usz n = formatter.printf("{")!;
@pool()
{
foreach (i, key : o.map.key_tlist())
foreach (i, key : self.map.key_tlist())
{
formatter.printf(i == 0 ? " " : ", ")?;
formatter.printf(`"%s": `, key)?;
o.map.get(key).to_format(formatter)?;
if (i > 0) n += formatter.printf(",")!;
n += formatter.printf(`"%s":`, key)!;
n += self.map.get(key).to_format(formatter)!;
}
};
formatter.printf(" }")?;
n += formatter.printf("}")!;
return n;
default:
switch (o.type.kindof)
switch (self.type.kindof)
{
case SIGNED_INT:
formatter.printf("%d", o.i)?;
return formatter.printf("%d", self.i)!;
case UNSIGNED_INT:
formatter.printf("%d", (uint128)o.i)?;
return formatter.printf("%d", (uint128)self.i)!;
case FLOAT:
formatter.printf("%d", o.f)?;
return formatter.printf("%d", self.f)!;
case ENUM:
formatter.printf("%d", o.i)?;
return formatter.printf("%d", self.i)!;
default:
formatter.printf("<>")?;
return formatter.printf("<>")!;
}
}
}
fn Object* new_obj(Allocator* using = mem::heap())
fn Object* new_obj(Allocator* allocator)
{
Object* o = malloc(Object, .using = using);
*o = { .allocator = using, .type = void.typeid };
Object* o = allocator.new(Object);
*o = { .allocator = allocator, .type = void.typeid };
return o;
}
@@ -92,31 +90,31 @@ fn Object* new_null()
return &NULL_OBJECT;
}
fn Object* new_int(int128 i, Allocator* using = mem::heap())
fn Object* new_int(int128 i, Allocator* allocator)
{
Object* o = malloc(Object, .using = using);
*o = { .i = i, .allocator = using, .type = int128.typeid };
Object* o = allocator.new(Object);
*o = { .i = i, .allocator = allocator, .type = int128.typeid };
return o;
}
macro Object* new_enum(e, Allocator* using = mem::heap())
macro Object* new_enum(e, Allocator* allocator)
{
Object* o = malloc(Object, .using = using);
*o = { .i = (int128)e, .allocator = using, .type = $typeof(e).typeid };
Object* o = allocator.new(Object);
*o = { .i = (int128)e, .allocator = allocator, .type = @typeid(e) };
return o;
}
fn Object* new_float(double f, Allocator* using = mem::current_allocator())
fn Object* new_float(double f, Allocator* allocator)
{
Object* o = malloc(Object, .using = using);
*o = { .f = f, .allocator = using, .type = double.typeid };
Object* o = allocator.new(Object);
*o = { .f = f, .allocator = allocator, .type = double.typeid };
return o;
}
fn Object* new_string(String s, Allocator* using = mem::heap())
fn Object* new_string(String s, Allocator* allocator)
{
Object* o = malloc(Object, .using = using);
*o = { .s = s.copy(using), .allocator = using, .type = String.typeid };
Object* o = allocator.new(Object);
*o = { .s = s.copy(allocator), .allocator = allocator, .type = String.typeid };
return o;
}
@@ -126,187 +124,190 @@ fn Object* new_bool(bool b)
return b ? &TRUE_OBJECT : &FALSE_OBJECT;
}
/**
* @param [&inout] o
**/
fn void Object.free(Object* o)
fn void Object.free(&self)
{
switch (o.type)
switch (self.type)
{
case void:
break;
case String:
free(o.s, .using = o.allocator);
self.allocator.free(self.s);
case ObjectInternalList:
foreach (ol : o.array)
foreach (ol : self.array)
{
ol.free();
}
o.array.free();
self.array.free();
case ObjectInternalMap:
@pool()
{
foreach (key : o.map.key_tlist())
{
o.map.get(key).free();
free(key, .using = o.allocator);
}
o.map.free();
self.map.@each_entry(; ObjectInternalMapEntry* entry) {
self.allocator.free(entry.key);
entry.value.free();
};
default:
break;
}
if (o.allocator) free(o, .using = o.allocator);
if (self.allocator) self.allocator.free(self);
}
fn bool Object.is_null(Object* this) @inline => this == &NULL_OBJECT;
fn bool Object.is_empty(Object* this) @inline => this.type == void.typeid;
fn bool Object.is_map(Object* this) @inline => this.type == ObjectInternalMap.typeid;
fn bool Object.is_array(Object* this) @inline => this.type == ObjectInternalList.typeid;
fn bool Object.is_bool(Object* this) @inline => this.type == bool.typeid;
fn bool Object.is_string(Object* this) @inline => this.type == String.typeid;
fn bool Object.is_float(Object* this) @inline => this.type == double.typeid;
fn bool Object.is_int(Object* this) @inline => this.type == int128.typeid;
fn bool Object.is_keyable(Object* this) => this.is_empty() || this.is_map();
fn bool Object.is_indexable(Object* this) => this.is_empty() || this.is_array();
fn bool Object.is_null(&self) @inline => self == &NULL_OBJECT;
fn bool Object.is_empty(&self) @inline => self.type == void.typeid;
fn bool Object.is_map(&self) @inline => self.type == ObjectInternalMap.typeid;
fn bool Object.is_array(&self) @inline => self.type == ObjectInternalList.typeid;
fn bool Object.is_bool(&self) @inline => self.type == bool.typeid;
fn bool Object.is_string(&self) @inline => self.type == String.typeid;
fn bool Object.is_float(&self) @inline => self.type == double.typeid;
fn bool Object.is_int(&self) @inline => self.type == int128.typeid;
fn bool Object.is_keyable(&self) => self.is_empty() || self.is_map();
fn bool Object.is_indexable(&self) => self.is_empty() || self.is_array();
/**
* @require o.is_keyable()
* @require self.is_keyable()
**/
fn void Object.init_map_if_needed(Object* o) @private
fn void Object.init_map_if_needed(&self) @private
{
if (o.is_empty())
if (self.is_empty())
{
o.type = ObjectInternalMap.typeid;
o.map.init(.using = o.allocator);
self.type = ObjectInternalMap.typeid;
self.map.init_new(.allocator = self.allocator);
}
}
/**
* @require o.is_indexable()
* @require self.is_indexable()
**/
fn void Object.init_array_if_needed(Object* o) @private
fn void Object.init_array_if_needed(&self) @private
{
if (o.is_empty())
if (self.is_empty())
{
o.type = ObjectInternalList.typeid;
o.array.init(.using = o.allocator);
self.type = ObjectInternalList.typeid;
self.array.init_new(.allocator = self.allocator);
}
}
/**
* @require o.is_keyable()
* @require self.is_keyable()
**/
fn void Object.set_object(Object* o, String key, Object* new_object) @private
fn void Object.set_object(&self, String key, Object* new_object) @private
{
o.init_map_if_needed();
ObjectInternalMapEntry*! entry = o.map.get_entry(key);
self.init_map_if_needed();
ObjectInternalMapEntry*! entry = self.map.get_entry(key);
defer
{
(void)free(entry.key, .using = o.allocator);
entry.value.free();
(void)self.allocator.free(entry.key);
(void)entry.value.free();
}
o.map.set(key.copy(o.map.allocator), new_object);
self.map.set(key.copy(self.map.allocator), new_object);
}
macro Object* object_from_value(value) @private
macro Object* Object.object_from_value(&self, value) @private
{
var $Type = $typeof(value);
$switch
$case types::is_int($Type):
return new_int(value);
$case types::is_float($Type):
return new_float(value);
$case $Type.typeid == String.typeid:
return new_string(value);
$case $Type.typeid == bool.typeid:
return new_bool(value);
$case $Type.typeid == Object*.typeid:
return value;
$case $Type.typeid == void*.typeid:
assert(value == null);
return &NULL_OBJECT;
$case $checks(String s = value):
return new_string(value);
$default:
$assert(false, "Unsupported object type.");
$endswitch
$switch
$case types::is_int($Type):
return new_int(value, self.allocator);
$case types::is_float($Type):
return new_float(value, self.allocator);
$case $Type.typeid == String.typeid:
return new_string(value, self.allocator);
$case $Type.typeid == bool.typeid:
return new_bool(value);
$case $Type.typeid == Object*.typeid:
return value;
$case $Type.typeid == void*.typeid:
if (value != null) return CastResult.TYPE_MISMATCH?;
return &NULL_OBJECT;
$case $assignable(value, String):
return new_string(value, self.allocator);
$default:
$error "Unsupported object type.";
$endswitch
}
macro Object* Object.set(Object* o, String key, value)
macro Object* Object.set(&self, String key, value)
{
Object* val = object_from_value(value);
o.set_object(key, val);
Object* val = self.object_from_value(value);
self.set_object(key, val);
return val;
}
/**
* @require o.is_indexable()
* @require self.is_indexable()
**/
macro Object* Object.set_at(Object* o, usz index, String key, value)
macro Object* Object.set_at(&self, usz index, String key, value)
{
Object* val = object_from_value(value);
o.set_object_at(key, index, val);
Object* val = self.object_from_value(value);
self.set_object_at(key, index, val);
return val;
}
/**
* @require o.is_indexable()
* @require self.is_indexable()
* @ensure return != null
**/
macro Object* Object.append(Object* o, value)
macro Object* Object.append(&self, value)
{
Object* val = object_from_value(value);
o.append_object(val);
Object* val = self.object_from_value(value);
self.append_object(val);
return val;
}
/**
* @require o.is_keyable()
* @require self.is_keyable()
**/
fn Object*! Object.get(Object* o, String key) => o.is_empty() ? SearchResult.MISSING! : o.map.get(key);
fn Object*! Object.get(&self, String key) => self.is_empty() ? SearchResult.MISSING? : self.map.get(key);
fn bool Object.has_key(Object* o, String key) => o.is_map() && o.map.has_key(key);
fn bool Object.has_key(&self, String key) => self.is_map() && self.map.has_key(key);
/**
* @require o.is_indexable()
* @require self.is_indexable()
**/
fn Object* Object.get_at(Object* o, usz index)
fn Object* Object.get_at(&self, usz index)
{
return o.array.get(index);
return self.array.get(index);
}
/**
* @require o.is_indexable()
* @require self.is_indexable()
**/
fn void Object.append_object(Object* o, Object* to_append)
fn usz Object.get_len(&self)
{
o.init_array_if_needed();
o.array.append(to_append);
return self.array.len();
}
/**
* @require o.is_indexable()
* @require self.is_indexable()
**/
fn void Object.set_object_at(Object* o, usz index, Object* to_set)
fn void Object.append_object(&self, Object* to_append)
{
o.init_array_if_needed();
while (o.array.len() < index)
self.init_array_if_needed();
self.array.append(to_append);
}
/**
* @require self.is_indexable()
**/
fn void Object.set_object_at(&self, usz index, Object* to_set)
{
self.init_array_if_needed();
while (self.array.len() < index)
{
o.array.append(&NULL_OBJECT);
self.array.append(&NULL_OBJECT);
}
if (o.array.len() == index)
if (self.array.len() == index)
{
o.array.append(to_set);
self.array.append(to_set);
return;
}
o.array.get(index).free();
o.array.set_at(index, to_set);
self.array.get(index).free();
self.array.set_at(index, to_set);
}
/**
* @require $Type.kindof.is_int() "Expected an integer type."
**/
macro get_integer_value(Object* value, $Type)
{
if (value.is_float())
@@ -315,123 +316,126 @@ macro get_integer_value(Object* value, $Type)
}
if (value.is_string())
{
$if ($Type.kindof == TypeKind.SIGNED_INT)
return ($Type)str::to_int128(value.s);
$if $Type.kindof == TypeKind.SIGNED_INT:
return ($Type)value.s.to_int128();
$else
return ($Type)str::to_uint128(value.s);
return ($Type)value.s.to_uint128();
$endif
}
if (!value.is_int()) return NumberConversion.MALFORMED_INTEGER!;
if (!value.is_int()) return NumberConversion.MALFORMED_INTEGER?;
return ($Type)value.i;
}
/**
* @require o.is_indexable()
* @require self.is_indexable()
* @require $Type.kindof.is_int() : "Expected an integer type"
**/
macro Object.get_integer_at(Object* o, $Type, usz index) @private
macro Object.get_integer_at(&self, $Type, usz index) @private
{
return get_integer_value(o.get_at(index), $Type);
return get_integer_value(self.get_at(index), $Type);
}
/**
* @require o.is_keyable()
* @require self.is_keyable()
* @require $Type.kindof.is_int() : "Expected an integer type"
**/
macro Object.get_integer(Object* o, $Type, String key) @private
macro Object.get_integer(&self, $Type, String key) @private
{
return get_integer_value(o.get(key), $Type);
return get_integer_value(self.get(key), $Type);
}
fn ichar! Object.get_ichar(Object* o, String key) => o.get_integer(ichar, key);
fn short! Object.get_short(Object* o, String key) => o.get_integer(short, key);
fn int! Object.get_int(Object* o, String key) => o.get_integer(int, key);
fn long! Object.get_long(Object* o, String key) => o.get_integer(long, key);
fn int128! Object.get_int128(Object* o, String key) => o.get_integer(int128, key);
fn ichar! Object.get_ichar(&self, String key) => self.get_integer(ichar, key);
fn short! Object.get_short(&self, String key) => self.get_integer(short, key);
fn int! Object.get_int(&self, String key) => self.get_integer(int, key);
fn long! Object.get_long(&self, String key) => self.get_integer(long, key);
fn int128! Object.get_int128(&self, String key) => self.get_integer(int128, key);
fn ichar! Object.get_ichar_at(Object* o, usz index) => o.get_integer_at(ichar, index);
fn short! Object.get_short_at(Object* o, usz index) => o.get_integer_at(short, index);
fn int! Object.get_int_at(Object* o, usz index) => o.get_integer_at(int, index);
fn long! Object.get_long_at(Object* o, usz index) => o.get_integer_at(long, index);
fn int128! Object.get_int128_at(Object* o, usz index) => o.get_integer_at(int128, index);
fn ichar! Object.get_ichar_at(&self, usz index) => self.get_integer_at(ichar, index);
fn short! Object.get_short_at(&self, usz index) => self.get_integer_at(short, index);
fn int! Object.get_int_at(&self, usz index) => self.get_integer_at(int, index);
fn long! Object.get_long_at(&self, usz index) => self.get_integer_at(long, index);
fn int128! Object.get_int128_at(&self, usz index) => self.get_integer_at(int128, index);
fn char! Object.get_char(Object* o, String key) => o.get_integer(ichar, key);
fn short! Object.get_ushort(Object* o, String key) => o.get_integer(ushort, key);
fn uint! Object.get_uint(Object* o, String key) => o.get_integer(uint, key);
fn ulong! Object.get_ulong(Object* o, String key) => o.get_integer(ulong, key);
fn uint128! Object.get_uint128(Object* o, String key) => o.get_integer(uint128, key);
fn char! Object.get_char(&self, String key) => self.get_integer(ichar, key);
fn short! Object.get_ushort(&self, String key) => self.get_integer(ushort, key);
fn uint! Object.get_uint(&self, String key) => self.get_integer(uint, key);
fn ulong! Object.get_ulong(&self, String key) => self.get_integer(ulong, key);
fn uint128! Object.get_uint128(&self, String key) => self.get_integer(uint128, key);
fn char! Object.get_char_at(Object* o, usz index) => o.get_integer_at(char, index);
fn ushort! Object.get_ushort_at(Object* o, usz index) => o.get_integer_at(ushort, index);
fn uint! Object.get_uint_at(Object* o, usz index) => o.get_integer_at(uint, index);
fn ulong! Object.get_ulong_at(Object* o, usz index) => o.get_integer_at(ulong, index);
fn uint128! Object.get_uint128_at(Object* o, usz index) => o.get_integer_at(uint128, index);
fn char! Object.get_char_at(&self, usz index) => self.get_integer_at(char, index);
fn ushort! Object.get_ushort_at(&self, usz index) => self.get_integer_at(ushort, index);
fn uint! Object.get_uint_at(&self, usz index) => self.get_integer_at(uint, index);
fn ulong! Object.get_ulong_at(&self, usz index) => self.get_integer_at(ulong, index);
fn uint128! Object.get_uint128_at(&self, usz index) => self.get_integer_at(uint128, index);
/**
* @require o.is_keyable()
* @require self.is_keyable()
**/
fn String! Object.get_string(Object* o, String key)
fn String! Object.get_string(&self, String key)
{
Object* value = o.get(key)?;
assert(value.is_string());
Object* value = self.get(key)!;
if (!value.is_string()) return CastResult.TYPE_MISMATCH?;
return value.s;
}
/**
* @require o.is_indexable()
* @require self.is_indexable()
**/
fn String Object.get_string_at(Object* o, usz index)
fn String! Object.get_string_at(&self, usz index)
{
Object* value = o.get_at(index);
assert(value.is_string());
Object* value = self.get_at(index);
if (!value.is_string()) return CastResult.TYPE_MISMATCH?;
return value.s;
}
/**
* @require o.is_keyable()
* @require self.is_keyable()
**/
macro String! Object.get_enum(Object* o, $EnumType, String key)
macro String! Object.get_enum(&self, $EnumType, String key)
{
Object value = o.get(key)?;
assert($EnumType.typeid == value.type);
Object value = self.get(key)!;
if ($EnumType.typeid != value.type) return CastResult.TYPE_MISMATCH?;
return ($EnumType)value.i;
}
/**
* @require o.is_indexable()
* @require self.is_indexable()
**/
macro String Object.get_enum_at(Object* o, $EnumType, usz index)
macro String! Object.get_enum_at(&self, $EnumType, usz index)
{
Object value = o.get_at(index);
assert($EnumType.typeid == value.type);
Object value = self.get_at(index);
if ($EnumType.typeid != value.type) return CastResult.TYPE_MISMATCH?;
return ($EnumType)value.i;
}
/**
* @require o.is_keyable()
* @require self.is_keyable()
**/
fn bool! Object.get_bool(Object* o, String key)
fn bool! Object.get_bool(&self, String key)
{
Object* value = o.get(key)?;
assert(value.is_bool());
Object* value = self.get(key)!;
if (!value.is_bool()) return CastResult.TYPE_MISMATCH?;
return value.b;
}
/**
* @require o.is_indexable()
* @require self.is_indexable()
**/
fn bool Object.get_bool_at(Object* o, usz index)
fn bool! Object.get_bool_at(&self, usz index)
{
Object* value = o.get_at(index);
assert(value.is_bool());
Object* value = self.get_at(index);
if (!value.is_bool()) return CastResult.TYPE_MISMATCH?;
return value.b;
}
/**
* @require o.is_keyable()
* @require self.is_keyable()
**/
fn double! Object.get_float(Object* o, String key)
fn double! Object.get_float(&self, String key)
{
Object* value = o.get(key)?;
Object* value = self.get(key)!;
switch (value.type.kindof)
{
case SIGNED_INT:
@@ -441,16 +445,16 @@ fn double! Object.get_float(Object* o, String key)
case FLOAT:
return value.f;
default:
unreachable();
return CastResult.TYPE_MISMATCH?;
}
}
/**
* @require o.is_indexable()
* @require self.is_indexable()
**/
fn double Object.get_float_at(Object* o, usz index)
fn double! Object.get_float_at(&self, usz index)
{
Object* value = o.get_at(index);
Object* value = self.get_at(index);
switch (value.type.kindof)
{
case SIGNED_INT:
@@ -460,19 +464,19 @@ fn double Object.get_float_at(Object* o, usz index)
case FLOAT:
return value.f;
default:
unreachable();
return CastResult.TYPE_MISMATCH?;
}
}
fn Object* Object.get_or_create_obj(Object* o, String key)
fn Object* Object.get_or_create_obj(&self, String key)
{
if (try obj = o.get(key) && !obj.is_null()) return obj;
Object* container = new_obj();
o.set(key, container);
if (try obj = self.get(key) && !obj.is_null()) return obj;
Object* container = new_obj(self.allocator);
self.set(key, container);
return container;
}
typedef ObjectInternalMap @private = HashMap<String, Object*>;
typedef ObjectInternalList @private = List<Object*>;
typedef ObjectInternalMapEntry @private = Entry<String, Object*>;
def ObjectInternalMap = HashMap(<String, Object*>) @private;
def ObjectInternalList = List(<Object*>) @private;
def ObjectInternalMapEntry = Entry(<String, Object*>) @private;

View File

@@ -20,94 +20,126 @@
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
module std::collections::priorityqueue<Type>;
module std::collections::priorityqueue(<Type>);
import std::collections::priorityqueue::private;
distinct PriorityQueue = inline PrivatePriorityQueue(<Type, false>);
distinct PriorityQueueMax = inline PrivatePriorityQueue(<Type, true>);
module std::collections::priorityqueue::private(<Type, MAX>);
import std::collections::list;
typedef Heap = List<Type>;
def Heap = List(<Type>);
struct PriorityQueue
struct PrivatePriorityQueue (Printable)
{
Heap heap;
bool max; // true if max-heap, false if min-heap
}
fn void PriorityQueue.push(PriorityQueue* pq, Type element)
fn void PrivatePriorityQueue.init_new(&self, usz initial_capacity = 16, Allocator* allocator = mem::heap()) @inline
{
pq.heap.push(element);
usz i = pq.heap.len() - 1;
self.heap.init_new(initial_capacity, allocator);
}
fn void PrivatePriorityQueue.init_temp(&self, usz initial_capacity = 16) @inline
{
self.heap.init_new(initial_capacity, mem::temp()) @inline;
}
fn void PrivatePriorityQueue.push(&self, Type element)
{
self.heap.push(element);
usz i = self.heap.len() - 1;
while (i > 0)
{
usz parent = (i - 1) / 2;
if ((pq.max && greater(pq.heap.get(i), pq.heap.get(parent))) || (!pq.max && less(pq.heap.get(i), pq.heap.get(parent))))
{
pq.heap.swap(i, parent);
i = parent;
continue;
}
break;
Type item = self.heap[i];
Type parent_item = self.heap[parent];
$if MAX:
bool ok = greater(item, parent_item);
$else
bool ok = less(item, parent_item);
$endif
if (!ok) break;
self.heap.swap(i, parent);
i = parent;
}
}
/**
* @require pq != null
* @require self != null
*/
fn Type! PriorityQueue.pop(PriorityQueue* pq)
fn Type! PrivatePriorityQueue.pop(&self)
{
usz i = 0;
usz len = pq.heap.len() @inline;
if (!len) return IteratorResult.NO_MORE_ELEMENT!;
usz len = self.heap.len();
if (!len) return IteratorResult.NO_MORE_ELEMENT?;
usz newCount = len - 1;
pq.heap.swap(0, newCount);
self.heap.swap(0, newCount);
while ((2 * i + 1) < newCount)
{
usz j = 2 * i + 1;
if (((j + 1) < newCount) &&
((pq.max && greater(pq.heap.get(j + 1), pq.heap[j]))
|| (!pq.max && less(pq.heap.get(j + 1), pq.heap.get(j)))))
Type itemj = self.heap[j];
if ((j + 1) < newCount)
{
j++;
Type nextj = self.heap[j + 1];
$if MAX:
bool ok = greater(nextj, itemj);
$else
bool ok = less(nextj, itemj);
$endif
if (ok) j++;
}
if ((pq.max && less(pq.heap.get(i), pq.heap.get(j))) || (!pq.max && greater(pq.heap.get(i), pq.heap.get(j))))
{
pq.heap.swap(i, j);
i = j;
continue;
}
break;
Type item = self.heap[i];
$if MAX:
bool ok = less(item, itemj);
$else
bool ok = greater(item, itemj);
$endif
if (!ok) break;
self.heap.swap(i, j);
i = j;
}
return pq.heap.pop();
return self.heap.pop();
}
fn Type! PrivatePriorityQueue.peek(&self)
{
if (!self.len()) return IteratorResult.NO_MORE_ELEMENT?;
return self.heap.get(0);
}
fn void PrivatePriorityQueue.free(&self)
{
self.heap.free();
}
fn usz PrivatePriorityQueue.len(&self) @operator(len)
{
return self.heap.len();
}
fn bool PrivatePriorityQueue.is_empty(&self)
{
return self.heap.is_empty();
}
/**
* @require pq != null
* @require index < self.len()
*/
fn Type! PriorityQueue.peek(PriorityQueue* pq)
fn Type PrivatePriorityQueue.peek_at(&self, usz index) @operator([])
{
if (!pq.len()) return IteratorResult.NO_MORE_ELEMENT!;
return pq.heap.get(0);
return self.heap[index];
}
/**
* @require pq != null
*/
fn void PriorityQueue.free(PriorityQueue* pq)
fn usz! PrivatePriorityQueue.to_format(&self, Formatter* formatter) @dynamic
{
pq.heap.free();
return self.heap.to_format(formatter);
}
/**
* @require pq != null
*/
fn usz PriorityQueue.len(PriorityQueue* pq) @operator(len)
fn String PrivatePriorityQueue.to_new_string(&self, Allocator* allocator = mem::heap()) @dynamic
{
return pq.heap.len();
return self.heap.to_new_string(allocator);
}
/**
* @require pq != null, index < pq.len()
*/
fn Type PriorityQueue.peek_at(PriorityQueue* pq, usz index) @operator([])
{
return pq.heap[index];
}

View File

@@ -0,0 +1,84 @@
/**
* @require Type.is_ordered : "The type must be ordered"
**/
module std::collections::range(<Type>);
struct Range (Printable)
{
Type start;
Type end;
}
fn usz Range.len(&self) @operator(len)
{
if (self.end < self.start) return 0;
return (usz)(self.end - self.start) + 1;
}
fn bool Range.contains(&self, Type value) @inline
{
return value >= self.start && value <= self.end;
}
/**
* @require index < self.len() : "Can't index into an empty range"
**/
fn Type Range.get(&self, usz index) @operator([])
{
return (Type)(self.start + (usz)index);
}
fn String Range.to_new_string(&self, Allocator* allocator = mem::heap()) @dynamic
{
return string::new_format("[%s..%s]", self.start, self.end, .allocator = allocator);
}
fn String Range.to_tstring(&self)
{
return self.to_new_string(mem::temp());
}
fn usz! Range.to_format(&self, Formatter* formatter) @dynamic
{
return formatter.printf("[%s..%s]", self.start, self.end)!;
}
struct ExclusiveRange (Printable)
{
Type start;
Type end;
}
fn usz ExclusiveRange.len(&self) @operator(len)
{
if (self.end < self.start) return 0;
return (usz)(self.end - self.start);
}
fn bool ExclusiveRange.contains(&self, Type value) @inline
{
return value >= self.start && value < self.end;
}
fn usz! ExclusiveRange.to_format(&self, Formatter* formatter) @dynamic
{
return formatter.printf("[%s..<%s]", self.start, self.end)!;
}
fn String ExclusiveRange.to_new_string(&self, Allocator* allocator = mem::heap()) @dynamic
{
return string::new_format("[%s..<%s]", self.start, self.end, .allocator = allocator);
}
fn String ExclusiveRange.to_tstring(&self)
{
return self.to_new_string(mem::temp());
}
/**
* @require index < self.len() : "Can't index into an empty range"
**/
fn Type ExclusiveRange.get(&self, usz index) @operator([])
{
return (Type)(self.start + index);
}

View File

@@ -0,0 +1,103 @@
module std::collections::ringbuffer(<Type, SIZE>);
struct RingBuffer
{
Type[SIZE] buf;
usz written;
usz head;
}
fn void RingBuffer.init(&self) @inline
{
*self = {};
}
fn void RingBuffer.putc(&self, Type c)
{
if (self.written < SIZE)
{
self.buf[self.written] = c;
self.written++;
}
else
{
self.buf[self.head] = c;
self.head = (self.head + 1) % SIZE;
}
}
fn Type RingBuffer.getc(&self, usz index)
{
index %= SIZE;
usz avail = SIZE - self.head;
if (index < avail)
{
return self.buf[self.head + index];
}
return self.buf[index - avail];
}
fn Type! RingBuffer.popc(&self)
{
switch
{
case self.written == 0:
return SearchResult.MISSING?;
case self.written < SIZE:
self.written--;
return self.buf[self.written];
default:
self.head = (self.head - 1) % SIZE;
return self.buf[self.head];
}
}
fn usz RingBuffer.get(&self, usz index, Type[] buffer)
{
index %= SIZE;
if (self.written < SIZE)
{
if (index >= self.written) return 0;
usz end = self.written - index;
usz n = min(end, buffer.len);
buffer[:n] = self.buf[index:n];
return n;
}
usz end = SIZE - self.head;
if (index >= end)
{
index -= end;
if (index >= self.head) return 0;
usz n = min(self.head - index, buffer.len);
buffer[:n] = self.buf[index:n];
return n;
}
if (buffer.len <= SIZE - index)
{
usz n = buffer.len;
buffer[:n] = self.buf[self.head + index:n];
return n;
}
usz n1 = SIZE - index;
buffer[:n1] = self.buf[self.head + index:n1];
buffer = buffer[n1..];
index -= n1;
usz n2 = min(self.head - index, buffer.len);
buffer[:n2] = self.buf[index:n2];
return n1 + n2;
}
fn void RingBuffer.push(&self, Type[] buffer)
{
usz i;
while (self.written < SIZE && i < buffer.len)
{
self.buf[self.written] = buffer[i++];
self.written++;
}
foreach (c : buffer[i..])
{
self.buf[self.head] = c;
self.head = (self.head + 1) % SIZE;
}
}

View File

@@ -0,0 +1,16 @@
module std::collections::tuple(<Type1, Type2>);
struct Tuple
{
Type1 first;
Type2 second;
}
module std::collections::triple(<Type1, Type2, Type3>);
struct Triple
{
Type1 first;
Type2 second;
Type3 third;
}

View File

@@ -3,147 +3,114 @@
// a copy of which can be found in the LICENSE_STDLIB file.
module std::core::mem::allocator;
struct ArenaAllocator
struct ArenaAllocator (Allocator)
{
inline Allocator allocator;
char[] data;
usz used;
}
/**
* Initialize a memory arena for use using the provided bytes.
*
* @require this != null
**/
fn void ArenaAllocator.init(ArenaAllocator* this, char[] data)
fn void ArenaAllocator.init(&self, char[] data)
{
this.function = &arena_allocator_function;
this.data = data;
this.used = 0;
self.data = data;
self.used = 0;
}
/**
* @require this != null
**/
fn void ArenaAllocator.reset(ArenaAllocator* this)
fn void ArenaAllocator.clear(&self)
{
this.used = 0;
self.used = 0;
}
struct ArenaAllocatorHeader
struct ArenaAllocatorHeader @local
{
usz size;
char[*] data;
}
/**
* @require !alignment || math::is_power_of_2(alignment)
* @require data `unexpectedly missing the allocator`
*/
fn void*! arena_allocator_function(Allocator* data, usz size, usz alignment, usz offset, void* old_pointer, AllocationKind kind) @private
fn void ArenaAllocator.release(&self, void* ptr, bool) @dynamic
{
ArenaAllocator* arena = (ArenaAllocator*)data;
bool clear = false;
switch (kind)
if (!ptr) return;
assert((uptr)ptr >= (uptr)self.data.ptr, "Pointer originates from a different allocator.");
ArenaAllocatorHeader* header = ptr - ArenaAllocatorHeader.sizeof;
// Reclaim memory if it's the last element.
if (ptr + header.size == &self.data[self.used])
{
case CALLOC:
case ALIGNED_CALLOC:
clear = true;
nextcase;
case ALLOC:
case ALIGNED_ALLOC:
assert(!old_pointer, "Unexpected old pointer for alloc.");
if (!size) return null;
alignment = alignment_for_allocation(alignment);
void* mem = arena._alloc(size, alignment, offset)?;
if (clear) mem::clear(mem, size, mem::DEFAULT_MEM_ALIGNMENT);
return mem;
case ALIGNED_REALLOC:
case REALLOC:
if (!size) nextcase FREE;
if (!old_pointer) nextcase ALLOC;
alignment = alignment_for_allocation(alignment);
return arena._realloc(old_pointer, size, alignment, offset)?;
case ALIGNED_FREE:
case FREE:
if (!old_pointer) return null;
assert((uptr)old_pointer >= (uptr)arena.data.ptr, "Pointer originates from a different allocator.");
ArenaAllocatorHeader* header = old_pointer - ArenaAllocatorHeader.sizeof;
// Reclaim memory if it's the last element.
if (old_pointer + header.size == &arena.data[arena.used])
{
arena.used -= header.size + ArenaAllocatorHeader.sizeof;
}
return null;
case MARK:
return (void*)(uptr)arena.used;
case RESET:
arena.used = size;
return null;
self.used -= header.size + ArenaAllocatorHeader.sizeof;
}
unreachable();
}
fn usz ArenaAllocator.mark(&self) @dynamic => self.used;
fn void ArenaAllocator.reset(&self, usz mark) @dynamic => self.used = mark;
/**
* @require alignment > 0 `alignment must be non zero`
* @require math::is_power_of_2(alignment)
* @require size > 0
* @require !alignment || math::is_power_of_2(alignment)
* @require alignment <= mem::MAX_MEMORY_ALIGNMENT `alignment too big`
* @require offset <= mem::MAX_MEMORY_ALIGNMENT `offset too big`
* @require offset <= size && offset >= 0
* @require mem::aligned_offset(offset, ArenaAllocatorHeader.alignof) == offset
* @require this != null
**/
fn void*! ArenaAllocator._alloc(ArenaAllocator* this, usz size, usz alignment, usz offset) @private
fn void*! ArenaAllocator.acquire(&self, usz size, bool clear, usz alignment, usz offset) @dynamic
{
usz total_len = this.data.len;
if (size > total_len) return AllocationFailure.CHUNK_TOO_LARGE!;
void* start_mem = this.data.ptr;
void* unaligned_pointer_to_offset = start_mem + this.used + ArenaAllocatorHeader.sizeof + offset;
if (!size) return null;
alignment = alignment_for_allocation(alignment);
usz total_len = self.data.len;
if (size > total_len) return AllocationFailure.CHUNK_TOO_LARGE?;
void* start_mem = self.data.ptr;
void* unaligned_pointer_to_offset = start_mem + self.used + ArenaAllocatorHeader.sizeof + offset;
void* aligned_pointer_to_offset = mem::aligned_pointer(unaligned_pointer_to_offset, alignment);
usz end = (usz)(aligned_pointer_to_offset - this.data.ptr) + size - offset;
if (end > total_len) return AllocationFailure.OUT_OF_MEMORY!;
this.used = end;
void *mem = aligned_pointer_to_offset - offset;
usz end = (usz)(aligned_pointer_to_offset - self.data.ptr) + size - offset;
if (end > total_len) return AllocationFailure.OUT_OF_MEMORY?;
self.used = end;
void* mem = aligned_pointer_to_offset - offset;
ArenaAllocatorHeader* header = mem - ArenaAllocatorHeader.sizeof;
header.size = size;
if (clear) mem::clear(mem, size, mem::DEFAULT_MEM_ALIGNMENT);
return mem;
}
/**
* @require alignment > 0 `alignment must be non zero`
* @require math::is_power_of_2(alignment)
* @require size > 0
* @require !alignment || math::is_power_of_2(alignment)
* @require alignment <= mem::MAX_MEMORY_ALIGNMENT `alignment too big`
* @require offset <= mem::MAX_MEMORY_ALIGNMENT `offset too big`
* @require offset <= size && offset >= 0
* @require mem::aligned_offset(offset, ArenaAllocatorHeader.alignof) == offset
* @require this != null
**/
fn void*! ArenaAllocator._realloc(ArenaAllocator* this, void *old_pointer, usz size, usz alignment, usz offset) @private
fn void*! ArenaAllocator.resize(&self, void *old_pointer, usz size, usz alignment, usz offset) @dynamic
{
assert(old_pointer >= this.data.ptr, "Pointer originates from a different allocator.");
usz total_len = this.data.len;
if (size > total_len) return AllocationFailure.CHUNK_TOO_LARGE!;
if (!size)
{
self.release(old_pointer, alignment > 0);
return null;
}
if (!old_pointer)
{
return self.acquire(size, true, alignment, offset);
}
alignment = alignment_for_allocation(alignment);
assert(old_pointer >= self.data.ptr, "Pointer originates from a different allocator.");
usz total_len = self.data.len;
if (size > total_len) return AllocationFailure.CHUNK_TOO_LARGE?;
ArenaAllocatorHeader* header = old_pointer - ArenaAllocatorHeader.sizeof;
usz old_size = header.size;
// Do last allocation and alignment match?
if (&this.data[this.used] == old_pointer + old_size && mem::ptr_is_aligned(old_pointer + offset, alignment))
if (&self.data[self.used] == old_pointer + old_size && mem::ptr_is_aligned(old_pointer + offset, alignment))
{
if (old_size >= size)
{
this.used -= old_size - size;
}
else
{
usz new_used = this.used + size - old_size;
if (new_used > total_len) return AllocationFailure.OUT_OF_MEMORY!;
this.used = new_used;
}
header.size = size;
return old_pointer;
{
self.used -= old_size - size;
}
else
{
usz new_used = self.used + size - old_size;
if (new_used > total_len) return AllocationFailure.OUT_OF_MEMORY?;
self.used = new_used;
}
header.size = size;
return old_pointer;
}
// Otherwise just allocate new memory.
void* mem = this._alloc(size, alignment, offset)?;
void* mem = self.acquire(size, false, alignment, offset)!;
mem::copy(mem, old_pointer, old_size, mem::DEFAULT_MEM_ALIGNMENT, mem::DEFAULT_MEM_ALIGNMENT);
return mem;
}

View File

@@ -1,12 +1,10 @@
// Copyright (c) 2021 Christoffer Lerno. All rights reserved.
// Use of this source code is governed by the MIT license
// a copy of which can be found in the LICENSE_STDLIB file.
module std::core::mem::allocator;
struct DynamicArenaAllocator
struct DynamicArenaAllocator (Allocator)
{
inline Allocator allocator;
Allocator* backing_allocator;
DynamicArenaPage* page;
DynamicArenaPage* unused_page;
@@ -14,48 +12,44 @@ struct DynamicArenaAllocator
}
/**
* @param [&inout] allocator
* @require page_size >= 128
* @require this != null
**/
fn void DynamicArenaAllocator.init(DynamicArenaAllocator* this, usz page_size, Allocator* backing_allocator = mem::heap())
fn void DynamicArenaAllocator.init(&self, usz page_size, Allocator* allocator)
{
this.function = &dynamic_arena_allocator_function;
this.page = null;
this.unused_page = null;
this.page_size = page_size;
this.backing_allocator = backing_allocator;
self.page = null;
self.unused_page = null;
self.page_size = page_size;
self.backing_allocator = allocator;
}
/**
* @require this != null
**/
fn void DynamicArenaAllocator.destroy(DynamicArenaAllocator* this)
fn void DynamicArenaAllocator.free(&self)
{
DynamicArenaPage* page = this.page;
DynamicArenaPage* page = self.page;
while (page)
{
DynamicArenaPage* next_page = page.prev_arena;
free(page, .using = this.backing_allocator);
self.backing_allocator.free(page);
page = next_page;
}
page = this.unused_page;
page = self.unused_page;
while (page)
{
DynamicArenaPage* next_page = page.prev_arena;
free(page, .using = this.backing_allocator);
self.backing_allocator.free(page);
page = next_page;
}
this.page = null;
this.unused_page = null;
self.page = null;
self.unused_page = null;
}
struct DynamicArenaPage
struct DynamicArenaPage @local
{
void* memory;
void* prev_arena;
usz total;
usz used;
void* last_ptr;
void* current_stack_ptr;
}
struct DynamicArenaChunk @local
@@ -64,26 +58,34 @@ struct DynamicArenaChunk @local
}
/**
* @require ptr && this
* @require this.page `tried to free pointer on invalid allocator`
* @require self.page `tried to free pointer on invalid allocator`
*/
fn void DynamicArenaAllocator.free(DynamicArenaAllocator* this, void* ptr) @private
fn void DynamicArenaAllocator.release(&self, void* ptr, bool) @dynamic
{
DynamicArenaPage* current_page = this.page;
if (ptr == current_page.last_ptr)
if (!ptr) return;
DynamicArenaPage* current_page = self.page;
if (ptr == current_page.current_stack_ptr)
{
current_page.used = (usz)((ptr - DEFAULT_SIZE_PREFIX) - current_page.memory);
}
current_page.last_ptr = null;
current_page.current_stack_ptr = null;
}
/**
* @require old_pointer && size > 0
* @require this.page `tried to realloc pointer on invalid allocator`
* @require self.page `tried to realloc pointer on invalid allocator`
*/
fn void*! DynamicArenaAllocator._realloc(DynamicArenaAllocator* this, void* old_pointer, usz size, usz alignment, usz offset) @local
fn void*! DynamicArenaAllocator.resize(&self, void* old_pointer, usz size, usz alignment, usz offset) @dynamic
{
DynamicArenaPage* current_page = this.page;
if (!size)
{
self.release(old_pointer, alignment > 0);
return null;
}
if (!old_pointer)
{
return self.acquire(size, true, alignment, offset);
}
DynamicArenaPage* current_page = self.page;
alignment = alignment_for_allocation(alignment);
usz* old_size_ptr = old_pointer - DEFAULT_SIZE_PREFIX;
usz old_size = *old_size_ptr;
@@ -91,13 +93,13 @@ fn void*! DynamicArenaAllocator._realloc(DynamicArenaAllocator* this, void* old_
if (old_size >= size && mem::ptr_is_aligned(old_pointer, alignment))
{
*old_size_ptr = size;
if (current_page.last_ptr == old_pointer)
if (current_page.current_stack_ptr == old_pointer)
{
current_page.used = (usz)((old_pointer - DEFAULT_SIZE_PREFIX) - current_page.memory);
}
return old_pointer;
}
if REUSE: (current_page.last_ptr == old_pointer && mem::ptr_is_aligned(old_pointer, alignment))
if REUSE: (current_page.current_stack_ptr == old_pointer && mem::ptr_is_aligned(old_pointer, alignment))
{
assert(size > old_size);
usz add_size = size - old_size;
@@ -106,142 +108,99 @@ fn void*! DynamicArenaAllocator._realloc(DynamicArenaAllocator* this, void* old_
current_page.used += add_size;
return old_pointer;
}
void* new_mem = this._alloc(size, alignment, offset)?;
void* new_mem = self.acquire(size, false, alignment, offset)!;
mem::copy(new_mem, old_pointer, old_size, mem::DEFAULT_MEM_ALIGNMENT);
return new_mem;
}
fn void DynamicArenaAllocator.reset(DynamicArenaAllocator* this) @private
fn void DynamicArenaAllocator.reset(&self, usz mark = 0) @dynamic
{
DynamicArenaPage* page = this.page;
DynamicArenaPage** unused_page_ptr = &this.unused_page;
while (page)
{
DynamicArenaPage* next_page = page.prev_arena;
page.used = 0;
DynamicArenaPage* prev_unused = *unused_page_ptr;
*unused_page_ptr = page;
page.prev_arena = prev_unused;
page = next_page;
}
this.page = page;
assert(mark == 0, "Unexpectedly reset dynamic arena allocator with mark %d", mark);
DynamicArenaPage* page = self.page;
DynamicArenaPage** unused_page_ptr = &self.unused_page;
while (page)
{
DynamicArenaPage* next_page = page.prev_arena;
page.used = 0;
DynamicArenaPage* prev_unused = *unused_page_ptr;
*unused_page_ptr = page;
page.prev_arena = prev_unused;
page = next_page;
}
self.page = page;
}
/**
* @require math::is_power_of_2(alignment)
* @require size > 0
*/
fn void*! DynamicArenaAllocator._alloc_new(DynamicArenaAllocator* this, usz size, usz alignment, usz offset) @local
fn void*! DynamicArenaAllocator._alloc_new(&self, usz size, usz alignment, usz offset) @local
{
// First, make sure that we can align it, extending the page size if needed.
usz page_size = max(this.page_size, mem::aligned_offset(size + DynamicArenaChunk.sizeof + offset, alignment) - offset);
usz page_size = max(self.page_size, mem::aligned_offset(size + DynamicArenaChunk.sizeof + offset, alignment) - offset);
// Grab the page without alignment (we do it ourselves)
void* mem = this.backing_allocator.alloc(page_size)?;
DynamicArenaPage*! page = malloc(DynamicArenaPage, .using = this.backing_allocator);
void* mem = self.backing_allocator.alloc_checked(page_size)!;
DynamicArenaPage*! page = self.backing_allocator.new(DynamicArenaPage);
if (catch err = page)
{
free(mem, .using = this.backing_allocator);
return err!;
self.backing_allocator.free(mem);
return err?;
}
page.memory = mem;
void* mem_start = mem::aligned_pointer(mem + offset + DynamicArenaChunk.sizeof, alignment) - offset;
assert(mem_start + size < mem + page_size);
DynamicArenaChunk* chunk = (DynamicArenaChunk*)mem_start - 1;
chunk.size = size;
page.prev_arena = this.page;
page.memory = mem;
void* mem_start = mem::aligned_pointer(mem + offset + DynamicArenaChunk.sizeof, alignment) - offset;
assert(mem_start + size < mem + page_size);
DynamicArenaChunk* chunk = (DynamicArenaChunk*)mem_start - 1;
chunk.size = size;
page.prev_arena = self.page;
page.total = page_size;
page.used = mem_start + size - page.memory;
this.page = page;
page.last_ptr = mem_start;
self.page = page;
page.current_stack_ptr = mem_start;
return mem_start;
}
/**
* @require !alignment || math::is_power_of_2(alignment)
* @require size > 0
* @require this
*/
fn void*! DynamicArenaAllocator._alloc(DynamicArenaAllocator* this, usz size, usz alignment, usz offset) @local
fn void*! DynamicArenaAllocator.acquire(&self, usz size, bool clear, usz alignment, usz offset) @dynamic
{
if (!size) return null;
alignment = alignment_for_allocation(alignment);
DynamicArenaPage* page = this.page;
if (!page && this.unused_page)
{
this.page = page = this.unused_page;
this.unused_page = page.prev_arena;
page.prev_arena = null;
}
if (!page) return this._alloc_new(size, alignment, offset);
void* start = mem::aligned_pointer(page.memory + page.used + DynamicArenaChunk.sizeof + offset, alignment) - offset;
usz new_used = start - page.memory + size;
if ALLOCATE_NEW: (new_used > page.total)
{
if ((page = this.unused_page))
{
start = mem::aligned_pointer(page.memory + page.used + DynamicArenaChunk.sizeof + offset, alignment) - offset;
new_used = start + size - page.memory;
if (page.total >= new_used)
{
this.unused_page = page.prev_arena;
page.prev_arena = this.page;
this.page = page;
break ALLOCATE_NEW;
}
}
return this._alloc_new(size, alignment, offset);
}
page.used = new_used;
assert(start + size == page.memory + page.used);
void* mem = start;
DynamicArenaChunk* chunk = (DynamicArenaChunk*)mem - 1;
chunk.size = size;
return mem;
}
/**
* @require !alignment || math::is_power_of_2(alignment)
* @require data `unexpectedly missing the allocator`
*/
fn void*! dynamic_arena_allocator_function(Allocator* data, usz size, usz alignment, usz offset, void* old_pointer, AllocationKind kind) @private
{
DynamicArenaAllocator* allocator = (DynamicArenaAllocator*)data;
switch (kind)
{
case CALLOC:
case ALIGNED_CALLOC:
assert(!old_pointer, "Unexpected no old pointer for calloc.");
if (!size) return null;
void* mem = allocator._alloc(size, alignment, offset)?;
mem::clear(mem, size, mem::DEFAULT_MEM_ALIGNMENT);
return mem;
case ALLOC:
case ALIGNED_ALLOC:
assert(!old_pointer, "Unexpected no old pointer for alloc.");
if (!size) return null;
return allocator._alloc(size, alignment, offset);
case REALLOC:
case ALIGNED_REALLOC:
if (!size)
DynamicArenaPage* page = self.page;
void* ptr = {|
if (!page && self.unused_page)
{
self.page = page = self.unused_page;
self.unused_page = page.prev_arena;
page.prev_arena = null;
}
if (!page) return self._alloc_new(size, alignment, offset);
void* start = mem::aligned_pointer(page.memory + page.used + DynamicArenaChunk.sizeof + offset, alignment) - offset;
usz new_used = start - page.memory + size;
if ALLOCATE_NEW: (new_used > page.total)
{
if ((page = self.unused_page))
{
if (!old_pointer) return null;
allocator.free(old_pointer);
return null;
start = mem::aligned_pointer(page.memory + page.used + DynamicArenaChunk.sizeof + offset, alignment) - offset;
new_used = start + size - page.memory;
if (page.total >= new_used)
{
self.unused_page = page.prev_arena;
page.prev_arena = self.page;
self.page = page;
break ALLOCATE_NEW;
}
}
if (!old_pointer) return allocator._alloc(size, alignment, offset);
void* mem = allocator._realloc(old_pointer, size, alignment, offset)?;
return mem;
case ALIGNED_FREE:
case FREE:
if (!old_pointer) return null;
allocator.free(old_pointer);
return null;
case MARK:
unreachable("Tried to mark a dynamic arena");
case RESET:
allocator.reset();
return null;
}
unreachable();
return self._alloc_new(size, alignment, offset);
}
page.used = new_used;
assert(start + size == page.memory + page.used);
void* mem = start;
DynamicArenaChunk* chunk = (DynamicArenaChunk*)mem - 1;
chunk.size = size;
return mem;
|}!;
if (clear) mem::clear(ptr, size, mem::DEFAULT_MEM_ALIGNMENT);
return ptr;
}

View File

@@ -1,99 +1,95 @@
// Copyright (c) 2021 Christoffer Lerno. All rights reserved.
// Copyright (c) 2021-2023 Christoffer Lerno. All rights reserved.
// Use of this source code is governed by the MIT license
// a copy of which can be found in the LICENSE_STDLIB file.
module std::core::mem::allocator;
typedef MemoryAllocFn = fn char[]!(usz);
struct SimpleHeapAllocator
struct SimpleHeapAllocator (Allocator)
{
inline Allocator allocator;
MemoryAllocFn alloc_fn;
Header* free_list;
}
/**
* @require this "Unexpectedly missing the allocator"
* @require allocator "An underlying memory provider must be given"
* @require !this.free_list "The allocator may not be already initialized"
* @require !self.free_list "The allocator may not be already initialized"
**/
fn void SimpleHeapAllocator.init(SimpleHeapAllocator* this, MemoryAllocFn allocator)
fn void SimpleHeapAllocator.init(&self, MemoryAllocFn allocator)
{
this.alloc_fn = allocator;
this.allocator = { &simple_heap_allocator_function };
this.free_list = null;
self.alloc_fn = allocator;
self.free_list = null;
}
/**
* @require !alignment || math::is_power_of_2(alignment)
* @require this `unexpectedly missing the allocator`
*/
fn void*! simple_heap_allocator_function(Allocator* this, usz size, usz alignment, usz offset, void* old_pointer, AllocationKind kind) @private
fn void*! SimpleHeapAllocator.acquire(&self, usz size, bool clear, usz alignment, usz offset) @dynamic
{
SimpleHeapAllocator* heap = (SimpleHeapAllocator*)this;
switch (kind)
{
case ALIGNED_ALLOC:
return @aligned_alloc(heap._alloc, size, alignment, offset);
case ALLOC:
return heap._alloc(size);
case ALIGNED_CALLOC:
return @aligned_calloc(heap._calloc, size, alignment, offset);
case CALLOC:
return heap._calloc(size);
case ALIGNED_REALLOC:
if (!size) nextcase ALIGNED_FREE;
if (!old_pointer) nextcase ALIGNED_CALLOC;
return @aligned_realloc(heap._calloc, heap._free, old_pointer, size, alignment, offset);
case REALLOC:
if (!size) nextcase FREE;
if (!old_pointer) nextcase CALLOC;
return heap._realloc(old_pointer, size);
case RESET:
return AllocationFailure.UNSUPPORTED_OPERATION!;
case ALIGNED_FREE:
@aligned_free(heap._free, old_pointer)?;
return null;
case FREE:
heap._free(old_pointer);
return null;
default:
unreachable();
if (!size) return null;
if (clear)
{
return alignment > 0 ? @aligned_calloc(self._calloc, size, alignment, offset) : self._calloc(size);
}
return alignment > 0 ? @aligned_alloc(self._alloc, size, alignment, offset) : self._alloc(size);
}
fn void*! SimpleHeapAllocator.resize(&self, void* old_pointer, usz size, usz alignment, usz offset) @dynamic
{
if (!size)
{
self.release(old_pointer, alignment > 0);
return null;
}
if (!old_pointer)
{
return self.acquire(size, true, alignment, offset);
}
return alignment > 0
? @aligned_realloc(self._calloc, self._free, old_pointer, size, alignment, offset)
: self._realloc(old_pointer, size);
}
fn void SimpleHeapAllocator.release(&self, void* old_pointer, bool aligned) @dynamic
{
if (aligned)
{
@aligned_free(self._free, old_pointer)!!;
}
else
{
self._free(old_pointer);
}
}
/**
* @require this && old_pointer && bytes > 0
* @require old_pointer && bytes > 0
**/
fn void*! SimpleHeapAllocator._realloc(SimpleHeapAllocator* this, void* old_pointer, usz bytes)
fn void*! SimpleHeapAllocator._realloc(&self, void* old_pointer, usz bytes) @local
{
// Find the block header.
Header* block = (Header*)old_pointer - 1;
if (block.size >= bytes) return old_pointer;
void* new = this._alloc(bytes)?;
void* new = self._alloc(bytes)!;
usz max_to_copy = math::min(block.size, bytes);
mem::copy(new, old_pointer, max_to_copy);
this._free(old_pointer);
self._free(old_pointer);
return new;
}
fn void*! SimpleHeapAllocator._calloc(SimpleHeapAllocator* this, usz bytes) @local
fn void*! SimpleHeapAllocator._calloc(&self, usz bytes) @local
{
void* data = this._alloc(bytes)?;
void* data = self._alloc(bytes)!;
mem::clear(data, bytes, mem::DEFAULT_MEM_ALIGNMENT);
return data;
}
fn void*! SimpleHeapAllocator._alloc(SimpleHeapAllocator* this, usz bytes) @local
fn void*! SimpleHeapAllocator._alloc(&self, usz bytes) @local
{
usz aligned_bytes = mem::aligned_offset(bytes, mem::DEFAULT_MEM_ALIGNMENT);
if (!this.free_list)
{
this.add_block(aligned_bytes)?;
}
if (!self.free_list)
{
self.add_block(aligned_bytes)!;
}
Header* current = this.free_list;
Header* current = self.free_list;
Header* previous = current;
while (current)
{
@@ -102,21 +98,21 @@ fn void*! SimpleHeapAllocator._alloc(SimpleHeapAllocator* this, usz bytes) @loca
case current.size >= aligned_bytes && current.size <= aligned_bytes + Header.sizeof + 64:
if (current == previous)
{
this.free_list = current.next;
self.free_list = current.next;
}
else
{
previous.next = current.next;
}
current.next = null;
return current + 1;
case current.size > aligned_bytes:
}
current.next = null;
return current + 1;
case current.size > aligned_bytes:
Header* unallocated = (Header*)((char*)current + aligned_bytes + Header.sizeof);
unallocated.size = current.size - aligned_bytes;
unallocated.next = current.next;
if (current == this.free_list)
if (current == self.free_list)
{
this.free_list = unallocated;
self.free_list = unallocated;
}
else
{
@@ -130,22 +126,22 @@ fn void*! SimpleHeapAllocator._alloc(SimpleHeapAllocator* this, usz bytes) @loca
current = current.next;
}
}
this.add_block(aligned_bytes)?;
return this.alloc(aligned_bytes);
self.add_block(aligned_bytes)!;
return self._alloc(aligned_bytes);
}
fn void! SimpleHeapAllocator.add_block(SimpleHeapAllocator* this, usz aligned_bytes) @local
fn void! SimpleHeapAllocator.add_block(&self, usz aligned_bytes) @local
{
assert(mem::aligned_offset(aligned_bytes, mem::DEFAULT_MEM_ALIGNMENT) == aligned_bytes);
char[] result = this.alloc_fn(aligned_bytes + Header.sizeof)?;
char[] result = self.alloc_fn(aligned_bytes + Header.sizeof)!;
Header* new_block = (Header*)result.ptr;
new_block.size = result.len - Header.sizeof;
new_block.next = null;
this._free(new_block + 1);
self._free(new_block + 1);
}
fn void SimpleHeapAllocator._free(SimpleHeapAllocator* this, void* ptr) @local
fn void SimpleHeapAllocator._free(&self, void* ptr) @local
{
// Empty ptr -> do nothing.
if (!ptr) return;
@@ -153,15 +149,15 @@ fn void SimpleHeapAllocator._free(SimpleHeapAllocator* this, void* ptr) @local
// Find the block header.
Header* block = (Header*)ptr - 1;
// No free list? Then just return this.
if (!this.free_list)
// No free list? Then just return self.
if (!self.free_list)
{
this.free_list = block;
self.free_list = block;
return;
}
// Find where in the list it should be inserted.
Header* current = this.free_list;
Header* current = self.free_list;
Header* prev = current;
while (current)
{
@@ -178,46 +174,46 @@ fn void SimpleHeapAllocator._free(SimpleHeapAllocator* this, void* ptr) @local
if (current)
{
// Insert after the current block.
// Are the blocks adjacent?
if (current == (Header*)((char*)(block + 1) + block.size))
{
// Merge
block.size += current.size + Header.sizeof;
block.next = current.next;
}
else
{
// Chain to current
block.next = current;
}
// Are the blocks adjacent?
if (current == (Header*)((char*)(block + 1) + block.size))
{
// Merge
block.size += current.size + Header.sizeof;
block.next = current.next;
}
else
{
// Chain to current
block.next = current;
}
}
if (prev == current)
{
// Swap new start of free list
this.free_list = block;
self.free_list = block;
}
else
{
// Prev adjacent?
if (block == (Header*)((char*)(prev + 1) + prev.size))
{
prev.size += block.size + Header.sizeof;
prev.size += block.size + Header.sizeof;
prev.next = block.next;
}
else
{
// Link prev to block
prev.next = block;
// Link prev to block
prev.next = block;
}
}
}
union Header @private
union Header @local
{
struct
{
Header* next;
usz size;
}
usz align;
usz size;
}
usz align;
}

View File

@@ -0,0 +1,63 @@
// Copyright (c) 2021 Christoffer Lerno. All rights reserved.
// Use of this source code is governed by the MIT license
// a copy of which can be found in the LICENSE_STDLIB file.
module std::core::mem::allocator;
import libc;
const LibcAllocator LIBC_ALLOCATOR = {};
distinct LibcAllocator (Allocator) = uptr;
fn void*! LibcAllocator.acquire(&self, usz bytes, bool clear, usz alignment, usz offset) @dynamic
{
assert(alignment != 0 || offset == 0);
if (clear)
{
void* data = alignment ? @aligned_calloc(fn void*(usz bytes) => libc::calloc(bytes, 1), bytes, alignment, offset)!! : libc::calloc(bytes, 1);
return data ?: AllocationFailure.OUT_OF_MEMORY?;
}
else
{
void* data = alignment ? @aligned_alloc(libc::malloc, bytes, alignment, offset)!! : libc::malloc(bytes);
if (!data) return AllocationFailure.OUT_OF_MEMORY?;
$if env::TESTING:
for (usz i = 0; i < bytes; i++) ((char*)data)[i] = 0xAA;
$endif
return data;
}
}
fn void*! LibcAllocator.resize(&self, void* old_ptr, usz new_bytes, usz alignment, usz offset) @dynamic
{
assert(alignment != 0 || offset == 0);
if (!new_bytes)
{
self.release(old_ptr, alignment > 0);
return null;
}
if (!old_ptr)
{
return self.acquire(new_bytes, true, alignment, offset);
}
if (alignment)
{
void* data = @aligned_realloc(fn void*(usz bytes) => libc::calloc(bytes, 1), libc::free, old_ptr, new_bytes, alignment, offset)!!;
return data ?: AllocationFailure.OUT_OF_MEMORY?;
}
return libc::realloc(old_ptr, new_bytes) ?: AllocationFailure.OUT_OF_MEMORY?;
}
fn void LibcAllocator.release(&self, void* old_ptr, bool aligned) @dynamic
{
if (aligned)
{
@aligned_free(libc::free, old_ptr)!!;
}
else
{
libc::free(old_ptr);
}
}

View File

@@ -1,136 +0,0 @@
// Copyright (c) 2021 Christoffer Lerno. All rights reserved.
// Use of this source code is governed by the MIT license
// a copy of which can be found in the LICENSE_STDLIB file.
module std::core::mem::allocator;
import libc;
const Allocator _NULL_ALLOCATOR @private = { &null_allocator_fn };
const Allocator _SYSTEM_ALLOCATOR @private = { &libc_allocator_fn };
fn void*! null_allocator_fn(Allocator* this, usz bytes, usz alignment, usz offset, void* old_pointer, AllocationKind kind) @private
{
switch (kind)
{
case ALLOC:
case CALLOC:
case REALLOC:
case ALIGNED_ALLOC:
case ALIGNED_REALLOC:
case ALIGNED_CALLOC:
return AllocationFailure.OUT_OF_MEMORY!;
default:
return null;
}
}
struct AlignedBlock
{
usz len;
void* start;
}
/**
* @require bytes > 0
* @require alignment > 0
**/
macro void*! @aligned_alloc(#alloc_fn, usz bytes, usz alignment, usz offset)
{
usz header = mem::aligned_offset(AlignedBlock.sizeof + offset, alignment) - offset;
$if ($checks(#alloc_fn(bytes)?))
void* data = #alloc_fn(header + bytes)?;
$else
void* data = #alloc_fn(header + bytes);
$endif
void* mem = mem::aligned_pointer(data + header + offset, alignment) - offset;
assert(mem > data);
AlignedBlock* desc = (AlignedBlock*)mem - 1;
*desc = { bytes, data };
return mem;
}
/**
* @require bytes > 0
* @require alignment > 0
**/
macro void*! @aligned_calloc(#calloc_fn, usz bytes, usz alignment, usz offset)
{
usz header = mem::aligned_offset(AlignedBlock.sizeof + offset, alignment) - offset;
$if ($checks(#calloc_fn(bytes)?))
void* data = #calloc_fn(header + bytes)?;
$else
void* data = #calloc_fn(header + bytes);
$endif
void* mem = mem::aligned_pointer(data + header + offset, alignment) - offset;
AlignedBlock* desc = (AlignedBlock*)mem - 1;
assert(mem > data);
*desc = { bytes, data };
return mem;
}
/**
* @require bytes > 0
* @require alignment > 0
**/
macro void*! @aligned_realloc(#calloc_fn, #free_fn, void* old_pointer, usz bytes, usz alignment, usz offset)
{
AlignedBlock* desc = (AlignedBlock*)old_pointer - 1;
void* data_start = desc.start;
void* new_data = @aligned_calloc(#calloc_fn, bytes, alignment, offset)?;
mem::copy(new_data, old_pointer, desc.len > bytes ? desc.len : bytes, mem::DEFAULT_MEM_ALIGNMENT, mem::DEFAULT_MEM_ALIGNMENT);
$if ($checks(#free_fn(data_start)?))
#free_fn(data_start)?;
$else
#free_fn(data_start);
$endif
return new_data;
}
macro void! @aligned_free(#free_fn, void* old_pointer)
{
AlignedBlock* desc = (AlignedBlock*)old_pointer - 1;
$if ($checks(#free_fn(desc.start)?))
#free_fn(desc.start)?;
$else
#free_fn(desc.start);
$endif
}
fn void*! libc_allocator_fn(Allocator* unused, usz bytes, usz alignment, usz offset, void* old_pointer, AllocationKind kind) @inline
{
if (!alignment) alignment = mem::DEFAULT_MEM_ALIGNMENT;
assert(math::is_power_of_2(alignment), "Alignment was not a power of 2");
void* data;
switch (kind)
{
case ALIGNED_ALLOC:
data = @aligned_alloc(libc::malloc, bytes, alignment, offset)!!;
case ALLOC:
data = libc::malloc(bytes);
case ALIGNED_CALLOC:
data = @aligned_calloc(fn void*(usz bytes) => libc::calloc(bytes, 1), bytes, alignment, offset)!!;
case CALLOC:
data = libc::calloc(bytes, 1);
case ALIGNED_REALLOC:
if (!bytes) nextcase ALIGNED_FREE;
if (!old_pointer) nextcase ALIGNED_CALLOC;
data = @aligned_realloc(fn void*(usz bytes) => libc::calloc(bytes, 1), libc::free, old_pointer, bytes, alignment, offset)!!;
case REALLOC:
if (!bytes) nextcase FREE;
if (!old_pointer) nextcase CALLOC;
data = libc::realloc(old_pointer, bytes);
case RESET:
return AllocationFailure.UNSUPPORTED_OPERATION!;
case ALIGNED_FREE:
@aligned_free(libc::free, old_pointer)!!;
return null;
case FREE:
libc::free(old_pointer);
return null;
default:
unreachable();
}
if (!data) return AllocationFailure.OUT_OF_MEMORY!;
return data;
}

View File

@@ -1,34 +1,13 @@
module std::core::mem::allocator;
struct OnStackAllocator
struct OnStackAllocator (Allocator)
{
inline Allocator allocator;
Allocator* backing_allocator;
char[] data;
usz used;
OnStackAllocatorExtraChunk* chunk;
}
macro void @stack_mem(usz $size; @body(Allocator* mem)) @builtin
{
char[$size] buffer;
OnStackAllocator allocator;
allocator.init(&buffer, mem::heap());
defer allocator.free();
@body(&allocator);
}
macro void @stack_pool(usz $size; @body) @builtin
{
char[$size] buffer;
OnStackAllocator allocator;
allocator.init(&buffer, mem::heap());
defer allocator.free();
mem::@scoped(&allocator)
{
@body();
};
}
struct OnStackAllocatorExtraChunk @local
{
@@ -38,40 +17,35 @@ struct OnStackAllocatorExtraChunk @local
}
/**
* @param [&inout] allocator
* Initialize a memory arena for use using the provided bytes.
*
* @require this != null
**/
fn void OnStackAllocator.init(OnStackAllocator* this, char[] data, Allocator* using = mem::heap())
fn void OnStackAllocator.init(&self, char[] data, Allocator* allocator)
{
this.function = &on_stack_allocator_function;
this.data = data;
this.backing_allocator = using;
this.used = 0;
self.data = data;
self.backing_allocator = allocator;
self.used = 0;
}
/**
* @require this != null
**/
fn void OnStackAllocator.free(OnStackAllocator* this)
fn void OnStackAllocator.free(&self)
{
OnStackAllocatorExtraChunk* chunk = this.chunk;
OnStackAllocatorExtraChunk* chunk = self.chunk;
while (chunk)
{
if (chunk.is_aligned)
{
this.backing_allocator.free_aligned(chunk.data)!!;
self.backing_allocator.free_aligned(chunk.data);
}
else
{
this.backing_allocator.free(chunk.data)!!;
self.backing_allocator.free(chunk.data);
}
void* old = chunk;
chunk = chunk.prev;
this.backing_allocator.free(old)!!;
self.backing_allocator.free(old);
}
this.chunk = null;
this.used = 0;
self.chunk = null;
self.used = 0;
}
struct OnStackAllocatorHeader
@@ -80,49 +54,12 @@ struct OnStackAllocatorHeader
char[*] data;
}
/**
* @require !alignment || math::is_power_of_2(alignment)
* @require data `unexpectedly missing the allocator`
*/
fn void*! on_stack_allocator_function(Allocator* data, usz size, usz alignment, usz offset, void* old_pointer, AllocationKind kind) @private
fn void OnStackAllocator.release(&self, void* old_pointer, bool aligned) @dynamic
{
OnStackAllocator* allocator = (OnStackAllocator*)data;
bool clear = false;
switch (kind)
{
case CALLOC:
case ALIGNED_CALLOC:
clear = true;
nextcase;
case ALLOC:
case ALIGNED_ALLOC:
assert(!old_pointer, "Unexpected old pointer for alloc.");
if (!size) return null;
return on_stack_allocator_alloc(allocator, size, alignment, offset, clear, kind == AllocationKind.ALIGNED_ALLOC || kind == AllocationKind.ALIGNED_CALLOC);
case ALIGNED_REALLOC:
case REALLOC:
if (!size) nextcase FREE;
if (!old_pointer) nextcase ALLOC;
return on_stack_allocator_realloc(allocator, old_pointer, size, alignment, offset, kind == AllocationKind.ALIGNED_REALLOC);
case ALIGNED_FREE:
case FREE:
if (!old_pointer) return null;
if (allocation_in_stack_mem(allocator, old_pointer)) return null;
on_stack_allocator_remove_chunk(allocator, old_pointer);
if (kind == AllocationKind.ALIGNED_FREE)
{
allocator.backing_allocator.free_aligned(old_pointer)?;
}
else
{
allocator.backing_allocator.free(old_pointer)?;
}
return null;
case MARK:
case RESET:
return AllocationFailure.UNSUPPORTED_OPERATION!;
}
unreachable();
if (!old_pointer) return;
if (allocation_in_stack_mem(self, old_pointer)) return;
on_stack_allocator_remove_chunk(self, old_pointer);
self.release(old_pointer, aligned);
}
fn bool allocation_in_stack_mem(OnStackAllocator* a, void* ptr) @local
@@ -139,7 +76,7 @@ fn void on_stack_allocator_remove_chunk(OnStackAllocator* a, void* ptr) @local
if (chunk.data == ptr)
{
*addr = chunk.prev;
a.backing_allocator.free(chunk)!!;
a.backing_allocator.free(chunk);
return;
}
addr = &chunk.prev;
@@ -164,70 +101,52 @@ fn OnStackAllocatorExtraChunk* on_stack_allocator_find_chunk(OnStackAllocator* a
* @require alignment <= mem::MAX_MEMORY_ALIGNMENT `alignment too big`
* @require offset <= mem::MAX_MEMORY_ALIGNMENT `offset too big`
* @require offset <= size && offset >= 0
* @require mem::aligned_offset(offset, ArenaAllocatorHeader.alignof) == offset
* @require a != null
* @require mem::aligned_offset(offset, OnStackAllocatorExtraChunk.alignof) == offset
**/
fn void*! on_stack_allocator_realloc(OnStackAllocator* a, void* old_pointer, usz size, usz alignment, usz offset, bool aligned) @local @inline
fn void*! OnStackAllocator.resize(&self, void* old_pointer, usz size, usz alignment, usz offset) @dynamic
{
if (!allocation_in_stack_mem(a, old_pointer))
if (!allocation_in_stack_mem(self, old_pointer))
{
OnStackAllocatorExtraChunk* chunk = on_stack_allocator_find_chunk(a, old_pointer);
OnStackAllocatorExtraChunk* chunk = on_stack_allocator_find_chunk(self, old_pointer);
assert(chunk, "Tried to realloc pointer not belonging to the allocator");
if (aligned)
{
return chunk.data = a.backing_allocator.realloc_aligned(old_pointer, size, alignment, offset)?;
}
return chunk.data = a.backing_allocator.realloc(old_pointer, size)?;
return chunk.data = self.backing_allocator.resize(old_pointer, size, alignment, offset)!;
}
OnStackAllocatorHeader* header = old_pointer - OnStackAllocatorHeader.sizeof;
usz old_size = header.size;
void* mem = on_stack_allocator_alloc(a, size, alignment, offset, true, aligned)?;
void* mem = self.acquire(size, true, alignment, offset)!;
mem::copy(mem, old_pointer, old_size, mem::DEFAULT_MEM_ALIGNMENT, mem::DEFAULT_MEM_ALIGNMENT);
return mem;
}
import std::io;
/**
* @require size > 0
* @require alignment <= mem::MAX_MEMORY_ALIGNMENT `alignment too big`
* @require offset <= mem::MAX_MEMORY_ALIGNMENT `offset too big`
* @require offset <= size && offset >= 0
* @require mem::aligned_offset(offset, ArenaAllocatorHeader.alignof) == offset
* @require a != null
* @require offset == 0 || alignment > 0
* @require mem::aligned_offset(offset, OnStackAllocatorHeader.alignof) == offset
**/
fn void*! on_stack_allocator_alloc(OnStackAllocator* a, usz size, usz alignment, usz offset, bool clear, bool aligned) @local @inline
fn void*! OnStackAllocator.acquire(&self, usz size, bool clear, usz alignment, usz offset) @dynamic
{
if (size == 0) return null;
bool aligned = alignment > 0;
alignment = alignment_for_allocation(alignment);
usz total_len = a.data.len;
void* start_mem = a.data.ptr;
void* unaligned_pointer_to_offset = start_mem + a.used + OnStackAllocatorHeader.sizeof + offset;
usz total_len = self.data.len;
void* start_mem = self.data.ptr;
void* unaligned_pointer_to_offset = start_mem + self.used + OnStackAllocatorHeader.sizeof + offset;
void* aligned_pointer_to_offset = mem::aligned_pointer(unaligned_pointer_to_offset, alignment);
usz end = (usz)(aligned_pointer_to_offset - a.data.ptr) + size - offset;
Allocator* backing_allocator = a.backing_allocator;
usz end = (usz)(aligned_pointer_to_offset - self.data.ptr) + size - offset;
Allocator* backing_allocator = self.backing_allocator;
if (end > total_len)
{
OnStackAllocatorExtraChunk* chunk = backing_allocator.alloc(OnStackAllocatorExtraChunk.sizeof)?;
defer catch backing_allocator.free(chunk)!!;
defer try a.chunk = chunk;
*chunk = { .prev = a.chunk, .is_aligned = aligned };
void* data @noinit;
switch
{
case !aligned && !clear:
data = backing_allocator.alloc(size)?;
case aligned && !clear:
data = backing_allocator.alloc_aligned(size, alignment, offset)?;
case !aligned && clear:
data = backing_allocator.calloc(size)?;
case aligned && clear:
data = backing_allocator.calloc_aligned(size, alignment, offset)?;
}
return chunk.data = data;
OnStackAllocatorExtraChunk* chunk = backing_allocator.alloc_checked(OnStackAllocatorExtraChunk.sizeof)!;
defer catch backing_allocator.free(chunk);
defer try self.chunk = chunk;
*chunk = { .prev = self.chunk, .is_aligned = aligned };
return chunk.data = backing_allocator.acquire(size, clear, aligned ? alignment : 0, offset)!;
}
a.used = end;
self.used = end;
void *mem = aligned_pointer_to_offset - offset;
OnStackAllocatorHeader* header = mem - OnStackAllocatorHeader.sizeof;
header.size = size;

View File

@@ -7,9 +7,8 @@ struct TempAllocatorChunk @local
char[*] data;
}
struct TempAllocator
struct TempAllocator (Allocator)
{
inline Allocator allocator;
Allocator* backing_allocator;
TempAllocatorPage* last_page;
usz used;
@@ -17,7 +16,6 @@ struct TempAllocator
char[*] data;
}
const usz PAGE_IS_ALIGNED @private = (usz)isz.max + 1u;
@@ -31,98 +29,59 @@ struct TempAllocatorPage
char[*] data;
}
macro usz TempAllocatorPage.pagesize(TempAllocatorPage* page) => page.size & ~PAGE_IS_ALIGNED;
macro bool TempAllocatorPage.is_aligned(TempAllocatorPage* page) => page.size & PAGE_IS_ALIGNED == PAGE_IS_ALIGNED;
macro usz TempAllocatorPage.pagesize(&self) => self.size & ~PAGE_IS_ALIGNED;
macro bool TempAllocatorPage.is_aligned(&self) => self.size & PAGE_IS_ALIGNED == PAGE_IS_ALIGNED;
/**
* @require size >= 16
**/
fn TempAllocator*! new_temp(usz size, Allocator* backing_allocator)
fn TempAllocator*! new_temp(usz size, Allocator* allocator)
{
TempAllocator* allocator = malloc_checked(TempAllocator, .using = backing_allocator, .end_padding = size)?;
allocator.last_page = null;
allocator.function = &temp_allocator_function;
allocator.backing_allocator = backing_allocator;
allocator.used = 0;
allocator.capacity = size;
return allocator;
TempAllocator* temp = allocator.alloc_checked(TempAllocator.sizeof + size)!;
temp.last_page = null;
temp.backing_allocator = allocator;
temp.used = 0;
temp.capacity = size;
return temp;
}
/**
* @require !alignment || math::is_power_of_2(alignment)
* @require data `unexpectedly missing the allocator`
*/
fn void*! temp_allocator_function(Allocator* data, usz size, usz alignment, usz offset, void* old_pointer, AllocationKind kind) @private
{
TempAllocator* arena = (TempAllocator*)data;
switch (kind)
{
case CALLOC:
case ALIGNED_CALLOC:
assert(!old_pointer, "Unexpected old pointer for alloc.");
if (!size) return null;
return arena._alloc(size, alignment_for_allocation(alignment), offset, true);
case ALLOC:
case ALIGNED_ALLOC:
assert(!old_pointer, "Unexpected old pointer for alloc.");
if (!size) return null;
return arena._alloc(size, alignment_for_allocation(alignment), offset, false);
case ALIGNED_REALLOC:
case REALLOC:
if (!size) nextcase FREE;
if (!old_pointer) nextcase ALLOC;
return arena._realloc(old_pointer, size, alignment_for_allocation(alignment), offset);
case FREE:
case ALIGNED_FREE:
if (!old_pointer) return null;
arena._free(old_pointer)?;
return null;
case MARK:
return (void*)(uptr)arena.used;
case RESET:
arena._reset(size)?;
return null;
}
unreachable();
}
fn usz TempAllocator.mark(&self) @dynamic => self.used;
fn void! TempAllocator._free(TempAllocator* this, void* old_pointer) @local
fn void TempAllocator.release(&self, void* old_pointer, bool) @dynamic
{
// TODO fix free
assert((uptr)old_pointer >= (uptr)&this.data, "Pointer originates from a different allocator.");
usz old_size = *(usz*)(old_pointer - DEFAULT_SIZE_PREFIX);
if (old_pointer + old_size == &this.data[this.used])
{
this.used -= old_size;
}
if (old_pointer + old_size == &self.data[self.used])
{
self.used -= old_size;
}
}
fn void! TempAllocator._reset(TempAllocator* this, usz mark) @local
fn void TempAllocator.reset(&self, usz mark) @dynamic
{
TempAllocatorPage *last_page = this.last_page;
TempAllocatorPage *last_page = self.last_page;
while (last_page && last_page.mark > mark)
{
TempAllocatorPage *to_free = last_page;
last_page = last_page.prev_page;
this._free_page(to_free)?;
self._free_page(to_free)!!;
}
this.last_page = last_page;
this.used = mark;
self.last_page = last_page;
self.used = mark;
}
fn void! TempAllocator._free_page(TempAllocator* this, TempAllocatorPage* page) @inline @local
fn void! TempAllocator._free_page(&self, TempAllocatorPage* page) @inline @local
{
void* mem = page.start;
if (page.is_aligned()) return this.backing_allocator.free_aligned(mem);
return this.backing_allocator.free(mem);
if (page.is_aligned()) return self.backing_allocator.free_aligned(mem);
return self.backing_allocator.free(mem);
}
fn void*! TempAllocator._realloc_page(TempAllocator* this, TempAllocatorPage* page, usz size, usz alignment, usz offset) @inline @local
fn void*! TempAllocator._realloc_page(&self, TempAllocatorPage* page, usz size, usz alignment, usz offset) @inline @local
{
// Then the actual start pointer:
void* real_pointer = page.start;
// Walk backwards to find the pointer to this page.
TempAllocatorPage **pointer_to_prev = &this.last_page;
TempAllocatorPage **pointer_to_prev = &self.last_page;
// Remove the page from the list
while (*pointer_to_prev != page)
{
@@ -131,47 +90,56 @@ fn void*! TempAllocator._realloc_page(TempAllocator* this, TempAllocatorPage* pa
*pointer_to_prev = page.prev_page;
usz page_size = page.pagesize();
// Clear on size > original size.
void* data = this._alloc(size, alignment, offset, false)?;
void* data = self.acquire(size, size > page_size, alignment, offset)!;
mem::copy(data, &page.data[0], page_size, mem::DEFAULT_MEM_ALIGNMENT, mem::DEFAULT_MEM_ALIGNMENT);
if (page.is_aligned())
{
this.backing_allocator.free_aligned(real_pointer)?;
self.backing_allocator.free_aligned(real_pointer);
}
else
{
this.backing_allocator.free(real_pointer)?;
self.backing_allocator.free(real_pointer);
}
return data;
}
fn void*! TempAllocator._realloc(TempAllocator* this, void* pointer, usz size, usz alignment, usz offset) @inline @local
fn void*! TempAllocator.resize(&self, void* pointer, usz size, usz alignment, usz offset) @dynamic
{
if (!size)
{
self.release(pointer, alignment > 0);
return null;
}
if (!pointer)
{
return self.acquire(size, true, alignment, offset);
}
TempAllocatorChunk *chunk = pointer - TempAllocatorChunk.sizeof;
if (chunk.size == (usz)-1)
{
assert(this.last_page, "Realloc of non temp pointer");
assert(self.last_page, "Realloc of non temp pointer");
// First grab the page
TempAllocatorPage *page = pointer - TempAllocatorPage.sizeof;
return this._realloc_page(page, size, alignment, offset);
return self._realloc_page(page, size, alignment, offset);
}
// TODO optimize last allocation
TempAllocatorChunk* data = this._alloc(size, alignment, offset, size > chunk.size)?;
TempAllocatorChunk* data = self.acquire(size, size > chunk.size, alignment, offset)!;
mem::copy(data, pointer, chunk.size, mem::DEFAULT_MEM_ALIGNMENT, mem::DEFAULT_MEM_ALIGNMENT);
return data;
}
/**
* @require math::is_power_of_2(alignment)
* @require size > 0
* @require !alignment || math::is_power_of_2(alignment)
* @require alignment <= mem::MAX_MEMORY_ALIGNMENT `alignment too big`
* @require this != null
**/
fn void*! TempAllocator._alloc(TempAllocator* this, usz size, usz alignment, usz offset, bool clear) @local
fn void*! TempAllocator.acquire(&self, usz size, bool clear, usz alignment, usz offset) @dynamic
{
void* start_mem = &this.data;
void* starting_ptr = start_mem + this.used;
if (!size) return null;
alignment = alignment_for_allocation(alignment);
void* start_mem = &self.data;
void* starting_ptr = start_mem + self.used;
void* aligned_header_start = mem::aligned_pointer(starting_ptr, TempAllocatorChunk.alignof);
void* mem = aligned_header_start + TempAllocatorChunk.sizeof;
if (alignment > TempAllocatorChunk.alignof)
@@ -181,13 +149,13 @@ fn void*! TempAllocator._alloc(TempAllocator* this, usz size, usz alignment, usz
usz new_usage = (usz)(mem - start_mem) + size;
// Arena alignment, simple!
if (new_usage <= this.capacity)
if (new_usage <= self.capacity)
{
TempAllocatorChunk* chunk_start = mem - TempAllocatorChunk.sizeof;
chunk_start.size = size;
this.used = new_usage;
if (clear) mem::clear(mem, size, mem::DEFAULT_MEM_ALIGNMENT);
return mem;
chunk_start.size = size;
self.used = new_usage;
if (clear) mem::clear(mem, size, mem::DEFAULT_MEM_ALIGNMENT);
return mem;
}
// Fallback to backing allocator
@@ -198,13 +166,13 @@ fn void*! TempAllocator._alloc(TempAllocator* this, usz size, usz alignment, usz
{
// This is actually simpler, since it will create the offset for us.
usz total_alloc_size = TempAllocatorPage.sizeof + size;
if (clear)
if (clear)
{
page = this.backing_allocator.calloc_aligned(total_alloc_size, alignment, TempAllocatorPage.sizeof + offset)?;
page = self.backing_allocator.calloc_aligned(total_alloc_size, alignment, TempAllocatorPage.sizeof + offset)!;
}
else
{
page = this.backing_allocator.alloc_aligned(total_alloc_size, alignment, TempAllocatorPage.sizeof + offset)?;
page = self.backing_allocator.alloc_aligned(total_alloc_size, alignment, TempAllocatorPage.sizeof + offset)!;
}
page.start = page;
page.size = size | PAGE_IS_ALIGNED;
@@ -214,7 +182,7 @@ fn void*! TempAllocator._alloc(TempAllocator* this, usz size, usz alignment, usz
// Here we might need to pad
usz padded_header_size = mem::aligned_offset(TempAllocatorPage.sizeof, mem::DEFAULT_MEM_ALIGNMENT);
usz total_alloc_size = padded_header_size + size;
void* alloc = (clear ? this.backing_allocator.calloc(total_alloc_size) : this.backing_allocator.alloc(total_alloc_size))?;
void* alloc = self.backing_allocator.acquire(total_alloc_size, clear, 0, 0)!;
// Find the page.
page = alloc + padded_header_size - TempAllocatorPage.sizeof;
@@ -227,28 +195,28 @@ fn void*! TempAllocator._alloc(TempAllocator* this, usz size, usz alignment, usz
// Mark it as a page
page.ident = ~(usz)0;
// Store when it was created
page.mark = ++this.used;
page.mark = ++self.used;
// Hook up the page.
page.prev_page = this.last_page;
this.last_page = page;
page.prev_page = self.last_page;
self.last_page = page;
return &page.data[0];
}
fn void TempAllocator.print_pages(TempAllocator* this, File f)
fn void! TempAllocator.print_pages(&self, File* f)
{
TempAllocatorPage *last_page = this.last_page;
TempAllocatorPage *last_page = self.last_page;
if (!last_page)
{
f.printf("No pages.\n");
io::fprintf(f, "No pages.\n")!;
return;
}
f.printf("---Pages----\n");
io::fprintf(f, "---Pages----\n")!;
uint index = 0;
while (last_page)
{
bool is_not_aligned = !(last_page.size & (1u64 << 63));
f.printf("%d. Alloc: %d %d at %p%s\n", ++index,
last_page.size & ~(1u64 << 63), last_page.mark, &last_page.data[0], is_not_aligned ? "" : " [aligned]");
io::fprintf(f, "%d. Alloc: %d %d at %p%s\n", ++index,
last_page.size & ~(1u64 << 63), last_page.mark, &last_page.data[0], is_not_aligned ? "" : " [aligned]")!;
last_page = last_page.prev_page;
}
}

View File

@@ -4,102 +4,227 @@
module std::core::mem::allocator;
import std::collections::map;
import std::collections::list;
typedef PtrMap = HashMap<uptr, usz>;
const MAX_BACKTRACE = 16;
struct Allocation
{
void* ptr;
usz size;
void*[MAX_BACKTRACE] backtrace;
}
def AllocMap = HashMap(<uptr, Allocation>);
// A simple tracking allocator.
// It tracks allocations using a hash map but
// is not compatible with allocators that uses mark()
struct TrackingAllocator
struct TrackingAllocator (Allocator)
{
inline Allocator allocator;
Allocator* inner_allocator;
PtrMap map;
AllocMap map;
usz mem_total;
usz allocs_total;
}
/**
* Initialize a memory arena for use using the provided bytes.
* Initialize a tracking allocator to wrap (and track) another allocator.
*
* @require this != null
* @param [&inout] allocator "The allocator to track"
**/
fn void TrackingAllocator.init(TrackingAllocator* this, Allocator* allocator)
fn void TrackingAllocator.init(&self, Allocator* allocator)
{
*this = { .inner_allocator = allocator, .allocator.function = &tracking_allocator_fn };
this.map.init(.using = allocator);
}
fn void TrackingAllocator.free(TrackingAllocator* this)
{
this.map.free();
*this = {};
*self = { .inner_allocator = allocator };
self.map.init_new(.allocator = allocator);
}
/**
* @param [inout] data
* @require !alignment || math::is_power_of_2(alignment)
*/
fn void*! tracking_allocator_fn(Allocator* data, usz size, usz alignment, usz offset, void* old_pointer, AllocationKind kind) @private
* Free this tracking allocator.
**/
fn void TrackingAllocator.free(&self)
{
TrackingAllocator* this = (TrackingAllocator*)data;
void* result = this.inner_allocator.function(this.inner_allocator, size, alignment, offset, old_pointer, kind)?;
switch (kind)
{
case CALLOC:
case ALIGNED_CALLOC:
case ALLOC:
case ALIGNED_ALLOC:
this.map.set((uptr)result, size);
this.mem_total += size;
this.allocs_total++;
return result;
case REALLOC:
case ALIGNED_REALLOC:
this.map.remove((uptr)old_pointer);
this.map.set((uptr)result, size);
this.mem_total += size;
if (size > 0) this.allocs_total++;
return result;
case ALIGNED_FREE:
case FREE:
if (!old_pointer) return null;
this.map.remove((uptr)old_pointer);
return null;
case MARK:
// Unsupported
return null;
case RESET:
this.map.clear();
return null;
}
unreachable();
self.map.free();
*self = {};
}
fn usz TrackingAllocator.allocated(TrackingAllocator* this)
/**
* @return "the total allocated memory not yet freed."
**/
fn usz TrackingAllocator.allocated(&self)
{
usz allocated = 0;
@pool()
{
foreach (usz allocation : this.map.value_tlist())
{
allocated += allocation;
}
foreach (&allocation : self.map.value_tlist()) allocated += allocation.size;
};
return allocated;
}
fn usz TrackingAllocator.total_allocated(TrackingAllocator* this)
/**
* @return "the total memory allocated (freed or not)."
**/
fn usz TrackingAllocator.total_allocated(&self) => self.mem_total;
/**
* @return "the total number of allocations (freed or not)."
**/
fn usz TrackingAllocator.total_allocation_count(&self) => self.allocs_total;
fn Allocation[] TrackingAllocator.allocations_tlist(&self, Allocator* allocator)
{
return this.mem_total;
return self.map.value_tlist();
}
fn usz TrackingAllocator.total_allocation_count(TrackingAllocator* this)
/**
* @return "the number of non-freed allocations."
**/
fn usz TrackingAllocator.allocation_count(&self) => self.map.count;
fn void*! TrackingAllocator.acquire(&self, usz size, bool clear, usz alignment, usz offset) @dynamic
{
return this.allocs_total;
void* data = self.inner_allocator.acquire(size, clear, alignment, offset)!;
self.allocs_total++;
if (data)
{
void*[MAX_BACKTRACE] bt;
backtrace::capture_current(&bt);
self.map.set((uptr)data, { data, size, bt });
self.mem_total += size;
self.allocs_total++;
}
return data;
}
fn usz TrackingAllocator.allocation_count(TrackingAllocator* this)
fn void*! TrackingAllocator.resize(&self, void* old_pointer, usz size, usz alignment, usz offset) @dynamic
{
return this.map.count;
void* data = self.inner_allocator.resize(old_pointer, size, alignment, offset)!;
if (old_pointer)
{
self.map.remove((uptr)old_pointer);
}
if (data)
{
void*[MAX_BACKTRACE] bt;
backtrace::capture_current(&bt);
self.map.set((uptr)data, { data, size, bt });
self.mem_total += size;
self.allocs_total++;
}
return data;
}
fn void TrackingAllocator.release(&self, void* old_pointer, bool is_aligned) @dynamic
{
if (old_pointer)
{
if (catch self.map.remove((uptr)old_pointer))
{
assert(false, "Attempt to release untracked pointer %p, this is likely a bug.", old_pointer);
}
}
self.inner_allocator.release(old_pointer, is_aligned);
}
fn void TrackingAllocator.clear(&self)
{
self.map.clear();
}
fn void TrackingAllocator.print_report(&self) => self.fprint_report(io::stdout())!!;
fn void! TrackingAllocator.fprint_report(&self, OutStream* out)
{
usz total = 0;
usz entries = 0;
bool leaks = false;
@pool()
{
Allocation[] allocs = self.map.value_tlist();
if (allocs.len)
{
if (!allocs[0].backtrace[0])
{
io::fprintn(out, "======== Memory Report ========")!;
io::fprintn(out, "Size in bytes Address")!;
foreach (i, &allocation : allocs)
{
entries++;
total += allocation.size;
io::fprintfn(out, "%13s %p", allocation.size, allocation.ptr)!;
}
io::fprintn(out, "===============================")!;
}
else
{
io::fprintn(out, "================================== Memory Report ==================================")!;
io::fprintn(out, "Size in bytes Address Function ")!;
foreach (i, &allocation : allocs)
{
entries++;
total += allocation.size;
BacktraceList backtraces = {};
Backtrace trace = backtrace::BACKTRACE_UNKNOWN;
if (allocation.backtrace[3])
{
trace = backtrace::symbolize_backtrace(allocation.backtrace[3:1], mem::temp()).get(0) ?? backtrace::BACKTRACE_UNKNOWN;
}
if (trace.function.len) leaks = true;
io::fprintfn(out, "%13s %p %s:%d", allocation.size,
allocation.ptr, trace.function.len ? trace.function : "???",
trace.line ? trace.line : 0)!;
}
io::fprintn(out, "===================================================================================")!;
}
}
else
{
io::fprintn(out, "* NO ALLOCATIONS FOUND *")!;
}
io::fprintfn(out, "- Total currently allocated memory: %d", total)!;
io::fprintfn(out, "- Total current allocations: %d", entries)!;
io::fprintfn(out, "- Total allocations (freed and retained): %d", self.allocs_total)!;
io::fprintfn(out, "- Total allocated memory (freed and retained): %d", self.mem_total)!;
if (leaks)
{
io::fprintn(out)!;
io::fprintn(out, "Full leak report:")!;
foreach (i, &allocation : allocs)
{
if (!allocation.backtrace[3])
{
io::fprintfn(out, "Allocation %d (%d bytes) - no backtrace available.", i + 1, allocation.size)!;
continue;
}
BacktraceList backtraces = {};
usz end = MAX_BACKTRACE;
foreach (j, val : allocation.backtrace)
{
if (!val)
{
end = j;
break;
}
}
BacktraceList list = backtrace::symbolize_backtrace(allocation.backtrace[3..(end - 1)], mem::temp())!;
io::fprintfn(out, "Allocation %d (%d bytes): ", i + 1, allocation.size)!;
foreach (trace : list)
{
if (trace.has_file())
{
io::fprintfn(out, " %s (in %s:%d)", trace.function, trace.file, trace.line);
continue;
}
if (trace.is_unknown())
{
io::fprintfn(out, " ??? (in unknown)");
continue;
}
io::fprintfn(out, " %s (source unavailable)", trace.function);
}
}
}
};
}

View File

@@ -1,33 +1,50 @@
module std::core::array;
macro tconcat(arr1, arr2)
{
var $Type = $typeof(arr1[0]);
$Type[] result = array::talloc($Type, arr1.len + arr2.len);
if (arr1.len > 0)
{
mem::copy(result.ptr, &arr1[0], arr1.len * $Type.sizeof, $Type.alignof, $Type.alignof);
}
if (arr2.len > 0)
{
mem::copy(&result[arr1.len], &arr2[0], arr2.len * $Type.sizeof, $Type.alignof, $Type.alignof);
}
return result;
}
/**
* @param [in] array
* @param [in] element
* @return "the first index of the element"
* @return! SearchResult.MISSING
**/
macro index_of(array, element)
{
foreach (i, &e : array)
{
if (*e == element) return i;
}
return SearchResult.MISSING!;
return SearchResult.MISSING?;
}
macro concat(arr1, arr2)
/**
* @param [in] array
* @param [in] element
* @return "the last index of the element"
* @return! SearchResult.MISSING
**/
macro rindex_of(array, element)
{
foreach_r (i, &e : array)
{
if (*e == element) return i;
}
return SearchResult.MISSING?;
}
/**
* Concatenate two arrays or subarrays, returning a subarray containing the concatenation of them.
*
* @param [in] arr1
* @param [in] arr2
* @param [&inout] allocator "The allocator to use, default is the heap allocator"
* @require @typekind(arr1) == SUBARRAY || @typekind(arr1) == ARRAY
* @require @typekind(arr2) == SUBARRAY || @typekind(arr2) == ARRAY
* @require @typeis(arr1[0], $typeof(arr2[0])) "Arrays must have the same type"
* @ensure result.len == arr1.len + arr2.len
**/
macro concat_new(arr1, arr2, Allocator* allocator = mem::heap())
{
var $Type = $typeof(arr1[0]);
$Type[] result = array::alloc($Type, arr1.len + arr2.len);
$Type[] result = allocator.new_array($Type, arr1.len + arr2.len);
if (arr1.len > 0)
{
mem::copy(result.ptr, &arr1[0], arr1.len * $Type.sizeof, $Type.alignof, $Type.alignof);
@@ -37,4 +54,17 @@ macro concat(arr1, arr2)
mem::copy(&result[arr1.len], &arr2[0], arr2.len * $Type.sizeof, $Type.alignof, $Type.alignof);
}
return result;
}
}
/**
* Concatenate two arrays or subarrays, returning a subarray containing the concatenation of them,
* allocated using the temp allocator.
*
* @param [in] arr1
* @param [in] arr2
* @require @typekind(arr1) == SUBARRAY || @typekind(arr1) == ARRAY
* @require @typekind(arr2) == SUBARRAY || @typekind(arr2) == ARRAY
* @require @typeis(arr1[0], $typeof(arr2[0])) "Arrays must have the same type"
* @ensure result.len == arr1.len + arr2.len
**/
macro tconcat(arr1, arr2) => concat(arr1, arr2, mem::temp());

View File

@@ -1,5 +1,12 @@
// Copyright (c) 2023 Christoffer Lerno and contributors. All rights reserved.
// Use of this source code is governed by the MIT license
// a copy of which can be found in the LICENSE_STDLIB file.
module std::core::bitorder;
// This module contains types of different endianness.
// *BE types represent big-endian types
// *LE types represent little-endian types.
bitstruct ShortBE : short @bigendian
{
short val : 0..15;
@@ -80,3 +87,94 @@ bitstruct UInt128LE : uint128 @littleendian
uint128 val : 0..127;
}
/**
* @require is_array_or_sub_of_char(bytes) "argument must be an array, a pointer to an array or a subarray of char"
* @require is_bitorder($Type) "type must be a bitorder integer"
**/
macro read(bytes, $Type)
{
char[] s;
$switch (@typekind(bytes))
$case POINTER:
s = (*bytes)[:$Type.sizeof];
$default:
s = bytes[:$Type.sizeof];
$endswitch
return bitcast(*(char[$Type.sizeof]*)s.ptr, $Type).val;
}
/**
* @require is_arrayptr_or_sub_of_char(bytes) "argument must be a pointer to an array or a subarray of char"
* @require is_bitorder($Type) "type must be a bitorder integer"
**/
macro write(x, bytes, $Type)
{
char[] s;
$switch (@typekind(bytes))
$case POINTER:
s = (*bytes)[:$Type.sizeof];
$default:
s = bytes[:$Type.sizeof];
$endswitch
*($typeof(x)*)s.ptr = bitcast(x, $Type).val;
}
macro is_bitorder($Type)
{
$switch ($Type)
$case UShortLE:
$case ShortLE:
$case UIntLE:
$case IntLE:
$case ULongLE:
$case LongLE:
$case UInt128LE:
$case Int128LE:
$case UShortBE:
$case ShortBE:
$case UIntBE:
$case IntBE:
$case ULongBE:
$case LongBE:
$case UInt128BE:
$case Int128BE:
return true;
$default:
return false;
$endswitch
}
macro bool is_array_or_sub_of_char(bytes)
{
$switch (@typekind(bytes))
$case POINTER:
var $Inner = $typefrom($typeof(bytes).inner);
$if $Inner.kindof == ARRAY:
var $Inner2 = $typefrom($Inner.inner);
return $Inner2.typeid == char.typeid;
$endif
$case ARRAY:
$case SUBARRAY:
var $Inner = $typefrom($typeof(bytes).inner);
return $Inner.typeid == char.typeid;
$default:
return false;
$endswitch
}
macro bool is_arrayptr_or_sub_of_char(bytes)
{
$switch (@typekind(bytes))
$case POINTER:
var $Inner = $typefrom($typeof(bytes).inner);
$if $Inner.kindof == ARRAY:
var $Inner2 = $typefrom($Inner.inner);
return $Inner2.typeid == char.typeid;
$endif
$case SUBARRAY:
var $Inner = $typefrom($typeof(bytes).inner);
return $Inner.typeid == char.typeid;
$default:
return false;
$endswitch
}

View File

@@ -5,17 +5,26 @@ module std::core::builtin;
import libc;
import std::hash;
/**
* Use `IteratorResult` when reading the end of an iterator, or accessing a result out of bounds.
**/
fault IteratorResult
{
NO_MORE_ELEMENT
}
/**
* Use `SearchResult` when trying to return a value from some collection but the element is missing.
**/
fault SearchResult
{
MISSING
}
fault VarCastResult
/**
* Use `CastResult` when an attempt at conversion fails.
**/
fault CastResult
{
TYPE_MISMATCH
}
@@ -28,127 +37,229 @@ fault VarCastResult
**/
macro void @scope(&variable; @body) @builtin
{
var temp = variable;
defer variable = temp;
var temp = *variable;
defer *variable = temp;
@body();
}
/**
* Swap two variables
* @require $assignable(*b, $typeof(*a)) && $assignable(*a, $typeof(*b))
**/
macro void @swap(&a, &b) @builtin
{
var temp = a;
a = b;
b = temp;
var temp = *a;
*a = *b;
*b = temp;
}
/**
* Convert a variant type to a type, returning an failure if there is a type mismatch.
* Convert an `any` type to a type, returning an failure if there is a type mismatch.
*
* @param v `the variant to convert to the given type.`
* @param v `the any to convert to the given type.`
* @param $Type `the type to convert to`
* @return `The variant.ptr converted to its type.`
* @return `The any.ptr converted to its type.`
* @ensure @typeis(return, $Type*)
* @return! CastResult.TYPE_MISMATCH
**/
macro varcast(variant v, $Type) @builtin
macro anycast(any* v, $Type) @builtin
{
if (v.type != $Type.typeid) return VarCastResult.TYPE_MISMATCH!;
if (v.type != $Type.typeid) return CastResult.TYPE_MISMATCH?;
return ($Type*)v.ptr;
}
struct CallstackElement
fn bool print_backtrace(String message, int backtraces_to_ignore) @if(env::NATIVE_STACKTRACE)
{
CallstackElement* prev;
String function;
String file;
uint line;
@pool()
{
void*[256] buffer;
void*[] backtraces = backtrace::capture_current(&buffer);
backtraces_to_ignore++;
BacktraceList! backtrace = backtrace::symbolize_backtrace(backtraces, mem::temp());
if (catch backtrace) return false;
if (backtrace.len() <= backtraces_to_ignore) return false;
io::eprint("\nERROR: '");
io::eprint(message);
io::eprintn("'");
foreach (i, &trace : backtrace)
{
if (i < backtraces_to_ignore) continue;
if (trace.is_unknown())
{
io::eprintn(" in ???");
continue;
}
if (trace.has_file())
{
io::eprintfn(" in %s (%s:%d) [%s]", trace.function, trace.file, trace.line, trace.object_file);
continue;
}
io::eprintfn(" in %s (source unavailable) [%s]", trace.function, trace.object_file);
}
return true;
};
}
fn void default_panic(String message, String file, String function, uint line)
fn void default_panic(String message, String file, String function, uint line) @if(env::NATIVE_STACKTRACE)
{
CallstackElement* stack = $$stacktrace();
$if ($defined(libc::stderr) && $defined(libc::fprintf))
if (stack) stack = stack.prev;
if (stack)
{
libc::fprintf(libc::stderr(), "\nERROR: '%.*s'\n", (int)message.len, message.ptr);
}
else
{
libc::fprintf(libc::stderr(), "\nERROR: '%.*s', function %.*s (%.*s:%d)\n",
(int)message.len, message.ptr, (int)function.len, function.ptr, (int)file.len, file.ptr, line);
}
while (stack)
{
libc::fprintf(libc::stderr(), " at function %.*s (%.*s:%u)\n", (int)stack.function.len, stack.function.ptr,
(int)stack.file.len, stack.file.ptr, stack.line);
if (stack == stack.prev) break;
stack = stack.prev;
}
$if $defined(io::stderr):
if (!print_backtrace(message, 2))
{
io::eprintfn("\nERROR: '%s', in %s (%s:%d)", message, function, file, line);
return;
}
$endif
$$trap();
}
fn void default_panic(String message, String file, String function, uint line) @if(!env::NATIVE_STACKTRACE)
{
$if $defined(io::stderr):
io::eprint("\nERROR: '");
io::eprint(message);
io::eprintfn("', in %s (%s:%d)", function, file, line);
$endif
$$trap();
}
typedef PanicFn = fn void(String message, String file, String function, uint line);
def PanicFn = fn void(String message, String file, String function, uint line);
PanicFn panic = &default_panic;
macro void unreachable($string = "Unreachable statement reached.") @builtin @noreturn
fn void panicf(String fmt, String file, String function, uint line, args...)
{
panic($string, $$FILE, $$FUNC, $$LINE);
$$unreachable();
}
macro bitcast(expr, $Type) @builtin
{
var $size = (usz)($sizeof(expr));
$assert($size == $Type.sizeof, "Cannot bitcast between types of different size.");
$Type x @noinit;
mem::copy(&x, &expr, $size, $Type.alignof, $alignof(expr));
return x;
@stack_mem(512; Allocator* allocator)
{
DString s;
s.init_new(.allocator = allocator);
s.appendf(fmt, ...args);
panic(s.str_view(), file, function, line);
};
}
/**
* @require $Type.kindof == TypeKind.ENUM `Only enums may be used`
* Marks the path as unreachable. This will panic in safe mode, and in fast will simply be assumed
* never happens.
* @param [in] string "The panic message or format string"
**/
macro void unreachable(String string = "Unreachable statement reached.", ...) @builtin @noreturn
{
panicf(string, $$FILE, $$FUNC, $$LINE, $vasplat());
$$unreachable();
}
/**
* Marks the path as unsupported, this is similar to unreachable.
* @param [in] string "The error message"
**/
macro void unsupported(String string = "Unsupported function invoked") @builtin @noreturn
{
panicf(string, $$FILE, $$FUNC, $$LINE, $vasplat());
$$unreachable();
}
macro any_make(void* ptr, typeid type) @builtin
{
return $$any_make(ptr, type);
}
macro any.retype_to(&self, typeid type)
{
return $$any_make(self.ptr, type);
}
macro any.as_inner(&self)
{
return $$any_make(self.ptr, self.type.inner);
}
/**
* @param expr "the expression to cast"
* @param $Type "the type to cast to"
*
* @require $sizeof(expr) == $Type.sizeof "Cannot bitcast between types of different size."
* @ensure @typeis(return, $Type)
**/
macro bitcast(expr, $Type) @builtin
{
$if $Type.alignof <= $alignof(expr):
return *($Type*)&expr;
$else
$Type x @noinit;
$$memcpy(&x, &expr, $sizeof(expr), false, $Type.alignof, $alignof(expr));
return x;
$endif
}
/**
* @param $Type `The type of the enum`
* @param [in] enum_name `The name of the enum to search for`
* @require $Type.kindof == ENUM `Only enums may be used`
* @ensure @typeis(return, $Type)
* @return! SearchResult.MISSING
**/
macro enum_by_name($Type, String enum_name) @builtin
{
typeid x = $Type.typeid;
foreach (i, name : x.names)
{
if (str::compare(name, enum_name)) return ($Type)i;
if (name == enum_name) return ($Type)i;
}
return SearchResult.MISSING!;
}
macro bool @likely(bool value, $probability = 1.0) @builtin
{
$if ($probability == 1.0)
return $$expect(value, true);
$else
return $$expect_with_probability(value, true, $probability);
$endif
}
macro bool @unlikely(bool value, $probability = 1.0) @builtin
{
$if ($probability == 1.0)
return $$expect(value, false);
$else
return $$expect_with_probability(value, false, $probability);
$endif
return SearchResult.MISSING?;
}
/**
* @require values::@is_int(value) || values::@is_bool(value)
* @checked $typeof(value) a = expected
* Mark an expression as likely to be true
*
* @param #value "expression to be marked likely"
* @param $probability "in the range 0 - 1"
* @require $probability >= 0 && $probability <= 1.0
**/
macro @expect(value, expected, $probability = 1.0) @builtin
macro bool @likely(bool #value, $probability = 1.0) @builtin
{
$if ($probability == 1.0)
return $$expect(value, ($typeof(value))expected);
$else
return $$expect_with_probability(value, expected, $probability);
$endif
$switch
$case env::BUILTIN_EXPECT_IS_DISABLED:
return #value;
$case $probability == 1.0:
return $$expect(#value, true);
$default:
return $$expect_with_probability(#value, true, $probability);
$endswitch
}
/**
* Mark an expression as unlikely to be true
*
* @param #value "expression to be marked unlikely"
* @param $probability "in the range 0 - 1"
* @require $probability >= 0 && $probability <= 1.0
**/
macro bool @unlikely(bool #value, $probability = 1.0) @builtin
{
$switch
$case env::BUILTIN_EXPECT_IS_DISABLED:
return #value;
$case $probability == 1.0:
return $$expect(#value, false);
$default:
return $$expect_with_probability(#value, false, $probability);
$endswitch
}
/**
* @require values::@is_int(#value) || values::@is_bool(#value)
* @require $assignable(expected, $typeof(#value))
* @require $probability >= 0 && $probability <= 1.0
**/
macro @expect(#value, expected, $probability = 1.0) @builtin
{
$switch
$case env::BUILTIN_EXPECT_IS_DISABLED:
return #value == expected;
$case $probability == 1.0:
return $$expect(#value, ($typeof(#value))expected);
$default:
return $$expect_with_probability(#value, expected, $probability);
$endswitch
}
/**
@@ -170,9 +281,11 @@ enum PrefetchLocality
* @param $locality `Locality ranging from none to extremely local`
* @param $write `Prefetch for write, otherwise prefetch for read.`
**/
macro prefetch(void* ptr, PrefetchLocality $locality = VERY_NEAR, bool $write = false) @builtin
macro @prefetch(void* ptr, PrefetchLocality $locality = VERY_NEAR, bool $write = false) @builtin
{
$$prefetch(ptr, $write ? 1 : 0, $locality.ordinal);
$if !env::BUILTIN_PREFETCH_IS_DISABLED:
$$prefetch(ptr, $write ? 1 : 0, $locality.ordinal);
$endif
}
macro swizzle(v, ...) @builtin
@@ -185,14 +298,21 @@ macro swizzle2(v, v2, ...) @builtin
return $$swizzle2(v, v2, $vasplat());
}
macro bool @castable(#expr, $To) @builtin
macro anyfault @catch(#expr) @builtin
{
return $checks(($To)#expr);
if (catch f = #expr) return f;
return anyfault {};
}
macro bool @convertible(#expr, $To) @builtin
macro bool @ok(#expr) @builtin
{
return $checks($To x = #expr);
if (catch #expr) return false;
return true;
}
macro char[] @as_char_view(&value) @builtin
{
return ((char*)value)[:$sizeof(*value)];
}
macro uint int.hash(int i) => i;
@@ -203,7 +323,349 @@ macro uint char.hash(char c) => c;
macro uint ichar.hash(ichar c) => c;
macro uint long.hash(long i) => (uint)((i >> 32) ^ i);
macro uint ulong.hash(ulong i) => (uint)((i >> 32) ^ i);
macro uint int128.hash(int128 i) => (uint)((i >> 96) ^ (i >> 64) ^ (i >> 32) ^ i);
macro uint uint128.hash(uint128 i) => (uint)((i >> 96) ^ (i >> 64) ^ (i >> 32) ^ i);
macro uint bool.hash(bool b) => (uint)b;
macro uint typeid.hash(typeid t) => (uint)(((uptr)t >> 32) ^ (uptr)t);
macro uint typeid.hash(typeid t) => ((ulong)(uptr)t).hash();
macro uint String.hash(String c) => (uint)fnv32a::encode(c);
macro uint char[].hash(char[] c) => (uint)fnv32a::encode(c);
macro uint char[].hash(char[] c) => (uint)fnv32a::encode(c);
macro uint void*.hash(void* ptr) => ((ulong)(uptr)ptr).hash();
const MAX_FRAMEADDRESS = 128;
/**
* @require n >= 0
**/
macro void* get_frameaddress(int n)
{
if (n > MAX_FRAMEADDRESS) return null;
switch (n)
{
case 0: return $$frameaddress(0);
case 1: return $$frameaddress(1);
case 2: return $$frameaddress(2);
case 3: return $$frameaddress(3);
case 4: return $$frameaddress(4);
case 5: return $$frameaddress(5);
case 6: return $$frameaddress(6);
case 7: return $$frameaddress(7);
case 8: return $$frameaddress(8);
case 9: return $$frameaddress(9);
case 10: return $$frameaddress(10);
case 11: return $$frameaddress(11);
case 12: return $$frameaddress(12);
case 13: return $$frameaddress(13);
case 14: return $$frameaddress(14);
case 15: return $$frameaddress(15);
case 16: return $$frameaddress(16);
case 17: return $$frameaddress(17);
case 18: return $$frameaddress(18);
case 19: return $$frameaddress(19);
case 20: return $$frameaddress(20);
case 21: return $$frameaddress(21);
case 22: return $$frameaddress(22);
case 23: return $$frameaddress(23);
case 24: return $$frameaddress(24);
case 25: return $$frameaddress(25);
case 26: return $$frameaddress(26);
case 27: return $$frameaddress(27);
case 28: return $$frameaddress(28);
case 29: return $$frameaddress(29);
case 30: return $$frameaddress(30);
case 31: return $$frameaddress(31);
case 32: return $$frameaddress(32);
case 33: return $$frameaddress(33);
case 34: return $$frameaddress(34);
case 35: return $$frameaddress(35);
case 36: return $$frameaddress(36);
case 37: return $$frameaddress(37);
case 38: return $$frameaddress(38);
case 39: return $$frameaddress(39);
case 40: return $$frameaddress(40);
case 41: return $$frameaddress(41);
case 42: return $$frameaddress(42);
case 43: return $$frameaddress(43);
case 44: return $$frameaddress(44);
case 45: return $$frameaddress(45);
case 46: return $$frameaddress(46);
case 47: return $$frameaddress(47);
case 48: return $$frameaddress(48);
case 49: return $$frameaddress(49);
case 50: return $$frameaddress(50);
case 51: return $$frameaddress(51);
case 52: return $$frameaddress(52);
case 53: return $$frameaddress(53);
case 54: return $$frameaddress(54);
case 55: return $$frameaddress(55);
case 56: return $$frameaddress(56);
case 57: return $$frameaddress(57);
case 58: return $$frameaddress(58);
case 59: return $$frameaddress(59);
case 60: return $$frameaddress(60);
case 61: return $$frameaddress(61);
case 62: return $$frameaddress(62);
case 63: return $$frameaddress(63);
case 64: return $$frameaddress(64);
case 65: return $$frameaddress(65);
case 66: return $$frameaddress(66);
case 67: return $$frameaddress(67);
case 68: return $$frameaddress(68);
case 69: return $$frameaddress(69);
case 70: return $$frameaddress(70);
case 71: return $$frameaddress(71);
case 72: return $$frameaddress(72);
case 73: return $$frameaddress(73);
case 74: return $$frameaddress(74);
case 75: return $$frameaddress(75);
case 76: return $$frameaddress(76);
case 77: return $$frameaddress(77);
case 78: return $$frameaddress(78);
case 79: return $$frameaddress(79);
case 80: return $$frameaddress(80);
case 81: return $$frameaddress(81);
case 82: return $$frameaddress(82);
case 83: return $$frameaddress(83);
case 84: return $$frameaddress(84);
case 85: return $$frameaddress(85);
case 86: return $$frameaddress(86);
case 87: return $$frameaddress(87);
case 88: return $$frameaddress(88);
case 89: return $$frameaddress(89);
case 90: return $$frameaddress(90);
case 91: return $$frameaddress(91);
case 92: return $$frameaddress(92);
case 93: return $$frameaddress(93);
case 94: return $$frameaddress(94);
case 95: return $$frameaddress(95);
case 96: return $$frameaddress(96);
case 97: return $$frameaddress(97);
case 98: return $$frameaddress(98);
case 99: return $$frameaddress(99);
case 100: return $$frameaddress(100);
case 101: return $$frameaddress(101);
case 102: return $$frameaddress(102);
case 103: return $$frameaddress(103);
case 104: return $$frameaddress(104);
case 105: return $$frameaddress(105);
case 106: return $$frameaddress(106);
case 107: return $$frameaddress(107);
case 108: return $$frameaddress(108);
case 109: return $$frameaddress(109);
case 110: return $$frameaddress(110);
case 111: return $$frameaddress(111);
case 112: return $$frameaddress(112);
case 113: return $$frameaddress(113);
case 114: return $$frameaddress(114);
case 115: return $$frameaddress(115);
case 116: return $$frameaddress(116);
case 117: return $$frameaddress(117);
case 118: return $$frameaddress(118);
case 119: return $$frameaddress(119);
case 120: return $$frameaddress(120);
case 121: return $$frameaddress(121);
case 122: return $$frameaddress(122);
case 123: return $$frameaddress(123);
case 124: return $$frameaddress(124);
case 125: return $$frameaddress(125);
case 126: return $$frameaddress(126);
case 127: return $$frameaddress(127);
case 128: return $$frameaddress(128);
default: unreachable();
}
}
/**
* @require n >= 0
**/
macro void* get_returnaddress(int n)
{
if (n > MAX_FRAMEADDRESS) return null;
switch (n)
{
case 0: return $$returnaddress(0);
case 1: return $$returnaddress(1);
case 2: return $$returnaddress(2);
case 3: return $$returnaddress(3);
case 4: return $$returnaddress(4);
case 5: return $$returnaddress(5);
case 6: return $$returnaddress(6);
case 7: return $$returnaddress(7);
case 8: return $$returnaddress(8);
case 9: return $$returnaddress(9);
case 10: return $$returnaddress(10);
case 11: return $$returnaddress(11);
case 12: return $$returnaddress(12);
case 13: return $$returnaddress(13);
case 14: return $$returnaddress(14);
case 15: return $$returnaddress(15);
case 16: return $$returnaddress(16);
case 17: return $$returnaddress(17);
case 18: return $$returnaddress(18);
case 19: return $$returnaddress(19);
case 20: return $$returnaddress(20);
case 21: return $$returnaddress(21);
case 22: return $$returnaddress(22);
case 23: return $$returnaddress(23);
case 24: return $$returnaddress(24);
case 25: return $$returnaddress(25);
case 26: return $$returnaddress(26);
case 27: return $$returnaddress(27);
case 28: return $$returnaddress(28);
case 29: return $$returnaddress(29);
case 30: return $$returnaddress(30);
case 31: return $$returnaddress(31);
case 32: return $$returnaddress(32);
case 33: return $$returnaddress(33);
case 34: return $$returnaddress(34);
case 35: return $$returnaddress(35);
case 36: return $$returnaddress(36);
case 37: return $$returnaddress(37);
case 38: return $$returnaddress(38);
case 39: return $$returnaddress(39);
case 40: return $$returnaddress(40);
case 41: return $$returnaddress(41);
case 42: return $$returnaddress(42);
case 43: return $$returnaddress(43);
case 44: return $$returnaddress(44);
case 45: return $$returnaddress(45);
case 46: return $$returnaddress(46);
case 47: return $$returnaddress(47);
case 48: return $$returnaddress(48);
case 49: return $$returnaddress(49);
case 50: return $$returnaddress(50);
case 51: return $$returnaddress(51);
case 52: return $$returnaddress(52);
case 53: return $$returnaddress(53);
case 54: return $$returnaddress(54);
case 55: return $$returnaddress(55);
case 56: return $$returnaddress(56);
case 57: return $$returnaddress(57);
case 58: return $$returnaddress(58);
case 59: return $$returnaddress(59);
case 60: return $$returnaddress(60);
case 61: return $$returnaddress(61);
case 62: return $$returnaddress(62);
case 63: return $$returnaddress(63);
case 64: return $$returnaddress(64);
case 65: return $$returnaddress(65);
case 66: return $$returnaddress(66);
case 67: return $$returnaddress(67);
case 68: return $$returnaddress(68);
case 69: return $$returnaddress(69);
case 70: return $$returnaddress(70);
case 71: return $$returnaddress(71);
case 72: return $$returnaddress(72);
case 73: return $$returnaddress(73);
case 74: return $$returnaddress(74);
case 75: return $$returnaddress(75);
case 76: return $$returnaddress(76);
case 77: return $$returnaddress(77);
case 78: return $$returnaddress(78);
case 79: return $$returnaddress(79);
case 80: return $$returnaddress(80);
case 81: return $$returnaddress(81);
case 82: return $$returnaddress(82);
case 83: return $$returnaddress(83);
case 84: return $$returnaddress(84);
case 85: return $$returnaddress(85);
case 86: return $$returnaddress(86);
case 87: return $$returnaddress(87);
case 88: return $$returnaddress(88);
case 89: return $$returnaddress(89);
case 90: return $$returnaddress(90);
case 91: return $$returnaddress(91);
case 92: return $$returnaddress(92);
case 93: return $$returnaddress(93);
case 94: return $$returnaddress(94);
case 95: return $$returnaddress(95);
case 96: return $$returnaddress(96);
case 97: return $$returnaddress(97);
case 98: return $$returnaddress(98);
case 99: return $$returnaddress(99);
case 100: return $$returnaddress(100);
case 101: return $$returnaddress(101);
case 102: return $$returnaddress(102);
case 103: return $$returnaddress(103);
case 104: return $$returnaddress(104);
case 105: return $$returnaddress(105);
case 106: return $$returnaddress(106);
case 107: return $$returnaddress(107);
case 108: return $$returnaddress(108);
case 109: return $$returnaddress(109);
case 110: return $$returnaddress(110);
case 111: return $$returnaddress(111);
case 112: return $$returnaddress(112);
case 113: return $$returnaddress(113);
case 114: return $$returnaddress(114);
case 115: return $$returnaddress(115);
case 116: return $$returnaddress(116);
case 117: return $$returnaddress(117);
case 118: return $$returnaddress(118);
case 119: return $$returnaddress(119);
case 120: return $$returnaddress(120);
case 121: return $$returnaddress(121);
case 122: return $$returnaddress(122);
case 123: return $$returnaddress(123);
case 124: return $$returnaddress(124);
case 125: return $$returnaddress(125);
case 126: return $$returnaddress(126);
case 127: return $$returnaddress(127);
case 128: return $$returnaddress(128);
default: unreachable();
}
}
module std::core::builtin @if((env::LINUX || env::DARWIN) && env::COMPILER_SAFE_MODE && env::DEBUG_SYMBOLS);
import libc;
fn void sig_panic(String message)
{
default_panic(message, "???", "???", 0);
}
SignalFunction old_bus_error;
SignalFunction old_segmentation_fault;
fn void sig_bus_error(CInt i)
{
$if !env::NATIVE_STACKTRACE:
sig_panic("Illegal memory access.");
$else
$if $defined(io::stderr):
if (!print_backtrace("Illegal memory access.", 1))
{
io::eprintn("\nERROR: 'Illegal memory access'.");
}
$endif
$endif
$$trap();
}
fn void sig_segmentation_fault(CInt i)
{
$if !env::NATIVE_STACKTRACE:
sig_panic("Out of bounds memory access.");
$else
$if $defined(io::stderr):
if (!print_backtrace("Out of bounds memory access.", 1))
{
io::eprintn("\nERROR: Memory error without backtrace, possible stack overflow.");
}
$endif
$endif
$$trap();
}
fn void install_signal_handler(CInt signal, SignalFunction func) @local
{
SignalFunction old = libc::signal(signal, func);
// Restore
if ((iptr)old > 1024) libc::signal(signal, old);
}
// Clean this up
fn void install_signal_handlers() @init(101) @local
{
install_signal_handler(libc::SIGBUS, &sig_bus_error);
install_signal_handler(libc::SIGSEGV, &sig_segmentation_fault);
}

View File

@@ -8,14 +8,14 @@ module std::core::builtin;
**/
macro less(a, b) @builtin
{
$switch
$switch
$case $defined(a.less):
return a.less(b);
$case $defined(a.compare_to):
return a.compare_to(b) < 0;
$default:
return a < b;
$endswitch
$endswitch
}
/**
@@ -23,14 +23,14 @@ $endswitch
**/
macro less_eq(a, b) @builtin
{
$switch
$switch
$case $defined(a.less):
return !b.less(a);
$case $defined(a.compare_to):
return a.compare_to(b) <= 0;
$default:
return a <= b;
$endswitch
$endswitch
}
/**
@@ -38,29 +38,43 @@ $endswitch
**/
macro greater(a, b) @builtin
{
$switch
$switch
$case $defined(a.less):
return b.less(a);
$case $defined(a.compare_to):
return a.compare_to(b) > 0;
$default:
return a > b;
$endswitch
$endswitch
}
/**
* @require types::is_comparable_value(a) && types::is_comparable_value(b)
**/
macro int compare_to(a, b) @builtin
{
$switch
$case $defined(a.compare_to):
return a.compare_to(b);
$case $defined(a.less):
return (int)b.less(a) - (int)a.less(b);
$default:
return (int)(a > b) - (int)(a < b);
$endswitch
}
/**
* @require types::is_comparable_value(a) && types::is_comparable_value(b)
**/
macro greater_eq(a, b) @builtin
{
$switch
$switch
$case $defined(a.less):
return !a.less(b);
$case $defined(a.compare_to):
return a.compare_to(b) >= 0;
$default:
return a >= b;
$endswitch
$endswitch
}
/**
@@ -68,47 +82,47 @@ $endswitch
**/
macro bool equals(a, b) @builtin
{
$switch
$case $defined(a.equals):
$switch
$case $defined(a.equals, a.equals(b)):
return a.equals(b);
$case $defined(a.compare_to):
$case $defined(a.compare_to, a.compare_to(b)):
return a.compare_to(b) == 0;
$case $defined(a.less):
return !a.less(b) && !b.less(a);
$default:
return a == b;
$endswitch
$endswitch
}
macro min(x, ...) @builtin
{
$if ($vacount == 1)
return less(x, $vaarg(0)) ? x : $vaarg(0);
$else
var result = x;
$for (var $i = 0; $i < $vacount; $i++)
if (less($vaarg($i), result))
{
result = $vaarg($i);
}
$endfor
return result;
$endif
$if $vacount == 1:
return less(x, $vaarg(0)) ? x : $vaarg(0);
$else
var result = x;
$for (var $i = 0; $i < $vacount; $i++)
if (less($vaarg($i), result))
{
result = $vaarg($i);
}
$endfor
return result;
$endif
}
macro max(x, ...) @builtin
{
$if ($vacount == 1)
return greater(x, $vaarg(0)) ? x : $vaarg(0);
$else
var result = x;
$for (var $i = 0; $i < $vacount; $i++)
if (greater($vaarg($i), result))
{
result = $vaarg($i);
}
$endfor
return result;
$endif
$if $vacount == 1:
return greater(x, $vaarg(0)) ? x : $vaarg(0);
$else
var result = x;
$for (var $i = 0; $i < $vacount; $i++)
if (greater($vaarg($i), result))
{
result = $vaarg($i);
}
$endfor
return result;
$endif
}

View File

@@ -8,80 +8,48 @@ const C_LONG_SIZE = $$C_LONG_SIZE;
const C_SHORT_SIZE = $$C_SHORT_SIZE;
const C_LONG_LONG_SIZE = $$C_LONG_LONG_SIZE;
$assert (C_SHORT_SIZE < 32);
$assert (C_INT_SIZE < 128);
$assert (C_LONG_SIZE < 128);
$assert (C_LONG_LONG_SIZE <= 128);
$assert (C_SHORT_SIZE <= C_INT_SIZE);
$assert (C_INT_SIZE <= C_LONG_SIZE);
$assert (C_LONG_SIZE <= C_LONG_LONG_SIZE);
$assert C_SHORT_SIZE < 32;
$assert C_INT_SIZE < 128;
$assert C_LONG_SIZE < 128;
$assert C_LONG_LONG_SIZE <= 128;
$assert C_SHORT_SIZE <= C_INT_SIZE;
$assert C_INT_SIZE <= C_LONG_SIZE;
$assert C_LONG_SIZE <= C_LONG_LONG_SIZE;
$switch ($$C_INT_SIZE)
$case 64:
typedef CInt = long;
typedef CUInt = ulong;
$case 32:
typedef CInt = int;
typedef CUInt = uint;
$case 16:
typedef CInt = short;
typedef CUInt = ushort;
$default:
$assert(false, "Invalid C int size");
$endswitch
def CShort = $typefrom(signed_int_from_bitsize($$C_SHORT_SIZE));
def CUShort = $typefrom(unsigned_int_from_bitsize($$C_SHORT_SIZE));
def CInt = $typefrom(signed_int_from_bitsize($$C_INT_SIZE));
def CUInt = $typefrom(unsigned_int_from_bitsize($$C_INT_SIZE));
def CLong = $typefrom(signed_int_from_bitsize($$C_LONG_SIZE));
def CULong = $typefrom(unsigned_int_from_bitsize($$C_LONG_SIZE));
def CLongLong = $typefrom(signed_int_from_bitsize($$C_LONG_LONG_SIZE));
def CULongLong = $typefrom(unsigned_int_from_bitsize($$C_LONG_LONG_SIZE));
def CSChar = ichar;
def CUChar = char;
$switch ($$C_LONG_SIZE)
$case 64:
typedef CLong = long;
typedef CULong = ulong;
$case 32:
typedef CLong = int;
typedef CULong = uint;
$case 16:
typedef CLong = short;
typedef CULong = ushort;
$default:
$assert(false, "Invalid C long size");
$endswitch
def CChar = $typefrom($$C_CHAR_IS_SIGNED ? ichar.typeid : char.typeid);
$switch ($$C_SHORT_SIZE)
$case 32:
typedef CShort = int;
typedef CUShort = uint;
$case 16:
typedef CShort = short;
typedef CUShort = ushort;
$case 8:
typedef CShort = ichar;
typedef CUShort = char;
$default:
$assert(false, "Invalid C short size");
$endswitch
// Helper macros
macro typeid signed_int_from_bitsize(usz $bitsize) @private
{
$switch ($bitsize)
$case 128: return int128.typeid;
$case 64: return long.typeid;
$case 32: return int.typeid;
$case 16: return short.typeid;
$case 8: return ichar.typeid;
$default: $error("Invalid bitsize");
$endswitch
}
$switch ($$C_LONG_LONG_SIZE)
$case 128:
typedef CLongLong = int128;
typedef CULongLong = uint128;
$case 64:
typedef CLongLong = long;
typedef CULongLong = ulong;
$case 32:
typedef CLongLong = int;
typedef CULongLong = uint;
$case 16:
typedef CLongLong = short;
typedef CULongLong = ushort;
$default:
$assert(false, "Invalid C long long size");
$endswitch
typedef CSChar = ichar;
typedef CUChar = char;
$if ($$C_CHAR_IS_SIGNED)
typedef CChar = ichar;
$else
typedef CChar = char;
$endif
macro typeid unsigned_int_from_bitsize(usz $bitsize) @private
{
$switch ($bitsize)
$case 128: return uint128.typeid;
$case 64: return ulong.typeid;
$case 32: return uint.typeid;
$case 16: return ushort.typeid;
$case 8: return char.typeid;
$default: $error("Invalid bitsize");
$endswitch
}

View File

@@ -12,37 +12,36 @@ const uint UTF16_SURROGATE_HIGH_VALUE @private = 0xD800;
/**
* @param c `The utf32 codepoint to convert`
* @param [out] output `the resulting buffer`
* @param available `the size available`
**/
fn usz! char32_to_utf8(Char32 c, char* output, usz available)
fn usz! char32_to_utf8(Char32 c, char[] output)
{
if (!available) return UnicodeResult.CONVERSION_FAILED!;
if (!output.len) return UnicodeResult.CONVERSION_FAILED?;
switch (true)
{
case c <= 0x7f:
output[0] = (char)c;
return 1;
case c <= 0x7ff:
if (available < 2) return UnicodeResult.CONVERSION_FAILED!;
if (output.len < 2) return UnicodeResult.CONVERSION_FAILED?;
output[0] = (char)(0xC0 | c >> 6);
output[1] = (char)(0x80 | (c & 0x3F));
return 2;
case c <= 0xffff:
if (available < 3) return UnicodeResult.CONVERSION_FAILED!;
output[0] = (char)(0xE0 | c >> 12);
output[1] = (char)(0x80 | (c >> 6 & 0x3F));
output[2] = (char)(0x80 | (c & 0x3F));
return 3;
case c <= 0x10ffff:
if (available < 4) return UnicodeResult.CONVERSION_FAILED!;
output[0] = (char)(0xF0 | c >> 18);
output[1] = (char)(0x80 | (c >> 12 & 0x3F));
output[2] = (char)(0x80 | (c >> 6 & 0x3F));
output[3] = (char)(0x80 | (c & 0x3F));
return 4;
default:
// 0x10FFFF and above is not defined.
return UnicodeResult.CONVERSION_FAILED!;
output[1] = (char)(0x80 | (c & 0x3F));
return 2;
case c <= 0xffff:
if (output.len < 3) return UnicodeResult.CONVERSION_FAILED?;
output[0] = (char)(0xE0 | c >> 12);
output[1] = (char)(0x80 | (c >> 6 & 0x3F));
output[2] = (char)(0x80 | (c & 0x3F));
return 3;
case c <= 0x10ffff:
if (output.len < 4) return UnicodeResult.CONVERSION_FAILED?;
output[0] = (char)(0xF0 | c >> 18);
output[1] = (char)(0x80 | (c >> 12 & 0x3F));
output[2] = (char)(0x80 | (c >> 6 & 0x3F));
output[3] = (char)(0x80 | (c & 0x3F));
return 4;
default:
// 0x10FFFF and above is not defined.
return UnicodeResult.CONVERSION_FAILED?;
}
}
@@ -83,21 +82,21 @@ fn void! char16_to_utf8_unsafe(Char16 *ptr, usz *available, char** output)
*available = 1;
return;
}
// Low surrogate first is an error
if (high & UTF16_SURROGATE_MASK != UTF16_SURROGATE_HIGH_VALUE) return UnicodeResult.INVALID_UTF16!;
// Low surrogate first is an error
if (high & UTF16_SURROGATE_MASK != UTF16_SURROGATE_HIGH_VALUE) return UnicodeResult.INVALID_UTF16?;
// Unmatched high surrogate is an error
if (*available == 1) return UnicodeResult.INVALID_UTF16!;
if (*available == 1) return UnicodeResult.INVALID_UTF16?;
Char16 low = ptr[1];
// Unmatched high surrogate, invalid
if (low & UTF16_SURROGATE_MASK != UTF16_SURROGATE_LOW_VALUE) return UnicodeResult.INVALID_UTF16!;
if (low & UTF16_SURROGATE_MASK != UTF16_SURROGATE_LOW_VALUE) return UnicodeResult.INVALID_UTF16?;
// The high bits of the codepoint are the value bits of the high surrogate
// The low bits of the codepoint are the value bits of the low surrogate
Char32 uc = (high & UTF16_SURROGATE_CODEPOINT_MASK) << UTF16_SURROGATE_BITS
| (low & UTF16_SURROGATE_CODEPOINT_MASK) + UTF16_SURROGATE_OFFSET;
// The high bits of the codepoint are the value bits of the high surrogate
// The low bits of the codepoint are the value bits of the low surrogate
Char32 uc = (high & UTF16_SURROGATE_CODEPOINT_MASK) << UTF16_SURROGATE_BITS
| (low & UTF16_SURROGATE_CODEPOINT_MASK) + UTF16_SURROGATE_OFFSET;
char32_to_utf8_unsafe(uc, output);
*available = 2;
}
@@ -105,24 +104,28 @@ fn void! char16_to_utf8_unsafe(Char16 *ptr, usz *available, char** output)
* @param c `The utf32 codepoint to convert`
* @param [inout] output `the resulting buffer`
**/
fn void char32_to_utf8_unsafe(Char32 c, char** output)
fn usz char32_to_utf8_unsafe(Char32 c, char** output)
{
switch (true)
switch
{
case c < 0x7f:
(*output)++[0] = (char)c;
return 1;
case c < 0x7ff:
(*output)++[0] = (char)(0xC0 | c >> 6);
(*output)++[0] = (char)(0x80 | (c & 0x3F));
case c < 0xffff:
(*output)++[0] = (char)(0xE0 | c >> 12);
(*output)++[0] = (char)(0x80 | (c >> 6 & 0x3F));
(*output)++[0] = (char)(0x80 | (c & 0x3F));
default:
(*output)++[0] = (char)(0xF0 | c >> 18);
(*output)++[0] = (char)(0x80 | (c >> 12 & 0x3F));
(*output)++[0] = (char)(0x80 | (c >> 6 & 0x3F));
(*output)++[0] = (char)(0x80 | (c & 0x3F));
(*output)++[0] = (char)(0x80 | (c & 0x3F));
return 2;
case c < 0xffff:
(*output)++[0] = (char)(0xE0 | c >> 12);
(*output)++[0] = (char)(0x80 | (c >> 6 & 0x3F));
(*output)++[0] = (char)(0x80 | (c & 0x3F));
return 3;
default:
(*output)++[0] = (char)(0xF0 | c >> 18);
(*output)++[0] = (char)(0x80 | (c >> 12 & 0x3F));
(*output)++[0] = (char)(0x80 | (c >> 6 & 0x3F));
(*output)++[0] = (char)(0x80 | (c & 0x3F));
return 4;
}
}
@@ -134,50 +137,50 @@ fn void char32_to_utf8_unsafe(Char32 c, char** output)
fn Char32! utf8_to_char32(char* ptr, usz* size)
{
usz max_size = *size;
if (max_size < 1) return UnicodeResult.INVALID_UTF8!;
if (max_size < 1) return UnicodeResult.INVALID_UTF8?;
char c = (ptr++)[0];
if ((c & 0x80) == 0)
{
*size = 1;
return c;
}
if ((c & 0xE0) == 0xC0)
{
if (max_size < 2) return UnicodeResult.INVALID_UTF8!;
*size = 2;
Char32 uc = (c & 0x1F) << 6;
c = *ptr;
// Overlong sequence or invalid second.
if (!uc || c & 0xC0 != 0x80) return UnicodeResult.INVALID_UTF8!;
if ((c & 0x80) == 0)
{
*size = 1;
return c;
}
if ((c & 0xE0) == 0xC0)
{
if (max_size < 2) return UnicodeResult.INVALID_UTF8?;
*size = 2;
Char32 uc = (c & 0x1F) << 6;
c = *ptr;
// Overlong sequence or invalid second.
if (!uc || c & 0xC0 != 0x80) return UnicodeResult.INVALID_UTF8?;
return uc + c & 0x3F;
}
if ((c & 0xF0) == 0xE0)
{
if (max_size < 3) return UnicodeResult.INVALID_UTF8!;
*size = 3;
Char32 uc = (c & 0x0F) << 12;
}
if ((c & 0xF0) == 0xE0)
{
if (max_size < 3) return UnicodeResult.INVALID_UTF8?;
*size = 3;
Char32 uc = (c & 0x0F) << 12;
c = ptr++[0];
if (c & 0xC0 != 0x80) return UnicodeResult.INVALID_UTF8!;
if (c & 0xC0 != 0x80) return UnicodeResult.INVALID_UTF8?;
uc += (c & 0x3F) << 6;
c = ptr++[0];
// Overlong sequence or invalid last
if (!uc || c & 0xC0 != 0x80) return UnicodeResult.INVALID_UTF8!;
if (!uc || c & 0xC0 != 0x80) return UnicodeResult.INVALID_UTF8?;
return uc + c & 0x3F;
}
if (max_size < 4) return UnicodeResult.INVALID_UTF8!;
if ((c & 0xF8) != 0xF0) return UnicodeResult.INVALID_UTF8!;
*size = 4;
Char32 uc = (c & 0x07) << 18;
}
if (max_size < 4) return UnicodeResult.INVALID_UTF8?;
if ((c & 0xF8) != 0xF0) return UnicodeResult.INVALID_UTF8?;
*size = 4;
Char32 uc = (c & 0x07) << 18;
c = ptr++[0];
if (c & 0xC0 != 0x80) return UnicodeResult.INVALID_UTF8!;
if (c & 0xC0 != 0x80) return UnicodeResult.INVALID_UTF8?;
uc += (c & 0x3F) << 12;
c = ptr++[0];
if (c & 0xC0 != 0x80) return UnicodeResult.INVALID_UTF8!;
if (c & 0xC0 != 0x80) return UnicodeResult.INVALID_UTF8?;
uc += (c & 0x3F) << 6;
c = ptr++[0];
// Overlong sequence or invalid last
if (!uc || c & 0xC0 != 0x80) return UnicodeResult.INVALID_UTF8!;
if (!uc || c & 0xC0 != 0x80) return UnicodeResult.INVALID_UTF8?;
return uc + c & 0x3F;
}
@@ -272,8 +275,8 @@ fn usz utf16len_for_utf8(String utf8)
if (c & 0xF0 == 0xE0) continue;
i++;
len16++;
}
return len16;
}
return len16;
}
/**
@@ -297,19 +300,17 @@ fn usz utf16len_for_utf32(Char32[] utf32)
* @param [out] utf8_buffer
* @return `the number of bytes written.`
**/
fn usz! utf32to8(Char32[] utf32, String utf8_buffer)
fn usz! utf32to8(Char32[] utf32, char[] utf8_buffer)
{
usz len = utf8_buffer.len;
char* ptr = utf8_buffer.ptr;
foreach (Char32 uc : utf32)
char[] buffer = utf8_buffer;
foreach (uc : utf32)
{
usz used = char32_to_utf8(uc, ptr, len) @inline?;
len -= used;
ptr += used;
usz used = char32_to_utf8(uc, buffer) @inline!;
buffer = buffer[used..];
}
// Zero terminate if there is space.
if (len > 0) ptr[0] = 0;
return utf8_buffer.len - len;
if (buffer.len > 0) buffer[0] = 0;
return utf8_buffer.len - buffer.len;
}
/**
@@ -323,19 +324,19 @@ fn usz! utf8to32(String utf8, Char32[] utf32_buffer)
{
usz len = utf8.len;
Char32* ptr = utf32_buffer.ptr;
usz len32 = 0;
usz buf_len = utf32_buffer.len;
for (usz i = 0; i < len;)
{
if (len32 == buf_len) return UnicodeResult.CONVERSION_FAILED!;
usz width = len - i;
Char32 uc = utf8_to_char32(&utf8[i], &width) @inline?;
i += width;
ptr[len32++] = uc;
}
// Zero terminate if possible
if (len32 + 1 < buf_len) ptr[len32] = 0;
return len32;
usz len32 = 0;
usz buf_len = utf32_buffer.len;
for (usz i = 0; i < len;)
{
if (len32 == buf_len) return UnicodeResult.CONVERSION_FAILED?;
usz width = len - i;
Char32 uc = utf8_to_char32(&utf8[i], &width) @inline!;
i += width;
ptr[len32++] = uc;
}
// Zero terminate if possible
if (len32 + 1 < buf_len) ptr[len32] = 0;
return len32;
}
/**
@@ -352,7 +353,7 @@ fn void! utf16to8_unsafe(Char16[] utf16, char* utf8_buffer)
for (usz i = 0; i < len16;)
{
usz available = len16 - i;
char16_to_utf8_unsafe(&utf16[i], &available, &utf8_buffer) @inline?;
char16_to_utf8_unsafe(&utf16[i], &available, &utf8_buffer) @inline!;
i += available;
}
}
@@ -369,12 +370,12 @@ fn void! utf8to32_unsafe(String utf8, Char32* utf32_buffer)
{
usz len = utf8.len;
for (usz i = 0; i < len;)
{
usz width = len - i;
Char32 uc = utf8_to_char32(&utf8[i], &width) @inline?;
i += width;
(utf32_buffer++)[0] = uc;
}
{
usz width = len - i;
Char32 uc = utf8_to_char32(&utf8[i], &width) @inline!;
i += width;
(utf32_buffer++)[0] = uc;
}
}
/**
@@ -388,13 +389,13 @@ fn void! utf8to32_unsafe(String utf8, Char32* utf32_buffer)
fn void! utf8to16_unsafe(String utf8, Char16* utf16_buffer)
{
usz len = utf8.len;
for (usz i = 0; i < len;)
{
usz width = len - i;
Char32 uc = utf8_to_char32(&utf8[i], &width) @inline?;
char32_to_utf16_unsafe(uc, &utf16_buffer) @inline;
i += width;
}
for (usz i = 0; i < len;)
{
usz width = len - i;
Char32 uc = utf8_to_char32(&utf8[i], &width) @inline!;
char32_to_utf16_unsafe(uc, &utf16_buffer) @inline;
i += width;
}
}
/**

View File

@@ -1,40 +1,43 @@
module std::core::dstring;
import std::io;
typedef DString = distinct void*;
distinct DString (OutStream) = void*;
const usz MIN_CAPACITY @private = 16;
/**
* @require !str.data() "String already initialized"
* @require !self.data() "String already initialized"
**/
fn void DString.init(DString *str, usz capacity = MIN_CAPACITY, Allocator* using = mem::heap())
fn DString DString.init_new(&self, usz capacity = MIN_CAPACITY, Allocator* allocator = mem::heap())
{
if (capacity < MIN_CAPACITY) capacity = MIN_CAPACITY;
StringData* data = malloc(StringData, 1, .using = using, .end_padding = capacity);
data.allocator = using;
StringData* data = allocator.new(StringData, .end_padding = capacity);
data.allocator = allocator;
data.len = 0;
data.capacity = capacity;
*str = (DString)data;
return *self = (DString)data;
}
/**
* @require !str.data() "String already initialized"
* @require !self.data() "String already initialized"
**/
fn void DString.tinit(DString *str, usz capacity = MIN_CAPACITY) => str.init(capacity, mem::temp()) @inline;
fn DString new_with_capacity(usz capacity, Allocator* using = mem::heap())
fn DString DString.init_temp(&self, usz capacity = MIN_CAPACITY)
{
DString dstr;
dstr.init(capacity, using);
return dstr;
self.init_new(capacity, mem::temp()) @inline;
return *self;
}
fn DString tnew_with_capacity(usz capacity) => new_with_capacity(capacity, mem::temp()) @inline;
fn DString new_with_capacity(usz capacity, Allocator* allocator = mem::heap())
{
return DString{}.init_new(capacity, allocator);
}
fn DString new(String c = "", Allocator* using = mem::heap())
fn DString temp_with_capacity(usz capacity) => new_with_capacity(capacity, mem::temp()) @inline;
fn DString new(String c = "", Allocator* allocator = mem::heap())
{
usz len = c.len;
StringData* data = (StringData*)new_with_capacity(len, using);
StringData* data = (StringData*)new_with_capacity(len, allocator);
if (len)
{
data.len = len;
@@ -43,26 +46,27 @@ fn DString new(String c = "", Allocator* using = mem::heap())
return (DString)data;
}
fn DString tnew(String s = "") => new(s, mem::temp()) @inline;
fn DString temp_new(String s = "") => new(s, mem::temp()) @inline;
fn DString DString.new_concat(DString a, DString b, Allocator* using = mem::heap())
fn DString DString.new_concat(self, DString b, Allocator* allocator = mem::heap())
{
DString string;
string.init(a.len() + b.len(), using);
string.append(a);
string.init_new(self.len() + b.len(), allocator);
string.append(self);
string.append(b);
return string;
}
fn DString DString.new_tconcat(DString a, DString b) => a.new_concat(b, mem::temp());
fn DString DString.new_tconcat(self, DString b) => self.new_concat(b, mem::temp());
fn ZString DString.zstr(DString str)
fn ZString DString.zstr_view(&self)
{
StringData* data = str.data();
StringData* data = self.data();
if (!data) return "";
if (data.capacity == data.len)
{
str.reserve(1);
self.reserve(1);
data = self.data();
data.chars[data.len] = 0;
}
else if (data.chars[data.len] != 0)
@@ -72,139 +76,116 @@ fn ZString DString.zstr(DString str)
return (ZString)&data.chars[0];
}
fn usz DString.capacity(DString this)
fn usz DString.capacity(self)
{
if (!this) return 0;
return this.data().capacity;
if (!self) return 0;
return self.data().capacity;
}
fn usz DString.len(DString this)
fn usz DString.len(&self) @dynamic
{
if (!this) return 0;
return this.data().len;
if (!*self) return 0;
return self.data().len;
}
/**
* @require new_size <= this.len()
* @require new_size <= self.len()
*/
fn void DString.chop(DString this, usz new_size)
fn void DString.chop(self, usz new_size)
{
if (!this) return;
this.data().len = new_size;
if (!self) return;
self.data().len = new_size;
}
fn String DString.str(DString str)
fn String DString.str_view(self)
{
StringData* data = (StringData*)str;
StringData* data = self.data();
if (!data) return "";
return (String)data.chars[:data.len];
}
fn void DString.append_utf32(DString* str, Char32[] chars)
fn void DString.append_utf32(&self, Char32[] chars)
{
str.reserve(chars.len);
self.reserve(chars.len);
foreach (Char32 c : chars)
{
str.append_char32(c);
self.append_char32(c);
}
}
/**
* @require index < str.len()
* @require index < self.len()
**/
fn void DString.set(DString str, usz index, char c)
fn void DString.set(self, usz index, char c)
{
str.data().chars[index] = c;
self.data().chars[index] = c;
}
fn void DString.append_repeat(DString* str, char c, usz times)
fn void DString.append_repeat(&self, char c, usz times)
{
if (times == 0) return;
str.reserve(times);
StringData* data = str.data();
self.reserve(times);
StringData* data = self.data();
for (usz i = 0; i < times; i++)
{
data.chars[data.len++] = c;
data.chars[data.len++] = c;
}
}
/**
* @require c <= 0x10ffff
*/
fn void DString.append_char32(DString* str, Char32 c)
fn void DString.append_char32(&self, Char32 c)
{
if (c < 0x7f)
{
str.reserve(1);
StringData* data = str.data();
data.chars[data.len++] = (char)c;
return;
}
if (c < 0x7ff)
{
str.reserve(2);
StringData* data = str.data();
data.chars[data.len++] = (char)(0xC0 | c >> 6);
data.chars[data.len++] = (char)(0x80 | (c & 0x3F));
return;
}
if (c < 0xffff)
{
str.reserve(3);
StringData* data = str.data();
data.chars[data.len++] = (char)(0xE0 | c >> 12);
data.chars[data.len++] = (char)(0x80 | (c >> 6 & 0x3F));
data.chars[data.len++] = (char)(0x80 | (c & 0x3F));
return;
}
str.reserve(4);
StringData* data = str.data();
data.chars[data.len++] = (char)(0xF0 | c >> 18);
data.chars[data.len++] = (char)(0x80 | (c >> 12 & 0x3F));
data.chars[data.len++] = (char)(0x80 | (c >> 6 & 0x3F));
data.chars[data.len++] = (char)(0x80 | (c & 0x3F));
char[4] buffer @noinit;
char* p = &buffer;
usz n = conv::char32_to_utf8_unsafe(c, &p);
self.reserve(n);
StringData* data = self.data();
data.chars[data.len:n] = buffer[:n];
data.len += n;
}
fn DString DString.tcopy(DString* str) => str.copy(mem::temp());
fn DString DString.tcopy(&self) => self.copy(mem::temp());
fn DString DString.copy(DString* str, Allocator* using = null)
fn DString DString.copy(self, Allocator* allocator = null)
{
if (!str)
if (!self)
{
if (using) return new_with_capacity(0, using);
if (allocator) return new_with_capacity(0, allocator);
return (DString)null;
}
if (!using) using = mem::heap();
StringData* data = str.data();
DString new_string = new_with_capacity(data.capacity, using);
StringData* data = self.data();
if (!allocator) allocator = mem::heap();
DString new_string = new_with_capacity(data.capacity, allocator);
mem::copy((char*)new_string.data(), (char*)data, StringData.sizeof + data.len);
return new_string;
}
fn ZString DString.copy_zstr(DString* str, Allocator* using = mem::heap())
fn ZString DString.copy_zstr(self, Allocator* allocator = mem::heap())
{
usz str_len = str.len();
usz str_len = self.len();
if (!str_len)
{
return (ZString)calloc(1, .using = using);
return (ZString)allocator.calloc(1);
}
char* zstr = malloc(str_len + 1, .using = using);
StringData* data = str.data();
char* zstr = allocator.alloc(str_len + 1);
StringData* data = self.data();
mem::copy(zstr, &data.chars, str_len);
zstr[str_len] = 0;
return (ZString)zstr;
}
fn String DString.copy_str(DString* str, Allocator* using = mem::heap())
fn String DString.copy_str(self, Allocator* allocator = mem::heap())
{
return (String)str.copy_zstr(using)[:str.len()];
return (String)self.copy_zstr(allocator)[:self.len()];
}
fn String DString.tcopy_str(DString* str) => str.copy_str(mem::temp()) @inline;
fn String DString.tcopy_str(self) => self.copy_str(mem::temp()) @inline;
fn bool DString.equals(DString str, DString other_string)
fn bool DString.equals(self, DString other_string)
{
StringData *str1 = str.data();
StringData *str1 = self.data();
StringData *str2 = other_string.data();
if (str1 == str2) return true;
if (!str1) return str2.len == 0;
@@ -218,18 +199,18 @@ fn bool DString.equals(DString str, DString other_string)
return true;
}
fn void DString.free(DString* str)
fn void DString.free(&self)
{
if (!*str) return;
StringData* data = str.data();
if (!*self) return;
StringData* data = self.data();
if (!data) return;
free(data, .using = data.allocator);
*str = (DString)null;
data.allocator.free(data);
*self = (DString)null;
}
fn bool DString.less(DString str, DString other_string)
fn bool DString.less(self, DString other_string)
{
StringData* str1 = str.data();
StringData* str1 = self.data();
StringData* str2 = other_string.data();
if (str1 == str2) return false;
if (!str1) return str2.len != 0;
@@ -244,93 +225,167 @@ fn bool DString.less(DString str, DString other_string)
return true;
}
fn void DString.append_chars(DString* this, String str)
fn void DString.append_chars(&self, String str)
{
usz other_len = str.len;
if (!other_len) return;
if (!*this)
if (!*self)
{
*this = new(str);
*self = new(str);
return;
}
this.reserve(other_len);
StringData* data = (StringData*)*this;
self.reserve(other_len);
StringData* data = self.data();
mem::copy(&data.chars[data.len], str.ptr, other_len);
data.len += other_len;
}
fn Char32[] DString.copy_utf32(DString* this, Allocator* using = mem::heap())
fn Char32[] DString.copy_utf32(&self, Allocator* allocator = mem::heap())
{
return str::utf8to32(this.str(), using) @inline!!;
return self.str_view().to_new_utf32(allocator) @inline!!;
}
fn void DString.append_string(DString* this, DString str)
fn void DString.append_string(&self, DString str)
{
StringData* other = (StringData*)str;
StringData* other = str.data();
if (!other) return;
this.append(str.str());
self.append(str.str_view());
}
fn void DString.clear(DString* str)
fn void DString.clear(self)
{
str.data().len = 0;
if (!self) return;
self.data().len = 0;
}
fn void DString.append_char(DString* str, char c)
fn usz! DString.write(&self, char[] buffer) @dynamic
{
if (!*str)
self.append_chars((String)buffer);
return buffer.len;
}
fn void! DString.write_byte(&self, char c) @dynamic
{
self.append_char(c);
}
fn void DString.append_char(&self, char c)
{
if (!*self)
{
*str = new_with_capacity(MIN_CAPACITY);
*self = new_with_capacity(MIN_CAPACITY);
}
str.reserve(1);
StringData* data = (StringData*)*str;
self.reserve(1);
StringData* data = self.data();
data.chars[data.len++] = c;
}
/**
* @require start < self.len()
* @require end < self.len()
* @require end >= start "End must be same or equal to the start"
**/
fn void DString.delete_range(&self, usz start, usz end)
{
self.delete(start, end - start + 1);
}
macro void DString.append(DString* str, value)
/**
* @require start < self.len()
* @require start + len <= self.len()
**/
fn void DString.delete(&self, usz start, usz len = 1)
{
if (!len) return;
StringData* data = self.data();
usz new_len = data.len - len;
if (new_len == 0)
{
data.len = 0;
return;
}
usz len_after = data.len - start - len;
if (len_after > 0)
{
data.chars[start:len_after] = data.chars[start + len:len_after];
}
data.len = new_len;
}
macro void DString.append(&self, value)
{
var $Type = $typeof(value);
$switch ($Type)
$case char:
$case ichar:
str.append_char(value);
self.append_char(value);
$case DString:
str.append_string(value);
self.append_string(value);
$case String:
str.append_chars(value);
self.append_chars(value);
$case Char32:
str.append_char32(value);
self.append_char32(value);
$default:
$switch
$case @convertible(value, Char32):
str.append_char32(value);
$case @convertible(value, String):
str.append_chars(value);
$case $defined((Char32)value):
self.append_char32((Char32)value);
$case $defined((String)value):
self.append_chars((String)value);
$default:
$assert(false, "Unsupported type for append use printf instead.");
$error "Unsupported type for append use appendf instead.";
$endswitch
$endswitch
}
fn void DString.insert_at(&self, usz index, String s)
{
if (s.len == 0) return;
self.reserve(s.len);
StringData* data = self.data();
usz len = self.len();
if (data.chars[:len].ptr == s.ptr)
{
// Source and destination are the same: nothing to do.
return;
}
index = min(index, len);
data.len += s.len;
fn usz! DString.printf(DString* str, String format, args...) @maydiscard
char* start = data.chars[index:s.len].ptr; // area to insert into
mem::move(start + s.len, start, len - index); // move existing data
switch
{
case s.ptr <= start && start < s.ptr + s.len:
// Overlapping areas.
foreach_r (i, c : s)
{
data.chars[index + i] = c;
}
case start <= s.ptr && s.ptr < start + len:
// Source has moved.
mem::move(start, s.ptr + s.len, s.len);
default:
mem::move(start, s, s.len);
}
}
fn usz! DString.appendf(&self, String format, args...) @maydiscard
{
Formatter formatter;
formatter.init(&out_string_append_fn, str);
formatter.init(&out_string_append_fn, self);
return formatter.vprintf(format, args);
}
fn usz! DString.printfn(DString* str, String format, args...) @maydiscard
fn usz! DString.appendfn(&self, String format, args...) @maydiscard
{
Formatter formatter;
formatter.init(&out_string_append_fn, str);
usz len = formatter.vprintf(format, args)?;
str.append('\n');
formatter.init(&out_string_append_fn, self);
usz len = formatter.vprintf(format, args)!;
self.append('\n');
return len + 1;
}
fn DString new_join(String[] s, String joiner, Allocator* using = mem::heap())
fn DString new_join(String[] s, String joiner, Allocator* allocator = mem::heap())
{
if (!s.len) return (DString)null;
usz total_size = joiner.len * s.len;
@@ -338,7 +393,7 @@ fn DString new_join(String[] s, String joiner, Allocator* using = mem::heap())
{
total_size += str.len;
}
DString res = new_with_capacity(total_size, using);
DString res = new_with_capacity(total_size, allocator);
res.append(s[0]);
foreach (String* &str : s[1..])
{
@@ -348,43 +403,45 @@ fn DString new_join(String[] s, String joiner, Allocator* using = mem::heap())
return res;
}
fn void! out_string_append_fn(char c, void* data) @private
fn void! out_string_append_fn(void* data, char c) @private
{
DynString* s = data;
DString* s = data;
s.append_char(c);
}
fn StringData* DString.data(DString str) @inline @private
fn StringData* DString.data(self) @inline @private
{
return (StringData*)str;
return (StringData*)self;
}
fn void DString.reserve(DString* str, usz addition)
fn void DString.reserve(&self, usz addition)
{
StringData* data = str.data();
StringData* data = self.data();
if (!data)
{
*str = dstring::new_with_capacity(addition);
*self = dstring::new_with_capacity(addition);
return;
}
usz len = data.len + addition;
if (data.capacity >= len) return;
usz new_capacity = data.capacity *= 2;
usz new_capacity = data.capacity * 2;
if (new_capacity < MIN_CAPACITY) new_capacity = MIN_CAPACITY;
*str = (DString)realloc(data, StringData.sizeof + new_capacity, .using = data.allocator);
while (new_capacity < len) new_capacity *= 2;
data.capacity = new_capacity;
*self = (DString)data.allocator.realloc(data, StringData.sizeof + new_capacity);
}
fn usz! DString.read_from_stream(DString* string, Stream* reader)
fn usz! DString.read_from_stream(&self, InStream* reader)
{
if (reader.supports_available())
if (&reader.available)
{
usz total_read = 0;
while (usz available = reader.available()?)
while (usz available = reader.available()!)
{
string.reserve(available);
StringData* data = string.data();
usz len = reader.read(data.chars[data.len..(data.capacity - 1)])?;
self.reserve(available);
StringData* data = self.data();
usz len = reader.read(data.chars[data.len..(data.capacity - 1)])!;
total_read += len;
data.len += len;
}
@@ -394,10 +451,10 @@ fn usz! DString.read_from_stream(DString* string, Stream* reader)
while (true)
{
// Reserve at least 16 bytes
string.reserve(16);
StringData* data = string.data();
self.reserve(16);
StringData* data = self.data();
// Read into the rest of the buffer
usz read = reader.read(data.chars[data.len..(data.capacity - 1)])?;
usz read = reader.read(data.chars[data.len..(data.capacity - 1)])!;
data.len += read;
// Ok, we reached the end.
if (read < 16) return total_read;

View File

@@ -2,13 +2,13 @@
// Use of this source code is governed by the MIT license
// a copy of which can be found in the LICENSE_STDLIB file.
module std::core::env;
import libc;
enum CompilerOptLevel
{
O0,
O1,
O2,
O3
O0,
O1,
O2,
O3
}
enum MemoryEnvironment
@@ -32,7 +32,7 @@ enum OsType
KFREEBSD,
LINUX,
PS3,
MACOSX,
MACOS,
NETBSD,
OPENBSD,
SOLARIS,
@@ -116,28 +116,38 @@ enum ArchType
const OsType OS_TYPE = (OsType)$$OS_TYPE;
const ArchType ARCH_TYPE = (ArchType)$$ARCH_TYPE;
const bool COMPILER_LIBC_AVAILABLE = $$COMPILER_LIBC_AVAILABLE;
const bool LIBC = $$COMPILER_LIBC_AVAILABLE;
const bool NO_LIBC = !$$COMPILER_LIBC_AVAILABLE;
const CompilerOptLevel COMPILER_OPT_LEVEL = (CompilerOptLevel)$$COMPILER_OPT_LEVEL;
const bool BIG_ENDIAN = $$PLATFORM_BIG_ENDIAN;
const bool I128_NATIVE_SUPPORT = $$PLATFORM_I128_SUPPORTED;
const bool F16_SUPPORT = $$PLATFORM_F16_SUPPORTED;
const bool F128_SUPPORT = $$PLATFORM_F128_SUPPORTED;
const bool COMPILER_SAFE_MODE = $$COMPILER_SAFE_MODE;
const bool DEBUG_SYMBOLS = $$DEBUG_SYMBOLS;
const usz LLVM_VERSION = $$LLVM_VERSION;
const bool BENCHMARKING = $$BENCHMARKING;
const bool TESTING = $$TESTING;
const MemoryEnvironment MEMORY_ENV = (MemoryEnvironment)$$MEMORY_ENVIRONMENT;
macro bool os_is_win32()
{
return OS_TYPE == OsType.WIN32;
}
const bool TRACK_MEMORY = DEBUG_SYMBOLS && (COMPILER_SAFE_MODE || TESTING);
const bool X86_64 = ARCH_TYPE == X86_64;
const bool AARCH64 = ARCH_TYPE == AARCH64;
const bool NATIVE_STACKTRACE = LINUX || DARWIN || WIN32;
const bool LINUX = LIBC && OS_TYPE == LINUX;
const bool DARWIN = LIBC && os_is_darwin();
const bool WIN32 = LIBC && OS_TYPE == WIN32;
const bool POSIX = LIBC && os_is_posix();
const bool OPENBSD = LIBC && OS_TYPE == OPENBSD;
const bool FREEBSD = LIBC && OS_TYPE == FREEBSD;
const bool NETBSD = LIBC && OS_TYPE == NETBSD;
const bool WASI = LIBC && OS_TYPE == WASI;
const bool WASM_NOLIBC @builtin = !LIBC && ARCH_TYPE == ArchType.WASM32 || ARCH_TYPE == ArchType.WASM64;
macro bool os_is_darwin()
{
$switch (OS_TYPE)
$case IOS:
$case MACOSX:
$case MACOS:
$case TVOS:
$case WATCHOS:
return true;
@@ -150,7 +160,7 @@ macro bool os_is_posix()
{
$switch (OS_TYPE)
$case IOS:
$case MACOSX:
$case MACOS:
$case NETBSD:
$case LINUX:
$case KFREEBSD:
@@ -170,57 +180,5 @@ macro bool os_is_posix()
$endswitch
}
/**
* @param [&in] name
* @require name.len > 0
**/
fn String! get_var(String name)
{
$if (COMPILER_LIBC_AVAILABLE && OS_TYPE != OsType.WIN32)
@pool()
{
ZString val = libc::getenv(name.zstr_tcopy());
return val ? val.as_str() : SearchResult.MISSING!;
};
$else
return "";
$endif
}
/**
* @param [&in] name
* @param [&in] value
* @require name.len > 0
**/
fn void set_var(String name, String value, bool overwrite = true)
{
$if (COMPILER_LIBC_AVAILABLE && OS_TYPE != OsType.WIN32)
@pool()
{
if (libc::setenv(name.zstr_tcopy(), value.zstr_copy(), (int)overwrite))
{
unreachable();
}
};
$endif
}
/**
* @param [&in] name
* @require name.len > 0
**/
fn void clear_var(String name)
{
$if (COMPILER_LIBC_AVAILABLE && OS_TYPE != OsType.WIN32)
@pool()
{
if (libc::unsetenv(name.zstr_tcopy()))
{
unreachable();
}
};
$endif
}
const BUILTIN_EXPECT_IS_DISABLED = $feature(DISABLE_BUILTIN_EXPECT);
const BUILTIN_PREFETCH_IS_DISABLED = $feature(DISABLE_BUILTIN_PREFETCH);

View File

@@ -7,21 +7,172 @@ const MAX_MEMORY_ALIGNMENT = 0x1000_0000;
const DEFAULT_MEM_ALIGNMENT = (void*.alignof) * 2;
macro @volatile_load(&x) @builtin
/**
* Load a vector from memory according to a mask assuming default alignment.
*
* @param ptr "The pointer address to load from."
* @param mask "The mask for the load"
* @param passthru "The value to use for non masked values"
* @require $assignable(&&passthru, $typeof(ptr)) : "Pointer and passthru must match"
* @require @typekind(passthru) == VECTOR : "Expected passthru to be a vector"
* @require passthru.len == mask.len : "Mask and passthru must have the same length"
*
* @return "A vector with the loaded values where the mask is true, passthru where the mask is false"
**/
macro masked_load(ptr, bool[<*>] mask, passthru)
{
return $$volatile_load(&x);
return $$masked_load(ptr, mask, passthru, 0);
}
/**
* Load a vector from memory according to a mask.
*
* @param ptr "The pointer address to load from."
* @param mask "The mask for the load"
* @param passthru "The value to use for non masked values"
* @param $alignment "The alignment to assume for the pointer"
*
* @require $assignable(&&passthru, $typeof(ptr)) : "Pointer and passthru must match"
* @require @typekind(passthru) == VECTOR : "Expected passthru to be a vector"
* @require passthru.len == mask.len : "Mask and passthru must have the same length"
* @require math::is_power_of_2($alignment) : "The alignment must be a power of two"
*
* @return "A vector with the loaded values where the mask is true, passthru where the mask is false"
**/
macro @masked_load_aligned(ptr, bool[<*>] mask, passthru, usz $alignment)
{
return $$masked_load(ptr, mask, passthru, $alignment);
}
/**
* Load values from a pointer vector, assuming default alignment.
*
* @param ptrvec "The vector of pointers to load from."
* @param mask "The mask for the load"
* @param passthru "The value to use for non masked values"
*
* @require @typekind(ptrvec) == VECTOR : "Expected ptrvec to be a vector"
* @require @typekind(passthru) == VECTOR : "Expected passthru to be a vector"
* @require $assignable(&&passthru[0], $typeof(ptrvec[0])) : "Pointer and passthru must match"
* @require passthru.len == mask.len : "Mask and passthru must have the same length"
* @require mask.len == ptrvec.len : "Mask and ptrvec must have the same length"
*
* @return "A vector with the loaded values where the mask is true, passthru where the mask is false"
**/
macro gather(ptrvec, bool[<*>] mask, passthru)
{
return $$gather(ptrvec, mask, passthru, 0);
}
/**
* Load values from a pointer vector.
*
* @param ptrvec "The vector of pointers to load from."
* @param mask "The mask for the load"
* @param passthru "The value to use for non masked values"
* @param $alignment "The alignment to assume for the pointers"
*
* @require @typekind(ptrvec) == VECTOR : "Expected ptrvec to be a vector"
* @require @typekind(passthru) == VECTOR : "Expected passthru to be a vector"
* @require $assignable(&&passthru[0], $typeof(ptrvec[0])) : "Pointer and passthru must match"
* @require passthru.len == mask.len : "Mask and passthru must have the same length"
* @require mask.len == ptrvec.len : "Mask and ptrvec must have the same length"
* @require math::is_power_of_2($alignment) : "The alignment must be a power of two"
*
* @return "A vector with the loaded values where the mask is true, passthru where the mask is false"
**/
macro @gather_aligned(ptrvec, bool[<*>] mask, passthru, usz $alignment)
{
return $$gather(ptrvec, mask, passthru, $alignment);
}
/**
* Store parts of a vector according to the mask, assuming default alignment.
*
* @param ptr "The pointer address to store to."
* @param value "The value to store masked"
* @param mask "The mask for the store"
*
* @require $assignable(&&value, $typeof(ptr)) : "Pointer and value must match"
* @require @typekind(value) == VECTOR : "Expected value to be a vector"
* @require value.len == mask.len : "Mask and value must have the same length"
**/
macro masked_store(ptr, value, bool[<*>] mask)
{
return $$masked_store(ptr, value, mask, 0);
}
/**
* @param ptr "The pointer address to store to."
* @param value "The value to store masked"
* @param mask "The mask for the store"
* @param $alignment "The alignment of the pointer"
*
* @require $assignable(&&value, $typeof(ptr)) : "Pointer and value must match"
* @require @typekind(value) == VECTOR : "Expected value to be a vector"
* @require value.len == mask.len : "Mask and value must have the same length"
* @require math::is_power_of_2($alignment) : "The alignment must be a power of two"
*
**/
macro @masked_store_aligned(ptr, value, bool[<*>] mask, usz $alignment)
{
return $$masked_store(ptr, value, mask, $alignment);
}
/**
* @param ptrvec "The vector pointer containing the addresses to store to."
* @param value "The value to store masked"
* @param mask "The mask for the store"
* @require @typekind(ptrvec) == VECTOR : "Expected ptrvec to be a vector"
* @require @typekind(value) == VECTOR : "Expected value to be a vector"
* @require $assignable(&&value[0], $typeof(ptrvec[0])) : "Pointer and value must match"
* @require value.len == mask.len : "Mask and value must have the same length"
* @require mask.len == ptrvec.len : "Mask and ptrvec must have the same length"
*
**/
macro scatter(ptrvec, value, bool[<*>] mask)
{
return $$scatter(ptrvec, value, mask, 0);
}
/**
* @param ptrvec "The vector pointer containing the addresses to store to."
* @param value "The value to store masked"
* @param mask "The mask for the store"
* @param $alignment "The alignment of the load"
*
* @require @typekind(ptrvec) == VECTOR : "Expected ptrvec to be a vector"
* @require @typekind(value) == VECTOR : "Expected value to be a vector"
* @require $assignable(&&value[0], $typeof(ptrvec[0])) : "Pointer and value must match"
* @require value.len == mask.len : "Mask and value must have the same length"
* @require mask.len == ptrvec.len : "Mask and ptrvec must have the same length"
* @require math::is_power_of_2($alignment) : "The alignment must be a power of two"
**/
macro @scatter_aligned(ptrvec, value, bool[<*>] mask, usz $alignment)
{
return $$scatter(ptrvec, value, mask, $alignment);
}
macro @volatile_load(&x) @builtin
{
return $$volatile_load(x);
}
/**
* @require $assignable(y, $typeof(*x)) : "The value doesn't match the variable"
**/
macro @volatile_store(&x, y) @builtin
{
return $$volatile_store(&x, ($typeof(x))y);
return $$volatile_store(x, ($typeof(*x))y);
}
enum AtomicOrdering : int
{
NOT_ATOMIC, // Not atomic
UNORDERED, // No lock
MONOTONIC, // Consistent ordering
RELAXED, // Consistent ordering
ACQUIRE, // Barrier locking load/store
RELEASE, // Barrier releasing load/store
ACQUIRE_RELEASE, // Barrier fence to load/store
@@ -37,10 +188,11 @@ enum AtomicOrdering : int
* @require $ordering != AtomicOrdering.RELEASE "Release ordering is not valid for load."
* @require $ordering != AtomicOrdering.ACQUIRE_RELEASE "Acquire release is not valid for load."
* @require types::may_load_atomic($typeof(x)) "Only integer, float and pointers may be used."
* @require @typekind(x) == POINTER "You can only load from a pointer"
**/
macro @atomic_load(&x, AtomicOrdering $ordering = SEQ_CONSISTENT, $volatile = false) @builtin
{
return $$atomic_load(&x, $volatile, (int)$ordering);
return $$atomic_load(x, $volatile, (int)$ordering);
}
/**
@@ -55,14 +207,22 @@ macro @atomic_load(&x, AtomicOrdering $ordering = SEQ_CONSISTENT, $volatile = fa
**/
macro void @atomic_store(&x, value, AtomicOrdering $ordering = SEQ_CONSISTENT, $volatile = false) @builtin
{
$$atomic_store(&x, value, $volatile, (int)$ordering);
$$atomic_store(x, value, $volatile, (int)$ordering);
}
/**
* @require $success != AtomicOrdering.NOT_ATOMIC && $success != AtomicOrdering.UNORDERED "Acquire ordering is not valid."
* @require $failure != AtomicOrdering.RELEASE && $failure != AtomicOrdering.ACQUIRE_RELEASE "Acquire release is not valid."
**/
macro compare_exchange(ptr, compare, value, AtomicOrdering $success = SEQ_CONSISTENT, AtomicOrdering $failure = SEQ_CONSISTENT, bool $volatile = true, bool $weak = false, usz $alignment = 0)
{
return $$compare_exchange(ptr, compare, value, $volatile, $weak, $success.ordinal, $failure.ordinal, $alignment);
}
/**
* @require $success != AtomicOrdering.NOT_ATOMIC && $success != AtomicOrdering.UNORDERED "Acquire ordering is not valid."
* @require $failure != AtomicOrdering.RELEASE && $failure != AtomicOrdering.ACQUIRE_RELEASE "Acquire release is not valid."
**/
macro compare_exchange_volatile(ptr, compare, value, AtomicOrdering $success = SEQ_CONSISTENT, AtomicOrdering $failure = SEQ_CONSISTENT)
{
return compare_exchange(ptr, compare, value, $success, $failure, true);
@@ -91,30 +251,65 @@ fn bool ptr_is_aligned(void* ptr, usz alignment) @inline
macro void clear(void* dst, usz len, usz $dst_align = 0, bool $is_volatile = false, bool $inlined = false)
{
$if ($inlined)
$if $inlined:
$$memset_inline(dst, (char)0, len, $is_volatile, $dst_align);
$else
$$memset(dst, (char)0, len, $is_volatile, $dst_align);
$endif
}
/**
* Copy memory from src to dst efficiently, assuming the memory ranges do not overlap.
*
* @param [&out] dst "The destination to copy to"
* @param [&in] src "The source to copy from"
* @param len "The number of bytes to copy"
* @param $dst_align "the alignment of the destination if different from the default, 0 assumes the default"
* @param $src_align "the alignment of the destination if different from the default, 0 assumes the default"
* @param $is_volatile "True if this copy should be treated as volatile, i.e. it can't be optimized away."
* @param $inlined "True if this copy should never call the OS memcpy."
*
* @require len == 0 || dst + len <= src || src + len <= dst : "Ranges may not overlap"
**/
macro void copy(void* dst, void* src, usz len, usz $dst_align = 0, usz $src_align = 0, bool $is_volatile = false, bool $inlined = false)
{
$if ($inlined)
$if $inlined:
$$memcpy_inline(dst, src, len, $is_volatile, $dst_align, $src_align);
$else
$$memcpy(dst, src, len, $is_volatile, $dst_align, $src_align);
$endif
}
/**
* Copy memory from src to dst but correctly handle the possibility of overlapping ranges.
*
* @param [&out] dst "The destination to copy to"
* @param [&in] src "The source to copy from"
* @param len "The number of bytes to copy"
* @param $dst_align "the alignment of the destination if different from the default, 0 assumes the default"
* @param $src_align "the alignment of the destination if different from the default, 0 assumes the default"
* @param $is_volatile "True if this copy should be treated as volatile, i.e. it can't be optimized away."
**/
macro void move(void* dst, void* src, usz len, usz $dst_align = 0, usz $src_align = 0, bool $is_volatile = false)
{
$$memmove(dst, src, len, $is_volatile, $dst_align, $src_align);
}
/**
* Sets all memory in a region to that of the provided byte.
*
* @param [&out] dst "The destination to copy to"
* @param val "The value to copy into memory"
* @param len "The number of bytes to copy"
* @param $dst_align "the alignment of the destination if different from the default, 0 assumes the default"
* @param $is_volatile "True if this copy should be treated as volatile, i.e. it can't be optimized away."
* @param $inlined "True if this copy should never call the OS memset."
*
* @ensure !len || (dst[0] == val && dst[len - 1] == val)
**/
macro void set(void* dst, char val, usz len, usz $dst_align = 0, bool $is_volatile = false, bool $inlined = false)
{
$if ($inlined)
$if $inlined:
$$memset_inline(dst, val, len, $is_volatile, $dst_align);
$else
$$memset(dst, val, len, $is_volatile, $dst_align);
@@ -126,16 +321,16 @@ macro void set(void* dst, char val, usz len, usz $dst_align = 0, bool $is_volati
* @require values::@inner_kind(b) == TypeKind.SUBARRAY || values::@inner_kind(b) == TypeKind.POINTER
* @require values::@inner_kind(a) != TypeKind.SUBARRAY || len == -1
* @require values::@inner_kind(a) != TypeKind.POINTER || len > -1
* @checked (a = b), (b = a)
* @require values::@assign_to(a, b) && values::@assign_to(b, a)
**/
macro bool equals(a, b, isz len = -1, usz $align = 0)
{
$if (!$align)
$if !$align:
$align = $typeof(a[0]).alignof;
$endif
void* x @noinit;
void* y @noinit;
$if (values::@inner_kind(a) == TypeKind.SUBARRAY)
$if values::@inner_kind(a) == TypeKind.SUBARRAY:
len = a.len;
if (len != b.len) return false;
x = a.ptr;
@@ -148,7 +343,7 @@ macro bool equals(a, b, isz len = -1, usz $align = 0)
if (!len) return true;
var $Type;
$switch ($align)
$switch ($align)
$case 1:
$Type = char;
$case 2:
@@ -173,243 +368,122 @@ macro bool equals(a, b, isz len = -1, usz $align = 0)
return true;
}
macro @clone(&value) @builtin
{
$typeof(value)* x = malloc($typeof(value));
*x = value;
return x;
}
macro @tclone(&value) @builtin
{
$typeof(value)* x = talloc($typeof(value));
*x = value;
return x;
}
macro type_alloc_must_be_aligned($Type)
{
return $Type.alignof > DEFAULT_MEM_ALIGNMENT;
}
/**
* @require $vacount > 0 && $vacount < 3 "Expected size, type, or type + len"
* @require $vacount != 2 || $checks($vatype(0).sizeof) "Expected 'malloc(Foo, 12)'"
**/
macro malloc(..., Allocator* using = mem::heap(), usz end_padding = 0) @builtin
{
return malloc_checked($vasplat(), .using = using, .end_padding = end_padding)!!;
}
/**
* @require $vacount > 0 && $vacount < 3 "Expected size, type, or type + len"
* @require $vacount != 2 || $checks($vatype(0).sizeof) "Expected 'malloc(Foo, 12)'"
**/
macro malloc_checked(..., Allocator* using = mem::heap(), usz end_padding = 0) @builtin
{
$if ($checks($vatype(0).sizeof))
var $Type = $vatype(0);
$assert(!type_alloc_must_be_aligned($vatype(0)), "Type must be allocated with malloc_aligned");
$if ($vacount == 2)
usz size = $vaarg(1);
return (($Type*)using.alloc($Type.sizeof * size + end_padding))[:size];
$else
return ($Type*)using.alloc($Type.sizeof + end_padding);
$endif
$else
return using.alloc($vaarg(0) + end_padding);
$endif
}
/**
* @require $vacount > 0 && $vacount < 3 "Expected size, type, or type + len"
* @require $vacount != 2 || $checks($vatype(0).sizeof) "Expected 'malloc(Foo, 12)'"
* @require alignment && math::is_power_of_2(alignment)
**/
macro malloc_aligned(..., usz alignment = 0, usz end_padding = 0, Allocator* using = mem::heap()) @builtin
{
$if ($checks($vatype(0).sizeof))
var $Type = $vatype(0);
$if ($vacount == 2)
usz size = $vaarg(1);
return (($Type*)using.alloc_aligned($Type.sizeof * size + end_padding, alignment))[:size];
$else
return ($Type*)using.alloc_aligned($Type.sizeof + end_padding, alignment);
$endif
$else
return using.alloc_aligned($vaarg(0) + end_padding, alignment);
$endif
}
macro alloc($Type) @deprecated => malloc($Type);
macro char[] alloc_bytes(usz bytes) @deprecated => malloc(char, bytes);
/**
* @require $vacount > 0 && $vacount < 3 "Expected size, type, or type + len"
* @require $vacount != 2 || $checks($vatype(0).sizeof) "Expected 'malloc(Foo, 12)'"
**/
macro calloc(..., Allocator* using = mem::heap(), usz end_padding = 0) @builtin
{
return calloc_checked($vasplat(), .using = using, .end_padding = end_padding)!!;
}
/**
* @require $vacount > 0 && $vacount < 3 "Expected size, type, or type + len"
* @require $vacount != 2 || $checks($vatype(0).sizeof) "Expected 'malloc(Foo, 12)'"
**/
macro calloc_checked(..., Allocator* using = mem::heap(), usz end_padding = 0) @builtin
{
$if ($checks($vatype(0).sizeof))
var $Type = $vatype(0);
$assert(!type_alloc_must_be_aligned($vatype(0)), "Type must be allocated with calloc_aligned");
$if ($vacount == 2)
usz size = $vaarg(1);
return (($Type*)using.calloc($Type.sizeof * size + end_padding))[:size];
$else
return ($Type*)using.calloc($Type.sizeof + end_padding);
$endif
$else
return using.calloc($vaarg(0) + end_padding);
$endif
}
/**
* @require $vacount > 0 && $vacount < 3 "Expected size, type, or type + len"
* @require $vacount != 2 || $checks($vatype(0).sizeof) "Expected 'malloc(Foo, 12)'"
* @require alignment && math::is_power_of_2(alignment)
**/
macro calloc_aligned(..., usz alignment = 0, Allocator* using = mem::heap(), usz end_padding = 0) @builtin
{
$if ($checks($vatype(0).sizeof))
var $Type = $vatype(0);
$if ($vacount == 2)
usz size = $vaarg(1);
return (($Type*)using.calloc_aligned($Type.sizeof * size + end_padding, alignment))[:size];
$else
return ($Type*)using.calloc_aligned($Type.sizeof + end_padding, alignment);
$endif
$else
return using.calloc_aligned($vaarg(0) + end_padding, alignment);
$endif
}
fn void* realloc(void *ptr, usz new_size, Allocator* using = mem::heap()) @builtin @inline
{
return using.realloc(ptr, new_size)!!;
}
fn void*! realloc_checked(void *ptr, usz new_size, Allocator* using = mem::heap()) @builtin @inline
{
return using.realloc(ptr, new_size);
}
/**
* @require alignment && math::is_power_of_2(alignment)
*/
fn void*! realloc_aligned(void *ptr, usz new_size, usz alignment, Allocator* using = mem::heap()) @builtin @inline
{
return using.realloc_aligned(ptr, new_size, alignment);
}
macro void free(void* ptr, Allocator* using = mem::heap()) @builtin => using.free(ptr)!!;
macro void! free_checked(void* ptr, Allocator* using = mem::heap()) @builtin => using.free(ptr);
macro void free_aligned(void* ptr, Allocator* using = mem::heap()) @builtin => using.free_aligned(ptr)!!;
macro void! free_aligned_checked(void* ptr, Allocator* using = mem::heap()) @builtin => using.free_aligned(ptr);
/**
* Run with a specific allocator inside of the macro body.
**/
macro void @scoped(Allocator* using; @body())
macro void @scoped(Allocator* allocator; @body())
{
Allocator* old_allocator = thread_allocator;
thread_allocator = using;
thread_allocator = allocator;
defer thread_allocator = old_allocator;
@body();
}
macro talloc($Type) @builtin @deprecated => tmalloc($Type);
/**
* @require $vacount > 0 && $vacount < 3 "Expected size, type, or type + len"
* @require $vacount != 2 || $checks($vatype(0).sizeof) "Expected 'malloc(Foo, 12)'"
**/
macro tmalloc(..., usz end_padding = 0, usz alignment = DEFAULT_MEM_ALIGNMENT) @builtin
macro void @report_heap_allocs_in_scope(;@body())
{
$if ($checks($vatype(0).sizeof))
var $Type = $vatype(0);
$if ($vacount == 2)
usz size = $vaarg(1);
return (($Type*)temp().alloc_aligned($Type.sizeof * size + end_padding, alignment))[:size]!!;
$else
return ($Type*)temp().alloc_aligned($Type.sizeof + end_padding, alignment)!!;
$endif
$else
return temp().alloc_aligned($vaarg(0) + end_padding, alignment)!!;
$endif
}
/**
* @require $vacount > 0 && $vacount < 3 "Expected size, type, or type + len"
* @require $vacount != 2 || $checks($vatype(0).sizeof) "Expected 'malloc(Foo, 12)'"
**/
macro tcalloc(..., usz end_padding = 0, usz alignment = mem::DEFAULT_MEM_ALIGNMENT) @builtin
{
$if ($checks($vatype(0).sizeof))
var $Type = $vatype(0);
$if ($vacount == 2)
usz size = $vaarg(1);
return (($Type*)temp().calloc_aligned($Type.sizeof * size + end_padding, alignment))[:size]!!;
$else
return ($Type*)temp().calloc_aligned($Type.sizeof + end_padding, alignment)!!;
$endif
$else
return temp().calloc_aligned($vaarg(0) + end_padding, alignment)!!;
$endif
}
fn void* trealloc(void* ptr, usz size, usz alignment = mem::DEFAULT_MEM_ALIGNMENT) @builtin @inline
{
return temp().realloc_aligned(ptr, size, alignment)!!;
}
macro void @pool(;@body) @builtin
{
TempAllocator* allocator = temp();
usz mark = allocator.used;
defer allocator.reset(mark);
TrackingAllocator tracker;
tracker.init(thread_allocator);
Allocator* old_allocator = thread_allocator;
thread_allocator = &tracker;
defer
{
thread_allocator = old_allocator;
tracker.print_report();
tracker.free();
}
@body();
}
macro void @allocating_pool(Allocator* using; @body(bool is_temp)) @builtin
macro void @stack_mem(usz $size; @body(Allocator* mem)) @builtin
{
TempAllocator* allocator = temp();
usz mark = allocator.used;
bool is_temp = allocator == using;
defer if (!is_temp) allocator.reset(mark);
@body(is_temp);
char[$size] buffer;
OnStackAllocator allocator;
allocator.init(&buffer, mem::heap());
defer allocator.free();
@body(&allocator);
}
tlocal Allocator* thread_allocator @private = allocator::LIBC_ALLOCATOR;
tlocal TempAllocator* thread_temp_allocator @private = null;
macro void @stack_pool(usz $size; @body) @builtin
{
char[$size] buffer;
OnStackAllocator allocator;
allocator.init(&buffer, mem::heap());
defer allocator.free();
mem::@scoped(&allocator)
{
@body();
};
}
macro TempAllocator* temp_allocator() => temp();
macro void @pool(TempAllocator* #other_temp = null; @body) @builtin
{
TempAllocator* current = temp();
var $has_arg = !$is_const(#other_temp);
$if $has_arg:
TempAllocator* original = current;
if (current == (void*)#other_temp) current = temp_allocator_next();
$endif
usz mark = current.used;
defer
{
current.reset(mark);
$if $has_arg:
thread_temp_allocator = original;
$endif;
}
@body();
}
tlocal Allocator* thread_allocator @private = &allocator::LIBC_ALLOCATOR;
tlocal TempAllocator* thread_temp_allocator @private = null;
tlocal TempAllocator*[2] temp_allocator_pair @private;
Allocator* temp_base_allocator @private = &allocator::LIBC_ALLOCATOR;
macro TempAllocator* create_default_sized_temp_allocator(Allocator* allocator) @local
{
$switch (env::MEMORY_ENV)
$case NORMAL:
return allocator::new_temp(1024 * 256, allocator)!!;
$case SMALL:
return allocator::new_temp(1024 * 16, allocator)!!;
$case TINY:
return allocator::new_temp(1024 * 2, allocator)!!;
$case NONE:
unreachable("Temp allocator must explicitly created when memory-env is set to 'none'.");
$endswitch
}
fn TempAllocator *temp_allocator_next() @private
{
if (!thread_temp_allocator)
{
init_default_temp_allocators();
return thread_temp_allocator;
}
usz index = thread_temp_allocator == temp_allocator_pair[0] ? 1 : 0;
return thread_temp_allocator = temp_allocator_pair[index];
}
import libc;
fn void init_default_temp_allocators() @private
{
temp_allocator_pair[0] = create_default_sized_temp_allocator(temp_base_allocator);
temp_allocator_pair[1] = create_default_sized_temp_allocator(temp_base_allocator);
thread_temp_allocator = temp_allocator_pair[0];
}
macro TempAllocator* temp()
{
if (!thread_temp_allocator)
{
$switch (env::MEMORY_ENV)
$case NORMAL:
thread_temp_allocator = allocator::new_temp(1024 * 256, thread_allocator)!!;
$case SMALL:
thread_temp_allocator = allocator::new_temp(1024 * 16, thread_allocator)!!;
$case TINY:
thread_temp_allocator = allocator::new_temp(1024 * 2, thread_allocator)!!;
$case NONE:
unreachable("Temp allocator must explicitly created when memory-env is set to 'none'.");
$endswitch
init_default_temp_allocators();
}
return thread_temp_allocator;
}
@@ -417,21 +491,117 @@ macro TempAllocator* temp()
macro Allocator* current_allocator() => thread_allocator;
macro Allocator* heap() => thread_allocator;
$if (!env::COMPILER_LIBC_AVAILABLE && env::ARCH_TYPE == ArchType.WASM32 || env::ARCH_TYPE == ArchType.WASM64)
module std::core::mem @if(WASM_NOLIBC);
SimpleHeapAllocator wasm_allocator @private;
extern int __heap_base;
static initialize @priority(1)
fn void initialize_wasm_mem() @init(1) @private
{
allocator::wasm_memory.allocate_block(mem::DEFAULT_MEM_ALIGNMENT)!!; // Give us a valid null.
// Check if we need to move the heap.
uptr start = (uptr)&__heap_base;
if (start > mem::DEFAULT_MEM_ALIGNMENT) allocator::wasm_memory.use = start;
wasm_allocator.init(fn (x) => allocator::wasm_memory.allocate_block(x));
temp_base_allocator = &wasm_allocator;
thread_allocator = &wasm_allocator;
}
$endif
module std::core::mem;
macro TrackingEnv* get_tracking_env()
{
$if env::TRACK_MEMORY:
return &&TrackingEnv { $$FILE, $$FUNC, $$LINE };
$else
return null;
$endif
}
macro @clone(value) @builtin
{
return mem::heap().clone(value);
}
macro @tclone(value) @builtin
{
return mem::temp().clone(value);
}
fn void* malloc(usz size) @builtin @inline
{
return mem::heap().alloc(size);
}
fn void* tmalloc(usz size, usz alignment = 0, usz offset = 0) @builtin @inline
{
return temp().acquire(size, false, alignment, offset)!!;
}
macro new($Type)
{
return heap().new($Type);
}
macro new_clear($Type)
{
return heap().new_clear($Type);
}
macro new_temp($Type)
{
return tmalloc($Type.sizeof);
}
macro new_temp_clear($Type)
{
return tcalloc($Type.sizeof);
}
macro new_array($Type, usz elements)
{
return heap().new_array($Type, elements);
}
macro temp_array($Type, usz elements)
{
return (($Type*)tmalloc($Type.sizeof * elements, $Type.alignof))[:elements];
}
macro new_zero_array($Type, usz elements)
{
return heap().new_zero_array($Type, elements);
}
macro temp_zero_array($Type, usz elements)
{
return (($Type*)tcalloc($Type.sizeof * elements, $Type.alignof))[:elements];
}
fn void* calloc(usz size) @builtin @inline
{
return heap().calloc(size);
}
fn void* tcalloc(usz size, usz alignment = 0, usz offset = 0) @builtin @inline
{
return temp().acquire(size, false, alignment, offset)!!;
}
fn void* realloc(void *ptr, usz new_size) @builtin @inline
{
return heap().realloc(ptr, new_size);
}
fn void free(void* ptr) @builtin @inline
{
heap().free(ptr);
}
fn void* trealloc(void* ptr, usz size, usz alignment = mem::DEFAULT_MEM_ALIGNMENT) @builtin @inline
{
return temp().resize(ptr, size, alignment, 0)!!;
}

View File

@@ -3,107 +3,222 @@ module std::core::mem::allocator;
const DEFAULT_SIZE_PREFIX = usz.sizeof;
const DEFAULT_SIZE_PREFIX_ALIGNMENT = usz.alignof;
const Allocator* NULL_ALLOCATOR = &_NULL_ALLOCATOR;
const Allocator* LIBC_ALLOCATOR = &_SYSTEM_ALLOCATOR;
typedef AllocatorFunction = fn void*!(Allocator* allocator, usz new_size, usz alignment, usz offset, void* old_pointer, AllocationKind kind);
struct Allocator
struct TrackingEnv
{
AllocatorFunction function;
String file;
String function;
uint line;
}
enum AllocationKind
interface Allocator
{
ALLOC,
CALLOC,
REALLOC,
FREE,
ALIGNED_ALLOC,
ALIGNED_CALLOC,
ALIGNED_REALLOC,
ALIGNED_FREE,
RESET,
MARK,
fn void reset(usz mark) @optional;
fn usz mark() @optional;
fn void*! acquire(usz size, bool clear, usz alignment, usz offset);
fn void*! resize(void* ptr, usz new_size, usz alignment, usz offset);
fn void release(void* ptr, bool aligned);
}
struct AlignedBlock
{
usz len;
void* start;
}
/**
* @require bytes > 0
* @require alignment > 0
**/
macro void*! @aligned_alloc(#alloc_fn, usz bytes, usz alignment, usz offset)
{
usz header = mem::aligned_offset(AlignedBlock.sizeof + offset, alignment) - offset;
$if @typekind(#alloc_fn(bytes)) == OPTIONAL:
void* data = #alloc_fn(header + bytes)!;
$else
void* data = #alloc_fn(header + bytes);
$endif
void* mem = mem::aligned_pointer(data + header + offset, alignment) - offset;
assert(mem > data);
AlignedBlock* desc = (AlignedBlock*)mem - 1;
*desc = { bytes, data };
return mem;
}
/**
* @require bytes > 0
* @require alignment > 0
**/
macro void*! @aligned_calloc(#calloc_fn, usz bytes, usz alignment, usz offset)
{
usz header = mem::aligned_offset(AlignedBlock.sizeof + offset, alignment) - offset;
$if @typekind(#calloc_fn(bytes)) == OPTIONAL:
void* data = #calloc_fn(header + bytes)!;
$else
void* data = #calloc_fn(header + bytes);
$endif
void* mem = mem::aligned_pointer(data + header + offset, alignment) - offset;
AlignedBlock* desc = (AlignedBlock*)mem - 1;
assert(mem > data);
*desc = { bytes, data };
return mem;
}
/**
* @require bytes > 0
* @require alignment > 0
**/
macro void*! @aligned_realloc(#calloc_fn, #free_fn, void* old_pointer, usz bytes, usz alignment, usz offset)
{
AlignedBlock* desc = (AlignedBlock*)old_pointer - 1;
void* data_start = desc.start;
void* new_data = @aligned_calloc(#calloc_fn, bytes, alignment, offset)!;
mem::copy(new_data, old_pointer, desc.len < bytes ? desc.len : bytes, mem::DEFAULT_MEM_ALIGNMENT, mem::DEFAULT_MEM_ALIGNMENT);
$if @typekind(#free_fn(data_start)) == OPTIONAL:
#free_fn(data_start)!;
$else
#free_fn(data_start);
$endif
return new_data;
}
macro void! @aligned_free(#free_fn, void* old_pointer)
{
AlignedBlock* desc = (AlignedBlock*)old_pointer - 1;
$if @typekind(#free_fn(desc.start)) == OPTIONAL:
#free_fn(desc.start)!;
$else
#free_fn(desc.start);
$endif
}
def MemoryAllocFn = fn char[]!(usz);
fault AllocationFailure
{
OUT_OF_MEMORY,
UNSUPPORTED_OPERATION,
CHUNK_TOO_LARGE,
}
fn void*! Allocator.alloc(Allocator* allocator, usz size) @inline
{
return allocator.function(allocator, size, 0, 0, null, ALLOC);
}
/**
* @require alignment && math::is_power_of_2(alignment)
*/
fn void*! Allocator.alloc_aligned(Allocator* allocator, usz size, usz alignment, usz offset = 0) @inline
{
return allocator.function(allocator, size, alignment, offset, null, ALIGNED_ALLOC);
}
fn void*! Allocator.realloc(Allocator* allocator, void* old_pointer, usz size) @inline
{
return allocator.function(allocator, size, 0, 0, old_pointer, REALLOC);
}
/**
* @require alignment && math::is_power_of_2(alignment)
*/
fn void*! Allocator.realloc_aligned(Allocator* allocator, void* old_pointer, usz size, usz alignment, usz offset = 0) @inline
{
return allocator.function(allocator, size, alignment, offset, old_pointer, ALIGNED_REALLOC);
}
fn usz! Allocator.mark(Allocator* allocator) @inline
{
return (usz)(uptr)allocator.function(allocator, 0, 0, 0, null, MARK);
}
fn void*! Allocator.calloc(Allocator* allocator, usz size) @inline
{
return allocator.function(allocator, size, 0, 0, null, CALLOC);
}
/**
* @require alignment && math::is_power_of_2(alignment)
*/
fn void*! Allocator.calloc_aligned(Allocator* allocator, usz size, usz alignment, usz offset = 0) @inline
{
return allocator.function(allocator, size, alignment, offset, null, ALIGNED_CALLOC);
}
fn void! Allocator.free(Allocator* allocator, void* old_pointer) @inline
{
allocator.function(allocator, 0, 0, 0, old_pointer, FREE)?;
}
fn void! Allocator.free_aligned(Allocator* allocator, void* old_pointer) @inline
{
allocator.function(allocator, 0, 0, 0, old_pointer, ALIGNED_FREE)?;
}
fn void Allocator.reset(Allocator* allocator, usz mark = 0)
{
allocator.function(allocator, mark, 0, 0, null, RESET)!!;
OUT_OF_MEMORY,
CHUNK_TOO_LARGE,
}
fn usz alignment_for_allocation(usz alignment) @inline @private
{
if (alignment < mem::DEFAULT_MEM_ALIGNMENT)
{
alignment = mem::DEFAULT_MEM_ALIGNMENT;
}
return alignment;
return alignment < mem::DEFAULT_MEM_ALIGNMENT ? alignment = mem::DEFAULT_MEM_ALIGNMENT : alignment;
}
// Allocator "functions"
macro void*! Allocator.alloc_checked(&self, usz size)
{
$if env::TESTING:
char* data = self.acquire(size, false, 0, 0)!;
mem::set(data, 0xAA, size, mem::DEFAULT_MEM_ALIGNMENT);
return data;
$else
return self.acquire(size, false, 0, 0);
$endif
}
macro void*! Allocator.calloc_checked(&self, usz size)
{
return self.acquire(size, true, 0, 0);
}
macro void*! Allocator.realloc_checked(&self, void* ptr, usz new_size)
{
return self.resize(ptr, new_size, 0, 0);
}
macro Allocator.new_array(&self, $Type, usz size, usz end_padding = 0)
{
return (($Type*)self.alloc_checked($Type.sizeof * size + end_padding))[:size]!!;
}
macro Allocator.new_array_checked(&self, $Type, usz size, usz end_padding = 0)
{
return (($Type*)self.alloc_checked($Type.sizeof * size + end_padding))[:size];
}
macro Allocator.new_zero_array(&self, $Type, usz size, usz end_padding = 0)
{
return (($Type*)self.calloc_checked($Type.sizeof * size + end_padding))[:size]!!;
}
macro Allocator.new_zero_array_checked(&self, $Type, usz size, usz end_padding = 0)
{
return (($Type*)self.calloc_checked($Type.sizeof * size + end_padding))[:size];
}
macro Allocator.new(&self, $Type, usz end_padding = 0) @nodiscard
{
return ($Type*)self.alloc_checked($Type.sizeof + end_padding)!!;
}
macro Allocator.new_checked(&self, $Type, usz end_padding = 0) @nodiscard
{
return ($Type*)self.alloc_checked($Type.sizeof + end_padding);
}
macro Allocator.new_clear(&self, $Type, usz end_padding = 0) @nodiscard
{
return ($Type*)self.calloc_checked($Type.sizeof + end_padding)!!;
}
macro Allocator.new_clear_checked(&self, $Type, usz end_padding = 0) @nodiscard
{
return ($Type*)self.calloc_checked($Type.sizeof + end_padding);
}
macro Allocator.clone(&self, value)
{
var x = self.alloc($typeof(value));
*x = value;
return x;
}
macro void* Allocator.alloc(&self, usz size) @nodiscard
{
return self.alloc_checked(size)!!;
}
macro void* Allocator.calloc(&self, usz size) @nodiscard
{
return self.acquire(size, true, 0, 0)!!;
}
macro void* Allocator.realloc(&self, void* ptr, usz new_size) @nodiscard
{
return self.resize(ptr, new_size, 0, 0)!!;
}
macro void*! Allocator.alloc_aligned(&self, usz size, usz alignment, usz offset = 0)
{
$if env::TESTING:
char* data = self.acquire(size, false, alignment, offset)!;
mem::set(data, 0xAA, size, mem::DEFAULT_MEM_ALIGNMENT);
return data;
$else
return self.acquire(size, false, alignment, offset);
$endif
}
macro void*! Allocator.calloc_aligned(&self, usz size, usz alignment, usz offset = 0)
{
return self.acquire(size, true, alignment, offset);
}
macro void*! Allocator.realloc_aligned(&self, void* ptr, usz new_size, usz alignment = 0, usz offset = 0)
{
return self.resize(ptr, new_size, alignment, offset);
}
macro void Allocator.free(&self, void* ptr)
{
$if env::TESTING:
if (ptr) ((char*)ptr)[0] = 0xBA;
$endif
self.release(ptr, false);
}
macro void Allocator.free_aligned(&self, void* ptr)
{
$if env::TESTING:
if (ptr) ((char*)ptr)[0] = 0xBA;
$endif
self.release(ptr, true);
}

View File

@@ -1,31 +0,0 @@
// Copyright (c) 2021-2023 Christoffer Lerno. All rights reserved.
// Use of this source code is governed by the MIT license
// a copy of which can be found in the LICENSE_STDLIB file.
module std::core::mem::array;
/**
* @require usz.max / elements > $Type.sizeof
**/
macro alloc($Type, usz elements) @deprecated => malloc($Type, elements);
/**
* @require usz.max / elements > $Type.sizeof
**/
macro talloc($Type, usz elements) @deprecated => tmalloc($Type, elements);
/**
* @require (usz.max / elements > $Type.sizeof)
**/
macro make($Type, usz elements, Allocator* using = mem::heap()) @deprecated
{
return calloc($Type, elements, .using = using);
}
/**
* @require (usz.max / elements > $Type.sizeof)
**/
macro tmake($Type, usz elements) @deprecated
{
return tcalloc($Type, elements);
}

View File

@@ -11,22 +11,22 @@ struct WasmMemory
uptr use;
}
fn char[]! WasmMemory.allocate_block(WasmMemory* this, usz bytes)
fn char[]! WasmMemory.allocate_block(&self, usz bytes)
{
if (!this.allocation)
if (!self.allocation)
{
this.allocation = $$wasm_memory_size(0) * WASM_BLOCK_SIZE;
self.allocation = $$wasm_memory_size(0) * WASM_BLOCK_SIZE;
}
isz bytes_required = bytes + this.use - this.allocation;
if (bytes_required <= 0)
isz bytes_required = bytes + self.use - self.allocation;
if (bytes_required <= 0)
{
defer this.use += bytes;
return ((char*)this.use)[:bytes];
defer self.use += bytes;
return ((char*)self.use)[:bytes];
}
usz blocks_required = (bytes_required + WASM_BLOCK_SIZE + 1) / WASM_BLOCK_SIZE;
if ($$wasm_memory_grow(0, blocks_required) == -1) return AllocationFailure.OUT_OF_MEMORY!;
this.allocation = $$wasm_memory_size(0) * WASM_BLOCK_SIZE;
defer this.use += bytes;
return ((char*)this.use)[:bytes];
if ($$wasm_memory_grow(0, blocks_required) == -1) return AllocationFailure.OUT_OF_MEMORY?;
self.allocation = $$wasm_memory_size(0) * WASM_BLOCK_SIZE;
defer self.use += bytes;
return ((char*)self.use)[:bytes];
}

View File

@@ -7,7 +7,11 @@ macro usz _strlen(ptr) @private
return len;
}
macro int @main_to_err_main(#m, int, char**) => catch? #m() ? 1 : 0;
macro int @main_to_err_main(#m, int, char**)
{
if (catch #m()) return 1;
return 0;
}
macro int @main_to_int_main(#m, int, char**) => #m();
macro int @main_to_void_main(#m, int, char**)
{
@@ -17,7 +21,7 @@ macro int @main_to_void_main(#m, int, char**)
macro String[] args_to_strings(int argc, char** argv) @private
{
String[] list = malloc(String, argc);
String[] list = mem::new_array(String, argc);
for (int i = 0; i < argc; i++)
{
char* arg = argv[i];
@@ -32,7 +36,8 @@ macro int @main_to_err_main_args(#m, int argc, char** argv)
{
String[] list = args_to_strings(argc, argv);
defer free(list.ptr);
return catch? #m(list) ? 1 : 0;
if (catch #m(list)) return 1;
return 0;
}
macro int @main_to_int_main_args(#m, int argc, char** argv)
@@ -50,7 +55,7 @@ macro int @main_to_void_main_args(#m, int argc, char** argv)
return 0;
}
$if (env::OS_TYPE == OsType.WIN32)
module std::core::main_stub @if(env::WIN32);
extern fn Char16** _win_command_line_to_argv_w(ushort* cmd_line, int* argc_ptr) @extern("CommandLineToArgvW");
@@ -63,12 +68,12 @@ macro String[] win_command_line_to_strings(ushort* cmd_line) @private
macro String[] wargs_strings(int argc, Char16** argv) @private
{
String[] list = malloc(String, argc);
String[] list = mem::new_array(String, argc);
for (int i = 0; i < argc; i++)
{
Char16* arg = argv[i];
Char16[] argstring = arg[:_strlen(arg)];
list[i] = string::from_utf16(argstring) ?? "?".copy();
list[i] = string::new_from_utf16(argstring) ?? "?".copy();
}
return list[:argc];
}
@@ -79,7 +84,11 @@ macro void release_wargs(String[] list) @private
free(list.ptr);
}
macro int @win_to_err_main_noargs(#m, void* handle, Char16* cmd_line, int show_cmd) => catch? #m() ? 1 : 0;
macro int @win_to_err_main_noargs(#m, void* handle, Char16* cmd_line, int show_cmd)
{
if (catch #m()) return 1;
return 0;
}
macro int @win_to_int_main_noargs(#m, void* handle, Char16* cmd_line, int show_cmd) => #m();
macro int @win_to_void_main_noargs(#m, void* handle, Char16* cmd_line, int show_cmd)
{
@@ -91,14 +100,15 @@ macro int @win_to_err_main_args(#m, void* handle, Char16* cmd_line, int show_cmd
{
String[] args = win_command_line_to_strings(cmd_line);
defer release_wargs(args);
return catch? #m(args) ? 1 : 0;
if (catch #m(args)) return 1;
return 0;
}
macro int @win_to_int_main_args(#m, void* handle, Char16* cmd_line, int show_cmd)
{
String[] args = win_command_line_to_strings(cmd_line);
defer release_wargs(args);
return #m(args);
return #m(args);
}
macro int @win_to_void_main_args(#m, void* handle, Char16* cmd_line, int show_cmd)
@@ -113,14 +123,15 @@ macro int @win_to_err_main(#m, void* handle, Char16* cmd_line, int show_cmd)
{
String[] args = win_command_line_to_strings(cmd_line);
defer release_wargs(args);
return catch? #m(handle, args, show_cmd) ? 1 : 0;
if (catch #m(handle, args, show_cmd)) return 1;
return 0;
}
macro int @win_to_int_main(#m, void* handle, Char16* cmd_line, int show_cmd)
{
String[] args = win_command_line_to_strings(cmd_line);
defer release_wargs(args);
return #m(handle, args, show_cmd);
return #m(handle, args, show_cmd);
}
macro int @win_to_void_main(#m, void* handle, Char16* cmd_line, int show_cmd)
@@ -135,14 +146,15 @@ macro int @wmain_to_err_main_args(#m, int argc, Char16** argv)
{
String[] args = wargs_strings(argc, argv);
defer release_wargs(args);
return catch? #m(args) ? 1 : 0;
if (catch #m(args)) return 1;
return 1;
}
macro int @wmain_to_int_main_args(#m, int argc, Char16** argv)
{
String[] args = wargs_strings(argc, argv);
defer release_wargs(args);
return #m(args);
return #m(args);
}
macro int @wmain_to_void_main_args(#m, int argc, Char16** argv)
@@ -152,5 +164,3 @@ macro int @wmain_to_void_main_args(#m, int argc, Char16** argv)
#m(args);
return 0;
}
$endif

View File

@@ -2,108 +2,249 @@
// Use of this source code is governed by the MIT license
// a copy of which can be found in the LICENSE_STDLIB file.
module std::core::runtime;
import libc;
struct VirtualAny
struct AnyStruct
{
void* ptr;
typeid type_id;
void* ptr;
typeid type;
}
struct VirtualContainer
struct SubArrayStruct
{
void* ptr;
void* impl_ptr;
void* ptr;
usz len;
}
struct SubArrayContainer
def BenchmarkFn = fn void!();
struct BenchmarkUnit
{
void* ptr;
usz len;
String name;
BenchmarkFn func;
}
struct VarArrayHeader
fn BenchmarkUnit[] benchmark_collection_create(Allocator* allocator = mem::heap())
{
usz size;
usz capacity;
void *allocator;
BenchmarkFn[] fns = $$BENCHMARK_FNS;
String[] names = $$BENCHMARK_NAMES;
BenchmarkUnit[] benchmarks = allocator.new_array(BenchmarkUnit, names.len);
foreach (i, benchmark : fns)
{
benchmarks[i] = { names[i], fns[i] };
}
return benchmarks;
}
typedef TestFn = fn void!();
const DEFAULT_BENCHMARK_WARMUP_ITERATIONS = 3;
const DEFAULT_BENCHMARK_MAX_ITERATIONS = 10000;
struct TestRunner
uint benchmark_warmup_iterations @private = DEFAULT_BENCHMARK_WARMUP_ITERATIONS;
uint benchmark_max_iterations @private = DEFAULT_BENCHMARK_MAX_ITERATIONS;
fn void set_benchmark_warmup_iterations(uint value) @builtin
{
String[] test_names;
TestFn[] test_fns;
JmpBuf buf;
benchmark_warmup_iterations = value;
}
fn TestRunner test_runner_create()
fn void set_benchmark_max_iterations(uint value) @builtin
{
return TestRunner {
.test_fns = $$TEST_FNS,
.test_names = $$TEST_NAMES,
assert(value > 0);
benchmark_max_iterations = value;
}
fn bool run_benchmarks(BenchmarkUnit[] benchmarks)
{
int benchmarks_passed = 0;
int benchmark_count = benchmarks.len;
usz max_name;
foreach (&unit : benchmarks)
{
if (max_name < unit.name.len) max_name = unit.name.len;
}
usz len = max_name + 9;
DString name = dstring::temp_with_capacity(64);
name.append_repeat('-', len / 2);
name.append(" BENCHMARKS ");
name.append_repeat('-', len - len / 2);
io::printn(name);
name.clear();
long sys_clock_started;
long sys_clock_finished;
long sys_clocks;
Clock clock;
anyfault err;
foreach(unit : benchmarks)
{
defer name.clear();
name.appendf("Benchmarking %s ", unit.name);
name.append_repeat('.', max_name - unit.name.len + 2);
io::printf("%s ", name.str_view());
for (uint i = 0; i < benchmark_warmup_iterations; i++)
{
err = @catch(unit.func()) @inline;
@volatile_load(err);
}
clock = std::time::clock::now();
sys_clock_started = $$sysclock();
for (uint i = 0; i < benchmark_max_iterations; i++)
{
err = @catch(unit.func()) @inline;
@volatile_load(err);
}
sys_clock_finished = $$sysclock();
NanoDuration nano_seconds = clock.mark();
sys_clocks = sys_clock_finished - sys_clock_started;
if (err)
{
io::printfn("[failed] Failed due to: %s", err);
continue;
}
io::printfn("[ok] %.2f ns, %.2f CPU's clocks", (float)nano_seconds / benchmark_max_iterations, (float)sys_clocks / benchmark_max_iterations);
benchmarks_passed++;
}
io::printfn("\n%d benchmark%s run.\n", benchmark_count, benchmark_count > 1 ? "s" : "");
io::printfn("Benchmarks Result: %s. %d passed, %d failed.",
benchmarks_passed < benchmark_count ? "FAILED" : "ok",
benchmarks_passed,
benchmark_count - benchmarks_passed);
return benchmark_count == benchmarks_passed;
}
fn bool default_benchmark_runner()
{
@pool()
{
return run_benchmarks(benchmark_collection_create(mem::temp()));
};
}
import libc;
def TestFn = fn void!();
struct TestUnit
{
String name;
TestFn func;
}
fn TestUnit[] test_collection_create(Allocator* allocator = mem::heap())
{
TestFn[] fns = $$TEST_FNS;
String[] names = $$TEST_NAMES;
TestUnit[] tests = allocator.new_array(TestUnit, names.len);
foreach (i, test : fns)
{
tests[i] = { names[i], fns[i] };
}
return tests;
}
struct TestContext
{
JmpBuf buf;
}
// Sort the tests by their name in ascending order.
fn int cmp_test_unit(TestUnit a, TestUnit b)
{
usz an = a.name.len;
usz bn = b.name.len;
if (an > bn) @swap(a, b);
foreach (i, ac : a.name)
{
char bc = b.name[i];
if (ac != bc) return an > bn ? bc - ac : ac - bc;
}
return (int)(an - bn);
}
TestContext* test_context @private;
TestRunner* current_runner @private;
fn void test_panic(String message, String file, String function, uint line)
{
io::printn("[error]");
io::printfn("\n Error: %s", message);
io::print("\n Error: ");
io::print(message);
io::printn();
io::printfn(" - in %s %s:%s.\n", function, file, line);
libc::longjmp(&current_runner.buf, 1);
libc::longjmp(&test_context.buf, 1);
}
fn bool TestRunner.run(TestRunner* runner)
fn bool run_tests(TestUnit[] tests)
{
current_runner = runner;
usz max_name;
foreach (&unit : tests)
{
if (max_name < unit.name.len) max_name = unit.name.len;
}
quicksort(tests, &cmp_test_unit);
TestContext context;
test_context = &context;
PanicFn old_panic = builtin::panic;
defer builtin::panic = old_panic;
builtin::panic = &test_panic;
int tests_passed = 0;
int tests = runner.test_names.len;
io::printn("----- TESTS -----");
foreach(i, String name : runner.test_names)
int test_count = tests.len;
DString name = dstring::temp_with_capacity(64);
usz len = max_name + 9;
name.append_repeat('-', len / 2);
name.append(" TESTS ");
name.append_repeat('-', len - len / 2);
io::printn(name);
name.clear();
foreach(unit : tests)
{
io::printf("Testing %s ... ", name);
if (libc::setjmp(&runner.buf) == 0)
defer name.clear();
name.appendf("Testing %s ", unit.name);
name.append_repeat('.', max_name - unit.name.len + 2);
io::printf("%s ", name.str_view());
if (libc::setjmp(&context.buf) == 0)
{
if (catch err = runner.test_fns[i]())
if (catch err = unit.func())
{
io::printn("[failed]");
io::printfn("[failed] Failed due to: %s", err);
continue;
}
io::printn("[ok]");
tests_passed++;
}
}
io::printfn("\n%d test(s) run.\n", tests);
io::print("Test Result: ");
if (tests_passed < tests)
{
io::print("FAILED");
}
else
{
io::print("ok");
}
io::printfn(". %d passed, %d failed.", tests_passed, tests - tests_passed);
return tests == tests_passed;
io::printfn("\n%d test%s run.\n", test_count, test_count > 1 ? "s" : "");
io::printfn("Test Result: %s. %d passed, %d failed.",
tests_passed < test_count ? "FAILED" : "ok", tests_passed, test_count - tests_passed);
return test_count == tests_passed;
}
fn bool __run_default_test_runner()
fn bool default_test_runner()
{
return test_runner_create().run();
@pool()
{
return run_tests(test_collection_create(mem::temp()));
};
}
$if (!env::COMPILER_LIBC_AVAILABLE && env::ARCH_TYPE == ArchType.WASM32 || env::ARCH_TYPE == ArchType.WASM64)
module std::core::runtime @if(WASM_NOLIBC);
extern fn void __wasm_call_ctors();
fn void wasm_initialize() @extern("_initialize") @wasm
{
// The linker synthesizes this to call constructors.
__wasm_call_ctors();
}
$endif
}

View File

@@ -1,259 +0,0 @@
module std::core::str;
fn VarString join(String[] s, String joiner)
{
if (!s.len) return (VarString)null;
usz total_size = joiner.len * s.len;
foreach (String* &str : s)
{
total_size += str.len;
}
VarString res = string::new_with_capacity(total_size);
res.append(s[0]);
foreach (String* &str : s[1..])
{
res.append(joiner);
res.append(*str);
}
return res;
}
macro bool char_in_set(char c, String set)
{
foreach (ch : set)
{
if (ch == c) return true;
}
return false;
}
macro char_is_space_tab(char c) @private
{
return c == ' ' || c == '\t';
}
macro to_integer($Type, String string) @private
{
usz len = string.len;
usz index = 0;
char* ptr = string.ptr;
while (index < len && char_is_space_tab(ptr[index])) index++;
if (len == index) return NumberConversion.EMPTY_STRING!;
bool is_negative;
switch (string[index])
{
case '-':
if ($Type.min == 0) return NumberConversion.NEGATIVE_VALUE!;
is_negative = true;
index++;
case '+':
index++;
default:
break;
}
if (len == index) return NumberConversion.MALFORMED_INTEGER!;
$Type base = 10;
if (string[index] == '0')
{
index++;
if (index == len) return ($Type)0;
switch (string[index])
{
case 'x':
case 'X':
base = 16;
index++;
case 'b':
case 'B':
base = 2;
index++;
case 'o':
case 'O':
base = 8;
index++;
default:
break;
}
if (len == index) return NumberConversion.MALFORMED_INTEGER!;
}
$Type value = 0;
while (index != len)
{
char c = {|
char ch = string[index++];
if (base != 16 || ch < 'A') return (char)(ch - '0');
if (ch <= 'F') return (char)(ch - 'A');
if (ch < 'a') return NumberConversion.MALFORMED_INTEGER!;
if (ch > 'f') return NumberConversion.MALFORMED_INTEGER!;
return (char)(ch - 'a');
|}?;
if (c >= base) return NumberConversion.MALFORMED_INTEGER!;
value = {|
if (is_negative)
{
$Type new_value = value * base - c;
if (new_value > value) return NumberConversion.INTEGER_OVERFLOW!;
return new_value;
}
$Type new_value = value * base + c;
if (new_value < value) return NumberConversion.INTEGER_OVERFLOW!;
return new_value;
|}?;
}
return value;
}
fn float! to_float(String string) => floatparse(string, float);
fn double! to_double(String string) => floatparse(string, double);
fn int128! to_int128(String string) => to_integer(int128, string);
fn long! to_long(String string) => to_integer(long, string);
fn int! to_int(String string) => to_integer(int, string);
fn short! to_short(String string) => to_integer(short, string);
fn ichar! to_ichar(String string) => to_integer(ichar, string);
fn uint128! to_uint128(String str) => to_integer(uint128, str);
fn ulong! to_ulong(String str) => to_integer(ulong, str);
fn uint! to_uint(String str) => to_integer(uint, str);
fn ushort! to_ushort(String str) => to_integer(ushort, str);
fn char! to_uchar(String str) => to_integer(char, str);
fn String trim(String string, String to_trim = "\t\n\r ") @deprecated => string.trim(to_trim);
fn bool starts_with(String s, String needle) @deprecated => s.starts_with(needle);
fn String[] tsplit(String s, String needle) @deprecated => s.split(needle, .using = mem::temp()) @inline;
fn String[] split(String s, String needle, Allocator* using = mem::heap()) @deprecated => s.split(needle, .using = using);
fn usz! rindex_of(String s, String needle) @deprecated => s.rindex_of(needle);
fn usz! index_of(String s, String needle) @deprecated => s.index_of(needle);
fn ZString String.zstrcopy(String s, Allocator* using = mem::heap()) @deprecated => s.zstr_copy(using);
fn ZString String.zstrtcopy(String s) @deprecated => s.zstr_tcopy();
fn ZString copy_zstring(String s, Allocator* using = mem::heap()) @deprecated => s.zstr_copy(using);
fn String copyz(String s, Allocator* using = mem::heap()) @deprecated => s.copy(using);
fn ZString tcopy_zstring(String s) @deprecated => s.zstr_tcopy();
fn bool compare(String a, String b)
{
if (a.len != b.len) return false;
foreach (i, c : a)
{
if (c != b[i]) return false;
}
return true;
}
fault UnicodeResult
{
INVALID_UTF8,
INVALID_UTF16,
CONVERSION_FAILED,
}
fn usz utf8_codepoints(String utf8)
{
usz len = 0;
foreach (char c : utf8)
{
if (c & 0xC0 != 0x80) len++;
}
return len;
}
fn Char32[]! utf8to32(String utf8, Allocator* using = mem::heap())
{
usz codepoints = conv::utf8_codepoints(utf8);
Char32* data = malloc_checked(Char32, codepoints + 1, .using = using)?;
conv::utf8to32_unsafe(utf8, data)?;
data[codepoints] = 0;
return data[:codepoints];
}
fn String utf32to8(Char32[] utf32, Allocator* using = mem::heap())
{
usz len = conv::utf8len_for_utf32(utf32);
char* data = malloc_checked(len + 1, .using = using)!!;
conv::utf32to8_unsafe(utf32, data);
data[len] = 0;
return (String)data[:len];
}
fn Char16[]! utf8to16(String utf8, Allocator* using = mem::heap())
{
usz len16 = conv::utf16len_for_utf8(utf8);
Char16* data = malloc_checked(Char16, len16 + 1, .using = using)?;
conv::utf8to16_unsafe(utf8, data)?;
data[len16] = 0;
return data[:len16];
}
fn String! utf16to8(Char16[] utf16, Allocator* using = mem::heap())
{
usz len = conv::utf8len_for_utf16(utf16);
char* data = malloc_checked(len + 1, .using = using)?;
conv::utf16to8_unsafe(utf16, data)?;
data[len] = 0;
return (String)data[:len];
}
fn String copy(String s, Allocator* using = mem::heap()) @deprecated
{
usz len = s.len;
ZString str_copy = s.zstr_copy(using) @inline;
return (String)str_copy[:len];
}
fn String tcopy(String s) @deprecated
{
usz len = s.len;
ZString str_copy = s.zstr_tcopy() @inline;
return (String)str_copy[:len];
}
fn String tconcat(String s1, String s2)
{
usz full_len = s1.len + s2.len;
char* str = tmalloc(full_len + 1);
usz s1_len = s1.len;
mem::copy(str, s1.ptr, s1_len);
mem::copy(str + s1_len, s2.ptr, s2.len);
str[full_len] = 0;
return (String)str[:full_len];
}
fn String concat(String s1, String s2)
{
usz full_len = s1.len + s2.len;
char* str = malloc(full_len + 1);
usz s1_len = s1.len;
mem::copy(str, s1.ptr, s1_len);
mem::copy(str + s1_len, s2.ptr, s2.len);
str[full_len] = 0;
return (String)str[:full_len];
}
fn String ZString.as_str(ZString str)
{
return (String)((char*)str)[:str.len()];
}
fn usz ZString.char_len(ZString str)
{
usz len = 0;
char* ptr = (char*)str;
while (char c = ptr++[0])
{
if (c & 0xC0 != 0x80) len++;
}
return len;
}
fn usz ZString.len(ZString str)
{
usz len = 0;
char* ptr = (char*)str;
while (char c = ptr++[0]) len++;
return len;
}

View File

@@ -1,8 +1,17 @@
module std::core::string;
import std::ascii;
typedef ZString = distinct inline char*;
typedef Char32 = uint;
typedef Char16 = ushort;
distinct ZString = inline char*;
distinct WString = inline Char16*;
def Char32 = uint;
def Char16 = ushort;
fault UnicodeResult
{
INVALID_UTF8,
INVALID_UTF16,
CONVERSION_FAILED,
}
const uint SURROGATE_OFFSET @private = 0x10000;
const uint SURROGATE_GENERIC_MASK @private = 0xF800;
@@ -22,24 +31,23 @@ fault NumberConversion
FLOAT_OUT_OF_RANGE,
}
macro String printf(String fmt, ..., Allocator* using = mem::heap())
macro String tformat(String fmt, ...)
{
@stack_mem(256; Allocator* mem)
DString str = dstring::temp_with_capacity(fmt.len + $vacount * 8);
str.appendf(fmt, $vasplat());
return str.str_view();
}
macro String new_format(String fmt, ..., Allocator* allocator = mem::heap())
{
@pool(allocator)
{
DString str;
str.init(.using = mem);
str.printf(fmt, $vasplat());
return str.copy_str(using);
DString str = dstring::temp_with_capacity(fmt.len + $vacount * 8);
str.appendf(fmt, $vasplat());
return str.copy_str(allocator);
};
}
macro String tprintf(String fmt, ...)
{
DString str;
str.tinit();
str.printf(fmt, $vasplat());
return str.str();
}
macro bool char_in_set(char c, String set)
{
@@ -47,11 +55,36 @@ macro bool char_in_set(char c, String set)
return false;
}
fn String join_new(String[] s, String joiner, Allocator* allocator = mem::heap())
{
if (!s)
{
return (String)allocator.new_zero_array(char, 2)[:0];
}
usz total_size = joiner.len * s.len;
foreach (String* &str : s)
{
total_size += str.len;
}
@pool(allocator)
{
DString res = dstring::temp_with_capacity(total_size);
res.append(s[0]);
foreach (String* &str : s[1..])
{
res.append(joiner);
res.append(*str);
}
return res.copy_str(allocator);
};
}
/**
* @param [in] string
* @param [in] to_trim
**/
fn String String.trim(String string, String to_trim = "\t\n\r ")
fn String String.trim(string, String to_trim = "\t\n\r ")
{
usz start = 0;
usz len = string.len;
@@ -66,7 +99,7 @@ fn String String.trim(String string, String to_trim = "\t\n\r ")
* @param [in] string
* @param [in] needle
**/
fn bool String.starts_with(String string, String needle)
fn bool String.starts_with(string, String needle)
{
if (needle.len > string.len) return false;
if (!needle.len) return true;
@@ -77,7 +110,7 @@ fn bool String.starts_with(String string, String needle)
* @param [in] string
* @param [in] needle
**/
fn bool String.ends_with(String string, String needle)
fn bool String.ends_with(string, String needle)
{
if (needle.len > string.len) return false;
if (!needle.len) return true;
@@ -90,7 +123,7 @@ fn bool String.ends_with(String string, String needle)
* @param [in] string
* @param [in] needle
**/
fn String String.strip(String string, String needle)
fn String String.strip(string, String needle)
{
if (!needle.len || !string.starts_with(needle)) return string;
return string[needle.len..];
@@ -102,7 +135,7 @@ fn String String.strip(String string, String needle)
* @param [in] string
* @param [in] needle
**/
fn String String.strip_end(String string, String needle)
fn String String.strip_end(string, String needle)
{
if (!needle.len || !string.ends_with(needle)) return string;
// Note that this is the safe way if we want to support zero length.
@@ -115,20 +148,20 @@ fn String String.strip_end(String string, String needle)
*
* @param [in] s
* @param [in] needle
* @param [&inout] using "The allocator, defaults to the heap allocator"
* @param [&inout] allocator "The allocator, defaults to the heap allocator"
* @param max "Max number of elements, 0 means no limit, defaults to 0"
* @require needle.len > 0 "The needle must be at least 1 character long"
* @ensure return.len > 0
**/
fn String[] String.split(String s, String needle, usz max = 0, Allocator* using = mem::heap())
fn String[] String.split(s, String needle, usz max = 0, Allocator* allocator = mem::heap())
{
usz capacity = 16;
usz i = 0;
String* holder = malloc(String, capacity, .using = using);
String* holder = allocator.new_array(String, capacity);
bool no_more = false;
while (!no_more)
{
usz! index = i == max - 1 ? SearchResult.MISSING! : s.index_of(needle);
usz! index = i == max - 1 ? SearchResult.MISSING? : s.index_of(needle);
String res @noinit;
if (try index)
{
@@ -143,7 +176,7 @@ fn String[] String.split(String s, String needle, usz max = 0, Allocator* using
if (i == capacity)
{
capacity *= 2;
holder = realloc(holder, String.sizeof * capacity, .using = using);
holder = allocator.realloc(holder, String.sizeof * capacity);
}
holder[i++] = res;
}
@@ -158,11 +191,52 @@ fn String[] String.split(String s, String needle, usz max = 0, Allocator* using
* @param [in] needle
* @param max "Max number of elements, 0 means no limit, defaults to 0"
**/
fn String[] String.tsplit(String s, String needle, usz max = 0)
fn String[] String.tsplit(s, String needle, usz max = 0)
{
return s.split(needle, max, mem::temp()) @inline;
}
fn bool String.contains(s, String needle)
{
return @ok(s.index_of(needle));
}
/**
* Find the index of the first incidence of a string.
*
* @param [in] s
* @pure
* @ensure return < s.len
* @return "the index of the needle"
* @return! SearchResult.MISSING "if the needle cannot be found"
**/
fn usz! String.index_of_char(s, char needle)
{
foreach (i, c : s)
{
if (c == needle) return i;
}
return SearchResult.MISSING?;
}
/**
* Find the index of the first incidence of a string.
*
* @param [in] s
* @pure
* @ensure return < s.len
* @return "the index of the needle"
* @return! SearchResult.MISSING "if the needle cannot be found"
**/
fn usz! String.rindex_of_char(s, char needle)
{
foreach_r (i, c : s)
{
if (c == needle) return i;
}
return SearchResult.MISSING?;
}
/**
* Find the index of the first incidence of a string.
*
@@ -170,31 +244,22 @@ fn String[] String.tsplit(String s, String needle, usz max = 0)
* @param [in] needle
* @pure
* @ensure return < s.len
* @require needle.len > 0 "The needle must be len 1 or more"
* @require needle.len > 0 : "The needle must be len 1 or more"
* @return "the index of the needle"
* @return! SearchResult.MISSING "if the needle cannot be found"
**/
fn usz! String.index_of(String s, String needle)
fn usz! String.index_of(s, String needle)
{
usz match = 0;
usz needed = needle.len;
usz index_start = 0;
char search = needle[0];
foreach (usz i, char c : s)
if (needed > 0 && s.len >= needed)
{
if (c == search)
char first = needle[0];
foreach (i, c: s[..^needed])
{
if (!match) index_start = i;
match++;
if (match == needed) return index_start;
search = needle[match];
continue;
}
if (match)
{
match = 0;
search = needle[0];
if (c == first && s[i:needed] == needle) return i;
}
}
return SearchResult.MISSING!;
return SearchResult.MISSING?;
}
/**
@@ -205,88 +270,348 @@ fn usz! String.index_of(String s, String needle)
* @pure
* @ensure return < s.len
* @require needle.len > 0 "The needle must be len 1 or more"
* @return "the index of the needle"
* @return! SearchResult.MISSING "if the needle cannot be found"
**/
fn usz! String.rindex_of(String s, String needle)
fn usz! String.rindex_of(s, String needle)
{
usz match = 0;
usz needed = needle.len;
usz index_start = 0;
char search = needle[^1];
foreach_r (usz i, char c : s)
if (needed > 0 && s.len >= needed)
{
if (c == search)
char first = needle[0];
foreach_r (i, c: s[..^needed])
{
if (!match) index_start = i;
match++;
if (match == needed) return index_start - needle.len + 1;
search = needle[^(match + 1)];
continue;
}
if (match)
{
match = 0;
search = needle[^1];
if (c == first && s[i:needed] == needle) return i;
}
}
return SearchResult.MISSING!;
return SearchResult.MISSING?;
}
fn ZString String.zstr_copy(String s, Allocator* using = mem::heap())
fn String ZString.str_view(str)
{
usz len = s.len;
char* str = malloc(len + 1, .using = using);
mem::copy(str, s.ptr, len);
str[len] = 0;
return (ZString)str;
return (String)(str[:str.len()]);
}
fn String String.concat(String s1, String s2, Allocator* using = mem::heap())
fn usz ZString.char_len(str)
{
usz full_len = s1.len + s2.len;
char* str = malloc(full_len + 1, .using = using);
usz s1_len = s1.len;
mem::copy(str, s1.ptr, s1_len);
mem::copy(str + s1_len, s2.ptr, s2.len);
str[full_len] = 0;
return (String)str[:full_len];
usz len = 0;
char* ptr = (char*)str;
while (char c = ptr++[0])
{
if (c & 0xC0 != 0x80) len++;
}
return len;
}
fn String String.tconcat(String s1, String s2) => s1.concat(s2, mem::temp());
fn ZString String.zstr_tcopy(String s) => s.zstr_copy(mem::temp()) @inline;
fn String String.copy(String s, Allocator* using = mem::heap())
fn usz ZString.len(str)
{
usz len = s.len;
char* str = malloc(len + 1, .using = using);
mem::copy(str, s.ptr, len);
str[len] = 0;
return (String)str[:len];
usz len = 0;
char* ptr = (char*)str;
while (char c = ptr++[0]) len++;
return len;
}
fn String String.tcopy(String s) => s.copy(mem::temp()) @inline;
fn String ZString.copy(ZString z, Allocator* using = mem::heap()) => z.as_str().copy(using) @inline;
fn String ZString.tcopy(ZString z) => z.as_str().copy(mem::temp()) @inline;
fn ZString String.zstr_copy(s, Allocator* allocator = mem::heap())
{
usz len = s.len;
char* str = allocator.alloc(len + 1);
mem::copy(str, s.ptr, len);
str[len] = 0;
return (ZString)str;
}
fn Char16[]! String.to_utf16(String s, Allocator* using = mem::heap())
fn String String.concat(s1, String s2, Allocator* allocator = mem::heap())
{
usz full_len = s1.len + s2.len;
char* str = allocator.alloc(full_len + 1);
usz s1_len = s1.len;
mem::copy(str, s1.ptr, s1_len);
mem::copy(str + s1_len, s2.ptr, s2.len);
str[full_len] = 0;
return (String)str[:full_len];
}
fn String String.tconcat(s1, String s2) => s1.concat(s2, mem::temp());
fn ZString String.zstr_tcopy(s) => s.zstr_copy(mem::temp()) @inline;
fn String String.copy(s, Allocator* allocator = mem::heap())
{
usz len = s.len;
char* str = allocator.alloc(len + 1);
mem::copy(str, s.ptr, len);
str[len] = 0;
return (String)str[:len];
}
fn void String.free(&s, Allocator* allocator = mem::heap())
{
if (!s.len) return;
allocator.free(s.ptr);
*s = "";
}
fn String String.tcopy(s) => s.copy(mem::temp()) @inline;
fn String ZString.copy(z, Allocator* allocator = mem::temp())
{
return z.str_view().copy(allocator) @inline;
}
fn String ZString.tcopy(z)
{
return z.str_view().copy(mem::temp()) @inline;
}
/**
* Convert an UTF-8 string to UTF-16
* @return "The UTF-16 string as a slice, allocated using the given allocator"
* @return! UnicodeResult.INVALID_UTF8 "If the string contained an invalid UTF-8 sequence"
* @return! AllocationFailure "If allocation of the string fails"
**/
fn Char16[]! String.to_new_utf16(s, Allocator* allocator = mem::heap())
{
usz len16 = conv::utf16len_for_utf8(s);
Char16* data = malloc_checked(Char16, len16 + 1, .using = using)?;
conv::utf8to16_unsafe(s, data)?;
Char16* data = allocator.new_array_checked(Char16, len16 + 1)!;
conv::utf8to16_unsafe(s, data)!;
data[len16] = 0;
return data[:len16];
}
fn String! from_utf16(Char16[] utf16, Allocator* using = mem::heap())
/**
* Convert an UTF-8 string to UTF-16
* @return "The UTF-16 string as a slice, allocated using the given allocator"
* @return! UnicodeResult.INVALID_UTF8 "If the string contained an invalid UTF-8 sequence"
* @return! AllocationFailure "If allocation of the string fails"
**/
fn Char16[]! String.to_temp_utf16(s)
{
usz len = conv::utf8len_for_utf16(utf16);
char* data = malloc_checked(len + 1, .using = using)?;
conv::utf16to8_unsafe(utf16, data)?;
return s.to_new_utf16(mem::temp());
}
fn WString! String.to_new_wstring(s, Allocator* allocator = mem::heap())
{
return (WString)s.to_new_utf16(allocator).ptr;
}
fn WString! String.to_temp_wstring(s)
{
return (WString)s.to_temp_utf16().ptr;
}
fn Char32[]! String.to_new_utf32(s, Allocator* allocator = mem::heap())
{
usz codepoints = conv::utf8_codepoints(s);
Char32* data = allocator.new_array(Char32, codepoints + 1);
conv::utf8to32_unsafe(s, data)!;
data[codepoints] = 0;
return data[:codepoints];
}
fn Char32[]! String.to_temp_utf32(s)
{
return s.to_new_utf32(mem::temp());
}
fn void String.convert_ascii_to_lower(s)
{
foreach (&c : s) if (c.is_upper()) *c += 'a' - 'A';
}
fn String String.new_ascii_to_lower(s, Allocator* allocator = mem::heap())
{
String copy = s.copy(allocator);
copy.convert_ascii_to_lower();
return copy;
}
fn String String.temp_ascii_to_lower(s, Allocator* allocator = mem::heap())
{
return s.new_ascii_to_lower(mem::temp());
}
fn void String.convert_ascii_to_upper(s)
{
foreach (&c : s) if (c.is_lower()) *c -= 'a' - 'A';
}
fn String String.new_ascii_to_upper(s, Allocator* allocator = mem::heap())
{
String copy = s.copy(allocator);
copy.convert_ascii_to_upper();
return copy;
}
fn StringIterator String.iterator(s)
{
return { s, 0 };
}
fn String String.temp_ascii_to_upper(s)
{
return s.new_ascii_to_upper(mem::temp());
}
fn String! new_from_utf32(Char32[] utf32, Allocator* allocator = mem::heap())
{
usz len = conv::utf8len_for_utf32(utf32);
char* data = allocator.alloc_checked(len + 1)!;
defer catch allocator.free(data);
conv::utf32to8_unsafe(utf32, data);
data[len] = 0;
return (String)data[:len];
}
fn String! new_from_utf16(Char16[] utf16, Allocator* allocator = mem::heap())
{
usz len = conv::utf8len_for_utf16(utf16);
char* data = allocator.alloc_checked(len + 1)!;
defer catch allocator.free(data);
conv::utf16to8_unsafe(utf16, data)!;
data[len] = 0;
return (String)data[:len];
}
fn String! new_from_wstring(WString wstring, Allocator* allocator = mem::heap())
{
usz utf16_len;
while (wstring[utf16_len] != 0) utf16_len++;
Char16[] utf16 = wstring[:utf16_len];
return new_from_utf16(utf16, allocator);
}
fn String! temp_from_wstring(WString wstring) => new_from_wstring(wstring, mem::temp()) @inline;
fn String! temp_from_utf16(Char16[] utf16) => new_from_utf16(utf16, mem::temp()) @inline;
fn usz String.utf8_codepoints(s)
{
usz len = 0;
foreach (char c : s)
{
if (c & 0xC0 != 0x80) len++;
}
return len;
}
macro String.to_integer(string, $Type)
{
usz len = string.len;
usz index = 0;
char* ptr = string.ptr;
while (index < len && ascii::is_blank_m(ptr[index])) index++;
if (len == index) return NumberConversion.EMPTY_STRING?;
bool is_negative;
switch (string[index])
{
case '-':
if ($Type.min == 0) return NumberConversion.NEGATIVE_VALUE?;
is_negative = true;
index++;
case '+':
index++;
default:
break;
}
if (len == index) return NumberConversion.MALFORMED_INTEGER?;
$Type base = 10;
if (string[index] == '0')
{
index++;
if (index == len) return ($Type)0;
switch (string[index])
{
case 'x':
case 'X':
base = 16;
index++;
case 'b':
case 'B':
base = 2;
index++;
case 'o':
case 'O':
base = 8;
index++;
default:
break;
}
if (len == index) return NumberConversion.MALFORMED_INTEGER?;
}
$Type value = 0;
while (index != len)
{
char c = {|
char ch = string[index++];
if (base != 16 || ch < 'A') return (char)(ch - '0');
if (ch <= 'F') return (char)(ch - 'A');
if (ch < 'a') return NumberConversion.MALFORMED_INTEGER?;
if (ch > 'f') return NumberConversion.MALFORMED_INTEGER?;
return (char)(ch - 'a');
|}!;
if (c >= base) return NumberConversion.MALFORMED_INTEGER?;
value = {|
if (is_negative)
{
$Type new_value = value * base - c;
if (new_value > value) return NumberConversion.INTEGER_OVERFLOW?;
return new_value;
}
$Type new_value = value * base + c;
if (new_value < value) return NumberConversion.INTEGER_OVERFLOW?;
return new_value;
|}!;
}
return value;
}
fn int128! String.to_int128(s) => s.to_integer(int128);
fn long! String.to_long(s) => s.to_integer(long);
fn int! String.to_int(s) => s.to_integer(int);
fn short! String.to_short(s) => s.to_integer(short);
fn ichar! String.to_ichar(s) => s.to_integer(ichar);
fn uint128! String.to_uint128(s) => s.to_integer(uint128);
fn ulong! String.to_ulong(s) => s.to_integer(ulong);
fn uint! String.to_uint(s) => s.to_integer(uint);
fn ushort! String.to_ushort(s) => s.to_integer(ushort);
fn char! String.to_uchar(s) => s.to_integer(char);
fn double! String.to_double(s) => s.to_real(double);
fn float! String.to_float(s) => s.to_real(float);
fn Splitter String.splitter(self, String split)
{
return Splitter { self, split, 0 };
}
struct Splitter
{
String string;
String split;
usz current;
}
fn void Splitter.reset(&self)
{
self.current = 0;
}
fn String! Splitter.next(&self)
{
usz len = self.string.len;
usz current = self.current;
if (current >= len) return IteratorResult.NO_MORE_ELEMENT?;
String remaining = self.string[current..];
usz! next = remaining.index_of(self.split);
if (try next)
{
defer self.current = current + next + self.split.len;
return remaining[:next];
}
self.current = len;
return remaining;
}
fn Char16[]! String.to_temp_utf16(String s) => s.to_utf16(mem::temp());

View File

@@ -6,18 +6,18 @@ struct StringIterator
usz current;
}
fn void StringIterator.reset(StringIterator* this)
fn void StringIterator.reset(&self)
{
this.current = 0;
self.current = 0;
}
fn Char32! StringIterator.next(StringIterator* this)
fn Char32! StringIterator.next(&self)
{
usz len = this.utf8.len;
usz current = this.current;
if (current >= len) return IteratorResult.NO_MORE_ELEMENT!;
usz len = self.utf8.len;
usz current = self.current;
if (current >= len) return IteratorResult.NO_MORE_ELEMENT?;
usz read = (len - current < 4 ? len - current : 4);
Char32 res = conv::utf8_to_char32(&this.utf8[current], &read)?;
this.current += read;
Char32 res = conv::utf8_to_char32(&self.utf8[current], &read)!;
self.current += read;
return res;
}
}

View File

@@ -1,4 +1,4 @@
module std::core::str;
module std::core::string;
import std::math;
// Float parsing based on code in Musl floatscan.c by Rich Felker.
@@ -64,7 +64,7 @@ macro double! decfloat(char[] chars, int $bits, int $emin, int sign)
got_rad = true;
if (index == last_char)
{
if (!got_digit) return NumberConversion.MALFORMED_FLOAT!;
if (!got_digit) return NumberConversion.MALFORMED_FLOAT?;
return sign * 0.0;
}
if (index != last_char && (c = chars[++index]) == '0')
@@ -83,7 +83,7 @@ macro double! decfloat(char[] chars, int $bits, int $emin, int sign)
switch
{
case c == '.':
if (got_rad) return NumberConversion.MALFORMED_FLOAT!;
if (got_rad) return NumberConversion.MALFORMED_FLOAT?;
got_rad = true;
lrp = dc;
case k < KMAX - 3:
@@ -105,7 +105,7 @@ macro double! decfloat(char[] chars, int $bits, int $emin, int sign)
got_digit = true;
default:
dc++;
if (c != '0') x[KMAX - 4] |= 1;
if (c != '0') x[KMAX - 4] |= 1;
}
if (index == last_char) break;
@@ -113,24 +113,24 @@ macro double! decfloat(char[] chars, int $bits, int $emin, int sign)
c = chars[++index];
}
if (!got_rad) lrp = dc;
if (!got_digit) return NumberConversion.MALFORMED_FLOAT!;
if (!got_digit) return NumberConversion.MALFORMED_FLOAT?;
if ((c | 32) == 'e')
{
if (last_char == index) return NumberConversion.MALFORMED_FLOAT!;
long e10 = str::to_long((String)chars[index + 1..]) ?? NumberConversion.MALFORMED_FLOAT!?;
if (last_char == index) return NumberConversion.MALFORMED_FLOAT?;
long e10 = String.to_long((String)chars[index + 1..]) ?? NumberConversion.MALFORMED_FLOAT?!;
lrp += e10;
}
else if (index != last_char)
{
return NumberConversion.MALFORMED_FLOAT!;
return NumberConversion.MALFORMED_FLOAT?;
}
// Handle zero specially to avoid nasty special cases later
if (!x[0]) return sign * 0.0;
// Optimize small integers (w/no exponent) and over/under-flow
if (lrp == dc && dc < 10 && ($bits > 30 || (ulong)x[0] >> $bits == 0)) return sign * (double)x[0];
if (lrp > - $emin / 2) return NumberConversion.FLOAT_OUT_OF_RANGE!;
if (lrp < $emin - 2 * math::DOUBLE_MANT_DIG) return NumberConversion.FLOAT_OUT_OF_RANGE!;
if (lrp > - $emin / 2) return NumberConversion.FLOAT_OUT_OF_RANGE?;
if (lrp < $emin - 2 * math::DOUBLE_MANT_DIG) return NumberConversion.FLOAT_OUT_OF_RANGE?;
// Align incomplete final B1B digit
if (j)
@@ -235,7 +235,7 @@ macro double! decfloat(char[] chars, int $bits, int $emin, int sign)
carry = (1000000000 >> sh) * tmp;
if (k == a && !x[k])
{
a = (a + 1) & MASK;
a = (a + 1) & MASK;
i--;
rp -= 9;
}
@@ -320,7 +320,7 @@ macro double! decfloat(char[] chars, int $bits, int $emin, int sign)
y *= 0.5;
e2++;
}
if (e2 + math::DOUBLE_MANT_DIG > emax || (denormal && frac)) return NumberConversion.MALFORMED_FLOAT!;
if (e2 + math::DOUBLE_MANT_DIG > emax || (denormal && frac)) return NumberConversion.MALFORMED_FLOAT?;
}
return math::scalbn(y, e2);
}
@@ -351,7 +351,7 @@ macro double! hexfloat(char[] chars, int $bits, int $emin, int sign)
got_rad = true;
if (index == last_char)
{
if (!got_digit) return NumberConversion.MALFORMED_FLOAT!;
if (!got_digit) return NumberConversion.MALFORMED_FLOAT?;
return sign * 0.0;
}
if (index != last_char && (c = chars[++index]) == '0')
@@ -369,7 +369,7 @@ macro double! hexfloat(char[] chars, int $bits, int $emin, int sign)
{
if (c == '.')
{
if (got_rad) return NumberConversion.MALFORMED_FLOAT!;
if (got_rad) return NumberConversion.MALFORMED_FLOAT?;
got_rad = true;
rp = dc;
}
@@ -396,20 +396,20 @@ macro double! hexfloat(char[] chars, int $bits, int $emin, int sign)
if (index == last_char) break;
c = chars[++index];
}
if (!got_digit) return NumberConversion.MALFORMED_FLOAT!;
if (!got_digit) return NumberConversion.MALFORMED_FLOAT?;
if (!got_rad) rp = dc;
for (; dc < 8; dc++) x *= 16;
long e2;
if ((c | 32) == 'p')
{
long e2val = str::to_long((String)chars[index + 1..]) ?? NumberConversion.MALFORMED_FLOAT!?;
e2 = e2val;
long e2val = String.to_long((String)chars[index + 1..]) ?? (NumberConversion.MALFORMED_FLOAT?)!;
e2 = e2val;
}
e2 += 4 * rp - 32;
if (!x) return sign * 0.0;
if (e2 > -$emin) return NumberConversion.FLOAT_OUT_OF_RANGE!;
if (e2 < $emin - 2 * math::DOUBLE_MANT_DIG) return NumberConversion.FLOAT_OUT_OF_RANGE!;
if (e2 > -$emin) return NumberConversion.FLOAT_OUT_OF_RANGE?;
if (e2 < $emin - 2 * math::DOUBLE_MANT_DIG) return NumberConversion.FLOAT_OUT_OF_RANGE?;
while (x < 0x80000000)
{
@@ -444,12 +444,12 @@ macro double! hexfloat(char[] chars, int $bits, int $emin, int sign)
}
y = bias + sign * (double)x + sign * y;
y -= bias;
if (!y) return NumberConversion.FLOAT_OUT_OF_RANGE!;
if (!y) return NumberConversion.FLOAT_OUT_OF_RANGE?;
return math::scalbn(y, (int)e2);
}
macro floatparse(String chars, $Type) @private
macro String.to_real(chars, $Type) @private
{
int sign = 1;
$switch ($Type)
@@ -460,23 +460,23 @@ macro floatparse(String chars, $Type) @private
const int BITS = math::DOUBLE_MANT_DIG;
const int EMIN = math::DOUBLE_MIN_EXP - BITS;
$case float128:
$assert(false, "Not yet supported");
$error "Not yet supported";
$default:
$assert(false, "Unexpected type");
$error "Unexpected type";
$endswitch
while (chars.len && chars[0] == ' ') chars = chars[1..];
if (!chars.len) return NumberConversion.MALFORMED_FLOAT!;
switch (chars[0])
{
case '-':
sign = -1;
nextcase;
case '+':
chars = chars[1..];
}
if (chars == "infinity" || chars == "INFINITY") return sign * $Type.inf;
if (chars == "NAN" || chars == "nan") return $Type.nan;
while (chars.len && chars[0] == ' ') chars = chars[1..];
if (!chars.len) return NumberConversion.MALFORMED_FLOAT?;
switch (chars[0])
{
case '-':
sign = -1;
nextcase;
case '+':
chars = chars[1..];
}
if (chars == "infinity" || chars == "INFINITY") return sign * $Type.inf;
if (chars == "NAN" || chars == "nan") return $Type.nan;
if (chars.len > 2 && chars[0] == '0' && (chars[1] | 32) == 'x')
{

View File

@@ -1,3 +1,4 @@
module std::core::types;
import libc;
@@ -10,107 +11,103 @@ fault ConversionResult
/**
* @require $Type.kindof.is_int() || $Type.kindof == TypeKind.ENUM "Argument was not an integer"
**/
macro variant_to_int(variant v, $Type)
macro any_to_int(any* v, $Type)
{
typeid variant_type = v.type;
TypeKind kind = variant_type.kindof;
typeid any_type = v.type;
TypeKind kind = any_type.kindof;
if (kind == TypeKind.ENUM)
{
variant_type = variant_type.inner;
kind = variant_type.kindof;
any_type = any_type.inner;
kind = any_type.kindof;
}
bool is_mixed_signed = $Type.kindof != variant_type.kindof;
bool is_mixed_signed = $Type.kindof != any_type.kindof;
$Type max = $Type.max;
$Type min = $Type.min;
switch (variant_type)
switch (any_type)
{
case ichar:
ichar c = *(char*)v.ptr;
if (is_mixed_signed && c < 0) return ConversionResult.VALUE_OUT_OF_UNSIGNED_RANGE!;
if (is_mixed_signed && c < 0) return ConversionResult.VALUE_OUT_OF_UNSIGNED_RANGE?;
return ($Type)c;
case short:
short s = *(short*)v.ptr;
if (is_mixed_signed && s < 0) return ConversionResult.VALUE_OUT_OF_UNSIGNED_RANGE!;
if (s > max || s < min) return ConversionResult.VALUE_OUT_OF_RANGE!;
if (is_mixed_signed && s < 0) return ConversionResult.VALUE_OUT_OF_UNSIGNED_RANGE?;
if (s > max || s < min) return ConversionResult.VALUE_OUT_OF_RANGE?;
return ($Type)s;
case int:
int i = *(int*)v.ptr;
if (is_mixed_signed && i < 0) return ConversionResult.VALUE_OUT_OF_UNSIGNED_RANGE!;
if (i > max || i < min) return ConversionResult.VALUE_OUT_OF_RANGE!;
if (is_mixed_signed && i < 0) return ConversionResult.VALUE_OUT_OF_UNSIGNED_RANGE?;
if (i > max || i < min) return ConversionResult.VALUE_OUT_OF_RANGE?;
return ($Type)i;
case long:
long l = *(long*)v.ptr;
if (is_mixed_signed && l < 0) return ConversionResult.VALUE_OUT_OF_UNSIGNED_RANGE!;
if (l > max || l < min) return ConversionResult.VALUE_OUT_OF_RANGE!;
if (is_mixed_signed && l < 0) return ConversionResult.VALUE_OUT_OF_UNSIGNED_RANGE?;
if (l > max || l < min) return ConversionResult.VALUE_OUT_OF_RANGE?;
return ($Type)l;
case int128:
int128 i = *(int128*)v.ptr;
if (is_mixed_signed && i < 0) return ConversionResult.VALUE_OUT_OF_UNSIGNED_RANGE!;
if (i > max || i < min) return ConversionResult.VALUE_OUT_OF_RANGE!;
if (is_mixed_signed && i < 0) return ConversionResult.VALUE_OUT_OF_UNSIGNED_RANGE?;
if (i > max || i < min) return ConversionResult.VALUE_OUT_OF_RANGE?;
return ($Type)i;
case char:
char c = *(char*)v.ptr;
if (c > max) return ConversionResult.VALUE_OUT_OF_RANGE!;
if (c > max) return ConversionResult.VALUE_OUT_OF_RANGE?;
return ($Type)c;
case ushort:
ushort s = *(ushort*)v.ptr;
if (s > max || s < min) return ConversionResult.VALUE_OUT_OF_RANGE!;
if (s > max || s < min) return ConversionResult.VALUE_OUT_OF_RANGE?;
return ($Type)s;
case uint:
uint i = *(uint*)v.ptr;
if (i > max || i < min) return ConversionResult.VALUE_OUT_OF_RANGE!;
if (i > max || i < min) return ConversionResult.VALUE_OUT_OF_RANGE?;
return ($Type)i;
case ulong:
ulong l = *(ulong*)v.ptr;
if (l > max || l < min) return ConversionResult.VALUE_OUT_OF_RANGE!;
if (l > max || l < min) return ConversionResult.VALUE_OUT_OF_RANGE?;
return ($Type)l;
case uint128:
uint128 i = *(uint128*)v.ptr;
if (i > max || i < min) return ConversionResult.VALUE_OUT_OF_RANGE!;
if (i > max || i < min) return ConversionResult.VALUE_OUT_OF_RANGE?;
return ($Type)i;
default:
unreachable();
}
}
fn bool typeid.is_subtype_of(self, typeid other)
{
while (self != void.typeid)
{
if (self == other) return true;
self = self.parentof;
}
return false;
}
macro bool is_subtype_of($Type, $OtherType)
{
var $typeid = $Type.typeid;
$switch ($Type)
$case $OtherType: return true;
$default: return false;
$endswitch
}
macro bool is_numerical($Type)
{
var $kind = $Type.kindof;
$if ($kind == TypeKind.DISTINCT)
return is_numerical($Type.inner);
$if $kind == TypeKind.DISTINCT:
return is_numerical($typefrom($Type.inner));
$else
return $kind == TypeKind.SIGNED_INT || $kind == TypeKind.UNSIGNED_INT || $kind == TypeKind.FLOAT
|| $kind == TypeKind.VECTOR;
$endif
}
fn bool TypeKind.is_int(TypeKind kind) @inline
fn bool TypeKind.is_int(kind) @inline
{
return kind == TypeKind.SIGNED_INT || kind == TypeKind.UNSIGNED_INT;
}
macro bool is_indexable($Type)
{
return $checks($Type t, int i, t[i]);
}
macro bool is_comparable($Type)
{
var $kind = $Type.kindof;
$if ($kind == TypeKind.DISTINCT)
return is_comparable($Type.inner);
$else
return $kind == TypeKind.SIGNED_INT || $kind == TypeKind.UNSIGNED_INT || $kind == TypeKind.FLOAT
|| $kind == TypeKind.VECTOR || $kind == TypeKind.BOOL || $kind == TypeKind.POINTER
|| $kind == TypeKind.ENUM;
$endif
}
macro bool is_equatable($Type)
{
return $checks($Type a, a == a);
}
macro bool is_subarray_convertable($Type)
{
$switch ($Type.kindof)
@@ -139,6 +136,18 @@ macro bool is_intlike($Type)
$endswitch
}
macro bool is_underlying_int($Type)
{
$switch ($Type.kindof)
$case SIGNED_INT:
$case UNSIGNED_INT:
return true;
$case DISTINCT:
return is_underlying_int($typefrom($Type.inner));
$default:
return false;
$endswitch
}
macro bool is_float($Type) => $Type.kindof == TypeKind.FLOAT;
@@ -161,18 +170,13 @@ macro bool is_vector($Type)
macro TypeKind inner_kind($Type)
{
$if ($Type.kindof == TypeKind.DISTINCT)
$if $Type.kindof == TypeKind.DISTINCT:
return inner_kind($typefrom($Type.inner));
$else
return $Type.kindof;
$endif
}
macro bool @convertable(#a, $TypeB) @builtin
{
return $checks($TypeB x = #a);
}
macro bool is_same($TypeA, $TypeB)
{
return $TypeA.typeid == $TypeB.typeid;
@@ -180,12 +184,12 @@ macro bool is_same($TypeA, $TypeB)
macro bool @has_same(#a, #b, ...)
{
var $type_a = $typeof(#a).typeid;
$if ($type_a != $typeof(#b).typeid)
return false;
var $type_a = @typeid(#a);
$if $type_a != @typeid(#b):
return false;
$endif
$for (var $i = 0; $i < $vacount; $i++)
$if ($typeof($vaexpr($i)).typeid != $type_a)
$if @typeid($vaexpr($i)) != $type_a:
return false;
$endif
$endfor
@@ -207,58 +211,97 @@ macro bool may_load_atomic($Type)
$endswitch
}
macro lower_to_atomic_compatible_type($Type)
{
$switch ($Type.kindof)
$case SIGNED_INT:
$case UNSIGNED_INT:
return $Type.typeid;
$case DISTINCT:
return lower_to_atomic_compatible_type($Type.inner);
$case FLOAT:
$switch ($Type)
$case float16:
return ushort.typeid;
$case float:
return uint.typeid;
$case double:
return ulong.typeid;
$case float128:
return uint128.typeid;
$default:
return void.typeid;
$endswitch
$default:
return void.typeid;
$endswitch
}
macro bool is_promotable_to_floatlike($Type) => types::is_floatlike($Type) || types::is_int($Type);
macro bool is_promotable_to_float($Type) => types::is_float($Type) || types::is_int($Type);
macro bool is_same_vector_type($Type1, $Type2)
{
$if ($Type1.kindof != TypeKind.VECTOR)
$if $Type1.kindof != TypeKind.VECTOR:
return $Type2.kindof != TypeKind.VECTOR;
$else
return $Type1.inner == $Type2.inner && $Type1.len == $Type2.len;
$endif
}
macro bool is_equatable_value(value)
macro bool is_equatable_type($Type)
{
$if ($defined(value.less) || $defined(value.compare_to) || $defined(value.equals))
$if $defined($Type.less) || $defined($Type.compare_to) || $defined($Type.equals):
return true;
$else
return is_equatable($typeof(value));
return $Type.is_eq;
$endif
}
/**
* Checks if a type implements the copy protocol.
**/
macro bool implements_copy($Type)
{
return $defined($Type.copy) && $defined($Type.free);
}
macro bool is_equatable_value(value)
{
return is_equatable_type($typeof(value));
}
macro bool is_comparable_value(value)
{
$if ($defined(value.less) || $defined(value.compare_to))
$if $defined(value.less) || $defined(value.compare_to):
return true;
$else
return is_comparable($typeof(value));
return $typeof(value).is_ordered;
$endif
}
enum TypeKind : char
{
VOID,
BOOL,
SIGNED_INT,
UNSIGNED_INT,
FLOAT,
TYPEID,
ANYERR,
VARIANT,
ENUM,
FAULT,
STRUCT,
UNION,
BITSTRUCT,
FUNC,
OPTIONAL,
ARRAY,
SUBARRAY,
VECTOR,
DISTINCT,
POINTER,
VOID,
BOOL,
SIGNED_INT,
UNSIGNED_INT,
FLOAT,
TYPEID,
ANYFAULT,
ANY,
ENUM,
FAULT,
STRUCT,
UNION,
BITSTRUCT,
FUNC,
OPTIONAL,
ARRAY,
SUBARRAY,
VECTOR,
DISTINCT,
POINTER,
}
struct TypeEnum

View File

@@ -1,18 +1,25 @@
module std::core::values;
macro typeid @typeid(#value) @builtin => $typeof(#value).typeid;
macro TypeKind @typekind(#value) @builtin => $typeof(#value).kindof;
macro bool @typeis(#value, $Type) @builtin => $typeof(#value).typeid == $Type.typeid;
/**
* Return true if two values have the same type before any conversions.
**/
macro bool @is_same_type(#value1, #value2) => $typeof(#value1).typeid == $typeof(#value2).typeid;
macro bool @is_bool(#value) => types::is_bool($typeof(#value));
macro bool @is_int(#value) => types::is_int($typeof(#value));
macro bool @convertable_to(#a, #b) => $checks($typeof(#b) x = #a);
macro bool @is_floatlike(#value) => types::is_floatlike($typeof(#value));
macro bool @is_float(#value) => types::is_float($typeof(#value));
macro bool @is_promotable_to_floatlike(#value) => types::is_promotable_to_floatlike($typeof(#value));
macro bool @is_promotable_to_float(#value) => types::is_promotable_to_float($typeof(#value));
macro bool @is_vector(#value) => types::is_vector($typeof(#value));
macro bool @is_same_vector_type(#value1, #value2) => types::is_same_vector_type($typeof(#value1), $typeof(#value2));
macro bool @assign_to(#value1, #value2) => $assignable(#value1, $typeof(#value2));
macro promote_int(x)
{
$if (@is_int(x))
$if @is_int(x):
return (double)x;
$else
return x;
@@ -20,3 +27,4 @@ macro promote_int(x)
}
macro TypeKind @inner_kind(#value) => types::inner_kind($typeof(#value));

View File

@@ -1,327 +0,0 @@
module std::core::string;
import libc;
typedef VarString = distinct void*;
typedef DynStr = VarString;
typedef DynString = VarString;
typedef VString = VarString;
typedef Text = VarString;
const usz MIN_CAPACITY = 16;
fn VarString new_with_capacity(usz capacity, Allocator* allocator = mem::heap())
{
if (capacity < MIN_CAPACITY) capacity = MIN_CAPACITY;
StringData* data = malloc(StringData, 1, .using = allocator, .end_padding = capacity);
data.allocator = allocator;
data.len = 0;
data.capacity = capacity;
return (VarString)data;
}
fn VarString new(String c)
{
usz len = c.len;
VarString str = new_with_capacity(len);
StringData* data = str.data();
if (len)
{
data.len = len;
mem::copy(&data.chars, c.ptr, len);
}
return (VarString)data;
}
fn ZString VarString.zstr(VarString str)
{
StringData* data = str.data();
if (!data) return (ZString)"";
if (data.capacity == data.len)
{
str.reserve(1);
data.chars[data.len] = 0;
}
else if (data.chars[data.len] != 0)
{
data.chars[data.len] = 0;
}
return (ZString)&data.chars[0];
}
fn usz VarString.capacity(VarString this)
{
if (!this) return 0;
return this.data().capacity;
}
fn usz VarString.len(VarString this)
{
if (!this) return 0;
return this.data().len;
}
/**
* @require new_size <= this.len()
*/
fn void VarString.chop(VarString this, usz new_size)
{
if (!this) return;
this.data().len = new_size;
}
fn String VarString.str(VarString str)
{
StringData* data = (StringData*)str;
if (!data) return "";
return (String)data.chars[:data.len];
}
fn void VarString.append_utf32(VarString* str, Char32[] chars)
{
str.reserve(chars.len);
foreach (Char32 c : chars)
{
str.append_char32(c);
}
}
/**
* @require index < str.len()
**/
fn void VarString.set(VarString str, usz index, char c)
{
str.data().chars[index] = c;
}
fn void VarString.append_repeat(VarString* str, char c, usz times)
{
if (times == 0) return;
str.reserve(times);
StringData* data = str.data();
for (usz i = 0; i < times; i++)
{
data.chars[data.len++] = c;
}
}
/**
* @require c <= 0x10ffff
*/
fn void VarString.append_char32(VarString* str, Char32 c)
{
if (c < 0x7f)
{
str.reserve(1);
StringData* data = str.data();
data.chars[data.len++] = (char)c;
return;
}
if (c < 0x7ff)
{
str.reserve(2);
StringData* data = str.data();
data.chars[data.len++] = (char)(0xC0 | c >> 6);
data.chars[data.len++] = (char)(0x80 | (c & 0x3F));
return;
}
if (c < 0xffff)
{
str.reserve(3);
StringData* data = str.data();
data.chars[data.len++] = (char)(0xE0 | c >> 12);
data.chars[data.len++] = (char)(0x80 | (c >> 6 & 0x3F));
data.chars[data.len++] = (char)(0x80 | (c & 0x3F));
return;
}
str.reserve(4);
StringData* data = str.data();
data.chars[data.len++] = (char)(0xF0 | c >> 18);
data.chars[data.len++] = (char)(0x80 | (c >> 12 & 0x3F));
data.chars[data.len++] = (char)(0x80 | (c >> 6 & 0x3F));
data.chars[data.len++] = (char)(0x80 | (c & 0x3F));
}
fn VarString VarString.tcopy(VarString* str) => str.copy(mem::temp());
fn VarString VarString.copy(VarString* str, Allocator* allocator = null)
{
if (!str)
{
if (allocator) return new_with_capacity(0, allocator);
return (VarString)null;
}
if (!allocator) allocator = mem::heap();
StringData* data = str.data();
VarString new_string = new_with_capacity(data.capacity, allocator);
mem::copy((char*)new_string.data(), (char*)data, StringData.sizeof + data.len);
return new_string;
}
fn ZString VarString.copy_zstr(VarString* str, Allocator* allocator = mem::heap())
{
usz str_len = str.len();
if (!str_len)
{
return (ZString)calloc(1, .using = allocator);
}
char* zstr = malloc(str_len + 1, .using = allocator);
StringData* data = str.data();
mem::copy(zstr, &data.chars, str_len);
zstr[str_len] = 0;
return (ZString)zstr;
}
fn String VarString.copy_str(VarString* str, Allocator* allocator = mem::heap())
{
return (String)str.copy_zstr(allocator)[:str.len()];
}
fn String VarString.tcopy_str(VarString* str) => str.copy_str(mem::temp()) @inline;
fn bool VarString.equals(VarString str, VarString other_string)
{
StringData *str1 = str.data();
StringData *str2 = other_string.data();
if (str1 == str2) return true;
if (!str1) return str2.len == 0;
if (!str2) return str1.len == 0;
usz str1_len = str1.len;
if (str1_len != str2.len) return false;
for (int i = 0; i < str1_len; i++)
{
if (str1.chars[i] != str2.chars[i]) return false;
}
return true;
}
fn void VarString.destroy(VarString* str)
{
if (!*str) return;
StringData* data = str.data();
if (!data) return;
free(data, .using = data.allocator);
*str = (VarString)null;
}
fn bool VarString.less(VarString str, VarString other_string)
{
StringData* str1 = str.data();
StringData* str2 = other_string.data();
if (str1 == str2) return false;
if (!str1) return str2.len != 0;
if (!str2) return str1.len == 0;
usz str1_len = str1.len;
usz str2_len = str2.len;
if (str1_len != str2_len) return str1_len < str2_len;
for (int i = 0; i < str1_len; i++)
{
if (str1.chars[i] >= str2.chars[i]) return false;
}
return true;
}
fn void VarString.append_chars(VarString* this, String str)
{
usz other_len = str.len;
if (!other_len) return;
if (!*this)
{
*this = new(str);
return;
}
this.reserve(other_len);
StringData* data = (StringData*)*this;
mem::copy(&data.chars[data.len], str.ptr, other_len);
data.len += other_len;
}
fn Char32[] VarString.copy_utf32(VarString* this, Allocator* allocator = mem::heap())
{
return str::utf8to32(this.str(), allocator) @inline!!;
}
fn void VarString.append_string(VarString* this, VarString str)
{
StringData* other = (StringData*)str;
if (!other) return;
this.append(str.str());
}
fn void VarString.clear(VarString* str)
{
str.data().len = 0;
}
fn void VarString.append_char(VarString* str, char c)
{
if (!*str)
{
*str = new_with_capacity(MIN_CAPACITY);
}
str.reserve(1);
StringData* data = (StringData*)*str;
data.chars[data.len++] = c;
}
macro void VarString.append(VarString* str, value)
{
var $Type = $typeof(value);
$switch ($Type)
$case char:
$case ichar:
str.append_char(value);
$case VarString:
str.append_string(value);
$case String:
str.append_chars(value);
$case Char32:
str.append_char32(value);
$default:
$switch
$case @convertible(value, Char32):
str.append_char32(value);
$case @convertible(value, String):
str.append_chars(value);
$default:
$assert(false, "Unsupported type for append use printf instead.");
$endswitch
$endswitch
}
fn StringData* VarString.data(VarString str) @inline @private
{
return (StringData*)str;
}
fn void VarString.reserve(VarString* str, usz addition) @private
{
StringData* data = str.data();
if (!data)
{
*str = string::new_with_capacity(addition);
return;
}
usz len = data.len + addition;
if (data.capacity >= len) return;
usz new_capacity = data.capacity *= 2;
if (new_capacity < MIN_CAPACITY) new_capacity = MIN_CAPACITY;
*str = (VarString)realloc(data, StringData.sizeof + new_capacity, .using = data.allocator);
}
fn VarString VarString.new_concat(VarString a, VarString b, Allocator* allocator = mem::heap())
{
VarString string = new_with_capacity(a.len() + b.len(), allocator);
string.append(a);
string.append(b);
return string;
}
struct StringData @private
{
Allocator* allocator;
usz len;
usz capacity;
char[*] chars;
}

View File

@@ -12,36 +12,34 @@ struct Rc4
/**
* Initialize the RC4 state.
*
* @param [inout] this "The RC4 state"
* @param [in] key "The key to use"
* @require key.len > 0 "The key must be at least 1 byte long"
**/
fn void Rc4.init(Rc4* this, char[] key)
fn void Rc4.init(&self, char[] key)
{
// Init the state matrix
foreach (char i, &c : this.state) *c = i;
foreach (char i, &c : self.state) *c = i;
for (int i = 0, int j = 0; i < 256; i++)
{
j = (j + this.state[i] + key[i % key.len]) & 0xFF;
@swap(this.state[i], this.state[j]);
j = (j + self.state[i] + key[i % key.len]) & 0xFF;
@swap(self.state[i], self.state[j]);
}
this.i = 0;
this.j = 0;
self.i = 0;
self.j = 0;
}
/**
* Encrypt or decrypt a sequence of bytes.
*
* @param [inout] this "The RC4 State"
* @param [in] in "The input"
* @param [out] out "The output"
* @require in.len <= out.len "Output would overflow"
**/
fn void Rc4.crypt(Rc4* this, char[] in, char[] out)
fn void Rc4.crypt(&self, char[] in, char[] out)
{
uint i = this.i;
uint j = this.j;
char* state = &this.state;
uint i = self.i;
uint j = self.j;
char* state = &self.state;
isz len = in.len;
foreach (idx, c : in)
{
@@ -50,16 +48,16 @@ fn void Rc4.crypt(Rc4* this, char[] in, char[] out)
@swap(state[i], state[j]);
out[idx] = in[idx] ^ state[(state[i] + state[j]) & 0xFF];
}
this.i = i;
this.j = j;
self.i = i;
self.j = j;
}
/**
* Clear the rc4 state.
*
* @param [out] this "The RC4 State"
* @param [&out] self "The RC4 State"
**/
fn void Rc4.destroy(Rc4* this)
fn void Rc4.destroy(&self)
{
*this = {};
*self = {};
}

288
lib/std/encoding/base64.c3 Normal file
View File

@@ -0,0 +1,288 @@
module std::encoding::base64;
import std::core::bitorder;
// The implementation is based on https://www.rfc-editor.org/rfc/rfc4648
// Specifically this section:
// https://www.rfc-editor.org/rfc/rfc4648#section-4
const STD_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
const URL_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
const MASK @private = 0b111111;
struct Base64Encoder
{
int padding;
String alphabet;
}
fault Base64Error
{
DUPLICATE_IN_ALPHABET,
PADDING_IN_ALPHABET,
DESTINATION_TOO_SMALL,
INVALID_PADDING,
INVALID_CHARACTER,
}
/**
* @param alphabet "The alphabet used for encoding."
* @param padding "Set to a negative value to disable padding."
* @require alphabet.len == 64
* @require padding < 256
* @return! Base64Error.DUPLICATE_IN_ALPHABET, Base64Error.PADDING_IN_ALPHABET
**/
fn void! Base64Encoder.init(&self, String alphabet, int padding = '=')
{
check_alphabet(alphabet, padding)!;
*self = { .padding = padding, .alphabet = alphabet };
}
/**
* Calculate the size of the encoded data.
* @param n "Size of the input to be encoded."
* @return "The size of the input once encoded."
**/
fn usz Base64Encoder.encode_len(&self, usz n)
{
if (self.padding >= 0) return (n + 2) / 3 * 4;
usz trailing = n % 3;
return n / 3 * 4 + (trailing * 4 + 2) / 3;
}
/**
* Encode the content of src into dst, which must be properly sized.
* @param src "The input to be encoded."
* @param dst "The encoded input."
* @return "The encoded size."
* @return! Base64Error.DESTINATION_TOO_SMALL
**/
fn usz! Base64Encoder.encode(&self, char[] src, char[] dst)
{
if (src.len == 0) return 0;
usz dn = self.encode_len(src.len);
if (dst.len < dn) return Base64Error.DESTINATION_TOO_SMALL?;
usz trailing = src.len % 3;
char[] src3 = src[:^trailing];
while (src3.len > 0)
{
uint group = (uint)src3[0] << 16 | (uint)src3[1] << 8 | (uint)src3[2];
dst[0] = self.alphabet[group >> 18 & MASK];
dst[1] = self.alphabet[group >> 12 & MASK];
dst[2] = self.alphabet[group >> 6 & MASK];
dst[3] = self.alphabet[group & MASK];
dst = dst[4..];
src3 = src3[3..];
}
// Encode the remaining bytes according to:
// https://www.rfc-editor.org/rfc/rfc4648#section-3.5
switch (trailing)
{
case 1:
uint group = (uint)src[^1] << 16;
dst[0] = self.alphabet[group >> 18 & MASK];
dst[1] = self.alphabet[group >> 12 & MASK];
if (self.padding >= 0)
{
char pad = (char)self.padding;
dst[2] = pad;
dst[3] = pad;
}
case 2:
uint group = (uint)src[^2] << 16 | (uint)src[^1] << 8;
dst[0] = self.alphabet[group >> 18 & MASK];
dst[1] = self.alphabet[group >> 12 & MASK];
dst[2] = self.alphabet[group >> 6 & MASK];
if (self.padding >= 0)
{
char pad = (char)self.padding;
dst[3] = pad;
}
}
return dn;
}
struct Base64Decoder
{
int padding;
String alphabet;
char[256] reverse;
char invalid;
}
/**
* @param alphabet "The alphabet used for encoding."
* @param padding "Set to a negative value to disable padding."
* @require alphabet.len == 64
* @require padding < 256
* @return! Base64Error.DUPLICATE_IN_ALPHABET, Base64Error.PADDING_IN_ALPHABET
**/
fn void! Base64Decoder.init(&self, String alphabet, int padding = '=')
{
check_alphabet(alphabet, padding)!;
*self = { .padding = padding, .alphabet = alphabet };
bool[256] checked;
foreach (i, c : alphabet)
{
checked[c] = true;
self.reverse[c] = (char)i;
}
if (padding < 0)
{
self.invalid = 255;
return;
}
// Find a character for invalid neither in the alphabet nor equal to the padding.
char pad = (char)padding;
foreach (i, ok : checked)
{
if (!ok && (char)i != pad)
{
self.invalid = (char)i;
break;
}
}
}
/**
* Calculate the size of the decoded data.
* @param n "Size of the input to be decoded."
* @return "The size of the input once decoded."
* @return! Base64Error.INVALID_PADDING
**/
fn usz! Base64Decoder.decode_len(&self, usz n)
{
usz dn = n / 4 * 3;
usz trailing = n % 4;
if (self.padding >= 0)
{
if (trailing != 0) return Base64Error.INVALID_PADDING?;
// source size is multiple of 4
}
else
{
if (trailing == 1) return Base64Error.INVALID_PADDING?;
dn += trailing * 3 / 4;
}
return dn;
}
/**
* Decode the content of src into dst, which must be properly sized.
* @param src "The input to be decoded."
* @param dst "The decoded input."
* @return "The decoded size."
* @return! Base64Error.DESTINATION_TOO_SMALL, Base64Error.INVALID_PADDING, Base64Error.INVALID_CHARACTER
**/
fn usz! Base64Decoder.decode(&self, char[] src, char[] dst)
{
if (src.len == 0) return 0;
usz dn = self.decode_len(src.len)!;
if (dst.len < dn) return Base64Error.DESTINATION_TOO_SMALL?;
usz trailing = src.len % 4;
char[] src4 = src;
switch
{
case self.padding < 0:
src4 = src[:^trailing];
default:
// If there is padding, keep the last 4 bytes for later.
// NB. src.len >= 4 as decode_len passed
trailing = 4;
char pad = (char)self.padding;
if (src[^1] == pad) src4 = src[:^4];
}
while (src4.len > 0)
{
char c0 = self.reverse[src4[0]];
char c1 = self.reverse[src4[1]];
char c2 = self.reverse[src4[2]];
char c3 = self.reverse[src4[3]];
switch (self.invalid)
{
case c0:
case c1:
case c2:
case c3:
return Base64Error.INVALID_CHARACTER?;
}
uint group = (uint)c0 << 18 | (uint)c1 << 12 | (uint)c2 << 6 | (uint)c3;
dst[0] = (char)(group >> 16);
dst[1] = (char)(group >> 8);
dst[2] = (char)group;
dst = dst[3..];
src4 = src4[4..];
}
if (trailing == 0) return dn;
src = src[^trailing..];
char c0 = self.reverse[src[0]];
char c1 = self.reverse[src[1]];
if (c0 == self.invalid || c1 == self.invalid) return Base64Error.INVALID_PADDING?;
if (self.padding < 0)
{
switch (src.len)
{
case 2:
uint group = (uint)c0 << 18 | (uint)c1 << 12;
dst[0] = (char)(group >> 16);
case 3:
char c2 = self.reverse[src[2]];
if (c2 == self.invalid) return Base64Error.INVALID_CHARACTER?;
uint group = (uint)c0 << 18 | (uint)c1 << 12 | (uint)c2 << 6;
dst[0] = (char)(group >> 16);
dst[1] = (char)(group >> 8);
}
}
else
{
// Valid paddings are:
// 2: xx==
// 1: xxx=
char pad = (char)self.padding;
switch (pad)
{
case src[2]:
if (src[3] != pad) return Base64Error.INVALID_PADDING?;
uint group = (uint)c0 << 18 | (uint)c1 << 12;
dst[0] = (char)(group >> 16);
dn -= 2;
case src[3]:
char c2 = self.reverse[src[2]];
if (c2 == self.invalid) return Base64Error.INVALID_CHARACTER?;
uint group = (uint)c0 << 18 | (uint)c1 << 12 | (uint)c2 << 6;
dst[0] = (char)(group >> 16);
dst[1] = (char)(group >> 8);
dn -= 1;
}
}
return dn;
}
// Make sure that all bytes in the alphabet are unique and
// the padding is not present in the alphabet.
fn void! check_alphabet(String alphabet, int padding) @local
{
bool[256] checked;
if (padding < 0)
{
foreach (c : alphabet)
{
if (checked[c]) return Base64Error.DUPLICATE_IN_ALPHABET?;
checked[c] = true;
}
return;
}
char pad = (char)padding;
foreach (c : alphabet)
{
if (c == pad) return Base64Error.PADDING_IN_ALPHABET?;
if (checked[c]) return Base64Error.DUPLICATE_IN_ALPHABET?;
checked[c] = true;
}
}

64
lib/std/encoding/csv.c3 Normal file
View File

@@ -0,0 +1,64 @@
module std::encoding::csv;
import std::io;
struct CsvReader
{
InStream* stream;
String separator;
}
fn void CsvReader.init(&self, InStream* stream, String separator = ",")
{
self.stream = stream;
self.separator = separator;
}
fn String[]! CsvReader.read_new_row(self, Allocator* allocator = mem::heap())
{
return self.read_new_row_with_allocator(mem::temp()) @inline;
}
fn String[]! CsvReader.read_new_row_with_allocator(self, Allocator* allocator = mem::heap())
{
@pool(allocator)
{
return io::treadline(self.stream).split(self.separator, .allocator = allocator);
};
}
fn String[]! CsvReader.read_temp_row(self)
{
return self.read_new_row_with_allocator(mem::temp()) @inline;
}
fn void! CsvReader.skip_row(self) @maydiscard
{
@pool()
{
(void)io::treadline(self.stream);
};
}
macro CsvReader.@each_row(self, int rows = int.max; @body(String[] row))
{
InputStream* stream = self.stream;
String sep = self.separator;
while (rows--)
{
@stack_mem(512; Allocator* mem)
{
String[] parts;
@pool()
{
String! s = stream.treadline();
if (catch err = s)
{
if (err == IoError.EOF) return;
return err?;
}
parts = s.split(sep, .allocator = mem);
};
@body(parts);
};
}
}

View File

@@ -6,7 +6,25 @@ import std::io;
import std::ascii;
import std::collections::object;
enum JsonTokenType
fault JsonParsingError
{
EOF,
UNEXPECTED_CHARACTER,
INVALID_ESCAPE_SEQUENCE,
DUPLICATE_MEMBERS,
INVALID_NUMBER,
}
fn Object*! parse(InStream* s, Allocator* allocator = mem::heap())
{
JsonContext context = { .last_string = dstring::new_with_capacity(64, allocator), .stream = s, .allocator = allocator };
defer context.last_string.free();
return parse_any(&context);
}
// -- Implementation follows --
enum JsonTokenType @local
{
NO_TOKEN,
LBRACE,
@@ -23,79 +41,67 @@ enum JsonTokenType
EOF,
}
struct JsonParser
struct JsonContext @local
{
uint line;
Stream stream;
InStream* stream;
Allocator* allocator;
JsonTokenType token;
DString last_string;
double last_number;
char current;
anyerr current_err;
bool skip_comments;
bool reached_end;
bitstruct : char {
bool skip_comments;
bool reached_end;
bool pushed_back;
}
}
fault JsonParsingError
{
EOF,
UNEXPECTED_CHARACTER,
INVALID_ESCAPE_SEQUENCE,
DUPLICATE_MEMBERS,
INVALID_NUMBER,
}
fn void JsonParser.init(JsonParser* parser, Stream s, Allocator* using = mem::heap())
{
*parser = { .last_string = dstring::new_with_capacity(64, using), .stream = s, .allocator = using };
}
fn Object*! JsonParser.parse_from_token(JsonParser* this, JsonTokenType token)
fn Object*! parse_from_token(JsonContext* context, JsonTokenType token) @local
{
switch (token)
{
case NO_TOKEN: unreachable();
case LBRACE: return this.parse_map();
case LBRACKET: return this.parse_array();
case LBRACE: return parse_map(context);
case LBRACKET: return parse_array(context);
case COMMA:
case RBRACE:
case RBRACKET:
case COLON: return JsonParsingError.UNEXPECTED_CHARACTER!;
case STRING: return object::new_string(this.last_string.str(), this.allocator);
case NUMBER: return object::new_float(this.last_number, this.allocator);
case COLON: return JsonParsingError.UNEXPECTED_CHARACTER?;
case STRING: return object::new_string(context.last_string.str_view(), context.allocator);
case NUMBER: return object::new_float(context.last_number, context.allocator);
case TRUE: return object::new_bool(true);
case FALSE: return object::new_bool(false);
case NULL: return object::new_null();
case EOF: return JsonParsingError.EOF!;
case EOF: return JsonParsingError.EOF?;
}
unreachable();
}
fn Object*! JsonParser.parse_any(JsonParser* this)
fn Object*! parse_any(JsonContext* context) @local
{
return this.parse_from_token(this.advance());
return parse_from_token(context, advance(context));
}
fn JsonTokenType! JsonParser.lex_number(JsonParser* this, char c)
fn JsonTokenType! lex_number(JsonContext *context, char c) @local
{
@pool()
{
DString t = dstring::tnew_with_capacity(32);
DString t = dstring::temp_with_capacity(32);
bool negate = c == '-';
if (negate)
{
t.append(c);
c = this.read_next()?;
c = read_next(context)!;
}
while (c >= '0' && c <= '9')
while (c.is_digit())
{
t.append(c);
c = this.read_next()?;
c = read_next(context)!;
}
if (c == '.')
{
t.append(c);
while (c = this.read_next()?, c >= '0' && c <= '9')
while (c = read_next(context)!, c.is_digit())
{
t.append(c);
}
@@ -103,113 +109,123 @@ fn JsonTokenType! JsonParser.lex_number(JsonParser* this, char c)
if ((c | 32) == 'e')
{
t.append(c);
c = this.read_next()?;
c = read_next(context)!;
switch (c)
{
case '-':
case '+':
t.append(c);
c = this.read_next()?;
c = read_next(context)!;
}
if (c < '0' || c > '9') return JsonParsingError.INVALID_NUMBER!;
while (c >= '0' && c <= '9')
if (!c.is_digit()) return JsonParsingError.INVALID_NUMBER?;
while (c.is_digit())
{
t.append(c);
c = this.read_next()?;
c = read_next(context)!;
}
}
this.pushback();
double! d = str::to_double(t.str()) ?? JsonParsingError.INVALID_NUMBER!;
this.last_number = d?;
pushback(context, c);
double! d = t.str_view().to_double() ?? JsonParsingError.INVALID_NUMBER?;
context.last_number = d!;
return NUMBER;
};
}
fn Object*! JsonParser.parse_map(JsonParser* this)
fn Object*! parse_map(JsonContext* context) @local
{
Object* map = object::new_obj(this.allocator);
JsonTokenType token = this.advance()?;
Object* map = object::new_obj(context.allocator);
JsonTokenType token = advance(context)!;
defer catch map.free();
DString temp_key = dstring::new_with_capacity(32, this.allocator);
DString temp_key = dstring::new_with_capacity(32, context.allocator);
defer temp_key.free();
while (token != JsonTokenType.RBRACE)
{
if (token != JsonTokenType.STRING) return JsonParsingError.UNEXPECTED_CHARACTER!;
DString string = this.last_string;
if (map.has_key(string.str())) return JsonParsingError.DUPLICATE_MEMBERS!;
// Copy the key to our temp holder. We do this to work around the issue
// if the temp allocator should be used as the default allocator.
temp_key.clear();
temp_key.append(string);
this.parse_expected(COLON)?;
Object* element = this.parse_any()?;
map.set(temp_key.str(), element);
token = this.advance()?;
if (token == JsonTokenType.COMMA)
{
token = this.advance()?;
continue;
}
if (token != JsonTokenType.RBRACE) return JsonParsingError.UNEXPECTED_CHARACTER!;
if (token != JsonTokenType.STRING) return JsonParsingError.UNEXPECTED_CHARACTER?;
DString string = context.last_string;
if (map.has_key(string.str_view())) return JsonParsingError.DUPLICATE_MEMBERS?;
// Copy the key to our temp holder. We do this to work around the issue
// if the temp allocator should be used as the default allocator.
temp_key.clear();
temp_key.append(string);
parse_expected(context, COLON)!;
Object* element = parse_any(context)!;
map.set(temp_key.str_view(), element);
token = advance(context)!;
if (token == JsonTokenType.COMMA)
{
token = advance(context)!;
continue;
}
if (token != JsonTokenType.RBRACE) return JsonParsingError.UNEXPECTED_CHARACTER?;
}
return map;
}
fn Object*! JsonParser.parse_array(JsonParser* this)
fn Object*! parse_array(JsonContext* context) @local
{
Object* list = object::new_obj(this.allocator);
Object* list = object::new_obj(context.allocator);
defer catch list.free();
JsonTokenType token = this.advance()?;
JsonTokenType token = advance(context)!;
while (token != JsonTokenType.RBRACKET)
{
Object* element = this.parse_from_token(token)?;
Object* element = parse_from_token(context, token)!;
list.append(element);
token = this.advance()?;
token = advance(context)!;
if (token == JsonTokenType.COMMA)
{
token = this.advance()?;
continue;
token = advance(context)!;
continue;
}
if (token != JsonTokenType.RBRACKET) return JsonParsingError.UNEXPECTED_CHARACTER!;
if (token != JsonTokenType.RBRACKET) return JsonParsingError.UNEXPECTED_CHARACTER?;
}
return list;
}
fn void JsonParser.pushback(JsonParser* this)
fn void pushback(JsonContext* context, char c) @local
{
if (!this.reached_end) this.stream.pushback_byte()!!;
if (!context.reached_end)
{
assert(!context.pushed_back);
context.pushed_back = true;
context.current = c;
}
}
fn char! JsonParser.read_next(JsonParser* this)
fn char! read_next(JsonContext* context) @local
{
if (this.reached_end) return '\0';
char! c = this.stream.read_byte();
if (context.reached_end) return '\0';
if (context.pushed_back)
{
context.pushed_back = false;
return context.current;
}
char! c = context.stream.read_byte();
if (catch err = c)
{
case IoError.EOF:
this.reached_end = true;
context.reached_end = true;
return '\0';
default:
return err!;
return err?;
}
if (c == 0)
{
this.reached_end = true;
context.reached_end = true;
}
return c;
}
fn JsonTokenType! JsonParser.advance(JsonParser* this)
fn JsonTokenType! advance(JsonContext* context) @local
{
char c;
// Skip whitespace
while WS: (c = this.read_next()?)
while WS: (c = read_next(context)!)
{
switch (c)
{
case '\n':
this.line++;
context.line++;
nextcase;
case ' ':
case '\t':
@@ -217,24 +233,24 @@ fn JsonTokenType! JsonParser.advance(JsonParser* this)
case '\v':
continue;
case '/':
if (!this.skip_comments) break;
c = this.read_next()?;
if (!context.skip_comments) break;
c = read_next(context)!;
if (c != '*')
{
this.pushback();
pushback(context, c);
break WS;
}
while COMMENT: (1)
while COMMENT: (true)
{
// Skip to */
while (c = this.read_next()?)
while (c = read_next(context)!)
{
if (c == '\n') this.line++;
if (c == '\n') context.line++;
if (c != '*') continue;
// Skip through all the '*'
while (c = this.read_next()?)
while (c = read_next(context)!)
{
if (c == '\n') this.line++;
if (c == '\n') context.line++;
if (c != '*') break;
}
if (c == '/') break COMMENT;
@@ -248,7 +264,7 @@ fn JsonTokenType! JsonParser.advance(JsonParser* this)
switch (c)
{
case '\0':
return IoError.EOF!;
return IoError.EOF?;
case '{':
return LBRACE;
case '}':
@@ -262,65 +278,65 @@ fn JsonTokenType! JsonParser.advance(JsonParser* this)
case ',':
return COMMA;
case '"':
return this.lex_string();
return lex_string(context);
case '-':
case '0'..'9':
return this.lex_number(c);
return lex_number(context, c);
case 't':
this.match("rue")?;
match(context, "rue")!;
return TRUE;
case 'f':
this.match("alse")?;
match(context, "alse")!;
return FALSE;
case 'n':
this.match("ull")?;
match(context, "ull")!;
return NULL;
default:
return JsonParsingError.UNEXPECTED_CHARACTER!;
return JsonParsingError.UNEXPECTED_CHARACTER?;
}
}
fn void! JsonParser.match(JsonParser* this, String str)
fn void! match(JsonContext* context, String str) @local
{
foreach (c : str)
{
char l = this.read_next()?;
if (l != c) return JsonParsingError.UNEXPECTED_CHARACTER!;
char l = read_next(context)!;
if (l != c) return JsonParsingError.UNEXPECTED_CHARACTER?;
}
}
fn void! JsonParser.parse_expected(JsonParser* this, JsonTokenType token) @local
fn void! parse_expected(JsonContext* context, JsonTokenType token) @local
{
if (this.advance()? != token) return JsonParsingError.UNEXPECTED_CHARACTER!;
if (advance(context)! != token) return JsonParsingError.UNEXPECTED_CHARACTER?;
}
fn JsonTokenType! JsonParser.lex_string(JsonParser *this)
fn JsonTokenType! lex_string(JsonContext* context)
{
this.last_string.clear();
while LOOP: (1)
context.last_string.clear();
while LOOP: (true)
{
char c = this.read_next()?;
char c = read_next(context)!;
switch (c)
{
case '\0':
return JsonParsingError.EOF!;
return JsonParsingError.EOF?;
case 1..31:
return JsonParsingError.UNEXPECTED_CHARACTER!;
return JsonParsingError.UNEXPECTED_CHARACTER?;
case '"':
break LOOP;
case '\\':
break;
default:
this.last_string.append(c);
context.last_string.append(c);
continue;
}
c = this.read_next()?;
c = read_next(context)!;
switch (c)
{
case '\0':
return JsonParsingError.EOF!;
return JsonParsingError.EOF?;
case 1..31:
return JsonParsingError.UNEXPECTED_CHARACTER!;
return JsonParsingError.UNEXPECTED_CHARACTER?;
case '"':
case '\\':
case '/':
@@ -339,14 +355,14 @@ fn JsonTokenType! JsonParser.lex_string(JsonParser *this)
uint val;
for (int i = 0; i < 4; i++)
{
c = this.read_next()?;
if (!c.is_xdigit()) return JsonParsingError.INVALID_ESCAPE_SEQUENCE!;
c = read_next(context)!;
if (!c.is_xdigit()) return JsonParsingError.INVALID_ESCAPE_SEQUENCE?;
val = val << 4 + (c > '9' ? (c | 32) - 'a' + 10 : c - '0');
}
this.last_string.append_char32(val);
context.last_string.append_char32(val);
continue;
default:
return JsonParsingError.INVALID_ESCAPE_SEQUENCE!;
return JsonParsingError.INVALID_ESCAPE_SEQUENCE?;
}
}
return STRING;

View File

@@ -8,46 +8,46 @@ const uint ADLER_CONST @private = 65521;
struct Adler32
{
uint a;
uint b;
uint a;
uint b;
}
fn void Adler32.init(Adler32 *this)
fn void Adler32.init(&self)
{
*this = { 1, 0 };
*self = { 1, 0 };
}
fn void Adler32.updatec(Adler32* this, char c)
fn void Adler32.updatec(&self, char c)
{
this.a = (this.a + c) % ADLER_CONST;
this.b = (this.b + this.a) % ADLER_CONST;
self.a = (self.a + c) % ADLER_CONST;
self.b = (self.b + self.a) % ADLER_CONST;
}
fn void Adler32.update(Adler32* this, char[] data)
fn void Adler32.update(&self, char[] data)
{
uint a = this.a;
uint b = this.b;
foreach (char x : data)
{
a = (a + x) % ADLER_CONST;
b = (b + a) % ADLER_CONST;
}
*this = { a, b };
uint a = self.a;
uint b = self.b;
foreach (char x : data)
{
a = (a + x) % ADLER_CONST;
b = (b + a) % ADLER_CONST;
}
*self = { a, b };
}
fn uint Adler32.final(Adler32* this)
fn uint Adler32.final(&self)
{
return (this.b << 16) | this.a;
return (self.b << 16) | self.a;
}
fn uint encode(char[] data)
{
uint a = 1;
uint b = 0;
foreach (char x : data)
{
a = (a + x) % ADLER_CONST;
b = (b + a) % ADLER_CONST;
}
return (b << 16) | a;
uint a = 1;
uint b = 0;
foreach (char x : data)
{
a = (a + x) % ADLER_CONST;
b = (b + a) % ADLER_CONST;
}
return (b << 16) | a;
}

View File

@@ -5,41 +5,41 @@ module std::hash::crc32;
struct Crc32
{
uint result;
uint result;
}
fn void Crc32.init(Crc32* this, uint seed = 0)
fn void Crc32.init(&self, uint seed = 0)
{
this.result = ~seed;
self.result = ~seed;
}
fn void Crc32.updatec(Crc32* this, char c)
fn void Crc32.updatec(&self, char c)
{
this.result = (this.result >> 8) ^ CRC32_TABLE[(this.result ^ c) & 0xFF];
self.result = (self.result >> 8) ^ CRC32_TABLE[(self.result ^ c) & 0xFF];
}
fn void Crc32.update(Crc32* this, char[] data)
fn void Crc32.update(&self, char[] data)
{
uint result = this.result;
foreach (char x : data)
{
result = (result >> 8) ^ CRC32_TABLE[(result ^ x) & 0xFF];
}
this.result = result;
uint result = self.result;
foreach (char x : data)
{
result = (result >> 8) ^ CRC32_TABLE[(result ^ x) & 0xFF];
}
self.result = result;
}
fn uint Crc32.final(Crc32* this)
fn uint Crc32.final(&self)
{
return ~this.result;
return ~self.result;
}
fn uint encode(char[] data)
{
uint result = ~(uint)(0);
foreach (char x : data)
{
result = (result >> 8) ^ CRC32_TABLE[(result ^ x) & 0xFF];
}
uint result = ~(uint)(0);
foreach (char x : data)
{
result = (result >> 8) ^ CRC32_TABLE[(result ^ x) & 0xFF];
}
return ~result;
}

View File

@@ -5,107 +5,107 @@ module std::hash::crc64;
struct Crc64
{
ulong result;
ulong result;
}
fn void Crc64.init(Crc64* this, uint seed = 0)
fn void Crc64.init(&self, uint seed = 0)
{
this.result = seed;
self.result = seed;
}
fn void Crc64.updatec(Crc64* this, char c)
fn void Crc64.updatec(&self, char c)
{
this.result = (this.result << 8) ^ CRC64_TABLE[(char)((this.result >> 56) ^ c)];
self.result = (self.result << 8) ^ CRC64_TABLE[(char)((self.result >> 56) ^ c)];
}
fn void Crc64.update(Crc64* this, char[] data)
fn void Crc64.update(&self, char[] data)
{
ulong result = this.result;
foreach (char x : data)
{
result = (result << 8) ^ CRC64_TABLE[(char)((result >> 56) ^ x)];
}
this.result = result;
ulong result = self.result;
foreach (char x : data)
{
result = (result << 8) ^ CRC64_TABLE[(char)((result >> 56) ^ x)];
}
self.result = result;
}
fn ulong Crc64.final(Crc64* this)
fn ulong Crc64.final(&self)
{
return this.result;
return self.result;
}
fn ulong encode(char[] data)
{
ulong result = (ulong)(0);
foreach (char x : data)
{
result = (result << 8) ^ CRC64_TABLE[(char)((result >> 56) ^ x)];
}
ulong result = (ulong)(0);
foreach (char x : data)
{
result = (result << 8) ^ CRC64_TABLE[(char)((result >> 56) ^ x)];
}
return result;
}
const ulong[256] CRC64_TABLE @private = {
0x0000000000000000, 0x42f0e1eba9ea3693, 0x85e1c3d753d46d26, 0xc711223cfa3e5bb5,
0x493366450e42ecdf, 0x0bc387aea7a8da4c, 0xccd2a5925d9681f9, 0x8e224479f47cb76a,
0x9266cc8a1c85d9be, 0xd0962d61b56fef2d, 0x17870f5d4f51b498, 0x5577eeb6e6bb820b,
0xdb55aacf12c73561, 0x99a54b24bb2d03f2, 0x5eb4691841135847, 0x1c4488f3e8f96ed4,
0x663d78ff90e185ef, 0x24cd9914390bb37c, 0xe3dcbb28c335e8c9, 0xa12c5ac36adfde5a,
0x2f0e1eba9ea36930, 0x6dfeff5137495fa3, 0xaaefdd6dcd770416, 0xe81f3c86649d3285,
0xf45bb4758c645c51, 0xb6ab559e258e6ac2, 0x71ba77a2dfb03177, 0x334a9649765a07e4,
0xbd68d2308226b08e, 0xff9833db2bcc861d, 0x388911e7d1f2dda8, 0x7a79f00c7818eb3b,
0xcc7af1ff21c30bde, 0x8e8a101488293d4d, 0x499b3228721766f8, 0x0b6bd3c3dbfd506b,
0x854997ba2f81e701, 0xc7b97651866bd192, 0x00a8546d7c558a27, 0x4258b586d5bfbcb4,
0x5e1c3d753d46d260, 0x1cecdc9e94ace4f3, 0xdbfdfea26e92bf46, 0x990d1f49c77889d5,
0x172f5b3033043ebf, 0x55dfbadb9aee082c, 0x92ce98e760d05399, 0xd03e790cc93a650a,
0xaa478900b1228e31, 0xe8b768eb18c8b8a2, 0x2fa64ad7e2f6e317, 0x6d56ab3c4b1cd584,
0xe374ef45bf6062ee, 0xa1840eae168a547d, 0x66952c92ecb40fc8, 0x2465cd79455e395b,
0x3821458aada7578f, 0x7ad1a461044d611c, 0xbdc0865dfe733aa9, 0xff3067b657990c3a,
0x711223cfa3e5bb50, 0x33e2c2240a0f8dc3, 0xf4f3e018f031d676, 0xb60301f359dbe0e5,
0xda050215ea6c212f, 0x98f5e3fe438617bc, 0x5fe4c1c2b9b84c09, 0x1d14202910527a9a,
0x93366450e42ecdf0, 0xd1c685bb4dc4fb63, 0x16d7a787b7faa0d6, 0x5427466c1e109645,
0x4863ce9ff6e9f891, 0x0a932f745f03ce02, 0xcd820d48a53d95b7, 0x8f72eca30cd7a324,
0x0150a8daf8ab144e, 0x43a04931514122dd, 0x84b16b0dab7f7968, 0xc6418ae602954ffb,
0xbc387aea7a8da4c0, 0xfec89b01d3679253, 0x39d9b93d2959c9e6, 0x7b2958d680b3ff75,
0xf50b1caf74cf481f, 0xb7fbfd44dd257e8c, 0x70eadf78271b2539, 0x321a3e938ef113aa,
0x2e5eb66066087d7e, 0x6cae578bcfe24bed, 0xabbf75b735dc1058, 0xe94f945c9c3626cb,
0x676dd025684a91a1, 0x259d31cec1a0a732, 0xe28c13f23b9efc87, 0xa07cf2199274ca14,
0x167ff3eacbaf2af1, 0x548f120162451c62, 0x939e303d987b47d7, 0xd16ed1d631917144,
0x5f4c95afc5edc62e, 0x1dbc74446c07f0bd, 0xdaad56789639ab08, 0x985db7933fd39d9b,
0x84193f60d72af34f, 0xc6e9de8b7ec0c5dc, 0x01f8fcb784fe9e69, 0x43081d5c2d14a8fa,
0xcd2a5925d9681f90, 0x8fdab8ce70822903, 0x48cb9af28abc72b6, 0x0a3b7b1923564425,
0x70428b155b4eaf1e, 0x32b26afef2a4998d, 0xf5a348c2089ac238, 0xb753a929a170f4ab,
0x3971ed50550c43c1, 0x7b810cbbfce67552, 0xbc902e8706d82ee7, 0xfe60cf6caf321874,
0xe224479f47cb76a0, 0xa0d4a674ee214033, 0x67c58448141f1b86, 0x253565a3bdf52d15,
0xab1721da49899a7f, 0xe9e7c031e063acec, 0x2ef6e20d1a5df759, 0x6c0603e6b3b7c1ca,
0xf6fae5c07d3274cd, 0xb40a042bd4d8425e, 0x731b26172ee619eb, 0x31ebc7fc870c2f78,
0xbfc9838573709812, 0xfd39626eda9aae81, 0x3a28405220a4f534, 0x78d8a1b9894ec3a7,
0x649c294a61b7ad73, 0x266cc8a1c85d9be0, 0xe17dea9d3263c055, 0xa38d0b769b89f6c6,
0x2daf4f0f6ff541ac, 0x6f5faee4c61f773f, 0xa84e8cd83c212c8a, 0xeabe6d3395cb1a19,
0x90c79d3fedd3f122, 0xd2377cd44439c7b1, 0x15265ee8be079c04, 0x57d6bf0317edaa97,
0xd9f4fb7ae3911dfd, 0x9b041a914a7b2b6e, 0x5c1538adb04570db, 0x1ee5d94619af4648,
0x02a151b5f156289c, 0x4051b05e58bc1e0f, 0x87409262a28245ba, 0xc5b073890b687329,
0x4b9237f0ff14c443, 0x0962d61b56fef2d0, 0xce73f427acc0a965, 0x8c8315cc052a9ff6,
0x3a80143f5cf17f13, 0x7870f5d4f51b4980, 0xbf61d7e80f251235, 0xfd913603a6cf24a6,
0x73b3727a52b393cc, 0x31439391fb59a55f, 0xf652b1ad0167feea, 0xb4a25046a88dc879,
0xa8e6d8b54074a6ad, 0xea16395ee99e903e, 0x2d071b6213a0cb8b, 0x6ff7fa89ba4afd18,
0xe1d5bef04e364a72, 0xa3255f1be7dc7ce1, 0x64347d271de22754, 0x26c49cccb40811c7,
0x5cbd6cc0cc10fafc, 0x1e4d8d2b65facc6f, 0xd95caf179fc497da, 0x9bac4efc362ea149,
0x158e0a85c2521623, 0x577eeb6e6bb820b0, 0x906fc95291867b05, 0xd29f28b9386c4d96,
0xcedba04ad0952342, 0x8c2b41a1797f15d1, 0x4b3a639d83414e64, 0x09ca82762aab78f7,
0x87e8c60fded7cf9d, 0xc51827e4773df90e, 0x020905d88d03a2bb, 0x40f9e43324e99428,
0x2cffe7d5975e55e2, 0x6e0f063e3eb46371, 0xa91e2402c48a38c4, 0xebeec5e96d600e57,
0x65cc8190991cb93d, 0x273c607b30f68fae, 0xe02d4247cac8d41b, 0xa2dda3ac6322e288,
0xbe992b5f8bdb8c5c, 0xfc69cab42231bacf, 0x3b78e888d80fe17a, 0x7988096371e5d7e9,
0xf7aa4d1a85996083, 0xb55aacf12c735610, 0x724b8ecdd64d0da5, 0x30bb6f267fa73b36,
0x4ac29f2a07bfd00d, 0x08327ec1ae55e69e, 0xcf235cfd546bbd2b, 0x8dd3bd16fd818bb8,
0x03f1f96f09fd3cd2, 0x41011884a0170a41, 0x86103ab85a2951f4, 0xc4e0db53f3c36767,
0xd8a453a01b3a09b3, 0x9a54b24bb2d03f20, 0x5d45907748ee6495, 0x1fb5719ce1045206,
0x919735e51578e56c, 0xd367d40ebc92d3ff, 0x1476f63246ac884a, 0x568617d9ef46bed9,
0xe085162ab69d5e3c, 0xa275f7c11f7768af, 0x6564d5fde549331a, 0x279434164ca30589,
0xa9b6706fb8dfb2e3, 0xeb46918411358470, 0x2c57b3b8eb0bdfc5, 0x6ea7525342e1e956,
0x72e3daa0aa188782, 0x30133b4b03f2b111, 0xf7021977f9cceaa4, 0xb5f2f89c5026dc37,
0x3bd0bce5a45a6b5d, 0x79205d0e0db05dce, 0xbe317f32f78e067b, 0xfcc19ed95e6430e8,
0x86b86ed5267cdbd3, 0xc4488f3e8f96ed40, 0x0359ad0275a8b6f5, 0x41a94ce9dc428066,
0xcf8b0890283e370c, 0x8d7be97b81d4019f, 0x4a6acb477bea5a2a, 0x089a2aacd2006cb9,
0x14dea25f3af9026d, 0x562e43b4931334fe, 0x913f6188692d6f4b, 0xd3cf8063c0c759d8,
0x5dedc41a34bbeeb2, 0x1f1d25f19d51d821, 0xd80c07cd676f8394, 0x9afce626ce85b507,
0x0000000000000000, 0x42f0e1eba9ea3693, 0x85e1c3d753d46d26, 0xc711223cfa3e5bb5,
0x493366450e42ecdf, 0x0bc387aea7a8da4c, 0xccd2a5925d9681f9, 0x8e224479f47cb76a,
0x9266cc8a1c85d9be, 0xd0962d61b56fef2d, 0x17870f5d4f51b498, 0x5577eeb6e6bb820b,
0xdb55aacf12c73561, 0x99a54b24bb2d03f2, 0x5eb4691841135847, 0x1c4488f3e8f96ed4,
0x663d78ff90e185ef, 0x24cd9914390bb37c, 0xe3dcbb28c335e8c9, 0xa12c5ac36adfde5a,
0x2f0e1eba9ea36930, 0x6dfeff5137495fa3, 0xaaefdd6dcd770416, 0xe81f3c86649d3285,
0xf45bb4758c645c51, 0xb6ab559e258e6ac2, 0x71ba77a2dfb03177, 0x334a9649765a07e4,
0xbd68d2308226b08e, 0xff9833db2bcc861d, 0x388911e7d1f2dda8, 0x7a79f00c7818eb3b,
0xcc7af1ff21c30bde, 0x8e8a101488293d4d, 0x499b3228721766f8, 0x0b6bd3c3dbfd506b,
0x854997ba2f81e701, 0xc7b97651866bd192, 0x00a8546d7c558a27, 0x4258b586d5bfbcb4,
0x5e1c3d753d46d260, 0x1cecdc9e94ace4f3, 0xdbfdfea26e92bf46, 0x990d1f49c77889d5,
0x172f5b3033043ebf, 0x55dfbadb9aee082c, 0x92ce98e760d05399, 0xd03e790cc93a650a,
0xaa478900b1228e31, 0xe8b768eb18c8b8a2, 0x2fa64ad7e2f6e317, 0x6d56ab3c4b1cd584,
0xe374ef45bf6062ee, 0xa1840eae168a547d, 0x66952c92ecb40fc8, 0x2465cd79455e395b,
0x3821458aada7578f, 0x7ad1a461044d611c, 0xbdc0865dfe733aa9, 0xff3067b657990c3a,
0x711223cfa3e5bb50, 0x33e2c2240a0f8dc3, 0xf4f3e018f031d676, 0xb60301f359dbe0e5,
0xda050215ea6c212f, 0x98f5e3fe438617bc, 0x5fe4c1c2b9b84c09, 0x1d14202910527a9a,
0x93366450e42ecdf0, 0xd1c685bb4dc4fb63, 0x16d7a787b7faa0d6, 0x5427466c1e109645,
0x4863ce9ff6e9f891, 0x0a932f745f03ce02, 0xcd820d48a53d95b7, 0x8f72eca30cd7a324,
0x0150a8daf8ab144e, 0x43a04931514122dd, 0x84b16b0dab7f7968, 0xc6418ae602954ffb,
0xbc387aea7a8da4c0, 0xfec89b01d3679253, 0x39d9b93d2959c9e6, 0x7b2958d680b3ff75,
0xf50b1caf74cf481f, 0xb7fbfd44dd257e8c, 0x70eadf78271b2539, 0x321a3e938ef113aa,
0x2e5eb66066087d7e, 0x6cae578bcfe24bed, 0xabbf75b735dc1058, 0xe94f945c9c3626cb,
0x676dd025684a91a1, 0x259d31cec1a0a732, 0xe28c13f23b9efc87, 0xa07cf2199274ca14,
0x167ff3eacbaf2af1, 0x548f120162451c62, 0x939e303d987b47d7, 0xd16ed1d631917144,
0x5f4c95afc5edc62e, 0x1dbc74446c07f0bd, 0xdaad56789639ab08, 0x985db7933fd39d9b,
0x84193f60d72af34f, 0xc6e9de8b7ec0c5dc, 0x01f8fcb784fe9e69, 0x43081d5c2d14a8fa,
0xcd2a5925d9681f90, 0x8fdab8ce70822903, 0x48cb9af28abc72b6, 0x0a3b7b1923564425,
0x70428b155b4eaf1e, 0x32b26afef2a4998d, 0xf5a348c2089ac238, 0xb753a929a170f4ab,
0x3971ed50550c43c1, 0x7b810cbbfce67552, 0xbc902e8706d82ee7, 0xfe60cf6caf321874,
0xe224479f47cb76a0, 0xa0d4a674ee214033, 0x67c58448141f1b86, 0x253565a3bdf52d15,
0xab1721da49899a7f, 0xe9e7c031e063acec, 0x2ef6e20d1a5df759, 0x6c0603e6b3b7c1ca,
0xf6fae5c07d3274cd, 0xb40a042bd4d8425e, 0x731b26172ee619eb, 0x31ebc7fc870c2f78,
0xbfc9838573709812, 0xfd39626eda9aae81, 0x3a28405220a4f534, 0x78d8a1b9894ec3a7,
0x649c294a61b7ad73, 0x266cc8a1c85d9be0, 0xe17dea9d3263c055, 0xa38d0b769b89f6c6,
0x2daf4f0f6ff541ac, 0x6f5faee4c61f773f, 0xa84e8cd83c212c8a, 0xeabe6d3395cb1a19,
0x90c79d3fedd3f122, 0xd2377cd44439c7b1, 0x15265ee8be079c04, 0x57d6bf0317edaa97,
0xd9f4fb7ae3911dfd, 0x9b041a914a7b2b6e, 0x5c1538adb04570db, 0x1ee5d94619af4648,
0x02a151b5f156289c, 0x4051b05e58bc1e0f, 0x87409262a28245ba, 0xc5b073890b687329,
0x4b9237f0ff14c443, 0x0962d61b56fef2d0, 0xce73f427acc0a965, 0x8c8315cc052a9ff6,
0x3a80143f5cf17f13, 0x7870f5d4f51b4980, 0xbf61d7e80f251235, 0xfd913603a6cf24a6,
0x73b3727a52b393cc, 0x31439391fb59a55f, 0xf652b1ad0167feea, 0xb4a25046a88dc879,
0xa8e6d8b54074a6ad, 0xea16395ee99e903e, 0x2d071b6213a0cb8b, 0x6ff7fa89ba4afd18,
0xe1d5bef04e364a72, 0xa3255f1be7dc7ce1, 0x64347d271de22754, 0x26c49cccb40811c7,
0x5cbd6cc0cc10fafc, 0x1e4d8d2b65facc6f, 0xd95caf179fc497da, 0x9bac4efc362ea149,
0x158e0a85c2521623, 0x577eeb6e6bb820b0, 0x906fc95291867b05, 0xd29f28b9386c4d96,
0xcedba04ad0952342, 0x8c2b41a1797f15d1, 0x4b3a639d83414e64, 0x09ca82762aab78f7,
0x87e8c60fded7cf9d, 0xc51827e4773df90e, 0x020905d88d03a2bb, 0x40f9e43324e99428,
0x2cffe7d5975e55e2, 0x6e0f063e3eb46371, 0xa91e2402c48a38c4, 0xebeec5e96d600e57,
0x65cc8190991cb93d, 0x273c607b30f68fae, 0xe02d4247cac8d41b, 0xa2dda3ac6322e288,
0xbe992b5f8bdb8c5c, 0xfc69cab42231bacf, 0x3b78e888d80fe17a, 0x7988096371e5d7e9,
0xf7aa4d1a85996083, 0xb55aacf12c735610, 0x724b8ecdd64d0da5, 0x30bb6f267fa73b36,
0x4ac29f2a07bfd00d, 0x08327ec1ae55e69e, 0xcf235cfd546bbd2b, 0x8dd3bd16fd818bb8,
0x03f1f96f09fd3cd2, 0x41011884a0170a41, 0x86103ab85a2951f4, 0xc4e0db53f3c36767,
0xd8a453a01b3a09b3, 0x9a54b24bb2d03f20, 0x5d45907748ee6495, 0x1fb5719ce1045206,
0x919735e51578e56c, 0xd367d40ebc92d3ff, 0x1476f63246ac884a, 0x568617d9ef46bed9,
0xe085162ab69d5e3c, 0xa275f7c11f7768af, 0x6564d5fde549331a, 0x279434164ca30589,
0xa9b6706fb8dfb2e3, 0xeb46918411358470, 0x2c57b3b8eb0bdfc5, 0x6ea7525342e1e956,
0x72e3daa0aa188782, 0x30133b4b03f2b111, 0xf7021977f9cceaa4, 0xb5f2f89c5026dc37,
0x3bd0bce5a45a6b5d, 0x79205d0e0db05dce, 0xbe317f32f78e067b, 0xfcc19ed95e6430e8,
0x86b86ed5267cdbd3, 0xc4488f3e8f96ed40, 0x0359ad0275a8b6f5, 0x41a94ce9dc428066,
0xcf8b0890283e370c, 0x8d7be97b81d4019f, 0x4a6acb477bea5a2a, 0x089a2aacd2006cb9,
0x14dea25f3af9026d, 0x562e43b4931334fe, 0x913f6188692d6f4b, 0xd3cf8063c0c759d8,
0x5dedc41a34bbeeb2, 0x1f1d25f19d51d821, 0xd80c07cd676f8394, 0x9afce626ce85b507,
};

View File

@@ -3,39 +3,39 @@
// a copy of which can be found in the LICENSE_STDLIB file.
module std::hash::fnv32a;
typedef Fnv32a = distinct uint;
distinct Fnv32a = uint;
const FNV32A_START @private = 0x811c9dc5;
const FNV32A_MUL @private = 0x01000193;
macro void @update(uint &h, char x) @private => h = (h * FNV32A_MUL) ^ x;
macro void @update(uint* &h, char x) @private => *h = (*h * FNV32A_MUL) ^ x;
fn void Fnv32a.init(Fnv32a* this)
fn void Fnv32a.init(&self)
{
*this = FNV32A_START;
*self = FNV32A_START;
}
fn void Fnv32a.update(Fnv32a* this, char[] data)
fn void Fnv32a.update(&self, char[] data)
{
uint h = (uint)*this;
uint h = (uint)*self;
foreach (char x : data)
{
@update(h, x);
}
*this = (Fnv32a)h;
{
@update(h, x);
}
*self = (Fnv32a)h;
}
macro void Fnv32a.update_char(Fnv32a* this, char c)
macro void Fnv32a.update_char(&self, char c)
{
@update(*this, x);
@update(*self, x);
}
fn uint encode(char[] data)
{
uint h = FNV32A_START;
foreach (char x : data)
{
{
@update(h, x);
}
return h;
}
return h;
}

41
lib/std/hash/fnv64a.c3 Normal file
View File

@@ -0,0 +1,41 @@
// Copyright (c) 2021 Christoffer Lerno. All rights reserved.
// Use of this source code is governed by the MIT license
// a copy of which can be found in the LICENSE_STDLIB file.
module std::hash::fnv64a;
distinct Fnv64a = ulong;
const FNV64A_START @private = 0xcbf29ce484222325;
const FNV64A_MUL @private = 0x00000100000001b3;
macro void @update(ulong* &h, char x) @private => *h = (*h * FNV64A_MUL) ^ x;
fn void Fnv64a.init(&self)
{
*self = FNV64A_START;
}
fn void Fnv64a.update(&self, char[] data)
{
ulong h = (ulong)*self;
foreach (char x : data)
{
@update(h, x);
}
*self = (Fnv64a)h;
}
macro void Fnv64a.update_char(&self, char c)
{
@update(*self, x);
}
fn ulong encode(char[] data)
{
ulong h = FNV64A_START;
foreach (char x : data)
{
@update(h, x);
}
return h;
}

View File

@@ -14,70 +14,69 @@ struct Sha1
char[64] buffer;
}
fn void Sha1.init(Sha1* this)
fn void Sha1.init(&self)
{
// SHA1 initialization constants
*this = {
.state = {
0x67452301,
0xEFCDAB89,
0x98BADCFE,
0x10325476,
0xC3D2E1F0
}
// SHA1 initialization constants
*self = {
.state = {
0x67452301,
0xEFCDAB89,
0x98BADCFE,
0x10325476,
0xC3D2E1F0
}
};
}
/**
* @param [&inout] this
* @param [in] data
* @require data.len <= uint.max
**/
fn void Sha1.update(Sha1* this, char[] data)
fn void Sha1.update(&self, char[] data)
{
uint j = this.count[0];
uint j = self.count[0];
uint len = data.len;
if ((this.count[0] += len << 3) < j) this.count[1]++;
this.count[1] += len >> 29;
if ((self.count[0] += len << 3) < j) self.count[1]++;
self.count[1] += len >> 29;
j = (j >> 3) & 63;
uint i;
if (j + len > 63)
{
i = 64 - j;
this.buffer[j..] = data[:i];
sha1_transform(&this.state, &this.buffer);
self.buffer[j..] = data[:i];
sha1_transform(&self.state, &self.buffer);
for (; i + 63 < len; i += 64)
{
sha1_transform(&this.state, &data[i]);
sha1_transform(&self.state, &data[i]);
}
j = 0;
}
this.buffer[j:len - i] = data[i..];
self.buffer[j:len - i] = data[i..];
}
fn char[20] Sha1.final(Sha1* this)
fn char[20] Sha1.final(&self)
{
char[8] finalcount;
for (uint i = 0; i < 8; i++)
{
finalcount[i] = (char)((this.count[(i >= 4 ? 0 : 1)] >> ((3 - (i & 3)) * 8)) & 0xFF);
finalcount[i] = (char)((self.count[(i >= 4 ? 0 : 1)] >> ((3 - (i & 3)) * 8)) & 0xFF);
}
this.update(char[] { 0o200 });
while ((this.count[0] & 504) != 448)
self.update(char[] { 0o200 });
while ((self.count[0] & 504) != 448)
{
this.update(char[] { 0 });
self.update(char[] { 0 });
}
this.update(&finalcount);
self.update(&finalcount);
char[20] digest;
for (uint i = 0; i < 20; i++)
{
digest[i] = (char)((this.state[i >> 2] >> ((3 - (i & 3)) * 8)) & 0xFF);
digest[i] = (char)((self.state[i >> 2] >> ((3 - (i & 3)) * 8)) & 0xFF);
}
// Clear mem
mem::clear(this, Sha1.sizeof);
mem::clear(self, Sha1.sizeof);
finalcount = {};
return digest;
}
@@ -96,42 +95,47 @@ macro @blk(&block, i) @local
macro @blk0(&block, i) @local
{
$if (env::BIG_ENDIAN)
return block.l[i];
$else
return block.l[i] = (block.l[i].rotl(24) & 0xFF00FF00)
| (block.l[i].rotl(8) & 0x00FF00FF);
$endif
$if env::BIG_ENDIAN:
return block.l[i];
$else
return block.l[i] = (block.l[i].rotl(24) & 0xFF00FF00)
| (block.l[i].rotl(8) & 0x00FF00FF);
$endif
}
macro @r0(&block, v, &w, x, y, &z, i) @local
macro @r0(&block, v, &wref, x, y, &z, i) @local
{
z += ((w & (x ^ y)) ^ y) + @blk0(block, i) + 0x5A827999 + v.rotl(5);
w = w.rotl(30);
var w = *wref;
*z += ((w & (x ^ y)) ^ y) + @blk0(*block, i) + 0x5A827999 + v.rotl(5);
*wref = w.rotl(30);
}
macro @r1(&block, v, &w, x, y, &z, i) @local
macro @r1(&block, v, &wref, x, y, &z, i) @local
{
z += ((w & (x ^ y)) ^ y) + @blk(block, i) + 0x5A827999 + v.rotl(5);
w = w.rotl(30);
var w = *wref;
*z += ((w & (x ^ y)) ^ y) + @blk(*block, i) + 0x5A827999 + v.rotl(5);
*wref = w.rotl(30);
}
macro @r2(&block, v, &w, x, y, &z, i) @local
macro @r2(&block, v, &wref, x, y, &z, i) @local
{
z += (w ^ x ^ y) + @blk(block, i) + 0x6ED9EBA1 + v.rotl(5);
w = w.rotl(30);
var w = *wref;
*z += (w ^ x ^ y) + @blk(*block, i) + 0x6ED9EBA1 + v.rotl(5);
*wref = w.rotl(30);
}
macro @r3(&block, v, &w, x, y, &z, i) @local
macro @r3(&block, v, &wref, x, y, &z, i) @local
{
z += (((w | x) &y) | (w & x)) + @blk(block, i) + 0x8F1BBCDC + v.rotl(5);
w = w.rotl(30);
var w = *wref;
*z += (((w | x) & y) | (w & x)) + @blk(*block, i) + 0x8F1BBCDC + v.rotl(5);
*wref = w.rotl(30);
}
macro @r4(&block, v, &w, x, y, &z, i) @local
macro @r4(&block, v, &wref, x, y, &z, i) @local
{
z += (w ^ x ^ y) + @blk(block, i) + 0xCA62C1D6 + v.rotl(5);
w = w.rotl(30);
var w = *wref;
*z += (w ^ x ^ y) + @blk(*block, i) + 0xCA62C1D6 + v.rotl(5);
*wref = w.rotl(30);
}
/**

85
lib/std/io/bits.c3 Normal file
View File

@@ -0,0 +1,85 @@
module std::io;
struct BitReader
{
InStream* reader;
uint bits;
uint len;
}
fn void BitReader.init(&self, InStream* byte_reader)
{
*self = { .reader = byte_reader };
}
fn void BitReader.clear(&self) @inline
{
self.len = 0;
}
/**
* @require nbits <= 8
* @require self.len + nbits <= uint.sizeof * 8
**/
fn char! BitReader.read_bits(&self, uint nbits)
{
uint bits = self.bits;
if (self.len < nbits)
{
// New bits are pushed right.
char c = self.reader.read_byte()!;
bits <<= 8;
bits |= c;
self.bits = bits;
self.len += 8;
}
self.len -= nbits;
uint mask = (1 << nbits) - 1;
return (char)((bits >> self.len) & mask);
}
struct BitWriter
{
OutStream* writer;
uint bits;
uint len;
}
fn void BitWriter.init(&self, OutStream* byte_writer)
{
*self = { .writer = byte_writer };
}
fn void! BitWriter.flush(&self)
{
if (self.len == 0) return;
uint bits = self.bits << (32 - self.len);
uint n = (self.len + 7) / 8;
char[4] buffer;
bitorder::write(bits, &buffer, UIntBE);
io::write_all(self.writer, buffer[:n])!;
self.len = 0;
}
/**
* @require nbits <= 8
**/
fn void! BitWriter.write_bits(&self, uint bits, uint nbits)
{
if (nbits == 0) return;
uint n = self.len + nbits;
uint to_write = n / 8;
uint left = n % 8;
if (to_write > 0)
{
ulong lbits;
if (self.len > 0) lbits = (ulong)self.bits << (64 - self.len);
lbits |= (ulong)(bits >> left) << (64 - (n - left));
char[8] buffer;
bitorder::write(lbits, &buffer, ULongBE);
io::write_all(self.writer, buffer[:to_write])!;
}
self.bits <<= left;
self.bits |= bits & ((1 << left) - 1);
self.len = left;
}

View File

@@ -1,3 +0,0 @@
module std::io::dir;
import std::io::os;

191
lib/std/io/file.c3 Normal file
View File

@@ -0,0 +1,191 @@
module std::io;
import libc;
struct File (InStream, OutStream)
{
CFile file;
}
module std::io::file;
import libc;
fn File! open(String filename, String mode)
{
return from_handle(os::native_fopen(filename, mode));
}
fn File! open_path(Path path, String mode)
{
return from_handle(os::native_fopen(path.str_view(), mode));
}
fn File from_handle(CFile file)
{
return { .file = file };
}
fn bool is_file(String path)
{
return os::native_is_file(path);
}
fn usz! get_size(String path)
{
return os::native_file_size(path);
}
fn void! delete(String filename) => os::native_remove(filename) @inline;
/**
* @require self.file != null
**/
fn void! File.reopen(&self, String filename, String mode)
{
self.file = os::native_freopen(self.file, filename, mode)!;
}
/**
* @require self.file != null
**/
fn usz! File.seek(&self, isz offset, Seek seek_mode = Seek.SET) @dynamic
{
os::native_fseek(self.file, offset, seek_mode)!;
return os::native_ftell(self.file);
}
/*
Implement later
/**
* @require self.file == null
**/
fn void! File.memopen(File* file, char[] data, String mode)
{
@pool()
{
file.file = libc::memopen(data.ptr, data.len, mode.zstr_tcopy(), file.file);
// TODO errors
};
}
*/
/**
* @require self.file != null
*/
fn void! File.write_byte(&self, char c) @dynamic
{
if (!libc::fputc(c, self.file)) return IoError.EOF?;
}
/**
* @param [&inout] self
*/
fn void! File.close(&self) @inline @dynamic
{
if (self.file && libc::fclose(self.file))
{
switch (libc::errno())
{
case errno::ECONNRESET:
case errno::EBADF: return IoError.FILE_NOT_VALID?;
case errno::EINTR: return IoError.INTERRUPTED?;
case errno::EDQUOT:
case errno::EFAULT:
case errno::EAGAIN:
case errno::EFBIG:
case errno::ENETDOWN:
case errno::ENETUNREACH:
case errno::ENOSPC:
case errno::EIO: return IoError.INCOMPLETE_WRITE?;
default: return IoError.UNKNOWN_ERROR?;
}
}
self.file = null;
}
/**
* @require self.file
*/
fn bool File.eof(&self) @inline
{
return libc::feof(self.file) != 0;
}
/**
* @param [in] buffer
*/
fn usz! File.read(&self, char[] buffer) @dynamic
{
return os::native_fread(self.file, buffer);
}
/**
* @param [out] buffer
* @require self.file `File must be initialized`
*/
fn usz! File.write(&self, char[] buffer) @dynamic
{
return os::native_fwrite(self.file, buffer);
}
fn char! File.read_byte(&self) @dynamic
{
int c = libc::fgetc(self.file);
if (c == -1) return IoError.EOF?;
return (char)c;
}
/**
* Load up to buffer.len characters. Returns IoError.OVERFLOW if the file is longer
* than the buffer.
*
* @param filename "The path to the file to read"
* @param [in] buffer "The buffer to read to"
**/
fn char[]! load_buffer(String filename, char[] buffer)
{
File file = open(filename, "rb")!;
defer (void)file.close();
usz len = file.seek(0, END)!;
if (len > buffer.len) return IoError.OVERFLOW?;
file.seek(0, SET)!;
usz read = 0;
while (read < len)
{
read += file.read(buffer[read:len - read])!;
}
return buffer[:len];
}
fn char[]! load_new(String filename, Allocator* allocator = mem::heap())
{
File file = open(filename, "rb")!;
defer (void)file.close();
usz len = file.seek(0, END)!;
file.seek(0, SET)!;
char* data = allocator.alloc_checked(len)!;
defer catch allocator.free(data);
usz read = 0;
while (read < len)
{
read += file.read(data[read:len - read])!;
}
return data[:len];
}
fn char[]! load_temp(String filename)
{
return load_new(filename, mem::temp());
}
/**
* @require self.file `File must be initialized`
*/
fn void! File.flush(&self) @dynamic
{
libc::fflush(self.file);
}

461
lib/std/io/formatter.c3 Normal file
View File

@@ -0,0 +1,461 @@
module std::io;
import std::collections::map;
import libc;
const int PRINTF_NTOA_BUFFER_SIZE = 256;
interface Printable
{
fn String to_new_string(Allocator *allocator) @optional;
fn usz! to_format(Formatter* formatter) @optional;
}
fault PrintFault
{
BUFFER_EXCEEDED,
INTERNAL_BUFFER_EXCEEDED,
INVALID_FORMAT_STRING,
MISSING_ARG,
INVALID_ARGUMENT_TYPE,
}
fault FormattingFault
{
UNTERMINATED_FORMAT,
MISSING_ARG,
INVALID_WIDTH_ARG,
INVALID_FORMAT_TYPE,
}
def OutputFn = fn void!(void* buffer, char c);
def FloatType = double;
fn usz! Formatter.printf(&self, String format, args...)
{
return self.vprintf(format, args) @inline;
}
struct Formatter
{
void *data;
OutputFn out_fn;
struct
{
PrintFlags flags;
uint width;
uint prec;
usz idx;
}
}
bitstruct PrintFlags : uint
{
bool zeropad;
bool left;
bool plus;
bool space;
bool hash;
bool uppercase;
bool precision;
}
fn void Formatter.init(&self, OutputFn out_fn, void* data = null)
{
*self = { .data = data, .out_fn = out_fn};
}
fn usz! Formatter.out(&self, char c) @private
{
self.out_fn(self.data, c)!;
return 1;
}
fn usz! Formatter.print_with_function(&self, Printable* arg)
{
if (&arg.to_format)
{
PrintFlags old = self.flags;
uint old_width = self.width;
uint old_prec = self.prec;
defer
{
self.flags = old;
self.width = old_width;
self.prec = old_prec;
}
return arg.to_format(self);
}
if (&arg.to_new_string)
{
PrintFlags old = self.flags;
uint old_width = self.width;
uint old_prec = self.prec;
defer
{
self.flags = old;
self.width = old_width;
self.prec = old_prec;
}
@pool()
{
return self.out_substr(arg.to_new_string(mem::temp()));
};
}
return SearchResult.MISSING?;
}
fn usz! Formatter.out_str(&self, any* arg) @private
{
switch (arg.type.kindof)
{
case TYPEID:
return self.out_substr("typeid");
case VOID:
return self.out_substr("void");
case ANYFAULT:
case FAULT:
return self.out_substr((*(anyfault*)arg.ptr).nameof);
case ANY:
return self.out_str(*(any**)arg);
case OPTIONAL:
unreachable();
case SIGNED_INT:
case UNSIGNED_INT:
PrintFlags flags = self.flags;
uint width = self.width;
defer
{
self.flags = flags;
self.width = width;
}
self.flags = {};
self.width = 0;
return self.ntoa_any(arg, 10);
case FLOAT:
PrintFlags flags = self.flags;
uint width = self.width;
defer
{
self.flags = flags;
self.width = width;
}
self.flags = {};
self.width = 0;
return self.ftoa(float_from_any(arg)!!);
case BOOL:
return self.out_substr(*(bool*)arg.ptr ? "true" : "false");
default:
}
usz! n = self.print_with_function((Printable*)arg);
if (catch err = n)
{
case SearchResult.MISSING:
break;
default:
return err?;
}
else
{
return n;
}
switch (arg.type.kindof)
{
case ENUM:
usz i = types::any_to_int(arg, usz)!!;
assert(i < arg.type.names.len, "Illegal enum value found, numerical value was %d.", i);
return self.out_substr(arg.type.names[i]);
case STRUCT:
return self.out_substr("<struct>");
case UNION:
return self.out_substr("<union>");
case BITSTRUCT:
return self.out_substr("<bitstruct>");
case FUNC:
return self.out_substr("<function>");
case DISTINCT:
if (arg.type == ZString.typeid)
{
return self.out_substr(((ZString*)arg).str_view());
}
if (arg.type == DString.typeid)
{
return self.out_substr(((DString*)arg).str_view());
}
return self.out_str(arg.as_inner());
case POINTER:
PrintFlags flags = self.flags;
uint width = self.width;
defer
{
self.flags = flags;
self.width = width;
}
self.flags = {};
self.width = 0;
return self.ntoa_any(arg, 16);
case ARRAY:
// this is SomeType[*] so grab the "SomeType"
PrintFlags flags = self.flags;
uint width = self.width;
defer
{
self.flags = flags;
self.width = width;
}
self.flags = {};
self.width = 0;
typeid inner = arg.type.inner;
usz size = inner.sizeof;
usz alen = arg.type.len;
// Pretend this is a String
void* ptr = (void*)arg.ptr;
usz len = self.out('[')!;
for (usz i = 0; i < alen; i++)
{
if (i != 0) len += self.out_substr(", ")!;
len += self.out_str(any_make(ptr, inner))!;
ptr += size;
}
len += self.out(']')!;
return len;
case VECTOR:
PrintFlags flags = self.flags;
uint width = self.width;
defer
{
self.flags = flags;
self.width = width;
}
self.flags = {};
self.width = 0;
// this is SomeType[*] so grab the "SomeType"
typeid inner = arg.type.inner;
usz size = inner.sizeof;
usz vlen = arg.type.len;
// Pretend this is a String
void* ptr = (void*)arg.ptr;
usz len = self.out_substr("[<")!;
for (usz i = 0; i < vlen; i++)
{
if (i != 0) len += self.out_substr(", ")!;
len += self.out_str(any_make(ptr, inner))!;
ptr += size;
}
len += self.out_substr(">]")!;
return len;
case SUBARRAY:
// this is SomeType[] so grab the "SomeType"
typeid inner = arg.type.inner;
if (inner == char.typeid)
{
return self.out_substr(*(String*)arg);
}
if (inner == void.typeid) inner = char.typeid;
PrintFlags flags = self.flags;
uint width = self.width;
defer
{
self.flags = flags;
self.width = width;
}
self.flags = {};
self.width = 0;
usz size = inner.sizeof;
// Pretend this is a String
String* temp = (void*)arg.ptr;
void* ptr = (void*)temp.ptr;
usz slen = temp.len;
usz len = self.out('[')!;
for (usz i = 0; i < slen; i++)
{
if (i != 0) len += self.out_substr(", ")!;
len += self.out_str(any_make(ptr, inner))!;
ptr += size;
}
len += self.out(']')!;
return len;
default:
}
return self.out_substr("Invalid type");
}
fn void! out_null_fn(void* data @unused, char c @unused) @private
{
}
fn usz! Formatter.vprintf(&self, String format, any*[] anys)
{
if (!self.out_fn)
{
// use null output function
self.out_fn = &out_null_fn;
}
usz total_len;
usz format_len = format.len;
usz variant_index = 0;
for (usz i = 0; i < format_len; i++)
{
// format specifier? %[flags][width][.precision][length]
char c = format[i];
if (c != '%')
{
// no
total_len += self.out(c)!;
continue;
}
i++;
if (i >= format_len) return PrintFault.INVALID_FORMAT_STRING?;
c = format[i];
if (c == '%')
{
total_len += self.out(c)!;
continue;
}
// evaluate flags
self.flags = {};
while FLAG_EVAL: (true)
{
switch (c)
{
case '0': self.flags.zeropad = true;
case '-': self.flags.left = true;
case '+': self.flags.plus = true;
case ' ': self.flags.space = true;
case '#': self.flags.hash = true;
default: break FLAG_EVAL;
}
if (++i >= format_len) return PrintFault.INVALID_FORMAT_STRING?;
c = format[i];
}
// evaluate width field
int w = printf_parse_format_field(anys.ptr, anys.len, &variant_index, format.ptr, format.len, &i)!;
c = format[i];
if (w < 0)
{
self.flags.left = true;
w = -w;
}
self.width = w;
// evaluate precision field
self.prec = 0;
if (c == '.')
{
self.flags.precision = true;
if (++i >= format_len) return PrintFault.INVALID_FORMAT_STRING?;
int prec = printf_parse_format_field(anys.ptr, anys.len, &variant_index, format.ptr, format.len, &i)!;
self.prec = prec < 0 ? 0 : prec;
c = format[i];
}
// evaluate specifier
uint base = 0;
if (variant_index >= anys.len) return PrintFault.MISSING_ARG?;
any* current = anys[variant_index++];
switch (c)
{
case 'd':
base = 10;
self.flags.hash = false;
case 'X' :
self.flags.uppercase = true;
nextcase;
case 'x' :
base = 16;
case 'O':
self.flags.uppercase = true;
nextcase;
case 'o' :
base = 8;
case 'B':
self.flags.uppercase = true;
nextcase;
case 'b' :
base = 2;
case 'A':
self.flags.uppercase = true;
nextcase;
case 'a':
total_len += self.atoa(float_from_any(current)!!)!;
continue;
case 'F' :
self.flags.uppercase = true;
nextcase;
case 'f':
total_len += self.ftoa(float_from_any(current)!!)!;
continue;
case 'E':
self.flags.uppercase = true;
nextcase;
case 'e':
total_len += self.etoa(float_from_any(current)!!)!;
continue;
case 'G':
self.flags.uppercase = true;
nextcase;
case 'g':
total_len += self.gtoa(float_from_any(current)!!)!;
continue;
case 'c':
total_len += self.out_char(current)!;
continue;
case 's':
if (self.flags.left)
{
usz len = self.out_str(current)!;
total_len += len;
total_len += self.pad(' ', self.width, len)!;
continue;
}
OutputFn out_fn = self.out_fn;
self.out_fn = (OutputFn)&out_null_fn;
usz len = self.out_str(current)!;
self.out_fn = out_fn;
total_len += self.pad(' ', self.width, len)!;
total_len += self.out_str(current)!;
continue;
case 'p':
self.flags.zeropad = true;
self.flags.hash = true;
base = 16;
default:
return PrintFault.INVALID_FORMAT_STRING?;
}
if (base != 10)
{
self.flags.plus = false;
self.flags.space = false;
}
// ignore '0' flag when precision is given
if (self.flags.precision) self.flags.zeropad = false;
bool is_neg;
uint128 v = int_from_any(current, &is_neg)!!;
total_len += self.ntoa(v, is_neg, base)!;
}
// termination
// out((char)0, buffer, idx < maxlen ? idx : maxlen - 1U, maxlen);
// return written chars without terminating \0
return total_len;
}
fn usz! Formatter.print(&self, String str)
{
if (!self.out_fn)
{
// use null output function
self.out_fn = &out_null_fn;
}
foreach (c : str) self.out(c)!;
return self.idx;
}

View File

@@ -3,30 +3,25 @@ module std::io;
const char[16] XDIGITS_H = "0123456789ABCDEF";
const char[16] XDIGITS_L = "0123456789abcdef";
fn void! Formatter.left_adjust(Formatter* this, usz len) @local
fn usz! Formatter.adjust(&self, usz len) @local
{
if (!this.flags.left) return;
for (usz l = len; l < this.width; l++) this.out(' ')?;
if (!self.flags.left) return 0;
return self.pad(' ', self.width, len);
}
fn void! Formatter.right_adjust(Formatter* this, usz len) @local
fn uint128! int_from_any(any* arg, bool *is_neg) @private
{
if (this.flags.left) return;
for (usz l = len; l < this.width; l++) this.out(' ')?;
}
fn uint128! int_from_variant(variant arg, bool *is_neg) @private
{
*is_neg = false;
if (arg.type.kindof == TypeKind.POINTER)
switch (arg.type.kindof)
{
return (uint128)(uptr)*(void**)arg.ptr;
case TypeKind.POINTER:
*is_neg = false;
return (uint128)(uptr)*(void**)arg.ptr;
case TypeKind.DISTINCT:
case TypeKind.ENUM:
return int_from_any(arg.as_inner(), is_neg);
default:
}
if (arg.type.kindof == TypeKind.DISTINCT)
{
return int_from_variant(variant { arg.ptr, arg.type.inner }, is_neg);
}
*is_neg = false;
switch (arg)
{
case bool:
@@ -44,8 +39,8 @@ fn uint128! int_from_variant(variant arg, bool *is_neg) @private
long val = *arg;
return (*is_neg = val < 0) ? (~(uint128)val) + 1 : (uint128)val;
case int128:
int128 val = *arg;
return (*is_neg = val < 0) ? (~(uint128)val) + 1 : (uint128)val;
int128 val = *arg;
return (*is_neg = val < 0) ? (~(uint128)val) + 1 : (uint128)val;
case char:
return *arg;
case ushort:
@@ -63,21 +58,21 @@ fn uint128! int_from_variant(variant arg, bool *is_neg) @private
double d = *arg;
return (uint128)((*is_neg = d < 0) ? -d : d);
default:
return PrintFault.INVALID_ARGUMENT_TYPE!;
return PrintFault.INVALID_ARGUMENT_TYPE?;
}
}
fn FloatType! float_from_variant(variant arg) @private
fn FloatType! float_from_any(any* arg) @private
{
$if (env::F128_SUPPORT)
$if env::F128_SUPPORT:
if (arg.type == float128.typeid) return (FloatType)*((float128*)arg.ptr);
$endif
$if (env::F16_SUPPORT)
$if env::F16_SUPPORT:
if (arg.type == float16.typeid) return *((float16*)arg.ptr);
$endif
if (arg.type.kindof == TypeKind.DISTINCT)
{
return float_from_variant(variant { arg.ptr, arg.type.inner });
return float_from_any(arg.as_inner());
}
switch (arg)
{
@@ -92,7 +87,7 @@ fn FloatType! float_from_variant(variant arg) @private
case long:
return *arg;
case int128:
return *arg;
return *arg;
case char:
return *arg;
case ushort:
@@ -102,13 +97,13 @@ fn FloatType! float_from_variant(variant arg) @private
case ulong:
return *arg;
case uint128:
return *arg;
return *arg;
case float:
return (FloatType)*arg;
case double:
return (FloatType)*arg;
default:
return PrintFault.INVALID_ARGUMENT_TYPE!;
return PrintFault.INVALID_ARGUMENT_TYPE?;
}
}
@@ -126,22 +121,21 @@ fn uint simple_atoi(char* buf, usz maxlen, usz* len_ptr) @inline @private
uint i = 0;
usz len = *len_ptr;
while (len < maxlen)
{
char c = buf[len];
if (c < '0' || c > '9') break;
i = i * 10 + c - '0';
len++;
}
*len_ptr = len;
return i;
{
char c = buf[len];
if (!c.is_digit()) break;
i = i * 10 + c - '0';
len++;
}
*len_ptr = len;
return i;
}
fn void! Formatter.out_substr(Formatter *this, String str) @private
fn usz! Formatter.out_substr(&self, String str) @private
{
usz l = conv::utf8_codepoints(str);
uint prec = this.prec;
if (this.flags.precision && l < prec) l = prec;
this.right_adjust(' ')?;
uint prec = self.prec;
if (self.flags.precision && l < prec) l = prec;
usz index = 0;
usz chars = str.len;
char* ptr = str.ptr;
@@ -149,16 +143,18 @@ fn void! Formatter.out_substr(Formatter *this, String str) @private
{
char c = ptr[index];
// Break if we have precision set and we ran out...
if (c & 0xC0 != 0x80 && this.flags.precision && !prec--) break;
this.out(c)?;
index++;
if (c & 0xC0 != 0x80 && self.flags.precision && !prec--) break;
self.out(c)!;
index++;
}
return this.left_adjust(l);
return index;
}
fn void! Formatter.pad(Formatter* this, char c, isz width, isz len) @inline
fn usz! Formatter.pad(&self, char c, isz width, isz len) @inline
{
for (isz i = len; i < width; i++) this.out(c)?;
isz delta = width - len;
for (isz i = 0; i < delta; i++) self.out(c)!;
return max(0, delta);
}
fn char* fmt_u(uint128 x, char* s)
@@ -168,9 +164,10 @@ fn char* fmt_u(uint128 x, char* s)
return s;
}
fn void! Formatter.out_chars(Formatter* this, char[] s)
fn usz! Formatter.out_chars(&self, char[] s)
{
foreach (c : s) this.out(c)?;
foreach (c : s) self.out(c)!;
return s.len;
}
enum FloatFormatting
@@ -181,35 +178,37 @@ enum FloatFormatting
HEX
}
fn void! Formatter.etoa(Formatter* this, double y) => this.floatformat(EXPONENTIAL, y);
fn void! Formatter.ftoa(Formatter* this, double y) => this.floatformat(FLOAT, y);
fn void! Formatter.gtoa(Formatter* this, double y) => this.floatformat(ADAPTIVE, y);
fn void! Formatter.atoa(Formatter* this, double y) => this.floatformat(HEX, y);
fn usz! Formatter.etoa(&self, double y) => self.floatformat(EXPONENTIAL, y);
fn usz! Formatter.ftoa(&self, double y) => self.floatformat(FLOAT, y);
fn usz! Formatter.gtoa(&self, double y) => self.floatformat(ADAPTIVE, y);
fn usz! Formatter.atoa(&self, double y) => self.floatformat(HEX, y);
fn void! Formatter.floatformat(Formatter* this, FloatFormatting formatting, double y) @private
fn usz! Formatter.floatformat(&self, FloatFormatting formatting, double y) @private
{
// This code is heavily based on musl's printf code
const BUF_SIZE = (math::DOUBLE_MANT_DIG + 28) / 29 + 1
+ (math::DOUBLE_MAX_EXP + math::DOUBLE_MANT_DIG + 28 + 8) / 9;
uint[BUF_SIZE] big;
bool is_neg = false;
if (math::signbit(y))
{
is_neg = true;
y = -y;
}
int pl = is_neg || this.flags.plus ? 1 : 0;
// Print inf/nan
bool is_neg = false;
if (math::signbit(y))
{
is_neg = true;
y = -y;
}
isz pl = is_neg || self.flags.plus ? 1 : 0;
// Print inf/nan
if (!math::is_finite(y))
{
usz len;
// Add padding
if (!this.flags.left) this.pad(' ', this.width, 3 + pl)?;
String s = this.flags.uppercase ? "INF" : "inf";
if (y != y) this.flags.uppercase ? "NAN" : "nan";
if (pl) this.out(is_neg ? '-' : '+')?;
this.out_chars(s)?;
if (this.flags.left) this.pad(' ', this.width, 3 + pl)?;
return;
if (!self.flags.left) len += self.pad(' ', self.width, 3 + pl)!;
String s = self.flags.uppercase ? "INF" : "inf";
if (y != y) s = self.flags.uppercase ? "NAN" : "nan";
len += s.len;
if (pl) len += self.out(is_neg ? '-' : '+')!;
len += self.out_chars(s)!;
if (self.flags.left) len += self.pad(' ', self.width, 3 + pl)!;
return len;
}
// Rescale
int e2;
@@ -220,7 +219,7 @@ fn void! Formatter.floatformat(Formatter* this, FloatFormatting formatting, doub
char* ebuf = 12 + (char*)&ebuf0;
char[9 + math::DOUBLE_MANT_DIG / 4] buf_array;
char* buf = &buf_array;
isz p = this.flags.precision ? this.prec : -1;
isz p = self.flags.precision ? self.prec : -1;
if (formatting == HEX)
{
double round = 8.0;
@@ -229,50 +228,51 @@ fn void! Formatter.floatformat(Formatter* this, FloatFormatting formatting, doub
if (p > 0 && p < math::DOUBLE_MANT_DIG / 4 - 1)
{
int re = math::DOUBLE_MANT_DIG / 4 - 1 - (int)p;
round *= 1 << (math::DOUBLE_MANT_DIG % 4);
while (re--) round *= 16;
if (is_neg)
{
y = -y;
round *= 1 << (math::DOUBLE_MANT_DIG % 4);
while (re--) round *= 16;
if (is_neg)
{
y = -y;
y -= round;
y += round;
y = -y;
}
else
{
y += round;
y -= round;
}
y += round;
y = -y;
}
else
{
y += round;
y -= round;
}
}
// Reverse print
char* estr = fmt_u(e2 < 0 ? (int128)-e2 : (int128)e2, ebuf);
if (estr == ebuf) *--estr = '0';
*--estr = (e2 < 0 ? '-' : '+');
*--estr = this.flags.uppercase ? 'P' : 'p';
*--estr = self.flags.uppercase ? 'P' : 'p';
char* s = buf;
char* xdigits = this.flags.uppercase ? &XDIGITS_H : &XDIGITS_L;
char* xdigits = self.flags.uppercase ? &XDIGITS_H : &XDIGITS_L;
do
{
int x = (int)y;
*s++ = xdigits[x];
y = 16 * (y - x);
if (s - buf == 1 && (y || p > 0 || this.flags.hash)) *s++ = '.';
if (s - buf == 1 && (y || p > 0 || self.flags.hash)) *s++ = '.';
} while (y);
isz outlen = s - buf;
isz explen = ebuf - estr;
if (p > int.max - 2 - explen - pl) return PrintFault.INTERNAL_BUFFER_EXCEEDED!;
if (p > int.max - 2 - explen - pl) return PrintFault.INTERNAL_BUFFER_EXCEEDED?;
usz len;
usz l = p && outlen - 2 < p
? p + 2 + explen
: outlen + explen;
if (!this.flags.left && !this.flags.zeropad) this.pad(' ', this.width, pl + l)?;
if (is_neg || this.flags.plus) this.out(is_neg ? '-' : '+')?;
this.out_chars(this.flags.uppercase ? "0X" : "0x")?;
if (this.flags.zeropad) this.pad('0', this.width, pl + l)?;
this.out_chars(buf[:outlen])?;
this.pad('0', l - outlen - explen, 0)?;
this.out_chars(estr[:explen])?;
if (this.flags.left) this.pad(' ', this.width, pl + l)?;
return;
if (!self.flags.left && !self.flags.zeropad) len += self.pad(' ', self.width, pl + l)!;
if (is_neg || self.flags.plus) len += self.out(is_neg ? '-' : '+')!;
len += self.out_chars(self.flags.uppercase ? "0X" : "0x")!;
if (self.flags.zeropad) len += self.pad('0', self.width, pl + l)!;
len += self.out_chars(buf[:outlen])!;
len += self.pad('0', l - outlen - explen, 0)!;
len += self.out_chars(estr[:explen])!;
if (self.flags.left) len += self.pad(' ', self.width, pl + l)!;
return len;
}
if (p < 0) p = 6;
if (y)
@@ -405,7 +405,7 @@ fn void! Formatter.floatformat(Formatter* this, FloatFormatting formatting, doub
formatting = EXPONENTIAL;
p--;
}
if (!this.flags.hash)
if (!self.flags.hash)
{
// Count trailing zeros in last place
if (z > a && z[-1])
@@ -427,12 +427,12 @@ fn void! Formatter.floatformat(Formatter* this, FloatFormatting formatting, doub
}
}
}
if (p > int.max - 1 - (isz)(p || this.flags.hash)) return PrintFault.INTERNAL_BUFFER_EXCEEDED!;
int l = (int)(1 + p + (isz)(p || this.flags.hash));
if (p > int.max - 1 - (isz)(p || self.flags.hash)) return PrintFault.INTERNAL_BUFFER_EXCEEDED?;
int l = (int)(1 + p + (isz)(p || self.flags.hash));
char* estr @noinit;
if (formatting == FLOAT)
{
if (e > int.max - l) return PrintFault.INTERNAL_BUFFER_EXCEEDED!;
if (e > int.max - l) return PrintFault.INTERNAL_BUFFER_EXCEEDED?;
if (e > 0) l += e;
}
else
@@ -440,14 +440,15 @@ fn void! Formatter.floatformat(Formatter* this, FloatFormatting formatting, doub
estr = fmt_u((uint128)(e < 0 ? -e : e), ebuf);
while (ebuf - estr < 2) (--estr)[0] = '0';
*--estr = (e < 0 ? '-' : '+');
*--estr = this.flags.uppercase ? 'E' : 'e';
if (ebuf - estr > (isz)int.max - l) return PrintFault.INTERNAL_BUFFER_EXCEEDED!;
*--estr = self.flags.uppercase ? 'E' : 'e';
if (ebuf - estr > (isz)int.max - l) return PrintFault.INTERNAL_BUFFER_EXCEEDED?;
l += (int)(ebuf - estr);
}
if (l > int.max - pl) return PrintFault.INTERNAL_BUFFER_EXCEEDED!;
if (!this.flags.left && !this.flags.zeropad) this.pad(' ', this.width, pl + l)?;
if (is_neg || this.flags.plus) this.out(is_neg ? '-' : '+')?;
if (this.flags.zeropad) this.pad('0', this.width, pl + l)?;
if (l > int.max - pl) return PrintFault.INTERNAL_BUFFER_EXCEEDED?;
usz len;
if (!self.flags.left && !self.flags.zeropad) len += self.pad(' ', self.width, pl + l)!;
if (is_neg || self.flags.plus) len += self.out(is_neg ? '-' : '+')!;
if (self.flags.zeropad) len += self.pad('0', self.width, pl + l)!;
if (formatting == FLOAT)
{
if (a > r) a = r;
@@ -462,106 +463,106 @@ fn void! Formatter.floatformat(Formatter* this, FloatFormatting formatting, doub
case s == buf + 9:
*--s = '0';
}
this.out_chars(s[:buf + 9 - s])?;
len += self.out_chars(s[:buf + 9 - s])!;
}
if (p || this.flags.hash) this.out('.')?;
if (p || self.flags.hash) len += self.out('.')!;
for (; d < z && p > 0; d++, p -= 9)
{
char* s = fmt_u(*d, buf + 9);
while (s > buf) *--s = '0';
this.out_chars(s[:math::min((isz)9, p)])?;
len += self.out_chars(s[:math::min((isz)9, p)])!;
}
this.pad('0', p + 9, 9)?;
len += self.pad('0', p + 9, 9)!;
}
else
{
if (z <= a) z = a + 1;
for (uint* d = a; d < z && p >= 0; d++)
{
for (uint* d = a; d < z && p >= 0; d++)
{
char* s = fmt_u(*d, buf + 9);
if (s == buf + 9) (--s)[0] = '0';
if (d != a)
{
while (s > buf) (--s)[0] = '0';
}
else
{
this.out(s++[0])?;
if (p > 0 || this.flags.hash) this.out('.')?;
}
this.out_chars(s[:math::min(buf + 9 - s, p)])?;
p -= buf + 9 - s;
}
this.pad('0', p + 18, 18)?;
this.out_chars(estr[:ebuf - estr])?;
if (d != a)
{
while (s > buf) (--s)[0] = '0';
}
else
{
len += self.out(s++[0])!;
if (p > 0 || self.flags.hash) len += self.out('.')!;
}
len += self.out_chars(s[:math::min(buf + 9 - s, p)])!;
p -= buf + 9 - s;
}
len += self.pad('0', p + 18, 18)!;
len += self.out_chars(estr[:ebuf - estr])!;
}
if (this.flags.left) this.pad(' ', this.width, pl + l)?;
if (self.flags.left) len += self.pad(' ', self.width, pl + l)!;
return;
return len;
}
fn void! Formatter.ntoa(Formatter* this, uint128 value, bool negative, uint base) @private
fn usz! Formatter.ntoa(&self, uint128 value, bool negative, uint base) @private
{
char[PRINTF_NTOA_BUFFER_SIZE] buf @noinit;
usz len = 0;
usz len;
// no hash for 0 values
if (!value) this.flags.hash = false;
if (!value) self.flags.hash = false;
// write if precision != 0 or value is != 0
if (!this.flags.precision || value)
if (!self.flags.precision || value)
{
char past_10 = (this.flags.uppercase ? 'A' : 'a') - 10;
char past_10 = (self.flags.uppercase ? 'A' : 'a') - 10;
do
{
if (len >= PRINTF_NTOA_BUFFER_SIZE) return PrintFault.INTERNAL_BUFFER_EXCEEDED!;
if (len >= PRINTF_NTOA_BUFFER_SIZE) return PrintFault.INTERNAL_BUFFER_EXCEEDED?;
char digit = (char)(value % base);
buf[len++] = digit + (digit < 10 ? '0' : past_10);
value /= base;
}
while (value);
}
return this.ntoa_format((String)buf[:PRINTF_NTOA_BUFFER_SIZE], len, negative, base);
return self.ntoa_format((String)buf[:PRINTF_NTOA_BUFFER_SIZE], len, negative, base);
}
fn void! Formatter.ntoa_format(Formatter* this, String buf, usz len, bool negative, uint base) @private
fn usz! Formatter.ntoa_format(&self, String buf, usz len, bool negative, uint base) @private
{
// pad leading zeros
if (!this.flags.left)
if (!self.flags.left)
{
if (this.width && this.flags.zeropad && (negative || this.flags.plus || this.flags.space)) this.width--;
while (len < this.prec)
if (self.width && self.flags.zeropad && (negative || self.flags.plus || self.flags.space)) self.width--;
while (len < self.prec)
{
if (len >= buf.len) return PrintFault.INTERNAL_BUFFER_EXCEEDED!;
if (len >= buf.len) return PrintFault.INTERNAL_BUFFER_EXCEEDED?;
buf[len++] = '0';
}
while (this.flags.zeropad && len < this.width)
while (self.flags.zeropad && len < self.width)
{
if (len >= buf.len) return PrintFault.INTERNAL_BUFFER_EXCEEDED!;
if (len >= buf.len) return PrintFault.INTERNAL_BUFFER_EXCEEDED?;
buf[len++] = '0';
}
}
// handle hash
if (this.flags.hash && base != 10)
if (self.flags.hash && base != 10)
{
if (!this.flags.precision && len && len == this.prec && len == this.width)
if (!self.flags.precision && len && len == self.prec && len == self.width)
{
len--;
if (len) len--;
}
if (base != 10)
{
if (len + 1 >= buf.len) return PrintFault.INTERNAL_BUFFER_EXCEEDED!;
if (len + 1 >= buf.len) return PrintFault.INTERNAL_BUFFER_EXCEEDED?;
switch (base)
{
case 16:
buf[len++] = this.flags.uppercase ? 'X' : 'x';
buf[len++] = self.flags.uppercase ? 'X' : 'x';
case 8:
buf[len++] = this.flags.uppercase ? 'O' : 'o';
buf[len++] = self.flags.uppercase ? 'O' : 'o';
case 2:
buf[len++] = this.flags.uppercase ? 'B' : 'b';
buf[len++] = self.flags.uppercase ? 'B' : 'b';
default:
unreachable();
}
@@ -572,96 +573,97 @@ fn void! Formatter.ntoa_format(Formatter* this, String buf, usz len, bool negati
switch (true)
{
case negative:
if (len >= buf.len) return PrintFault.INTERNAL_BUFFER_EXCEEDED!;
if (len >= buf.len) return PrintFault.INTERNAL_BUFFER_EXCEEDED?;
buf[len++] = '-';
case this.flags.plus:
if (len >= buf.len) return PrintFault.INTERNAL_BUFFER_EXCEEDED!;
case self.flags.plus:
if (len >= buf.len) return PrintFault.INTERNAL_BUFFER_EXCEEDED?;
buf[len++] = '+';
case this.flags.space:
if (len >= buf.len) return PrintFault.INTERNAL_BUFFER_EXCEEDED!;
case self.flags.space:
if (len >= buf.len) return PrintFault.INTERNAL_BUFFER_EXCEEDED?;
buf[len++] = ' ';
}
if (!len) return;
return this.out_reverse(buf[:len]);
if (len) self.out_reverse(buf[:len])!;
return len;
}
fn void! Formatter.ntoa_variant(Formatter* this, variant arg, uint base) @private
fn usz! Formatter.ntoa_any(&self, any* arg, uint base) @private
{
bool is_neg;
uint128 val = int_from_variant(arg, &is_neg)!!;
return this.ntoa(val, is_neg, base) @inline;
uint128 val = int_from_any(arg, &is_neg)!!;
return self.ntoa(val, is_neg, base) @inline;
}
fn void! Formatter.out_char(Formatter* this, variant arg) @private
fn usz! Formatter.out_char(&self, any* arg) @private
{
usz len = 1;
uint l = 1;
// pre padding
this.right_adjust(l)?;
// char output
Char32 c = types::variant_to_int(arg, uint) ?? 0xFFFD;
len += self.adjust(l)!;
// char output
Char32 c = types::any_to_int(arg, uint) ?? 0xFFFD;
switch (true)
{
case c < 0x7f:
this.out((char)c)?;
self.out((char)c)!;
case c < 0x7ff:
this.out((char)(0xC0 | c >> 6))?;
this.out((char)(0x80 | (c & 0x3F)))?;
case c < 0xffff:
this.out((char)(0xE0 | c >> 12))?;
this.out((char)(0x80 | (c >> 6 & 0x3F)))?;
this.out((char)(0x80 | (c & 0x3F)))?;
default:
this.out((char)(0xF0 | c >> 18))?;
this.out((char)(0x80 | (c >> 12 & 0x3F)))?;
this.out((char)(0x80 | (c >> 6 & 0x3F)))?;
this.out((char)(0x80 | (c & 0x3F)))?;
self.out((char)(0xC0 | c >> 6))!;
self.out((char)(0x80 | (c & 0x3F)))!;
case c < 0xffff:
self.out((char)(0xE0 | c >> 12))!;
self.out((char)(0x80 | (c >> 6 & 0x3F)))!;
self.out((char)(0x80 | (c & 0x3F)))!;
default:
self.out((char)(0xF0 | c >> 18))!;
self.out((char)(0x80 | (c >> 12 & 0x3F)))!;
self.out((char)(0x80 | (c >> 6 & 0x3F)))!;
self.out((char)(0x80 | (c & 0x3F)))!;
}
return this.left_adjust(l);
len += self.adjust(l)!;
return len;
}
fn void! Formatter.out_reverse(Formatter* this, char[] buf) @private
fn usz! Formatter.out_reverse(&self, char[] buf) @private
{
usz buffer_start_idx = this.idx;
usz n;
usz buffer_start_idx = self.idx;
usz len = buf.len;
// pad spaces up to given width
if (!this.flags.left && !this.flags.zeropad)
{
for (usz i = len; i < this.width; i++)
{
this.out(' ')?;
}
}
// reverse string
while (len) this.out(buf[--len])?;
// pad spaces up to given width
if (!self.flags.zeropad)
{
n += self.adjust(len)!;
}
// reverse string
while (len) n += self.out(buf[--len])!;
// append pad spaces up to given width
return this.left_adjust(this.idx - buffer_start_idx);
n += self.adjust(self.idx - buffer_start_idx)!;
return n;
}
fn void! printf_advance_format(usz format_len, usz *index_ptr) @inline @private
{
usz val = ++(*index_ptr);
if (val >= format_len) return FormattingFault.UNTERMINATED_FORMAT!;
if (val >= format_len) return FormattingFault.UNTERMINATED_FORMAT?;
}
fn variant! next_variant(variant* args_ptr, usz args_len, usz* arg_index_ptr) @inline @private
fn any*! next_any(any** args_ptr, usz args_len, usz* arg_index_ptr) @inline @private
{
if (*arg_index_ptr >= args_len) return FormattingFault.MISSING_ARG!;
if (*arg_index_ptr >= args_len) return FormattingFault.MISSING_ARG?;
return args_ptr[(*arg_index_ptr)++];
}
fn int! printf_parse_format_field(
variant* args_ptr, usz args_len, usz* args_index_ptr,
any** args_ptr, usz args_len, usz* args_index_ptr,
char* format_ptr, usz format_len, usz* index_ptr) @inline @private
{
char c = format_ptr[*index_ptr];
if (c >= '0' && c <= '9') return simple_atoi(format_ptr, format_len, index_ptr);
if (c.is_digit()) return simple_atoi(format_ptr, format_len, index_ptr);
if (c != '*') return 0;
printf_advance_format(format_len, index_ptr)?;
variant val = next_variant(args_ptr, args_len, args_index_ptr)?;
if (!val.type.kindof.is_int()) return FormattingFault.INVALID_WIDTH_ARG!;
uint! intval = types::variant_to_int(val, int);
return intval ?? FormattingFault.INVALID_WIDTH_ARG!;
printf_advance_format(format_len, index_ptr)!;
any* val = next_any(args_ptr, args_len, args_index_ptr)!;
if (!val.type.kindof.is_int()) return FormattingFault.INVALID_WIDTH_ARG?;
uint! intval = types::any_to_int(val, int);
return intval ?? FormattingFault.INVALID_WIDTH_ARG?;
}

View File

@@ -4,136 +4,287 @@
module std::io;
import libc;
struct File
{
CFile file;
}
enum Seek
{
SET,
CURSOR,
END
SET,
CURSOR,
END
}
fault IoError
{
FILE_NOT_FOUND,
FILE_NOT_VALID,
INVALID_POSITION,
OVERFLOW,
FILE_IS_PIPE,
FILE_EOF,
INCOMPLETE_WRITE,
NO_PERMISSION,
OUT_OF_SPACE,
INVALID_PUSHBACK,
EOF,
CANNOT_READ_DIR,
TOO_MANY_DESCRIPTORS,
FILE_IS_DIR,
READ_ONLY,
FILE_NOT_DIR,
SYMLINK_FAILED,
ALREADY_EXISTS,
NOT_SEEKABLE,
NAME_TOO_LONG,
WOULD_BLOCK,
INTERRUPTED,
GENERAL_ERROR,
UNKNOWN_ERROR,
UNSUPPORTED_OPERATION,
ALREADY_EXISTS,
BUSY,
CANNOT_READ_DIR,
DIR_NOT_EMPTY,
EOF,
FILE_CANNOT_DELETE,
FILE_IS_DIR,
FILE_IS_PIPE,
FILE_NOT_DIR,
FILE_NOT_FOUND,
FILE_NOT_VALID,
GENERAL_ERROR,
ILLEGAL_ARGUMENT,
INCOMPLETE_WRITE,
INTERRUPTED,
INVALID_POSITION,
INVALID_PUSHBACK,
NAME_TOO_LONG,
NOT_SEEKABLE,
NO_PERMISSION,
OUT_OF_SPACE,
OVERFLOW,
READ_ONLY,
SYMLINK_FAILED,
TOO_MANY_DESCRIPTORS,
UNEXPECTED_EOF,
UNKNOWN_ERROR,
UNSUPPORTED_OPERATION,
WOULD_BLOCK,
}
fn void putchar(char c) @inline
/**
* @param stream
* @require @is_instream(stream)
**/
macro String! readline(stream = io::stdin(), Allocator* allocator = mem::heap())
{
libc::putchar(c);
bool $is_stream = @typeid(stream) == InStream*.typeid;
$if $is_stream:
$typeof(&stream.read_byte) func = &stream.read_byte;
char val = func((void*)stream)!;
$else
char val = stream.read_byte()!;
$endif
if (val == '\n') return "";
@pool(allocator)
{
DString str = dstring::temp_with_capacity(256);
if (val != '\r') str.append(val);
while (1)
{
$if $is_stream:
char! c = func((void*)stream);
$else
char! c = stream.read_byte();
$endif
if (catch err = c)
{
if (err == IoError.EOF) break;
return err?;
}
if (c == '\r') continue;
if (c == '\n') break;
str.append_char(c);
}
return str.copy_str(allocator);
};
}
macro String! treadline(stream = io::stdin()) => readline(stream, mem::temp()) @inline;
/**
* @require @is_outstream(out) "The output must implement OutStream"
*/
macro usz! fprint(out, x)
{
var $Type = $typeof(x);
$switch ($Type)
$case String:
return out.write(x);
$case ZString:
return out.write(x.str_view());
$case DString:
return out.write(x.str_view());
$default:
$if $assignable(x, String):
return out.write((String)x);
$else
return fprintf(out, "%s", x);
$endif
$endswitch
}
fn usz! fprintf(OutStream* out, String format, args...)
{
Formatter formatter;
formatter.init(&out_putstream_fn, &out);
return formatter.vprintf(format, args);
}
fn usz! fprintfn(OutStream* out, String format, args...) @maydiscard
{
Formatter formatter;
formatter.init(&out_putstream_fn, &out);
usz len = formatter.vprintf(format, args)!;
out.write_byte('\n')!;
if (&out.flush) out.flush()!;
return len + 1;
}
/**
* @require @is_outstream(out) "The output must implement OutStream"
*/
macro usz! fprintn(out, x = "")
{
usz len = fprint(out, x)!;
out.write_byte('\n')!;
$switch
$case @typeid(out) == OutStream*.typeid:
if (&out.flush) out.flush()!;
$case $defined(out.flush):
out.flush()!;
$endswitch
return len + 1;
}
macro void print(x)
{
var $Type = $typeof(x);
$switch ($Type)
$case String:
catch? stdout().print(x);
$case ZString:
catch? stdout().print(x.as_str());
$case DString:
catch? stdout().print(x.str());
$case VarString:
catch? stdout().print(x.str());
$default:
$if (@convertible(x, String))
catch? stdout().print((String)x);
$else
catch? stdout().printf("%s", x);
$endif
$endswitch
(void)fprint(io::stdout(), x);
}
macro void printn(x = "")
{
var $Type = $typeof(x);
$switch ($Type)
$case String:
catch? stdout().printn(x);
$case ZString:
catch? stdout().printn(x.as_str());
$case DString:
catch? stdout().printn(x.str());
$case VarString:
catch? stdout().printn(x.str());
$default:
$if (@convertible(x, String))
catch? stdout().printn((String)x);
$else
catch? stdout().printfn("%s", x);
$endif
$endswitch
(void)fprintn(io::stdout(), x);
}
macro void println(x = "") @deprecated => printn(x);
fn File stdout()
macro void eprint(x)
{
return { libc::stdout() };
(void)fprint(io::stderr(), x);
}
fn File stderr()
macro void eprintn(x)
{
return { libc::stderr() };
(void)fprintn(io::stderr(), x);
}
fn File stdin()
fn void! out_putstream_fn(void* data, char c) @private
{
return { libc::stdin() };
OutStream** stream = data;
return (*stream).write_byte(c);
}
/*
error FileError
fn void! out_putchar_fn(void* data @unused, char c) @private
{
ulong errno;
libc::putchar(c);
}
fn FileError errorFromErrno()
fn usz! printf(String format, args...) @maydiscard
{
return FileError { };
Formatter formatter;
formatter.init(&out_putchar_fn);
return formatter.vprintf(format, args);
}
pubic fn void! File.clearerr(File *file) @inline
fn usz! printfn(String format, args...) @maydiscard
{
clearerr(file->file);
Formatter formatter;
formatter.init(&out_putchar_fn);
usz len = formatter.vprintf(format, args)!;
putchar('\n');
io::stdout().flush()!;
return len + 1;
}
fn void File.error(File *file) @inline
fn usz! eprintf(String format, args...) @maydiscard
{
int err = ferror
Formatter formatter;
OutStream* stream = stderr();
formatter.init(&out_putstream_fn, &stream);
return formatter.vprintf(format, args);
}
fn usz! eprintfn(String format, args...) @maydiscard
{
Formatter formatter;
OutStream* stream = stderr();
formatter.init(&out_putstream_fn, &stream);
usz len = formatter.vprintf(format, args)! + 1;
stderr().write_byte('\n')!;
stderr().flush()!;
return len;
}
fn char[]! bprintf(char[] buffer, String format, args...) @maydiscard
{
Formatter formatter;
BufferData data = { .buffer = buffer };
formatter.init(&out_buffer_fn, &data);
usz size = formatter.vprintf(format, args)!;
return buffer[:data.written];
}
fn void! out_buffer_fn(void *data, char c) @private
{
BufferData *buffer_data = data;
if (buffer_data.written >= buffer_data.buffer.len) return PrintFault.BUFFER_EXCEEDED?;
buffer_data.buffer[buffer_data.written++] = c;
}
struct BufferData @private
{
char[] buffer;
usz written;
}
module std::io @if (env::LIBC);
import libc;
fn void putchar(char c) @inline
{
libc::putchar(c);
}
fn File* stdout()
{
static File file;
if (!file.file) file = file::from_handle(libc::stdout());
return &file;
}
fn File* stderr()
{
static File file;
if (!file.file) file = file::from_handle(libc::stderr());
return &file;
}
fn File* stdin()
{
static File file;
if (!file.file) file = file::from_handle(libc::stdin());
return &file;
}
module std::io @if(!env::LIBC);
File stdin_file;
File stdout_file;
File stderr_file;
fn void putchar(char c) @inline
{
(void)stdout_file.putc(c);
}
fn File* stdout()
{
return &stdout_file;
}
fn File* stderr()
{
return &stderr_file;
}
fn File* stdin()
{
return &stdin_file;
}
*/

View File

@@ -1,173 +0,0 @@
module std::io::file;
import libc;
fn File! open(String filename, String mode)
{
return { .file = os::native_fopen(filename, mode) };
}
fn void! File.open(File* file, String filename, String mode) @deprecated
{
file.file = os::native_fopen(filename, mode)?;
}
/**
* @require file.file != null
**/
fn void! File.reopen(File* file, String filename, String mode)
{
file.file = os::native_freopen(file.file, filename, mode)?;
}
/**
* @require file.file != null
**/
fn usz! File.seek(File file, isz offset, Seek seek_mode = Seek.SET)
{
os::native_fseek(file.file, offset, seek_mode)?;
return os::native_ftell(file.file);
}
/*
Implement later
/**
* @require file.file == null
**/
fn void! File.memopen(File* file, char[] data, String mode)
{
@pool()
{
file.file = libc::memopen(data.ptr, data.len, mode.zstr_tcopy(), file.file);
// TODO errors
};
}
*/
/**
* @require file && file.file != null
*/
fn void! File.putc(File *file, char c)
{
if (!libc::fputc(c, file.file)) return IoError.FILE_EOF!;
}
/**
* @require file != null
*/
fn void! File.close(File *file) @inline
{
if (file.file && libc::fclose(file.file))
{
switch (libc::errno())
{
case errno::ECONNRESET:
case errno::EBADF: return IoError.FILE_NOT_VALID!;
case errno::EINTR: return IoError.INTERRUPTED!;
case errno::EDQUOT:
case errno::EFAULT:
case errno::EAGAIN:
case errno::EFBIG:
case errno::ENETDOWN:
case errno::ENETUNREACH:
case errno::ENOSPC:
case errno::EIO: return IoError.INCOMPLETE_WRITE!;
default: return IoError.UNKNOWN_ERROR!;
}
}
file.file = null;
}
/**
* @require file && file.file
*/
fn bool File.eof(File* file) @inline
{
return libc::feof(file.file) != 0;
}
/**
* @param [in] buffer
*/
fn usz! File.read(File* file, char[] buffer)
{
return os::native_fread(file.file, buffer);
}
/**
* @param [&in] file
* @param [&out] buffer
* @require file.file `File must be initialized`
*/
fn usz! File.write(File file, char[] buffer)
{
return os::native_fwrite(file.file, buffer);
}
/**
* @param [&in] file
* @require file.file `File must be initialized`
*/
fn usz! File.printn(File file, String string)
{
usz len = file.print(string)?;
if (!libc::putc('\n', file.file)) return IoError.UNKNOWN_ERROR!;
return len + 1;
}
/**
* @param [&in] file
* @require file.file `File must be initialized`
*/
fn usz! File.print(File file, String string)
{
usz len = string.len;
if (len != file.write((char[])string)?) return IoError.UNKNOWN_ERROR!;
return len;
}
fn usz! File.println(File file, String string) @deprecated => file.printn(string);
/**
* @param [&in] file
* @require file.file `File must be initialized`
*/
fn DString File.getline(File* file, Allocator* using = mem::heap())
{
DString s = dstring::new_with_capacity(120, using);
while (!file.eof())
{
int c = libc::fgetc(file.file);
if (c == -1) break;
if (c == '\n') break;
s.append_char((char)c);
}
return s;
}
/**
* @param [&in] file
* @require file.file `File must be initialized`
* @return "a zero terminated String (the pointer may be safely cast into a ZString)"
*/
fn String File.tgetline(File* file)
{
return file.getline(mem::temp()).zstr().as_str();
}
fn char! File.getc(File* file)
{
int c = libc::fgetc(file.file);
if (c == -1) return IoError.FILE_EOF!;
return (char)c;
}
/**
* @param [&in] file
* @require file.file `File must be initialized`
*/
fn void File.flush(File* file)
{
libc::fflush(file.file);
}

View File

@@ -1,14 +0,0 @@
module std::io::file;
import libc;
fn bool is_file(String path)
{
return os::native_is_file(path);
}
fn usz! get_size(String path)
{
return os::native_file_size(path);
}

View File

@@ -1,502 +0,0 @@
module std::io;
import std::collections::map;
import libc;
const int PRINTF_NTOA_BUFFER_SIZE = 256;
fault PrintFault
{
BUFFER_EXCEEDED,
INTERNAL_BUFFER_EXCEEDED,
INVALID_FORMAT_STRING,
MISSING_ARG,
INVALID_ARGUMENT_TYPE,
}
fault FormattingFault
{
UNTERMINATED_FORMAT,
MISSING_ARG,
INVALID_WIDTH_ARG,
INVALID_FORMAT_TYPE,
}
typedef OutputFn = fn void!(char c, void* buffer);
typedef ToStringFunction = fn String(void* value, Allocator *using);
typedef ToFormatFunction = fn void!(void* value, Formatter* formatter);
typedef FloatType = double;
fn usz! printf(String format, args...) @maydiscard
{
Formatter formatter;
formatter.init(&out_putchar_fn);
return formatter.vprintf(format, args);
}
define printfln = printfn;
fn usz! printfn(String format, args...) @maydiscard
{
Formatter formatter;
formatter.init(&out_putchar_fn);
usz len = formatter.vprintf(format, args)?;
putchar('\n');
return len + 1;
}
fn usz! VarString.printf(VarString* str, String format, args...) @maydiscard
{
Formatter formatter;
formatter.init(&out_string_append_fn, str);
return formatter.vprintf(format, args);
}
fn usz! VarString.printfn(VarString* str, String format, args...) @maydiscard
{
Formatter formatter;
formatter.init(&out_string_append_fn, str);
usz len = formatter.vprintf(format, args)?;
str.append('\n');
return len + 1;
}
fn char[]! bprintf(char[] buffer, String format, args...) @maydiscard
{
Formatter formatter;
BufferData data = { .buffer = buffer };
formatter.init(&out_buffer_fn, &data);
usz size = formatter.vprintf(format, args)?;
return buffer[:data.written];
}
fn usz! File.printf(File file, String format, args...) @maydiscard
{
Formatter formatter;
formatter.init(&out_fputchar_fn, &file);
return formatter.vprintf(format, args)?;
}
fn usz! File.printfn(File file, String format, args...) @maydiscard
{
Formatter formatter;
formatter.init(&out_fputchar_fn, &file);
usz len = formatter.vprintf(format, args)?;
file.putc('\n')?;
file.flush();
return len + 1;
}
fn usz! Formatter.printf(Formatter* this, String format, args...)
{
return this.vprintf(format, args) @inline;
}
struct Formatter
{
void *data;
OutputFn out_fn;
struct
{
PrintFlags flags;
uint width;
uint prec;
usz idx;
}
}
bitstruct PrintFlags : uint
{
bool zeropad : 0;
bool left : 1;
bool plus : 2;
bool space : 3;
bool hash : 4;
bool uppercase : 5;
bool precision : 6;
}
fn void Formatter.init(Formatter* this, OutputFn out_fn, void* data = null)
{
*this = { .data = data, .out_fn = out_fn};
}
/**
* @require $checks($Type a, a.to_string()) || $checks($Type a, a.to_format(&&Formatter{})) "Expected a type with 'to_string' or 'to_format' defined"
* @require !$checks($Type a, a.to_string()) || $checks($Type a, a.to_string(&&Allocator{})) "Expected 'to_string' to take an allocator as argument."
* @require !$checks($Type a, a.to_format(&&Formatter{})) || $checks($Type a, Formatter b, a.to_format(&b)) "Expected 'to_format' to take a Formatter as argument."
*/
macro void formatter_register_type($Type)
{
$if ($checks($Type a, a.to_format(&&Formatter {})))
if (!toformat_functions.table.len)
{
toformat_functions.init(64);
}
toformat_functions.set($Type.typeid, (ToFormatFunction)&$Type.to_format);
$else
if (!tostring_functions.table.len)
{
tostring_functions.init(64);
}
tostring_functions.set($Type.typeid, (ToStringFunction)&$Type.to_string);
$endif
}
static initialize @priority(101)
{
if (!toformat_functions.table.len)
{
toformat_functions.init(64);
}
if (!tostring_functions.table.len)
{
tostring_functions.init(64);
}
}
fn void! Formatter.out(Formatter* this, char c) @private
{
this.out_fn(c, this.data)?;
}
macro bool! Formatter.print_with_function(Formatter* this, variant arg)
{
if (try to_format = toformat_functions.get(arg.type))
{
PrintFlags old = this.flags;
uint old_width = this.width;
uint old_prec = this.prec;
defer
{
this.flags = old;
this.width = old_width;
this.prec = old_prec;
}
to_format(arg.ptr, this)?;
return true;
}
if (try to_string = tostring_functions.get(arg.type))
{
PrintFlags old = this.flags;
uint old_width = this.width;
uint old_prec = this.prec;
defer
{
this.flags = old;
this.width = old_width;
this.prec = old_prec;
}
@pool()
{
this.out_substr(to_string(arg.ptr, mem::temp()))?;
return true;
};
}
return false;
}
fn void! Formatter.out_str(Formatter* this, variant arg) @private
{
switch (arg.type.kindof)
{
case TYPEID:
return this.out_substr("<typeid>");
case VOID:
return this.out_substr("void");
case ANYERR:
case FAULT:
return this.out_substr((*(anyerr*)arg.ptr).nameof);
case VARIANT:
return this.out_str(*(variant*)arg);
case ENUM:
if (this.print_with_function(arg)?) return;
return this.out_substr(arg.type.names[types::variant_to_int(arg, usz)!!]);
case STRUCT:
if (this.print_with_function(arg)?) return;
return this.out_substr("<struct>");
case UNION:
if (this.print_with_function(arg)?) return;
return this.out_substr("<union>");
case BITSTRUCT:
if (this.print_with_function(arg)?) return;
return this.out_substr("<bitstruct>");
case FUNC:
if (this.print_with_function(arg)?) return;
return this.out_substr("<function>");
case OPTIONAL:
unreachable();
case DISTINCT:
if (this.print_with_function(arg)?) return;
if (arg.type == VarString.typeid)
{
return this.out_substr(((VarString*)arg).str());
}
if (arg.type == DString.typeid)
{
return this.out_substr(((DString*)arg).str());
}
return this.out_str(variant { arg.ptr, arg.type.inner });
case POINTER:
if (this.print_with_function(arg)?) return;
return this.ntoa_variant(arg, 16);
case SIGNED_INT:
case UNSIGNED_INT:
return this.ntoa_variant(arg, 10);
case FLOAT:
return this.ftoa(float_from_variant(arg)!!);
case ARRAY:
if (this.print_with_function(arg)?) return;
// this is SomeType[*] so grab the "SomeType"
typeid inner = arg.type.inner;
usz size = inner.sizeof;
usz len = arg.type.len;
// Pretend this is a String
void* ptr = (void*)arg.ptr;
this.out('[')?;
for (usz i = 0; i < len; i++)
{
if (i != 0) this.out_substr(", ")?;
this.out_str(variant { ptr, inner })?;
ptr += size;
}
return this.out(']');
case VECTOR:
if (this.print_with_function(arg)?) return;
// this is SomeType[*] so grab the "SomeType"
typeid inner = arg.type.inner;
usz size = inner.sizeof;
usz len = arg.type.len;
// Pretend this is a String
void* ptr = (void*)arg.ptr;
this.out_substr("[<")?;
for (usz i = 0; i < len; i++)
{
if (i != 0) this.out_substr(", ")?;
this.out_str(variant { ptr, inner })?;
ptr += size;
}
return this.out_substr(">]");
case SUBARRAY:
if (this.print_with_function(arg)?) return;
// this is SomeType[] so grab the "SomeType"
typeid inner = arg.type.inner;
if (inner == char.typeid)
{
return this.out_substr(*(String*)arg);
}
usz size = inner.sizeof;
// Pretend this is a String
String* temp = (void*)arg.ptr;
void* ptr = (void*)temp.ptr;
usz len = temp.len;
this.out('[')?;
for (usz i = 0; i < len; i++)
{
if (i != 0) this.out_substr(", ")?;
this.out_str(variant { ptr, inner })?;
ptr += size;
}
this.out(']')?;
case BOOL:
if (*(bool*)arg.ptr)
{
return this.out_substr("true");
}
else
{
return this.out_substr("false");
}
default:
if (this.print_with_function(arg)?) return;
return this.out_substr("Invalid type");
}
}
fn void! out_buffer_fn(char c, void *data) @private
{
BufferData *buffer_data = data;
if (buffer_data.written >= buffer_data.buffer.len) return PrintFault.BUFFER_EXCEEDED!;
buffer_data.buffer[buffer_data.written++] = c;
}
fn void! out_null_fn(char c @unused, void* data @unused) @private
{
}
fn void! out_putchar_fn(char c, void* data @unused) @private
{
libc::putchar(c);
}
fn void! out_fputchar_fn(char c, void* data) @private
{
File* f = data;
f.putc(c)?;
}
fn void! out_string_append_fn(char c, void* data) @private
{
VarString* s = data;
s.append_char(c);
}
struct BufferData @private
{
char[] buffer;
usz written;
}
fn usz! Formatter.vprintf(Formatter* this, String format, variant[] variants)
{
if (!this.out_fn)
{
// use null output function
this.out_fn = &out_null_fn;
}
usz format_len = format.len;
usz variant_index = 0;
for (usz i = 0; i < format_len; i++)
{
// format specifier? %[flags][width][.precision][length]
char c = format[i];
if (c != '%')
{
// no
this.out(c)?;
continue;
}
i++;
if (i >= format_len) return PrintFault.INVALID_FORMAT_STRING!;
c = format[i];
if (c == '%')
{
this.out(c)?;
continue;
}
// evaluate flags
this.flags = {};
while FLAG_EVAL: (true)
{
switch (c)
{
case '0': this.flags.zeropad = true;
case '-': this.flags.left = true;
case '+': this.flags.plus = true;
case ' ': this.flags.space = true;
case '#': this.flags.hash = true;
default: break FLAG_EVAL;
}
if (++i >= format_len) return PrintFault.INVALID_FORMAT_STRING!;
c = format[i];
}
// evaluate width field
int w = printf_parse_format_field(variants.ptr, variants.len, &variant_index, format.ptr, format.len, &i)?;
c = format[i];
if (w < 0)
{
this.flags.left = true;
w = -w;
}
this.width = w;
// evaluate precision field
this.prec = 0;
if (c == '.')
{
this.flags.precision = true;
if (++i >= format_len) return PrintFault.INVALID_FORMAT_STRING!;
int prec = printf_parse_format_field(variants.ptr, variants.len, &variant_index, format.ptr, format.len, &i)?;
this.prec = prec < 0 ? 0 : prec;
c = format[i];
}
// evaluate specifier
uint base = 0;
if (variant_index >= variants.len) return PrintFault.MISSING_ARG!;
variant current = variants[variant_index++];
switch (c)
{
case 'd':
base = 10;
this.flags.hash = false;
case 'X' :
this.flags.uppercase = true;
nextcase;
case 'x' :
base = 16;
case 'O':
this.flags.uppercase = true;
nextcase;
case 'o' :
base = 8;
case 'B':
this.flags.uppercase = true;
nextcase;
case 'b' :
base = 2;
case 'A':
this.flags.uppercase = true;
nextcase;
case 'a':
this.atoa(float_from_variant(current)!!)?;
continue;
case 'F' :
this.flags.uppercase = true;
nextcase;
case 'f':
this.ftoa(float_from_variant(current)!!)?;
continue;
case 'E':
this.flags.uppercase = true;
nextcase;
case 'e':
this.etoa(float_from_variant(current)!!)?;
continue;
case 'G':
this.flags.uppercase = true;
nextcase;
case 'g':
this.gtoa(float_from_variant(current)!!)?;
continue;
case 'c':
this.out_char(current)?;
continue;
case 's':
this.out_str(current)?;
continue;
case 'p':
this.flags.zeropad = true;
this.flags.hash = true;
base = 16;
default:
return PrintFault.INVALID_FORMAT_STRING!;
}
if (base != 10)
{
this.flags.plus = false;
this.flags.space = false;
}
// ignore '0' flag when precision is given
if (this.flags.precision) this.flags.zeropad = false;
bool is_neg;
uint128 v = int_from_variant(current, &is_neg)!!;
this.ntoa(v, is_neg, base)?;
}
// termination
// out((char)0, buffer, idx < maxlen ? idx : maxlen - 1U, maxlen);
// return written chars without terminating \0
return this.idx;
}
typedef StringFunctionMap @private = HashMap<typeid, ToStringFunction>;
typedef FormatterFunctionMap @private = HashMap<typeid, ToFormatFunction>;
FormatterFunctionMap toformat_functions @private;
StringFunctionMap tostring_functions @private;

View File

@@ -1,205 +0,0 @@
module std::io;
typedef CloseStreamFn = fn void!(Stream*);
typedef FlushStreamFn = fn void!(Stream*);
typedef SeekStreamFn = fn usz!(Stream*, isz offset, Seek seek);
typedef LenStreamFn = fn usz(Stream*);
typedef AvailableStreamFn = fn usz(Stream*);
typedef ReadStreamFn = fn usz!(Stream*, char[] bytes);
typedef ReadFromStreamFn = fn usz!(Stream*, Stream*);
typedef ReadByteStreamFn = fn char!(Stream*);
typedef PushbackByteStreamFn = fn void!(Stream*);
typedef WriteStreamFn = fn usz!(Stream*, char[] bytes);
typedef WriteToStreamFn = fn usz!(Stream*, Stream* out);
typedef WriteByteStreamFn = fn void!(Stream*, char c);
typedef DestroyStreamFn = fn void!(Stream*);
struct StreamInterface
{
CloseStreamFn close_fn;
FlushStreamFn flush_fn;
SeekStreamFn seek_fn;
LenStreamFn len_fn;
AvailableStreamFn available_fn;
ReadStreamFn read_fn;
ReadFromStreamFn read_stream_fn;
ReadByteStreamFn read_byte_fn;
PushbackByteStreamFn pushback_byte_fn;
WriteStreamFn write_fn;
WriteToStreamFn write_stream_fn;
WriteByteStreamFn write_byte_fn;
DestroyStreamFn destroy_fn;
}
struct Stream
{
StreamInterface *fns;
void* data;
}
fn bool Stream.supports_seek(Stream* s) @inline => (bool)s.fns.seek_fn;
fn bool Stream.supports_available(Stream* s) @inline => s.fns.available_fn || s.fns.seek_fn;
fn bool Stream.supports_len(Stream* s) @inline => s.fns.len_fn || s.fns.seek_fn;
fn bool Stream.supports_read(Stream* s) @inline => s.fns.read_fn || s.fns.read_byte_fn;
fn bool Stream.supports_read_from(Stream* s) @inline => (bool)s.fns.read_stream_fn;
fn bool Stream.supports_write_to(Stream* s) @inline => (bool)s.fns.write_stream_fn;
fn bool Stream.supports_pushback_byte(Stream* s) @inline => s.fns.pushback_byte_fn || s.fns.seek_fn;
fn bool Stream.supports_write(Stream* s) @inline => s.fns.write_fn || s.fns.write_byte_fn;
fn void! Stream.destroy(Stream* s) @inline @maydiscard
{
if (s.fns.destroy_fn) return s.fns.destroy_fn(s);
return s.close();
}
fn void! Stream.close(Stream* s) @inline @maydiscard
{
if (CloseStreamFn func = s.fns.close_fn) return func(s);
}
fn usz! Stream.seek(Stream* s, isz offset, Seek seek) @inline
{
if (SeekStreamFn func = s.fns.seek_fn) return func(s, offset, seek);
return IoError.NOT_SEEKABLE!;
}
fn usz! Stream.available(Stream* s) @inline
{
if (AvailableStreamFn func = s.fns.available_fn) return func(s);
if (SeekStreamFn func = s.fns.seek_fn)
{
usz curr = func(s, 0, Seek.CURSOR)?;
usz len = func(s, 0, Seek.END)?;
func(s, curr, Seek.SET)?;
return len - curr;
}
return IoError.NOT_SEEKABLE!;
}
fn usz! Stream.read(Stream* s, char[] buffer)
{
if (ReadStreamFn func = s.fns.read_fn) return func(s, buffer);
if (ReadByteStreamFn func = s.fns.read_byte_fn)
{
usz len = 0;
foreach (&cptr : buffer)
{
char! c = func(s);
if (catch err = c)
{
case IoError.EOF: return len;
default: return err!;
}
*cptr = c;
len++;
}
}
return IoError.UNSUPPORTED_OPERATION!;
}
fn char! Stream.read_byte(Stream* s) @inline
{
if (ReadByteStreamFn func = s.fns.read_byte_fn) return func(s);
return IoError.UNSUPPORTED_OPERATION!;
}
fn usz! Stream.write(Stream* s, char[] bytes) @inline
{
if (WriteStreamFn func = s.fns.write_fn) return func(s, bytes);
if (WriteByteStreamFn func = s.fns.write_byte_fn)
{
foreach (c : bytes) func(s, c)?;
return bytes.len;
}
return IoError.UNSUPPORTED_OPERATION!;
}
fn void! Stream.write_byte(Stream* s, char b) @inline
{
if (WriteByteStreamFn func = s.fns.write_byte_fn) return func(s, b);
return IoError.UNSUPPORTED_OPERATION!;
}
fn usz! Stream.write_to(Stream* s, Stream* to) @inline
{
if (WriteToStreamFn func = s.fns.write_stream_fn) return func(s, to);
return IoError.UNSUPPORTED_OPERATION!;
}
fn usz! Stream.read_from(Stream* s, Stream* from) @inline
{
if (ReadFromStreamFn func = s.fns.read_stream_fn) return func(s, from);
return IoError.UNSUPPORTED_OPERATION!;
}
fn void! Stream.flush(Stream* s) @inline @maydiscard
{
if (FlushStreamFn func = s.fns.flush_fn) return func(s);
return IoError.UNSUPPORTED_OPERATION!;
}
fn usz! Stream.len(Stream* s) @inline
{
if (LenStreamFn func = s.fns.len_fn) return func(s);
if (SeekStreamFn func = s.fns.seek_fn)
{
usz curr = func(s, 0, Seek.CURSOR)?;
usz len = func(s, 0, Seek.END)?;
func(s, curr, Seek.SET)?;
return len;
}
return IoError.NOT_SEEKABLE!;
}
fn void! Stream.pushback_byte(Stream* s) @inline
{
if (PushbackByteStreamFn func = s.fns.pushback_byte_fn) return func(s);
if (SeekStreamFn func = s.fns.seek_fn)
{
func(s, -1, CURSOR)?;
return;
}
return IoError.UNSUPPORTED_OPERATION!;
}
fn void! Stream.write_string(Stream* s, String str) @inline => (void)(s.write((char[])str)?);
fn usz! Stream.copy_to(Stream* s, Stream* dst, char[] buffer = {})
{
if (buffer.len) return copy_through_buffer(s, dst, buffer);
if (WriteToStreamFn func = s.fns.write_stream_fn) return func(s, dst);
if (ReadFromStreamFn func = dst.fns.read_stream_fn) return func(dst, s);
$switch (env::MEMORY_ENV)
$case NORMAL:
@pool()
{
return copy_through_buffer(s, dst, tmalloc(char, 4096));
};
$case SMALL:
@pool()
{
return copy_through_buffer(s, dst, tmalloc(char, 1024));
};
$case TINY:
$case NONE:
return copy_through_buffer(s, dst, &&(char[256]{}));
$endswitch
}
macro usz! copy_through_buffer(Stream* s, Stream* dst, char[] buffer) @local
{
usz total_copied;
while (true)
{
usz! len = s.read(buffer);
if (catch err = len)
{
case IoError.EOF: return total_copied;
default: return err!;
}
if (!len) return total_copied;
usz written = dst.write(buffer[:len])?;
total_copied += len;
if (written != len) return IoError.INCOMPLETE_WRITE!;
}
}

View File

@@ -1,43 +1,30 @@
module std::io::os;
import libc;
$switch
$case !env::COMPILER_LIBC_AVAILABLE:
fn void! native_chdir(Path path)
{
unreachable("'getcwd' not available");
}
$case env::os_is_win32():
macro void! native_chdir(Path path)
{
@pool()
{
if (files::win32_SetCurrentDirectoryW(path.as_str().to_temp_utf16()!!)) return;
};
return IoError.GENERAL_ERROR!;
$switch
$case env::POSIX:
if (posix::chdir(path.as_zstr()))
{
switch (libc::errno())
{
case errno::EACCES: return IoError.NO_PERMISSION?;
case errno::ENAMETOOLONG: return IoError.NAME_TOO_LONG?;
case errno::ENOTDIR: return IoError.FILE_NOT_DIR?;
case errno::ENOENT: return IoError.FILE_NOT_FOUND?;
case errno::ELOOP: return IoError.SYMLINK_FAILED?;
default: return IoError.GENERAL_ERROR?;
}
}
$case env::WIN32:
@pool()
{
// TODO improve with better error handling.
if (win32::setCurrentDirectoryW(path.str_view().to_temp_utf16()!!)) return;
};
return IoError.GENERAL_ERROR?;
$default:
return IoError.UNSUPPORTED_OPERATION?;
$endswitch
}
$default:
extern fn int _chdir(ZString) @extern("chdir");
macro void! native_chdir(Path p)
{
if (_chdir((ZString)p.as_str()))
{
switch (libc::errno())
{
case errno::EACCES: return IoError.NO_PERMISSION!;
case errno::ENAMETOOLONG: return IoError.NAME_TOO_LONG!;
case errno::ENOTDIR: return IoError.FILE_NOT_DIR!;
case errno::ENOENT: return IoError.FILE_NOT_FOUND!;
case errno::ELOOP: return IoError.SYMLINK_FAILED!;
default: return IoError.GENERAL_ERROR!;
}
}
}
$endswitch

View File

@@ -1,187 +0,0 @@
module std::io::os;
import libc;
typedef FopenFn = fn void*!(String, String);
typedef FreopenFn = fn void*!(void*, String, String);
typedef FcloseFn = fn void!(void*);
typedef FseekFn = fn void!(void*, isz, Seek);
typedef FtellFn = fn usz!(void*);
typedef FwriteFn = fn usz!(void*, char[] buffer);
typedef FreadFn = fn usz!(void*, char[] buffer);
$if (!$defined(native_fopen_fn))
FopenFn native_fopen_fn @weak;
$endif
$if (!$defined(native_fclose_fn))
FcloseFn native_fclose_fn @weak;
$endif
$if (!$defined(native_freopen_fn))
FreopenFn native_freopen_fn @weak;
$endif
$if (!$defined(native_fseek_fn))
FseekFn native_fseek_fn @weak;
$endif
$if (!$defined(native_ftell_fn))
FtellFn native_ftell_fn @weak;
$endif
$if (!$defined(native_fwrite_fn))
FwriteFn native_fwrite_fn @weak;
$endif
$if (!$defined(native_fread_fn))
FreadFn native_fread_fn @weak;
$endif
/**
* @require mode.len > 0
* @require filename.len > 0
**/
fn void*! native_fopen(String filename, String mode) @inline
{
$if (!env::COMPILER_LIBC_AVAILABLE)
if (native_fopen_fn) return native_fopen_fn(filename, mode);
unreachable("Tried to call fopen without support.");
$else
@pool()
{
$if (env::os_is_win32())
void* file = (CFile)_wfopen(filename.to_temp_utf16(), filename.to_temp_utf16())?;
$else
void* file = libc::fopen(filename.zstr_tcopy(), mode.zstr_tcopy());
$endif
return file ?: file_open_errno()!;
};
$endif
}
/**
* @require mode.len > 0
* @require filename.len > 0
**/
fn void*! native_freopen(void* file, String filename, String mode) @inline
{
$if (!env::COMPILER_LIBC_AVAILABLE)
if (native_freopen_fn) return native_freopen_fn(file, filename, mode);
unreachable("Tried to call freopen without support.");
$else
@pool()
{
$if (env::os_is_win32())
file = _wfreopen(filename.to_temp_utf16(), mode.to_temp_utf16(), file)?;
$else
file = libc::freopen(filename.zstr_tcopy(), mode.zstr_tcopy(), file);
$endif
return file ?: file_open_errno()!;
};
$endif
}
fn void! native_fseek(void* file, isz offset, Seek seek_mode) @inline
{
$if (!env::COMPILER_LIBC_AVAILABLE)
if (native_fseek_fn) return native_fseek_fn(file, offset, seek_mode);
unreachable("Tried to call fseek without support.");
$else
$if (env::os_is_win32())
bool success = _fseeki64(file, (long)offset, (int)seek_mode) == 0;
$else
bool success = libc::fseek(file, (SeekIndex)offset, (CInt)seek_mode) == 0;
$endif
if (!success) return file_seek_errno()!;
$endif
}
fn usz! native_ftell(CFile file) @inline
{
$if (!env::COMPILER_LIBC_AVAILABLE)
if (native_ftell_fn) return native_ftell_fn(file);
unreachable("Tried to call ftell without support.");
$else
$if (env::os_is_win32())
long index = _ftelli64(file);
return index >= 0 ? index : file_seek_errno()!;
$else
SeekIndex index = libc::ftell(file);
return index >= 0 ? index : file_seek_errno()!;
$endif
$endif
}
fn usz! native_fwrite(CFile file, char[] buffer) @inline
{
$if (!env::COMPILER_LIBC_AVAILABLE)
if (native_fwrite_fn) return native_fwrite_fn(file, buffer);
unreachable("Tried to call fwrite without support.");
$else
return libc::fwrite(buffer.ptr, 1, buffer.len, file);
$endif
}
fn usz! native_fread(CFile file, char[] buffer) @inline
{
$if (!env::COMPILER_LIBC_AVAILABLE)
if (native_fread_fn) return native_fread_fn(file, buffer);
unreachable("Tried to call fread without support.");
$else
return libc::fread(buffer.ptr, 1, buffer.len, file);
$endif
}
macro anyerr file_open_errno() @local
{
switch (libc::errno())
{
case errno::EACCES: return IoError.NO_PERMISSION;
case errno::EDQUOT: return IoError.OUT_OF_SPACE;
case errno::EBADF: return IoError.FILE_NOT_VALID;
case errno::EEXIST: return IoError.ALREADY_EXISTS;
case errno::EINTR: return IoError.INTERRUPTED;
case errno::EFAULT: return IoError.GENERAL_ERROR;
case errno::EISDIR: return IoError.FILE_IS_DIR;
case errno::ELOOP: return IoError.SYMLINK_FAILED;
case errno::EMFILE: return IoError.TOO_MANY_DESCRIPTORS;
case errno::ENAMETOOLONG: return IoError.NAME_TOO_LONG;
case errno::ENFILE: return IoError.OUT_OF_SPACE;
case errno::ENOTDIR: return IoError.FILE_NOT_DIR;
case errno::ENOENT: return IoError.FILE_NOT_FOUND;
case errno::ENOSPC: return IoError.OUT_OF_SPACE;
case errno::ENXIO: return IoError.FILE_NOT_FOUND;
case errno::EOVERFLOW: return IoError.OVERFLOW;
case errno::EROFS: return IoError.READ_ONLY;
case errno::EOPNOTSUPP: return IoError.UNSUPPORTED_OPERATION;
case errno::EIO: return IoError.INCOMPLETE_WRITE;
case errno::EWOULDBLOCK: return IoError.WOULD_BLOCK;
default: return IoError.UNKNOWN_ERROR;
}
}
macro anyerr file_seek_errno() @local
{
switch (libc::errno())
{
case errno::ESPIPE: return IoError.FILE_IS_PIPE;
case errno::EPIPE: return IoError.FILE_IS_PIPE;
case errno::EOVERFLOW: return IoError.OVERFLOW;
case errno::ENXIO: return IoError.FILE_NOT_FOUND;
case errno::ENOSPC: return IoError.OUT_OF_SPACE;
case errno::EIO: return IoError.INCOMPLETE_WRITE;
case errno::EINVAL: return IoError.INVALID_POSITION;
case errno::EINTR: return IoError.INTERRUPTED;
case errno::EFBIG: return IoError.OUT_OF_SPACE;
case errno::EBADF: return IoError.FILE_NOT_VALID;
case errno::EAGAIN: return IoError.WOULD_BLOCK;
default: return IoError.UNKNOWN_ERROR;
}
}
// Win functions
$if (env::os_is_win32())
extern fn void* _wfopen(Char16*, Char16*) @local;
extern fn void* _wfreopen(Char16*, Char16*, CFile) @local;
extern fn int _fseeki64(CFile, long, int) @local;
extern fn long _ftelli64(CFile) @local;
$endif
$if (env::os_is_posix())
extern fn CInt access(ZString path, CInt mode);
$endif

129
lib/std/io/os/file_libc.c3 Normal file
View File

@@ -0,0 +1,129 @@
module std::io::os @if(env::LIBC);
import libc;
/**
* @require mode.len > 0
* @require filename.len > 0
**/
fn void*! native_fopen(String filename, String mode) @inline
{
@pool()
{
$if env::WIN32:
void* file = libc::_wfopen(filename.to_temp_wstring(), mode.to_temp_wstring())!;
$else
void* file = libc::fopen(filename.zstr_tcopy(), mode.zstr_tcopy());
$endif
return file ?: file_open_errno()?;
};
}
fn void! native_remove(String filename)
{
@pool()
{
$if env::WIN32:
CInt result = libc::_wremove(filename.to_temp_wstring())!;
$else
CInt result = libc::remove(filename.zstr_tcopy());
$endif
if (result)
{
switch (libc::errno())
{
case errno::ENOENT:
return IoError.FILE_NOT_FOUND?;
case errno::EACCES:
default:
return IoError.FILE_CANNOT_DELETE?;
}
}
};
}
/**
* @require mode.len > 0
* @require filename.len > 0
**/
fn void*! native_freopen(void* file, String filename, String mode) @inline
{
@pool()
{
$if env::WIN32:
file = libc::_wfreopen(filename.to_temp_wstring(), mode.to_temp_wstring(), file)!;
$else
file = libc::freopen(filename.zstr_tcopy(), mode.zstr_tcopy(), file);
$endif
return file ?: file_open_errno()?;
};
}
fn void! native_fseek(void* file, isz offset, Seek seek_mode) @inline
{
if (libc::fseek(file, (SeekIndex)offset, (CInt)seek_mode)) return file_seek_errno()?;
}
fn usz! native_ftell(CFile file) @inline
{
long index = libc::ftell(file);
return index >= 0 ? (usz)index : file_seek_errno()?;
}
fn usz! native_fwrite(CFile file, char[] buffer) @inline
{
return libc::fwrite(buffer.ptr, 1, buffer.len, file);
}
fn usz! native_fread(CFile file, char[] buffer) @inline
{
return libc::fread(buffer.ptr, 1, buffer.len, file);
}
macro anyfault file_open_errno() @local
{
switch (libc::errno())
{
case errno::EACCES: return IoError.NO_PERMISSION;
case errno::EDQUOT: return IoError.OUT_OF_SPACE;
case errno::EBADF: return IoError.FILE_NOT_VALID;
case errno::EEXIST: return IoError.ALREADY_EXISTS;
case errno::EINTR: return IoError.INTERRUPTED;
case errno::EFAULT: return IoError.GENERAL_ERROR;
case errno::EISDIR: return IoError.FILE_IS_DIR;
case errno::ELOOP: return IoError.SYMLINK_FAILED;
case errno::EMFILE: return IoError.TOO_MANY_DESCRIPTORS;
case errno::ENAMETOOLONG: return IoError.NAME_TOO_LONG;
case errno::ENFILE: return IoError.OUT_OF_SPACE;
case errno::ENOTDIR: return IoError.FILE_NOT_DIR;
case errno::ENOENT: return IoError.FILE_NOT_FOUND;
case errno::ENOSPC: return IoError.OUT_OF_SPACE;
case errno::ENXIO: return IoError.FILE_NOT_FOUND;
case errno::EOVERFLOW: return IoError.OVERFLOW;
case errno::EROFS: return IoError.READ_ONLY;
case errno::EOPNOTSUPP: return IoError.UNSUPPORTED_OPERATION;
case errno::EIO: return IoError.INCOMPLETE_WRITE;
case errno::EWOULDBLOCK: return IoError.WOULD_BLOCK;
default: return IoError.UNKNOWN_ERROR;
}
}
macro anyfault file_seek_errno() @local
{
switch (libc::errno())
{
case errno::ESPIPE: return IoError.FILE_IS_PIPE;
case errno::EPIPE: return IoError.FILE_IS_PIPE;
case errno::EOVERFLOW: return IoError.OVERFLOW;
case errno::ENXIO: return IoError.FILE_NOT_FOUND;
case errno::ENOSPC: return IoError.OUT_OF_SPACE;
case errno::EIO: return IoError.INCOMPLETE_WRITE;
case errno::EINVAL: return IoError.INVALID_POSITION;
case errno::EINTR: return IoError.INTERRUPTED;
case errno::EFBIG: return IoError.OUT_OF_SPACE;
case errno::EBADF: return IoError.FILE_NOT_VALID;
case errno::EAGAIN: return IoError.WOULD_BLOCK;
default: return IoError.UNKNOWN_ERROR;
}
}

View File

@@ -0,0 +1,75 @@
module std::io::os @if(env::NO_LIBC);
import libc;
def FopenFn = fn void*!(String, String);
def FreopenFn = fn void*!(void*, String, String);
def FcloseFn = fn void!(void*);
def FseekFn = fn void!(void*, isz, Seek);
def FtellFn = fn usz!(void*);
def FwriteFn = fn usz!(void*, char[] buffer);
def FreadFn = fn usz!(void*, char[] buffer);
def RemoveFn = fn void!(String);
FopenFn native_fopen_fn @weak @if(!$defined(native_fopen_fn));
FcloseFn native_fclose_fn @weak @if(!$defined(native_fclose_fn));
FreopenFn native_freopen_fn @weak @if(!$defined(native_freopen_fn));
FseekFn native_fseek_fn @weak @if(!$defined(native_fseek_fn));
FtellFn native_ftell_fn @weak @if(!$defined(native_ftell_fn));
FwriteFn native_fwrite_fn @weak @if(!$defined(native_fwrite_fn));
FreadFn native_fread_fn @weak @if(!$defined(native_fread_fn));
RemoveFn native_remove_fn @weak @if(!$defined(native_remove_fn));
/**
* @require mode.len > 0
* @require filename.len > 0
**/
fn void*! native_fopen(String filename, String mode) @inline
{
if (native_fopen_fn) return native_fopen_fn(filename, mode);
return IoError.UNSUPPORTED_OPERATION?;
}
/**
* Delete a file.
*
* @require filename.len > 0
**/
fn void! native_remove(String filename) @inline
{
if (native_remove_fn) return native_remove_fn(filename);
return IoError.UNSUPPORTED_OPERATION?;
}
/**
* @require mode.len > 0
* @require filename.len > 0
**/
fn void*! native_freopen(void* file, String filename, String mode) @inline
{
if (native_freopen_fn) return native_freopen_fn(file, filename, mode);
return IoError.UNSUPPORTED_OPERATION?;
}
fn void! native_fseek(void* file, isz offset, Seek seek_mode) @inline
{
if (native_fseek_fn) return native_fseek_fn(file, offset, seek_mode);
return IoError.UNSUPPORTED_OPERATION?;
}
fn usz! native_ftell(CFile file) @inline
{
if (native_ftell_fn) return native_ftell_fn(file);
return IoError.UNSUPPORTED_OPERATION?;
}
fn usz! native_fwrite(CFile file, char[] buffer) @inline
{
if (native_fwrite_fn) return native_fwrite_fn(file, buffer);
return IoError.UNSUPPORTED_OPERATION?;
}
fn usz! native_fread(CFile file, char[] buffer) @inline
{
if (native_fread_fn) return native_fread_fn(file, buffer);
return IoError.UNSUPPORTED_OPERATION?;
}

116
lib/std/io/os/fileinfo.c3 Normal file
View File

@@ -0,0 +1,116 @@
module std::io::os;
import libc;
fn void! native_stat(Stat* stat, String path) @if(env::DARWIN || env::LINUX)
{
@pool()
{
$if env::DARWIN || env::LINUX:
int res = libc::stat(path.zstr_tcopy(), stat);
$else
unreachable("Stat unimplemented");
int res = 0;
$endif
if (res != 0)
{
switch (libc::errno())
{
case errno::EBADF:
return IoError.FILE_NOT_VALID?;
case errno::EFAULT:
unreachable("Invalid stat");
case errno::EIO:
return IoError.GENERAL_ERROR?;
case errno::EACCES:
return IoError.NO_PERMISSION?;
case errno::ELOOP:
return IoError.NO_PERMISSION?;
case errno::ENAMETOOLONG:
return IoError.NAME_TOO_LONG?;
case errno::ENOENT:
return IoError.FILE_NOT_FOUND?;
case errno::ENOTDIR:
return IoError.FILE_NOT_DIR?;
case errno::EOVERFLOW:
return IoError.GENERAL_ERROR?;
default:
return IoError.UNKNOWN_ERROR?;
}
}
};
}
fn usz! native_file_size(String path) @if(env::WIN32)
{
@pool()
{
Win32_FILE_ATTRIBUTE_DATA data;
win32::getFileAttributesExW(path.to_temp_wstring()!, Win32_GET_FILEEX_INFO_LEVELS.STANDARD, &data);
Win32_LARGE_INTEGER size;
size.lowPart = data.nFileSizeLow;
size.highPart = data.nFileSizeHigh;
return (usz)size.quadPart;
};
}
fn usz! native_file_size(String path) @if(!env::WIN32 && !env::DARWIN)
{
File f = file::open(path, "r")!;
defer (void)f.close();
return f.seek(0, Seek.END)!;
}
fn usz! native_file_size(String path) @if(env::DARWIN)
{
Stat stat;
native_stat(&stat, path)!;
return stat.st_size;
}
fn bool native_file_or_dir_exists(String path)
{
$switch
$case env::DARWIN:
$case env::LINUX:
Stat stat;
return @ok(native_stat(&stat, path));
$case env::WIN32:
@pool()
{
return (bool)win32::pathFileExistsW(path.to_temp_utf16()) ?? false;
};
$case env::POSIX:
@pool()
{
return posix::access(path.zstr_tcopy(), 0 /* F_OK */) != -1;
};
$default:
unreachable("Not supported");
$endswitch
}
fn bool native_is_file(String path)
{
$switch
$case env::DARWIN:
$case env::LINUX:
Stat stat;
return @ok(native_stat(&stat, path)) && stat.st_mode & libc::S_IFREG;
$default:
File! f = file::open(path, "r");
defer (void)f.close();
return @ok(f);
$endswitch
}
fn bool native_is_dir(String path)
{
$if env::DARWIN || env::LINUX:
Stat stat;
return @ok(native_stat(&stat, path)) && stat.st_mode & libc::S_IFDIR;
$else
return native_file_or_dir_exists(path) && !native_is_file(path);
$endif
}

View File

@@ -1,102 +0,0 @@
module std::io::file::os;
import libc;
$if (env::os_is_darwin())
struct DarwinTimespec @private
{
long tv_sec;
long tv_nsec;
}
struct Darwin64Stat @private
{
int st_dev;
ushort st_mode;
ushort st_nlink;
ulong st_ino;
uint st_uid;
uint st_gid;
int st_rdev;
DarwinTimespec st_atimespec; // time of last access
DarwinTimespec st_mtimespec; // time of last data modification
DarwinTimespec st_ctimespec; // time of last status change
DarwinTimespec st_birthtimespec; // time of file creation(birth)
long st_size;
long st_blocks;
int st_blocksize;
uint st_flags;
uint st_gen;
int st_lspare;
long[2] st_qspare;
}
extern fn int _stat(ZString str, Darwin64Stat* stat) @extern("stat64");
const S_IFMT = 0o170000; // type of file mask
const S_IFIFO = 0o010000; // named pipe (fifo)
const S_IFCHR = 0o020000; // character special
const S_IFDIR = 0o040000; // directory
const S_IFBLK = 0o060000; // block special
const S_IFREG = 0o100000; // regular
const S_IFLNK = 0o120000; // symbolic link
const S_IFSOCK = 0o140000; // socket
fn usz! native_file_size(String path)
{
Darwin64Stat stat;
read_stat(&stat, path)?;
return stat.st_size;
}
fn bool native_file_or_dir_exists(String path)
{
Darwin64Stat stat;
return try? read_stat(&stat, path);
}
fn bool native_is_file(String path)
{
Darwin64Stat stat;
return try? read_stat(&stat, path) && (stat.st_mode & S_IFREG);
}
fn bool native_is_dir(String path)
{
Darwin64Stat stat;
return try? read_stat(&stat, path) && (stat.st_mode & S_IFDIR);
}
fn void! read_stat(Darwin64Stat* stat, String path) @local
{
@pool()
{
int res = _stat(path.zstr_tcopy(), stat);
if (res != 0)
{
switch (libc::errno())
{
case errno::EBADF:
return IoError.FILE_NOT_VALID!;
case errno::EFAULT:
unreachable("Invalid stat");
case errno::EIO:
return IoError.GENERAL_ERROR!;
case errno::EACCES:
return IoError.NO_PERMISSION!;
case errno::ELOOP:
return IoError.NO_PERMISSION!;
case errno::ENAMETOOLONG:
return IoError.NAME_TOO_LONG!;
case errno::ENOENT:
return IoError.FILE_NOT_FOUND!;
case errno::ENOTDIR:
return IoError.FILE_NOT_DIR!;
case errno::EOVERFLOW:
return IoError.GENERAL_ERROR!;
default:
return IoError.UNKNOWN_ERROR!;
}
}
};
}
$endif

View File

@@ -1,143 +0,0 @@
module std::io::file::os;
// native_temp_directory, for non Win32
$if (!env::os_is_win32())
fn Path! native_temp_directory(Allocator* using = mem::heap())
{
foreach (String env : { "TMPDIR", "TMP", "TEMP", "TEMPDIR" })
{
String tmpdir = env::get_var(env) ?? "";
if (tmpdir) return path::new(tmpdir, using);
}
return path::new("/tmp", using);
}
$if (env::COMPILER_LIBC_AVAILABLE)
extern fn void* opendir(ZString);
extern fn void closedir(void*);
const DT_UNKNOWN = 0;
const DT_FIFO = 1;
const DT_CHR = 2;
const DT_DIR = 4;
const DT_BLK = 6;
const DT_REG = 8;
const DT_LNK = 10;
const DT_SOCK = 12;
const DT_WHT = 14;
fn PathList! native_readdir(Path dir, bool no_dirs, bool no_symlinks, String mask, Allocator* using)
{
PathList list;
list.init(.using = using);
void* directory = opendir(dir.as_str() ? dir.as_zstr() : (ZString)".");
defer if (directory) closedir(directory);
if (!directory) return (path::is_dir(dir) ? IoError.CANNOT_READ_DIR : IoError.FILE_NOT_DIR)!;
NativeDirentry* entry;
while ((entry = readdir(directory)))
{
String name = ((ZString)&entry.name).as_str();
if (!name || name == "." || name == "..") continue;
if (entry.type == DT_LNK && no_symlinks) continue;
if (entry.type == DT_DIR && no_dirs) continue;
Path path = path::new(name.copy(using), using)!!;
list.append(path);
}
return list;
}
$endif
$endif
$if (!env::os_is_darwin() && !env::os_is_win32())
fn usz! native_file_size(String path)
{
File f = file::open(path, "r")?;
defer (void)f.close();
return f.seek(0, Seek.END)?;
}
$if (env::os_is_posix() && env::COMPILER_LIBC_AVAILABLE)
fn bool native_file_or_dir_exists(String path)
{
@pool()
{
return os::access(path.zstr_tcopy(), 0 /* F_OK */) != -1;
};
}
fn bool native_is_file(String path)
{
File! f = file::open(path, "r");
defer (void)f.close();
return try? f;
}
fn bool native_is_dir(String path)
{
return native_file_or_dir_exists(path) && !native_is_file(path);
}
$else
fn bool native_file_or_dir_exists(String path)
{
unreachable("Tried to call file_or_dir_exists without support.");
}
fn bool native_is_dir(String path)
{
unreachable("Tried to call is_dir without support.");
}
fn bool native_is_file(String path)
{
unreachable("Tried to call is_file without support.");
}
$endif
$endif
$switch (env::OS_TYPE)
$case IOS:
$case MACOSX:
$case TVOS:
$case WATCHOS:
extern fn NativeDirentry* readdir(void*) @extern("readdir$INODE64");
struct NativeDirentry
{
usz ino;
usz seekoff;
ushort reclen;
ushort namelen;
char type;
char[1024] name;
}
$case LINUX:
extern fn NativeDirentry* readdir(void*);
struct NativeDirentry
{
usz ino;
isz seekoff;
ushort reclen;
char type;
char[*] name;
}
$default:
// Fix this as we go along.
extern fn NativeDirentry* readdir(void*);
struct NativeDirentry
{
usz ino;
isz seekoff;
ushort reclen;
char type;
char[*] name;
}
$endswitch

View File

@@ -1,63 +0,0 @@
module std::io::file::os;
import std::os::win32::files;
$if (env::os_is_win32())
fn usz! native_file_size(String path)
{
@pool()
{
Char16[] path16 = path.to_temp_utf16()?;
Win32_FILE_ATTRIBUTE_DATA data;
files::win32_GetFileAttributesExW(path16, Win32_GET_FILEEX_INFO_LEVELS.STANDARD, &data);
Win32_LARGE_INTEGER size;
size.lowPart = data.nFileSizeLow;
size.highPart = data.nFileSizeHigh;
return (usz)size.quadPart;
};
}
fn bool native_file_or_dir_exists(String path)
{
@pool()
{
return (bool)files::win32_PathFileExistsW(path.to_temp_utf16()) ?? false;
};
}
fn bool native_is_file(String path)
{
File! f = file::open(path, "r");
defer (void)f.close();
return try? f;
}
fn bool native_is_dir(String path)
{
return native_file_or_dir_exists(path) && !native_is_file(path);
}
fn Path! native_temp_directory(Allocator* using = mem::heap())
{
@stack_mem(256; Allocator* mem)
{
Win32_DWORD len = files::win32_GetTempPathW(0, null);
if (!len) return IoError.GENERAL_ERROR!;
Char16[] buff = malloc(Char16, len + 1, .using = mem);
if (!files::win32_GetTempPathW(len, buff)) return IoError.GENERAL_ERROR!;
return path::new(string::from_utf16(buff[:len], .using = mem), using);
};
}
/*
}else if(method == file_size_methods::get_attributes){
WIN32_FILE_ATTRIBUTE_DATA file_attr_data;
if(GetFileAttributesEx(path, GetFileExInfoStandard, &file_attr_data)){
file_size.LowPart = file_attr_data.nFileSizeLow;
file_size.HighPart = file_attr_data.nFileSizeHigh;
}
}
*/
$endif

View File

@@ -1,54 +1,41 @@
module std::io::os;
import libc;
$switch
$case !env::COMPILER_LIBC_AVAILABLE:
fn String! getcwd(Allocator* using = mem::heap())
macro String! getcwd(Allocator* allocator = mem::heap())
{
unreachable("'getcwd' not available");
$switch
$case env::WIN32:
const DEFAULT_BUFFER = 256;
Char16[DEFAULT_BUFFER] buffer;
WString res = win32::_wgetcwd(&buffer, DEFAULT_BUFFER);
bool free = false;
defer if (free) libc::free(res);
if (!res)
{
if (libc::errno() != errno::ERANGE) return IoError.GENERAL_ERROR?;
res = win32::_wgetcwd(null, 0);
free = true;
}
Char16[] str16 = res[:win32::wcslen(res)];
return string::new_from_utf16(str16, allocator);
$case env::POSIX:
const usz DEFAULT_BUFFER = 256;
char[DEFAULT_BUFFER] buffer;
ZString res = posix::getcwd(&buffer, DEFAULT_BUFFER);
bool free = false;
if (!res)
{
// Improve error
if (libc::errno() != errno::ERANGE) return IoError.GENERAL_ERROR?;
res = posix::getcwd(null, 0);
free = true;
}
defer if (free) libc::free((void*)res);
return res.copy(allocator);
$default:
return IoError.UNSUPPORTED_OPERATION?;
$endswitch
}
$case env::os_is_win32():
extern fn Char16* _wgetcwd(Char16* buffer, int maxlen);
extern fn usz wcslen(Char16* str);
macro String! getcwd(Allocator* using = mem::heap())
{
const DEFAULT_BUFFER = 256;
Char16[DEFAULT_BUFFER] buffer;
Char16 *res = _wgetcwd(&buffer, DEFAULT_BUFFER);
bool free = false;
defer if (free) libc::free(res);
if (!res)
{
if (libc::errno() != errno::ERANGE) return IoError.GENERAL_ERROR!;
res = _wgetcwd(null, 0);
free = true;
}
Char16[] str16 = res[:wcslen(res)];
return str::utf16to8(str16, using);
}
$default:
extern fn ZString _getcwd(char* pwd, usz len) @extern("getcwd");
macro String! getcwd(Allocator* using = mem::heap())
{
const usz DEFAULT_BUFFER = 256;
char[DEFAULT_BUFFER] buffer;
ZString res = _getcwd(&buffer, DEFAULT_BUFFER);
bool free = false;
if (!res)
{
// Improve error
if (libc::errno() != errno::ERANGE) return IoError.GENERAL_ERROR!;
res = _getcwd(null, 0);
free = true;
}
defer if (free) libc::free((void*)res);
return res.copy(using);
}
$endswitch

49
lib/std/io/os/ls.c3 Normal file
View File

@@ -0,0 +1,49 @@
module std::io::file::os @if(env::POSIX);
fn PathList! native_ls(Path dir, bool no_dirs, bool no_symlinks, String mask, Allocator* allocator)
{
PathList list;
list.init_new(.allocator = allocator);
DIRPtr directory = posix::opendir(dir.str_view() ? dir.as_zstr() : (ZString)".");
defer if (directory) posix::closedir(directory);
if (!directory) return (path::is_dir(dir) ? IoError.CANNOT_READ_DIR : IoError.FILE_NOT_DIR)?;
Posix_dirent* entry;
while ((entry = posix::readdir(directory)))
{
String name = ((ZString)&entry.name).str_view();
if (!name || name == "." || name == "..") continue;
if (entry.d_type == posix::DT_LNK && no_symlinks) continue;
if (entry.d_type == posix::DT_DIR && no_dirs) continue;
Path path = path::new(name, allocator)!!;
list.append(path);
}
return list;
}
module std::io::os @if(env::WIN32);
fn PathList! native_ls(Path dir, bool no_dirs, bool no_symlinks, String mask, Allocator* allocator)
{
PathList list;
list.init_new(.allocator = allocator);
@pool(allocator)
{
WString result = dir.str_view().tconcat(`\*`).to_temp_wstring()!!;
Win32_WIN32_FIND_DATAW find_data;
Win32_HANDLE find = win32::findFirstFileW(result, &find_data);
if (find == win32::INVALID_HANDLE_VALUE) return IoError.CANNOT_READ_DIR?;
defer win32::findClose(find);
do
{
if (no_dirs && (find_data.dwFileAttributes & win32::FILE_ATTRIBUTE_DIRECTORY)) continue;
@pool(allocator)
{
String filename = string::temp_from_wstring((WString)&find_data.cFileName)!;
if (filename == ".." || filename == ".") continue;
list.append(path::new(filename, allocator)!);
};
} while (win32::findNextFileW(find, &find_data));
return list;
};
}

50
lib/std/io/os/mkdir.c3 Normal file
View File

@@ -0,0 +1,50 @@
module std::io::os;
import libc;
import std::io::path;
import std::os::win32;
import std::os::posix;
macro bool! native_mkdir(Path path, MkdirPermissions permissions)
{
$switch
$case env::POSIX:
if (!posix::mkdir(path.as_zstr(), permissions == NORMAL ? 0o777 : 0o700)) return true;
switch (libc::errno())
{
case errno::EACCES:
case errno::EPERM:
case errno::EROFS:
case errno::EFAULT: return IoError.NO_PERMISSION?;
case errno::ENAMETOOLONG: return IoError.NAME_TOO_LONG?;
case errno::EDQUOT:
case errno::ENOSPC: return IoError.OUT_OF_SPACE?;
case errno::EISDIR:
case errno::EEXIST: return false;
case errno::ELOOP: return IoError.SYMLINK_FAILED?;
case errno::ENOTDIR: return IoError.FILE_NOT_FOUND?;
default: return IoError.GENERAL_ERROR?;
}
$case env::WIN32:
@pool()
{
// TODO security attributes
if (win32::createDirectoryW(path.str_view().to_temp_utf16()!!, null)) return true;
switch (win32::getLastError())
{
case win32::ERROR_ACCESS_DENIED:
return IoError.NO_PERMISSION?;
case win32::ERROR_DISK_FULL:
return IoError.OUT_OF_SPACE?;
case win32::ERROR_ALREADY_EXISTS:
return false;
case win32::ERROR_PATH_NOT_FOUND:
return IoError.FILE_NOT_FOUND?;
default:
return IoError.GENERAL_ERROR?;
}
};
$default:
return IoError.UNSUPPORTED_OPERATION?;
$endswitch
}

48
lib/std/io/os/rmdir.c3 Normal file
View File

@@ -0,0 +1,48 @@
module std::io::os;
import libc;
import std::io::path;
import std::os::win32;
import std::os::posix;
macro bool! native_rmdir(Path path)
{
$switch
$case env::POSIX:
if (!posix::rmdir(path.as_zstr())) return true;
switch (libc::errno())
{
case errno::EBUSY: return IoError.BUSY?;
case errno::EACCES:
case errno::EPERM:
case errno::EROFS:
case errno::EFAULT: return IoError.NO_PERMISSION?;
case errno::ENAMETOOLONG: return IoError.NAME_TOO_LONG?;
case errno::ENOTDIR:
case errno::ENOENT: return false;
case errno::ENOTEMPTY: return IoError.DIR_NOT_EMPTY?;
case errno::ELOOP: return IoError.SYMLINK_FAILED?;
default: return IoError.GENERAL_ERROR?;
}
$case env::WIN32:
@pool()
{
if (win32::removeDirectoryW(path.str_view().to_temp_utf16()!!)) return true;
switch (win32::getLastError())
{
case win32::ERROR_ACCESS_DENIED:
return IoError.NO_PERMISSION?;
case win32::ERROR_CURRENT_DIRECTORY:
return IoError.BUSY?;
case win32::ERROR_DIR_NOT_EMPTY:
return IoError.DIR_NOT_EMPTY?;
case win32::ERROR_DIRECTORY:
case win32::ERROR_PATH_NOT_FOUND:
return false;
default:
return IoError.GENERAL_ERROR?;
}
};
$default:
return IoError.UNSUPPORTED_OPERATION?;
$endswitch
}

63
lib/std/io/os/rmtree.c3 Normal file
View File

@@ -0,0 +1,63 @@
module std::io::file::os @if(env::POSIX);
import libc;
/**
* @require dir.str_view()
**/
fn void! native_rmtree(Path dir)
{
DIRPtr directory = posix::opendir(dir.as_zstr());
defer if (directory) posix::closedir(directory);
if (!directory) return path::is_dir(dir) ? IoError.CANNOT_READ_DIR? : IoError.FILE_NOT_DIR?;
Posix_dirent* entry;
while ((entry = posix::readdir(directory)))
{
@pool()
{
String name = ((ZString)&entry.name).str_view();
if (!name || name == "." || name == "..") continue;
Path new_path = dir.tappend(name)!;
if (entry.d_type == posix::DT_DIR)
{
native_rmtree(new_path)!;
continue;
}
if (libc::remove(new_path.as_zstr()))
{
// TODO improve
return IoError.GENERAL_ERROR?;
}
};
}
os::native_rmdir(dir)!;
}
module std::io::os @if(env::WIN32);
fn void! native_rmtree(Path path)
{
Win32_WIN32_FIND_DATAW find_data;
String s = path.str_view().tconcat("\\*");
Win32_HANDLE find = win32::findFirstFileW(s.to_temp_utf16(), &find_data)!;
if (find == win32::INVALID_HANDLE_VALUE) return IoError.CANNOT_READ_DIR?;
defer win32::findClose(find);
do
{
@pool()
{
String filename = string::new_from_wstring((WString)&find_data.cFileName, mem::temp())!;
if (filename == "." || filename == "..") continue;
Path file_path = path.tappend(filename)!;
if (find_data.dwFileAttributes & win32::FILE_ATTRIBUTE_DIRECTORY)
{
native_rmtree(file_path)!;
}
else
{
win32::deleteFileW(file_path.str_view().to_temp_wstring()!!);
}
};
} while (win32::findNextFileW(find, &find_data) != 0);
os::native_rmdir(path)!;
}

View File

@@ -0,0 +1,30 @@
module std::io::os @if(env::LIBC);
fn Path! native_temp_directory(Allocator* allocator = mem::heap()) @if(!env::WIN32)
{
foreach (String env : { "TMPDIR", "TMP", "TEMP", "TEMPDIR" })
{
String tmpdir = env::get_var(env) ?? "";
if (tmpdir) return path::new(tmpdir, allocator);
}
return path::new("/tmp", allocator);
}
fn Path! native_temp_directory(Allocator* allocator = mem::heap()) @if(env::WIN32)
{
@pool(allocator)
{
Win32_DWORD len = win32::getTempPathW(0, null);
if (!len) return IoError.GENERAL_ERROR?;
Char16[] buff = mem::temp_array(Char16, len + (usz)1);
if (!win32::getTempPathW(len, buff)) return IoError.GENERAL_ERROR?;
return path::new(string::temp_from_utf16(buff[:len]), allocator);
};
}
module std::io::os @if(env::NO_LIBC);
macro Path! native_temp_directory(Allocator* allocator = mem::heap())
{
return IoError.UNSUPPORTED_OPERATION?;
}

View File

@@ -1,12 +1,12 @@
module std::io::path;
import std::collections::list;
const PathEnv DEFAULT_PATH_ENV = env::os_is_win32() ? PathEnv.WIN32 : PathEnv.POSIX;
const PathEnv DEFAULT_PATH_ENV = env::WIN32 ? PathEnv.WIN32 : PathEnv.POSIX;
const char PREFERRED_SEPARATOR_WIN32 = '\\';
const char PREFERRED_SEPARATOR_POSIX = '/';
const char PREFERRED_SEPARATOR = env::os_is_win32() ? PREFERRED_SEPARATOR_WIN32 : PREFERRED_SEPARATOR_POSIX;
const char PREFERRED_SEPARATOR = env::WIN32 ? PREFERRED_SEPARATOR_WIN32 : PREFERRED_SEPARATOR_POSIX;
typedef PathList = List<Path>;
def PathList = List(<Path>);
fault PathResult
{
@@ -14,7 +14,7 @@ fault PathResult
NO_PARENT,
}
struct Path
struct Path (Printable)
{
String path_string;
PathEnv env;
@@ -26,22 +26,22 @@ enum PathEnv
POSIX
}
fn Path! getcwd(Allocator* using = mem::heap())
fn Path! getcwd(Allocator* allocator = mem::heap())
{
@stack_mem(256; Allocator* mem)
@pool(allocator)
{
return new(os::getcwd(mem), using);
return new(os::getcwd(mem::temp()), allocator);
};
}
fn bool is_dir(Path path) => os::native_is_dir(path.as_str());
fn bool is_file(Path path) => os::native_is_file(path.as_str());
fn usz! file_size(Path path) => os::native_file_size(path.as_str());
fn bool exists(Path path) => os::native_file_or_dir_exists(path.as_str());
fn bool is_dir(Path path) => os::native_is_dir(path.str_view());
fn bool is_file(Path path) => os::native_is_file(path.str_view());
fn usz! file_size(Path path) => os::native_file_size(path.str_view());
fn bool exists(Path path) => os::native_file_or_dir_exists(path.str_view());
fn Path! tgetcwd() => getcwd(mem::temp()) @inline;
fn void! chdir(Path path) => os::native_chdir(path) @inline;
fn Path! temp_directory(Allocator* using = mem::heap()) => os::native_temp_directory(using);
fn Path! temp_directory(Allocator* allocator = mem::heap()) => os::native_temp_directory(allocator);
fn void! delete(Path path) => os::native_remove(path.str_view()) @inline;
macro bool is_separator(char c, PathEnv path_env = DEFAULT_PATH_ENV)
{
@@ -58,94 +58,181 @@ macro bool is_win32_separator(char c)
return c == '/' || c == '\\';
}
fn Path! new(String path, Allocator* using = mem::heap(), PathEnv path_env = DEFAULT_PATH_ENV)
fn PathList! ls(Path dir, bool no_dirs = false, bool no_symlinks = false, String mask = "", Allocator* allocator = mem::heap())
{
return { normalize(path.copy(using), path_env), path_env };
$if $defined(os::native_ls):
return os::native_ls(dir, no_dirs, no_symlinks, mask, allocator);
$else
return IoError.UNSUPPORTED_OPERATION?;
$endif
}
fn Path! new_windows(String path, Allocator* using = mem::heap())
enum MkdirPermissions
{
return new(path, using, WIN32);
NORMAL,
USER_ONLY,
USER_AND_ADMIN
}
fn Path! new_posix(String path, Allocator* using = mem::heap())
fn bool! mkdir(Path path, bool recursive = false, MkdirPermissions permissions = NORMAL)
{
return new(path, using, POSIX);
if (!path.path_string.len) return PathResult.INVALID_PATH?;
if (is_dir(path)) return false;
if (exists(path)) return IoError.FILE_NOT_DIR?;
if (recursive)
{
if (try parent = path.parent()) mkdir(parent, true, permissions)!;
}
if (!is_dir(path.parent()) ?? false) return IoError.CANNOT_READ_DIR?;
return os::native_mkdir(path, permissions);
}
fn bool Path.equals(Path p1, Path p2)
fn bool! rmdir(Path path)
{
return p1.env == p2.env && p1.path_string == p2.path_string;
if (!path.path_string.len) return PathResult.INVALID_PATH?;
return os::native_rmdir(path);
}
fn void! rmtree(Path path)
{
if (!path.path_string.len) return PathResult.INVALID_PATH?;
$if $defined(os::native_rmtree):
return os::native_rmtree(path);
$else
return IoError.UNSUPPORTED_OPERATION?;
$endif
}
fn Path! new(String path, Allocator* allocator = mem::heap(), PathEnv path_env = DEFAULT_PATH_ENV)
{
return { normalize(path.copy(allocator), path_env), path_env };
}
fn Path! temp_new(String path, PathEnv path_env = DEFAULT_PATH_ENV)
{
return new(path, mem::temp(), path_env);
}
fn Path! new_win32_wstring(WString path, Allocator* allocator = mem::heap())
{
@pool(allocator)
{
return path::new(string::temp_from_wstring(path)!, .allocator = allocator);
};
}
fn Path! new_windows(String path, Allocator* allocator = mem::heap())
{
return new(path, allocator, WIN32);
}
fn Path! new_posix(String path, Allocator* allocator = mem::heap())
{
return new(path, allocator, POSIX);
}
fn bool Path.equals(self, Path p2)
{
return self.env == p2.env && self.path_string == p2.path_string;
}
/**
* Append the string to the current path.
*
* @param [in] path
* @param [in] filename
* @ensure return.path_string.len >= path.path_string.len
**/
fn Path! Path.append(Path path, String filename, Allocator* using = mem::heap())
fn Path! Path.append(self, String filename, Allocator* allocator = mem::heap())
{
if (!path.path_string.len) return new(filename, using, path.env)?;
assert(!is_separator(path.path_string[^1], path.env));
if (!self.path_string.len) return new(filename, allocator, self.env)!;
assert(!is_separator(self.path_string[^1], self.env));
@stack_mem(256; Allocator* mem)
@pool(allocator)
{
DString dstr = dstring::new_with_capacity(path.path_string.len + 1 + filename.len, .using = mem);
dstr.append(path.path_string);
DString dstr = dstring::temp_with_capacity(self.path_string.len + 1 + filename.len);
dstr.append(self.path_string);
dstr.append(PREFERRED_SEPARATOR);
dstr.append(filename);
return { normalize(dstr.copy_str(using), path.env), path.env };
return { normalize(dstr.copy_str(allocator), self.env), self.env };
};
}
fn Path! Path.tappend(Path path, String filename) => path.append(filename, mem::temp());
fn Path! Path.tappend(self, String filename) => self.append(filename, mem::temp());
fn usz Path.start_of_base_name(Path path) @local
fn usz Path.start_of_base_name(self) @local
{
String path_str = path.path_string;
String path_str = self.path_string;
if (!path_str.len) return 0;
if (path.env == PathEnv.WIN32)
if (self.env == PathEnv.WIN32)
{
return path_str.rindex_of(`\`) + 1 ?? volume_name_len(path_str, path.env)!!;
return path_str.rindex_of_char('\\') + 1 ?? volume_name_len(path_str, self.env)!!;
}
return path_str.rindex_of("/") + 1 ?? 0;
return path_str.rindex_of_char('/') + 1 ?? 0;
}
fn String Path.basename(Path path)
fn bool! Path.is_absolute(self)
{
usz basename_start = path.start_of_base_name();
String path_str = path.path_string;
String path_str = self.str_view();
if (!path_str.len) return false;
usz path_start = volume_name_len(path_str, self.env)!;
return path_start < path_str.len && is_separator(path_str[path_start], self.env);
}
fn Path! Path.absolute(self, Allocator* allocator = mem::heap())
{
String path_str = self.str_view();
if (!path_str.len) path_str = ".";
if (path_str == ".")
{
String cwd = os::getcwd(mem::temp())!;
return new(cwd, allocator, self.env);
}
switch (self.env)
{
case WIN32:
usz path_start = volume_name_len(path_str, self.env)!;
if (path_start > 0) return self;
case POSIX:
if (path_str[0] == PREFERRED_SEPARATOR_POSIX) return self;
}
String cwd = os::getcwd(mem::temp())!;
return Path{ cwd, self.env }.append(path_str, allocator)!;
}
fn String Path.basename(self)
{
usz basename_start = self.start_of_base_name();
String path_str = self.path_string;
if (basename_start == path_str.len) return "";
return path_str[basename_start..];
}
fn String Path.dirname(Path path)
fn String Path.dirname(self)
{
usz basename_start = path.start_of_base_name();
String path_str = path.path_string;
usz basename_start = self.start_of_base_name();
String path_str = self.path_string;
if (basename_start == 0) return "";
usz start = volume_name_len(path_str, path.env)!!;
usz start = volume_name_len(path_str, self.env)!!;
if (basename_start <= start + 1) return path_str[:basename_start];
return path_str[:basename_start - 1];
}
fn String! Path.extension(Path path)
fn String! Path.extension(self)
{
String basename = path.basename();
usz index = basename.rindex_of(".")?;
String basename = self.basename();
usz index = basename.rindex_of(".")!;
// Plain ".foo" does not have an
if (index == 0) return SearchResult.MISSING!;
if (index == 0) return SearchResult.MISSING?;
if (index == basename.len) return "";
return basename[index + 1..];
}
fn String Path.volume_name(Path path)
fn String Path.volume_name(self)
{
usz len = volume_name_len(path.as_str(), path.env)!!;
usz len = volume_name_len(self.str_view(), self.env)!!;
if (!len) return "";
return path.path_string[:len];
return self.path_string[:len];
}
fn usz! volume_name_len(String path, PathEnv path_env) @local
@@ -153,47 +240,47 @@ fn usz! volume_name_len(String path, PathEnv path_env) @local
usz len = path.len;
if (len < 2 || path_env != PathEnv.WIN32) return 0;
switch (path[0])
{
case '\\':
// "\\" paths.. must be longer than 2
if (len == 2) return 0;
{
case '\\':
// "\\" paths.. must be longer than 2
if (len == 2) return 0;
int count = 1;
while (count < len && path[count] == '\\') count++;
// Not 2 => folded paths
if (count != 2) return 0;
// Check that we have a name followed by '/'
for (usz i = 2; i < len; i++)
{
char c = path[i];
if (is_win32_separator(c)) return i;
if (is_reserved_win32_path_char(c)) return PathResult.INVALID_PATH!;
}
return PathResult.INVALID_PATH!;
case 'A'..'Z':
case 'a'..'z':
return path[1] == ':' ? 2 : 0;
default:
return 0;
}
{
char c = path[i];
if (is_win32_separator(c)) return i;
if (is_reserved_win32_path_char(c)) return PathResult.INVALID_PATH?;
}
return PathResult.INVALID_PATH?;
case 'A'..'Z':
case 'a'..'z':
return path[1] == ':' ? 2 : 0;
default:
return 0;
}
}
fn Path! Path.parent(Path path)
fn Path! Path.parent(self)
{
if (path.path_string.len == 1 && is_separator(path.path_string[0], path.env)) return PathResult.NO_PARENT!;
foreach_r(i, c : path.path_string)
if (self.path_string.len == 1 && is_separator(self.path_string[0], self.env)) return PathResult.NO_PARENT?;
foreach_r(i, c : self.path_string)
{
if (is_separator(c, path.env))
if (is_separator(c, self.env))
{
return { path.path_string[:i], path.env };
return { self.path_string[:i], self.env };
}
}
return PathResult.NO_PARENT!;
return PathResult.NO_PARENT?;
}
fn String! normalize(String path_str, PathEnv path_env = DEFAULT_PATH_ENV)
{
if (!path_str.len) return path_str;
usz path_start = volume_name_len(path_str, path_env)?;
if (!path_str.len) return "";
usz path_start = volume_name_len(path_str, path_env)!;
usz path_len = path_str.len;
if (path_start == path_len) return path_str;
char path_separator = path_env == PathEnv.WIN32 ? PREFERRED_SEPARATOR_WIN32 : PREFERRED_SEPARATOR_POSIX;
@@ -225,7 +312,7 @@ fn String! normalize(String path_str, PathEnv path_env = DEFAULT_PATH_ENV)
// The rest are names of the path elements, so check that the
// characters are valid.
if (is_reserved_path_char(c, path_env)) return PathResult.INVALID_PATH!;
if (is_reserved_path_char(c, path_env)) return PathResult.INVALID_PATH?;
// If we have '.' after a separator
if (c == '.' && previous_was_separator)
@@ -233,13 +320,20 @@ fn String! normalize(String path_str, PathEnv path_env = DEFAULT_PATH_ENV)
// Get the number of dots until next separator, expecting 1 or 2
bool is_last = i == path_len - 1;
int dots = 1;
if (!is_last && path_str[i + 1] == '.')
if (!is_last)
{
dots = 2;
is_last = i == path_len - 2;
if (!is_last && !is_separator(path_str[i + 2], path_env))
char next = path_str[i + 1];
switch
{
dots = 0;
case next == '.':
dots = 2;
is_last = i == path_len - 2;
if (!is_last && !is_separator(path_str[i + 2], path_env))
{
dots = 0;
}
case !is_separator(next, path_env):
dots = 0;
}
}
switch (dots)
@@ -251,7 +345,7 @@ fn String! normalize(String path_str, PathEnv path_env = DEFAULT_PATH_ENV)
continue;
case 2:
// This is an error: /a/../..
if (len == path_start && has_root) return PathResult.INVALID_PATH!;
if (len == path_start && has_root) return PathResult.INVALID_PATH?;
// If this .. at the start, or after ../? If so, we just copy ..
if (len == path_start ||
@@ -278,7 +372,7 @@ fn String! normalize(String path_str, PathEnv path_env = DEFAULT_PATH_ENV)
}
// Reading, we go from /../abc to /../abc
// ^ ^
i += 2;
i += 2;
continue;
default:
break;
@@ -294,16 +388,16 @@ fn String! normalize(String path_str, PathEnv path_env = DEFAULT_PATH_ENV)
return path_str[:len];
}
fn ZString Path.as_zstr(Path path) => (ZString)path.path_string.ptr;
fn ZString Path.as_zstr(self) => (ZString)self.path_string.ptr;
fn String Path.root_directory(Path path)
fn String Path.root_directory(self)
{
String path_str = path.as_str();
String path_str = self.str_view();
usz len = path_str.len;
if (!len) return "";
if (path.env == PathEnv.WIN32)
if (self.env == PathEnv.WIN32)
{
usz root_len = volume_name_len(path_str, path.env)!!;
usz root_len = volume_name_len(path_str, self.env)!!;
if (root_len == len || !is_win32_separator(path_str[root_len])) return "";
return path_str[root_len..root_len];
}
@@ -318,24 +412,57 @@ fn String Path.root_directory(Path path)
return path_str;
}
def PathWalker = fn bool! (Path, bool is_dir, void*);
fn String Path.as_str(Path path)
/*
* Walk the path recursively. PathWalker is run on every file and
* directory found. Return true to abort the walk.
*/
fn bool! Path.walk(self, PathWalker w, void* data)
{
return path.path_string;
const PATH_MAX = 512;
@stack_mem(PATH_MAX; Allocator* allocator)
{
Path abs = self.absolute(allocator)!;
PathList files = ls(abs, .allocator = allocator)!;
foreach (f : files)
{
if (f.str_view() == "." || f.str_view() == "..") continue;
f = abs.append(f.str_view(), allocator)!;
bool is_directory = is_dir(f);
if (w(f, is_directory, data)!) return true;
if (is_directory && f.walk(w, data)!) return true;
}
};
return false;
}
fn String Path.str_view(self) @inline
{
return self.path_string;
}
fn bool Path.has_suffix(Path path, String str)
fn bool Path.has_suffix(self, String str)
{
return path.as_str().ends_with(str);
return self.str_view().ends_with(str);
}
fn void Path.free(self)
{
free(self.path_string.ptr);
}
fn void Path.free(Path path)
fn usz! Path.to_format(&self, Formatter* formatter) @dynamic
{
free(path.path_string.ptr);
return formatter.print(self.str_view());
}
fn String Path.to_new_string(&self, Allocator* allocator = mem::heap()) @dynamic
{
return self.str_view().copy(allocator);
}
const bool[256] RESERVED_PATH_CHAR_POSIX = {
[0] = true,
@@ -364,5 +491,4 @@ macro bool is_reserved_path_char(char c, PathEnv path_env = DEFAULT_PATH_ENV)
return path_env == PathEnv.WIN32
? RESERVED_PATH_CHAR_WIN32[c]
: RESERVED_PATH_CHAR_POSIX[c];
}
}

233
lib/std/io/stream.c3 Normal file
View File

@@ -0,0 +1,233 @@
module std::io;
interface InStream
{
fn void! close() @optional;
fn usz! seek(isz offset, Seek seek) @optional;
fn usz len() @optional;
fn usz! available() @optional;
fn usz! read(char[] buffer);
fn char! read_byte();
fn usz! write_to(OutStream* out) @optional;
fn void! pushback_byte() @optional;
}
interface OutStream
{
fn void! destroy() @optional;
fn void! close() @optional;
fn void! flush() @optional;
fn usz! write(char[] bytes);
fn void! write_byte(char c);
fn usz! read_to(InStream* in) @optional;
}
fn usz! available(InStream* s)
{
if (&s.available) return s.available();
if (&s.seek)
{
usz curr = s.seek(0, Seek.CURSOR)!;
usz len = s.seek(0, Seek.END)!;
s.seek(curr, Seek.SET)!;
return len - curr;
}
return 0;
}
macro bool @is_instream(#expr)
{
return $assignable(#expr, InStream*);
}
macro bool @is_outstream(#expr)
{
return $assignable(#expr, OutStream*);
}
/**
* @param [&out] ref
* @require @is_instream(stream)
**/
macro usz! read_any(stream, any* ref)
{
return read_all(stream, ((char*)ref)[:ref.type.sizeof]);
}
/**
* @param [&in] ref "the object to write."
* @require @is_outstream(stream)
* @ensure return == ref.type.sizeof
*/
macro usz! write_any(stream, any* ref)
{
return write_all(stream, ((char*)ref)[:ref.type.sizeof]);
}
/**
* @require @is_instream(stream)
*/
macro usz! read_all(stream, char[] buffer)
{
if (buffer.len == 0) return 0;
usz n = stream.read(buffer)!;
if (n != buffer.len) return IoError.UNEXPECTED_EOF?;
return n;
}
/**
* @require @is_outstream(stream)
*/
macro usz! write_all(stream, char[] buffer)
{
if (buffer.len == 0) return 0;
usz n = stream.write(buffer)!;
if (n != buffer.len) return IoError.INCOMPLETE_WRITE?;
return n;
}
macro usz! @read_using_read_byte(&s, char[] buffer)
{
usz len = 0;
foreach (&cptr : buffer)
{
char! c = s.read_byte();
if (catch err = c)
{
case IoError.EOF: return len;
default: return err?;
}
*cptr = c;
len++;
}
return len;
}
macro void! @write_byte_using_write(&s, char c)
{
char[1] buff = { c };
(*s).write(&buff)!;
}
macro char! @read_byte_using_read(&s)
{
char[1] buffer;
usz read = (*s).read(&buffer)!;
if (read != 1) return IoError.EOF?;
return buffer[0];
}
def ReadByteFn = fn char!();
macro usz! @write_using_write_byte(&s, char[] bytes)
{
foreach (c : bytes) s.write_byte(self, c)!;
return bytes.len;
}
macro void! @pushback_using_seek(&s)
{
s.seek(-1, CURSOR)!;
}
fn usz! copy_to(InStream* in, OutStream* dst, char[] buffer = {})
{
if (buffer.len) return copy_through_buffer(in, dst, buffer);
if (&in.write_to) return in.write_to(dst);
if (&dst.read_to) return dst.read_to(in);
$switch (env::MEMORY_ENV)
$case NORMAL:
@pool()
{
return copy_through_buffer(in, dst, mem::temp_array(char, 4096));
};
$case SMALL:
@pool()
{
return copy_through_buffer(in, dst, mem::temp_array(char, 1024));
};
$case TINY:
$case NONE:
return copy_through_buffer(in, dst, &&(char[256]{}));
$endswitch
}
macro usz! copy_through_buffer(InStream *in, OutStream* dst, char[] buffer) @local
{
usz total_copied;
while (true)
{
usz! len = in.read(buffer);
if (catch err = len)
{
case IoError.EOF: return total_copied;
default: return err?;
}
if (!len) return total_copied;
usz written = dst.write(buffer[:len])!;
total_copied += len;
if (written != len) return IoError.INCOMPLETE_WRITE?;
}
}
const char[*] MAX_VARS @private = { [2] = 3, [4] = 5, [8] = 10 };
/**
* @require @is_instream(stream)
* @require @typekind(x_ptr) == POINTER && $typeof(x_ptr).inner.kindof.is_int()
**/
macro usz! read_varint(stream, x_ptr)
{
var $Type = $typefrom($typeof(x_ptr).inner);
const MAX = MAX_VARS[$Type.sizeof];
$Type x;
uint shift;
usz n;
for (usz i = 0; i < MAX; i++)
{
char! c = stream.read_byte();
if (catch err = c)
{
case IoError.EOF:
return IoError.UNEXPECTED_EOF?;
default:
return err?;
}
n++;
if (c & 0x80 == 0)
{
if (i + 1 == MAX && c > 1) break;
x |= c << shift;
$if $Type.kindof == SIGNED_INT:
x = x & 1 == 0 ? x >> 1 : ~(x >> 1);
$endif
*x_ptr = x;
return n;
}
x |= (c & 0x7F) << shift;
shift += 7;
}
return MathError.OVERFLOW?;
}
/**
* @require @is_outstream(stream)
* @require @typekind(x).is_int()
**/
macro usz! write_varint(stream, x)
{
var $Type = $typeof(x);
const MAX = MAX_VARS[$Type.sizeof];
char[MAX] buffer @noinit;
usz i;
while (x >= 0x80)
{
buffer[i] = (char)(x | 0x80);
x >>= 7;
i++;
}
buffer[i] = (char)x;
return write_all(stream, buffer[:i + 1]);
}

133
lib/std/io/stream/buffer.c3 Normal file
View File

@@ -0,0 +1,133 @@
module std::io;
struct ReadBuffer (InStream)
{
InStream* wrapped_stream;
char[] bytes;
usz read_idx;
usz write_idx;
}
/**
* Buffer reads from a stream.
* @param [inout] self
* @require bytes.len > 0
* @require self.bytes.len == 0 "Init may not run on already initialized data"
**/
fn ReadBuffer* ReadBuffer.init(&self, InStream* wrapped_stream, char[] bytes)
{
*self = { .wrapped_stream = wrapped_stream, .bytes = bytes };
return self;
}
fn String ReadBuffer.str_view(&self) @inline
{
return (String)self.bytes[self.read_idx:self.write_idx - self.read_idx];
}
fn void! ReadBuffer.close(&self) @dynamic
{
if (&self.wrapped_stream.close) self.wrapped_stream.close()!;
}
fn usz! ReadBuffer.read(&self, char[] bytes) @dynamic
{
if (self.read_idx == self.write_idx)
{
if (self.read_idx == 0 && bytes.len >= self.bytes.len)
{
// Read directly into the input buffer.
return self.wrapped_stream.read(bytes)!;
}
self.refill()!;
}
usz n = min(self.write_idx - self.read_idx, bytes.len);
bytes[:n] = self.bytes[self.read_idx:n];
self.read_idx += n;
return n;
}
fn char! ReadBuffer.read_byte(&self) @dynamic
{
if (self.read_idx == self.write_idx) self.refill()!;
if (self.read_idx == self.write_idx) return IoError.EOF?;
char c = self.bytes[self.read_idx];
self.read_idx++;
return c;
}
fn void! ReadBuffer.refill(&self) @local @inline
{
self.read_idx = 0;
self.write_idx = self.wrapped_stream.read(self.bytes)!;
}
struct WriteBuffer (OutStream)
{
OutStream* wrapped_stream;
char[] bytes;
usz index;
}
/**
* Buffer writes to a stream. Call `flush` when done writing to the buffer.
* @param [inout] self
* @require bytes.len > 0 "Non-empty buffer required"
* @require self.bytes.len == 0 "Init may not run on already initialized data"
**/
fn WriteBuffer* WriteBuffer.init(&self, OutStream* wrapped_stream, char[] bytes)
{
*self = { .wrapped_stream = wrapped_stream, .bytes = bytes };
return self;
}
fn String WriteBuffer.str_view(&self) @inline
{
return (String)self.bytes[:self.index];
}
fn void! WriteBuffer.close(&self) @dynamic
{
if (&self.wrapped_stream.close) return self.wrapped_stream.close();
}
fn void! WriteBuffer.flush(&self) @dynamic
{
self.write_pending()!;
if (&self.wrapped_stream.flush) self.wrapped_stream.flush()!;
}
fn usz! WriteBuffer.write(&self, char[] bytes) @dynamic
{
usz n = self.bytes.len - self.index;
if (bytes.len < n)
{
// Enough room in the buffer.
self.bytes[self.index:bytes.len] = bytes[..];
self.index += bytes.len;
return bytes.len;
}
self.write_pending()!;
if (bytes.len >= self.bytes.len)
{
// Write directly to the stream.
return self.wrapped_stream.write(bytes);
}
// Buffer the data.
self.bytes[:bytes.len] = bytes[..];
self.index = bytes.len;
return bytes.len;
}
fn void! WriteBuffer.write_byte(&self, char c) @dynamic
{
usz n = self.bytes.len - self.index;
if (n == 0) self.write_pending()!;
self.bytes[0] = c;
self.index = 1;
}
fn void! WriteBuffer.write_pending(&self) @local
{
self.index -= self.wrapped_stream.write(self.bytes[:self.index])!;
if (self.index != 0) return IoError.INCOMPLETE_WRITE?;
}

View File

@@ -0,0 +1,148 @@
module std::io;
import std::math;
struct ByteBuffer (InStream, OutStream)
{
Allocator* allocator;
usz max_read;
char[] bytes;
usz read_idx;
usz write_idx;
bool has_last;
}
/**
* ByteBuffer provides a streamable read/write buffer.
* max_read defines how many bytes might be kept before its internal buffer is shrinked.
* @require self.bytes.len == 0 "Buffer already initialized."
**/
fn ByteBuffer*! ByteBuffer.init_new(&self, usz max_read, usz initial_capacity = 16, Allocator* allocator = mem::heap())
{
*self = { .allocator = allocator, .max_read = max_read };
initial_capacity = max(initial_capacity, 16);
self.grow(initial_capacity)!;
return self;
}
fn ByteBuffer*! ByteBuffer.init_temp(&self, usz max_read, usz initial_capacity = 16)
{
return self.init_new(max_read, initial_capacity, mem::temp());
}
/**
* @require buf.len > 0
* @require self.bytes.len == 0 "Buffer already initialized."
**/
fn ByteBuffer*! ByteBuffer.init_with_buffer(&self, char[] buf)
{
*self = { .max_read = buf.len, .bytes = buf };
return self;
}
fn void ByteBuffer.free(&self)
{
if (self.allocator) self.allocator.free(self.bytes);
*self = {};
}
fn usz! ByteBuffer.write(&self, char[] bytes) @dynamic
{
usz cap = self.bytes.len - self.write_idx;
if (cap < bytes.len) self.grow(bytes.len)!;
self.bytes[self.write_idx:bytes.len] = bytes[..];
self.write_idx += bytes.len;
return bytes.len;
}
fn void! ByteBuffer.write_byte(&self, char c) @dynamic
{
usz cap = self.bytes.len - self.write_idx;
if (cap == 0) self.grow(1)!;
self.bytes[self.write_idx] = c;
self.write_idx++;
}
fn usz! ByteBuffer.read(&self, char[] bytes) @dynamic
{
usz readable = self.write_idx - self.read_idx;
if (readable == 0)
{
self.has_last = false;
return IoError.EOF?;
}
usz n = min(readable, bytes.len);
bytes[:n] = self.bytes[self.read_idx:n];
self.read_idx += n;
self.has_last = n > 0;
self.shrink();
return n;
}
fn char! ByteBuffer.read_byte(&self) @dynamic
{
usz readable = self.write_idx - self.read_idx;
if (readable == 0)
{
self.has_last = false;
return IoError.EOF?;
}
char c = self.bytes[self.read_idx];
self.read_idx++;
self.has_last = true;
self.shrink();
return c;
}
/*
* Only the last byte of a successful read can be pushed back.
*/
fn void! ByteBuffer.pushback_byte(&self) @dynamic
{
if (!self.has_last) return IoError.EOF?;
assert(self.read_idx > 0);
self.read_idx--;
self.has_last = false;
}
fn usz! ByteBuffer.seek(&self, isz offset, Seek seek) @dynamic
{
switch (seek)
{
case SET:
if (offset < 0 || offset > self.write_idx) return IoError.INVALID_POSITION?;
self.read_idx = offset;
return offset;
case CURSOR:
if ((offset < 0 && self.read_idx < -offset) ||
(offset > 0 && self.read_idx + offset > self.write_idx)) return IoError.INVALID_POSITION?;
self.read_idx += offset;
case END:
if (offset < 0 || offset > self.write_idx) return IoError.INVALID_POSITION?;
self.read_idx = self.write_idx - offset;
}
return self.read_idx;
}
fn usz! ByteBuffer.available(&self) @inline @dynamic
{
return self.write_idx - self.read_idx;
}
fn void! ByteBuffer.grow(&self, usz n)
{
n = math::next_power_of_2(n);
char* p = self.allocator.realloc_aligned(self.bytes, n, .alignment = char.alignof)!;
self.bytes = p[:n];
}
macro ByteBuffer.shrink(&self)
{
if (self.read_idx >= self.max_read)
{
// Drop the read data besides the last byte (for pushback_byte).
usz readable = self.write_idx - self.read_idx;
self.bytes[:1 + readable] = self.bytes[self.read_idx - 1:1 + readable];
self.write_idx = 1 + readable;
self.read_idx = 1;
}
}

View File

@@ -1,80 +1,68 @@
module std::io;
import std::math;
struct ByteReader
struct ByteReader (InStream)
{
char[] bytes;
usz index;
}
fn void ByteReader.init(ByteReader* reader, char[] bytes)
fn usz ByteReader.len(&self) @dynamic
{
*reader = { .bytes = bytes };
return self.bytes.len;
}
fn Stream ByteReader.as_stream(ByteReader* reader)
fn ByteReader* ByteReader.init(&self, char[] bytes)
{
return { .fns = &bytereader_interface, .data = reader };
*self = { .bytes = bytes };
return self;
}
fn usz! ByteReader.read(ByteReader* reader, char[] bytes)
fn usz! ByteReader.read(&self, char[] bytes) @dynamic
{
if (reader.index >= reader.bytes.len) return IoError.EOF!;
usz len = math::min(reader.bytes.len - reader.index, bytes.len);
if (self.index >= self.bytes.len) return IoError.EOF?;
usz len = min(self.bytes.len - self.index, bytes.len);
if (len == 0) return 0;
mem::copy(bytes.ptr, &reader.bytes[reader.index], len);
reader.index += len;
mem::copy(bytes.ptr, &self.bytes[self.index], len);
self.index += len;
return len;
}
fn char! ByteReader.read_byte(ByteReader* reader)
fn char! ByteReader.read_byte(&self) @dynamic
{
if (reader.index >= reader.bytes.len) return IoError.EOF!;
return reader.bytes[reader.index++];
if (self.index >= self.bytes.len) return IoError.EOF?;
return self.bytes[self.index++];
}
fn void! ByteReader.pushback_byte(ByteReader* reader)
fn void! ByteReader.pushback_byte(&self) @dynamic
{
if (!reader.index) return IoError.INVALID_PUSHBACK!;
reader.index--;
if (!self.index) return IoError.INVALID_PUSHBACK?;
self.index--;
}
fn usz! ByteReader.seek(ByteReader* reader, isz offset, Seek seek)
fn usz! ByteReader.seek(&self, isz offset, Seek seek) @dynamic
{
isz new_index;
switch (seek)
{
case SET: new_index = offset;
case CURSOR: new_index = reader.index + offset;
case END: new_index = reader.bytes.len + offset;
case CURSOR: new_index = self.index + offset;
case END: new_index = self.bytes.len + offset;
}
if (new_index < 0) return IoError.INVALID_POSITION!;
reader.index = new_index;
if (new_index < 0) return IoError.INVALID_POSITION?;
self.index = new_index;
return new_index;
}
fn usz! ByteReader.write_stream(ByteReader* reader, Stream* writer)
fn usz! ByteReader.write_to(&self, OutStream* writer) @dynamic
{
if (reader.index >= reader.bytes.len) return 0;
usz written = writer.write(reader.bytes[reader.index..])?;
reader.index += written;
assert(reader.index <= reader.bytes.len);
if (self.index >= self.bytes.len) return 0;
usz written = writer.write(self.bytes[self.index..])!;
self.index += written;
assert(self.index <= self.bytes.len);
return written;
}
fn usz ByteReader.available(ByteReader* reader)
fn usz! ByteReader.available(&self) @inline @dynamic
{
return math::max((isz)0, (isz)reader.bytes.len - reader.index);
}
StreamInterface bytereader_interface = {
.len_fn = fn (s) => ((ByteReader*)s.data).bytes.len,
.read_fn = fn (s, char[] bytes) => ((ByteReader*)s.data).read(bytes) @inline,
.read_byte_fn = fn (s) => ((ByteReader*)s.data).read_byte() @inline,
.pushback_byte_fn = fn (s) => ((ByteReader*)s.data).pushback_byte() @inline,
.seek_fn = fn (s, offset, seek) => ((ByteReader*)s.data).seek(offset, seek) @inline,
.write_stream_fn = fn (s, writer) => ((ByteReader*)s.data).write_stream(writer) @inline,
.available_fn = fn (s) => ((ByteReader*)s.data).available() @inline,
};
return max(0, self.bytes.len - self.index);
}

View File

@@ -1,6 +1,6 @@
module std::io;
struct ByteWriter
struct ByteWriter (OutStream)
{
char[] bytes;
usz index;
@@ -8,112 +8,105 @@ struct ByteWriter
}
/**
* @param [&inout] writer
* @param [&in] using
* @require writer.bytes.len == 0 "Init may not run on on already initialized data"
* @ensure using != null, index == 0
* @param [&inout] self
* @param [&inout] allocator
* @require self.bytes.len == 0 "Init may not run on on already initialized data"
* @ensure (bool)allocator, self.index == 0
**/
fn void ByteWriter.init(ByteWriter* writer, Allocator* using = mem::heap())
fn ByteWriter* ByteWriter.init_new(&self, Allocator* allocator = mem::heap())
{
*writer = { .bytes = {}, .allocator = using };
}
fn void ByteWriter.init_buffer(ByteWriter* writer, char[] data)
{
*writer = { .bytes = data, .allocator = null };
*self = { .bytes = {}, .allocator = allocator };
return self;
}
/**
* @param [&inout] writer
* @require writer.bytes.len == 0 "Init may not run on on already initialized data"
* @param [&inout] self
* @require self.bytes.len == 0 "Init may not run on on already initialized data"
* @ensure self.index == 0
**/
fn void ByteWriter.tinit(ByteWriter* writer)
fn ByteWriter* ByteWriter.init_temp(&self)
{
*writer = { .bytes = {}, .allocator = mem::temp() };
return self.init_new(mem::temp());
}
fn Stream ByteWriter.as_stream(ByteWriter* writer)
fn ByteWriter* ByteWriter.init_with_buffer(&self, char[] data)
{
return { .fns = &bytewriter_interface, .data = writer };
*self = { .bytes = data, .allocator = null };
return self;
}
fn void ByteWriter.destroy(ByteWriter* writer)
fn void! ByteWriter.destroy(&self) @dynamic
{
if (!writer.allocator) return;
if (void* ptr = writer.bytes.ptr) free(ptr, .using = writer.allocator);
*writer = { };
if (!self.allocator) return;
if (void* ptr = self.bytes.ptr) self.allocator.free(ptr);
*self = { };
}
fn String ByteWriter.as_str(ByteWriter* writer)
fn String ByteWriter.str_view(&self) @inline
{
return (String)writer.bytes[:writer.index];
return (String)self.bytes[:self.index];
}
fn void! ByteWriter.ensure_capacity(ByteWriter* writer, usz len) @inline
fn void! ByteWriter.ensure_capacity(&self, usz len) @inline
{
if (writer.bytes.len > len) return;
if (!writer.allocator) return IoError.OUT_OF_SPACE!;
if (self.bytes.len > len) return;
if (!self.allocator) return IoError.OUT_OF_SPACE?;
if (len < 16) len = 16;
usz new_capacity = math::next_power_of_2(len);
char* new_ptr = realloc_checked(writer.bytes.ptr, new_capacity, .using = writer.allocator)?;
writer.bytes = new_ptr[:new_capacity];
char* new_ptr = self.allocator.realloc_checked(self.bytes.ptr, new_capacity)!;
self.bytes = new_ptr[:new_capacity];
}
fn usz! ByteWriter.write(ByteWriter* writer, char[] bytes)
fn usz! ByteWriter.write(&self, char[] bytes) @dynamic
{
writer.ensure_capacity(writer.index + bytes.len)?;
mem::copy(&writer.bytes[writer.index], bytes.ptr, bytes.len);
writer.index += bytes.len;
self.ensure_capacity(self.index + bytes.len)!;
mem::copy(&self.bytes[self.index], bytes.ptr, bytes.len);
self.index += bytes.len;
return bytes.len;
}
fn void! ByteWriter.write_byte(ByteWriter* writer, char c)
fn void! ByteWriter.write_byte(&self, char c) @dynamic
{
writer.ensure_capacity(writer.index + 1)?;
writer.bytes[writer.index++] = c;
self.ensure_capacity(self.index + 1)!;
self.bytes[self.index++] = c;
}
/**
* @param [&inout] writer
* @param [&inout] reader
* @param [&inout] self
* @param reader
**/
fn usz! ByteWriter.read_from(ByteWriter* writer, Stream* reader)
fn usz! ByteWriter.read_from(&self, InStream* reader) @dynamic
{
if (reader.supports_available())
usz start_index = self.index;
if (&reader.available)
{
usz total_read = 0;
while (usz available = reader.available()?)
while (usz available = reader.available()!)
{
writer.ensure_capacity(writer.index + available)?;
usz len = reader.read(writer.bytes[writer.index..])?;
total_read += len;
writer.index += len;
self.ensure_capacity(self.index + available)!;
usz read = reader.read(self.bytes[self.index..])!;
self.index += read;
}
return total_read;
return self.index - start_index;
}
if (self.bytes.len == 0)
{
self.ensure_capacity(16)!;
}
usz total_read = 0;
while (true)
{
// See how much we can read.
usz len_to_read = writer.bytes.len - writer.index;
usz len_to_read = self.bytes.len - self.index;
// Less than 16 bytes? Double the capacity
if (len_to_read < 16)
{
writer.ensure_capacity(writer.bytes.len * 2)?;
self.ensure_capacity(self.bytes.len * 2)!;
len_to_read = self.bytes.len - self.index;
}
// Read into the rest of the buffer
usz read = reader.read(writer.bytes[writer.index..])?;
writer.index += read;
usz read = reader.read(self.bytes[self.index..])!;
self.index += read;
// Ok, we reached the end.
if (read < len_to_read) return total_read;
if (read < len_to_read) return self.index - start_index;
// Otherwise go another round
}
}
StreamInterface bytewriter_interface = {
.destroy_fn = fn (s) => ((ByteWriter*)s.data).destroy(),
.len_fn = fn (s) => ((ByteWriter*)s.data).bytes.len,
.write_fn = fn (s, char[] bytes) => ((ByteWriter*)s.data).write(bytes),
.write_byte_fn = fn (s, char c) => ((ByteWriter*)s.data).write_byte(c),
.read_stream_fn = fn (s, reader) => ((ByteWriter*)s.data).read_from(reader),
};

View File

@@ -1,14 +0,0 @@
module std::io;
fn Stream DString.as_stream(DString* dstring)
{
return { .fns = &dstring_interface, .data = dstring };
}
StreamInterface dstring_interface = {
.destroy_fn = fn (s) => ((DString*)s.data).free(),
.len_fn = fn (s) => ((DString*)s.data).len(),
.write_fn = fn (s, char[] bytes) { ((DString*)s.data).append_chars((String)bytes); return bytes.len; },
.write_byte_fn = fn (s, char c) => ((DString*)s.data).append_char(c),
.read_stream_fn = fn (s, reader) => ((DString*)s.data).read_from_stream(reader),
};

View File

@@ -1,18 +0,0 @@
module std::io;
fn Stream File.as_stream(File* file)
{
return { .fns = &filestream_interface, .data = file };
}
StreamInterface filestream_interface = {
.close_fn = fn (s) => ((File*)s.data).close(),
.seek_fn = fn (s, offset, seek) => ((File*)s.data).seek(offset, seek) @inline,
.read_fn = fn (s, char[] bytes) => ((File*)s.data).read(bytes) @inline,
.write_fn = fn (s, char[] bytes) => ((File*)s.data).write(bytes) @inline,
.write_byte_fn = fn (s, char c) => ((File*)s.data).putc(c) @inline,
.read_byte_fn = fn (s) => ((File*)s.data).getc() @inline,
.flush_fn = fn (s) => ((File*)s.data).flush() @inline,
};

View File

@@ -0,0 +1,44 @@
module std::io;
struct LimitReader (InStream)
{
InStream* wrapped_stream;
usz limit;
}
/**
* @param [&inout] wrapped_stream "The stream to read from"
* @param limit "The max limit to read"
**/
fn LimitReader* LimitReader.init(&self, InStream* wrapped_stream, usz limit)
{
*self = { .wrapped_stream = wrapped_stream, .limit = limit };
return self;
}
fn void! LimitReader.close(&self) @dynamic
{
if (&self.wrapped_stream.close) return self.wrapped_stream.close();
}
fn usz! LimitReader.read(&self, char[] bytes) @dynamic
{
if (self.limit == 0) return IoError.EOF?;
usz m = min(bytes.len, self.limit);
usz n = self.wrapped_stream.read(bytes[:m])!;
self.limit -= n;
return n;
}
fn char! LimitReader.read_byte(&self) @dynamic
{
if (self.limit == 0) return IoError.EOF?;
defer try self.limit--;
return self.wrapped_stream.read_byte();
}
fn usz! LimitReader.available(&self) @inline @dynamic
{
return self.limit;
}

View File

@@ -0,0 +1,124 @@
module std::io;
struct Scanner (InStream)
{
InStream* wrapped_stream;
char[] buf;
usz pattern_idx;
usz read_idx;
}
/**
* Scanner provides a way to read delimited data (with newlines as the default).
* The supplied buffer must be at least as large as the expected data length
* including its pattern.
*
* @param [&in] stream "The stream to read data from."
* @require buffer.len > 0 "Non-empty buffer required."
**/
fn void Scanner.init(&self, InStream* stream, char[] buffer)
{
*self = { .wrapped_stream = stream, .buf = buffer };
}
/**
* Return and clear any remaining unscanned data.
**/
fn char[] Scanner.flush(&self) @dynamic
{
assert(self.read_idx >= self.pattern_idx);
usz n = self.read_idx - self.pattern_idx;
char[] buf = self.buf[self.pattern_idx:n];
self.pattern_idx = 0;
self.read_idx = 0;
return buf;
}
fn void! Scanner.close(&self) @dynamic
{
if (&self.wrapped_stream.close) return self.wrapped_stream.close();
}
/**
* Scan the stream for the next split character and return data up to the match.
* @require pattern.len > 0 "Non-empty pattern required."
* @require self.buf.len > pattern.len "Pattern too large."
**/
fn char[]! Scanner.scan(&self, String pattern = "\n")
{
if (self.read_idx == 0)
{
// First read.
self.read_idx = self.refill(self.buf)!;
self.pattern_idx = 0;
}
assert(self.read_idx >= self.pattern_idx);
usz n = self.read_idx - self.pattern_idx;
char[] buf = self.buf[self.pattern_idx:n];
if (try i = self.find(buf, pattern))
{
self.pattern_idx += i + pattern.len;
return buf[:i];
}
if (self.pattern_idx == 0 || self.read_idx < self.buf.len)
{
// Split pattern not found with maximized search, abort.
// Split pattern not found and already read as much as possible.
return SearchResult.MISSING?;
}
// Split pattern not found: maximize the search and try one more time.
self.buf[:n] = buf[..];
self.pattern_idx = 0;
buf = self.buf[n..];
usz p = self.refill(buf)!;
self.read_idx = n + p;
buf = buf[:p];
usz i = self.find(buf, pattern)!;
self.pattern_idx = n + i + pattern.len;
return self.buf[:n + i];
}
macro usz! Scanner.find(&self, buf, pattern) @private
{
return ((String)buf).index_of(pattern);
}
macro usz! Scanner.refill(&self, buf) @private
{
usz! n = self.wrapped_stream.read(buf);
if (catch err = n)
{
case IoError.EOF:
return SearchResult.MISSING?;
default:
return err?;
}
return n;
}
fn usz! Scanner.read(&self, char[] bytes) @dynamic
{
usz n;
if (self.pattern_idx < self.read_idx)
{
n = min(bytes.len, self.read_idx - self.pattern_idx);
bytes[:n] = self.buf[self.pattern_idx:n];
self.pattern_idx += n;
bytes = bytes[n..];
}
n += self.wrapped_stream.read(bytes)!;
return n;
}
fn char! Scanner.read_byte(&self) @dynamic
{
if (self.pattern_idx < self.read_idx)
{
return self.buf[self.pattern_idx++];
}
return self.wrapped_stream.read_byte();
}

View File

@@ -11,14 +11,14 @@ const int RAND_MAX = 0x7fffffff;
struct DivResult
{
int quot;
int rem;
CInt quot;
CInt rem;
}
struct LongDivResult
{
long quot;
long rem;
CLong quot;
CLong rem;
}
fn Errno errno()
@@ -31,73 +31,200 @@ fn void errno_set(Errno e)
os::errno_set((int)e);
}
typedef TerminateFunction = fn void();
typedef CompareFunction = fn int(void*, void*);
typedef JmpBuf = uptr[$$JMP_BUF_SIZE];
distinct Errno = inline CInt;
def TerminateFunction = fn void();
def CompareFunction = fn int(void*, void*);
def JmpBuf = uptr[$$JMP_BUF_SIZE];
def Fd = CInt;
def Fpos_t = long; // TODO make sure fpos is correct on all targets.
def SignalFunction = fn void(CInt);
$if (env::COMPILER_LIBC_AVAILABLE)
const CInt SIGHUP = 1;
const CInt SIGINT = 2;
const CInt SIGQUIT = 3;
const CInt SIGILL = 4;
const CInt SIGTRAP = 5;
const CInt SIGABTR = 6;
const CInt SIGBUS = BSD_FLAVOR_SIG ? 10 : 7; // Or Mips
const CInt SIGFPE = 8;
const CInt SIGKILL = 9;
const CInt SIGSEGV = 11;
const CInt SIGSYS = BSD_FLAVOR_SIG ? 12 : 31;
const CInt SIGPIPE = 13;
const CInt SIGALRM = 14;
const CInt SIGTERM = 15;
const CInt SIGURG = BSD_FLAVOR_SIG ? 16 : 23;
const CInt SIGSTOP = BSD_FLAVOR_SIG ? 17 : 19;
const CInt SIGTSTP = BSD_FLAVOR_SIG ? 18 : 20;
const CInt SIGCONT = BSD_FLAVOR_SIG ? 19 : 18;
const CInt SIGCHLD = BSD_FLAVOR_SIG ? 20 : 17;
const bool BSD_FLAVOR_SIG @local = env::OPENBSD || env::DARWIN || env::FREEBSD || env::NETBSD;
def Time_t = $typefrom(env::WIN32 ? long.typeid : CLong.typeid);
def Off_t = $typefrom(env::WIN32 ? int.typeid : usz.typeid);
struct Timespec
{
Time_t tv_sec;
CLong tv_nsec;
}
module libc @if(env::LIBC);
extern fn void abort();
extern fn CInt abs(CInt n);
extern fn ZString asctime(Tm* timeptr);
extern fn ZString asctime_r(Tm* timeptr, char* buf);
extern fn CInt atexit(TerminateFunction func);
extern fn double atof(char* str);
extern fn int atoi(char* str);
extern fn CLongLong atoll(char* str);
extern fn double strtod(char* str, char** endptr);
extern fn CLong strtol(char* str, char** endptr, int base);
extern fn CULong stroul(char* str, char** endptr, int base);
extern fn void abort();
extern fn void atexit(TerminateFunction f);
extern fn void exit(int status);
extern fn ZString getenv(ZString name);
extern fn int setenv(ZString name, ZString value, int overwrite);
extern fn int unsetenv(ZString name);
extern fn int system(char* str);
extern fn void bsearch(void* key, void *base, usz items, usz size, CompareFunction compare);
extern fn void qsort(void* base, usz items, usz size, CompareFunction compare);
extern fn DivResult div(int numer, int denom);
extern fn long labs(long x);
extern fn LongDivResult ldiv(long number, long denom);
extern fn int rand();
extern fn void srand(uint seed);
extern fn void* calloc(usz count, usz size);
extern fn void clearerr(CFile stream);
extern fn Clock_t clock();
extern fn CInt close(CInt fd) @if(!env::WIN32);
extern fn double difftime(Time_t time1, Time_t time2) @if(!env::WIN32);
extern fn DivResult div(CInt numer, CInt denom);
extern fn void exit(CInt status);
extern fn CInt fclose(CFile stream);
extern fn CFile fdopen(CInt fd, ZString mode) @if(!env::WIN32);
extern fn CInt feof(CFile stream);
extern fn CInt ferror(CFile stream);
extern fn CInt fflush(CFile stream);
extern fn CInt fgetc(CFile stream);
extern fn ZString fgets(char* string, CInt n, CFile stream);
extern fn CInt fgetpos(CFile stream, Fpos_t* pos);
extern fn Fd fileno(CFile stream) @if(!env::WIN32);
extern fn CFile fopen(ZString filename, ZString mode);
extern fn CInt fprintf(CFile stream, ZString format, ...);
extern fn CInt fputc(CInt c, CFile stream);
extern fn CInt fputs(ZString string, CFile stream);
extern fn usz fread(void* ptr, usz size, usz nmemb, CFile stream);
extern fn void* free(void*);
extern fn CFile freopen(ZString filename, ZString mode, CFile stream);
extern fn CInt fscanf(CFile stream, ZString format, ...);
extern fn CInt fseek(CFile stream, SeekIndex offset, CInt whence) @if(!env::WIN32);
extern fn CInt fsetpos(CFile stream, Fpos_t* pos);
extern fn SeekIndex ftell(CFile stream) @if(!env::WIN32);
extern fn usz fwrite(void* ptr, usz size, usz nmemb, CFile stream);
extern fn CInt getc(CFile stream);
extern fn CInt getchar();
extern fn ZString getenv(ZString name);
extern fn ZString gets(char* buffer);
extern fn Tm* gmtime(Time_t* timer);
extern fn Tm* gmtime_r(Time_t *timer, Tm* buf) @if(!env::WIN32);
extern fn CLong labs(CLong x);
extern fn LongDivResult ldiv(CLong number, CLong denom);
extern fn Tm* localtime(Time_t* timer);
extern fn Tm* localtime_r(Time_t* timer, Tm* result) @if(!env::WIN32);
extern fn void longjmp(JmpBuf* buffer, CInt value);
$if (env::OS_TYPE == OsType.WIN32)
// TODO win32 aarch64
extern fn CInt _setjmp(void* frameptr, JmpBuf* buffer);
macro CInt setjmp(JmpBuf* buffer) => _setjmp($$frameaddress(), buffer);
$else
extern fn CInt setjmp(JmpBuf* buffer);
$endif
// MB functions omitted
// string
extern fn void* memchr(void* str, int c, usz n);
extern fn int memcmp(void* str1, void* str2, usz n);
extern fn void* malloc(usz size);
extern fn void* memchr(void* str, CInt c, usz n);
extern fn CInt memcmp(void* buf1, void* buf2, usz count);
extern fn void* memcpy(void* dest, void* src, usz n);
extern fn void* memmove(void* dest, void* src, usz n);
extern fn void* memset(void* dest, CInt value, usz n);
extern fn char* strcat(char* dest, char* src);
extern fn char* strncat(char* dest, char* src, usz n);
extern fn char* strchr(char* str, int c);
extern fn int strcmp(char* str1, char* str2);
extern fn int strncmp(char* str1, char* str2, usz n);
extern fn int strcoll(char* str1, char* str2);
extern fn char* strcpy(char* dst, char* src);
extern fn char* strncpy(char* dst, char* src, usz n);
extern fn usz strcspn(char* str1, char* str2);
extern fn char* strerror(int errn);
extern fn usz strlen(char* str);
extern fn char* strpbrk(char* str1, char* str2);
extern fn usz strspn(char* str1, char* str2);
extern fn char* strstr(char* haystack, char* needle);
extern fn char* strtok(char* str, char* delim);
extern fn usz strxfrm(char* dest, char* src, usz n);
// malloc
extern fn void* malloc(usz size);
extern fn void* calloc(usz count, usz size);
extern fn void* free(void*);
extern fn Time_t* mktime(Tm* time) @if(!env::WIN32);
extern fn void perror(ZString string);
extern fn CInt printf(ZString format, ...);
extern fn CInt putc(CInt c, CFile stream);
extern fn CInt putchar(CInt c);
extern fn CInt puts(ZString str);
extern fn void qsort(void* base, usz items, usz size, CompareFunction compare);
extern fn CInt raise(CInt signal);
extern fn CInt rand();
extern fn isz read(Fd fd, void* buf, usz nbyte) @if(!env::WIN32);
extern fn void* realloc(void* ptr, usz size);
extern fn CInt remove(ZString filename);
extern fn CInt rename(ZString old_name, ZString new_name);
extern fn void rewind(CFile stream);
extern fn CInt scanf(ZString format, ...);
extern fn void setbuf(CFile stream, char* buffer);
extern fn int setenv(ZString name, ZString value, CInt overwrite);
extern fn CInt setjmp(JmpBuf* buffer) @if(!env::WIN32);
extern fn void setvbuf(CFile stream, char* buf, CInt type, usz size);
extern fn SignalFunction signal(CInt sig, SignalFunction function);
extern fn CInt snprintf(char* buffer, usz size, ZString format, ...);
extern fn CInt sprintf(char* buffer, ZString format, ...);
extern fn void srand(uint seed);
extern fn CInt sscanf(char* buffer, ZString format, ...);
extern fn ZString strcat(ZString dest, ZString src);
extern fn char* strchr(char* str, CInt c);
extern fn CInt strcmp(ZString str1, ZString str2);
extern fn CInt strcoll(ZString str1, ZString str2);
extern fn usz strcspn(ZString str1, ZString str2);
extern fn ZString strcpy(ZString dst, ZString src);
extern fn ZString strerror(CInt errn);
extern fn usz strftime(char* dest, usz maxsize, ZString format, Tm* timeptr);
extern fn usz strlen(ZString str);
extern fn ZString strncat(char* dest, char* src, usz n);
extern fn CInt strncmp(char* str1, char* str2, usz n);
extern fn char* strncpy(char* dst, char* src, usz n);
extern fn CULong stroul(char* str, char** endptr, int base);
extern fn char* strpbrk(ZString str1, ZString str2);
extern fn usz strspn(ZString str1, ZString str2);
extern fn ZString strptime(char* buf, ZString format, Tm* tm);
extern fn char* strrchr(ZString str, CInt c);
extern fn char* strstr(ZString haystack, ZString needle);
extern fn double strtod(char* str, char** endptr);
extern fn float strtof(char* str, char** endptr);
extern fn ZString strtok(ZString str, ZString delim);
extern fn CLong strtol(char* str, char** endptr, CInt base);
extern fn CULong strtul(char* str, char** endptr, CInt base);
extern fn usz strxfrm(char* dest, ZString src, usz n);
extern fn CInt system(ZString str);
extern fn Time_t timegm(Tm *timeptr) @if(!env::WIN32);
extern fn ZString tmpnam(ZString str);
extern fn CInt ungetc(CInt c, CFile stream);
extern fn CInt unsetenv(ZString name);
extern fn isz write(Fd fd, void* buffer, usz count) @if(!env::WIN32);
$else
extern fn CFile fmemopen(void* ptr, usz size, ZString mode);
extern fn isz getline(char** linep, usz* linecapp, CFile stream);
extern fn int timespec_get(TimeSpec* ts, int base);
extern fn int nanosleep(TimeSpec* req, TimeSpec* remaining);
extern fn ZString ctime(Time_t *timer);
extern fn Time_t time(Time_t *timer);
const CInt STDIN_FD = 0;
const CInt STDOUT_FD = 1;
const CInt STDERR_FD = 2;
module libc @if(env::LINUX);
extern CFile __stdin @extern("stdin");
extern CFile __stdout @extern("stdout");
extern CFile __stderr @extern("stderr");
extern fn usz malloc_usable_size(void* ptr);
macro usz malloc_size(void* ptr) => malloc_usable_size(ptr);
extern fn void* aligned_alloc(usz align, usz size);
macro CFile stdin() => __stdin;
macro CFile stdout() => __stdout;
macro CFile stderr() => __stderr;
module libc @if(env::DARWIN);
extern CFile __stdinp;
extern CFile __stdoutp;
extern CFile __stderrp;
extern fn usz malloc_size(void* ptr);
extern fn void* aligned_alloc(usz align, usz size);
macro CFile stdin() => __stdinp;
macro CFile stdout() => __stdoutp;
macro CFile stderr() => __stderrp;
module libc @if(env::WIN32);
macro usz malloc_size(void* ptr) => _msize(ptr);
macro CFile stdin() => __acrt_iob_func(STDIN_FD);
macro CFile stdout() => __acrt_iob_func(STDOUT_FD);
macro CFile stderr() => __acrt_iob_func(STDERR_FD);
module libc @if(env::LIBC && !env::WIN32 && !env::LINUX && !env::DARWIN);
macro CFile stdin() { return (CFile*)(uptr)STDIN_FD; }
macro CFile stdout() { return (CFile*)(uptr)STDOUT_FD; }
macro CFile stderr() { return (CFile*)(uptr)STDERR_FD; }
module libc @if(!env::LIBC);
fn void longjmp(JmpBuf* buffer, CInt value) @weak @extern("longjmp") @nostrip
{
@@ -144,110 +271,6 @@ fn void* memset(void* dest, CInt value, usz n) @weak @extern("memset") @nostrip
return dest;
}
$endif
// stdio
typedef Fpos = long;
typedef CFile = void*;
$switch
$case env::COMPILER_LIBC_AVAILABLE && env::OS_TYPE == OsType.LINUX:
extern CFile __stdin @extern("stdin");
extern CFile __stdout @extern("stdout");
extern CFile __stderr @extern("stderr");
extern fn usz malloc_usable_size(void* ptr);
macro usz malloc_size(void* ptr) { return malloc_usable_size(ptr); }
extern fn void* aligned_alloc(usz align, usz size);
macro CFile stdin() { return __stdin; }
macro CFile stdout() { return __stdout; }
macro CFile stderr() { return __stderr; }
$case env::COMPILER_LIBC_AVAILABLE && env::OS_TYPE == OsType.MACOSX:
extern CFile __stdinp;
extern CFile __stdoutp;
extern CFile __stderrp;
extern fn usz malloc_size(void* ptr);
extern fn void* aligned_alloc(usz align, usz size);
macro CFile stdin() { return __stdinp; }
macro CFile stdout() { return __stdoutp; }
macro CFile stderr() { return __stderrp; }
$case env::COMPILER_LIBC_AVAILABLE && env::OS_TYPE == OsType.WIN32:
extern fn CFile __acrt_iob_func(CInt c);
extern fn usz _msize(void* ptr);
macro usz malloc_size(void* ptr) { return _msize(ptr); }
macro CFile stdin() { return __acrt_iob_func(0); }
macro CFile stdout() { return __acrt_iob_func(1); }
macro CFile stderr() { return __acrt_iob_func(2); }
$default:
macro CFile stdin() { return (CFile*)(uptr)0; }
macro CFile stdout() { return (CFile*)(uptr)1; }
macro CFile stderr() { return (CFile*)(uptr)2; }
$endswitch
const HAS_MALLOC_SIZE =
env::OS_TYPE == OsType.LINUX
|| env::OS_TYPE == OsType.WIN32
|| env::OS_TYPE == OsType.MACOSX;
// The following needs to be set per arch+os
// For now I have simply pulled the defaults from MacOS
const int SEEK_SET = 0;
const int SEEK_CUR = 1;
const int SEEK_END = 2;
const int _IOFBF = 0; // Fully buffered
const int _IOLBF = 1; // Line buffered
const int _IONBF = 2; // Unbuffered
const int BUFSIZ = 1024;
const int EOF = -1;
const int FOPEN_MAX = 20;
const int FILENAME_MAX = 1024;
typedef Errno = distinct CInt;
typedef SeekIndex = CLong;
$if (env::COMPILER_LIBC_AVAILABLE)
extern fn int fclose(CFile stream);
extern fn void clearerr(CFile stream);
extern fn int feof(CFile stream);
extern fn int ferror(CFile stream);
extern fn int fflush(CFile stream);
extern fn int fgetpos(CFile stream, Fpos* pos);
extern fn CFile fopen(ZString filename, ZString mode);
extern fn usz fread(void* ptr, usz size, usz nmemb, CFile stream);
extern fn CFile freopen(ZString filename, ZString mode, CFile stream);
extern fn CFile fmemopen(void* ptr, usz size, ZString mode);
extern fn int fseek(CFile stream, SeekIndex offset, int whence);
extern fn int fsetpos(CFile stream, Fpos* pos);
extern fn SeekIndex ftell(CFile stream);
extern fn usz fwrite(void* ptr, usz size, usz nmemb, CFile stream);
extern fn int remove(char* filename);
extern fn int rename(char* old_name, char* new_name);
extern fn void rewind(CFile stream);
extern fn void setbuf(CFile stream, char* buffer);
extern fn void setvbuf(CFile stream, char* buffer, int mode, usz size);
extern fn CFile tmpnam(char* str);
extern fn int fprintf(CFile stream, char* format, ...);
extern fn int printf(char* format, ...);
extern fn int sprintf(char* str, char* format, ...);
extern fn int snprintf(char* str, usz size, char* format, ...);
extern fn int fscanf(CFile stream, char* format, ...);
extern fn int scanf(char* format, ...);
extern fn int sscanf(char* str, char* format, ...);
extern fn int fgetc(CFile stream);
extern fn char* fgets(char* str, int n, CFile stream);
extern fn int fputc(int c, CFile stream);
extern fn int getc(CFile stream);
extern fn int getchar();
extern fn int putc(int c, CFile stream);
extern fn int putchar(int c);
extern fn int puts(char* str);
extern fn int ungetc(int c, CFile stream);
extern fn void perror(char* str);
extern fn isz getline(char** linep, usz* linecapp, CFile stream);
$else
fn int fseek(CFile stream, SeekIndex offset, int whence) @weak @extern("fseek") @nostrip
{
unreachable("'fseek' not available.");
@@ -315,150 +338,240 @@ fn int puts(ZString str) @weak @extern("puts") @nostrip
unreachable("'puts' not available.");
}
$endif
module libc;
// stdio
def CFile = void*;
const HAS_MALLOC_SIZE = env::LINUX || env::WIN32 || env::DARWIN;
// The following needs to be set per arch+os
// For now I have simply pulled the defaults from MacOS
const int SEEK_SET = 0;
const int SEEK_CUR = 1;
const int SEEK_END = 2;
const int _IOFBF = 0; // Fully buffered
const int _IOLBF = 1; // Line buffered
const int _IONBF = 2; // Unbuffered
const int BUFSIZ = 1024;
const int EOF = -1;
const int FOPEN_MAX = 20;
const int FILENAME_MAX = 1024;
const S_IFMT = 0o170000; // type of file mask
const S_IFIFO = 0o010000; // named pipe (fifo)
const S_IFCHR = 0o020000; // character special
const S_IFDIR = 0o040000; // directory
const S_IFBLK = 0o060000; // block special
const S_IFREG = 0o100000; // regular
const S_IFLNK = 0o120000; // symbolic link
const S_IFSOCK = 0o140000; // socket
const S_ISUID = 0o004000; // Set user id on execution
const S_ISGID = 0o002000; // Set group id on execution
const S_ISVTX = 0o001000; // Save swapped text even after use
const S_IRUSR = 0o000400; // Read permission, owner
const S_IWUSR = 0o000200; // Write permission, owner
const S_IXUSR = 0o000100; // Execute/search permission, owner
def SeekIndex = CLong;
// vsprintf vprintf not supported
// time.h
typedef TimeOffset = CLong;
struct Tm
{
int tm_sec; /* seconds after the minute [0-60] */
int tm_min; /* minutes after the hour [0-59] */
int tm_hour; /* hours since midnight [0-23] */
int tm_mday; /* day of the month [1-31] */
int tm_mon; /* months since January [0-11] */
int tm_year; /* years since 1900 */
int tm_wday; /* days since Sunday [0-6] */
int tm_yday; /* days since January 1 [0-365] */
int tm_isdst; /* Daylight Savings Time flag */
TimeOffset tm_gmtoff; /* offset from UTC in seconds */
char *tm_zone; /* timezone abbreviation */
CInt tm_sec; // seconds after the minute [0-60]
CInt tm_min; // minutes after the hour [0-59]
CInt tm_hour; // hours since midnight [0-23]
CInt tm_mday; // day of the month [1-31]
CInt tm_mon; // months since January [0-11]
CInt tm_year; // years since 1900
CInt tm_wday; // days since Sunday [0-6]
CInt tm_yday; // days since January 1 [0-365]
CInt tm_isdst; // Daylight Savings Time flag
TimeOffset tm_gmtoff @if(!env::WIN32); /* offset from UTC in seconds */
char *tm_zone @if(!env::WIN32); /* timezone abbreviation */
CInt tm_nsec @if(env::WASI);
}
struct TimeSpec
{
Time s;
ulong ns;
Time_t s;
ulong ns @if(env::WIN32);
CLong ns @if(!env::WIN32);
}
def Clock_t = int @if(env::WIN32);
def Clock_t = CULong @if(!env::WIN32);
def TimeOffset = int @if(env::WASI) ;
def TimeOffset = CLong @if(!env::WASI) ;
const int TIME_UTC = 1;
extern fn int timespec_get(TimeSpec* ts, int base);
extern fn int nanosleep(TimeSpec* req, TimeSpec* remaining);
// Likely wrong, must be per platform.
const CLOCKS_PER_SEC = 1000000;
// Time also needs to be per platform
typedef Time = long;
typedef Clock = ulong;
extern fn ZString asctime(Tm *timeptr);
extern fn Clock clock();
extern fn ZString ctime(Time *timer);
extern fn double difftime(Time time1, Time time2);
extern fn Tm* gmtime(Time *timer);
extern fn Tm* localtime(Time *timer);
extern fn Time mktime(Tm *timeptr);
extern fn usz strftime(char* str, usz maxsize, char* format, Tm *timeptr);
extern fn Time time(Time *timer);
// signal
typedef SignalFunction = fn void(int);
extern fn SignalFunction signal(int sig, SignalFunction function);
// Incomplete
module libc::errno;
const Errno OK = 0;
const Errno EPERM = 1; // Operation not permitted
const Errno ENOENT = 2; // No such file or directory
const Errno ESRCH = 3; // No such process
const Errno EINTR = 4; // Interrupted system call
const Errno EIO = 5; // I/O error
const Errno ENXIO = 6; // No such device or address
const Errno E2BIG = 7; // Argument list too long
const Errno ENOEXEC = 8; // Exec format error
const Errno EBADF = 9; // Bad file number
const Errno ECHILD = 10; // No child processes
const Errno OK = 0;
const Errno EPERM = 1; // Operation not permitted
const Errno ENOENT = 2; // No such file or directory
const Errno ESRCH = 3; // No such process
const Errno EINTR = 4; // Interrupted system call
const Errno EIO = 5; // I/O error
const Errno ENXIO = 6; // No such device or address
const Errno E2BIG = 7; // Argument list too long
const Errno ENOEXEC = 8; // Exec format error
const Errno EBADF = 9; // Bad file number
const Errno ECHILD = 10; // No child processes
$if (env::OS_TYPE == MACOSX)
const Errno EAGAIN = 35; // Try again Macos
$else
const Errno EAGAIN = 11; // Try again
$endif
const Errno ENOMEM = 12; // Out of memory
const Errno EACCES = 13; // Permission denied
const Errno EFAULT = 14; // Bad address
const Errno ENOTBLK = 15; // Block device required, not on Win32
const Errno EBUSY = 16; // Device or resource busy
const Errno EEXIST = 17; // File exists
const Errno EXDEV = 18; // Cross-device link
const Errno ENODEV = 19; // No such device
const Errno ENOTDIR = 20; // Not a directory
const Errno EISDIR = 21; // Is a directory
const Errno EINVAL = 22; // Invalid argument
const Errno ENFILE = 23; // File table overflow
const Errno EMFILE = 24; // Too many open files
const Errno ENOTTY = 25; // Not a typewriter
const Errno ETXTBSY = 26; // Text file busy, not on Win32
const Errno EFBIG = 27; // File too large
const Errno ENOSPC = 28; // No space left on device
const Errno ESPIPE = 29; // Illegal seek
const Errno EROFS = 30; // Read-only file system
const Errno EMLINK = 31; // Too many links
const Errno EPIPE = 32; // Broken pipe
const Errno EDOM = 33; // Math argument out of domain of func
const Errno ERANGE = 34; // Math result not representable
const Errno EAGAIN @if(env::DARWIN) = 35; // Try again Macos
const Errno EAGAIN @if(!env::DARWIN) = 11; // Try again
$switch (env::OS_TYPE)
const Errno ENOMEM = 12; // Out of memory
const Errno EACCES = 13; // Permission denied
const Errno EFAULT = 14; // Bad address
const Errno ENOTBLK = 15; // Block device required, not on Win32
const Errno EBUSY = 16; // Device or resource busy
const Errno EEXIST = 17; // File exists
const Errno EXDEV = 18; // Cross-device link
const Errno ENODEV = 19; // No such device
const Errno ENOTDIR = 20; // Not a directory
const Errno EISDIR = 21; // Is a directory
const Errno EINVAL = 22; // Invalid argument
const Errno ENFILE = 23; // File table overflow
const Errno EMFILE = 24; // Too many open files
const Errno ENOTTY = 25; // Not a typewriter
const Errno ETXTBSY = 26; // Text file busy, not on Win32
const Errno EFBIG = 27; // File too large
const Errno ENOSPC = 28; // No space left on device
const Errno ESPIPE = 29; // Illegal seek
const Errno EROFS = 30; // Read-only file system
const Errno EMLINK = 31; // Too many links
const Errno EPIPE = 32; // Broken pipe
const Errno EDOM = 33; // Math argument out of domain of func
const Errno ERANGE = 34; // Math result not representable
$case MACOSX:
const Errno EDEADLK = 11; // Resource deadlock would occur MacOS
const Errno ENAMETOOLONG = 63; // File name too long MacOS
const Errno ELOOP = 62; // Too many symbolic links encountered
const Errno EOVERFLOW = 84; // Value too large for defined data type Macos
const Errno ECONNRESET = 54; // Connection reset by peer Macos
const Errno ENETDOWN = 50; // Network is down MacOS
const Errno ENETUNREACH = 51; // Network is unreachable MacOS
const Errno ENETRESET = 52; // Network dropped connection because of reset MacOS
const Errno EOPNOTSUPP = 45; // Operation not supported on transport endpoint
// https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/intro.2.html
module libc::errno @if(env::DARWIN);
const Errno EWOULDBLOCK = EAGAIN; // Operation would block
const Errno EDEADLK = 11; // Resource deadlock would occur
const Errno EINPROGRESS = 36; // Operation now in progress
const Errno EALREADY = 37; // Operation already in progress
const Errno ENOTSOCK = 38; // Socket operation on non-socket
const Errno EDESTADDRREQ = 39; // Destination address required
const Errno EMSGSIZE = 40; // Message too long
const Errno EPROTOTYPE = 41; // Protocol wrong type for socket
const Errno ENOPROTOOPT = 42; // Protocol not available
const Errno EPROTONOSUPPORT = 43; // Protocol not supported
const Errno ESOCKTNOSUPPORT = 44; // Socket type not supported
const Errno ENOTSUP = 45; // Not supported
const Errno EPFNOSUPPORT = 46; // Protocol family not supported
const Errno EAFNOSUPPORT = 47; // Address family not supported by protocol family
const Errno EADDRINUSE = 48; // Address already in use
const Errno EADDRNOTAVAIL = 49; // Cannot assign requested address
const Errno ENETDOWN = 50; // Network is down
const Errno ENETUNREACH = 51; // Network is unreachable
const Errno ENETRESET = 52; // Network dropped connection on reset
const Errno ECONNABORTED = 53; // Software caused connection abort
const Errno ECONNRESET = 54; // Connection reset by peer
const Errno ENOBUFS = 55; // No buffer space available
const Errno EISCONN = 56; // Socket is already connected
const Errno ENOTCONN = 57; // Socket is not connected
const Errno ESHUTDOWN = 58; // Cannot send after socket shutdown
const Errno ETIMEDOUT = 60; // Operation timed out
const Errno ECONNREFUSED = 61; // Connection refused
const Errno ELOOP = 62; // Too many levels of symbolic links
const Errno ENAMETOOLONG = 63; // File name too long
const Errno EHOSTDOWN = 64; // Host is down
const Errno EHOSTUNREACH = 65; // No route to host
const Errno ENOTEMPTY = 66; // Directory not empty
const Errno EPROCLIM = 67; // Too many processes
const Errno EUSERS = 68; // Too many users
const Errno EDQUOT = 69; // Disc quota exceeded
const Errno ESTALE = 70; // Stale NFS file handle
const Errno EBADRPC = 72; // RPC struct is bad
const Errno ERPCMISMATCH = 73; // RPC version wrong
const Errno EPROGUNAVAIL = 74; // RPC prog. not avail
const Errno EPROGMISMATCH = 75; // Program version wrong
const Errno EPROCUNAVAIL = 76; // Bad procedure for program
const Errno ENOLCK = 77; // No locks available
const Errno ENOSYS = 78; // Function not implemented
const Errno EFTYPE = 79; // Inappropriate file type or format
const Errno EAUTH = 80; // Authentication error
const Errno ENEEDAUTH = 81; // Need authenticator
const Errno EPWROFF = 82; // Device power is off
const Errno EDEVERR = 83; // Device error
const Errno EOVERFLOW = 84; // Value too large to be stored in data type
const Errno EBADEXEC = 85; // Bad executable (or shared library)
const Errno EBADARCH = 86; // Bad CPU type in executable
const Errno ESHLIBVERS = 87; // Shared library version mismatch
const Errno EBADMACHO = 88; // Malformed Mach-o file
const Errno ECANCELED = 89; // Operation canceled
const Errno EIDRM = 90; // Identifier removed
const Errno ENOMSG = 91; // No message of desired type
const Errno EILSEQ = 92; // Illegal byte sequence
const Errno ENOATTR = 93; // Attribute not found
const Errno EBADMSG = 94; // Bad message
const Errno EMULTIHOP = 95; // Reserved
const Errno ENODATA = 96; // No message available
const Errno ENOLINK = 97; // Reserved
const Errno ENOSR = 98; // No STREAM resources
const Errno ENOSTR = 99; // Not a STREAM
const Errno EPROTO = 100; // Protocol error
const Errno ETIME = 101; // STREAM ioctl() timeout
const Errno EOPNOTSUPP = 102; // Operation not supported on socket
$case WIN32:
const Errno EDEADLK = 36; // Resource deadlock would occur Win32
const Errno ENAMETOOLONG = 38; // File name too long Win32
const Errno ELOOP = 114; // Too many symbolic links encountered
const Errno EOVERFLOW = 132; // Value too large for defined data type
const Errno ENETDOWN = 116; // Network is down
const Errno ECONNRESET = 108; // Connection reset by peer
const Errno ENETUNREACH = 118; // Network is unreachable
const Errno ENETRESET = 117; // Network dropped connection because of reset
const Errno EOPNOTSUPP = 130; // Operation not supported on transport endpoint
$default:
const Errno EDEADLK = 35; // Resource deadlock would occur Linux (others?)
const Errno ENAMETOOLONG = 36; // File name too long Linux (others?)
const Errno ELOOP = 40; // Too many symbolic links encountered
const Errno EOVERFLOW = 75; // Value too large for defined data type
const Errno ENETDOWN = 100; // Network is down
const Errno ECONNRESET = 104; // Connection reset by peer
const Errno ENETUNREACH = 101; // Network is unreachable
const Errno ENETRESET = 102; // Network dropped connection because of reset
const Errno EOPNOTSUPP = 95; // Operation not supported on transport endpoint
$endswitch
module libc::errno @if(env::WIN32);
const Errno EDEADLK = 36; // Resource deadlock would occur Win32
const Errno ENAMETOOLONG = 38; // File name too long Win32
const Errno ENOTEMPTY = 41; // Directory not empty
const Errno ELOOP = 114; // Too many symbolic links encountered
const Errno EOVERFLOW = 132; // Value too large for defined data type
const Errno ENETDOWN = 116; // Network is down
const Errno ECONNRESET = 108; // Connection reset by peer
const Errno ENETUNREACH = 118; // Network is unreachable
const Errno ENETRESET = 117; // Network dropped connection because of reset
const Errno EOPNOTSUPP = 130; // Operation not supported on transport endpoint
const Errno ETIMEDOUT = 138; // Connection timed out
const Errno EALREADY = 103; // Operation already in progress
const Errno EINPROGRESS = 112; // Operation now in progress Win32
const Errno EDQUOT = -122; // Quota exceeded, not in Win32
const Errno EWOULDBLOCK = 140; // Operation would block
module libc::errno @if(!env::WIN32 && !env::DARWIN);
const Errno EDEADLK = 35; // Resource deadlock would occur Linux (others?)
const Errno ENAMETOOLONG = 36; // File name too long Linux (others?)
const Errno ENOTEMPTY = 39; // Directory not empty
const Errno ELOOP = 40; // Too many symbolic links encountered
const Errno EWOULDBLOCK = EAGAIN; // Operation would block
const Errno EOVERFLOW = 75; // Value too large for defined data type
const Errno ENOTSOCK = 88; // Socket operation on non-socket
const Errno EOPNOTSUPP = 95; // Operation not supported on transport endpoint
const Errno EADDRINUSE = 98; // Address already in use
const Errno EADDRNOTAVAIL = 99; // Cannot assign requested address
const Errno ENETDOWN = 100; // Network is down
const Errno ENETUNREACH = 101; // Network is unreachable
const Errno ENETRESET = 102; // Network dropped connection because of reset
const Errno ECONNRESET = 104; // Connection reset by peer
const Errno EISCONN = 106; // Socket is already connected
const Errno ETIMEDOUT = 110; // Connection timed out
const Errno ECONNREFUSED = 111; // Connection refused
const Errno EALREADY = 114; // Operation already in progress
const Errno EINPROGRESS = 115; // Operation now in progress
const Errno EDQUOT = 122; // Quota exceeded
/*
const Errno ENOLCK = 37; /* No record locks available */
const Errno ENOSYS = 38; /* Function not implemented */
const Errno ENOTEMPTY = 39; /* Directory not empty */
const Errno ENOMSG = 42; /* No message of desired type */
const Errno EIDRM = 43; /* Identifier removed */
@@ -515,7 +628,6 @@ const Errno ESOCKTNOSUPPORT = 94; /* Socket type not supported */
const Errno EPFNOSUPPORT = 96; /* Protocol family not supported */
const Errno EAFNOSUPPORT = 97; /* Address family not supported by protocol */
const Errno EADDRINUSE = 98; /* Address already in use */
const Errno EADDRNOTAVAIL = 99; /* Cannot assign requested address */
const Errno ECONNABORTED = 103; /* Software caused connection abort */
const Errno ENOBUFS = 105; /* No buffer space available */
const Errno EISCONN = 106; /* Transport endpoint is already connected */
@@ -527,29 +639,6 @@ const Errno EHOSTDOWN = 112; /* Host is down */
const Errno EHOSTUNREACH = 113; /* No route to host */
*/
$switch (env::OS_TYPE)
$case MACOSX:
const Errno ETIMEDOUT = 60; // Connection timed out
const Errno EINPROGRESS = 36; // Operation now in progress MacOS
const Errno EALREADY = 37; // Operation already in progress MacOS
const Errno EDQUOT = 69; // Quota exceeded, MacOS
const Errno EWOULDBLOCK = 35; // Operation would block
$case WIN32:
const Errno ETIMEDOUT = 138; // Connection timed out
const Errno EALREADY = 103; // Operation already in progress
const Errno EINPROGRESS = 112; // Operation now in progress Win32
const Errno EDQUOT = -122; // Quota exceeded, not in Win32
const Errno EWOULDBLOCK = 140; // Operation would block
$default:
const Errno ETIMEDOUT = 110; // Connection timed out
const Errno EALREADY = 114; // Operation already in progress
const Errno EINPROGRESS = 115; // Operation now in progress
const Errno EDQUOT = 122; // Quota exceeded
const Errno EWOULDBLOCK = 41; // Operation would block
$endswitch
/*
const Errno ESTALE = 116; /* Stale NFS file handle */

View File

@@ -0,0 +1,22 @@
module libc;
import std::time;
/**
* @require self >= 0
**/
fn TimeSpec NanoDuration.to_timespec(self) @inline
{
CLong ns = (CLong)(self % 1000_000_000);
Time_t sec = (Time_t)(self / 1000_000_000);
return { .s = sec, .ns = ns };
}
/**
* @require self >= 0
**/
fn TimeSpec Duration.to_timespec(self) @inline
{
CLong ns = (CLong)(1000 * (self % time::SEC));
Time_t sec = (Time_t)(self / time::SEC);
return { .s = sec, .ns = ns };
}

35
lib/std/libc/os/darwin.c3 Normal file
View File

@@ -0,0 +1,35 @@
module libc @if(env::DARWIN);
def Dev_t = int;
def Mode_t = ushort;
def Nlink_t = ushort;
def Blkcnt_t = long;
def Blksize_t = int;
def Ino_t = ulong;
struct Stat
{
Dev_t st_dev;
Mode_t st_mode;
Nlink_t st_nlink;
Ino_t st_ino;
Uid_t st_uid;
Gid_t st_gid;
Dev_t st_rdev;
Timespec st_atimespec; // time of last access
Timespec st_mtimespec; // time of last data modification
Timespec st_ctimespec; // time of last status change
Timespec st_birthtimespec; // time of file creation(birth)
Off_t st_size; // file size, in bytes
Blkcnt_t st_blocks; // blocks allocated for file
Blksize_t st_blocksize; // optimal blocksize for I/O
uint st_flags; // user defined flags for file
uint st_gen; // file generation number
int st_lspare; // RESERVED
long[2] st_qspare; // RESERVED
}
extern fn int stat(ZString str, Stat* stat) @extern("stat64");
extern fn CInt sysctl(CInt *name, CUInt namelen, void *oldp, usz *oldlenp, void *newp, usz newlen);

View File

@@ -1,38 +1,28 @@
module libc::os;
// Linux
extern fn int* __errno_location() @if(env::LINUX);
macro int errno() @if(env::LINUX) => *__errno_location();
macro void errno_set(int err) @if(env::LINUX) => *(__errno_location()) = err;
$switch
// Darwin
extern fn int* __error() @if(env::DARWIN);
macro int errno() @if(env::DARWIN) => *__error();
macro void errno_set(int err) @if(env::DARWIN) => *(__error()) = err;
$case env::COMPILER_LIBC_AVAILABLE && env::OS_TYPE == LINUX:
extern fn int* __errno_location();
macro int errno() => *__errno_location();
macro void errno_set(int err) => *(__errno_location()) = err;
$case env::COMPILER_LIBC_AVAILABLE && env::OS_TYPE == MACOSX:
extern fn int* __error();
macro int errno() => *__error();
macro void errno_set(int err) => *(__error()) = err;
$case env::COMPILER_LIBC_AVAILABLE && env::OS_TYPE == WIN32:
macro int errno()
// Win32
macro int errno() @if(env::WIN32)
{
int holder;
_get_errno(&holder);
return holder;
}
macro void errno_set(int err) @if(env::WIN32) => _set_errno(err);
extern fn void _get_errno(int* result) @if(env::WIN32);
extern fn void _set_errno(int err) @if(env::WIN32);
macro void errno_set(int err) => _set_errno(err);
extern fn void _get_errno(int* result);
extern fn void _set_errno(int err);
$default:
tlocal int _errno_c3 = 0;
fn void errno_set(int err) => _errno_c3 = err;
fn int errno() => _errno_c3;
$endswitch
// Default
const ERRNO_DEFAULT @local = !env::LINUX && !env::DARWIN && !env::WIN32;
tlocal int _errno_c3 @if(ERRNO_DEFAULT) = 0;
fn void errno_set(int err) @if(ERRNO_DEFAULT) => _errno_c3 = err;
fn int errno() @if(ERRNO_DEFAULT) => _errno_c3;

62
lib/std/libc/os/linux.c3 Normal file
View File

@@ -0,0 +1,62 @@
module libc @if(env::LINUX);
// Checked for x86_64, this is notoriously incorrect when comparing with Rust code etc
def Blksize_t = $typefrom(env::X86_64 ? long.typeid : CInt.typeid);
def Nlink_t = $typefrom(env::X86_64 ? ulong.typeid : CUInt.typeid);
def Blkcnt_t = long;
def Ino_t = ulong;
def Dev_t = ulong;
def Mode_t = uint;
def Ino64_t = ulong;
def Blkcnt64_t = long;
struct Stat @if(env::X86_64)
{
Dev_t st_dev;
Ino_t st_ino;
Nlink_t st_nlink;
Mode_t st_mode;
Uid_t st_uid;
Gid_t st_gid;
CInt __pad0;
Dev_t st_rdev;
Off_t st_size;
Blksize_t st_blksize;
Blkcnt_t st_blocks;
Time_t st_atime;
long st_atime_nsec;
Time_t st_mtime;
long st_mtime_nsec;
Time_t st_ctime;
long st_ctime_nsec;
long[3] __unused;
}
struct Stat @if(!env::X86_64)
{
Dev_t st_dev;
Ino_t st_ino;
Mode_t st_mode;
Nlink_t st_nlink;
Uid_t st_uid;
Gid_t st_gid;
Dev_t st_rdev;
CInt __pad1;
Off_t st_size;
Blksize_t st_blksize;
CInt __pad2;
Blkcnt_t st_blocks;
Time_t st_atime;
long st_atime_nsec;
Time_t st_mtime;
long st_mtime_nsec;
Time_t st_ctime;
long st_ctime_nsec;
CInt[2] __unused;
}
extern fn CInt stat(ZString path, Stat* stat);
extern fn CInt get_nprocs();
extern fn CInt get_nprocs_conf();

55
lib/std/libc/os/posix.c3 Normal file
View File

@@ -0,0 +1,55 @@
module libc @if(env::POSIX);
extern fn void* dlopen(ZString path, int flags);
extern fn CInt dlclose(void*);
extern fn void* dlsym(void* handle, ZString symbol);
const int RTLD_LAZY = 0x1;
const int RTLD_NOW = 0x2;
const int RTLD_LOCAL = 0x4;
const int RTLD_GLOBAL = 0x8;
def Pid_t = int;
def Uid_t = uint;
def Gid_t = uint;
const CUInt SA_ONSTACK = env::LINUX ? 0x08000000 : 0x0001;
const CUInt SA_RESTART = env::LINUX ? 0x10000000 : 0x0002;
const CUInt SA_RESETHAND = env::LINUX ? 0x80000000 : 0x0004;
const CUInt SA_SIGINFO = env::LINUX ? 0x00000004 : 0x0040;
def Sigset_t = uint @if(!env::LINUX);
def Sigset_t = ulong[16] @if(env::LINUX);
def SigActionFunction = fn void(CInt, void*, void*);
struct Sigaction
{
union
{
SignalFunction sa_handler;
SigActionFunction sa_sigaction;
}
CInt sa_flags @if(env::FREEBSD);
Sigset_t sa_mask; // 128
CInt sa_flags @if(!env::FREEBSD);
void* sa_restorer @if(env::LINUX);
}
struct Stack_t
{
void* ss_sp;
struct @if(!env::LINUX)
{
usz ss_size;
CInt ss_flags;
}
struct @if(env::LINUX)
{
CInt ss_flags;
usz ss_size;
}
}
extern fn CInt sigaltstack(Stack_t* ss, Stack_t* old_ss);
extern fn CInt sigaction(CInt signum, Sigaction *action, Sigaction *oldaction);

51
lib/std/libc/os/win32.c3 Normal file
View File

@@ -0,0 +1,51 @@
module libc @if(env::WIN32);
extern fn CFile __acrt_iob_func(CInt c);
extern fn CInt _close(Fd fd); def close = _close;
extern fn double _difftime64(Time_t time1, Time_t time2); def difftime = _difftime64;
extern fn CFile _fdopen(Fd fd, ZString mode); def fdopen = _fdopen;
extern fn CInt _fileno(CFile stream); def fileno = _fileno;
extern fn CInt _fseeki64(CFile, long, int); def fseek = _fseeki64;
extern fn CLong _ftelli64(CFile); def ftell = _ftelli64;
extern fn Errno _get_timezone(CLong *timezone);
extern fn Tm* _gmtime64_s(Tm* buf, Time_t *timer);
extern fn Tm* _localtime64_s(Tm* buf, Time_t *timer);
extern fn Time_t _mkgmtime64(Tm* timeptr); def timegm = _mkgmtime64;
extern fn Time_t _mktime64(Tm *timeptr); def mktime = _mktime64;
extern fn usz _msize(void* ptr);
extern fn CInt _read(Fd fd, void* buffer, CUInt buffer_size);
extern fn CInt _setjmp(void* frameptr, JmpBuf* buffer) @if(env::WIN32);
extern fn CFile _wfopen(WString, WString);
extern fn CFile _wfreopen(WString, WString, CFile);
extern fn CInt _write(Fd fd, void* buffer, CUInt count);
extern fn CInt _wremove(WString);
struct SystemInfo
{
union {
uint dwOemId;
struct {
ushort wProcessorArchitecture;
ushort wReserved;
}
}
uint dwPageSize;
void* lpMinimumApplicationAddress;
void* lpMaximumApplicationAddress;
usz dwActiveProcessorMask;
uint dwNumberOfProcessors;
uint dwProcessorType;
uint dwAllocationGranularity;
ushort wProcessorLevel;
ushort wProcessorRevision;
}
extern fn CInt get_system_info(SystemInfo*) @extern("GetSystemInfo");
// Aliases to simplify libc use
macro Tm* localtime_r(Time_t* timer, Tm* buf) => _localtime64_s(buf, timer);
macro CInt setjmp(JmpBuf* buffer) => _setjmp($$frameaddress(0), buffer);
macro Tm* gmtime_r(Time_t* timer, Tm* buf) => _gmtime64_s(buf, timer);
macro isz read(Fd fd, void* buffer, usz buffer_size) => _read(fd, buffer, (CUInt)buffer_size);
macro isz write(Fd fd, void* buffer, usz count) => _write(fd, buffer, (CUInt)count);

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