mirror of
https://github.com/c3lang/c3c.git
synced 2026-02-27 12:01:16 +00:00
Compare commits
222 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fe6817f90d | ||
|
|
98a72007f8 | ||
|
|
87c1e09a7a | ||
|
|
e0fbe31f00 | ||
|
|
7d6c844b99 | ||
|
|
a03446a26d | ||
|
|
a7e77fec78 | ||
|
|
05c3fa1afd | ||
|
|
30c8435669 | ||
|
|
94497c968b | ||
|
|
281d4af464 | ||
|
|
cb2d0e798e | ||
|
|
da67cd4eb0 | ||
|
|
7d06ca6d35 | ||
|
|
6d45450130 | ||
|
|
27bbeaf79c | ||
|
|
3af5a537da | ||
|
|
6287e8dfbf | ||
|
|
1f49a5448e | ||
|
|
ece4a2b6fb | ||
|
|
e68bd0c57f | ||
|
|
eaeafb7299 | ||
|
|
44d736a537 | ||
|
|
122dbb3668 | ||
|
|
c2abbe2e2f | ||
|
|
3ccabd625c | ||
|
|
cfe6534c15 | ||
|
|
f5090eb158 | ||
|
|
d3db91536c | ||
|
|
9c42919e5a | ||
|
|
a6d33ec4af | ||
|
|
b03ae8bb17 | ||
|
|
59fd777198 | ||
|
|
d8286fa2a5 | ||
|
|
3345e70c63 | ||
|
|
12eea4a98d | ||
|
|
fdc20dc642 | ||
|
|
c5e3a1b2da | ||
|
|
35270fb0bf | ||
|
|
d782dad149 | ||
|
|
92aefb15f8 | ||
|
|
8342ac80d3 | ||
|
|
c71444e7a0 | ||
|
|
06e10bb69f | ||
|
|
fabd96552f | ||
|
|
2e99ae5ab9 | ||
|
|
8fea6ee8ab | ||
|
|
e6b10ee00c | ||
|
|
6aff6d66de | ||
|
|
8035991ac3 | ||
|
|
c0bd14cee7 | ||
|
|
3ba0beee96 | ||
|
|
8e6535f13c | ||
|
|
0d8f9520e9 | ||
|
|
3caaf0a3e8 | ||
|
|
a2206f1bcd | ||
|
|
7b5277d52c | ||
|
|
9f55a74d2e | ||
|
|
3eb8f68ded | ||
|
|
bd9bc118db | ||
|
|
95375a2591 | ||
|
|
b7115e9c70 | ||
|
|
078d9dc0b7 | ||
|
|
79c0c8e082 | ||
|
|
69b3263a00 | ||
|
|
cbd415881b | ||
|
|
6dbd81a6f9 | ||
|
|
e605a21fd3 | ||
|
|
d1349c9cfb | ||
|
|
c375aef9a3 | ||
|
|
3c1f692d49 | ||
|
|
29e20ee1be | ||
|
|
cf14787552 | ||
|
|
10241df23c | ||
|
|
8795ffc4f1 | ||
|
|
e25812a071 | ||
|
|
14a929588a | ||
|
|
02d1486af9 | ||
|
|
bab317282c | ||
|
|
a3a6319bcf | ||
|
|
17dfbb377e | ||
|
|
ff39f14dd1 | ||
|
|
af4309b286 | ||
|
|
176fb47c23 | ||
|
|
3a69c9f1fe | ||
|
|
944cc00d34 | ||
|
|
a751177a3e | ||
|
|
d291a40f69 | ||
|
|
cb006dd715 | ||
|
|
c7f09f2879 | ||
|
|
c0387221af | ||
|
|
0c7c5fbd7b | ||
|
|
fafcf3d0a9 | ||
|
|
b757f1447b | ||
|
|
bc3d9d761f | ||
|
|
10bc68fb39 | ||
|
|
de8aed9d96 | ||
|
|
1080303768 | ||
|
|
0503e15e31 | ||
|
|
ca2fabc9f9 | ||
|
|
0178a44b3c | ||
|
|
8f3cb9c6e9 | ||
|
|
c339278ff7 | ||
|
|
47316dac59 | ||
|
|
90d3f429aa | ||
|
|
239d249f01 | ||
|
|
7312c10b9e | ||
|
|
3c6e6f1965 | ||
|
|
28b9be64ee | ||
|
|
d2cae909e1 | ||
|
|
e194081e21 | ||
|
|
04cc34f12e | ||
|
|
f7143c1852 | ||
|
|
1781e97f02 | ||
|
|
c17cb7d0ca | ||
|
|
21343baa75 | ||
|
|
58c59361ea | ||
|
|
cb17cfff7d | ||
|
|
1634217fc4 | ||
|
|
bc9b0900a5 | ||
|
|
f43a7540c5 | ||
|
|
410a25f334 | ||
|
|
35c04cdc36 | ||
|
|
3e641ab82b | ||
|
|
7972397c65 | ||
|
|
9bf933ae31 | ||
|
|
a69ee59b82 | ||
|
|
961aa0ef61 | ||
|
|
48318c3ad4 | ||
|
|
a004cd3d03 | ||
|
|
768ce6092d | ||
|
|
e4e499edd2 | ||
|
|
f36e9fea48 | ||
|
|
d5eec296a0 | ||
|
|
e2e2ca1d7f | ||
|
|
6ab7198f2f | ||
|
|
2e1f7c95ce | ||
|
|
a2ef63f5b6 | ||
|
|
b7c9a4e2e9 | ||
|
|
28ffb864a3 | ||
|
|
18b4ce4e7d | ||
|
|
551ce34b9b | ||
|
|
de09a19a48 | ||
|
|
ba55946c9a | ||
|
|
33ab18033a | ||
|
|
96127d4ff3 | ||
|
|
43163fe2a0 | ||
|
|
a52b30c951 | ||
|
|
7d6a864d56 | ||
|
|
eeab73df4e | ||
|
|
db45abdfc7 | ||
|
|
cb32441533 | ||
|
|
643aa47e99 | ||
|
|
5e1bf75621 | ||
|
|
7c8e3dd4fd | ||
|
|
ad02fad167 | ||
|
|
261184b5c1 | ||
|
|
d07da2804e | ||
|
|
702b63ddb7 | ||
|
|
e35dbd29fb | ||
|
|
b52ab886d2 | ||
|
|
4b95d6be4c | ||
|
|
34b0b6f8f9 | ||
|
|
4fea202e6d | ||
|
|
8cfdb76869 | ||
|
|
858f8d2405 | ||
|
|
67aa18c1aa | ||
|
|
31b15c775e | ||
|
|
bf7e7e2397 | ||
|
|
eb8fb8871f | ||
|
|
85dc9c45ab | ||
|
|
076ef187cb | ||
|
|
c8d39251a9 | ||
|
|
f5e6b697b8 | ||
|
|
e8e88c1920 | ||
|
|
82a58e1c66 | ||
|
|
8e8d0436ad | ||
|
|
db99de9717 | ||
|
|
fd9fbe26a1 | ||
|
|
582453cb45 | ||
|
|
b73a44ec7d | ||
|
|
be98a01ed8 | ||
|
|
625a6d987d | ||
|
|
2597f6217e | ||
|
|
0470f3be8e | ||
|
|
1d25197bfd | ||
|
|
aae873c044 | ||
|
|
6471728ee5 | ||
|
|
29bae1fbd6 | ||
|
|
9c770f360e | ||
|
|
3b6d68ef21 | ||
|
|
24c03f9800 | ||
|
|
ed61b51489 | ||
|
|
0205ee8688 | ||
|
|
abd3585c44 | ||
|
|
aa910a1c44 | ||
|
|
00b88a8027 | ||
|
|
229fdd6193 | ||
|
|
5292e08cd6 | ||
|
|
90990ed2f3 | ||
|
|
c99284103d | ||
|
|
b463358add | ||
|
|
0e10b71cbf | ||
|
|
cb2d8133e0 | ||
|
|
f2d27229d2 | ||
|
|
604661b12c | ||
|
|
440df8415e | ||
|
|
c31c423386 | ||
|
|
8358af2240 | ||
|
|
4625b457fb | ||
|
|
151a28a92a | ||
|
|
9fe6c77d28 | ||
|
|
1c4f7a4b61 | ||
|
|
f23bbb342c | ||
|
|
91b866c967 | ||
|
|
483fe62750 | ||
|
|
2a47cc2ca9 | ||
|
|
e707190539 | ||
|
|
cb62554a26 | ||
|
|
9c58db99af | ||
|
|
90c339ebdb | ||
|
|
e3a8a3ec02 |
35
.github/workflows/main.yml
vendored
35
.github/workflows/main.yml
vendored
@@ -8,11 +8,11 @@ on:
|
||||
|
||||
env:
|
||||
LLVM_RELEASE_VERSION_WINDOWS: 18
|
||||
LLVM_RELEASE_VERSION_MAC: 17
|
||||
LLVM_RELEASE_VERSION_LINUX: 17
|
||||
LLVM_RELEASE_VERSION_MAC: 18
|
||||
LLVM_RELEASE_VERSION_LINUX: 19
|
||||
LLVM_RELEASE_VERSION_OPENBSD: 19
|
||||
LLVM_RELEASE_VERSION_UBUNTU22: 17
|
||||
LLVM_DEV_VERSION: 21
|
||||
LLVM_RELEASE_VERSION_UBUNTU22: 19
|
||||
LLVM_DEV_VERSION: 22
|
||||
jobs:
|
||||
|
||||
build-msvc:
|
||||
@@ -88,16 +88,16 @@ jobs:
|
||||
..\build\${{ matrix.build_type }}\c3c.exe compile --lib raylib55 --print-linking examples\raylib\raylib_snake.c3
|
||||
..\build\${{ matrix.build_type }}\c3c.exe compile --lib raylib55 --print-linking examples\raylib\raylib_tetris.c3
|
||||
|
||||
- name: run compiler tests
|
||||
run: |
|
||||
cd test
|
||||
..\build\${{ matrix.build_type }}\c3c.exe compile-run -O1 src/test_suite_runner.c3 -- ..\build\${{ matrix.build_type }}\c3c.exe test_suite/
|
||||
|
||||
- name: Compile run unit tests
|
||||
run: |
|
||||
cd test
|
||||
..\build\${{ matrix.build_type }}\c3c.exe compile-test unit -O1 -D SLOW_TESTS
|
||||
|
||||
- name: run compiler tests
|
||||
run: |
|
||||
cd test
|
||||
..\build\${{ matrix.build_type }}\c3c.exe compile-run -O1 src/test_suite_runner.c3 -- ..\build\${{ matrix.build_type }}\c3c.exe test_suite/ --no-terminal
|
||||
|
||||
- name: Test python script
|
||||
run: |
|
||||
py msvc_build_libraries.py --accept-license
|
||||
@@ -230,13 +230,14 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
build_type: [Release, Debug]
|
||||
llvm_version: [17, 18, 19, 20]
|
||||
llvm_version: [17, 18, 19, 20, 21, 22]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install common deps
|
||||
run: |
|
||||
sudo apt-get install zlib1g zlib1g-dev python3 ninja-build curl
|
||||
sudo apt-get update
|
||||
sudo apt-get install zlib1g zlib1g-dev python3 ninja-build curl libcurl4-openssl-dev
|
||||
|
||||
- name: Install Clang ${{matrix.llvm_version}}
|
||||
run: |
|
||||
@@ -381,6 +382,10 @@ jobs:
|
||||
./build/c3c init myproject
|
||||
ls myproject
|
||||
|
||||
- name: Vendor-fetch
|
||||
run: |
|
||||
./build/c3c vendor-fetch raylib55
|
||||
|
||||
- name: run compiler tests
|
||||
run: |
|
||||
cd test
|
||||
@@ -416,7 +421,8 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install common deps
|
||||
run: |
|
||||
sudo apt-get install zlib1g zlib1g-dev python3 ninja-build curl
|
||||
sudo apt-get update
|
||||
sudo apt-get install zlib1g zlib1g-dev python3 ninja-build curl libcurl4-openssl-dev
|
||||
|
||||
- name: Install Clang ${{matrix.llvm_version}}
|
||||
run: |
|
||||
@@ -460,6 +466,7 @@ jobs:
|
||||
-DLLVM_ENABLE_LIBXML2=OFF \
|
||||
-DC3_LLVM_VERSION=${{matrix.llvm_version}}.1
|
||||
cmake --build build
|
||||
|
||||
- name: Compile and run some examples
|
||||
run: |
|
||||
cd resources
|
||||
@@ -506,6 +513,10 @@ jobs:
|
||||
cd resources/testproject
|
||||
../../build/c3c run -vvv --linker=builtin --trust=full
|
||||
|
||||
- name: Vendor-fetch
|
||||
run: |
|
||||
./build/c3c vendor-fetch raylib55
|
||||
|
||||
- name: run compiler tests
|
||||
run: |
|
||||
cd test
|
||||
|
||||
10
.gitignore
vendored
10
.gitignore
vendored
@@ -1,5 +1,7 @@
|
||||
# Prerequisites
|
||||
*.d
|
||||
testrun
|
||||
benchmarkrun
|
||||
|
||||
# Object files
|
||||
*.o
|
||||
@@ -7,6 +9,8 @@
|
||||
*.obj
|
||||
*.elf
|
||||
*.ll
|
||||
*.wasm
|
||||
*.s
|
||||
|
||||
# Linker output
|
||||
*.ilk
|
||||
@@ -76,8 +80,10 @@ TAGS
|
||||
/.cache/
|
||||
/compile_commands.json
|
||||
|
||||
# 'nix build' resulting symlink
|
||||
# Nix
|
||||
result
|
||||
/.envrc
|
||||
/.direnv/
|
||||
|
||||
# macOS
|
||||
.DS_Store
|
||||
@@ -85,4 +91,4 @@ result
|
||||
# tests
|
||||
/test/tmp/*
|
||||
/test/testrun
|
||||
/test/test_suite_runner
|
||||
/test/test_suite_runner
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
cmake_minimum_required(VERSION 3.20)
|
||||
|
||||
set(C3_LLVM_MIN_VERSION 17)
|
||||
set(C3_LLVM_MAX_VERSION 21)
|
||||
set(C3_LLVM_MAX_VERSION 22)
|
||||
set(C3_LLVM_DEFAULT_VERSION 19)
|
||||
|
||||
if (CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_CURRENT_BINARY_DIR)
|
||||
@@ -70,15 +70,16 @@ else()
|
||||
endif()
|
||||
|
||||
# Options
|
||||
set(C3_LINK_DYNAMIC OFF CACHE BOOL "Link dynamically with LLVM/LLD libs")
|
||||
set(C3_WITH_LLVM ON CACHE BOOL "Build with LLVM")
|
||||
set(C3_LLVM_VERSION "auto" CACHE STRING "Use LLVM version [default: auto]")
|
||||
set(C3_USE_MIMALLOC OFF CACHE BOOL "Use built-in mimalloc")
|
||||
set(C3_MIMALLOC_TAG "v1.7.3" CACHE STRING "Used version of mimalloc")
|
||||
set(C3_USE_TB OFF CACHE BOOL "Use TB")
|
||||
set(C3_LLD_DIR "" CACHE STRING "Use custom LLD directory")
|
||||
set(C3_ENABLE_CLANGD_LSP OFF CACHE BOOL "Enable/Disable output of compile commands during generation")
|
||||
set(LLVM_CRT_LIBRARY_DIR "" CACHE STRING "Use custom llvm's compiler-rt directory")
|
||||
set(C3_LINK_DYNAMIC OFF CACHE BOOL "Link dynamically with LLVM/LLD libs")
|
||||
set(C3_WITH_LLVM ON CACHE BOOL "Build with LLVM")
|
||||
set(C3_LLVM_VERSION "auto" CACHE STRING "Use LLVM version [default: auto]")
|
||||
set(C3_USE_MIMALLOC OFF CACHE BOOL "Use built-in mimalloc")
|
||||
set(C3_MIMALLOC_TAG "v1.7.3" CACHE STRING "Used version of mimalloc")
|
||||
set(C3_USE_TB OFF CACHE BOOL "Use TB")
|
||||
set(C3_LLD_DIR "" CACHE STRING "Use custom LLD directory")
|
||||
set(C3_ENABLE_CLANGD_LSP OFF CACHE BOOL "Enable/Disable output of compile commands during generation")
|
||||
set(LLVM_CRT_LIBRARY_DIR "" CACHE STRING "Use custom llvm's compiler-rt directory")
|
||||
set(TCC_LIB_PATH "/usr/lib/tcc/libtcc1.a" CACHE STRING "Use custom libtcc1.a path")
|
||||
|
||||
set(C3_OPTIONS
|
||||
C3_LINK_DYNAMIC
|
||||
@@ -390,6 +391,7 @@ add_executable(c3c
|
||||
src/utils/unzipper.c
|
||||
src/compiler/c_codegen.c
|
||||
src/compiler/decltable.c
|
||||
src/compiler/methodtable.c
|
||||
src/compiler/mac_support.c
|
||||
src/compiler/windows_support.c
|
||||
src/compiler/codegen_asm.c
|
||||
@@ -574,6 +576,17 @@ else()
|
||||
target_link_options(c3c PRIVATE -pthread)
|
||||
endif()
|
||||
|
||||
if(CMAKE_C_COMPILER_ID STREQUAL "TinyCC")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wl,--no-eh-frame-hdr -z noexecstack")
|
||||
|
||||
# Link the static tcc runtime archive if it exists
|
||||
if(EXISTS "${TCC_LIB_PATH}")
|
||||
target_link_libraries(c3c "${TCC_LIB_PATH}")
|
||||
else()
|
||||
message(FATAL_ERROR "TCC runtime not found at ${TCC_LIB_PATH}; Ensure the path is correct.")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
install(TARGETS c3c DESTINATION bin)
|
||||
install(DIRECTORY lib/ DESTINATION lib/c3)
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@ further defined and clarified by project maintainers.
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported by contacting the project team at . All
|
||||
reported by contacting the project team at info@c3-lang.org. All
|
||||
complaints will be reviewed and investigated and will result in a response that
|
||||
is deemed necessary and appropriate to the circumstances. The project team is
|
||||
obligated to maintain confidentiality with regard to the reporter of an incident.
|
||||
|
||||
179
LICENSE
179
LICENSE
@@ -1,165 +1,20 @@
|
||||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
Copyright (c) 2022-2025 Christoffer Lernö and contributors
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
This version of the GNU Lesser General Public License incorporates
|
||||
the terms and conditions of version 3 of the GNU General Public
|
||||
License, supplemented by the additional permissions listed below.
|
||||
|
||||
0. Additional Definitions.
|
||||
|
||||
As used herein, "this License" refers to version 3 of the GNU Lesser
|
||||
General Public License, and the "GNU GPL" refers to version 3 of the GNU
|
||||
General Public License.
|
||||
|
||||
"The Library" refers to a covered work governed by this License,
|
||||
other than an Application or a Combined Work as defined below.
|
||||
|
||||
An "Application" is any work that makes use of an interface provided
|
||||
by the Library, but which is not otherwise based on the Library.
|
||||
Defining a subclass of a class defined by the Library is deemed a mode
|
||||
of using an interface provided by the Library.
|
||||
|
||||
A "Combined Work" is a work produced by combining or linking an
|
||||
Application with the Library. The particular version of the Library
|
||||
with which the Combined Work was made is also called the "Linked
|
||||
Version".
|
||||
|
||||
The "Minimal Corresponding Source" for a Combined Work means the
|
||||
Corresponding Source for the Combined Work, excluding any source code
|
||||
for portions of the Combined Work that, considered in isolation, are
|
||||
based on the Application, and not on the Linked Version.
|
||||
|
||||
The "Corresponding Application Code" for a Combined Work means the
|
||||
object code and/or source code for the Application, including any data
|
||||
and utility programs needed for reproducing the Combined Work from the
|
||||
Application, but excluding the System Libraries of the Combined Work.
|
||||
|
||||
1. Exception to Section 3 of the GNU GPL.
|
||||
|
||||
You may convey a covered work under sections 3 and 4 of this License
|
||||
without being bound by section 3 of the GNU GPL.
|
||||
|
||||
2. Conveying Modified Versions.
|
||||
|
||||
If you modify a copy of the Library, and, in your modifications, a
|
||||
facility refers to a function or data to be supplied by an Application
|
||||
that uses the facility (other than as an argument passed when the
|
||||
facility is invoked), then you may convey a copy of the modified
|
||||
version:
|
||||
|
||||
a) under this License, provided that you make a good faith effort to
|
||||
ensure that, in the event an Application does not supply the
|
||||
function or data, the facility still operates, and performs
|
||||
whatever part of its purpose remains meaningful, or
|
||||
|
||||
b) under the GNU GPL, with none of the additional permissions of
|
||||
this License applicable to that copy.
|
||||
|
||||
3. Object Code Incorporating Material from Library Header Files.
|
||||
|
||||
The object code form of an Application may incorporate material from
|
||||
a header file that is part of the Library. You may convey such object
|
||||
code under terms of your choice, provided that, if the incorporated
|
||||
material is not limited to numerical parameters, data structure
|
||||
layouts and accessors, or small macros, inline functions and templates
|
||||
(ten or fewer lines in length), you do both of the following:
|
||||
|
||||
a) Give prominent notice with each copy of the object code that the
|
||||
Library is used in it and that the Library and its use are
|
||||
covered by this License.
|
||||
|
||||
b) Accompany the object code with a copy of the GNU GPL and this license
|
||||
document.
|
||||
|
||||
4. Combined Works.
|
||||
|
||||
You may convey a Combined Work under terms of your choice that,
|
||||
taken together, effectively do not restrict modification of the
|
||||
portions of the Library contained in the Combined Work and reverse
|
||||
engineering for debugging such modifications, if you also do each of
|
||||
the following:
|
||||
|
||||
a) Give prominent notice with each copy of the Combined Work that
|
||||
the Library is used in it and that the Library and its use are
|
||||
covered by this License.
|
||||
|
||||
b) Accompany the Combined Work with a copy of the GNU GPL and this license
|
||||
document.
|
||||
|
||||
c) For a Combined Work that displays copyright notices during
|
||||
execution, include the copyright notice for the Library among
|
||||
these notices, as well as a reference directing the user to the
|
||||
copies of the GNU GPL and this license document.
|
||||
|
||||
d) Do one of the following:
|
||||
|
||||
0) Convey the Minimal Corresponding Source under the terms of this
|
||||
License, and the Corresponding Application Code in a form
|
||||
suitable for, and under terms that permit, the user to
|
||||
recombine or relink the Application with a modified version of
|
||||
the Linked Version to produce a modified Combined Work, in the
|
||||
manner specified by section 6 of the GNU GPL for conveying
|
||||
Corresponding Source.
|
||||
|
||||
1) Use a suitable shared library mechanism for linking with the
|
||||
Library. A suitable mechanism is one that (a) uses at run time
|
||||
a copy of the Library already present on the user's computer
|
||||
system, and (b) will operate properly with a modified version
|
||||
of the Library that is interface-compatible with the Linked
|
||||
Version.
|
||||
|
||||
e) Provide Installation Information, but only if you would otherwise
|
||||
be required to provide such information under section 6 of the
|
||||
GNU GPL, and only to the extent that such information is
|
||||
necessary to install and execute a modified version of the
|
||||
Combined Work produced by recombining or relinking the
|
||||
Application with a modified version of the Linked Version. (If
|
||||
you use option 4d0, the Installation Information must accompany
|
||||
the Minimal Corresponding Source and Corresponding Application
|
||||
Code. If you use option 4d1, you must provide the Installation
|
||||
Information in the manner specified by section 6 of the GNU GPL
|
||||
for conveying Corresponding Source.)
|
||||
|
||||
5. Combined Libraries.
|
||||
|
||||
You may place library facilities that are a work based on the
|
||||
Library side by side in a single library together with other library
|
||||
facilities that are not Applications and are not covered by this
|
||||
License, and convey such a combined library under terms of your
|
||||
choice, if you do both of the following:
|
||||
|
||||
a) Accompany the combined library with a copy of the same work based
|
||||
on the Library, uncombined with any other library facilities,
|
||||
conveyed under the terms of this License.
|
||||
|
||||
b) Give prominent notice with the combined library that part of it
|
||||
is a work based on the Library, and explaining where to find the
|
||||
accompanying uncombined form of the same work.
|
||||
|
||||
6. Revised Versions of the GNU Lesser General Public License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions
|
||||
of the GNU Lesser General Public License from time to time. Such new
|
||||
versions will be similar in spirit to the present version, but may
|
||||
differ in detail to address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Library as you received it specifies that a certain numbered version
|
||||
of the GNU Lesser General Public License "or any later version"
|
||||
applies to it, you have the option of following the terms and
|
||||
conditions either of that published version or of any later version
|
||||
published by the Free Software Foundation. If the Library as you
|
||||
received it does not specify a version number of the GNU Lesser
|
||||
General Public License, you may choose any version of the GNU Lesser
|
||||
General Public License ever published by the Free Software Foundation.
|
||||
|
||||
If the Library as you received it specifies that a proxy can decide
|
||||
whether future versions of the GNU Lesser General Public License shall
|
||||
apply, that proxy's public statement of acceptance of any version is
|
||||
permanent authorization for you to choose that version for the
|
||||
Library.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 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.
|
||||
165
LICENSE_SRC
Normal file
165
LICENSE_SRC
Normal file
@@ -0,0 +1,165 @@
|
||||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
|
||||
This version of the GNU Lesser General Public License incorporates
|
||||
the terms and conditions of version 3 of the GNU General Public
|
||||
License, supplemented by the additional permissions listed below.
|
||||
|
||||
0. Additional Definitions.
|
||||
|
||||
As used herein, "this License" refers to version 3 of the GNU Lesser
|
||||
General Public License, and the "GNU GPL" refers to version 3 of the GNU
|
||||
General Public License.
|
||||
|
||||
"The Library" refers to a covered work governed by this License,
|
||||
other than an Application or a Combined Work as defined below.
|
||||
|
||||
An "Application" is any work that makes use of an interface provided
|
||||
by the Library, but which is not otherwise based on the Library.
|
||||
Defining a subclass of a class defined by the Library is deemed a mode
|
||||
of using an interface provided by the Library.
|
||||
|
||||
A "Combined Work" is a work produced by combining or linking an
|
||||
Application with the Library. The particular version of the Library
|
||||
with which the Combined Work was made is also called the "Linked
|
||||
Version".
|
||||
|
||||
The "Minimal Corresponding Source" for a Combined Work means the
|
||||
Corresponding Source for the Combined Work, excluding any source code
|
||||
for portions of the Combined Work that, considered in isolation, are
|
||||
based on the Application, and not on the Linked Version.
|
||||
|
||||
The "Corresponding Application Code" for a Combined Work means the
|
||||
object code and/or source code for the Application, including any data
|
||||
and utility programs needed for reproducing the Combined Work from the
|
||||
Application, but excluding the System Libraries of the Combined Work.
|
||||
|
||||
1. Exception to Section 3 of the GNU GPL.
|
||||
|
||||
You may convey a covered work under sections 3 and 4 of this License
|
||||
without being bound by section 3 of the GNU GPL.
|
||||
|
||||
2. Conveying Modified Versions.
|
||||
|
||||
If you modify a copy of the Library, and, in your modifications, a
|
||||
facility refers to a function or data to be supplied by an Application
|
||||
that uses the facility (other than as an argument passed when the
|
||||
facility is invoked), then you may convey a copy of the modified
|
||||
version:
|
||||
|
||||
a) under this License, provided that you make a good faith effort to
|
||||
ensure that, in the event an Application does not supply the
|
||||
function or data, the facility still operates, and performs
|
||||
whatever part of its purpose remains meaningful, or
|
||||
|
||||
b) under the GNU GPL, with none of the additional permissions of
|
||||
this License applicable to that copy.
|
||||
|
||||
3. Object Code Incorporating Material from Library Header Files.
|
||||
|
||||
The object code form of an Application may incorporate material from
|
||||
a header file that is part of the Library. You may convey such object
|
||||
code under terms of your choice, provided that, if the incorporated
|
||||
material is not limited to numerical parameters, data structure
|
||||
layouts and accessors, or small macros, inline functions and templates
|
||||
(ten or fewer lines in length), you do both of the following:
|
||||
|
||||
a) Give prominent notice with each copy of the object code that the
|
||||
Library is used in it and that the Library and its use are
|
||||
covered by this License.
|
||||
|
||||
b) Accompany the object code with a copy of the GNU GPL and this license
|
||||
document.
|
||||
|
||||
4. Combined Works.
|
||||
|
||||
You may convey a Combined Work under terms of your choice that,
|
||||
taken together, effectively do not restrict modification of the
|
||||
portions of the Library contained in the Combined Work and reverse
|
||||
engineering for debugging such modifications, if you also do each of
|
||||
the following:
|
||||
|
||||
a) Give prominent notice with each copy of the Combined Work that
|
||||
the Library is used in it and that the Library and its use are
|
||||
covered by this License.
|
||||
|
||||
b) Accompany the Combined Work with a copy of the GNU GPL and this license
|
||||
document.
|
||||
|
||||
c) For a Combined Work that displays copyright notices during
|
||||
execution, include the copyright notice for the Library among
|
||||
these notices, as well as a reference directing the user to the
|
||||
copies of the GNU GPL and this license document.
|
||||
|
||||
d) Do one of the following:
|
||||
|
||||
0) Convey the Minimal Corresponding Source under the terms of this
|
||||
License, and the Corresponding Application Code in a form
|
||||
suitable for, and under terms that permit, the user to
|
||||
recombine or relink the Application with a modified version of
|
||||
the Linked Version to produce a modified Combined Work, in the
|
||||
manner specified by section 6 of the GNU GPL for conveying
|
||||
Corresponding Source.
|
||||
|
||||
1) Use a suitable shared library mechanism for linking with the
|
||||
Library. A suitable mechanism is one that (a) uses at run time
|
||||
a copy of the Library already present on the user's computer
|
||||
system, and (b) will operate properly with a modified version
|
||||
of the Library that is interface-compatible with the Linked
|
||||
Version.
|
||||
|
||||
e) Provide Installation Information, but only if you would otherwise
|
||||
be required to provide such information under section 6 of the
|
||||
GNU GPL, and only to the extent that such information is
|
||||
necessary to install and execute a modified version of the
|
||||
Combined Work produced by recombining or relinking the
|
||||
Application with a modified version of the Linked Version. (If
|
||||
you use option 4d0, the Installation Information must accompany
|
||||
the Minimal Corresponding Source and Corresponding Application
|
||||
Code. If you use option 4d1, you must provide the Installation
|
||||
Information in the manner specified by section 6 of the GNU GPL
|
||||
for conveying Corresponding Source.)
|
||||
|
||||
5. Combined Libraries.
|
||||
|
||||
You may place library facilities that are a work based on the
|
||||
Library side by side in a single library together with other library
|
||||
facilities that are not Applications and are not covered by this
|
||||
License, and convey such a combined library under terms of your
|
||||
choice, if you do both of the following:
|
||||
|
||||
a) Accompany the combined library with a copy of the same work based
|
||||
on the Library, uncombined with any other library facilities,
|
||||
conveyed under the terms of this License.
|
||||
|
||||
b) Give prominent notice with the combined library that part of it
|
||||
is a work based on the Library, and explaining where to find the
|
||||
accompanying uncombined form of the same work.
|
||||
|
||||
6. Revised Versions of the GNU Lesser General Public License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions
|
||||
of the GNU Lesser General Public License from time to time. Such new
|
||||
versions will be similar in spirit to the present version, but may
|
||||
differ in detail to address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Library as you received it specifies that a certain numbered version
|
||||
of the GNU Lesser General Public License "or any later version"
|
||||
applies to it, you have the option of following the terms and
|
||||
conditions either of that published version or of any later version
|
||||
published by the Free Software Foundation. If the Library as you
|
||||
received it does not specify a version number of the GNU Lesser
|
||||
General Public License, you may choose any version of the GNU Lesser
|
||||
General Public License ever published by the Free Software Foundation.
|
||||
|
||||
If the Library as you received it specifies that a proxy can decide
|
||||
whether future versions of the GNU Lesser General Public License shall
|
||||
apply, that proxy's public statement of acceptance of any version is
|
||||
permanent authorization for you to choose that version for the
|
||||
Library.
|
||||
@@ -1,20 +0,0 @@
|
||||
Copyright (c) 2022 Christoffer Lernö and contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 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.
|
||||
158
README.md
158
README.md
@@ -142,7 +142,7 @@ fn void main()
|
||||
|
||||
### Current status
|
||||
|
||||
The current stable version of the compiler is **version 0.7.4**.
|
||||
The current stable version of the compiler is **version 0.7.6**.
|
||||
|
||||
The upcoming 0.7.x releases will focus on expanding the standard library,
|
||||
fixing bugs and improving compile time analysis.
|
||||
@@ -209,12 +209,43 @@ This installs the latest prerelease build, as opposed to the latest released ver
|
||||
3. If you don't have Visual Studio 17 installed you can either do so, or run the `msvc_build_libraries.py` Python script which will download the necessary files to compile on Windows.
|
||||
4. Run `c3c.exe`.
|
||||
|
||||
#### Installing on Windows with the install script
|
||||
|
||||
Open a PowerShell terminal (you may need to run it as an administrator) and run the following command:
|
||||
```bash
|
||||
iwr -useb https://raw.githubusercontent.com/c3lang/c3c/refs/heads/master/install/install.ps1 | iex
|
||||
```
|
||||
The script will inform you once the installation is successful and add the `~/.c3` directory to your PATH, which will allow you to run the c3c command from any location.
|
||||
|
||||
You can choose another version with option `C3_VERSION`.
|
||||
For example, you can force the installation of the 0.7.4 version:
|
||||
```bash
|
||||
$env:C3_VERSION='0.7.4'; powershell -ExecutionPolicy Bypass -Command "iwr -useb https://raw.githubusercontent.com/c3lang/c3c/refs/heads/master/install/install.ps1 | iex"
|
||||
```
|
||||
|
||||
If you don't have Visual Studio 17 installed you can either do so, or run the `msvc_build_libraries.py` Python script which will download the necessary files to compile on Windows.
|
||||
|
||||
|
||||
#### Installing on Debian with precompiled binaries
|
||||
1. Download tar file: [https://github.com/c3lang/c3c/releases/download/latest-prerelease/c3-linux.tar.gz](https://github.com/c3lang/c3c/releases/download/latest-prerelease/c3-linux.tar.gz)
|
||||
(debug version [here](https://github.com/c3lang/c3c/releases/download/latest-prerelease/c3-linux-debug.tar.gz))
|
||||
2. Unpack executable and standard lib.
|
||||
3. Run `./c3c`.
|
||||
|
||||
#### Installing on Debian with the install script
|
||||
|
||||
Open a terminal and run the following command:
|
||||
```bash
|
||||
curl -fsSL https://raw.githubusercontent.com/c3lang/c3c/refs/heads/master/install/install.sh | bash
|
||||
```
|
||||
The C3 compiler will be installed, and the script will also update your ~/.bashrc to include `~/.c3` in your PATH, allowing you to invoke the c3c command from anywhere. You might need to restart your terminal or source your shell for the changes to take effect.
|
||||
|
||||
You can choose another version with option `C3_VERSION`.
|
||||
For example, you can force the installation of the 0.7.4 version:
|
||||
```bash
|
||||
curl -fsSL https://raw.githubusercontent.com/c3lang/c3c/refs/heads/master/install/install.sh | C3_VERSION=0.7.4 bash
|
||||
```
|
||||
|
||||
#### Installing on Ubuntu with precompiled binaries
|
||||
1. Download tar file: [https://github.com/c3lang/c3c/releases/download/latest-prerelease/c3-ubuntu-20.tar.gz](https://github.com/c3lang/c3c/releases/download/latest-prerelease/c3-ubuntu-20.tar.gz)
|
||||
(debug version [here](https://github.com/c3lang/c3c/releases/download/latest-prerelease/c3-ubuntu-20-debug.tar.gz))
|
||||
@@ -264,6 +295,60 @@ cd c3c-git
|
||||
makepkg -si
|
||||
```
|
||||
|
||||
#### Installing via Nix
|
||||
|
||||
You can access `c3c` via [flake.nix](./flake.nix), which will contain the latest commit of the compiler. To add `c3c` to your `flake.nix`, do the following:
|
||||
```nix
|
||||
{
|
||||
inputs = {
|
||||
nixpkgs.url = "github:nixos/nixpkgs?ref=nixpkgs-unstable";
|
||||
flake-utils.url = "github:numtide/flake-utils";
|
||||
c3c.url = "github:c3lang/c3c";
|
||||
# Those are desired if you don't want to copy extra nixpkgs
|
||||
c3c.inputs = {
|
||||
nixpkgs.follows = "nixpkgs";
|
||||
flake-utils.follows = "flake-utils";
|
||||
};
|
||||
};
|
||||
|
||||
outputs = { self, ... } @ inputs: inputs.flake-utils.lib.eachDefaultSystem (system:
|
||||
let
|
||||
pkgs = import inputs.nixpkgs { inherit system; };
|
||||
c3c = inputs.c3c.packages.${system}.c3c;
|
||||
in
|
||||
{
|
||||
devShells.default = pkgs.mkShell {
|
||||
buildInputs = [
|
||||
pkgs.c3c
|
||||
];
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Installing on Gentoo
|
||||
|
||||
`c3c` is available in the [Gentoo GURU overlay](https://wiki.gentoo.org/wiki/Project:GURU).
|
||||
|
||||
Enable and sync the GURU repository (if not already done):
|
||||
|
||||
```sh
|
||||
sudo eselect repository enable guru
|
||||
sudo emaint sync -r guru
|
||||
```
|
||||
|
||||
Install `c3c` with:
|
||||
|
||||
```sh
|
||||
sudo emerge -av dev-lang/c3c
|
||||
```
|
||||
|
||||
* The compiler binary is installed to `/usr/bin/c3c`.
|
||||
* The standard library is installed to `/usr/lib/c3`.
|
||||
|
||||
For Gentoo-specific issues, please use the [Gentoo Bugzilla](https://bugs.gentoo.org/) (Product: *GURU*).
|
||||
|
||||
#### Building via Docker
|
||||
|
||||
You can build `c3c` using an Ubuntu container. By default, the script will build through Ubuntu 22.04. You can specify the version by passing the `UBUNTU_VERSION` environment variable.
|
||||
@@ -276,14 +361,15 @@ See the `build-with-docker.sh` script for more information on other configurable
|
||||
|
||||
#### Installing on OS X using Homebrew
|
||||
|
||||
2. Install CMake: `brew install cmake`
|
||||
3. Install LLVM 17+: `brew install llvm`
|
||||
4. Clone the C3C github repository: `git clone https://github.com/c3lang/c3c.git`
|
||||
5. Enter the C3C directory `cd c3c`.
|
||||
6. Create a build directory `mkdir build`
|
||||
7. Change directory to the build directory `cd build`
|
||||
8. Set up CMake build for debug: `cmake ..`
|
||||
9. Build: `cmake --build .`
|
||||
1. Install [Homebrew](https://brew.sh/)
|
||||
2. Install LLVM 17+: `brew install llvm`
|
||||
3. Install lld: `brew install lld`
|
||||
4. Install CMake: `brew install cmake`
|
||||
5. Clone the C3C github repository: `git clone https://github.com/c3lang/c3c.git`
|
||||
6. Enter the C3C directory `cd c3c`.
|
||||
7. Set up CMake build for debug: `cmake -B build -S .`
|
||||
8. Build: `cmake --build build`
|
||||
9. Change directory to the build directory `cd build`
|
||||
|
||||
#### Installing on Windows using Scoop
|
||||
|
||||
@@ -347,13 +433,12 @@ You should now have a `c3c` executable in `build-debug\Debug`.
|
||||
#### Compiling on Ubuntu 24.04 LTS
|
||||
|
||||
1. Make sure you have a C compiler that handles C11 and a C++ compiler, such as GCC or Clang. Git also needs to be installed.
|
||||
2. Install LLVM 18 `sudo apt-get install cmake git clang zlib1g zlib1g-dev libllvm18 llvm llvm-dev llvm-runtime liblld-dev liblld-18 libpolly-18-dev`
|
||||
2. Install LLVM 18 `sudo apt-get install cmake git clang zlib1g zlib1g-dev libllvm18 llvm llvm-dev llvm-runtime liblld-dev liblld-18 libpolly-18-dev`. If you're using Ubuntu 25.04, also install `libpolly-20-dev`.
|
||||
3. Clone the C3C github repository: `git clone https://github.com/c3lang/c3c.git`
|
||||
4. Enter the C3C directory `cd c3c`.
|
||||
5. Create a build directory `mkdir build`
|
||||
6. Change directory to the build directory `cd build`
|
||||
7. Set up CMake build: `cmake ..`
|
||||
8. Build: `cmake --build .`
|
||||
5. Set up CMake build: `cmake -B build -S .`
|
||||
6. Build: `cmake --build build`
|
||||
7. Change directory to the build directory `cd build`
|
||||
|
||||
You should now have a `c3c` executable.
|
||||
|
||||
@@ -366,13 +451,12 @@ You can try it out by running some sample code: `./c3c compile ../resources/exam
|
||||
2. Clone the C3C repository: `git clone https://github.com/c3lang/c3c.git`
|
||||
- If you only need the latest commit, you may want to make a shallow clone instead: `git clone https://github.com/c3lang/c3c.git --depth=1`
|
||||
3. Enter the directory: `cd c3c`
|
||||
4. Create a build directory: `mkdir build`
|
||||
5. Enter the build directory: `cd build`
|
||||
6. Create the CMake build cache: `cmake ..`
|
||||
7. Build: `cmake --build .`
|
||||
4. Create the CMake build cache: `cmake -B build -S .`
|
||||
5. Build: `cmake --build build`
|
||||
6. Enter the build directory: `cd build`
|
||||
|
||||
Your c3c executable should have compiled properly. You may want to test it: `./c3c compile ../resources/examples/hash.c3`
|
||||
For a sytem-wide installation, run the following as root: `cmake --install .`
|
||||
For a system-wide installation, run the following as root: `cmake --install .`
|
||||
|
||||
|
||||
#### Compiling on Fedora
|
||||
@@ -382,15 +466,15 @@ For a sytem-wide installation, run the following as root: `cmake --install .`
|
||||
3. Clone the C3C repository: `git clone https://github.com/c3lang/c3c.git`
|
||||
- If you only need the latest commit, you may want to make a shallow clone: `git clone https://github.com/c3lang/c3c.git --depth=1`
|
||||
4. Enter the C3C directory: `cd c3c`
|
||||
5. Create a build directory and navigate into it: `mkdir build && cd build`
|
||||
6. Create the CMake build cache. The Fedora repositories provide `.so` libraries for lld, so you need to set the C3_LINK_DYNAMIC flag: `cmake .. -DC3_LINK_DYNAMIC=1`
|
||||
7. Build the project: `cmake --build .`
|
||||
5. Create the CMake build cache. The Fedora repositories provide `.so` libraries for lld, so you need to set the C3_LINK_DYNAMIC flag: `cmake -B build -S . -DC3_LINK_DYNAMIC=1`
|
||||
6. Build the project: `cmake --build build`
|
||||
7. Enter the build directory: `cd build`
|
||||
|
||||
The c3c binary should be created in the build directory. You can try it out by running some sample code: `./c3c compile ../resources/examples/hash.c3`
|
||||
|
||||
#### Compiling on Arch Linux
|
||||
|
||||
1. Install required project dependencies: `sudo pacman -S curl lld llvm-libs clang cmake git libedit llvm`
|
||||
1. Install required project dependencies: `sudo pacman -S curl lld llvm-libs clang cmake git libedit llvm libxml2`
|
||||
2. Clone the C3C repository: `git clone https://github.com/c3lang/c3c.git`
|
||||
- If you only need the latest commit, you may want to make a shallow clone: `git clone https://github.com/c3lang/c3c.git --depth=1`
|
||||
3. Enter the C3C directory: `cd c3c`
|
||||
@@ -400,23 +484,30 @@ cmake -B build \
|
||||
-D C3_LINK_DYNAMIC=ON \
|
||||
-D CMAKE_BUILD_TYPE=Release
|
||||
```
|
||||
5. Build the project: `make -C build`.
|
||||
5. Build the project: `cmake --build build`.
|
||||
|
||||
After compilation, the `c3c` binary will be located in the `build` directory. You can test it by compiling an example: `./build/c3c compile resources/examples/ls.c3`.
|
||||
|
||||
6. To install the compiler globally: `sudo cmake --install build`
|
||||
|
||||
#### Compiling on NixOS
|
||||
|
||||
1. Enter nix shell, by typing `nix develop` in root directory
|
||||
2. Configure cmake via `cmake . -Bbuild $=C3_CMAKE_FLAGS`. Note: passing `C3_CMAKE_FLAGS` is needed in due to generate `compile_commands.json` and find missing libs.
|
||||
4. Build it `cmake --build build`
|
||||
5. Test it out: `./build/c3c -V`
|
||||
6. If you use `clangd` lsp server for your editor, it is recommended to make a symbolic link to `compile_command.json` in the root: `ln -s ./build/compile_commands.json compile_commands.json`
|
||||
|
||||
#### Compiling on other Linux / Unix variants
|
||||
|
||||
1. Install CMake.
|
||||
2. Install or compile LLVM and LLD *libraries* (version 17+ or higher)
|
||||
3. Clone the C3C github repository: `git clone https://github.com/c3lang/c3c.git`
|
||||
4. Enter the C3C directory `cd c3c`.
|
||||
5. Create a build directory `mkdir build`
|
||||
6. Change directory to the build directory `cd build`
|
||||
7. Set up CMake build for debug: `cmake ..`. At this point you may need to manually
|
||||
provide the link path to the LLVM CMake directories, e.g. `cmake -DLLVM_DIR=/usr/local/opt/llvm/lib/cmake/llvm/ ..`
|
||||
8. Build: `cmake --build .`
|
||||
5. Set up CMake build for debug: `cmake -B build -S .`. At this point you may need to manually
|
||||
provide the link path to the LLVM CMake directories, e.g. `cmake -B build -S . -DLLVM_DIR=/usr/local/opt/llvm/lib/cmake/llvm/`
|
||||
6. Build: `cmake --build build`
|
||||
7. Change directory to the build directory `cd build`
|
||||
|
||||
*A note on compiling for Linux/Unix/MacOS: to be able to fetch vendor libraries
|
||||
libcurl is needed. The CMake script should detect it if it is available. Note that
|
||||
@@ -424,8 +515,13 @@ this functionality is non-essential and it is perfectly fine to user the compile
|
||||
|
||||
#### Licensing
|
||||
|
||||
The C3 compiler is licensed under LGPL 3.0, the standard library itself is
|
||||
MIT licensed.
|
||||
Unless specified otherwise, the code in this repository is MIT licensed.
|
||||
The exception is the compiler source code (the source code under `src`),
|
||||
which is licensed under LGPL 3.0.
|
||||
|
||||
This means you are free to use all parts of standard library,
|
||||
tests, benchmarks, grammar, examples and so on under the MIT license, including
|
||||
using those libraries and tests if your build your own C3 compiler.
|
||||
|
||||
#### Editor plugins
|
||||
|
||||
|
||||
219
benchmarks/stdlib/collections/hashmap.c3
Normal file
219
benchmarks/stdlib/collections/hashmap.c3
Normal file
@@ -0,0 +1,219 @@
|
||||
// Copyright (c) 2025 Zack Puhl <github@xmit.xyz>. 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.
|
||||
//
|
||||
// Some benchmark test ideas are sourced from this article on C++ hashmap benchmarking:
|
||||
// https://martin.ankerl.com/2022/08/27/hashmap-bench-01/
|
||||
//
|
||||
module hashmap_benchmarks;
|
||||
|
||||
import std::collections::map;
|
||||
import std::math::random;
|
||||
|
||||
|
||||
const DEFAULT_ITERATIONS = 16384;
|
||||
|
||||
Lcg64Random rand;
|
||||
|
||||
HashMap { int, int } modifying_numbers_random;
|
||||
|
||||
fn void bench_setup() @init
|
||||
{
|
||||
set_benchmark_warmup_iterations(3);
|
||||
set_benchmark_max_iterations(DEFAULT_ITERATIONS);
|
||||
|
||||
// TODO: Cannot take the address of a @benchmark function. If we could, we could pass &insert_erase as a fn ptr and use the $qnameof CT eval internally.
|
||||
set_benchmark_func_iterations($qnameof(insert_erase), 32);
|
||||
set_benchmark_func_iterations($qnameof(random_access), 1024);
|
||||
|
||||
random::seed(&rand, 0x4528_21e6_38d0_1377);
|
||||
|
||||
for (usz i = 0; i < 1_000; ++i) modifying_numbers_random.set(rand.next_int(), rand.next_int());
|
||||
}
|
||||
|
||||
|
||||
// ==============================================================================================
|
||||
module hashmap_benchmarks @benchmark;
|
||||
|
||||
import std::collections::map;
|
||||
|
||||
import std::math::random;
|
||||
import std::encoding::base64;
|
||||
|
||||
|
||||
fn void generic_hash_speeds()
|
||||
{
|
||||
(char){}.hash();
|
||||
(char[<100>]){}.hash();
|
||||
(char[100]){}.hash();
|
||||
(ichar){}.hash();
|
||||
(ichar[<100>]){}.hash();
|
||||
(ichar[100]){}.hash();
|
||||
(short){}.hash();
|
||||
(short[<100>]){}.hash();
|
||||
(short[100]){}.hash();
|
||||
(ushort){}.hash();
|
||||
(ushort[<100>]){}.hash();
|
||||
(ushort[100]){}.hash();
|
||||
(int){}.hash();
|
||||
(int[<100>]){}.hash();
|
||||
(int[100]){}.hash();
|
||||
(uint){}.hash();
|
||||
(uint[<100>]){}.hash();
|
||||
(uint[100]){}.hash();
|
||||
(long){}.hash();
|
||||
(long[<20>]){}.hash();
|
||||
(long[100]){}.hash();
|
||||
(ulong){}.hash();
|
||||
(ulong[<20>]){}.hash();
|
||||
(ulong[100]){}.hash();
|
||||
(int128){}.hash();
|
||||
(int128[<20>]){}.hash();
|
||||
(int128[100]){}.hash();
|
||||
(uint128){}.hash();
|
||||
(uint128[<20>]){}.hash();
|
||||
(uint128[100]){}.hash();
|
||||
(bool){}.hash();
|
||||
(bool[<100>]){}.hash();
|
||||
(bool[100]){}.hash();
|
||||
String x = "abc";
|
||||
char[] y = "abc";
|
||||
assert(x.hash() == y.hash());
|
||||
String z1 = "This is a much longer string than the above value because longer values lead to longer hashing times.";
|
||||
char[] z2 = "This is a much longer string than the above value because longer values lead to longer hashing times.";
|
||||
assert(z1.hash() == z2.hash());
|
||||
assert(int.typeid.hash());
|
||||
}
|
||||
|
||||
|
||||
fn void hash_speeds_of_many_random_values() => @pool()
|
||||
{
|
||||
var $arrsz = 10_000;
|
||||
uint fake_checksum;
|
||||
|
||||
char[] chars = allocator::new_array(tmem, char, $arrsz)[:$arrsz];
|
||||
foreach (&v : chars) *v = (char)random::next(&rand, uint.max);
|
||||
|
||||
ushort[] shorts = allocator::new_array(tmem, ushort, $arrsz)[:$arrsz];
|
||||
foreach (&v : shorts) *v = (ushort)random::next(&rand, uint.max);
|
||||
|
||||
uint[] ints = allocator::new_array(tmem, uint, $arrsz)[:$arrsz];
|
||||
foreach (&v : ints) *v = random::next(&rand, uint.max);
|
||||
|
||||
ulong[] longs = allocator::new_array(tmem, ulong, $arrsz)[:$arrsz];
|
||||
foreach (&v : longs) *v = (ulong)random::next(&rand, uint.max);
|
||||
|
||||
uint128[] vwideints = allocator::new_array(tmem, uint128, $arrsz)[:$arrsz];
|
||||
foreach (&v : vwideints) *v = (uint128)random::next(&rand, uint.max);
|
||||
|
||||
char[48][] zstrs = allocator::new_array(tmem, char[48], $arrsz)[:$arrsz];
|
||||
|
||||
String[] strs = mem::temp_array(String, $arrsz);
|
||||
foreach (x, &v : zstrs)
|
||||
{
|
||||
foreach (&c : (*v)[:random::next(&rand, 48)]) *c = (char)random::next(&rand, char.max);
|
||||
strs[x] = ((ZString)&v[0]).str_view();
|
||||
}
|
||||
|
||||
runtime::@start_benchmark();
|
||||
foreach (v : chars) fake_checksum += v.hash();
|
||||
foreach (v : shorts) fake_checksum += v.hash();
|
||||
foreach (v : ints) fake_checksum += v.hash();
|
||||
foreach (v : longs) fake_checksum += v.hash();
|
||||
foreach (v : vwideints) fake_checksum += v.hash();
|
||||
foreach (v : strs) fake_checksum += v.hash();
|
||||
runtime::@end_benchmark();
|
||||
}
|
||||
|
||||
|
||||
fn void modifying_numbers_init_from_map() => @pool()
|
||||
{
|
||||
HashMap { int, int } v;
|
||||
v.tinit_from_map(&modifying_numbers_random);
|
||||
v.free();
|
||||
}
|
||||
|
||||
|
||||
fn void insert_erase() => @pool()
|
||||
{
|
||||
uint iters = 1_000_000;
|
||||
HashMap { int, int } v;
|
||||
v.tinit();
|
||||
|
||||
runtime::@start_benchmark();
|
||||
for (int i = 0; i < iters; ++i) v[i] = i;
|
||||
for (int i = 0; i < iters; ++i) v.remove(i);
|
||||
|
||||
runtime::@end_benchmark();
|
||||
|
||||
v.free();
|
||||
}
|
||||
|
||||
|
||||
fn void random_access() => @pool()
|
||||
{
|
||||
HashMap { int, int } v;
|
||||
v.tinit();
|
||||
|
||||
uint bound = 10_000;
|
||||
usz pseudo_checksum = 0;
|
||||
|
||||
for (uint i = 0; i < bound; ++i) v[i] = i;
|
||||
|
||||
runtime::@start_benchmark();
|
||||
for (uint i = 0; i < 1_000_000; ++i) pseudo_checksum += (v[i.hash() % bound] ?? 0);
|
||||
runtime::@end_benchmark();
|
||||
|
||||
v.free();
|
||||
}
|
||||
|
||||
|
||||
fn void random_access_erase() => @pool()
|
||||
{
|
||||
HashMap { int, int } v;
|
||||
v.tinit();
|
||||
|
||||
uint bound = 10_000;
|
||||
|
||||
for (uint i = 0; i < bound; ++i) v[i] = i;
|
||||
|
||||
runtime::@start_benchmark();
|
||||
for (uint i = 0; i < bound; ++i)
|
||||
{
|
||||
v[i.hash() % bound] = i; // supplant an entry
|
||||
|
||||
v.remove(random::next(&rand, bound)); // remove a random entry
|
||||
}
|
||||
runtime::@end_benchmark();
|
||||
|
||||
v.free();
|
||||
}
|
||||
|
||||
|
||||
fn void random_access_string_keys() => @pool()
|
||||
{
|
||||
HashMap { String, ulong } v;
|
||||
v.tinit();
|
||||
|
||||
usz pseudo_checksum = 0;
|
||||
String[] saved = mem::temp_array(String, 5_000);
|
||||
|
||||
for (usz i = 0; i < saved.len; ++i)
|
||||
{
|
||||
ulong hash = i.hash();
|
||||
String b64key = base64::tencode(@as_char_view(hash));
|
||||
|
||||
v[b64key] = hash;
|
||||
|
||||
if (i < saved.len) saved[i] = b64key;
|
||||
}
|
||||
|
||||
runtime::@start_benchmark();
|
||||
for (usz i = 0; i < saved.len; ++i)
|
||||
{
|
||||
pseudo_checksum += v[ saved[random::next(&rand, saved.len)] ]!! % 512;
|
||||
}
|
||||
runtime::@end_benchmark();
|
||||
|
||||
v.free();
|
||||
}
|
||||
38
benchmarks/stdlib/collections/linkedlist.c3
Normal file
38
benchmarks/stdlib/collections/linkedlist.c3
Normal file
@@ -0,0 +1,38 @@
|
||||
module linkedlist_benchmarks;
|
||||
|
||||
import std::collections::linkedlist;
|
||||
|
||||
|
||||
LinkedList{int} long_list;
|
||||
const HAY = 2;
|
||||
const NEEDLE = 1000;
|
||||
|
||||
fn void bench_setup() @init
|
||||
{
|
||||
set_benchmark_warmup_iterations(3);
|
||||
set_benchmark_max_iterations(4096);
|
||||
|
||||
int[*] haystack = { [0..999] = HAY };
|
||||
long_list = linkedlist::@new{int}(mem, haystack[..]);
|
||||
long_list.push(NEEDLE);
|
||||
long_list.push_all(haystack[..]);
|
||||
}
|
||||
|
||||
|
||||
// ==============================================================================================
|
||||
module linkedlist_benchmarks @benchmark;
|
||||
|
||||
String die_str = "Failed to find the value `1`. Is something broken?";
|
||||
|
||||
|
||||
fn void foreach_iterator()
|
||||
{
|
||||
foreach (v : long_list.array_view()) if (v == NEEDLE) return;
|
||||
runtime::@kill_benchmark(die_str);
|
||||
}
|
||||
|
||||
fn void foreach_r_iterator()
|
||||
{
|
||||
foreach_r (v : long_list.array_view()) if (v == NEEDLE) return;
|
||||
runtime::@kill_benchmark(die_str);
|
||||
}
|
||||
46
benchmarks/stdlib/core/string_trim.c3
Normal file
46
benchmarks/stdlib/core/string_trim.c3
Normal file
@@ -0,0 +1,46 @@
|
||||
module string_trim_wars;
|
||||
|
||||
const String WHITESPACE_TARGET = " \n\t\r\f\va \tbcde\v\f\r\t\n ";
|
||||
const String WHITESPACE_NUMERIC_TARGET = " 25290 0969 99a \tbcde12332 34 43 0000";
|
||||
|
||||
fn void initialize_bench() @init
|
||||
{
|
||||
set_benchmark_warmup_iterations(64);
|
||||
set_benchmark_max_iterations(1 << 24);
|
||||
}
|
||||
|
||||
macro void trim_bench($trim_str, String $target = WHITESPACE_TARGET) => @pool()
|
||||
{
|
||||
String s1;
|
||||
String s2 = $target.tcopy();
|
||||
|
||||
runtime::@start_benchmark();
|
||||
|
||||
$switch:
|
||||
$case $typeof($trim_str) == String:
|
||||
s1 = s2.trim($trim_str);
|
||||
$case $typeof($trim_str) == AsciiCharset:
|
||||
s1 = s2.trim_charset($trim_str);
|
||||
$default: $error "Unable to determine the right String `trim` operation to use.";
|
||||
$endswitch
|
||||
|
||||
@volatile_load(s1);
|
||||
|
||||
runtime::@end_benchmark();
|
||||
}
|
||||
|
||||
|
||||
module string_trim_wars @benchmark;
|
||||
|
||||
fn void trim_control() => trim_bench(" "); // only spaces
|
||||
|
||||
fn void trim_whitespace_default() => trim_bench("\t\n\r "); // default set
|
||||
fn void trim_whitespace_default_ordered() => trim_bench(" \n\t\r"); // default \w set, but ordered by expected freq
|
||||
|
||||
fn void trim_whitespace_bad() => trim_bench("\f\v\n\t\r "); // bad-perf ordering, all \w
|
||||
|
||||
fn void trim_whitespace_ordered_extended() => trim_bench(" \n\t\r\f\v"); // proposed ordering, all \w
|
||||
fn void trim_charset_whitespace() => trim_bench(ascii::WHITESPACE_SET); // use charset, all \w
|
||||
|
||||
fn void trim_many() => trim_bench(" \n\t\r\f\v0123456789", WHITESPACE_NUMERIC_TARGET); // ordered, all \w + num
|
||||
fn void trim_charset_many() => trim_bench(ascii::WHITESPACE_SET | ascii::NUMBER_SET, WHITESPACE_NUMERIC_TARGET); // set, all \w + num
|
||||
31
benchmarks/stdlib/crypto/aes_bench.c3
Normal file
31
benchmarks/stdlib/crypto/aes_bench.c3
Normal file
@@ -0,0 +1,31 @@
|
||||
module std::crypto::aes_bench;
|
||||
|
||||
import std::crypto::aes;
|
||||
|
||||
fn void init() @init
|
||||
{
|
||||
set_benchmark_warmup_iterations(5);
|
||||
set_benchmark_max_iterations(10_000);
|
||||
}
|
||||
|
||||
|
||||
AesType aes = AES256;
|
||||
char[] key = x"603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4";
|
||||
char[] text = x"6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710";
|
||||
char[] cipher = x"601ec313775789a5b7a7f504bbf3d228f443e3ca4d62b59aca84e990cacaf5c52b0930daa23de94ce87017ba2d84988ddfc9c58db67aada613c2dd08457941a6";
|
||||
char[16] iv = x"f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff";
|
||||
|
||||
fn void bench_ctr_xcrypt() @benchmark
|
||||
{
|
||||
char[64] out;
|
||||
Aes ctx;
|
||||
|
||||
// encrypt
|
||||
ctx.init_with_iv(aes, CTR, key, iv);
|
||||
ctx.encrypt_buffer(text, &out);
|
||||
|
||||
// decrypt
|
||||
ctx.init_with_iv(aes, CTR, key, iv);
|
||||
ctx.decrypt_buffer(cipher, &out);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
|
||||
: ${DOCKER:=docker}
|
||||
: ${IMAGE:="c3c-builder"}
|
||||
@@ -41,4 +41,4 @@ exec $DOCKER run -i --rm \
|
||||
-DCMAKE_DLLTOOL=llvm-dlltool-$LLVM_VERSION \
|
||||
-DC3_LLVM_VERSION=auto && \
|
||||
cmake --build build && \
|
||||
cp -r build/c3c build/lib bin"
|
||||
cp -r build/c3c build/lib bin"
|
||||
|
||||
@@ -6,7 +6,7 @@ ENV LLVM_DEV_VERSION=20
|
||||
|
||||
ARG CMAKE_VERSION=3.20
|
||||
|
||||
RUN apt-get update && apt-get install -y wget gnupg software-properties-common zlib1g zlib1g-dev python3 ninja-build curl g++ && \
|
||||
RUN apt-get update && apt-get install -y wget gnupg software-properties-common zlib1g zlib1g-dev python3 ninja-build curl g++ libcurl4-openssl-dev && \
|
||||
wget https://github.com/Kitware/CMake/releases/download/v${CMAKE_VERSION}/cmake-$CMAKE_VERSION-linux-x86_64.sh && \
|
||||
mkdir -p /opt/cmake && \
|
||||
sh cmake-${CMAKE_VERSION}-linux-x86_64.sh --prefix=/opt/cmake --skip-license && \
|
||||
@@ -46,4 +46,4 @@ RUN groupadd -g 1337 c3c && \
|
||||
USER c3c
|
||||
ENV PATH="/opt/cmake/bin:${PATH}"
|
||||
|
||||
WORKDIR /home/c3c
|
||||
WORKDIR /home/c3c
|
||||
|
||||
18
flake.nix
18
flake.nix
@@ -6,29 +6,27 @@
|
||||
flake-utils.url = "github:numtide/flake-utils";
|
||||
};
|
||||
|
||||
outputs = { self, ... } @ inputs: inputs.flake-utils.lib.eachDefaultSystem
|
||||
outputs = { self, ... }@inputs: inputs.flake-utils.lib.eachDefaultSystem
|
||||
(system:
|
||||
let pkgs = import inputs.nixpkgs { inherit system; };
|
||||
call = set: pkgs.callPackage ./nix/default.nix (
|
||||
set // {
|
||||
rev = self.rev or "unknown";
|
||||
}
|
||||
);
|
||||
c3cBuild = set: pkgs.callPackage ./nix/default.nix (set // {
|
||||
rev = self.rev or "unknown";
|
||||
});
|
||||
in {
|
||||
packages = {
|
||||
default = self.packages.${system}.c3c;
|
||||
|
||||
c3c = call {};
|
||||
c3c = c3cBuild {};
|
||||
|
||||
c3c-checks = pkgs.callPackage ./nix/default.nix {
|
||||
c3c-checks = c3cBuild {
|
||||
checks = true;
|
||||
};
|
||||
|
||||
c3c-debug = pkgs.callPackage ./nix/default.nix {
|
||||
c3c-debug = c3cBuild {
|
||||
debug = true;
|
||||
};
|
||||
|
||||
c3c-debug-checks = pkgs.callPackage ./nix/default.nix {
|
||||
c3c-debug-checks = c3cBuild {
|
||||
debug = true;
|
||||
checks = true;
|
||||
};
|
||||
|
||||
189
install/install.ps1
Normal file
189
install/install.ps1
Normal file
@@ -0,0 +1,189 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
C3 install script.
|
||||
.DESCRIPTION
|
||||
This script installs C3 on Windows from the command line.
|
||||
.PARAMETER C3Version
|
||||
Specifies the version of C3 to install.
|
||||
Default is 'latest'. Can also be set via environment variable 'C3_VERSION'.
|
||||
.PARAMETER C3Home
|
||||
Specifies C3's installation directory.
|
||||
Default is '$Env:USERPROFILE\.c3'. Can also be set via environment variable 'C3_HOME'.
|
||||
.PARAMETER NoPathUpdate
|
||||
If specified, the script will not modify the PATH environment variable.
|
||||
.PARAMETER C3Repourl
|
||||
Specifies the repository URL of C3.
|
||||
Default is 'https://github.com/c3lang/c3c'. Can also be set via environment variable 'C3_REPOURL'.
|
||||
.LINK
|
||||
https://c3-lang.org/
|
||||
.LINK
|
||||
https://github.com/c3lang/c3c
|
||||
#>
|
||||
|
||||
# Script parameters with defaults
|
||||
param (
|
||||
[string] $C3Version = 'latest',
|
||||
[string] $C3Home = "$Env:USERPROFILE\.c3",
|
||||
[switch] $NoPathUpdate,
|
||||
[string] $C3Repourl = 'https://github.com/c3lang/c3c'
|
||||
)
|
||||
|
||||
# Enable strict mode for better error handling
|
||||
Set-StrictMode -Version Latest
|
||||
|
||||
# Function to broadcast environment variable changes to Windows system
|
||||
function Publish-Env {
|
||||
# Add P/Invoke type if it does not exist
|
||||
if (-not ("Win32.NativeMethods" -as [Type])) {
|
||||
Add-Type -Namespace Win32 -Name NativeMethods -MemberDefinition @"
|
||||
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
|
||||
public static extern IntPtr SendMessageTimeout(
|
||||
IntPtr hWnd, uint Msg, UIntPtr wParam, string lParam,
|
||||
uint fuFlags, uint uTimeout, out UIntPtr lpdwResult);
|
||||
"@
|
||||
}
|
||||
|
||||
# Constants for broadcasting environment changes
|
||||
$HWND_BROADCAST = [IntPtr] 0xffff
|
||||
$WM_SETTINGCHANGE = 0x1a
|
||||
$result = [UIntPtr]::Zero
|
||||
|
||||
# Broadcast the message to all windows
|
||||
[Win32.Nativemethods]::SendMessageTimeout($HWND_BROADCAST,
|
||||
$WM_SETTINGCHANGE,
|
||||
[UIntPtr]::Zero,
|
||||
"Environment",
|
||||
2,
|
||||
5000,
|
||||
[ref] $result
|
||||
) | Out-Null
|
||||
}
|
||||
|
||||
# Function to write or update an environment variable in the registry
|
||||
function Write-Env {
|
||||
param(
|
||||
[String] $name,
|
||||
[String] $val,
|
||||
[Switch] $global
|
||||
)
|
||||
|
||||
# Determine the registry key based on scope (user or system)
|
||||
$RegisterKey = if ($global) {
|
||||
Get-Item -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager'
|
||||
} else {
|
||||
Get-Item -Path 'HKCU:'
|
||||
}
|
||||
|
||||
$EnvRegisterKey = $RegisterKey.OpenSubKey('Environment', $true)
|
||||
|
||||
# If value is null, delete the variable
|
||||
if ($null -eq $val) {
|
||||
$EnvRegisterKey.DeleteValue($name)
|
||||
} else {
|
||||
# Determine the correct registry value type
|
||||
$RegistryValueKind = if ($val.Contains('%')) {
|
||||
[Microsoft.Win32.RegistryValueKind]::ExpandString
|
||||
} elseif ($EnvRegisterKey.GetValue($name)) {
|
||||
$EnvRegisterKey.GetValueKind($name)
|
||||
} else {
|
||||
[Microsoft.Win32.RegistryValueKind]::String
|
||||
}
|
||||
$EnvRegisterKey.SetValue($name, $val, $RegistryValueKind)
|
||||
}
|
||||
|
||||
# Broadcast the change to the system
|
||||
Publish-Env
|
||||
}
|
||||
|
||||
# Function to get an environment variable from the registry
|
||||
function Get-Env {
|
||||
param(
|
||||
[String] $name,
|
||||
[Switch] $global
|
||||
)
|
||||
|
||||
# Determine registry key based on scope
|
||||
$RegisterKey = if ($global) {
|
||||
Get-Item -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager'
|
||||
} else {
|
||||
Get-Item -Path 'HKCU:'
|
||||
}
|
||||
|
||||
$EnvRegisterKey = $RegisterKey.OpenSubKey('Environment')
|
||||
$RegistryValueOption = [Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames
|
||||
|
||||
# Retrieve the value without expanding environment variables
|
||||
$EnvRegisterKey.GetValue($name, $null, $RegistryValueOption)
|
||||
}
|
||||
|
||||
# Override defaults if environment variables exist
|
||||
if ($Env:C3_VERSION) { $C3Version = $Env:C3_VERSION }
|
||||
if ($Env:C3_HOME) { $C3Home = $Env:C3_HOME }
|
||||
if ($Env:C3_NO_PATH_UPDATE) { $NoPathUpdate = $true }
|
||||
if ($Env:C3_REPOURL) { $C3Repourl = $Env:C3_REPOURL -replace '/$', '' }
|
||||
|
||||
# Set binary name
|
||||
$BINARY = "c3-windows"
|
||||
|
||||
# Determine the download URL based on version
|
||||
if ($C3Version -eq 'latest') {
|
||||
$DOWNLOAD_URL = "$C3Repourl/releases/latest/download/$BINARY.zip"
|
||||
} else {
|
||||
# Ensure version starts with 'v'
|
||||
$C3Version = "v" + ($C3Version -replace '^v', '')
|
||||
$DOWNLOAD_URL = "$C3Repourl/releases/download/$C3Version/$BINARY.zip"
|
||||
}
|
||||
|
||||
$BinDir = $C3Home
|
||||
|
||||
Write-Host "This script will automatically download and install C3 ($C3Version) for you."
|
||||
Write-Host "Getting it from this url: $DOWNLOAD_URL"
|
||||
Write-Host "The binary will be installed into '$BinDir'"
|
||||
|
||||
# Create temporary file for download
|
||||
$TEMP_FILE = [System.IO.Path]::GetTempFileName()
|
||||
|
||||
try {
|
||||
# Download the binary
|
||||
Invoke-WebRequest -Uri $DOWNLOAD_URL -OutFile $TEMP_FILE
|
||||
|
||||
# Remove previous installation if it exists
|
||||
if (Test-Path -Path $BinDir) {
|
||||
Remove-Item -Path $BinDir -Recurse -Force | Out-Null
|
||||
}
|
||||
|
||||
# Rename temp file to .zip
|
||||
$ZIP_FILE = $TEMP_FILE + ".zip"
|
||||
Rename-Item -Path $TEMP_FILE -NewName $ZIP_FILE
|
||||
|
||||
# Extract downloaded zip
|
||||
Expand-Archive -Path $ZIP_FILE -DestinationPath $Env:USERPROFILE -Force
|
||||
|
||||
# Rename extracted folder to target installation directory
|
||||
Rename-Item -Path "$Env:USERPROFILE/c3-windows-Release" -NewName $BinDir
|
||||
} catch {
|
||||
Write-Host "Error: '$DOWNLOAD_URL' is not available or failed to download"
|
||||
exit 1
|
||||
} finally {
|
||||
# Cleanup temporary zip file
|
||||
Remove-Item -Path $ZIP_FILE
|
||||
}
|
||||
|
||||
# Update PATH environment variable if requested
|
||||
if (!$NoPathUpdate) {
|
||||
$PATH = Get-Env 'PATH'
|
||||
if ($PATH -notlike "*$BinDir*") {
|
||||
Write-Output "Adding $BinDir to PATH"
|
||||
|
||||
# Persist PATH for future sessions
|
||||
Write-Env -name 'PATH' -val "$BinDir;$PATH"
|
||||
|
||||
# Update PATH for current session
|
||||
$Env:PATH = "$BinDir;$PATH"
|
||||
Write-Output "You may need to restart your shell"
|
||||
} else {
|
||||
Write-Output "$BinDir is already in PATH"
|
||||
}
|
||||
} else {
|
||||
Write-Output "You may need to update your PATH manually to use c3"
|
||||
}
|
||||
137
install/install.sh
Normal file
137
install/install.sh
Normal file
@@ -0,0 +1,137 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail # Exit on error, unset variables, and fail pipelines on any error
|
||||
|
||||
__wrap__() {
|
||||
# Version of C3 to install (default: latest)
|
||||
VERSION="${C3_VERSION:-latest}"
|
||||
# Installation directory (default: ~/.c3)
|
||||
C3_HOME="${C3_HOME:-$HOME/.c3}"
|
||||
# Expand '~' if present
|
||||
C3_HOME="${C3_HOME/#\~/$HOME}"
|
||||
BIN_DIR="$C3_HOME"
|
||||
# C3 compiler repository URL
|
||||
REPO="c3lang/c3c"
|
||||
REPOURL="${C3_REPOURL:-https://github.com/$REPO}"
|
||||
|
||||
detect_platform() {
|
||||
# Detects the operating system
|
||||
local os_type
|
||||
os_type="$(uname -s | tr '[:upper:]' '[:lower:]')"
|
||||
|
||||
case "$os_type" in
|
||||
darwin) # macOS
|
||||
echo "macos"
|
||||
;;
|
||||
msys*|mingw*|cygwin*) # Windows (Git Bash / MSYS / Cygwin)
|
||||
IS_MSYS=true
|
||||
echo "windows"
|
||||
;;
|
||||
*)
|
||||
echo $os_type
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Determine platform string
|
||||
PLATFORM="$(detect_platform)"
|
||||
|
||||
# File extension for the archive (ZIP for Windows, TAR.GZ for others)
|
||||
EXT=".tar.gz"
|
||||
BINARY="c3-${PLATFORM}"
|
||||
if [[ "${IS_MSYS:-false}" == true ]]; then
|
||||
EXT=".zip"
|
||||
fi
|
||||
|
||||
# Determine the download URL (latest release or specific version)
|
||||
if [[ "$VERSION" == "latest" ]]; then
|
||||
URL="${REPOURL%/}/releases/latest/download/${BINARY}${EXT}"
|
||||
else
|
||||
URL="${REPOURL%/}/releases/download/v${VERSION#v}/${BINARY}${EXT}"
|
||||
fi
|
||||
|
||||
# Temporary file for the downloaded archive
|
||||
TEMP_FILE="$(mktemp "${TMPDIR:-/tmp}/.C3_install.XXXXXXXX")"
|
||||
trap 'rm -f "$TEMP_FILE"' EXIT # Ensure temp file is deleted on exit
|
||||
|
||||
download_file() {
|
||||
# Download the archive using curl or wget
|
||||
# Check that the curl version is not 8.8.0, which is broken for --write-out
|
||||
# https://github.com/curl/curl/issues/13845
|
||||
if command -v curl >/dev/null && [[ "$(curl --version | awk 'NR==1{print $2}')" != "8.8.0" ]]; then
|
||||
curl -SL "$URL" -o "$TEMP_FILE"
|
||||
elif command -v wget >/dev/null; then
|
||||
wget -O "$TEMP_FILE" "$URL"
|
||||
else
|
||||
echo "Error: curl or wget is required." >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
echo "Downloading C3 ($VERSION) from $URL..."
|
||||
download_file
|
||||
|
||||
# Remove existing installation and extract the new one
|
||||
rm -rf "$BIN_DIR"
|
||||
if [[ "$EXT" == ".zip" ]]; then
|
||||
unzip "$TEMP_FILE" -d "$HOME"
|
||||
else
|
||||
tar -xzf "$TEMP_FILE" -C "$HOME"
|
||||
fi
|
||||
|
||||
# Move extracted folder to installation directory
|
||||
mv "$HOME/c3" "$BIN_DIR"
|
||||
chmod +x "$BIN_DIR/c3c" # Ensure compiler binary is executable
|
||||
echo "✅ Installation completed in $BIN_DIR"
|
||||
|
||||
# Update PATH unless suppressed by environment variable
|
||||
if [ -n "${C3_NO_PATH_UPDATE:-}" ]; then
|
||||
echo "No path update because C3_NO_PATH_UPDATE is set"
|
||||
else
|
||||
update_shell() {
|
||||
FILE="$1"
|
||||
LINE="$2"
|
||||
|
||||
# Create shell config file if missing
|
||||
if [ ! -f "$FILE" ]; then
|
||||
touch "$FILE"
|
||||
fi
|
||||
|
||||
# Add the PATH line if not already present
|
||||
if ! grep -Fxq "$LINE" "$FILE"; then
|
||||
echo "Updating '${FILE}'"
|
||||
echo "$LINE" >>"$FILE"
|
||||
echo "Please restart or source your shell."
|
||||
fi
|
||||
}
|
||||
|
||||
# Detect the current shell and add C3 to its PATH
|
||||
case "$(basename "${SHELL-}")" in
|
||||
bash)
|
||||
# Default to bashrc as that is used in non login shells instead of the profile.
|
||||
LINE="export PATH=\"${BIN_DIR}:\$PATH\""
|
||||
update_shell ~/.bashrc "$LINE"
|
||||
;;
|
||||
fish)
|
||||
LINE="fish_add_path ${BIN_DIR}"
|
||||
update_shell ~/.config/fish/config.fish "$LINE"
|
||||
;;
|
||||
zsh)
|
||||
LINE="export PATH=\"${BIN_DIR}:\$PATH\""
|
||||
update_shell ~/.zshrc "$LINE"
|
||||
;;
|
||||
tcsh)
|
||||
LINE="set path = ( ${BIN_DIR} \$path )"
|
||||
update_shell ~/.tcshrc "$LINE"
|
||||
;;
|
||||
'')
|
||||
echo "warn: Could not detect shell type." >&2
|
||||
echo " Please permanently add '${BIN_DIR}' to your \$PATH to enable the 'c3c' command." >&2
|
||||
;;
|
||||
*)
|
||||
echo "warn: Could not update shell $(basename "$SHELL")" >&2
|
||||
echo " Please permanently add '${BIN_DIR}' to your \$PATH to enable the 'c3c' command." >&2
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
}
|
||||
__wrap__
|
||||
@@ -58,7 +58,7 @@ fn CInt __atomic_compare_exchange(CInt size, any ptr, any expected, any desired,
|
||||
nextcase;
|
||||
$endif
|
||||
default:
|
||||
unreachable("Unsuported size (%d) for atomic_compare_exchange", size);
|
||||
unreachable("Unsupported size (%d) for atomic_compare_exchange", size);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
// 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::anylist;
|
||||
import std::io,std::math;
|
||||
import std::collections::interfacelist;
|
||||
|
||||
alias AnyPredicate = fn bool(any value);
|
||||
alias AnyTest = fn bool(any type, any context);
|
||||
alias AnyPredicate = InterfacePredicate {any};
|
||||
alias AnyTest = InterfaceTest {any};
|
||||
|
||||
<*
|
||||
The AnyList contains a heterogenous set of types. Anything placed in the
|
||||
@@ -18,282 +18,7 @@ alias AnyTest = fn bool(any type, any context);
|
||||
If we're not doing pop, then things are easier, since we can just hand over
|
||||
the existing any.
|
||||
*>
|
||||
struct AnyList (Printable)
|
||||
{
|
||||
usz size;
|
||||
usz capacity;
|
||||
Allocator allocator;
|
||||
any* entries;
|
||||
}
|
||||
|
||||
|
||||
<*
|
||||
Initialize the list. If not initialized then it will use the temp allocator
|
||||
when something is pushed to it.
|
||||
|
||||
@param [&inout] allocator : "The allocator to use"
|
||||
@param initial_capacity : "The initial capacity to reserve, defaults to 16"
|
||||
*>
|
||||
fn AnyList* AnyList.init(&self, Allocator allocator, usz initial_capacity = 16)
|
||||
{
|
||||
self.allocator = allocator;
|
||||
self.size = 0;
|
||||
if (initial_capacity > 0)
|
||||
{
|
||||
initial_capacity = math::next_power_of_2(initial_capacity);
|
||||
self.entries = allocator::alloc_array(allocator, any, initial_capacity);
|
||||
}
|
||||
else
|
||||
{
|
||||
self.entries = null;
|
||||
}
|
||||
self.capacity = initial_capacity;
|
||||
return self;
|
||||
}
|
||||
|
||||
<*
|
||||
Initialize the list using the temp allocator.
|
||||
|
||||
@param initial_capacity : "The initial capacity to reserve"
|
||||
*>
|
||||
fn AnyList* AnyList.tinit(&self, usz initial_capacity = 16)
|
||||
{
|
||||
return self.init(tmem, initial_capacity) @inline;
|
||||
}
|
||||
|
||||
fn bool AnyList.is_initialized(&self) @inline => self.allocator != null;
|
||||
|
||||
<*
|
||||
Push an element on the list by cloning it.
|
||||
*>
|
||||
macro void AnyList.push(&self, element)
|
||||
{
|
||||
if (!self.allocator) self.allocator = tmem;
|
||||
self._append(allocator::clone(self.allocator, element));
|
||||
}
|
||||
|
||||
<*
|
||||
Free a retained element removed using *_retained.
|
||||
*>
|
||||
fn void AnyList.free_element(&self, any element) @inline
|
||||
{
|
||||
allocator::free(self.allocator, element.ptr);
|
||||
}
|
||||
|
||||
<*
|
||||
Pop a value who's type is known. If the type is incorrect, this
|
||||
will still pop the element.
|
||||
|
||||
@param $Type : "The type we assume the value has"
|
||||
@return "The last value as the type given"
|
||||
@return? TYPE_MISMATCH, NO_MORE_ELEMENT
|
||||
*>
|
||||
macro AnyList.pop(&self, $Type)
|
||||
{
|
||||
if (!self.size) return NO_MORE_ELEMENT?;
|
||||
defer self.free_element(self.entries[self.size]);
|
||||
return *anycast(self.entries[--self.size], $Type);
|
||||
}
|
||||
|
||||
<*
|
||||
Copy the last value, pop it and return the copy of it.
|
||||
|
||||
@param [&inout] allocator : "The allocator to use for copying"
|
||||
@return "A copy of the last value if it exists"
|
||||
@return? NO_MORE_ELEMENT
|
||||
*>
|
||||
fn any? AnyList.copy_pop(&self, Allocator allocator)
|
||||
{
|
||||
if (!self.size) return NO_MORE_ELEMENT?;
|
||||
defer self.free_element(self.entries[self.size]);
|
||||
return allocator::clone_any(allocator, self.entries[--self.size]);
|
||||
}
|
||||
|
||||
|
||||
<*
|
||||
Copy the last value, pop it and return the copy of it.
|
||||
|
||||
@return "A temp copy of the last value if it exists"
|
||||
@return? NO_MORE_ELEMENT
|
||||
*>
|
||||
fn any? AnyList.tcopy_pop(&self) => self.copy_pop(tmem);
|
||||
|
||||
|
||||
<*
|
||||
Pop the last value. It must later be released using `list.free_element()`.
|
||||
|
||||
@return "The last value if it exists"
|
||||
@return? NO_MORE_ELEMENT
|
||||
*>
|
||||
fn any? AnyList.pop_retained(&self)
|
||||
{
|
||||
if (!self.size) return NO_MORE_ELEMENT?;
|
||||
return self.entries[--self.size];
|
||||
}
|
||||
|
||||
<*
|
||||
Remove all elements in the list.
|
||||
*>
|
||||
fn void AnyList.clear(&self)
|
||||
{
|
||||
for (usz i = 0; i < self.size; i++)
|
||||
{
|
||||
self.free_element(self.entries[i]);
|
||||
}
|
||||
self.size = 0;
|
||||
}
|
||||
|
||||
<*
|
||||
Pop a value who's type is known. If the type is incorrect, this
|
||||
will still pop the element.
|
||||
|
||||
@param $Type : "The type we assume the value has"
|
||||
@return "The first value as the type given"
|
||||
@return? TYPE_MISMATCH, NO_MORE_ELEMENT
|
||||
*>
|
||||
macro AnyList.pop_first(&self, $Type)
|
||||
{
|
||||
if (!self.size) return NO_MORE_ELEMENT?;
|
||||
defer self.remove_at(0);
|
||||
return *anycast(self.entries[0], $Type);
|
||||
}
|
||||
|
||||
<*
|
||||
Pop the first value. It must later be released using `list.free_element()`.
|
||||
|
||||
@return "The first value if it exists"
|
||||
@return? NO_MORE_ELEMENT
|
||||
*>
|
||||
fn any? AnyList.pop_first_retained(&self)
|
||||
{
|
||||
if (!self.size) return NO_MORE_ELEMENT?;
|
||||
defer self.remove_at(0);
|
||||
return self.entries[0];
|
||||
}
|
||||
|
||||
|
||||
<*
|
||||
Copy the first value, pop it and return the copy of it.
|
||||
|
||||
@param [&inout] allocator : "The allocator to use for copying"
|
||||
@return "A copy of the first value if it exists"
|
||||
@return? NO_MORE_ELEMENT
|
||||
*>
|
||||
fn any? AnyList.copy_pop_first(&self, Allocator allocator)
|
||||
{
|
||||
if (!self.size) return NO_MORE_ELEMENT?;
|
||||
defer self.free_element(self.entries[self.size]);
|
||||
defer self.remove_at(0);
|
||||
return allocator::clone_any(allocator, self.entries[0]);
|
||||
}
|
||||
|
||||
<*
|
||||
Copy the first value, pop it and return the temp copy of it.
|
||||
|
||||
@return "A temp copy of the first value if it exists"
|
||||
@return? NO_MORE_ELEMENT
|
||||
*>
|
||||
fn any? AnyList.tcopy_pop_first(&self) => self.copy_pop_first(tmem);
|
||||
|
||||
<*
|
||||
Remove the element at the particular index.
|
||||
|
||||
@param index : "The index of the element to remove"
|
||||
@require index < self.size
|
||||
*>
|
||||
fn void AnyList.remove_at(&self, usz index)
|
||||
{
|
||||
if (!--self.size || index == self.size) return;
|
||||
self.free_element(self.entries[index]);
|
||||
self.entries[index .. self.size - 1] = self.entries[index + 1 .. self.size];
|
||||
}
|
||||
|
||||
<*
|
||||
Add all the elements in another AnyList.
|
||||
|
||||
@param [&in] other_list : "The list to add"
|
||||
*>
|
||||
fn void AnyList.add_all(&self, AnyList* other_list)
|
||||
{
|
||||
if (!other_list.size) return;
|
||||
self.reserve(other_list.size);
|
||||
foreach (value : other_list)
|
||||
{
|
||||
self.entries[self.size++] = allocator::clone_any(self.allocator, value);
|
||||
}
|
||||
}
|
||||
|
||||
<*
|
||||
Reverse the order of the elements in the list.
|
||||
*>
|
||||
fn void AnyList.reverse(&self)
|
||||
{
|
||||
if (self.size < 2) return;
|
||||
usz half = self.size / 2U;
|
||||
usz end = self.size - 1;
|
||||
for (usz i = 0; i < half; i++)
|
||||
{
|
||||
self.swap(i, end - i);
|
||||
}
|
||||
}
|
||||
|
||||
<*
|
||||
Return a view of the data as a slice.
|
||||
|
||||
@return "The slice view"
|
||||
*>
|
||||
fn any[] AnyList.array_view(&self)
|
||||
{
|
||||
return self.entries[:self.size];
|
||||
}
|
||||
|
||||
<*
|
||||
Push an element to the front of the list.
|
||||
|
||||
@param value : "The value to push to the list"
|
||||
*>
|
||||
macro void AnyList.push_front(&self, value)
|
||||
{
|
||||
self.insert_at(0, value);
|
||||
}
|
||||
|
||||
<*
|
||||
Insert an element at a particular index.
|
||||
|
||||
@param index : "the index where the element should be inserted"
|
||||
@param type : "the value to insert"
|
||||
@require index <= self.size : "The index is out of bounds"
|
||||
*>
|
||||
macro void AnyList.insert_at(&self, usz index, type)
|
||||
{
|
||||
if (index == self.size)
|
||||
{
|
||||
self.push(type);
|
||||
return;
|
||||
}
|
||||
any value = allocator::copy(self.allocator, type);
|
||||
self._insert_at(self, index, value);
|
||||
}
|
||||
|
||||
<*
|
||||
Remove the last element in the list. The list may not be empty.
|
||||
|
||||
@require self.size > 0 : "The list was already empty"
|
||||
*>
|
||||
fn void AnyList.remove_last(&self)
|
||||
{
|
||||
self.free_element(self.entries[--self.size]);
|
||||
}
|
||||
|
||||
<*
|
||||
Remove the first element in the list, the list may not be empty.
|
||||
|
||||
@require self.size > 0
|
||||
*>
|
||||
fn void AnyList.remove_first(&self)
|
||||
{
|
||||
self.remove_at(0);
|
||||
}
|
||||
typedef AnyList = inline InterfaceList {any};
|
||||
|
||||
<*
|
||||
Return the first element by value, assuming it is the given type.
|
||||
@@ -313,10 +38,7 @@ macro AnyList.first(&self, $Type)
|
||||
@return "The first element"
|
||||
@return? NO_MORE_ELEMENT
|
||||
*>
|
||||
fn any? AnyList.first_any(&self) @inline
|
||||
{
|
||||
return self.size ? self.entries[0] : NO_MORE_ELEMENT?;
|
||||
}
|
||||
fn any? AnyList.first_any(&self) @inline => InterfaceList {any}.first(self);
|
||||
|
||||
<*
|
||||
Return the last element by value, assuming it is the given type.
|
||||
@@ -336,29 +58,36 @@ macro AnyList.last(&self, $Type)
|
||||
@return "The last element"
|
||||
@return? NO_MORE_ELEMENT
|
||||
*>
|
||||
fn any? AnyList.last_any(&self) @inline
|
||||
fn any? AnyList.last_any(&self) @inline => InterfaceList {any}.last(self);
|
||||
|
||||
<*
|
||||
Pop a value who's type is known. If the type is incorrect, this
|
||||
will still pop the element.
|
||||
|
||||
@param $Type : "The type we assume the value has"
|
||||
@return "The last value as the type given"
|
||||
@return? TYPE_MISMATCH, NO_MORE_ELEMENT
|
||||
*>
|
||||
macro AnyList.pop(&self, $Type)
|
||||
{
|
||||
return self.size ? self.entries[self.size - 1] : NO_MORE_ELEMENT?;
|
||||
if (!self.size) return NO_MORE_ELEMENT?;
|
||||
defer self.free_element(self.entries[self.size]);
|
||||
return *anycast(self.entries[--self.size], $Type);
|
||||
}
|
||||
|
||||
<*
|
||||
Return whether the list is empty.
|
||||
Pop a value who's type is known. If the type is incorrect, this
|
||||
will still pop the element.
|
||||
|
||||
@return "True if the list is empty"
|
||||
@param $Type : "The type we assume the value has"
|
||||
@return "The first value as the type given"
|
||||
@return? TYPE_MISMATCH, NO_MORE_ELEMENT
|
||||
*>
|
||||
fn bool AnyList.is_empty(&self) @inline
|
||||
macro AnyList.pop_first(&self, $Type)
|
||||
{
|
||||
return !self.size;
|
||||
}
|
||||
|
||||
<*
|
||||
Return the length of the list.
|
||||
|
||||
@return "The number of elements in the list"
|
||||
*>
|
||||
fn usz AnyList.len(&self) @operator(len) @inline
|
||||
{
|
||||
return self.size;
|
||||
if (!self.size) return NO_MORE_ELEMENT?;
|
||||
defer self.remove_at(0);
|
||||
return *anycast(self.entries[0], $Type);
|
||||
}
|
||||
|
||||
<*
|
||||
@@ -383,222 +112,11 @@ macro AnyList.get(&self, usz index, $Type)
|
||||
@return? TYPE_MISMATCH, NO_MORE_ELEMENT
|
||||
@require index < self.size : "Index out of range"
|
||||
*>
|
||||
fn any AnyList.get_any(&self, usz index) @inline @operator([])
|
||||
{
|
||||
return self.entries[index];
|
||||
}
|
||||
fn any AnyList.get_any(&self, usz index) @inline @operator([]) => InterfaceList {any}.get(self, index);
|
||||
|
||||
<*
|
||||
Completely free and clear a list.
|
||||
Return the length of the list.
|
||||
|
||||
@return "The number of elements in the list"
|
||||
*>
|
||||
fn void AnyList.free(&self)
|
||||
{
|
||||
if (!self.allocator) return;
|
||||
self.clear();
|
||||
allocator::free(self.allocator, self.entries);
|
||||
self.capacity = 0;
|
||||
self.entries = null;
|
||||
}
|
||||
|
||||
<*
|
||||
Swap two elements in a list.
|
||||
|
||||
@param i : "Index of one of the elements"
|
||||
@param j : "Index of the other element"
|
||||
@require i < self.size : "The first index is out of range"
|
||||
@require j < self.size : "The second index is out of range"
|
||||
*>
|
||||
fn void AnyList.swap(&self, usz i, usz j)
|
||||
{
|
||||
any temp = self.entries[i];
|
||||
self.entries[i] = self.entries[j];
|
||||
self.entries[j] = temp;
|
||||
}
|
||||
|
||||
<*
|
||||
Print the list to a formatter.
|
||||
*>
|
||||
fn usz? AnyList.to_format(&self, Formatter* formatter) @dynamic
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
<*
|
||||
Remove any elements matching the predicate.
|
||||
|
||||
@param filter : "The function to determine if it should be removed or not"
|
||||
@return "the number of deleted elements"
|
||||
*>
|
||||
fn usz AnyList.remove_if(&self, AnyPredicate filter)
|
||||
{
|
||||
return self._remove_if(filter, false);
|
||||
}
|
||||
|
||||
<*
|
||||
Retain the elements matching the predicate.
|
||||
|
||||
@param selection : "The function to determine if it should be kept or not"
|
||||
@return "the number of deleted elements"
|
||||
*>
|
||||
fn usz AnyList.retain_if(&self, AnyPredicate selection)
|
||||
{
|
||||
return self._remove_if(selection, true);
|
||||
}
|
||||
|
||||
<*
|
||||
Remove any elements matching the predicate.
|
||||
|
||||
@param filter : "The function to determine if it should be removed or not"
|
||||
@param context : "The context to the function"
|
||||
@return "the number of deleted elements"
|
||||
*>
|
||||
fn usz AnyList.remove_using_test(&self, AnyTest filter, any context)
|
||||
{
|
||||
return self._remove_using_test(filter, false, context);
|
||||
}
|
||||
|
||||
<*
|
||||
Retain any elements matching the predicate.
|
||||
|
||||
@param selection : "The function to determine if it should be retained or not"
|
||||
@param context : "The context to the function"
|
||||
@return "the number of deleted elements"
|
||||
*>
|
||||
fn usz AnyList.retain_using_test(&self, AnyTest selection, any context)
|
||||
{
|
||||
return self._remove_using_test(selection, true, context);
|
||||
}
|
||||
|
||||
|
||||
<*
|
||||
Reserve memory so that at least the `min_capacity` exists.
|
||||
|
||||
@param min_capacity : "The min capacity to hold"
|
||||
*>
|
||||
fn void AnyList.reserve(&self, usz min_capacity)
|
||||
{
|
||||
if (!min_capacity) return;
|
||||
if (self.capacity >= min_capacity) return;
|
||||
if (!self.allocator) self.allocator = tmem;
|
||||
min_capacity = math::next_power_of_2(min_capacity);
|
||||
self.entries = allocator::realloc(self.allocator, self.entries, any.sizeof * min_capacity);
|
||||
self.capacity = min_capacity;
|
||||
}
|
||||
|
||||
<*
|
||||
Set the element at any index.
|
||||
|
||||
@param index : "The index where to set the value."
|
||||
@param value : "The value to set"
|
||||
@require index <= self.size : "Index out of range"
|
||||
*>
|
||||
macro void AnyList.set(&self, usz index, value)
|
||||
{
|
||||
if (index == self.size)
|
||||
{
|
||||
self.push(value);
|
||||
return;
|
||||
}
|
||||
self.free_element(self.entries[index]);
|
||||
self.entries[index] = allocator::copy(self.allocator, value);
|
||||
}
|
||||
|
||||
// -- private
|
||||
|
||||
fn void AnyList.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);
|
||||
}
|
||||
|
||||
fn void AnyList._append(&self, any element) @local
|
||||
{
|
||||
self.ensure_capacity();
|
||||
self.entries[self.size++] = element;
|
||||
}
|
||||
|
||||
<*
|
||||
@require index < self.size
|
||||
*>
|
||||
fn void AnyList._insert_at(&self, usz index, any value) @local
|
||||
{
|
||||
self.ensure_capacity();
|
||||
for (usz i = self.size; i > index; i--)
|
||||
{
|
||||
self.entries[i] = self.entries[i - 1];
|
||||
}
|
||||
self.size++;
|
||||
self.entries[index] = value;
|
||||
}
|
||||
|
||||
macro usz AnyList._remove_using_test(&self, AnyTest 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;
|
||||
for (usz j = i; j < k; j++) self.free_element(self.entries[j]);
|
||||
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;
|
||||
}
|
||||
|
||||
macro usz AnyList._remove_if(&self, AnyPredicate 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;
|
||||
for (usz j = i; j < k; j++) self.free_element(self.entries[j]);
|
||||
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 AnyList.len(&self) @operator(len) @inline => InterfaceList {any}.len(self);
|
||||
|
||||
@@ -136,6 +136,7 @@ fn void BitSet.unset(&self, usz i)
|
||||
@param i : "The index of the bit"
|
||||
|
||||
@require i < SIZE : "Index was out of range"
|
||||
@pure
|
||||
*>
|
||||
fn bool BitSet.get(&self, usz i) @operator([]) @inline
|
||||
{
|
||||
@@ -144,6 +145,11 @@ fn bool BitSet.get(&self, usz i) @operator([]) @inline
|
||||
return self.data[q] & (1 << r) != 0;
|
||||
}
|
||||
|
||||
<*
|
||||
Return the number of bits.
|
||||
|
||||
@pure
|
||||
*>
|
||||
fn usz BitSet.len(&self) @operator(len) @inline
|
||||
{
|
||||
return SZ * BITS;
|
||||
|
||||
@@ -11,7 +11,7 @@ alias ElementPredicate = fn bool(Type *type);
|
||||
alias ElementTest = fn bool(Type *type, any context);
|
||||
const ELEMENT_IS_EQUATABLE = types::is_equatable_type(Type);
|
||||
const ELEMENT_IS_POINTER = Type.kindof == POINTER;
|
||||
macro type_is_overaligned() => Type.alignof > mem::DEFAULT_MEM_ALIGNMENT;
|
||||
macro bool type_is_overaligned() => Type.alignof > mem::DEFAULT_MEM_ALIGNMENT;
|
||||
|
||||
struct ElasticArray (Printable)
|
||||
{
|
||||
@@ -121,7 +121,24 @@ fn usz ElasticArray.add_all_to_limit(&self, ElasticArray* other_list)
|
||||
|
||||
@param [in] array
|
||||
*>
|
||||
fn usz ElasticArray.add_array_to_limit(&self, Type[] array)
|
||||
fn usz ElasticArray.add_array_to_limit(&self, Type[] array) @deprecated("Use push_all_to_limit")
|
||||
{
|
||||
if (!array.len) return 0;
|
||||
foreach (i, &value : array)
|
||||
{
|
||||
if (self.size == MAX_SIZE) return array.len - i;
|
||||
self.entries[self.size++] = *value;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
<*
|
||||
Add as many values from this array as possible, returning the
|
||||
number of elements that didn't fit.
|
||||
|
||||
@param [in] array
|
||||
*>
|
||||
fn usz ElasticArray.push_all_to_limit(&self, Type[] array)
|
||||
{
|
||||
if (!array.len) return 0;
|
||||
foreach (i, &value : array)
|
||||
@@ -139,7 +156,7 @@ fn usz ElasticArray.add_array_to_limit(&self, Type[] array)
|
||||
@require array.len + self.size <= MAX_SIZE : `Size would exceed max.`
|
||||
@ensure self.size >= array.len
|
||||
*>
|
||||
fn void ElasticArray.add_array(&self, Type[] array)
|
||||
fn void ElasticArray.add_array(&self, Type[] array) @deprecated("Use push_all")
|
||||
{
|
||||
if (!array.len) return;
|
||||
foreach (&value : array)
|
||||
@@ -148,6 +165,21 @@ fn void ElasticArray.add_array(&self, Type[] array)
|
||||
}
|
||||
}
|
||||
|
||||
<*
|
||||
Add the values of an array to this list.
|
||||
|
||||
@param [in] array
|
||||
@require array.len + self.size <= MAX_SIZE : `Size would exceed max.`
|
||||
@ensure self.size >= array.len
|
||||
*>
|
||||
fn void ElasticArray.push_all(&self, Type[] array)
|
||||
{
|
||||
if (!array.len) return;
|
||||
foreach (&value : array)
|
||||
{
|
||||
self.entries[self.size++] = *value;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
<*
|
||||
@@ -426,4 +458,4 @@ fn usz ElasticArray.compact_count(&self) @if(ELEMENT_IS_POINTER)
|
||||
fn usz ElasticArray.compact(&self) @if(ELEMENT_IS_POINTER)
|
||||
{
|
||||
return list_common::list_compact(self);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,8 +30,10 @@ struct HashMap (Printable)
|
||||
{
|
||||
Entry*[] table;
|
||||
Allocator allocator;
|
||||
uint count; // Number of elements
|
||||
uint threshold; // Resize limit
|
||||
<* Last inserted LinkedEntry *>
|
||||
uint count;
|
||||
<* Resize limit *>
|
||||
uint threshold;
|
||||
float load_factor;
|
||||
}
|
||||
|
||||
@@ -182,6 +184,24 @@ fn Value*? HashMap.get_ref(&map, Key key)
|
||||
return NOT_FOUND?;
|
||||
}
|
||||
|
||||
fn Value* HashMap.get_or_create_ref(&map, Key key) @operator(&[])
|
||||
{
|
||||
uint hash = rehash(key.hash());
|
||||
if (map.count)
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
map.set(key, {});
|
||||
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;
|
||||
}
|
||||
unreachable();
|
||||
}
|
||||
|
||||
fn Entry*? HashMap.get_entry(&map, Key key)
|
||||
{
|
||||
if (!map.count) return NOT_FOUND?;
|
||||
@@ -194,8 +214,9 @@ fn Entry*? HashMap.get_entry(&map, Key key)
|
||||
}
|
||||
|
||||
<*
|
||||
Get the value or update and
|
||||
@require @assignable_to(#expr, Value)
|
||||
Get the value or set it to the value
|
||||
|
||||
@require $defined(Value val = #expr)
|
||||
*>
|
||||
macro Value HashMap.@get_or_set(&map, Key key, Value #expr)
|
||||
{
|
||||
@@ -229,15 +250,15 @@ fn bool HashMap.has_key(&map, Key key)
|
||||
fn bool HashMap.set(&map, Key key, Value value) @operator([]=)
|
||||
{
|
||||
// If the map isn't initialized, use the defaults to initialize it.
|
||||
switch (map.allocator.ptr)
|
||||
{
|
||||
case &dummy:
|
||||
map.init(mem);
|
||||
case null:
|
||||
map.tinit();
|
||||
default:
|
||||
break;
|
||||
}
|
||||
switch (map.allocator.ptr)
|
||||
{
|
||||
case &dummy:
|
||||
map.init(mem);
|
||||
case null:
|
||||
map.tinit();
|
||||
default:
|
||||
break;
|
||||
}
|
||||
uint hash = rehash(key.hash());
|
||||
uint index = index_for(hash, map.table.len);
|
||||
for (Entry *e = map.table[index]; e != null; e = e.next)
|
||||
@@ -332,10 +353,7 @@ macro HashMap.@each_entry(map; @body(entry))
|
||||
}
|
||||
}
|
||||
|
||||
fn Value[] HashMap.tvalues(&map)
|
||||
{
|
||||
return map.values(tmem) @inline;
|
||||
}
|
||||
fn Value[] HashMap.tvalues(&self) => self.values(tmem) @inline;
|
||||
|
||||
fn Value[] HashMap.values(&self, Allocator allocator)
|
||||
{
|
||||
@@ -421,7 +439,7 @@ fn usz? HashMap.to_format(&self, Formatter* f) @dynamic
|
||||
{
|
||||
if (len > 2) len += f.print(", ")!;
|
||||
len += f.printf("%s: %s", entry.key, entry.value)!;
|
||||
};
|
||||
};
|
||||
return len + f.print(" }");
|
||||
}
|
||||
|
||||
@@ -589,4 +607,4 @@ macro uint index_for(uint hash, uint capacity) @private
|
||||
return hash & (capacity - 1);
|
||||
}
|
||||
|
||||
int dummy @local;
|
||||
int dummy @local;
|
||||
|
||||
@@ -14,19 +14,21 @@ const Allocator SET_HEAP_ALLOCATOR = (Allocator)&dummy;
|
||||
<* Copy the ONHEAP allocator to initialize to a set that is heap allocated *>
|
||||
const HashSet ONHEAP = { .allocator = SET_HEAP_ALLOCATOR };
|
||||
|
||||
struct Entry
|
||||
struct Entry
|
||||
{
|
||||
uint hash;
|
||||
Value value;
|
||||
Entry* next;
|
||||
}
|
||||
|
||||
struct HashSet (Printable)
|
||||
struct HashSet (Printable)
|
||||
{
|
||||
Entry*[] table;
|
||||
Allocator allocator;
|
||||
usz count; // Number of elements
|
||||
usz threshold; // Resize limit
|
||||
<* Number of elements *>
|
||||
usz count;
|
||||
<* Resize limit *>
|
||||
usz threshold;
|
||||
float load_factor;
|
||||
}
|
||||
|
||||
@@ -39,7 +41,7 @@ fn int HashSet.len(&self) @operator(len) => (int) self.count;
|
||||
@require !self.is_initialized() : "Set was already initialized"
|
||||
@require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum"
|
||||
*>
|
||||
fn HashSet* HashSet.init(&self, Allocator allocator, usz capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
|
||||
fn HashSet* HashSet.init(&self, Allocator allocator, usz capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
|
||||
{
|
||||
capacity = math::next_power_of_2(capacity);
|
||||
self.allocator = allocator;
|
||||
@@ -285,7 +287,7 @@ fn usz HashSet.remove_all_from(&set, HashSet* other)
|
||||
<*
|
||||
Free all memory allocated by the hash set.
|
||||
*>
|
||||
fn void HashSet.free(&set)
|
||||
fn void HashSet.free(&set)
|
||||
{
|
||||
if (!set.is_initialized()) return;
|
||||
set.clear();
|
||||
@@ -329,7 +331,23 @@ fn void HashSet.reserve(&set, usz capacity)
|
||||
}
|
||||
}
|
||||
|
||||
fn Value[] HashSet.tvalues(&self) => self.values(tmem) @inline;
|
||||
|
||||
fn Value[] HashSet.values(&self, Allocator allocator)
|
||||
{
|
||||
if (!self.count) return {};
|
||||
Value[] list = allocator::alloc_array(allocator, Value, self.count);
|
||||
usz index = 0;
|
||||
foreach (Entry* entry : self.table)
|
||||
{
|
||||
while (entry)
|
||||
{
|
||||
list[index++] = entry.value;
|
||||
entry = entry.next;
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
// --- Set Operations ---
|
||||
|
||||
|
||||
539
lib/std/collections/interfacelist.c3
Normal file
539
lib/std/collections/interfacelist.c3
Normal file
@@ -0,0 +1,539 @@
|
||||
// Copyright (c) 2024-2025 Christoffer Lerno. All rights reserved.
|
||||
// Use of self source code is governed by the MIT license
|
||||
// a copy of which can be found in the LICENSE_STDLIB file.
|
||||
<*
|
||||
@require Type.kindof == INTERFACE || Type.kindof == ANY : "The kind of an interfacelist must be an interface or `any`"
|
||||
*>
|
||||
module std::collections::interfacelist {Type};
|
||||
import std::io,std::math;
|
||||
|
||||
alias InterfacePredicate = fn bool(Type value);
|
||||
alias InterfaceTest = fn bool(Type type, Type context);
|
||||
|
||||
<*
|
||||
The InterfaceList contains a heterogenous set of types implementing an interface. anything placed in the
|
||||
list will shallowly copied in order to be stored as the interface. This means
|
||||
that the list will copy and free its elements.
|
||||
|
||||
However, because we're getting interface values back when we pop, those operations
|
||||
need to take an allocator, as we can only copy then pop then return the copy.
|
||||
|
||||
If we're not doing pop, then things are easier, since we can just hand over
|
||||
the existing value.
|
||||
*>
|
||||
struct InterfaceList (Printable)
|
||||
{
|
||||
usz size;
|
||||
usz capacity;
|
||||
Allocator allocator;
|
||||
Type* entries;
|
||||
}
|
||||
|
||||
|
||||
<*
|
||||
Initialize the list. If not initialized then it will use the temp allocator
|
||||
when something is pushed to it.
|
||||
|
||||
@param [&inout] allocator : "The allocator to use"
|
||||
@param initial_capacity : "The initial capacity to reserve, defaults to 16"
|
||||
*>
|
||||
fn InterfaceList* InterfaceList.init(&self, Allocator allocator, usz initial_capacity = 16)
|
||||
{
|
||||
self.allocator = allocator;
|
||||
self.size = 0;
|
||||
if (initial_capacity > 0)
|
||||
{
|
||||
initial_capacity = math::next_power_of_2(initial_capacity);
|
||||
self.entries = allocator::alloc_array(allocator, Type, initial_capacity);
|
||||
}
|
||||
else
|
||||
{
|
||||
self.entries = null;
|
||||
}
|
||||
self.capacity = initial_capacity;
|
||||
return self;
|
||||
}
|
||||
|
||||
<*
|
||||
Initialize the list using the temp allocator.
|
||||
|
||||
@param initial_capacity : "The initial capacity to reserve"
|
||||
*>
|
||||
fn InterfaceList* InterfaceList.tinit(&self, usz initial_capacity = 16)
|
||||
{
|
||||
return self.init(tmem, initial_capacity) @inline;
|
||||
}
|
||||
|
||||
fn bool InterfaceList.is_initialized(&self) @inline => self.allocator != null;
|
||||
|
||||
<*
|
||||
Push an element on the list by cloning it.
|
||||
@require $defined(Type t = &element) : "Element must implement the interface"
|
||||
*>
|
||||
macro void InterfaceList.push(&self, element)
|
||||
{
|
||||
if (!self.allocator) self.allocator = tmem;
|
||||
self._append(allocator::clone(self.allocator, element));
|
||||
}
|
||||
|
||||
<*
|
||||
Free a retained element removed using *_retained.
|
||||
*>
|
||||
fn void InterfaceList.free_element(&self, Type element) @inline
|
||||
{
|
||||
allocator::free(self.allocator, element.ptr);
|
||||
}
|
||||
|
||||
<*
|
||||
Copy the last value, pop it and return the copy of it.
|
||||
|
||||
@param [&inout] allocator : "The allocator to use for copying"
|
||||
@return "A copy of the last value if it exists"
|
||||
@return? NO_MORE_ELEMENT
|
||||
*>
|
||||
fn Type? InterfaceList.copy_pop(&self, Allocator allocator)
|
||||
{
|
||||
if (!self.size) return NO_MORE_ELEMENT?;
|
||||
defer self.free_element(self.entries[self.size]);
|
||||
return (Type)allocator::clone_any(allocator, self.entries[--self.size]);
|
||||
}
|
||||
|
||||
<*
|
||||
Copy the last value, pop it and return the copy of it.
|
||||
|
||||
@return "A temp copy of the last value if it exists"
|
||||
@return? NO_MORE_ELEMENT
|
||||
*>
|
||||
fn Type? InterfaceList.tcopy_pop(&self) => self.copy_pop(tmem);
|
||||
|
||||
<*
|
||||
Pop the last value. It must later be released using `list.free_element()`.
|
||||
|
||||
@return "The last value if it exists"
|
||||
@return? NO_MORE_ELEMENT
|
||||
*>
|
||||
fn Type? InterfaceList.pop_retained(&self)
|
||||
{
|
||||
if (!self.size) return NO_MORE_ELEMENT?;
|
||||
return self.entries[--self.size];
|
||||
}
|
||||
|
||||
<*
|
||||
Remove all elements in the list.
|
||||
*>
|
||||
fn void InterfaceList.clear(&self)
|
||||
{
|
||||
for (usz i = 0; i < self.size; i++)
|
||||
{
|
||||
self.free_element(self.entries[i]);
|
||||
}
|
||||
self.size = 0;
|
||||
}
|
||||
|
||||
<*
|
||||
Pop the first value. It must later be released using `list.free_element()`.
|
||||
|
||||
@return "The first value if it exists"
|
||||
@return? NO_MORE_ELEMENT
|
||||
*>
|
||||
fn Type? InterfaceList.pop_first_retained(&self)
|
||||
{
|
||||
if (!self.size) return NO_MORE_ELEMENT?;
|
||||
defer self.remove_at(0);
|
||||
return self.entries[0];
|
||||
}
|
||||
|
||||
<*
|
||||
Copy the first value, pop it and return the copy of it.
|
||||
|
||||
@param [&inout] allocator : "The allocator to use for copying"
|
||||
@return "A copy of the first value if it exists"
|
||||
@return? NO_MORE_ELEMENT
|
||||
*>
|
||||
fn Type? InterfaceList.copy_pop_first(&self, Allocator allocator)
|
||||
{
|
||||
if (!self.size) return NO_MORE_ELEMENT?;
|
||||
defer self.free_element(self.entries[self.size]);
|
||||
defer self.remove_at(0);
|
||||
return (Type)allocator::clone_any(allocator, self.entries[0]);
|
||||
}
|
||||
|
||||
<*
|
||||
Copy the first value, pop it and return the temp copy of it.
|
||||
|
||||
@return "A temp copy of the first value if it exists"
|
||||
@return? NO_MORE_ELEMENT
|
||||
*>
|
||||
fn Type? InterfaceList.tcopy_pop_first(&self) => self.copy_pop_first(tmem);
|
||||
|
||||
<*
|
||||
Remove the element at the particular index.
|
||||
|
||||
@param index : "The index of the element to remove"
|
||||
@require index < self.size
|
||||
*>
|
||||
fn void InterfaceList.remove_at(&self, usz index)
|
||||
{
|
||||
if (!--self.size || index == self.size) return;
|
||||
self.free_element(self.entries[index]);
|
||||
self.entries[index .. self.size - 1] = self.entries[index + 1 .. self.size];
|
||||
}
|
||||
|
||||
<*
|
||||
Add all the elements in another InterfaceList.
|
||||
|
||||
@param [&in] other_list : "The list to add"
|
||||
*>
|
||||
fn void InterfaceList.add_all(&self, InterfaceList* other_list)
|
||||
{
|
||||
if (!other_list.size) return;
|
||||
self.reserve(other_list.size);
|
||||
foreach (value : other_list)
|
||||
{
|
||||
self.entries[self.size++] = (Type)allocator::clone_any(self.allocator, value);
|
||||
}
|
||||
}
|
||||
|
||||
<*
|
||||
Reverse the order of the elements in the list.
|
||||
*>
|
||||
fn void InterfaceList.reverse(&self)
|
||||
{
|
||||
if (self.size < 2) return;
|
||||
usz half = self.size / 2U;
|
||||
usz end = self.size - 1;
|
||||
for (usz i = 0; i < half; i++)
|
||||
{
|
||||
self.swap(i, end - i);
|
||||
}
|
||||
}
|
||||
|
||||
<*
|
||||
Return a view of the data as a slice.
|
||||
|
||||
@return "The slice view"
|
||||
*>
|
||||
fn Type[] InterfaceList.array_view(&self)
|
||||
{
|
||||
return self.entries[:self.size];
|
||||
}
|
||||
|
||||
<*
|
||||
Push an element to the front of the list.
|
||||
|
||||
@param value : "The value to push to the list"
|
||||
@require $defined(Type t = &value) : "Value must implement the interface"
|
||||
*>
|
||||
macro void InterfaceList.push_front(&self, value)
|
||||
{
|
||||
self.insert_at(0, value);
|
||||
}
|
||||
|
||||
<*
|
||||
Insert an element at a particular index.
|
||||
|
||||
@param index : "the index where the element should be inserted"
|
||||
@param type : "the value to insert"
|
||||
@require index <= self.size : "The index is out of bounds"
|
||||
@require $defined(Type t = &type) : "Type must implement the interface"
|
||||
*>
|
||||
macro void InterfaceList.insert_at(&self, usz index, type)
|
||||
{
|
||||
if (index == self.size)
|
||||
{
|
||||
self.push(type);
|
||||
return;
|
||||
}
|
||||
Type value = allocator::clone(self.allocator, type);
|
||||
self._insert_at(self, index, value);
|
||||
}
|
||||
|
||||
<*
|
||||
Remove the last element in the list. The list may not be empty.
|
||||
|
||||
@require self.size > 0 : "The list was already empty"
|
||||
*>
|
||||
fn void InterfaceList.remove_last(&self)
|
||||
{
|
||||
self.free_element(self.entries[--self.size]);
|
||||
}
|
||||
|
||||
<*
|
||||
Remove the first element in the list, the list may not be empty.
|
||||
|
||||
@require self.size > 0
|
||||
*>
|
||||
fn void InterfaceList.remove_first(&self)
|
||||
{
|
||||
self.remove_at(0);
|
||||
}
|
||||
|
||||
<*
|
||||
Return the first element
|
||||
|
||||
@return "The first element"
|
||||
@return? NO_MORE_ELEMENT
|
||||
*>
|
||||
fn Type? InterfaceList.first(&self) @inline
|
||||
{
|
||||
return self.size ? self.entries[0] : NO_MORE_ELEMENT?;
|
||||
}
|
||||
|
||||
<*
|
||||
Return the last element
|
||||
|
||||
@return "The last element"
|
||||
@return? NO_MORE_ELEMENT
|
||||
*>
|
||||
fn Type? InterfaceList.last(&self) @inline
|
||||
{
|
||||
return self.size ? self.entries[self.size - 1] : NO_MORE_ELEMENT?;
|
||||
}
|
||||
|
||||
<*
|
||||
Return whether the list is empty.
|
||||
|
||||
@return "True if the list is empty"
|
||||
*>
|
||||
fn bool InterfaceList.is_empty(&self) @inline
|
||||
{
|
||||
return !self.size;
|
||||
}
|
||||
|
||||
<*
|
||||
Return the length of the list.
|
||||
|
||||
@return "The number of elements in the list"
|
||||
*>
|
||||
fn usz InterfaceList.len(&self) @operator(len) @inline
|
||||
{
|
||||
return self.size;
|
||||
}
|
||||
|
||||
<*
|
||||
Return an element in the list.
|
||||
|
||||
@param index : "The index of the element to retrieve"
|
||||
@return "The element at the index"
|
||||
@return? TYPE_MISMATCH, NO_MORE_ELEMENT
|
||||
@require index < self.size : "Index out of range"
|
||||
*>
|
||||
fn Type InterfaceList.get(&self, usz index) @inline @operator([])
|
||||
{
|
||||
return self.entries[index];
|
||||
}
|
||||
|
||||
<*
|
||||
Completely free and clear a list.
|
||||
*>
|
||||
fn void InterfaceList.free(&self)
|
||||
{
|
||||
if (!self.allocator) return;
|
||||
self.clear();
|
||||
allocator::free(self.allocator, self.entries);
|
||||
self.capacity = 0;
|
||||
self.entries = null;
|
||||
}
|
||||
|
||||
<*
|
||||
Swap two elements in a list.
|
||||
|
||||
@param i : "Index of one of the elements"
|
||||
@param j : "Index of the other element"
|
||||
@require i < self.size : "The first index is out of range"
|
||||
@require j < self.size : "The second index is out of range"
|
||||
*>
|
||||
fn void InterfaceList.swap(&self, usz i, usz j)
|
||||
{
|
||||
Type temp = self.entries[i];
|
||||
self.entries[i] = self.entries[j];
|
||||
self.entries[j] = temp;
|
||||
}
|
||||
|
||||
<*
|
||||
Print the list to a formatter.
|
||||
*>
|
||||
fn usz? InterfaceList.to_format(&self, Formatter* formatter) @dynamic
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
<*
|
||||
Remove Type elements matching the predicate.
|
||||
|
||||
@param filter : "The function to determine if it should be removed or not"
|
||||
@return "the number of deleted elements"
|
||||
*>
|
||||
fn usz InterfaceList.remove_if(&self, InterfacePredicate filter)
|
||||
{
|
||||
return self._remove_if(filter, false);
|
||||
}
|
||||
|
||||
<*
|
||||
Retain the elements matching the predicate.
|
||||
|
||||
@param selection : "The function to determine if it should be kept or not"
|
||||
@return "the number of deleted elements"
|
||||
*>
|
||||
fn usz InterfaceList.retain_if(&self, InterfacePredicate selection)
|
||||
{
|
||||
return self._remove_if(selection, true);
|
||||
}
|
||||
|
||||
<*
|
||||
Remove Type elements matching the predicate.
|
||||
|
||||
@param filter : "The function to determine if it should be removed or not"
|
||||
@param context : "The context to the function"
|
||||
@return "the number of deleted elements"
|
||||
*>
|
||||
fn usz InterfaceList.remove_using_test(&self, InterfaceTest filter, Type context)
|
||||
{
|
||||
return self._remove_using_test(filter, false, context);
|
||||
}
|
||||
|
||||
<*
|
||||
Retain Type elements matching the predicate.
|
||||
|
||||
@param selection : "The function to determine if it should be retained or not"
|
||||
@param context : "The context to the function"
|
||||
@return "the number of deleted elements"
|
||||
*>
|
||||
fn usz InterfaceList.retain_using_test(&self, InterfaceTest selection, Type context)
|
||||
{
|
||||
return self._remove_using_test(selection, true, context);
|
||||
}
|
||||
|
||||
<*
|
||||
Reserve memory so that at least the `min_capacity` exists.
|
||||
|
||||
@param min_capacity : "The min capacity to hold"
|
||||
*>
|
||||
fn void InterfaceList.reserve(&self, usz min_capacity)
|
||||
{
|
||||
if (!min_capacity) return;
|
||||
if (self.capacity >= min_capacity) return;
|
||||
if (!self.allocator) self.allocator = tmem;
|
||||
min_capacity = math::next_power_of_2(min_capacity);
|
||||
self.entries = allocator::realloc(self.allocator, self.entries, Type.sizeof * min_capacity);
|
||||
self.capacity = min_capacity;
|
||||
}
|
||||
|
||||
<*
|
||||
Set the element at Type index.
|
||||
|
||||
@param index : "The index where to set the value."
|
||||
@param value : "The value to set"
|
||||
@require index <= self.size : "Index out of range"
|
||||
@require $defined(Type t = &value) : "Value must implement the interface"
|
||||
*>
|
||||
macro void InterfaceList.set(&self, usz index, value)
|
||||
{
|
||||
if (index == self.size)
|
||||
{
|
||||
self.push(value);
|
||||
return;
|
||||
}
|
||||
self.free_element(self.entries[index]);
|
||||
self.entries[index] = allocator::clone(self.allocator, value);
|
||||
}
|
||||
|
||||
// -- private
|
||||
|
||||
fn void InterfaceList.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);
|
||||
}
|
||||
|
||||
fn void InterfaceList._append(&self, Type element) @local
|
||||
{
|
||||
self.ensure_capacity();
|
||||
self.entries[self.size++] = element;
|
||||
}
|
||||
|
||||
<*
|
||||
@require index < self.size
|
||||
*>
|
||||
fn void InterfaceList._insert_at(&self, usz index, Type value) @local
|
||||
{
|
||||
self.ensure_capacity();
|
||||
for (usz i = self.size; i > index; i--)
|
||||
{
|
||||
self.entries[i] = self.entries[i - 1];
|
||||
}
|
||||
self.size++;
|
||||
self.entries[index] = value;
|
||||
}
|
||||
|
||||
macro usz InterfaceList._remove_using_test(&self, InterfaceTest 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;
|
||||
for (usz j = i; j < k; j++) self.free_element(self.entries[j]);
|
||||
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;
|
||||
}
|
||||
|
||||
macro usz InterfaceList._remove_if(&self, InterfacePredicate 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;
|
||||
for (usz j = i; j < k; j++) self.free_element(self.entries[j]);
|
||||
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;
|
||||
}
|
||||
333
lib/std/collections/linked_blockingqueue.c3
Normal file
333
lib/std/collections/linked_blockingqueue.c3
Normal file
@@ -0,0 +1,333 @@
|
||||
module std::collections::blockingqueue { Value };
|
||||
import std::thread, std::time;
|
||||
|
||||
|
||||
const INITIAL_CAPACITY = 16;
|
||||
|
||||
struct QueueEntry
|
||||
{
|
||||
Value value;
|
||||
<* Next in queue order *>
|
||||
QueueEntry* next;
|
||||
<* Previous in queue order *>
|
||||
QueueEntry* prev;
|
||||
}
|
||||
|
||||
struct LinkedBlockingQueue
|
||||
{
|
||||
<* First element in queue *>
|
||||
QueueEntry* head;
|
||||
<* Last element in queue *>
|
||||
QueueEntry* tail;
|
||||
<* Current number of elements *>
|
||||
usz count;
|
||||
<* Maximum capacity (0 for unbounded) *>
|
||||
usz capacity;
|
||||
Mutex lock;
|
||||
ConditionVariable not_empty;
|
||||
ConditionVariable not_full;
|
||||
Allocator allocator;
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&inout] allocator : "The allocator to use"
|
||||
@param capacity : "Maximum capacity (0 for unbounded)"
|
||||
@require !self.is_initialized() : "Queue was already initialized"
|
||||
*>
|
||||
fn LinkedBlockingQueue* LinkedBlockingQueue.init(&self, Allocator allocator, usz capacity = 0)
|
||||
{
|
||||
self.allocator = allocator;
|
||||
self.capacity = capacity;
|
||||
self.count = 0;
|
||||
self.head = null;
|
||||
self.tail = null;
|
||||
|
||||
self.lock.init()!!;
|
||||
self.not_empty.init()!!;
|
||||
self.not_full.init()!!;
|
||||
return self;
|
||||
}
|
||||
|
||||
fn LinkedBlockingQueue* LinkedBlockingQueue.tinit(&self, usz capacity = 0)
|
||||
{
|
||||
return self.init(tmem, capacity) @inline;
|
||||
}
|
||||
|
||||
<*
|
||||
@require self.is_initialized() : "Queue must be initialized"
|
||||
*>
|
||||
fn void LinkedBlockingQueue.free(&self)
|
||||
{
|
||||
self.lock.@in_lock()
|
||||
{
|
||||
// Free all remaining entries
|
||||
QueueEntry* entry = self.head;
|
||||
while (entry != null)
|
||||
{
|
||||
QueueEntry* next = entry.next;
|
||||
allocator::free(self.allocator, entry);
|
||||
entry = next;
|
||||
}
|
||||
};
|
||||
|
||||
(void)self.lock.destroy();
|
||||
(void)self.not_empty.destroy();
|
||||
(void)self.not_full.destroy();
|
||||
}
|
||||
|
||||
fn void LinkedBlockingQueue.link_entry(&self, QueueEntry* entry) @private
|
||||
{
|
||||
entry.next = null;
|
||||
entry.prev = self.tail;
|
||||
|
||||
if (self.tail == null)
|
||||
{
|
||||
// First element in queue
|
||||
self.head = entry;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Append to tail
|
||||
self.tail.next = entry;
|
||||
}
|
||||
self.tail = entry;
|
||||
self.count++;
|
||||
}
|
||||
|
||||
|
||||
fn QueueEntry* LinkedBlockingQueue.unlink_head(&self) @private
|
||||
{
|
||||
if (self.head == null) return null;
|
||||
|
||||
QueueEntry* entry = self.head;
|
||||
self.head = entry.next;
|
||||
|
||||
if (self.head != null)
|
||||
{
|
||||
self.head.prev = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Queue is now empty
|
||||
self.tail = null;
|
||||
}
|
||||
|
||||
self.count--;
|
||||
return entry;
|
||||
}
|
||||
|
||||
<*
|
||||
@param value : "Value to add to the queue"
|
||||
@require self.is_initialized() : "Queue must be initialized"
|
||||
*>
|
||||
fn void LinkedBlockingQueue.push(&self, Value value)
|
||||
{
|
||||
self.lock.@in_lock()
|
||||
{
|
||||
while (self.capacity > 0 && self.count >= self.capacity)
|
||||
{
|
||||
self.not_full.wait(&self.lock)!!;
|
||||
}
|
||||
|
||||
QueueEntry* entry = allocator::new(self.allocator, QueueEntry, {
|
||||
.value = value,
|
||||
.next = null,
|
||||
.prev = null
|
||||
});
|
||||
self.link_entry(entry);
|
||||
|
||||
// Signal that queue is no longer empty
|
||||
self.not_empty.signal()!!;
|
||||
};
|
||||
}
|
||||
|
||||
<*
|
||||
Get a value from the queue, blocking if there is no element in the queue.
|
||||
|
||||
@require self.is_initialized() : "Queue must be initialized"
|
||||
@return "The removed value"
|
||||
*>
|
||||
fn Value LinkedBlockingQueue.poll(&self)
|
||||
{
|
||||
self.lock.@in_lock()
|
||||
{
|
||||
while (self.count == 0)
|
||||
{
|
||||
self.not_empty.wait(&self.lock)!!;
|
||||
}
|
||||
|
||||
QueueEntry* entry = self.unlink_head();
|
||||
Value value = entry.value;
|
||||
allocator::free(self.allocator, entry);
|
||||
if (self.capacity > 0)
|
||||
{
|
||||
self.not_full.signal()!!;
|
||||
}
|
||||
return value;
|
||||
};
|
||||
}
|
||||
|
||||
<*
|
||||
Pop an element from the queue, fail is it is empty.
|
||||
|
||||
@require self.is_initialized() : "Queue must be initialized"
|
||||
@return "The removed value"
|
||||
@return? NO_MORE_ELEMENT : "If the queue is empty"
|
||||
*>
|
||||
fn Value? LinkedBlockingQueue.pop(&self)
|
||||
{
|
||||
self.lock.@in_lock()
|
||||
{
|
||||
if (self.count == 0) return NO_MORE_ELEMENT?;
|
||||
|
||||
QueueEntry* entry = self.unlink_head();
|
||||
Value value = entry.value;
|
||||
allocator::free(self.allocator, entry);
|
||||
|
||||
if (self.capacity > 0)
|
||||
{
|
||||
self.not_full.signal()!!;
|
||||
}
|
||||
return value;
|
||||
};
|
||||
}
|
||||
|
||||
<*
|
||||
Poll with a timeout.
|
||||
|
||||
@param timeout : "Timeout in microseconds"
|
||||
@require self.is_initialized() : "Queue must be initialized"
|
||||
@return "The removed value or null if timeout occurred"
|
||||
@return? NO_MORE_ELEMENT : "If we reached the timeout"
|
||||
*>
|
||||
fn Value? LinkedBlockingQueue.poll_timeout(&self, Duration timeout)
|
||||
{
|
||||
self.lock.@in_lock()
|
||||
{
|
||||
// Use while loop to handle spurious wakeups
|
||||
if (!self.count)
|
||||
{
|
||||
Time start = time::now();
|
||||
Time end = start + timeout;
|
||||
while (!self.count)
|
||||
{
|
||||
if (end <= time::now()) break;
|
||||
if (catch self.not_empty.wait_until(&self.lock, end)) break;
|
||||
}
|
||||
if (!self.count) return NO_MORE_ELEMENT?;
|
||||
}
|
||||
|
||||
QueueEntry* entry = self.unlink_head();
|
||||
Value value = entry.value;
|
||||
allocator::free(self.allocator, entry);
|
||||
|
||||
// Must signal not_full after removing an item
|
||||
if (self.capacity > 0)
|
||||
{
|
||||
self.not_full.signal()!!;
|
||||
}
|
||||
return value;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
<*
|
||||
@require self.is_initialized() : "Queue must be initialized"
|
||||
@return "Current size of the queue"
|
||||
*>
|
||||
fn usz LinkedBlockingQueue.size(&self)
|
||||
{
|
||||
self.lock.@in_lock()
|
||||
{
|
||||
return self.count;
|
||||
};
|
||||
}
|
||||
|
||||
<*
|
||||
@require self.is_initialized() : "Queue must be initialized"
|
||||
@return "True if queue is empty"
|
||||
*>
|
||||
fn bool LinkedBlockingQueue.is_empty(&self)
|
||||
{
|
||||
self.lock.@in_lock()
|
||||
{
|
||||
return self.count == 0;
|
||||
};
|
||||
}
|
||||
|
||||
<*
|
||||
Try to push, return CAPACITY_EXCEEDED if the queue is full.
|
||||
|
||||
@param value : "Value to add to the queue"
|
||||
@require self.is_initialized() : "Queue must be initialized"
|
||||
@return? CAPACITY_EXCEEDED : "If the queue is full"
|
||||
*>
|
||||
fn void? LinkedBlockingQueue.try_push(&self, Value value)
|
||||
{
|
||||
self.lock.@in_lock()
|
||||
{
|
||||
if (self.capacity > 0 && self.count >= self.capacity) return CAPACITY_EXCEEDED?;
|
||||
|
||||
QueueEntry* entry = allocator::new(self.allocator, QueueEntry, {
|
||||
.value = value,
|
||||
.next = null,
|
||||
.prev = null
|
||||
});
|
||||
self.link_entry(entry);
|
||||
self.not_empty.signal()!!;
|
||||
};
|
||||
}
|
||||
|
||||
<*
|
||||
Try to push, return CAPACITY_EXCEEDED if the queue is still full after timeout is reached.
|
||||
|
||||
@param value : "Value to add to the queue"
|
||||
@param timeout : "Timeout in microseconds"
|
||||
@require self.is_initialized() : "Queue must be initialized"
|
||||
@return? CAPACITY_EXCEEDED : "If the queue is full"
|
||||
*>
|
||||
fn void? LinkedBlockingQueue.push_timeout(&self, Value value, Duration timeout)
|
||||
{
|
||||
self.lock.@in_lock()
|
||||
{
|
||||
if (self.capacity > 0 && self.count >= self.capacity)
|
||||
{
|
||||
Time start = time::now();
|
||||
Time end = start + timeout;
|
||||
while (self.capacity > 0 && self.count >= self.capacity)
|
||||
{
|
||||
if (end <= time::now()) break;
|
||||
if (catch self.not_empty.wait_until(&self.lock, end)) break;
|
||||
}
|
||||
if (self.capacity > 0 && self.count >= self.capacity) return CAPACITY_EXCEEDED?;
|
||||
}
|
||||
|
||||
QueueEntry* entry = allocator::new(self.allocator, QueueEntry, {
|
||||
.value = value,
|
||||
.next = null,
|
||||
.prev = null
|
||||
});
|
||||
self.link_entry(entry);
|
||||
self.not_empty.signal()!!;
|
||||
};
|
||||
}
|
||||
|
||||
<*
|
||||
@require self.is_initialized() : "Queue must be initialized"
|
||||
@return "The head value or NO_MORE_ELEMENT? if queue is empty"
|
||||
*>
|
||||
fn Value? LinkedBlockingQueue.peek(&self)
|
||||
{
|
||||
self.lock.@in_lock()
|
||||
{
|
||||
return (self.head != null) ? self.head.value : NO_MORE_ELEMENT?;
|
||||
};
|
||||
}
|
||||
|
||||
<*
|
||||
@return "True if queue is initialized"
|
||||
*>
|
||||
fn bool LinkedBlockingQueue.is_initialized(&self)
|
||||
{
|
||||
return self.allocator && self.lock.initialized;
|
||||
}
|
||||
@@ -15,9 +15,12 @@ struct LinkedEntry
|
||||
uint hash;
|
||||
Key key;
|
||||
Value value;
|
||||
LinkedEntry* next; // For bucket chain
|
||||
LinkedEntry* before; // Previous in insertion order
|
||||
LinkedEntry* after; // Next in insertion order
|
||||
<* For bucket chain *>
|
||||
LinkedEntry* next;
|
||||
<* Previous in insertion order *>
|
||||
LinkedEntry* before;
|
||||
<* Next in insertion order *>
|
||||
LinkedEntry* after;
|
||||
}
|
||||
|
||||
struct LinkedHashMap (Printable)
|
||||
@@ -27,8 +30,10 @@ struct LinkedHashMap (Printable)
|
||||
usz count;
|
||||
usz threshold;
|
||||
float load_factor;
|
||||
LinkedEntry* head; // First inserted LinkedEntry
|
||||
LinkedEntry* tail; // Last inserted LinkedEntry
|
||||
<* First inserted LinkedEntry *>
|
||||
LinkedEntry* head;
|
||||
<* Last inserted LinkedEntry *>
|
||||
LinkedEntry* tail;
|
||||
}
|
||||
|
||||
|
||||
@@ -188,8 +193,9 @@ fn LinkedEntry*? LinkedHashMap.get_entry(&map, Key key)
|
||||
}
|
||||
|
||||
<*
|
||||
Get the value or update and
|
||||
@require @assignable_to(#expr, Value)
|
||||
Get the value or set it to the value
|
||||
|
||||
@require $defined(Value val = #expr)
|
||||
*>
|
||||
macro Value LinkedHashMap.@get_or_set(&map, Key key, Value #expr)
|
||||
{
|
||||
@@ -248,7 +254,7 @@ fn void? LinkedHashMap.remove(&map, Key key) @maydiscard
|
||||
fn void LinkedHashMap.clear(&map)
|
||||
{
|
||||
if (!map.count) return;
|
||||
|
||||
|
||||
LinkedEntry* entry = map.head;
|
||||
while (entry)
|
||||
{
|
||||
@@ -256,12 +262,12 @@ fn void LinkedHashMap.clear(&map)
|
||||
map.free_entry(entry);
|
||||
entry = next;
|
||||
}
|
||||
|
||||
|
||||
foreach (LinkedEntry** &bucket : map.table)
|
||||
{
|
||||
*bucket = null;
|
||||
}
|
||||
|
||||
|
||||
map.count = 0;
|
||||
map.head = null;
|
||||
map.tail = null;
|
||||
@@ -283,10 +289,10 @@ fn Key[] LinkedHashMap.tkeys(&self)
|
||||
fn Key[] LinkedHashMap.keys(&self, Allocator allocator)
|
||||
{
|
||||
if (!self.count) return {};
|
||||
|
||||
|
||||
Key[] list = allocator::alloc_array(allocator, Key, self.count);
|
||||
usz index = 0;
|
||||
|
||||
|
||||
LinkedEntry* entry = self.head;
|
||||
while (entry)
|
||||
{
|
||||
@@ -337,7 +343,7 @@ fn Value[] LinkedHashMap.values(&self, Allocator allocator)
|
||||
fn bool LinkedHashMap.has_value(&map, Value v) @if(VALUE_IS_EQUATABLE)
|
||||
{
|
||||
if (!map.count) return false;
|
||||
|
||||
|
||||
LinkedEntry* entry = map.head;
|
||||
while (entry)
|
||||
{
|
||||
@@ -395,7 +401,7 @@ fn void LinkedHashMap.add_entry(&map, uint hash, Key key, Value value, uint buck
|
||||
$if COPY_KEYS:
|
||||
key = key.copy(map.allocator);
|
||||
$endif
|
||||
|
||||
|
||||
LinkedEntry* entry = allocator::new(map.allocator, LinkedEntry, {
|
||||
.hash = hash,
|
||||
.key = key,
|
||||
@@ -404,10 +410,10 @@ fn void LinkedHashMap.add_entry(&map, uint hash, Key key, Value value, uint buck
|
||||
.before = map.tail,
|
||||
.after = null
|
||||
});
|
||||
|
||||
|
||||
// Update bucket chain
|
||||
map.table[bucket_index] = entry;
|
||||
|
||||
|
||||
// Update linked list
|
||||
if (map.tail)
|
||||
{
|
||||
@@ -419,7 +425,7 @@ fn void LinkedHashMap.add_entry(&map, uint hash, Key key, Value value, uint buck
|
||||
map.head = entry;
|
||||
}
|
||||
map.tail = entry;
|
||||
|
||||
|
||||
if (map.count++ >= map.threshold)
|
||||
{
|
||||
map.resize(map.table.len * 2);
|
||||
@@ -430,28 +436,28 @@ fn void LinkedHashMap.resize(&map, uint new_capacity) @private
|
||||
{
|
||||
LinkedEntry*[] old_table = map.table;
|
||||
uint old_capacity = old_table.len;
|
||||
|
||||
|
||||
if (old_capacity == MAXIMUM_CAPACITY)
|
||||
{
|
||||
map.threshold = uint.max;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
LinkedEntry*[] new_table = allocator::new_array(map.allocator, LinkedEntry*, new_capacity);
|
||||
map.table = new_table;
|
||||
map.threshold = (uint)(new_capacity * map.load_factor);
|
||||
|
||||
|
||||
// Rehash all entries - linked list order remains unchanged
|
||||
foreach (uint i, LinkedEntry *e : old_table)
|
||||
{
|
||||
if (!e) continue;
|
||||
|
||||
|
||||
// Split the bucket chain into two chains based on new bit
|
||||
LinkedEntry* lo_head = null;
|
||||
LinkedEntry* lo_tail = null;
|
||||
LinkedEntry* hi_head = null;
|
||||
LinkedEntry* hi_tail = null;
|
||||
|
||||
|
||||
do
|
||||
{
|
||||
LinkedEntry* next = e.next;
|
||||
@@ -483,7 +489,7 @@ fn void LinkedHashMap.resize(&map, uint new_capacity) @private
|
||||
e = next;
|
||||
}
|
||||
while (e);
|
||||
|
||||
|
||||
if (lo_tail)
|
||||
{
|
||||
lo_tail.next = null;
|
||||
@@ -495,7 +501,7 @@ fn void LinkedHashMap.resize(&map, uint new_capacity) @private
|
||||
new_table[i + old_capacity] = hi_head;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
map.free_internal(old_table.ptr);
|
||||
}
|
||||
|
||||
@@ -561,12 +567,12 @@ fn void LinkedHashMap.free_internal(&map, void* ptr) @inline @private
|
||||
fn bool LinkedHashMap.remove_entry_for_key(&map, Key key) @private
|
||||
{
|
||||
if (!map.count) return false;
|
||||
|
||||
|
||||
uint hash = rehash(key.hash());
|
||||
uint i = index_for(hash, map.table.len);
|
||||
LinkedEntry* prev = null;
|
||||
LinkedEntry* e = map.table[i];
|
||||
|
||||
|
||||
while (e)
|
||||
{
|
||||
if (e.hash == hash && equals(key, e.key))
|
||||
@@ -579,7 +585,7 @@ fn bool LinkedHashMap.remove_entry_for_key(&map, Key key) @private
|
||||
{
|
||||
map.table[i] = e.next;
|
||||
}
|
||||
|
||||
|
||||
if (e.before)
|
||||
{
|
||||
e.before.after = e.after;
|
||||
@@ -588,7 +594,7 @@ fn bool LinkedHashMap.remove_entry_for_key(&map, Key key) @private
|
||||
{
|
||||
map.head = e.after;
|
||||
}
|
||||
|
||||
|
||||
if (e.after)
|
||||
{
|
||||
e.after.before = e.before;
|
||||
@@ -597,7 +603,7 @@ fn bool LinkedHashMap.remove_entry_for_key(&map, Key key) @private
|
||||
{
|
||||
map.tail = e.before;
|
||||
}
|
||||
|
||||
|
||||
map.count--;
|
||||
map.free_entry(e);
|
||||
return true;
|
||||
|
||||
@@ -11,20 +11,27 @@ struct LinkedEntry
|
||||
{
|
||||
uint hash;
|
||||
Value value;
|
||||
LinkedEntry* next; // For bucket chain
|
||||
LinkedEntry* before; // Previous in insertion order
|
||||
LinkedEntry* after; // Next in insertion order
|
||||
<* For bucket chain *>
|
||||
LinkedEntry* next;
|
||||
<* Previous in insertion order *>
|
||||
LinkedEntry* before;
|
||||
<* Next in insertion order *>
|
||||
LinkedEntry* after;
|
||||
}
|
||||
|
||||
struct LinkedHashSet (Printable)
|
||||
{
|
||||
LinkedEntry*[] table;
|
||||
Allocator allocator;
|
||||
usz count; // Number of elements
|
||||
usz threshold; // Resize limit
|
||||
<* Number of elements *>
|
||||
usz count;
|
||||
<* Resize limit *>
|
||||
usz threshold;
|
||||
float load_factor;
|
||||
LinkedEntry* head; // First inserted LinkedEntry
|
||||
LinkedEntry* tail; // Last inserted LinkedEntry
|
||||
<* Resize limit *>
|
||||
LinkedEntry* head;
|
||||
<* First inserted LinkedEntry *>
|
||||
LinkedEntry* tail;
|
||||
}
|
||||
|
||||
fn int LinkedHashSet.len(&self) @operator(len) => (int) self.count;
|
||||
@@ -43,7 +50,7 @@ fn LinkedHashSet* LinkedHashSet.init(&self, Allocator allocator, usz capacity =
|
||||
self.threshold = (usz)(capacity * load_factor);
|
||||
self.load_factor = load_factor;
|
||||
self.table = allocator::new_array(allocator, LinkedEntry*, capacity);
|
||||
|
||||
|
||||
self.head = null;
|
||||
self.tail = null;
|
||||
return self;
|
||||
@@ -304,7 +311,7 @@ fn void LinkedHashSet.free(&set)
|
||||
fn void LinkedHashSet.clear(&set)
|
||||
{
|
||||
if (!set.count) return;
|
||||
|
||||
|
||||
LinkedEntry* entry = set.head;
|
||||
while (entry)
|
||||
{
|
||||
@@ -312,12 +319,12 @@ fn void LinkedHashSet.clear(&set)
|
||||
set.free_entry(entry);
|
||||
entry = next;
|
||||
}
|
||||
|
||||
|
||||
foreach (LinkedEntry** &bucket : set.table)
|
||||
{
|
||||
*bucket = null;
|
||||
}
|
||||
|
||||
|
||||
set.count = 0;
|
||||
set.head = null;
|
||||
set.tail = null;
|
||||
@@ -363,16 +370,16 @@ fn LinkedHashSet LinkedHashSet.intersection(&self, Allocator allocator, LinkedHa
|
||||
{
|
||||
LinkedHashSet result;
|
||||
result.init(allocator, math::min(self.table.len, other.table.len), self.load_factor);
|
||||
|
||||
|
||||
// Iterate through the smaller set for efficiency
|
||||
LinkedHashSet* smaller = self.count <= other.count ? self : other;
|
||||
LinkedHashSet* larger = self.count > other.count ? self : other;
|
||||
|
||||
smaller.@each(;Value value)
|
||||
|
||||
smaller.@each(;Value value)
|
||||
{
|
||||
if (larger.contains(value)) result.add(value);
|
||||
};
|
||||
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -435,7 +442,7 @@ fn bool LinkedHashSet.is_subset(&self, LinkedHashSet* other)
|
||||
{
|
||||
if (self.count == 0) return true;
|
||||
if (self.count > other.count) return false;
|
||||
|
||||
|
||||
self.@each(; Value value) {
|
||||
if (!other.contains(value)) return false;
|
||||
};
|
||||
@@ -453,10 +460,10 @@ fn void LinkedHashSet.add_entry(&set, uint hash, Value value, uint bucket_index)
|
||||
.before = set.tail,
|
||||
.after = null
|
||||
});
|
||||
|
||||
|
||||
// Update bucket chain
|
||||
set.table[bucket_index] = entry;
|
||||
|
||||
|
||||
// Update linked list
|
||||
if (set.tail)
|
||||
{
|
||||
@@ -468,7 +475,7 @@ fn void LinkedHashSet.add_entry(&set, uint hash, Value value, uint bucket_index)
|
||||
set.head = entry;
|
||||
}
|
||||
set.tail = entry;
|
||||
|
||||
|
||||
if (set.count++ >= set.threshold)
|
||||
{
|
||||
set.resize(set.table.len * 2);
|
||||
@@ -479,28 +486,28 @@ fn void LinkedHashSet.resize(&set, usz new_capacity) @private
|
||||
{
|
||||
LinkedEntry*[] old_table = set.table;
|
||||
usz old_capacity = old_table.len;
|
||||
|
||||
|
||||
if (old_capacity == MAXIMUM_CAPACITY)
|
||||
{
|
||||
set.threshold = uint.max;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
LinkedEntry*[] new_table = allocator::new_array(set.allocator, LinkedEntry*, new_capacity);
|
||||
set.table = new_table;
|
||||
set.threshold = (uint)(new_capacity * set.load_factor);
|
||||
|
||||
|
||||
// Rehash all entries - linked list order remains unchanged
|
||||
foreach (uint i, LinkedEntry *e : old_table)
|
||||
{
|
||||
if (!e) continue;
|
||||
|
||||
|
||||
// Split the bucket chain into two chains based on new bit
|
||||
LinkedEntry* lo_head = null;
|
||||
LinkedEntry* lo_tail = null;
|
||||
LinkedEntry* hi_head = null;
|
||||
LinkedEntry* hi_tail = null;
|
||||
|
||||
|
||||
do
|
||||
{
|
||||
LinkedEntry* next = e.next;
|
||||
@@ -532,7 +539,7 @@ fn void LinkedHashSet.resize(&set, usz new_capacity) @private
|
||||
e = next;
|
||||
}
|
||||
while (e);
|
||||
|
||||
|
||||
if (lo_tail)
|
||||
{
|
||||
lo_tail.next = null;
|
||||
@@ -544,7 +551,7 @@ fn void LinkedHashSet.resize(&set, usz new_capacity) @private
|
||||
new_table[i + old_capacity] = hi_head;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
set.free_internal(old_table.ptr);
|
||||
}
|
||||
|
||||
@@ -601,16 +608,16 @@ fn void LinkedHashSet.free_internal(&set, void* ptr) @inline @private
|
||||
|
||||
fn void LinkedHashSet.create_entry(&set, uint hash, Value value, int bucket_index) @private
|
||||
{
|
||||
LinkedEntry* entry = allocator::new(set.allocator, LinkedEntry, {
|
||||
.hash = hash,
|
||||
.value = value,
|
||||
LinkedEntry* entry = allocator::new(set.allocator, LinkedEntry, {
|
||||
.hash = hash,
|
||||
.value = value,
|
||||
.next = set.table[bucket_index],
|
||||
.before = set.tail,
|
||||
.after = null
|
||||
});
|
||||
|
||||
|
||||
set.table[bucket_index] = entry;
|
||||
|
||||
|
||||
// Update linked list
|
||||
if (set.tail)
|
||||
{
|
||||
@@ -622,19 +629,19 @@ fn void LinkedHashSet.create_entry(&set, uint hash, Value value, int bucket_inde
|
||||
set.head = entry;
|
||||
}
|
||||
set.tail = entry;
|
||||
|
||||
|
||||
set.count++;
|
||||
}
|
||||
|
||||
fn bool LinkedHashSet.remove_entry_for_value(&set, Value value) @private
|
||||
{
|
||||
if (!set.count) return false;
|
||||
|
||||
|
||||
uint hash = rehash(value.hash());
|
||||
uint i = index_for(hash, set.table.len);
|
||||
LinkedEntry* prev = null;
|
||||
LinkedEntry* e = set.table[i];
|
||||
|
||||
|
||||
while (e)
|
||||
{
|
||||
if (e.hash == hash && equals(value, e.value))
|
||||
@@ -647,7 +654,7 @@ fn bool LinkedHashSet.remove_entry_for_value(&set, Value value) @private
|
||||
{
|
||||
set.table[i] = e.next;
|
||||
}
|
||||
|
||||
|
||||
if (e.before)
|
||||
{
|
||||
e.before.after = e.after;
|
||||
@@ -656,7 +663,7 @@ fn bool LinkedHashSet.remove_entry_for_value(&set, Value value) @private
|
||||
{
|
||||
set.head = e.after;
|
||||
}
|
||||
|
||||
|
||||
if (e.after)
|
||||
{
|
||||
e.after.before = e.before;
|
||||
@@ -665,7 +672,7 @@ fn bool LinkedHashSet.remove_entry_for_value(&set, Value value) @private
|
||||
{
|
||||
set.tail = e.before;
|
||||
}
|
||||
|
||||
|
||||
set.count--;
|
||||
set.free_entry(e);
|
||||
return true;
|
||||
@@ -715,9 +722,9 @@ fn bool LinkedHashSetIterator.has_next(&self)
|
||||
return self.current && self.current.after != null;
|
||||
}
|
||||
|
||||
fn usz LinkedHashSetIterator.len(&self) @operator(len)
|
||||
fn usz LinkedHashSetIterator.len(&self) @operator(len)
|
||||
{
|
||||
return self.set.count;
|
||||
}
|
||||
|
||||
int dummy @local;
|
||||
int dummy @local;
|
||||
|
||||
@@ -2,13 +2,14 @@
|
||||
// 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};
|
||||
import std::io;
|
||||
|
||||
const ELEMENT_IS_EQUATABLE = types::is_equatable_type(Type);
|
||||
|
||||
struct Node @private
|
||||
struct Node
|
||||
{
|
||||
Node *next;
|
||||
Node *prev;
|
||||
Node* next;
|
||||
Node* prev;
|
||||
Type value;
|
||||
}
|
||||
|
||||
@@ -16,8 +17,31 @@ struct LinkedList
|
||||
{
|
||||
Allocator allocator;
|
||||
usz size;
|
||||
Node *_first;
|
||||
Node *_last;
|
||||
Node* _first;
|
||||
Node* _last;
|
||||
}
|
||||
|
||||
fn usz? LinkedList.to_format(&self, Formatter* f) @dynamic
|
||||
{
|
||||
usz len = f.print("{ ")!;
|
||||
for (Node* node = self._first; node != null; node.next)
|
||||
{
|
||||
len += f.printf(node.next ? "%s, " : "s", node.value)!;
|
||||
}
|
||||
return len + f.print(" }");
|
||||
}
|
||||
|
||||
macro LinkedList @new(Allocator allocator, Type[] #default_values = {})
|
||||
{
|
||||
LinkedList new_list;
|
||||
new_list.init(allocator);
|
||||
new_list.push_all(#default_values);
|
||||
return new_list;
|
||||
}
|
||||
|
||||
macro LinkedList @tnew(Type[] #default_values = {})
|
||||
{
|
||||
return @new(tmem, #default_values);
|
||||
}
|
||||
|
||||
<*
|
||||
@@ -68,6 +92,11 @@ fn void LinkedList.push_front(&self, Type value)
|
||||
self.size++;
|
||||
}
|
||||
|
||||
fn void LinkedList.push_front_all(&self, Type[] value)
|
||||
{
|
||||
foreach_r (v : value) self.push_front(v);
|
||||
}
|
||||
|
||||
fn void LinkedList.push(&self, Type value)
|
||||
{
|
||||
Node *last = self._last;
|
||||
@@ -85,6 +114,11 @@ fn void LinkedList.push(&self, Type value)
|
||||
self.size++;
|
||||
}
|
||||
|
||||
fn void LinkedList.push_all(&self, Type[] value)
|
||||
{
|
||||
foreach (v : value) self.push(v);
|
||||
}
|
||||
|
||||
fn Type? LinkedList.peek(&self) => self.first() @inline;
|
||||
fn Type? LinkedList.peek_last(&self) => self.last() @inline;
|
||||
|
||||
@@ -133,6 +167,7 @@ macro Node* LinkedList.node_at_index(&self, usz index)
|
||||
while (index--) node = node.next;
|
||||
return node;
|
||||
}
|
||||
|
||||
<*
|
||||
@require index < self.size
|
||||
*>
|
||||
@@ -141,6 +176,14 @@ fn Type LinkedList.get(&self, usz index)
|
||||
return self.node_at_index(index).value;
|
||||
}
|
||||
|
||||
<*
|
||||
@require index < self.size
|
||||
*>
|
||||
fn Type* LinkedList.get_ref(&self, usz index)
|
||||
{
|
||||
return &self.node_at_index(index).value;
|
||||
}
|
||||
|
||||
<*
|
||||
@require index < self.size
|
||||
*>
|
||||
@@ -149,6 +192,26 @@ fn void LinkedList.set(&self, usz index, Type element)
|
||||
self.node_at_index(index).value = element;
|
||||
}
|
||||
|
||||
fn usz? LinkedList.index_of(&self, Type t) @if(ELEMENT_IS_EQUATABLE)
|
||||
{
|
||||
for (Node* node = self._first, usz i = 0; node != null; node = node.next, ++i)
|
||||
{
|
||||
if (node.value == t) return i;
|
||||
}
|
||||
return NOT_FOUND?;
|
||||
}
|
||||
|
||||
fn usz? LinkedList.rindex_of(&self, Type t) @if(ELEMENT_IS_EQUATABLE)
|
||||
{
|
||||
for (Node* node = self._last, usz i = self.size - 1; node != null; node = node.prev, --i)
|
||||
{
|
||||
if (node.value == t) return i;
|
||||
if (i == 0) break;
|
||||
}
|
||||
return NOT_FOUND?;
|
||||
}
|
||||
|
||||
|
||||
<*
|
||||
@require index < self.size
|
||||
*>
|
||||
@@ -334,3 +397,69 @@ fn void LinkedList.unlink(&self, Node* x) @private
|
||||
self.free_node(x);
|
||||
self.size--;
|
||||
}
|
||||
|
||||
|
||||
macro bool LinkedList.eq(&self, other) @operator(==) @if(ELEMENT_IS_EQUATABLE)
|
||||
{
|
||||
Node* node1 = self._first;
|
||||
Node* node2 = other._first;
|
||||
while (true)
|
||||
{
|
||||
if (!node1) return node2 == null;
|
||||
if (!node2) return false;
|
||||
if (node1.value != node2.value) return false;
|
||||
node1 = node1.next;
|
||||
node2 = node2.next;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
fn LinkedListArrayView LinkedList.array_view(&self)
|
||||
{
|
||||
return { .list = self, .current_node = self._first };
|
||||
}
|
||||
|
||||
|
||||
struct LinkedListArrayView
|
||||
{
|
||||
LinkedList* list;
|
||||
Node* current_node;
|
||||
usz current_index;
|
||||
}
|
||||
|
||||
fn usz LinkedListArrayView.len(&self) @operator(len) => self.list.size;
|
||||
|
||||
<*
|
||||
@require index < self.list.size
|
||||
*>
|
||||
fn Type LinkedListArrayView.get(&self, usz index) @operator([])
|
||||
{
|
||||
return *self.get_ref(index);
|
||||
}
|
||||
|
||||
<*
|
||||
@require index < self.list.size
|
||||
*>
|
||||
fn Type* LinkedListArrayView.get_ref(&self, usz index) @operator(&[])
|
||||
{
|
||||
if (index == self.list.size - 1)
|
||||
{
|
||||
self.current_node = self.list._last;
|
||||
self.current_index = index;
|
||||
}
|
||||
|
||||
while (self.current_index != index)
|
||||
{
|
||||
switch
|
||||
{
|
||||
case index < self.current_index: // reverse iteration
|
||||
self.current_node = self.current_node.prev;
|
||||
self.current_index--;
|
||||
case index > self.current_index:
|
||||
self.current_node = self.current_node.next;
|
||||
self.current_index++;
|
||||
}
|
||||
}
|
||||
|
||||
return &self.current_node.value;
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ const Allocator LIST_HEAP_ALLOCATOR = (Allocator)&dummy;
|
||||
|
||||
const List ONHEAP = { .allocator = LIST_HEAP_ALLOCATOR };
|
||||
|
||||
macro type_is_overaligned() => Type.alignof > mem::DEFAULT_MEM_ALIGNMENT;
|
||||
macro bool type_is_overaligned() => Type.alignof > mem::DEFAULT_MEM_ALIGNMENT;
|
||||
|
||||
struct List (Printable)
|
||||
{
|
||||
@@ -57,7 +57,7 @@ fn List* List.tinit(&self, usz initial_capacity = 16)
|
||||
fn List* List.init_with_array(&self, Allocator allocator, Type[] values)
|
||||
{
|
||||
self.init(allocator, values.len) @inline;
|
||||
self.add_array(values) @inline;
|
||||
self.push_all(values) @inline;
|
||||
return self;
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ fn List* List.init_with_array(&self, Allocator allocator, Type[] values)
|
||||
fn List* List.tinit_with_array(&self, Type[] values)
|
||||
{
|
||||
self.tinit(values.len) @inline;
|
||||
self.add_array(values) @inline;
|
||||
self.push_all(values) @inline;
|
||||
return self;
|
||||
}
|
||||
|
||||
@@ -137,9 +137,10 @@ fn Type? List.pop_first(&self)
|
||||
*>
|
||||
fn void List.remove_at(&self, usz index)
|
||||
{
|
||||
self.set_size(self.size - 1);
|
||||
if (!self.size || index == self.size) return;
|
||||
self.entries[index .. self.size - 1] = self.entries[index + 1 .. self.size];
|
||||
usz new_size = self.size - 1;
|
||||
defer self.set_size(new_size);
|
||||
if (!new_size || index == new_size) return;
|
||||
self.entries[index .. new_size - 1] = self.entries[index + 1 .. new_size];
|
||||
}
|
||||
|
||||
fn void List.add_all(&self, List* other_list)
|
||||
@@ -198,7 +199,21 @@ fn Type[] List.array_view(&self)
|
||||
@param [in] array
|
||||
@ensure self.size >= array.len
|
||||
*>
|
||||
fn void List.add_array(&self, Type[] array)
|
||||
fn void List.add_array(&self, Type[] array) @deprecated("Use push_all")
|
||||
{
|
||||
if (!array.len) return;
|
||||
self.reserve(array.len);
|
||||
usz index = self.set_size(self.size + array.len);
|
||||
self.entries[index : array.len] = array[..];
|
||||
}
|
||||
|
||||
<*
|
||||
Add the values of an array to this list.
|
||||
|
||||
@param [in] array
|
||||
@ensure self.size >= array.len
|
||||
*>
|
||||
fn void List.push_all(&self, Type[] array)
|
||||
{
|
||||
if (!array.len) return;
|
||||
self.reserve(array.len);
|
||||
|
||||
@@ -185,7 +185,7 @@ fn void Object.set_object(&self, String key, Object* new_object) @private
|
||||
|
||||
<*
|
||||
@require self.allocator != null : "This object is not properly initialized, was it really created using 'new'"
|
||||
@require !@typeis(value, void*) ||| value == null : "void pointers cannot be stored in an object"
|
||||
@require $typeof(value) != void* ||| value == null : "void pointers cannot be stored in an object"
|
||||
*>
|
||||
macro Object* Object.object_from_value(&self, value) @private
|
||||
{
|
||||
@@ -203,7 +203,7 @@ macro Object* Object.object_from_value(&self, value) @private
|
||||
return value;
|
||||
$case $Type.typeid == void*.typeid:
|
||||
return &NULL_OBJECT;
|
||||
$case @assignable_to(value, String):
|
||||
$case $defined(String x = value):
|
||||
return new_string(value, self.allocator);
|
||||
$default:
|
||||
$error "Unsupported object type.";
|
||||
|
||||
@@ -1,16 +1,22 @@
|
||||
module std::collections::pair{Type1, Type2};
|
||||
import std::io;
|
||||
|
||||
struct Pair
|
||||
struct Pair (Printable)
|
||||
{
|
||||
Type1 first;
|
||||
Type2 second;
|
||||
}
|
||||
|
||||
fn usz? Pair.to_format(&self, Formatter* f) @dynamic
|
||||
{
|
||||
return f.printf("{ %s, %s }", self.first, self.second);
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&out] a
|
||||
@param [&out] b
|
||||
@require @assignable_to(self.first, $typeof(*a)) : "You cannot assign the first value to a"
|
||||
@require @assignable_to(self.second, $typeof(*b)) : "You cannot assign the second value to b"
|
||||
@require $defined(*a = self.first) : "You cannot assign the first value to a"
|
||||
@require $defined(*b = self.second) : "You cannot assign the second value to b"
|
||||
*>
|
||||
macro void Pair.unpack(&self, a, b)
|
||||
{
|
||||
@@ -18,22 +24,34 @@ macro void Pair.unpack(&self, a, b)
|
||||
*b = self.second;
|
||||
}
|
||||
|
||||
module std::collections::triple{Type1, Type2, Type3};
|
||||
fn bool Pair.equal(self, Pair other) @operator(==) @if (types::has_equals(Type1) &&& types::has_equals(Type2))
|
||||
{
|
||||
return self.first == other.first && self.second == other.second;
|
||||
}
|
||||
|
||||
struct Triple
|
||||
|
||||
|
||||
module std::collections::triple{Type1, Type2, Type3};
|
||||
import std::io;
|
||||
|
||||
struct Triple (Printable)
|
||||
{
|
||||
Type1 first;
|
||||
Type2 second;
|
||||
Type3 third;
|
||||
}
|
||||
|
||||
fn usz? Triple.to_format(&self, Formatter* f) @dynamic
|
||||
{
|
||||
return f.printf("{ %s, %s, %s }", self.first, self.second, self.third);
|
||||
}
|
||||
<*
|
||||
@param [&out] a
|
||||
@param [&out] b
|
||||
@param [&out] c
|
||||
@require @assignable_to(self.first, $typeof(*a)) : "You cannot assign the first value to a"
|
||||
@require @assignable_to(self.second, $typeof(*b)) : "You cannot assign the second value to b"
|
||||
@require @assignable_to(self.third, $typeof(*c)) : "You cannot assign the second value to c"
|
||||
@require $defined(*a = self.first) : "You cannot assign the first value to a"
|
||||
@require $defined(*b = self.second) : "You cannot assign the second value to b"
|
||||
@require $defined(*c = self.third) : "You cannot assign the second value to c"
|
||||
*>
|
||||
macro void Triple.unpack(&self, a, b, c)
|
||||
{
|
||||
@@ -42,6 +60,12 @@ macro void Triple.unpack(&self, a, b, c)
|
||||
*c = self.third;
|
||||
}
|
||||
|
||||
fn bool Triple.equal(self, Triple other) @operator(==) @if (types::has_equals(Type1) &&& types::has_equals(Type2) &&& types::has_equals(Type3))
|
||||
{
|
||||
return self.first == other.first && self.second == other.second && self.third == other.third;
|
||||
}
|
||||
|
||||
|
||||
module std::collections::tuple{Type1, Type2};
|
||||
|
||||
struct Tuple @deprecated("Use 'Pair' instead")
|
||||
|
||||
@@ -7,10 +7,12 @@ const uint PIXELS_MAX = 400000000;
|
||||
Purely informative. It will be saved to the file header,
|
||||
but does not affect how chunks are en-/decoded.
|
||||
*>
|
||||
enum QOIColorspace : char (char id)
|
||||
enum QOIColorspace : const char
|
||||
{
|
||||
SRGB = 0, // sRGB with linear alpha
|
||||
LINEAR = 1 // all channels linear
|
||||
<* sRGB with linear alpha *>
|
||||
SRGB = 0,
|
||||
<* all channels linear *>
|
||||
LINEAR = 1
|
||||
}
|
||||
|
||||
<*
|
||||
@@ -19,7 +21,7 @@ enum QOIColorspace : char (char id)
|
||||
AUTO can be used when decoding to automatically determine
|
||||
the channels from the file's header.
|
||||
*>
|
||||
enum QOIChannels : char (char id)
|
||||
enum QOIChannels : const inline char
|
||||
{
|
||||
AUTO = 0,
|
||||
RGB = 3,
|
||||
@@ -132,7 +134,7 @@ fn char[]? encode(Allocator allocator, char[] input, QOIDesc* desc) @nodiscard
|
||||
if (pixels > PIXELS_MAX) return TOO_MANY_PIXELS?;
|
||||
|
||||
// check input data size
|
||||
uint image_size = pixels * desc.channels.id;
|
||||
uint image_size = pixels * desc.channels;
|
||||
if (image_size != input.len) return INVALID_DATA?;
|
||||
|
||||
// allocate memory for encoded data (output)
|
||||
@@ -146,13 +148,13 @@ fn char[]? encode(Allocator allocator, char[] input, QOIDesc* desc) @nodiscard
|
||||
.be_magic = bswap('qoif'),
|
||||
.be_width = bswap(desc.width),
|
||||
.be_height = bswap(desc.height),
|
||||
.channels = desc.channels.id,
|
||||
.colorspace = desc.colorspace.id
|
||||
.channels = desc.channels,
|
||||
.colorspace = desc.colorspace
|
||||
};
|
||||
|
||||
uint pos = Header.sizeof; // Current position in output
|
||||
uint loc; // Current position in image (top-left corner)
|
||||
uint loc_end = image_size - desc.channels.id; // End of image data
|
||||
uint loc_end = image_size - desc.channels; // End of image data
|
||||
char run_length = 0; // Length of the current run
|
||||
|
||||
Pixel[64] palette; // Zero-initialized by default
|
||||
@@ -163,7 +165,7 @@ fn char[]? encode(Allocator allocator, char[] input, QOIDesc* desc) @nodiscard
|
||||
ichar[<3>] luma; // ...and luma
|
||||
|
||||
// write chunks
|
||||
for (loc = 0; loc < image_size; loc += desc.channels.id)
|
||||
for (loc = 0; loc < image_size; loc += desc.channels)
|
||||
{
|
||||
// set previous pixel
|
||||
prev = p;
|
||||
@@ -292,8 +294,8 @@ fn char[]? decode(Allocator allocator, char[] data, QOIDesc* desc, QOIChannels c
|
||||
// copy header data to desc
|
||||
desc.width = bswap(header.be_width);
|
||||
desc.height = bswap(header.be_height);
|
||||
desc.channels = @enumcast(QOIChannels, header.channels)!; // Rethrow if invalid
|
||||
desc.colorspace = @enumcast(QOIColorspace, header.colorspace)!; // Rethrow if invalid
|
||||
desc.channels = header.channels;
|
||||
desc.colorspace = header.colorspace;
|
||||
if (desc.channels == AUTO) return INVALID_DATA?; // Channels must be specified in the header
|
||||
|
||||
// check width and height
|
||||
@@ -314,11 +316,11 @@ fn char[]? decode(Allocator allocator, char[] data, QOIDesc* desc, QOIChannels c
|
||||
if (channels == AUTO) channels = desc.channels;
|
||||
|
||||
// allocate memory for image data
|
||||
usz image_size = (usz)pixels * channels.id;
|
||||
usz image_size = (usz)pixels * channels;
|
||||
char[] image = allocator::alloc_array(allocator, char, image_size);
|
||||
defer catch allocator::free(allocator, image);
|
||||
|
||||
for (loc = 0; loc < image_size; loc += channels.id)
|
||||
for (loc = 0; loc < image_size; loc += channels)
|
||||
{
|
||||
// get chunk tag
|
||||
tag = data[pos];
|
||||
@@ -391,31 +393,22 @@ const OP_RUN = 0b11;
|
||||
|
||||
struct Header @packed
|
||||
{
|
||||
uint be_magic; // magic bytes "qoif"
|
||||
uint be_width; // image width in pixels (BE)
|
||||
uint be_height; // image height in pixels (BE)
|
||||
<* magic bytes "qoif" *>
|
||||
uint be_magic;
|
||||
<* image width in pixels (BE) *>
|
||||
uint be_width;
|
||||
<* image height in pixels (BE) *>
|
||||
uint be_height;
|
||||
|
||||
// informative fields
|
||||
char channels; // 3 = RGB, 4 = RGB
|
||||
char colorspace; // 0 = sRGB with linear alpha, 1 = all channels linear
|
||||
<* 3 = RGB, 4 = RGB *>
|
||||
QOIChannels channels;
|
||||
<* 0 = sRGB with linear alpha, 1 = all channels linear *>
|
||||
QOIColorspace colorspace;
|
||||
}
|
||||
|
||||
const char[*] END_OF_STREAM = {0, 0, 0, 0, 0, 0, 0, 1};
|
||||
|
||||
// inefficient, but it's only run once at a time
|
||||
|
||||
<*
|
||||
@return? INVALID_DATA
|
||||
*>
|
||||
macro @enumcast($Type, raw)
|
||||
{
|
||||
foreach (value : $Type.values)
|
||||
{
|
||||
if (value.id == raw) return value;
|
||||
}
|
||||
return INVALID_DATA?;
|
||||
}
|
||||
|
||||
typedef Pixel = inline char[<4>];
|
||||
macro char Pixel.hash(Pixel p)
|
||||
{
|
||||
|
||||
@@ -13,7 +13,7 @@ import std::math;
|
||||
|
||||
The advantage over the BackedArenaAllocator, is that when allocating beyond the first "page", it will
|
||||
retain the characteristics of an arena allocator (allocating a large piece of memory then handing off
|
||||
memory from that memory), wheras the BackedArenaAllocator will have heap allocator characteristics.
|
||||
memory from that memory), whereas the BackedArenaAllocator will have heap allocator characteristics.
|
||||
*>
|
||||
struct DynamicArenaAllocator (Allocator)
|
||||
{
|
||||
|
||||
@@ -19,7 +19,7 @@ alias AllocMap = HashMap { uptr, Allocation };
|
||||
// It tracks allocations using a hash map but
|
||||
// is not compatible with allocators that uses mark()
|
||||
//
|
||||
// It is also embarassingly single-threaded, so
|
||||
// It is also embarrassingly single-threaded, so
|
||||
// do not use it to track allocations that cross threads.
|
||||
|
||||
struct TrackingAllocator (Allocator)
|
||||
@@ -216,4 +216,4 @@ fn void? TrackingAllocator.fprint_report(&self, OutStream out) => @pool()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,9 +22,12 @@ struct Vmem (Allocator)
|
||||
|
||||
bitstruct VmemOptions : int
|
||||
{
|
||||
bool shrink_on_reset; // Release memory on reset
|
||||
bool protect_unused_pages; // Protect unused pages on reset
|
||||
bool scratch_released_data; // Overwrite released data with 0xAA
|
||||
<* Release memory on reset *>
|
||||
bool shrink_on_reset;
|
||||
<* Protect unused pages on reset *>
|
||||
bool protect_unused_pages;
|
||||
<* Overwrite released data with 0xAA *>
|
||||
bool scratch_released_data;
|
||||
}
|
||||
|
||||
<*
|
||||
@@ -38,11 +41,11 @@ bitstruct VmemOptions : int
|
||||
fn void? Vmem.init(&self, usz preferred_size, usz reserve_page_size = 0, VmemOptions options = { true, true, env::COMPILER_SAFE_MODE }, usz min_size = 0)
|
||||
{
|
||||
static usz page_size = 0;
|
||||
if (!page_size) page_size = mem::os_pagesize();
|
||||
if (page_size < reserve_page_size) page_size = reserve_page_size;
|
||||
preferred_size = mem::aligned_offset(preferred_size, page_size);
|
||||
if (!min_size) min_size = max(preferred_size / 1024, 1);
|
||||
VirtualMemory? memory = mem::OUT_OF_MEMORY?;
|
||||
if (!page_size) page_size = mem::os_pagesize();
|
||||
if (page_size < reserve_page_size) page_size = reserve_page_size;
|
||||
preferred_size = mem::aligned_offset(preferred_size, page_size);
|
||||
if (!min_size) min_size = max(preferred_size / 1024, 1);
|
||||
VirtualMemory? memory = mem::OUT_OF_MEMORY?;
|
||||
while (preferred_size >= min_size)
|
||||
{
|
||||
memory = vm::virtual_alloc(preferred_size, PROTECTED);
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
module std::core::array;
|
||||
import std::core::array::slice;
|
||||
import std::collections::pair, std::io;
|
||||
|
||||
<*
|
||||
Returns true if the array contains at least one element, else false
|
||||
|
||||
|
||||
@param [in] array
|
||||
@param [in] element
|
||||
@require @typekind(array) == SLICE || @typekind(array) == ARRAY
|
||||
@require @typeis(array[0], $typeof(element)) : "array and element must have the same type"
|
||||
@require $kindof(array) == SLICE || $kindof(array) == ARRAY
|
||||
@require @typematch(array[0], element) : "array and element must have the same type"
|
||||
*>
|
||||
macro bool contains(array, element)
|
||||
{
|
||||
@@ -15,18 +15,21 @@ macro bool contains(array, element)
|
||||
{
|
||||
if (*item == element) return true;
|
||||
}
|
||||
return false;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
<*
|
||||
Return the first index of element found in the array, searching from the start.
|
||||
|
||||
|
||||
@param [in] array
|
||||
@param [in] element
|
||||
@require $kindof(array) == SLICE || $kindof(array) == ARRAY
|
||||
@require @typematch(array[0], element) : "array and element must have the same type"
|
||||
@return "the first index of the element"
|
||||
@return? NOT_FOUND
|
||||
*>
|
||||
macro index_of(array, element)
|
||||
macro usz? index_of(array, element)
|
||||
{
|
||||
foreach (i, &e : array)
|
||||
{
|
||||
@@ -35,6 +38,7 @@ macro index_of(array, element)
|
||||
return NOT_FOUND?;
|
||||
}
|
||||
|
||||
|
||||
<*
|
||||
Slice a 2d array and create a Slice2d from it.
|
||||
|
||||
@@ -44,9 +48,9 @@ macro index_of(array, element)
|
||||
@param xlen : "The length of the slice in x, defaults to the length of the array"
|
||||
@param ylen : "The length of the slice in y, defaults to the length of the array"
|
||||
@return "A Slice2d from the array"
|
||||
@require @typekind(array_ptr) == POINTER
|
||||
@require @typekind(*array_ptr) == VECTOR || @typekind(*array_ptr) == ARRAY
|
||||
@require @typekind((*array_ptr)[0]) == VECTOR || @typekind((*array_ptr)[0]) == ARRAY
|
||||
@require $kindof(array_ptr) == POINTER
|
||||
@require $kindof(*array_ptr) == VECTOR || $kindof(*array_ptr) == ARRAY
|
||||
@require $kindof((*array_ptr)[0]) == VECTOR || $kindof((*array_ptr)[0]) == ARRAY
|
||||
*>
|
||||
macro slice2d(array_ptr, x = 0, xlen = 0, y = 0, ylen = 0)
|
||||
{
|
||||
@@ -59,13 +63,13 @@ macro slice2d(array_ptr, x = 0, xlen = 0, y = 0, ylen = 0)
|
||||
|
||||
<*
|
||||
Return the first index of element found in the array, searching in reverse from the end.
|
||||
|
||||
|
||||
@param [in] array
|
||||
@param [in] element
|
||||
@return "the last index of the element"
|
||||
@return? NOT_FOUND
|
||||
*>
|
||||
macro rindex_of(array, element)
|
||||
macro usz? rindex_of(array, element)
|
||||
{
|
||||
foreach_r (i, &e : array)
|
||||
{
|
||||
@@ -74,15 +78,16 @@ macro rindex_of(array, element)
|
||||
return NOT_FOUND?;
|
||||
}
|
||||
|
||||
|
||||
<*
|
||||
Concatenate two arrays or slices, returning a slice 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) == SLICE || @typekind(arr1) == ARRAY
|
||||
@require @typekind(arr2) == SLICE || @typekind(arr2) == ARRAY
|
||||
@require @typeis(arr1[0], $typeof(arr2[0])) : "Arrays must have the same type"
|
||||
@require $kindof(arr1) == SLICE || $kindof(arr1) == ARRAY
|
||||
@require $kindof(arr2) == SLICE || $kindof(arr2) == ARRAY
|
||||
@require @typematch(arr1[0], arr2[0]) : "Arrays must have the same type"
|
||||
@ensure result.len == arr1.len + arr2.len
|
||||
*>
|
||||
macro concat(Allocator allocator, arr1, arr2) @nodiscard
|
||||
@@ -99,15 +104,450 @@ macro concat(Allocator allocator, arr1, arr2) @nodiscard
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
<*
|
||||
Concatenate two arrays or slices, returning a slice containing the concatenation of them,
|
||||
allocated using the temp allocator.
|
||||
|
||||
@param [in] arr1
|
||||
@param [in] arr2
|
||||
@require @typekind(arr1) == SLICE || @typekind(arr1) == ARRAY
|
||||
@require @typekind(arr2) == SLICE || @typekind(arr2) == ARRAY
|
||||
@require @typeis(arr1[0], $typeof(arr2[0])) : "Arrays must have the same type"
|
||||
@require $kindof(arr1) == SLICE || $kindof(arr1) == ARRAY
|
||||
@require $kindof(arr2) == SLICE || $kindof(arr2) == ARRAY
|
||||
@require @typematch(arr1[0], arr2[0]) : "Arrays must have the same type"
|
||||
@ensure return.len == arr1.len + arr2.len
|
||||
*>
|
||||
macro tconcat(arr1, arr2) @nodiscard => concat(tmem, arr1, arr2);
|
||||
macro tconcat(arr1, arr2) @nodiscard => concat(tmem, arr1, arr2);
|
||||
|
||||
|
||||
<*
|
||||
Apply a reduction/folding operation to an iterable type. This walks along the input array
|
||||
and applies an `#operation` to each value, returning it to the `identity` (or "accumulator")
|
||||
base value.
|
||||
|
||||
For example:
|
||||
```c3
|
||||
int[] my_slice = { 1, 8, 12 };
|
||||
int folded = array::@reduce(my_slice, 2, fn (i, e) => i * e);
|
||||
assert(folded == (2 * 1 * 8 * 12));
|
||||
```
|
||||
|
||||
Notice how the given `identity` value started the multiplication chain at 2. When enumerating
|
||||
`my_slice`, each element is accumulated onto the `identity` value with each sequential iteration.
|
||||
```
|
||||
i = 2; // identity value
|
||||
i *= 1; // my_slice[0]
|
||||
i *= 8; // my_slice[1]
|
||||
i *= 12; // my_slice[2]
|
||||
```
|
||||
|
||||
@param [in] array
|
||||
@param identity
|
||||
@param #operation : "The reduction/folding lambda function or function pointer to apply."
|
||||
|
||||
@require @is_valid_list(array) : "Expected a valid list"
|
||||
@require $defined($typefrom(@reduce_fn(array, identity)) $func = #operation) : "Invalid lambda or function pointer type"
|
||||
*>
|
||||
macro @reduce(array, identity, #operation)
|
||||
{
|
||||
$typefrom(@reduce_fn(array, identity)) $func = #operation;
|
||||
foreach (index, element : array) identity = $func(identity, element, index);
|
||||
return identity;
|
||||
}
|
||||
|
||||
<*
|
||||
Apply a summation operator (+) to an identity value across a span of array elements
|
||||
and return the final accumulated result.
|
||||
|
||||
@pure
|
||||
|
||||
@param [in] array
|
||||
@param identity_value : "The base accumulator value to use for the sum"
|
||||
|
||||
@require @is_valid_list(array) : "Expected a valid list"
|
||||
@require $defined(array[0] + array[0]) : "Array element type must implement the '+' operator"
|
||||
@require $defined($typeof(array[0]) t = identity_value) : "The identity type must be assignable to the array element type"
|
||||
*>
|
||||
macro @sum(array, identity_value = 0)
|
||||
{
|
||||
return @reduce(array, ($typeof(array[0]))identity_value, fn (acc, e, u) => acc + e);
|
||||
}
|
||||
|
||||
<*
|
||||
Apply a product operator (*) to an identity value across a span of array elements
|
||||
and return the final accumulated result.
|
||||
|
||||
@pure
|
||||
|
||||
@param [in] array
|
||||
@param identity_value : "The base accumulator value to use for the product"
|
||||
|
||||
@require @is_valid_list(array) : "Expected a valid list"
|
||||
@require $defined(array[0] * array[0]) : "Array element type must implement the '*' operator"
|
||||
@require $defined($typeof(array[0]) t = identity_value) : "The identity type must be assignable to the array element type"
|
||||
*>
|
||||
macro @product(array, identity_value = 1)
|
||||
{
|
||||
return @reduce(array, ($typeof(array[0]))identity_value, fn (acc, e, u) => acc * e);
|
||||
}
|
||||
|
||||
<*
|
||||
Applies a given predicate function to each element of an array and returns a new
|
||||
array of `usz` values, each element representing an index within the original array
|
||||
where the predicate returned `true`.
|
||||
|
||||
The `.len` value of the returned array can also be used to quickly identify how many
|
||||
input array elements matched the predicate.
|
||||
|
||||
For example:
|
||||
```c3
|
||||
int[] arr = { 0, 20, 4, 30 };
|
||||
int[] matched_indices = array::@indices_of(mem, arr, fn (u, a) => a > 10);
|
||||
```
|
||||
|
||||
The `matched_indices` variable should contain a dynamically-allocated array of `[1, 3]`,
|
||||
and thus its count indicates that 2 of the 4 elements matched the predicate condition.
|
||||
|
||||
@param [&inout] allocator
|
||||
@param [in] array
|
||||
@param #predicate
|
||||
|
||||
@require @is_valid_list(array) : "Expected a valid list"
|
||||
@require $defined($typefrom(@predicate_fn(array)) p = #predicate)
|
||||
*>
|
||||
macro usz[] @indices_of(Allocator allocator, array, #predicate)
|
||||
{
|
||||
usz[] results = allocator::new_array(allocator, usz, lengthof(array));
|
||||
usz matches;
|
||||
|
||||
$typefrom(@predicate_fn(array)) $predicate = #predicate;
|
||||
foreach (index, element : array)
|
||||
{
|
||||
if ($predicate(element, index)) results[matches++] = index;
|
||||
}
|
||||
|
||||
return results[:matches];
|
||||
}
|
||||
|
||||
<*
|
||||
Array `@indices_of` using the temp allocator.
|
||||
|
||||
@param [in] array
|
||||
@param #predicate
|
||||
|
||||
@require @is_valid_list(array) : "Expected a valid list"
|
||||
@require $defined($typefrom(@predicate_fn(array)) p = #predicate)
|
||||
*>
|
||||
macro usz[] @tindices_of(array, #predicate)
|
||||
{
|
||||
return @indices_of(tmem, array, #predicate);
|
||||
}
|
||||
|
||||
|
||||
<*
|
||||
Applies a predicate function to each element of an input array and returns a new array
|
||||
containing shallow copies of _only_ the elements for which the predicate function returned
|
||||
a `true` value.
|
||||
|
||||
For example:
|
||||
```c3
|
||||
int[] my_arr = { 1, 2, 4, 10, 11, 45 };
|
||||
int[] evens = array::@filter(mem, my_arr, fn (e, u) => !(e % 2));
|
||||
assert(evens == (int[]){2, 4, 10 });
|
||||
```
|
||||
|
||||
@param [&inout] allocator
|
||||
@param [in] array
|
||||
@param #predicate
|
||||
|
||||
@require @is_valid_list(array) : "Expected a valid list"
|
||||
@require $defined($typefrom(@predicate_fn(array)) p = #predicate)
|
||||
*>
|
||||
macro @filter(Allocator allocator, array, #predicate) @nodiscard
|
||||
{
|
||||
var $InnerType = $typeof(array[0]);
|
||||
|
||||
usz[] matched_indices = @indices_of(allocator, array, #predicate);
|
||||
defer allocator::free(allocator, matched_indices.ptr); // can free this upon leaving this call
|
||||
|
||||
if (!matched_indices.len) return ($InnerType[]){};
|
||||
|
||||
$InnerType[] result = allocator::new_array(allocator, $InnerType, matched_indices.len);
|
||||
|
||||
foreach (i, index : matched_indices) result[i] = array[index];
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
<*
|
||||
Array `@filter` using the temp allocator.
|
||||
|
||||
@param [in] array
|
||||
@param #predicate
|
||||
|
||||
@require @is_valid_list(array) : "Expected a valid list"
|
||||
@require $defined($typefrom(@predicate_fn(array)) p = #predicate)
|
||||
*>
|
||||
macro @tfilter(array, #predicate) @nodiscard
|
||||
{
|
||||
return @filter(tmem, array, #predicate);
|
||||
}
|
||||
|
||||
|
||||
<*
|
||||
Returns `true` if _any_ element of the input array returns `true` when
|
||||
the `#predicate` function is applied.
|
||||
|
||||
@param [in] array
|
||||
@param #predicate
|
||||
|
||||
@require @is_valid_list(array) : "Expected a valid list"
|
||||
@require $defined($typefrom(@predicate_fn(array)) p = #predicate)
|
||||
*>
|
||||
macro bool @any(array, #predicate)
|
||||
{
|
||||
$typefrom(@predicate_fn(array)) $predicate = #predicate;
|
||||
foreach (index, element : array) if ($predicate(element, index)) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
<*
|
||||
Returns `true` if _all_ elements of the input array return `true` when
|
||||
the `#predicate` function is applied.
|
||||
|
||||
@param [in] array
|
||||
@param #predicate
|
||||
|
||||
@require @is_valid_list(array) : "Expected a valid list"
|
||||
@require $defined($typefrom(@predicate_fn(array)) p = #predicate)
|
||||
*>
|
||||
macro bool @all(array, #predicate)
|
||||
{
|
||||
$typefrom(@predicate_fn(array)) $predicate = #predicate;
|
||||
foreach (index, element : array) if (!$predicate(element, index)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
<*
|
||||
Zip together two separate arrays/slices into a single array of Pairs or return values. Values will
|
||||
be collected up to the length of the shorter array if `fill_with` is left undefined; otherwise, they
|
||||
will be collected up to the length of the LONGER array, with missing values in the shorter array being
|
||||
assigned to the value of `fill_with`. Return array elements do not have to be of the same type.
|
||||
|
||||
For example:
|
||||
```c3
|
||||
uint[] chosen_session_ids = server::get_random_sessions(instance)[:128];
|
||||
String[200] refreshed_session_keys = prng::new_keys_batch();
|
||||
|
||||
Pair { uint, String }[] sessions_meta = array::zip(mem, chosen_session_ids, refreshed_session_keys);
|
||||
// The resulting Pair{}[] slice is then length of the shortest of the two arrays, so 128.
|
||||
|
||||
foreach (i, &sess : sessions:meta) {
|
||||
// distribute new session keys to associated instance IDs
|
||||
}
|
||||
```
|
||||
|
||||
Or:
|
||||
```c3
|
||||
String[] client_names = server::online_usernames(instance);
|
||||
uint128[] session_ids = server::user_keys();
|
||||
|
||||
// in this example, we 'know' ahead of time that 'session_ids' can only ever be SHORTER
|
||||
// than 'client_names', but never longer, because it's possible new users have logged
|
||||
// in without getting whatever this 'session ID' is delegated to them.
|
||||
Pair { String, uint128 }[] zipped = array::tzip(client_names, session_ids, fill_with: uint128.max);
|
||||
|
||||
server::refresh_session_keys_by_pair(zipped)!;
|
||||
```
|
||||
|
||||
### When an `operation` is supplied...
|
||||
Apply an operation to each element of two slices or arrays and return the results of
|
||||
each operation into a newly allocated array.
|
||||
|
||||
This essentially combines Iterable1 with Iterable2 using the `operation` functor.
|
||||
|
||||
See the functional `zipWith` construct, which has a more appropriate name than, e.g., `map`;
|
||||
a la: https://hackage.haskell.org/package/base-4.21.0.0/docs/Prelude.html#v:zipWith
|
||||
|
||||
Similar to "normal" `zip`, this macro pads the shorter input array with a given `fill_with`, or
|
||||
an empty value if one isn't supplied. This `fill_with` is supplied to the `operation` functor
|
||||
_BEFORE_ calculating its result while zipping.
|
||||
|
||||
For example: a functor of `fn char (char a, char b) => a + b` with a `fill_with` of 7,
|
||||
where the `left` array is the shorter iterable, will put 7 into that lambda in each place
|
||||
where `left` is being filled in during the zip operation.
|
||||
|
||||
@param [&inout] allocator : "The allocator to use; default is the heap allocator."
|
||||
@param [in] left : "The left-side array. These items will be placed as the First in each Pair"
|
||||
@param [in] right : "The right-side array. These items will be placed as the Second in each Pair"
|
||||
@param #operation : "The function to apply. Must have a signature of `$typeof(a) (a, b)`, where the type of 'a' and 'b' is the element type of left/right respectively."
|
||||
@param fill_with : "The value used to fill or pad the shorter iterable to the length of the longer one while zipping."
|
||||
|
||||
@require @is_valid_list(left) &&& @is_valid_list(right) : "Left and right sides must be integer indexable"
|
||||
@require @is_valid_operation(left, right, ...#operation) : "The operator must take two parameters matching the elements of the left and right side"
|
||||
@require @is_valid_fill(left, right, ...fill_with) : "The specified fill value does not match either the left or the right array's underlying type."
|
||||
*>
|
||||
macro @zip(Allocator allocator, left, right, #operation = ..., fill_with = ...) @nodiscard
|
||||
{
|
||||
var $LeftType = $typeof(left[0]);
|
||||
var $RightType = $typeof(right[0]);
|
||||
|
||||
var $Type = Pair { $LeftType, $RightType };
|
||||
bool $is_op = $defined(#operation);
|
||||
$if $is_op:
|
||||
$Type = $typeof(#operation).returns;
|
||||
$endif
|
||||
|
||||
usz left_len = lengthof(left);
|
||||
usz right_len = lengthof(right);
|
||||
|
||||
$LeftType left_fill;
|
||||
$RightType right_fill;
|
||||
usz result_len = min(left_len, right_len);
|
||||
$if $defined(fill_with):
|
||||
switch
|
||||
{
|
||||
case left_len > right_len:
|
||||
$if !$defined(($RightType)fill_with):
|
||||
unreachable();
|
||||
$else
|
||||
right_fill = ($RightType)fill_with;
|
||||
result_len = left_len;
|
||||
$endif
|
||||
case left_len < right_len:
|
||||
$if !$defined(($LeftType)fill_with):
|
||||
unreachable();
|
||||
$else
|
||||
left_fill = ($LeftType)fill_with;
|
||||
result_len = right_len;
|
||||
$endif
|
||||
}
|
||||
$endif
|
||||
|
||||
if (result_len == 0) return ($Type[]){};
|
||||
|
||||
$Type[] result = allocator::alloc_array(allocator, $Type, result_len);
|
||||
|
||||
foreach (idx, &item : result)
|
||||
{
|
||||
$if $is_op:
|
||||
var $LambdaType = $typeof(fn $Type ($LeftType a, $RightType b) => ($Type){});
|
||||
$LambdaType $operation = ($LambdaType)#operation;
|
||||
$LeftType lval = idx >= left_len ? left_fill : left[idx];
|
||||
$RightType rval = idx >= right_len ? right_fill : right[idx];
|
||||
*item = $operation(lval, rval);
|
||||
$else
|
||||
*item = {
|
||||
idx >= left_len ? left_fill : left[idx],
|
||||
idx >= right_len ? right_fill : right[idx]
|
||||
};
|
||||
$endif
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
<*
|
||||
Array 'zip' using the temp allocator.
|
||||
|
||||
@param [in] left : "The left-side array. These items will be placed as the First in each Pair"
|
||||
@param [in] right : "The right-side array. These items will be placed as the Second in each Pair"
|
||||
@param #operation : "The function to apply. Must have a signature of `$typeof(a) (a, b)`, where the type of 'a' and 'b' is the element type of left/right respectively."
|
||||
@param fill_with : "The value used to fill or pad the shorter iterable to the length of the longer one while zipping."
|
||||
|
||||
@require @is_valid_list(left) &&& @is_valid_list(right) : "Left and right sides must be integer indexable"
|
||||
@require @is_valid_operation(left, right, ...#operation) : "The operator must take two parameters matching the elements of the left and right side"
|
||||
@require @is_valid_fill(left, right, ...fill_with) : "The specified fill value does not match either the left or the right array's underlying type."
|
||||
*>
|
||||
macro @tzip(left, right, #operation = ..., fill_with = ...) @nodiscard
|
||||
{
|
||||
return @zip(tmem, left, right, #operation: ...#operation, fill_with: ...fill_with);
|
||||
}
|
||||
|
||||
|
||||
<*
|
||||
Apply an operation to each element of two slices or arrays and store the results of
|
||||
each operation into the 'left' value.
|
||||
|
||||
This is useful because no memory allocations are required in order to perform the operation.
|
||||
|
||||
A good example of using this might be using algorithmic transformations on data in-place:
|
||||
```
|
||||
char[] partial_cipher = get_next_plaintext_block();
|
||||
|
||||
array::@zip_into(
|
||||
partial_cipher[ENCRYPT_OFFSET:BASE_KEY.len],
|
||||
BASE_KEY,
|
||||
fn char (char a, char b) => a ^ (b * 5) % 37
|
||||
);
|
||||
```
|
||||
|
||||
This parameterizes the lambda function with left (`partial_cipher`) and right (`BASE_KEY`) slice
|
||||
elements and stores the end result in-place within the left slice. This is in contrast to a
|
||||
regular `zip_with` which will create a cloned final result and return it.
|
||||
|
||||
@param [inout] left : `Slice to store results of applied functor/operation.`
|
||||
@param [in] right : `Slice to apply in the functor/operation.`
|
||||
@param #operation : "The function to apply. Must have a signature of `$typeof(a) (a, b)`, where the type of 'a' and 'b' is the element type of left/right respectively."
|
||||
|
||||
@require @is_valid_list(left) : "Expected a valid list"
|
||||
@require @is_valid_list(right) : "Expected a valid list"
|
||||
@require lengthof(right) >= lengthof(left) : `Right side length must be >= the destination (left) side length; consider using a sub-array of data for the assignment.`
|
||||
@require $defined($typefrom(@zip_into_fn(left, right)) x = #operation) : "The functor must use the same types as the `left` and `right` inputs, and return a value of the `left` type."
|
||||
*>
|
||||
macro @zip_into(left, right, #operation)
|
||||
{
|
||||
$typefrom(@zip_into_fn(left, right)) $operation = #operation;
|
||||
|
||||
foreach (i, &v : left) *v = $operation(left[i], right[i]);
|
||||
}
|
||||
|
||||
|
||||
// --- helper functions
|
||||
module std::core::array @private;
|
||||
|
||||
|
||||
macro typeid @predicate_fn(#array) @const
|
||||
{
|
||||
return $typeof(fn bool ($typeof(#array[0]) a, usz index = 0) => true).typeid;
|
||||
}
|
||||
|
||||
macro typeid @reduce_fn(#array, #identity) @const
|
||||
{
|
||||
return @typeid(fn $typeof(#identity) ($typeof(#identity) i, $typeof(#array[0]) a, usz index = 0) => i);
|
||||
}
|
||||
|
||||
macro typeid @zip_into_fn(#left, #right) @const
|
||||
{
|
||||
return @typeid(fn $typeof(#left[0]) ($typeof(#left[0]) l, $typeof(#right[0]) r) => l);
|
||||
}
|
||||
|
||||
macro bool @is_valid_operation(#left, #right, #operation = ...) @const
|
||||
{
|
||||
$switch:
|
||||
$case !$defined(#operation):
|
||||
return true;
|
||||
$case $kindof(#operation) != FUNC:
|
||||
return false;
|
||||
$default:
|
||||
return $defined(#operation(#left[0], #right[0]));
|
||||
$endswitch
|
||||
}
|
||||
|
||||
macro bool @is_valid_list(#expr) @const
|
||||
{
|
||||
return $defined(#expr[0], lengthof(#expr));
|
||||
}
|
||||
|
||||
macro bool @is_valid_fill(left, right, fill_with = ...)
|
||||
{
|
||||
$if !$defined(fill_with):
|
||||
return true;
|
||||
$else
|
||||
usz left_len = lengthof(left);
|
||||
usz right_len = lengthof(right);
|
||||
if (left_len == right_len) return true;
|
||||
return left_len > right_len ? $defined(($typeof(right[0]))fill_with) : $defined(($typeof(left[0]))fill_with);
|
||||
$endif
|
||||
}
|
||||
|
||||
@@ -112,3 +112,27 @@ const char[256] HEX_VALUE = {
|
||||
const char[256] TO_UPPER @private = { ['a'..'z'] = 'a' - 'A' };
|
||||
const char[256] TO_LOWER @private = { ['A'..'Z'] = 'a' - 'A' };
|
||||
|
||||
typedef AsciiCharset = uint128;
|
||||
|
||||
macro AsciiCharset @create_set(String $string) @const
|
||||
{
|
||||
AsciiCharset $set;
|
||||
$foreach $c : $string:
|
||||
$set |= 1ULL << $c;
|
||||
$endforeach
|
||||
return $set;
|
||||
}
|
||||
|
||||
fn AsciiCharset create_set(String string)
|
||||
{
|
||||
AsciiCharset set;
|
||||
foreach (c : string) set |= (AsciiCharset)1ULL << c;
|
||||
return set;
|
||||
}
|
||||
|
||||
macro bool AsciiCharset.contains(set, char c) => !!(c < 128) & !!(set & (AsciiCharset)(1ULL << c));
|
||||
|
||||
const AsciiCharset WHITESPACE_SET = @create_set("\t\n\v\f\r ");
|
||||
const AsciiCharset NUMBER_SET = @create_set("0123456789");
|
||||
|
||||
|
||||
|
||||
@@ -94,7 +94,7 @@ bitstruct UInt128LE : uint128 @littleendian
|
||||
macro read(bytes, $Type)
|
||||
{
|
||||
char[] s;
|
||||
$switch @typekind(bytes):
|
||||
$switch $kindof(bytes):
|
||||
$case POINTER:
|
||||
s = (*bytes)[:$Type.sizeof];
|
||||
$default:
|
||||
@@ -110,7 +110,7 @@ macro read(bytes, $Type)
|
||||
macro write(x, bytes, $Type)
|
||||
{
|
||||
char[] s;
|
||||
$switch @typekind(bytes):
|
||||
$switch $kindof(bytes):
|
||||
$case POINTER:
|
||||
s = (*bytes)[:$Type.sizeof];
|
||||
$default:
|
||||
@@ -119,7 +119,7 @@ macro write(x, bytes, $Type)
|
||||
*($typeof(x)*)s.ptr = bitcast(x, $Type).val;
|
||||
}
|
||||
|
||||
macro is_bitorder($Type)
|
||||
macro bool is_bitorder($Type)
|
||||
{
|
||||
$switch $Type:
|
||||
$case UShortLE:
|
||||
@@ -181,4 +181,4 @@ macro bool @is_arrayptr_or_slice_of_char(#bytes) @const
|
||||
$default:
|
||||
return false;
|
||||
$endswitch
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import libc, std::hash, std::io, std::os::backtrace;
|
||||
|
||||
<*
|
||||
EMPTY_MACRO_SLOT is a value used for implementing optional arguments for macros in an efficient
|
||||
way. It relies on the fact that distinct types are not implicitly convertable.
|
||||
way. It relies on the fact that distinct types are not implicitly convertible.
|
||||
|
||||
You can use `@is_empty_macro_slot()` and `@is_valid_macro_slot()` to figure out whether
|
||||
the argument has been used or not.
|
||||
@@ -18,17 +18,17 @@ import libc, std::hash, std::io, std::os::backtrace;
|
||||
macro foo(a, #b = EMPTY_MACRO_SLOT)
|
||||
{
|
||||
$if @is_valid_macro_slot(#b):
|
||||
return invoke_foo2(a, #b);
|
||||
$else
|
||||
return invoke_foo1(a);
|
||||
$endif
|
||||
return invoke_foo2(a, #b);
|
||||
$else
|
||||
return invoke_foo1(a);
|
||||
$endif
|
||||
}
|
||||
*>
|
||||
const EmptySlot EMPTY_MACRO_SLOT @builtin = null;
|
||||
|
||||
typedef EmptySlot = void*;
|
||||
macro @is_empty_macro_slot(#arg) @const @builtin => @typeis(#arg, EmptySlot);
|
||||
macro @is_valid_macro_slot(#arg) @const @builtin => !@typeis(#arg, EmptySlot);
|
||||
macro bool @is_empty_macro_slot(#arg) @const @builtin => $typeof(#arg) == EmptySlot;
|
||||
macro bool @is_valid_macro_slot(#arg) @const @builtin => $typeof(#arg) != EmptySlot;
|
||||
|
||||
<*
|
||||
Returns a random value at compile time.
|
||||
@@ -39,20 +39,28 @@ macro @is_valid_macro_slot(#arg) @const @builtin => !@typeis(#arg, EmptySlot);
|
||||
macro @rnd() @const @builtin => $$rnd();
|
||||
|
||||
/*
|
||||
Use `IteratorResult` when reading the end of an iterator, or accessing a result out of bounds.
|
||||
Use `NO_MORE_ELEMENT` when reading the end of an iterator, or accessing a result out of bounds.
|
||||
*/
|
||||
faultdef NO_MORE_ELEMENT @builtin;
|
||||
|
||||
/*
|
||||
Use `SearchResult` when trying to return a value from some collection but the element is missing.
|
||||
Use `NOT_FOUND` when trying to return a value from some collection but the element is missing.
|
||||
*/
|
||||
faultdef NOT_FOUND @builtin;
|
||||
|
||||
/*
|
||||
Use `CastResult` when an attempt at conversion fails.
|
||||
Use `TYPE_MISMATCH` when an attempt at conversion fails.
|
||||
*/
|
||||
faultdef TYPE_MISMATCH @builtin;
|
||||
|
||||
/*
|
||||
Use `CAPACITY_EXCEEDED` when trying to add to a bounded list or similar.
|
||||
*/
|
||||
faultdef CAPACITY_EXCEEDED @builtin;
|
||||
/*
|
||||
Use `NOT_IMPLEMENTED` when something is conditionally available.
|
||||
*/
|
||||
faultdef NOT_IMPLEMENTED @builtin;
|
||||
|
||||
alias VoidFn = fn void();
|
||||
|
||||
@@ -61,7 +69,7 @@ alias VoidFn = fn void();
|
||||
macro scope.
|
||||
|
||||
@param #variable : `the variable to store and restore`
|
||||
@require values::@is_lvalue(#variable)
|
||||
@require $defined(#variable = #variable) : `Expected an actual variable`
|
||||
*>
|
||||
macro void @scope(#variable; @body) @builtin
|
||||
{
|
||||
@@ -81,13 +89,17 @@ macro void @swap(#a, #b) @builtin
|
||||
#b = temp;
|
||||
}
|
||||
|
||||
macro usz bitsizeof($Type) @builtin @const => $Type.sizeof * 8u;
|
||||
|
||||
macro usz @bitsizeof(#expr) @builtin @const => $sizeof(#expr) * 8u;
|
||||
|
||||
<*
|
||||
Convert an `any` type to a type, returning an failure if there is a type mismatch.
|
||||
|
||||
@param v : `the any to convert to the given type.`
|
||||
@param $Type : `the type to convert to`
|
||||
@return `The any.ptr converted to its type.`
|
||||
@ensure @typeis(return, $Type*)
|
||||
@ensure $typeof(return) == $Type*
|
||||
@return? TYPE_MISMATCH
|
||||
*>
|
||||
macro anycast(any v, $Type) @builtin
|
||||
@@ -96,7 +108,7 @@ macro anycast(any v, $Type) @builtin
|
||||
return ($Type*)v.ptr;
|
||||
}
|
||||
|
||||
macro bool @assignable_to(#foo, $Type) @const @builtin => $defined(*&&($Type){} = #foo);
|
||||
macro bool @assignable_to(#foo, $Type) @const @builtin @deprecated("use '$defined($Type x = #foo)'") => $defined(*&&($Type){} = #foo);
|
||||
|
||||
macro @addr(#val) @builtin
|
||||
{
|
||||
@@ -112,12 +124,12 @@ macro typeid @typeid(#value) @const @builtin
|
||||
return $typeof(#value).typeid;
|
||||
}
|
||||
|
||||
macro TypeKind @typekind(#value) @const @builtin
|
||||
macro TypeKind @typekind(#value) @const @builtin @deprecated("Use `$kindof(#value)`.")
|
||||
{
|
||||
return $typeof(#value).kindof;
|
||||
return $kindof(#value);
|
||||
}
|
||||
|
||||
macro bool @typeis(#value, $Type) @const @builtin
|
||||
macro bool @typeis(#value, $Type) @const @builtin @deprecated("Use `$typeof(#value) == $Type` instead.")
|
||||
{
|
||||
return $typeof(#value).typeid == $Type.typeid;
|
||||
}
|
||||
@@ -273,7 +285,7 @@ macro any.as_inner(&self)
|
||||
@param $Type : "the type to cast to"
|
||||
|
||||
@require $sizeof(expr) == $Type.sizeof : "Cannot bitcast between types of different size."
|
||||
@ensure @typeis(return, $Type)
|
||||
@ensure $typeof(return) == $Type*
|
||||
*>
|
||||
macro bitcast(expr, $Type) @builtin
|
||||
{
|
||||
@@ -290,7 +302,7 @@ macro bitcast(expr, $Type) @builtin
|
||||
@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)
|
||||
@ensure $typeof(return) == $Type*
|
||||
@return? NOT_FOUND
|
||||
*>
|
||||
macro enum_by_name($Type, String enum_name) @builtin
|
||||
@@ -307,8 +319,8 @@ macro enum_by_name($Type, String enum_name) @builtin
|
||||
@param $Type : `The type of the enum`
|
||||
@require $Type.kindof == ENUM : `Only enums may be used`
|
||||
@require $defined($Type.#value) : `Expected '#value' to match an enum associated value`
|
||||
@require @assignable_to(value, $typeof(($Type){}.#value)) : `Expected the value to match the type of the associated value`
|
||||
@ensure @typeis(return, $Type)
|
||||
@require $defined($typeof(($Type){}.#value) v = value) : `Expected the value to match the type of the associated value`
|
||||
@ensure $typeof(return) == $Type*
|
||||
@return? NOT_FOUND
|
||||
*>
|
||||
macro @enum_from_value($Type, #value, value) @builtin @deprecated("Use Enum.lookup_field and Enum.lookup")
|
||||
@@ -360,7 +372,7 @@ macro bool @unlikely(bool #value, $probability = 1.0) @builtin
|
||||
|
||||
<*
|
||||
@require values::@is_int(#value) || values::@is_bool(#value)
|
||||
@require @assignable_to(expected, $typeof(#value))
|
||||
@require $defined($typeof(#value) v = expected)
|
||||
@require $probability >= 0 && $probability <= 1.0
|
||||
*>
|
||||
macro @expect(#value, expected, $probability = 1.0) @builtin
|
||||
@@ -411,11 +423,38 @@ macro swizzle2(v, v2, ...) @builtin
|
||||
return $$swizzle2(v, v2, $vasplat);
|
||||
}
|
||||
|
||||
|
||||
<*
|
||||
Returns the count of leading zero bits from an integer at compile-time.
|
||||
|
||||
@require types::is_int($typeof($value)) : "Input value must be an integer"
|
||||
@require $sizeof($value) * 8 <= 128 : "Input value must be 128 bits wide or lower"
|
||||
*>
|
||||
macro uint @clz($value) @builtin @const
|
||||
{
|
||||
$if $value == 0:
|
||||
return $sizeof($value) * 8; // it's all leading zeroes
|
||||
$endif
|
||||
|
||||
usz $n = 0;
|
||||
uint128 $x = (uint128)$value;
|
||||
|
||||
$if $x <= 0x0000_0000_0000_0000_FFFF_FFFF_FFFF_FFFF: $n += 64; $x <<= 64; $endif
|
||||
$if $x <= 0x0000_0000_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF: $n += 32; $x <<= 32; $endif
|
||||
$if $x <= 0x0000_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF: $n += 16; $x <<= 16; $endif
|
||||
$if $x <= 0x00FF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF: $n += 8; $x <<= 8; $endif
|
||||
$if $x <= 0x0FFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF: $n += 4; $x <<= 4; $endif
|
||||
$if $x <= 0x3FFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF: $n += 2; $x <<= 2; $endif
|
||||
$if $x <= 0x7FFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF: $n += 1; $endif
|
||||
|
||||
return $n % ($sizeof($value) * 8); // mod by the bitsize of the input value to go back from uint128 -> it's-type
|
||||
}
|
||||
|
||||
<*
|
||||
Return the excuse in the Optional if it is Empty, otherwise
|
||||
return a null fault.
|
||||
|
||||
@require @typekind(#expr) == OPTIONAL : `@catch expects an Optional value`
|
||||
@require $kindof(#expr) == OPTIONAL : `@catch expects an Optional value`
|
||||
*>
|
||||
macro fault @catch(#expr) @builtin
|
||||
{
|
||||
@@ -427,7 +466,7 @@ macro fault @catch(#expr) @builtin
|
||||
Check if an Optional expression holds a value or is empty, returning true
|
||||
if it has a value.
|
||||
|
||||
@require @typekind(#expr) == OPTIONAL : `@ok expects an Optional value`
|
||||
@require $kindof(#expr) == OPTIONAL : `@ok expects an Optional value`
|
||||
*>
|
||||
macro bool @ok(#expr) @builtin
|
||||
{
|
||||
@@ -441,9 +480,9 @@ macro bool @ok(#expr) @builtin
|
||||
|
||||
@require $defined(#v = #v) : "#v must be a variable"
|
||||
@require $defined(#expr!) : "Expected an optional expression"
|
||||
@require @assignable_to(#expr!!, $typeof(#v)) : `Type of #expr must be an optional of #v's type`
|
||||
@require $defined(#v = #expr!!) : `Type of #expr must be an optional of #v's type`
|
||||
*>
|
||||
macro void? @try(#v, #expr) @builtin
|
||||
macro void? @try(#v, #expr) @builtin @maydiscard
|
||||
{
|
||||
var res = #expr;
|
||||
if (catch err = res) return err?;
|
||||
@@ -459,10 +498,10 @@ macro void? @try(#v, #expr) @builtin
|
||||
|
||||
while (true)
|
||||
{
|
||||
char[] data;
|
||||
// Read until end of file
|
||||
if (@try_catch(data, load_line(), io::EOF)) break;
|
||||
.. use data ..
|
||||
char[] data;
|
||||
// Read until end of file
|
||||
if (@try_catch(data, load_line(), io::EOF)) break;
|
||||
.. use data ..
|
||||
}
|
||||
|
||||
In this example we read until we reach an EOF, which is expected. However, if we encounter some other
|
||||
@@ -470,19 +509,19 @@ macro void? @try(#v, #expr) @builtin
|
||||
|
||||
while (true)
|
||||
{
|
||||
char[]? data;
|
||||
data = load_line();
|
||||
if (catch err = data)
|
||||
{
|
||||
if (err = io::EOF) break;
|
||||
return err?
|
||||
}
|
||||
.. use data ..
|
||||
char[]? data;
|
||||
data = load_line();
|
||||
if (catch err = data)
|
||||
{
|
||||
if (err = io::EOF) break;
|
||||
return err?
|
||||
}
|
||||
.. use data ..
|
||||
}
|
||||
|
||||
@require $defined(#v = #v) : "#v must be a variable"
|
||||
@require $defined(#expr!) : "Expected an optional expression"
|
||||
@require @assignable_to(#expr!!, $typeof(#v)) : `Type of #expr must be an optional of #v's type`
|
||||
@require $defined(#v = #expr!!) : `Type of #expr must be an optional of #v's type`
|
||||
@return "True if it was the expected fault, false if the variable was assigned, otherwise returns an optional."
|
||||
*>
|
||||
macro bool? @try_catch(#v, #expr, fault expected_fault) @builtin
|
||||
@@ -517,7 +556,7 @@ macro @generic_hash_core(h, value)
|
||||
return h;
|
||||
}
|
||||
|
||||
macro @generic_hash(value)
|
||||
macro uint @generic_hash(value)
|
||||
{
|
||||
uint h = @generic_hash_core((uint)0x3efd4391, value);
|
||||
$for var $cnt = 4; $cnt < $sizeof(value); $cnt += 4:
|
||||
@@ -565,24 +604,36 @@ macro uint ichar[<*>].hash(self) => hash_vec(self);
|
||||
macro uint bool[<*>].hash(self) => hash_vec(self);
|
||||
|
||||
macro uint typeid.hash(typeid t) => @generic_hash(((ulong)(uptr)t));
|
||||
macro uint String.hash(String c) => (uint)fnv32a::hash(c);
|
||||
macro uint char[].hash(char[] c) => (uint)fnv32a::hash(c);
|
||||
macro uint String.hash(String c) => (uint)a5hash::hash(c);
|
||||
macro uint char[].hash(char[] c) => (uint)a5hash::hash(c);
|
||||
macro uint void*.hash(void* ptr) => @generic_hash(((ulong)(uptr)ptr));
|
||||
|
||||
<*
|
||||
@require @typekind(array_ptr) == POINTER &&& @typekind(*array_ptr) == ARRAY
|
||||
@require $kindof(array_ptr) == POINTER &&& $kindof(*array_ptr) == ARRAY
|
||||
*>
|
||||
macro uint hash_array(array_ptr) @local
|
||||
{
|
||||
return (uint)fnv32a::hash(((char*)array_ptr)[:$sizeof(*array_ptr)]);
|
||||
var $len = $sizeof(*array_ptr);
|
||||
|
||||
$if $len > 16:
|
||||
return (uint)komi::hash(((char*)array_ptr)[:$len]);
|
||||
$else
|
||||
return (uint)wyhash2::hash(((char*)array_ptr)[:$len]);
|
||||
$endif
|
||||
}
|
||||
|
||||
<*
|
||||
@require @typekind(vec) == VECTOR
|
||||
@require $kindof(vec) == VECTOR
|
||||
*>
|
||||
macro uint hash_vec(vec) @local
|
||||
{
|
||||
return (uint)fnv32a::hash(((char*)&&vec)[:$sizeof(vec.len * $typeof(vec).inner.sizeof)]);
|
||||
var $len = $sizeof(vec.len * $typeof(vec).inner.sizeof);
|
||||
|
||||
$if $len > 16:
|
||||
return (uint)komi::hash(((char*)&&vec)[:$len]);
|
||||
$else
|
||||
return (uint)wyhash2::hash(((char*)&&vec)[:$len]);
|
||||
$endif
|
||||
}
|
||||
|
||||
const MAX_FRAMEADDRESS = 128;
|
||||
|
||||
@@ -6,7 +6,7 @@ module std::core::builtin;
|
||||
<*
|
||||
@require types::@comparable_value(a) && types::@comparable_value(b)
|
||||
*>
|
||||
macro less(a, b) @builtin
|
||||
macro bool less(a, b) @builtin
|
||||
{
|
||||
$switch:
|
||||
$case $defined(a.less):
|
||||
@@ -21,7 +21,7 @@ macro less(a, b) @builtin
|
||||
<*
|
||||
@require types::@comparable_value(a) && types::@comparable_value(b)
|
||||
*>
|
||||
macro less_eq(a, b) @builtin
|
||||
macro bool less_eq(a, b) @builtin
|
||||
{
|
||||
$switch:
|
||||
$case $defined(a.less):
|
||||
@@ -36,7 +36,7 @@ macro less_eq(a, b) @builtin
|
||||
<*
|
||||
@require types::@comparable_value(a) && types::@comparable_value(b)
|
||||
*>
|
||||
macro greater(a, b) @builtin
|
||||
macro bool greater(a, b) @builtin
|
||||
{
|
||||
$switch:
|
||||
$case $defined(a.less):
|
||||
@@ -65,7 +65,7 @@ macro int compare_to(a, b) @builtin
|
||||
<*
|
||||
@require types::@comparable_value(a) && types::@comparable_value(b)
|
||||
*>
|
||||
macro greater_eq(a, b) @builtin
|
||||
macro bool greater_eq(a, b) @builtin
|
||||
{
|
||||
$switch:
|
||||
$case $defined(a.less):
|
||||
@@ -126,3 +126,36 @@ macro max(x, ...) @builtin
|
||||
$endif
|
||||
}
|
||||
|
||||
|
||||
<*
|
||||
@require types::is_numerical($typeof($a))
|
||||
*>
|
||||
macro @max($a, ...) @builtin @const
|
||||
{
|
||||
$if $vacount == 1:
|
||||
return $a > $vaconst[0] ? $a : $vaconst[0];
|
||||
$else
|
||||
var $result = $a;
|
||||
$for var $x = 0; $x < $vacount; ++$x:
|
||||
$if $vaconst[$x] > $result: $result = $vaconst[$x]; $endif
|
||||
$endfor
|
||||
return $result;
|
||||
$endif
|
||||
}
|
||||
|
||||
<*
|
||||
@require types::is_numerical($typeof($a))
|
||||
*>
|
||||
macro @min($a, ...) @builtin @const
|
||||
{
|
||||
$if $vacount == 1:
|
||||
return $a < $vaconst[0] ? $a : $vaconst[0];
|
||||
$else
|
||||
var $result = $a;
|
||||
$for var $x = 0; $x < $vacount; ++$x:
|
||||
$if $vaconst[$x] < $result: $result = $vaconst[$x]; $endif
|
||||
$endfor
|
||||
return $result;
|
||||
$endif
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
// 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::cinterop;
|
||||
import std::core::env;
|
||||
|
||||
|
||||
const C_INT_SIZE = $$C_INT_SIZE;
|
||||
const C_LONG_SIZE = $$C_LONG_SIZE;
|
||||
@@ -59,3 +61,57 @@ macro typeid unsigned_int_from_bitsize(usz $bitsize) @private
|
||||
$default: $error("Invalid bitsize");
|
||||
$endswitch
|
||||
}
|
||||
|
||||
const USE_STACK_VALIST = env::ARCH_32_BIT || env::WIN32 || (env::DARWIN && env::AARCH64);
|
||||
module std::core::cinterop @if(USE_STACK_VALIST);
|
||||
|
||||
typedef CVaList = void*;
|
||||
macro CVaList.next(&self, $Type)
|
||||
{
|
||||
void *ptr = mem::aligned_pointer((void*)*self, max($Type.alignof, 8));
|
||||
defer *self = (CVaList)(ptr + 1);
|
||||
return *($Type*)ptr;
|
||||
}
|
||||
|
||||
module std::core::cinterop @if(env::X86_64 && !env::WIN32);
|
||||
|
||||
struct CVaListData
|
||||
{
|
||||
uint gp_offset;
|
||||
uint fp_offset;
|
||||
void *overflow_arg_area;
|
||||
void *reg_save_area;
|
||||
}
|
||||
|
||||
typedef CVaList = CVaListData*;
|
||||
|
||||
macro CVaList.next(self, $Type)
|
||||
{
|
||||
CVaListData* data = (CVaListData*)self;
|
||||
$switch:
|
||||
$case $Type.kindof == FLOAT ||| ($Type.kindof == VECTOR && $Type.sizeof <= 16):
|
||||
var $LoadType = $Type.sizeof < 8 ? double : $Type;
|
||||
if (data.fp_offset < 6 * 8 + 8 * 16 )
|
||||
{
|
||||
defer data.fp_offset += (uint)mem::aligned_offset($Type.sizeof, 16);
|
||||
return ($Type)*($LoadType*)(data.reg_save_area + data.fp_offset);
|
||||
}
|
||||
void* ptr = mem::aligned_pointer(data.overflow_arg_area, max(8, $Type.alignof));
|
||||
defer data.overflow_arg_area = ptr + $Type.sizeof;
|
||||
return ($Type)*($LoadType*)ptr;
|
||||
$case $Type.kindof == SIGNED_INT || $Type.kindof == UNSIGNED_INT:
|
||||
var $LoadType = $Type.sizeof < 4 ? int : $Type;
|
||||
if (data.gp_offset < 6 * 8 && $Type.sizeof <= 8)
|
||||
{
|
||||
defer data.gp_offset += (uint)mem::aligned_offset($Type.sizeof, 8);
|
||||
return ($Type)*($LoadType*)(data.reg_save_area + data.gp_offset);
|
||||
}
|
||||
void* ptr = mem::aligned_pointer(data.overflow_arg_area, max(8, $Type.alignof));
|
||||
defer data.overflow_arg_area = ptr + $Type.sizeof;
|
||||
return ($Type)*($LoadType*)ptr;
|
||||
$default:
|
||||
void* ptr = mem::aligned_pointer(data.overflow_arg_area, max(8, $Type.alignof));
|
||||
defer data.overflow_arg_area = ptr + $Type.sizeof;
|
||||
return *($Type*)ptr;
|
||||
$endswitch
|
||||
}
|
||||
@@ -154,7 +154,12 @@ const bool NETBSD = LIBC && OS_TYPE == NETBSD;
|
||||
const bool BSD_FAMILY = env::FREEBSD || env::OPENBSD || env::NETBSD;
|
||||
const bool WASI = LIBC && OS_TYPE == WASI;
|
||||
const bool ANDROID = LIBC && OS_TYPE == ANDROID;
|
||||
const bool WASM_NOLIBC @builtin = !LIBC && ARCH_TYPE == ArchType.WASM32 || ARCH_TYPE == ArchType.WASM64;
|
||||
const bool WASM_NOLIBC @builtin @deprecated("Use 'FREESTANDING_WASM' instead") = !LIBC && ARCH_TYPE == ArchType.WASM32 || ARCH_TYPE == ArchType.WASM64;
|
||||
const bool FREESTANDING_PE32 = NO_LIBC && OS_TYPE == WIN32;
|
||||
const bool FREESTANDING_MACHO = NO_LIBC && OS_TYPE == MACOS;
|
||||
const bool FREESTANDING_ELF = NO_LIBC && !env::FREESTANDING_PE32 && !env::FREESTANDING_MACHO && !env::FREESTANDING_WASM;
|
||||
const bool FREESTANDING_WASM = NO_LIBC && (ARCH_TYPE == ArchType.WASM32 || ARCH_TYPE == ArchType.WASM64);
|
||||
const bool FREESTANDING = env::FREESTANDING_PE32 || env::FREESTANDING_MACHO || env::FREESTANDING_ELF || env::FREESTANDING_WASM;
|
||||
const bool ADDRESS_SANITIZER = $$ADDRESS_SANITIZER;
|
||||
const bool MEMORY_SANITIZER = $$MEMORY_SANITIZER;
|
||||
const bool THREAD_SANITIZER = $$THREAD_SANITIZER;
|
||||
@@ -201,5 +206,6 @@ macro bool os_is_posix() @const
|
||||
}
|
||||
const String[] AUTHORS = $$AUTHORS;
|
||||
const String[] AUTHOR_EMAILS = $$AUTHOR_EMAILS;
|
||||
const String PROJECT_VERSION = $$PROJECT_VERSION;
|
||||
const BUILTIN_EXPECT_IS_DISABLED = $feature(DISABLE_BUILTIN_EXPECT);
|
||||
const BUILTIN_PREFETCH_IS_DISABLED = $feature(DISABLE_BUILTIN_PREFETCH);
|
||||
|
||||
239
lib/std/core/logging.c3
Normal file
239
lib/std/core/logging.c3
Normal file
@@ -0,0 +1,239 @@
|
||||
module std::core::log;
|
||||
import std::io, std::thread, std::time, std::math::random;
|
||||
|
||||
const FULL_LOG = env::COMPILER_SAFE_MODE || $feature(FULL_LOG);
|
||||
|
||||
typedef LogCategory = inline char;
|
||||
typedef LogTag = char[12];
|
||||
|
||||
const LogCategory CATEGORY_APPLICATION = 0;
|
||||
const LogCategory CATEGORY_SYSTEM = 1;
|
||||
const LogCategory CATEGORY_KERNEL = 2;
|
||||
const LogCategory CATEGORY_AUDIO = 3;
|
||||
const LogCategory CATEGORY_VIDEO = 4;
|
||||
const LogCategory CATEGORY_RENDER = 5;
|
||||
const LogCategory CATEGORY_INPUT = 6;
|
||||
const LogCategory CATEGORY_NETWORK = 7;
|
||||
const LogCategory CATEGORY_SOCKET = 8;
|
||||
const LogCategory CATEGORY_SECURITY = 9;
|
||||
const LogCategory CATEGORY_TEST = 10;
|
||||
const LogCategory CATEGORY_ERROR = 11;
|
||||
const LogCategory CATEGORY_ASSERT = 12;
|
||||
const LogCategory CATEGORY_CRASH = 13;
|
||||
const LogCategory CATEGORY_STATS = 14;
|
||||
const LogCategory CATEGORY_CUSTOM_START = 100;
|
||||
|
||||
tlocal LogCategory default_category = CATEGORY_APPLICATION;
|
||||
tlocal LogTag current_tag;
|
||||
|
||||
|
||||
enum LogPriority : int
|
||||
{
|
||||
VERBOSE,
|
||||
DEBUG,
|
||||
INFO,
|
||||
WARN,
|
||||
ERROR,
|
||||
CRITICAL,
|
||||
}
|
||||
|
||||
interface Logger
|
||||
{
|
||||
fn void log(LogPriority priority, LogCategory category, LogTag tag, String file, String function, int line, String fmt, any[] args);
|
||||
}
|
||||
|
||||
macro void verbose(String fmt, ..., LogCategory category = default_category) => call_log(VERBOSE, category, fmt, $vasplat);
|
||||
macro void debug(String fmt, ..., LogCategory category = default_category) => call_log(DEBUG, category, fmt, $vasplat);
|
||||
macro void info(String fmt, ..., LogCategory category = default_category) => call_log(INFO, category, fmt, $vasplat);
|
||||
macro void warn(String fmt, ..., LogCategory category = default_category) => call_log(WARN, category, fmt, $vasplat);
|
||||
macro void error(String fmt, ..., LogCategory category = default_category) => call_log(ERROR, category, fmt, $vasplat);
|
||||
macro void critical(String fmt, ..., LogCategory category = default_category) => call_log(CRITICAL, category, fmt, $vasplat);
|
||||
|
||||
macro void @category_scope(LogCategory new_category; @body)
|
||||
{
|
||||
LogCategory old = default_category;
|
||||
default_category = new_category;
|
||||
defer default_category = old;
|
||||
@body();
|
||||
}
|
||||
|
||||
<*
|
||||
@require tag_prefix.len <= 3 : "The prefix may not exceed 3 bytes"
|
||||
*>
|
||||
macro void @tag_scope(String tag_prefix = ""; @body)
|
||||
{
|
||||
LogTag old = current_tag;
|
||||
push_tag(tag_prefix);
|
||||
defer current_tag = old;
|
||||
@body();
|
||||
}
|
||||
|
||||
<*
|
||||
@require tag_prefix.len <= 3 : "The prefix may not exceed 3 bytes"
|
||||
*>
|
||||
macro void push_tag(String tag_prefix = "")
|
||||
{
|
||||
current_tag = create_tag(tag_prefix);
|
||||
}
|
||||
|
||||
<*
|
||||
@require tag_prefix.len <= 3 : "The prefix may not exceed 3 bytes"
|
||||
*>
|
||||
fn LogTag create_tag(String tag_prefix)
|
||||
{
|
||||
LogTag tag @noinit;
|
||||
int start = 0;
|
||||
foreach (int i, c : tag_prefix)
|
||||
{
|
||||
if (c == 0) break;
|
||||
tag[start++] = c;
|
||||
}
|
||||
if (start > 0) tag[start++] = '_';
|
||||
for (int i = start; i < tag.len; i++)
|
||||
{
|
||||
tag[i] = (char)rand_in_range('a', 'z');
|
||||
}
|
||||
return tag;
|
||||
}
|
||||
|
||||
fn void set_priority_for_category(LogCategory category, LogPriority new_priority)
|
||||
{
|
||||
@atomic_store(config_priorities[category], new_priority, UNORDERED);
|
||||
}
|
||||
|
||||
fn LogPriority get_priority_for_category(LogCategory category)
|
||||
{
|
||||
return @atomic_load(config_priorities[category], UNORDERED);
|
||||
}
|
||||
|
||||
fn void set_priority_all(LogPriority new_priority)
|
||||
{
|
||||
for (int i = 0; i < config_priorities.len; i++)
|
||||
{
|
||||
@atomic_store(config_priorities[i], new_priority, UNORDERED);
|
||||
}
|
||||
}
|
||||
fn void set_logger(Logger logger)
|
||||
{
|
||||
init();
|
||||
if (!logger_mutex.is_initialized())
|
||||
{
|
||||
current_logger = logger;
|
||||
current_logfn = &logger.log;
|
||||
return;
|
||||
}
|
||||
logger_mutex.@in_lock()
|
||||
{
|
||||
current_logger = logger;
|
||||
current_logfn = &logger.log;
|
||||
};
|
||||
}
|
||||
|
||||
macro void init()
|
||||
{
|
||||
log_init.call(fn () => (void)logger_mutex.init());
|
||||
}
|
||||
|
||||
macro void call_log(LogPriority prio, LogCategory category, String fmt, args...)
|
||||
{
|
||||
$if FULL_LOG:
|
||||
call_log_internal(prio, category, $$FILE, $$FUNC, $$LINE, fmt, args);
|
||||
$else
|
||||
call_log_internal(prio, category, "", "", 0, fmt, args);
|
||||
$endif
|
||||
}
|
||||
|
||||
fn void call_log_internal(LogPriority prio, LogCategory category, String file, String func, int line, String fmt, any[] args)
|
||||
{
|
||||
LogPriority priority = mem::@atomic_load(config_priorities[category], UNORDERED);
|
||||
if (priority > prio) return;
|
||||
init();
|
||||
bool locked = logger_mutex.is_initialized() && @ok(logger_mutex.lock());
|
||||
Logger logger = current_logger;
|
||||
LogFn logfn = current_logfn;
|
||||
defer if (locked) (void)logger_mutex.unlock();
|
||||
logfn(logger.ptr, prio, category, current_tag, file, func, line, fmt, args);
|
||||
}
|
||||
|
||||
fn String? get_category_name(LogCategory category)
|
||||
{
|
||||
String val = category_names[category];
|
||||
return val ?: NOT_FOUND?;
|
||||
}
|
||||
|
||||
fn void set_category_name(LogCategory category, String name)
|
||||
{
|
||||
category_names[category] = name;
|
||||
}
|
||||
|
||||
struct NullLogger (Logger)
|
||||
{
|
||||
void* dummy;
|
||||
}
|
||||
|
||||
fn void NullLogger.log(&self, LogPriority priority, LogCategory category, LogTag tag, String file, String function, int line, String fmt, any[] args) @dynamic
|
||||
{}
|
||||
|
||||
struct MultiLogger (Logger)
|
||||
{
|
||||
Logger[] loggers;
|
||||
}
|
||||
|
||||
fn void MultiLogger.log(&self, LogPriority priority, LogCategory category, LogTag tag, String file, String function, int line, String fmt, any[] args) @dynamic
|
||||
{
|
||||
foreach (logger : self.loggers)
|
||||
{
|
||||
logger.log(priority, category, tag, file, function, line, fmt, args);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
module std::core::log @private;
|
||||
import std::io, std::thread, std::time;
|
||||
|
||||
struct StderrLogger (Logger) @if(env::LIBC)
|
||||
{
|
||||
void* dummy;
|
||||
}
|
||||
|
||||
fn void StderrLogger.log(&self, LogPriority priority, LogCategory category, LogTag tag, String file, String function, int line, String fmt, any[] args) @dynamic @if(env::LIBC)
|
||||
{
|
||||
@stack_mem(256 + 64; Allocator mem)
|
||||
{
|
||||
DString str;
|
||||
str.init(mem, 256);
|
||||
str.appendf(fmt, ...args);
|
||||
TzDateTime time = datetime::now().to_local();
|
||||
$if FULL_LOG:
|
||||
io::eprintfn("[%02d:%02d:%02d:%04d] %s:%d [%s] %s", time.hour, time.min, time.sec, (time.usec / 1000), file, line, priority, str);
|
||||
$else
|
||||
io::eprintfn("[%02d:%02d:%02d:%04d] [%s] %s", time.hour, time.min, time.sec, (time.usec / 1000), priority, str);
|
||||
$endif
|
||||
};
|
||||
}
|
||||
|
||||
alias LogFn = fn void(void*, LogPriority priority, LogCategory category, LogTag tag, String file, String function, int line, String fmt, any[] args);
|
||||
LogFn current_logfn = env::LIBC ??? (LogFn)&StderrLogger.log : (LogFn)&NullLogger.log;
|
||||
OnceFlag log_init;
|
||||
Mutex logger_mutex;
|
||||
Logger current_logger = env::LIBC ??? &stderr_logger : &null_logger;
|
||||
StderrLogger stderr_logger @if (env::LIBC);
|
||||
NullLogger null_logger;
|
||||
LogPriority[256] config_priorities = { [0..255] = ERROR, [CATEGORY_APPLICATION] = INFO, [CATEGORY_TEST] = VERBOSE, [CATEGORY_ASSERT] = WARN};
|
||||
String[256] category_names = {
|
||||
[CATEGORY_APPLICATION] = "APP",
|
||||
[CATEGORY_SYSTEM] = "SYSTEM",
|
||||
[CATEGORY_KERNEL] = "KERNEL",
|
||||
[CATEGORY_AUDIO] = "AUDIO",
|
||||
[CATEGORY_VIDEO] = "VIDEO",
|
||||
[CATEGORY_RENDER] = "RENDER",
|
||||
[CATEGORY_INPUT] = "INPUT",
|
||||
[CATEGORY_NETWORK] = "NETWORD",
|
||||
[CATEGORY_SOCKET] = "SOCKET",
|
||||
[CATEGORY_SECURITY] = "SECURITY",
|
||||
[CATEGORY_TEST] = "TEST",
|
||||
[CATEGORY_ERROR] = "ERROR",
|
||||
[CATEGORY_ASSERT] = "ASSERT",
|
||||
[CATEGORY_CRASH] = "CRASH",
|
||||
[CATEGORY_STATS] = "STATS"
|
||||
};
|
||||
@@ -20,6 +20,9 @@ macro bool @constant_is_power_of_2($x) @const @private
|
||||
return $x != 0 && ($x & ($x - 1)) == 0;
|
||||
}
|
||||
|
||||
<*
|
||||
@return "The os page size."
|
||||
*>
|
||||
fn usz os_pagesize()
|
||||
{
|
||||
$switch:
|
||||
@@ -44,8 +47,8 @@ fn usz os_pagesize()
|
||||
@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_to(&&passthru, $typeof(ptr)) : "Pointer and passthru must match"
|
||||
@require @typekind(passthru) == VECTOR : "Expected passthru to be a vector"
|
||||
@require $defined(*ptr = passthru) : "Pointer and passthru must match"
|
||||
@require $kindof(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"
|
||||
@@ -63,12 +66,13 @@ macro masked_load(ptr, bool[<*>] mask, passthru)
|
||||
@param passthru : "The value to use for non masked values"
|
||||
@param $alignment : "The alignment to assume for the pointer"
|
||||
|
||||
@require @assignable_to(&&passthru, $typeof(ptr)) : "Pointer and passthru must match"
|
||||
@require @typekind(passthru) == VECTOR : "Expected passthru to be a vector"
|
||||
@require $defined(*ptr = passthru) : "Pointer and passthru must match"
|
||||
@require $kindof(passthru) == VECTOR : "Expected passthru to be a vector"
|
||||
@require passthru.len == mask.len : "Mask and passthru must have the same length"
|
||||
@require @constant_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"
|
||||
@ensure $typeof(return) == $typeof(*ptr)
|
||||
*>
|
||||
macro @masked_load_aligned(ptr, bool[<*>] mask, passthru, usz $alignment)
|
||||
{
|
||||
@@ -82,9 +86,9 @@ macro @masked_load_aligned(ptr, bool[<*>] mask, passthru, usz $alignment)
|
||||
@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_to(&&passthru[0], $typeof(ptrvec[0])) : "Pointer and passthru must match"
|
||||
@require $kindof(ptrvec) == VECTOR : "Expected ptrvec to be a vector"
|
||||
@require $kindof(passthru) == VECTOR : "Expected passthru to be a vector"
|
||||
@require $defined(*ptrvec[0] = passthru[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"
|
||||
|
||||
@@ -104,9 +108,9 @@ macro gather(ptrvec, bool[<*>] mask, passthru)
|
||||
@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_to(&&passthru[0], $typeof(ptrvec[0])) : "Pointer and passthru must match"
|
||||
@require $kindof(ptrvec) == VECTOR : "Expected ptrvec to be a vector"
|
||||
@require $kindof(passthru) == VECTOR : "Expected passthru to be a vector"
|
||||
@require $defined(*ptrvec[0] = passthru[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 @constant_is_power_of_2($alignment) : "The alignment must be a power of two"
|
||||
@@ -126,8 +130,8 @@ macro @gather_aligned(ptrvec, bool[<*>] mask, passthru, usz $alignment)
|
||||
@param value : "The value to store masked"
|
||||
@param mask : "The mask for the store"
|
||||
|
||||
@require @assignable_to(&&value, $typeof(ptr)) : "Pointer and value must match"
|
||||
@require @typekind(value) == VECTOR : "Expected value to be a vector"
|
||||
@require $defined(*ptr = value) : "Pointer and value must match"
|
||||
@require $kindof(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)
|
||||
@@ -141,8 +145,8 @@ macro masked_store(ptr, value, bool[<*>] mask)
|
||||
@param mask : "The mask for the store"
|
||||
@param $alignment : "The alignment of the pointer"
|
||||
|
||||
@require @assignable_to(&&value, $typeof(ptr)) : "Pointer and value must match"
|
||||
@require @typekind(value) == VECTOR : "Expected value to be a vector"
|
||||
@require $defined(*ptr = value) : "Pointer and value must match"
|
||||
@require $kindof(value) == VECTOR : "Expected value to be a vector"
|
||||
@require value.len == mask.len : "Mask and value must have the same length"
|
||||
@require @constant_is_power_of_2($alignment) : "The alignment must be a power of two"
|
||||
|
||||
@@ -156,9 +160,9 @@ macro @masked_store_aligned(ptr, value, bool[<*>] mask, usz $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_to(&&value[0], $typeof(ptrvec[0])) : "Pointer and value must match"
|
||||
@require $kindof(ptrvec) == VECTOR : "Expected ptrvec to be a vector"
|
||||
@require $kindof(value) == VECTOR : "Expected value to be a vector"
|
||||
@require $defined(*ptrvec[0] = value[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"
|
||||
|
||||
@@ -174,9 +178,9 @@ macro scatter(ptrvec, value, bool[<*>] mask)
|
||||
@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_to(&&value[0], $typeof(ptrvec[0])) : "Pointer and value must match"
|
||||
@require $kindof(ptrvec) == VECTOR : "Expected ptrvec to be a vector"
|
||||
@require $kindof(value) == VECTOR : "Expected value to be a vector"
|
||||
@require $defined(*ptrvec[0] = value[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 @constant_is_power_of_2($alignment) : "The alignment must be a power of two"
|
||||
@@ -238,6 +242,9 @@ macro @volatile_store(#x, value) @builtin
|
||||
return $$volatile_store(&#x, ($typeof(#x))value);
|
||||
}
|
||||
|
||||
<*
|
||||
All possible atomic orderings
|
||||
*>
|
||||
enum AtomicOrdering : int
|
||||
{
|
||||
NOT_ATOMIC, // Not atomic
|
||||
@@ -433,7 +440,7 @@ macro void set_inline(void* dst, char val, usz $len, usz $dst_align = 0, bool $i
|
||||
@require values::@inner_kind(b) == TypeKind.SLICE || values::@inner_kind(b) == TypeKind.POINTER
|
||||
@require values::@inner_kind(a) != TypeKind.SLICE || len == -1
|
||||
@require values::@inner_kind(a) != TypeKind.POINTER || len > -1
|
||||
@require values::@assign_to(a, b) && values::@assign_to(b, a)
|
||||
@require $defined(a = b, b = a)
|
||||
*>
|
||||
macro bool equals(a, b, isz len = -1, usz $align = 0)
|
||||
{
|
||||
@@ -646,7 +653,7 @@ macro void @pool(usz reserve = 0; @body) @builtin
|
||||
@body();
|
||||
}
|
||||
|
||||
module std::core::mem @if(WASM_NOLIBC);
|
||||
module std::core::mem @if(env::FREESTANDING_WASM);
|
||||
import std::core::mem::allocator @public;
|
||||
SimpleHeapAllocator wasm_allocator @private;
|
||||
extern int __heap_base;
|
||||
@@ -684,6 +691,12 @@ macro @clone(value) @builtin @nodiscard
|
||||
return allocator::clone(mem, value);
|
||||
}
|
||||
|
||||
<*
|
||||
@param value : "The value to clone"
|
||||
@return "A pointer to the cloned value"
|
||||
*>
|
||||
macro @clone_slice(value) @builtin @nodiscard => allocator::clone_slice(mem, value);
|
||||
|
||||
<*
|
||||
@param value : "The value to clone"
|
||||
@return "A pointer to the cloned value, which must be released using free_aligned"
|
||||
@@ -706,6 +719,12 @@ macro @tclone(value) @builtin @nodiscard
|
||||
$endif
|
||||
}
|
||||
|
||||
<*
|
||||
@param value : "The value to clone"
|
||||
@return "A pointer to the cloned value"
|
||||
*>
|
||||
macro @tclone_slice(value) @builtin @nodiscard => allocator::clone_slice(tmem, value);
|
||||
|
||||
fn void* malloc(usz size) @builtin @inline @nodiscard
|
||||
{
|
||||
return allocator::malloc(mem, size);
|
||||
@@ -727,56 +746,69 @@ fn void* tmalloc(usz size, usz alignment = 0) @builtin @inline @nodiscard
|
||||
}
|
||||
|
||||
<*
|
||||
@require $vacount < 2 : "Too many arguments."
|
||||
@require $vacount == 0 ||| @assignable_to($vaexpr[0], $Type) : "The second argument must be an initializer for the type"
|
||||
@param $Type : "The type to allocate"
|
||||
@param #init : "The optional initializer"
|
||||
|
||||
@require !$defined(#init) ||| $defined($Type a = #init) : "#init must be an initializer for the type"
|
||||
@require $Type.alignof <= DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'alloc_aligned' instead"
|
||||
@return "A pointer to data of type $Type."
|
||||
*>
|
||||
macro new($Type, ...) @nodiscard
|
||||
macro new($Type, #init = ...) @nodiscard @safemacro
|
||||
{
|
||||
$if $vacount == 0:
|
||||
return ($Type*)calloc($Type.sizeof);
|
||||
$else
|
||||
$if $defined(#init):
|
||||
$Type* val = malloc($Type.sizeof);
|
||||
*val = $vaexpr[0];
|
||||
*val = #init;
|
||||
return val;
|
||||
$else
|
||||
return ($Type*)calloc($Type.sizeof);
|
||||
$endif
|
||||
}
|
||||
|
||||
<*
|
||||
@require $vacount < 2 : "Too many arguments."
|
||||
@require $vacount == 0 ||| @assignable_to($vaexpr[0], $Type) : "The second argument must be an initializer for the type"
|
||||
@require $Type.alignof <= DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'alloc_aligned' instead"
|
||||
@param $Type : "The type to allocate"
|
||||
@param padding : "The padding to add after the allocation"
|
||||
@param #init : "The optional initializer"
|
||||
|
||||
@require !$defined(#init) ||| $defined($Type a = #init) : "#init must be an initializer for the type"
|
||||
@return "A pointer to data of type $Type."
|
||||
*>
|
||||
macro new_with_padding($Type, usz padding, ...) @nodiscard
|
||||
macro new_with_padding($Type, usz padding, #init = ...) @nodiscard @safemacro
|
||||
{
|
||||
$if $vacount == 0:
|
||||
return ($Type*)calloc($Type.sizeof + padding);
|
||||
$else
|
||||
$if $defined(#init):
|
||||
$Type* val = malloc($Type.sizeof + padding);
|
||||
*val = $vaexpr[0];
|
||||
*val = #init;
|
||||
return val;
|
||||
$else
|
||||
return ($Type*)calloc($Type.sizeof + padding);
|
||||
$endif
|
||||
}
|
||||
|
||||
<*
|
||||
Allocate using an aligned allocation. This is necessary for types with a default memory alignment
|
||||
exceeding DEFAULT_MEM_ALIGNMENT. IMPORTANT! It must be freed using free_aligned.
|
||||
@require $vacount < 2 : "Too many arguments."
|
||||
@require $vacount == 0 ||| @assignable_to($vaexpr[0], $Type) : "The second argument must be an initializer for the type"
|
||||
|
||||
@param $Type : "The type to allocate"
|
||||
@param #init : "The optional initializer"
|
||||
|
||||
@require !$defined(#init) ||| $defined($Type a = #init) : "#init must be an initializer for the type"
|
||||
@return "A pointer to data of type $Type with the proper alignment"
|
||||
*>
|
||||
macro new_aligned($Type, ...) @nodiscard
|
||||
macro new_aligned($Type, #init = ...) @nodiscard @safemacro
|
||||
{
|
||||
$if $vacount == 0:
|
||||
return ($Type*)calloc_aligned($Type.sizeof, $Type.alignof);
|
||||
$else
|
||||
$if $defined(#init):
|
||||
$Type* val = malloc_aligned($Type.sizeof, $Type.alignof);
|
||||
*val = $vaexpr[0];
|
||||
*val = #init;
|
||||
return val;
|
||||
$else
|
||||
return ($Type*)calloc_aligned($Type.sizeof, $Type.alignof);
|
||||
$endif
|
||||
}
|
||||
|
||||
<*
|
||||
@param $Type : "The type to allocate"
|
||||
|
||||
@require $Type.alignof <= DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'alloc_aligned' instead"
|
||||
@return "A pointer to uninitialized data for the type $Type"
|
||||
*>
|
||||
macro alloc($Type) @nodiscard
|
||||
{
|
||||
@@ -784,7 +816,11 @@ macro alloc($Type) @nodiscard
|
||||
}
|
||||
|
||||
<*
|
||||
@param $Type : "The type to allocate"
|
||||
@param padding : "The padding to add after the allocation"
|
||||
|
||||
@require $Type.alignof <= DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'alloc_aligned' instead"
|
||||
@return "A pointer to uninitialized data for the type $Type"
|
||||
*>
|
||||
macro alloc_with_padding($Type, usz padding) @nodiscard
|
||||
{
|
||||
@@ -792,8 +828,12 @@ macro alloc_with_padding($Type, usz padding) @nodiscard
|
||||
}
|
||||
|
||||
<*
|
||||
|
||||
Allocate using an aligned allocation. This is necessary for types with a default memory alignment
|
||||
exceeding DEFAULT_MEM_ALIGNMENT. IMPORTANT! It must be freed using free_aligned.
|
||||
|
||||
@param $Type : "The type to allocate"
|
||||
@return "A pointer to uninitialized data for the type $Type with the proper alignment"
|
||||
*>
|
||||
macro alloc_aligned($Type) @nodiscard
|
||||
{
|
||||
@@ -801,32 +841,39 @@ macro alloc_aligned($Type) @nodiscard
|
||||
}
|
||||
|
||||
<*
|
||||
@require $vacount < 2 : "Too many arguments."
|
||||
@require $vacount == 0 ||| @assignable_to($vaexpr[0], $Type) : "The second argument must be an initializer for the type"
|
||||
@param $Type : "The type to allocate"
|
||||
@param #init : "The optional initializer"
|
||||
|
||||
@require !$defined(#init) ||| $defined($Type a = #init) : "#init must be an initializer for the type"
|
||||
@return "A pointer to temporary data of type $Type."
|
||||
*>
|
||||
macro tnew($Type, ...) @nodiscard
|
||||
macro tnew($Type, #init = ...) @nodiscard @safemacro
|
||||
{
|
||||
$if $vacount == 0:
|
||||
return ($Type*)tcalloc($Type.sizeof, $Type.alignof) @inline;
|
||||
$else
|
||||
$if $defined(#init):
|
||||
$Type* val = tmalloc($Type.sizeof, $Type.alignof) @inline;
|
||||
*val = $vaexpr[0];
|
||||
*val = #init;
|
||||
return val;
|
||||
$else
|
||||
return ($Type*)tcalloc($Type.sizeof, $Type.alignof) @inline;
|
||||
$endif
|
||||
}
|
||||
|
||||
<*
|
||||
@require $vacount < 2 : "Too many arguments."
|
||||
@require $vacount == 0 ||| @assignable_to($vaexpr[0], $Type) : "The second argument must be an initializer for the type"
|
||||
@param $Type : "The type to allocate"
|
||||
@param padding : "The padding to add after the allocation"
|
||||
@param #init : "The optional initializer"
|
||||
|
||||
@require !$defined(#init) ||| $defined($Type a = #init) : "#init must be an initializer for the type"
|
||||
@return "A pointer to temporary data of type $Type with added padding at the end."
|
||||
*>
|
||||
macro temp_with_padding($Type, usz padding, ...) @nodiscard
|
||||
macro temp_with_padding($Type, usz padding, #init = ...) @nodiscard @safemacro
|
||||
{
|
||||
$if $vacount == 0:
|
||||
return ($Type*)tcalloc($Type.sizeof + padding, $Type.alignof) @inline;
|
||||
$else
|
||||
$if $defined(#init):
|
||||
$Type* val = tmalloc($Type.sizeof + padding, $Type.alignof) @inline;
|
||||
*val = $vaexpr[0];
|
||||
*val = #init;
|
||||
return val;
|
||||
$else
|
||||
return ($Type*)tcalloc($Type.sizeof + padding, $Type.alignof) @inline;
|
||||
$endif
|
||||
}
|
||||
|
||||
|
||||
@@ -65,7 +65,7 @@ alias MemoryAllocFn = fn char[]?(usz);
|
||||
|
||||
|
||||
|
||||
fn usz alignment_for_allocation(usz alignment) @inline @private
|
||||
fn usz alignment_for_allocation(usz alignment) @inline
|
||||
{
|
||||
return alignment < mem::DEFAULT_MEM_ALIGNMENT ? mem::DEFAULT_MEM_ALIGNMENT : alignment;
|
||||
}
|
||||
@@ -167,7 +167,7 @@ macro void free_aligned(Allocator allocator, void* ptr)
|
||||
<*
|
||||
@require $Type.alignof <= mem::DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'new_aligned' instead"
|
||||
@require $vacount < 2 : "Too many arguments."
|
||||
@require $vacount == 0 ||| @assignable_to($vaexpr[0], $Type) : "The second argument must be an initializer for the type"
|
||||
@require $vacount == 0 ||| $defined($Type t = $vaexpr[0]) : "The second argument must be an initializer for the type"
|
||||
*>
|
||||
macro new(Allocator allocator, $Type, ...) @nodiscard
|
||||
{
|
||||
@@ -183,7 +183,7 @@ macro new(Allocator allocator, $Type, ...) @nodiscard
|
||||
<*
|
||||
@require $Type.alignof <= mem::DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'new_aligned' instead"
|
||||
@require $vacount < 2 : "Too many arguments."
|
||||
@require $vacount == 0 ||| @assignable_to($vaexpr[0], $Type) : "The second argument must be an initializer for the type"
|
||||
@require $vacount == 0 ||| $defined($Type t = $vaexpr[0]) : "The second argument must be an initializer for the type"
|
||||
*>
|
||||
macro new_try(Allocator allocator, $Type, ...) @nodiscard
|
||||
{
|
||||
@@ -200,7 +200,7 @@ macro new_try(Allocator allocator, $Type, ...) @nodiscard
|
||||
Allocate using an aligned allocation. This is necessary for types with a default memory alignment
|
||||
exceeding DEFAULT_MEM_ALIGNMENT. IMPORTANT! It must be freed using free_aligned.
|
||||
@require $vacount < 2 : "Too many arguments."
|
||||
@require $vacount == 0 ||| @assignable_to($vaexpr[0], $Type) : "The second argument must be an initializer for the type"
|
||||
@require $vacount == 0 ||| $defined($Type t = $vaexpr[0]) : "The second argument must be an initializer for the type"
|
||||
*>
|
||||
macro new_aligned(Allocator allocator, $Type, ...) @nodiscard
|
||||
{
|
||||
@@ -317,6 +317,23 @@ macro clone(Allocator allocator, value) @nodiscard
|
||||
return new(allocator, $typeof(value), value);
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&inout] allocator : "The allocator used to clone"
|
||||
@param slice : "The slice to clone"
|
||||
@return "A pointer to the cloned slice"
|
||||
|
||||
@require $kindof(slice) == SLICE || $kindof(slice) == ARRAY
|
||||
*>
|
||||
macro clone_slice(Allocator allocator, slice) @nodiscard
|
||||
{
|
||||
var $Type = $typeof(slice[0]);
|
||||
|
||||
$Type[] new_arr = new_array(allocator, $Type, slice.len);
|
||||
mem::copy(new_arr.ptr, &slice[0], slice.len * $Type.sizeof);
|
||||
|
||||
return new_arr;
|
||||
}
|
||||
|
||||
<*
|
||||
Clone overaligned values. Must be released using free_aligned.
|
||||
|
||||
@@ -348,7 +365,7 @@ macro void*? @aligned_alloc(#alloc_fn, usz bytes, usz alignment)
|
||||
if (alignment < void*.alignof) alignment = void*.alignof;
|
||||
usz header = AlignedBlock.sizeof + alignment;
|
||||
usz alignsize = bytes + header;
|
||||
$if @typekind(#alloc_fn(bytes)) == OPTIONAL:
|
||||
$if $kindof(#alloc_fn(bytes)) == OPTIONAL:
|
||||
void* data = #alloc_fn(alignsize)!;
|
||||
$else
|
||||
void* data = #alloc_fn(alignsize);
|
||||
@@ -369,7 +386,7 @@ struct AlignedBlock
|
||||
macro void? @aligned_free(#free_fn, void* old_pointer)
|
||||
{
|
||||
AlignedBlock* desc = (AlignedBlock*)old_pointer - 1;
|
||||
$if @typekind(#free_fn(desc.start)) == OPTIONAL:
|
||||
$if $kindof(#free_fn(desc.start)) == OPTIONAL:
|
||||
#free_fn(desc.start)!;
|
||||
$else
|
||||
#free_fn(desc.start);
|
||||
@@ -386,7 +403,7 @@ macro void*? @aligned_realloc(#calloc_fn, #free_fn, void* old_pointer, usz bytes
|
||||
void* data_start = desc.start;
|
||||
void* new_data = @aligned_alloc(#calloc_fn, bytes, alignment)!;
|
||||
mem::copy(new_data, old_pointer, desc.len < bytes ? desc.len : bytes, 1, 1);
|
||||
$if @typekind(#free_fn(data_start)) == OPTIONAL:
|
||||
$if $kindof(#free_fn(data_start)) == OPTIONAL:
|
||||
#free_fn(data_start)!;
|
||||
$else
|
||||
#free_fn(data_start);
|
||||
@@ -494,7 +511,7 @@ macro Allocator temp() @deprecated("Use 'tmem' instead")
|
||||
|
||||
alias tmem @builtin = current_temp;
|
||||
|
||||
fn void allow_implicit_temp_allocator_on_load_thread() @init(1) @local @if(env::LIBC || env::WASM_NOLIBC)
|
||||
fn void allow_implicit_temp_allocator_on_load_thread() @init(1) @local @if(env::LIBC || env::FREESTANDING_WASM)
|
||||
{
|
||||
auto_create_temp = true;
|
||||
}
|
||||
|
||||
252
lib/std/core/mem_mempool.c3
Normal file
252
lib/std/core/mem_mempool.c3
Normal file
@@ -0,0 +1,252 @@
|
||||
module std::core::mem::mempool;
|
||||
import std::core::mem, std::core::mem::allocator, std::math;
|
||||
import std::core::sanitizer::asan;
|
||||
|
||||
const INITIAL_CAPACITY = 0;
|
||||
|
||||
struct FixedBlockPoolNode
|
||||
{
|
||||
void* buffer;
|
||||
FixedBlockPoolNode *next;
|
||||
usz capacity;
|
||||
}
|
||||
|
||||
struct FixedBlockPoolEntry
|
||||
{
|
||||
void *previous;
|
||||
}
|
||||
|
||||
<*
|
||||
Fixed blocks pool pre-allocating blocks backed by an Allocator which are then reserved for the user,
|
||||
blocks deallocated by the user are later re-used by future blocks allocations
|
||||
|
||||
`grow_capacity` can be changed in order to affect how many blocks will be allocated by next pool allocation,
|
||||
it has to be greater than 0
|
||||
`allocated` number of allocated blocks
|
||||
`used` number of used blocks by the user
|
||||
*>
|
||||
struct FixedBlockPool
|
||||
{
|
||||
Allocator allocator;
|
||||
FixedBlockPoolNode head;
|
||||
FixedBlockPoolNode *tail;
|
||||
void *next_free;
|
||||
void *freelist;
|
||||
usz block_size;
|
||||
usz grow_capacity;
|
||||
usz allocated;
|
||||
usz page_size;
|
||||
usz alignment;
|
||||
usz used;
|
||||
bool initialized;
|
||||
}
|
||||
|
||||
<*
|
||||
Initialize an block pool
|
||||
|
||||
@param [in] allocator : "The allocator to use"
|
||||
@param block_size : "The block size to use"
|
||||
@param capacity : "The amount of blocks to be pre-allocated"
|
||||
@param alignment : "The alignment of the buffer"
|
||||
@require !alignment || math::is_power_of_2(alignment)
|
||||
@require !self.initialized : "The block pool must not be initialized"
|
||||
@require block_size > 0 : "Block size must be non zero"
|
||||
@require calculate_actual_capacity(capacity, block_size) * block_size >= block_size
|
||||
: "Total memory would overflow"
|
||||
*>
|
||||
macro FixedBlockPool* FixedBlockPool.init(&self, Allocator allocator, usz block_size, usz capacity = INITIAL_CAPACITY, usz alignment = 0)
|
||||
{
|
||||
self.allocator = allocator;
|
||||
self.tail = &self.head;
|
||||
self.head.next = null;
|
||||
self.block_size = math::max(block_size, FixedBlockPoolEntry.sizeof);
|
||||
capacity = calculate_actual_capacity(capacity, self.block_size);
|
||||
self.alignment = allocator::alignment_for_allocation(alignment);
|
||||
self.page_size = capacity * self.block_size;
|
||||
assert(self.page_size >= self.block_size, "Total memory would overflow %d %d", block_size, capacity);
|
||||
self.head.buffer = self.allocate_page();
|
||||
$if env::COMPILER_SAFE_MODE && env::ADDRESS_SANITIZER:
|
||||
asan::poison_memory_region(self.head.buffer, self.page_size);
|
||||
$endif
|
||||
self.head.capacity = capacity;
|
||||
self.next_free = self.head.buffer;
|
||||
self.freelist = null;
|
||||
self.grow_capacity = capacity;
|
||||
self.initialized = true;
|
||||
self.allocated = capacity;
|
||||
self.used = 0;
|
||||
return self;
|
||||
}
|
||||
|
||||
<*
|
||||
Initialize an block pool
|
||||
|
||||
@param [in] allocator : "The allocator to use"
|
||||
@param $Type : "The type used for setting the block size"
|
||||
@param capacity : "The amount of blocks to be pre-allocated"
|
||||
@require !self.initialized : "The block pool must not be initialized"
|
||||
*>
|
||||
macro FixedBlockPool* FixedBlockPool.init_for_type(&self, Allocator allocator, $Type, usz capacity = INITIAL_CAPACITY)
|
||||
{
|
||||
return self.init(allocator, $Type.sizeof, capacity, $Type.alignof);
|
||||
}
|
||||
|
||||
<*
|
||||
Initialize an block pool using Temporary allocator
|
||||
|
||||
@param $Type : "The type used for setting the block size"
|
||||
@param capacity : "The amount of blocks to be pre-allocated"
|
||||
@require !self.initialized : "The block pool must not be initialized"
|
||||
*>
|
||||
macro FixedBlockPool* FixedBlockPool.tinit_for_type(&self, $Type, usz capacity = INITIAL_CAPACITY) => self.init_for_type(tmem, $Type, capacity);
|
||||
|
||||
<*
|
||||
Initialize an block pool using Temporary allocator
|
||||
|
||||
@param block_size : "The block size to use"
|
||||
@param capacity : "The amount of blocks to be pre-allocated"
|
||||
@require !self.initialized : "The block pool must not be initialized"
|
||||
*>
|
||||
macro FixedBlockPool* FixedBlockPool.tinit(&self, usz block_size, usz capacity = INITIAL_CAPACITY) => self.init(tmem, block_size, capacity);
|
||||
|
||||
<*
|
||||
Free up the entire block pool
|
||||
|
||||
@require self.initialized : "The block pool must be initialized"
|
||||
*>
|
||||
fn void FixedBlockPool.free(&self)
|
||||
{
|
||||
$if env::COMPILER_SAFE_MODE && env::ADDRESS_SANITIZER:
|
||||
asan::unpoison_memory_region(self.head.buffer, self.page_size);
|
||||
$endif
|
||||
self.free_page(self.head.buffer);
|
||||
FixedBlockPoolNode* iter = self.head.next;
|
||||
|
||||
while (iter)
|
||||
{
|
||||
$if env::COMPILER_SAFE_MODE && env::ADDRESS_SANITIZER:
|
||||
asan::unpoison_memory_region(iter.buffer, self.page_size);
|
||||
$endif
|
||||
self.free_page(iter.buffer);
|
||||
FixedBlockPoolNode* current = iter;
|
||||
iter = iter.next;
|
||||
allocator::free(self.allocator, current);
|
||||
}
|
||||
self.initialized = false;
|
||||
self.allocated = 0;
|
||||
self.used = 0;
|
||||
}
|
||||
|
||||
<*
|
||||
Allocate an block on the block pool, re-uses previously deallocated blocks
|
||||
|
||||
@require self.initialized : "The block pool must be initialized"
|
||||
*>
|
||||
fn void* FixedBlockPool.alloc(&self)
|
||||
{
|
||||
defer self.used++;
|
||||
|
||||
if (self.freelist)
|
||||
{
|
||||
FixedBlockPoolEntry* entry = self.freelist;
|
||||
$if env::COMPILER_SAFE_MODE && env::ADDRESS_SANITIZER:
|
||||
asan::unpoison_memory_region(entry, self.block_size);
|
||||
$endif
|
||||
self.freelist = entry.previous;
|
||||
mem::clear(entry, self.block_size);
|
||||
return entry;
|
||||
}
|
||||
|
||||
void* end = self.tail.buffer + (self.tail.capacity * self.block_size);
|
||||
if (self.next_free >= end) self.new_node();
|
||||
void* ptr = self.next_free;
|
||||
self.next_free += self.block_size;
|
||||
$if env::COMPILER_SAFE_MODE && env::ADDRESS_SANITIZER:
|
||||
asan::unpoison_memory_region(ptr, self.block_size);
|
||||
$endif
|
||||
|
||||
return ptr;
|
||||
}
|
||||
|
||||
<*
|
||||
Deallocate a block from the block pool
|
||||
|
||||
@require self.initialized : "The block pool must be initialized"
|
||||
@require self.check_ptr(ptr) : "The pointer should be part of the pool"
|
||||
*>
|
||||
fn void FixedBlockPool.dealloc(&self, void* ptr)
|
||||
{
|
||||
$if env::COMPILER_SAFE_MODE && !env::ADDRESS_SANITIZER:
|
||||
mem::set(ptr, 0xAA, self.block_size);
|
||||
$endif
|
||||
|
||||
FixedBlockPoolEntry* entry = ptr;
|
||||
entry.previous = self.freelist;
|
||||
self.freelist = entry;
|
||||
self.used--;
|
||||
|
||||
$if env::COMPILER_SAFE_MODE && env::ADDRESS_SANITIZER:
|
||||
asan::poison_memory_region(ptr, self.block_size);
|
||||
$endif
|
||||
}
|
||||
|
||||
<*
|
||||
@require self.initialized : "The block pool must be initialized"
|
||||
*>
|
||||
fn bool FixedBlockPool.check_ptr(&self, void *ptr) @local
|
||||
{
|
||||
FixedBlockPoolNode* iter = &self.head;
|
||||
|
||||
while (iter)
|
||||
{
|
||||
void* end = iter.buffer + (iter.capacity * self.block_size);
|
||||
if (ptr >= iter.buffer && ptr < end) return true;
|
||||
iter = iter.next;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
<*
|
||||
@require self.grow_capacity > 0 : "How many blocks will it store"
|
||||
*>
|
||||
fn void FixedBlockPool.new_node(&self) @local
|
||||
{
|
||||
FixedBlockPoolNode* node = allocator::new(self.allocator, FixedBlockPoolNode);
|
||||
node.buffer = self.allocate_page();
|
||||
$if env::COMPILER_SAFE_MODE && env::ADDRESS_SANITIZER:
|
||||
asan::poison_memory_region(node.buffer, self.page_size);
|
||||
$endif
|
||||
node.capacity = self.grow_capacity;
|
||||
self.tail.next = node;
|
||||
self.tail = node;
|
||||
self.next_free = node.buffer;
|
||||
self.allocated += node.capacity;
|
||||
}
|
||||
|
||||
macro void* FixedBlockPool.allocate_page(&self) @private
|
||||
{
|
||||
return self.alignment > mem::DEFAULT_MEM_ALIGNMENT
|
||||
? allocator::calloc_aligned(self.allocator, self.page_size, self.alignment)!!
|
||||
: allocator::calloc(self.allocator, self.page_size);
|
||||
}
|
||||
|
||||
macro void FixedBlockPool.free_page(&self, void* page) @private
|
||||
{
|
||||
if (self.alignment > mem::DEFAULT_MEM_ALIGNMENT)
|
||||
{
|
||||
allocator::free_aligned(self.allocator, page);
|
||||
}
|
||||
else
|
||||
{
|
||||
allocator::free(self.allocator, page);
|
||||
}
|
||||
}
|
||||
|
||||
macro usz calculate_actual_capacity(usz capacity, usz block_size) @private
|
||||
{
|
||||
// Assume some overhead
|
||||
if (capacity) return capacity;
|
||||
capacity = (mem::os_pagesize() - 128) / block_size;
|
||||
return capacity ?: 1;
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
The VM module holds code for working with virtual memory on supported platforms (currently Win32 and Posix)
|
||||
*>
|
||||
module std::core::mem::vm;
|
||||
import std::os::win32, std::os::posix, libc;
|
||||
import std::io, std::os::win32, std::os::posix, libc;
|
||||
|
||||
<*
|
||||
VirtualMemory is an abstraction for working with an allocated virtual memory area. It will invoke vm:: functions
|
||||
@@ -219,6 +219,36 @@ fn void? decommit(void* ptr, usz len, bool block = true)
|
||||
$endswitch
|
||||
}
|
||||
|
||||
<*
|
||||
Map a portion of an already-opened file into memory.
|
||||
|
||||
@param fd : "File descriptor"
|
||||
@param size : "Number of bytes to map, will be rounded up to page size"
|
||||
@param offset : "Byte offset in file, must be page size aligned"
|
||||
@param access : "The initial access permissions"
|
||||
@param shared : "if True then MAP_SHARED else MAP_PRIVATE"
|
||||
@return? mem::OUT_OF_MEMORY, RANGE_OVERFLOW, UNKNOWN_ERROR, ACCESS_DENIED, INVALID_ARGS, io::NO_PERMISSION, io::FILE_NOT_VALID, io::WOULD_BLOCK, io::FILE_NOT_FOUND
|
||||
@return "Pointer to the mapped region"
|
||||
*>
|
||||
fn void*? mmap_file(Fd fd, usz size, usz offset = 0, VirtualMemoryAccess access = READ, bool shared = false) @if (env::POSIX)
|
||||
{
|
||||
CInt flags = shared ? posix::MAP_SHARED : posix::MAP_PRIVATE;
|
||||
void* ptr = posix::mmap(null, aligned_alloc_size(size), access.to_posix(), flags, fd, offset);
|
||||
if (ptr != posix::MAP_FAILED) return ptr;
|
||||
switch (libc::errno())
|
||||
{
|
||||
case errno::ENOMEM: return mem::OUT_OF_MEMORY?;
|
||||
case errno::EOVERFLOW: return RANGE_OVERFLOW?;
|
||||
case errno::EPERM: return ACCESS_DENIED?;
|
||||
case errno::EINVAL: return INVALID_ARGS?;
|
||||
case errno::EACCES: return io::NO_PERMISSION?;
|
||||
case errno::EBADF: return io::FILE_NOT_VALID?;
|
||||
case errno::EAGAIN: return io::WOULD_BLOCK?;
|
||||
case errno::ENXIO: return io::FILE_NOT_FOUND?;
|
||||
default: return UNKNOWN_ERROR?;
|
||||
}
|
||||
}
|
||||
|
||||
<*
|
||||
Create a VirtualMemory using
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
then free the pointer and the atomic variable assuming that they are allocated using the Allocator in the Ref.
|
||||
|
||||
@require !$defined(Type.dealloc) ||| $defined(Type.dealloc(&&(Type){})) : "'dealloc' must only take a pointer to the underlying type"
|
||||
@require !$defined(Type.dealloc) ||| @typeis((Type){}.dealloc(), void) : "'dealloc' must return 'void'"
|
||||
@require !$defined(Type.dealloc) ||| $typeof((Type){}.dealloc()) == void : "'dealloc' must return 'void'"
|
||||
*>
|
||||
module std::core::mem::ref { Type };
|
||||
import std::thread, std::atomic;
|
||||
@@ -21,7 +21,7 @@ fn Ref wrap(Type* ptr, Allocator allocator = mem)
|
||||
}
|
||||
<*
|
||||
@require $vacount < 2 : "Too many arguments."
|
||||
@require $vacount == 0 ||| @assignable_to($vaexpr[0], Type) : "The first argument must be an initializer for the type"
|
||||
@require $vacount == 0 ||| $defined(Type a = $vaexpr[0]) : "The first argument must be an initializer for the type"
|
||||
*>
|
||||
macro Ref new(..., Allocator allocator = mem)
|
||||
{
|
||||
@@ -99,7 +99,7 @@ struct RefCounted
|
||||
}
|
||||
|
||||
<*
|
||||
@require @assignable_to(refcounted, RefCounted*) : "Expected a ref counted value"
|
||||
@require $defined(RefCounted* c = refcounted) : "Expected a ref counted value"
|
||||
*>
|
||||
macro retain(refcounted)
|
||||
{
|
||||
@@ -112,8 +112,8 @@ macro retain(refcounted)
|
||||
}
|
||||
|
||||
<*
|
||||
@require @assignable_to(refcounted, RefCounted*) : "Expected a ref counted value"
|
||||
@require !$defined(refcounted.dealloc()) ||| @typeis(refcounted.dealloc(), void)
|
||||
@require $defined(RefCounted* c = refcounted) : "Expected a ref counted value"
|
||||
@require !$defined(refcounted.dealloc()) ||| $typeof(refcounted.dealloc()) == void
|
||||
: "Expected refcounted type to have a valid dealloc"
|
||||
*>
|
||||
macro void release(refcounted)
|
||||
|
||||
@@ -39,7 +39,7 @@ macro @enum_lookup_new($Type, $name, value)
|
||||
}
|
||||
|
||||
|
||||
module std::core::runtime @if(WASM_NOLIBC);
|
||||
module std::core::runtime @if(env::FREESTANDING_WASM);
|
||||
|
||||
extern fn void __wasm_call_ctors();
|
||||
fn void wasm_initialize() @extern("_initialize") @wasm
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
module std::core::runtime;
|
||||
import libc, std::time, std::io, std::sort;
|
||||
import libc, std::time, std::io, std::sort, std::math, std::collections::map;
|
||||
|
||||
alias BenchmarkFn = fn void();
|
||||
alias BenchmarkFn = fn void ();
|
||||
|
||||
HashMap { String, uint } bench_fn_iters @local;
|
||||
|
||||
struct BenchmarkUnit
|
||||
{
|
||||
@@ -17,6 +19,7 @@ fn BenchmarkUnit[] benchmark_collection_create(Allocator allocator)
|
||||
foreach (i, benchmark : fns)
|
||||
{
|
||||
benchmarks[i] = { names[i], fns[i] };
|
||||
if (!bench_fn_iters.has_key(names[i])) bench_fn_iters[names[i]] = benchmark_max_iterations;
|
||||
}
|
||||
return benchmarks;
|
||||
}
|
||||
@@ -36,6 +39,49 @@ fn void set_benchmark_max_iterations(uint value) @builtin
|
||||
{
|
||||
assert(value > 0);
|
||||
benchmark_max_iterations = value;
|
||||
foreach (k : bench_fn_iters.key_iter()) bench_fn_iters[k] = value;
|
||||
}
|
||||
|
||||
fn void set_benchmark_func_iterations(String func, uint value) @builtin
|
||||
{
|
||||
assert(value > 0);
|
||||
bench_fn_iters[func] = value;
|
||||
}
|
||||
|
||||
|
||||
Clock benchmark_clock @local;
|
||||
NanoDuration benchmark_nano_seconds @local;
|
||||
long cycle_start @local;
|
||||
long cycle_stop @local;
|
||||
DString benchmark_log @local;
|
||||
bool benchmark_warming @local;
|
||||
uint this_iteration @local;
|
||||
bool benchmark_stop @local;
|
||||
|
||||
macro void @start_benchmark()
|
||||
{
|
||||
benchmark_clock = clock::now();
|
||||
cycle_start = $$sysclock();
|
||||
}
|
||||
|
||||
macro void @end_benchmark()
|
||||
{
|
||||
benchmark_nano_seconds = benchmark_clock.mark();
|
||||
cycle_stop = $$sysclock();
|
||||
}
|
||||
|
||||
macro void @kill_benchmark(String format, ...)
|
||||
{
|
||||
@log_benchmark(format, $vasplat);
|
||||
benchmark_stop = true;
|
||||
}
|
||||
|
||||
macro void @log_benchmark(msg, args...) => @pool()
|
||||
{
|
||||
if (benchmark_warming) return;
|
||||
|
||||
benchmark_log.appendf("%s [%d]: ", $$FUNC, this_iteration);
|
||||
benchmark_log.appendfn(msg, ...args);
|
||||
}
|
||||
|
||||
fn bool run_benchmarks(BenchmarkUnit[] benchmarks)
|
||||
@@ -58,36 +104,69 @@ fn bool run_benchmarks(BenchmarkUnit[] benchmarks)
|
||||
|
||||
name.clear();
|
||||
|
||||
long sys_clock_started;
|
||||
long sys_clock_finished;
|
||||
long sys_clocks;
|
||||
Clock clock;
|
||||
|
||||
foreach(unit : benchmarks)
|
||||
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());
|
||||
|
||||
benchmark_warming = true;
|
||||
for (uint i = 0; i < benchmark_warmup_iterations; i++)
|
||||
{
|
||||
unit.func() @inline;
|
||||
}
|
||||
benchmark_warming = false;
|
||||
|
||||
clock = std::time::clock::now();
|
||||
sys_clock_started = $$sysclock();
|
||||
NanoDuration running_timer;
|
||||
long total_clocks;
|
||||
|
||||
for (uint i = 0; i < benchmark_max_iterations; i++)
|
||||
uint current_benchmark_iterations = bench_fn_iters[unit.name] ?? benchmark_max_iterations;
|
||||
char[] perc_str = { [0..19] = ' ', [20] = 0 };
|
||||
int perc = 0;
|
||||
uint print_step = current_benchmark_iterations / 100;
|
||||
|
||||
for (this_iteration = 0; this_iteration < current_benchmark_iterations; ++this_iteration, benchmark_nano_seconds = {})
|
||||
{
|
||||
if (0 == this_iteration % print_step) // only print right about when the % will update
|
||||
{
|
||||
perc_str[0..(uint)math::floor((this_iteration / (float)current_benchmark_iterations) * 20)] = '#';
|
||||
perc = (uint)math::ceil(100 * (this_iteration / (float)current_benchmark_iterations));
|
||||
|
||||
io::printf("\r%s [%s] %d / %d (%d%%)", name.str_view(), (ZString)perc_str, this_iteration, current_benchmark_iterations, perc);
|
||||
io::stdout().flush()!!;
|
||||
}
|
||||
|
||||
@start_benchmark(); // can be overridden by calls inside the unit's func
|
||||
|
||||
unit.func() @inline;
|
||||
if (benchmark_stop) return false;
|
||||
|
||||
if (benchmark_nano_seconds == (NanoDuration){}) @end_benchmark(); // only mark when it wasn't already by the unit.func
|
||||
|
||||
total_clocks += cycle_stop - cycle_start;
|
||||
running_timer += benchmark_nano_seconds;
|
||||
}
|
||||
|
||||
sys_clock_finished = $$sysclock();
|
||||
NanoDuration nano_seconds = clock.mark();
|
||||
sys_clocks = sys_clock_finished - sys_clock_started;
|
||||
float clock_cycles = (float)total_clocks / current_benchmark_iterations;
|
||||
float measurement = (float)running_timer / current_benchmark_iterations;
|
||||
String[] units = { "nanoseconds", "microseconds", "milliseconds", "seconds" };
|
||||
|
||||
io::printfn("[COMPLETE] %.2f ns, %.2f CPU's clocks", (float)nano_seconds / benchmark_max_iterations, (float)sys_clocks / benchmark_max_iterations);
|
||||
float adjusted_measurement = measurement;
|
||||
while (adjusted_measurement > 1_000) adjusted_measurement /= 1_000;
|
||||
float adjusted_runtime_total = (float)running_timer;
|
||||
while (adjusted_runtime_total > 1_000) adjusted_runtime_total /= 1_000;
|
||||
|
||||
io::printf("\r%s ", name.str_view());
|
||||
io::printfn(
|
||||
"[COMPLETE] %.2f %s, %.2f CPU clocks, %d iterations (runtime %.2f %s)",
|
||||
adjusted_measurement,
|
||||
units[math::min(3, (int)math::floor(math::log(measurement, 1_000)))],
|
||||
clock_cycles,
|
||||
current_benchmark_iterations,
|
||||
adjusted_runtime_total,
|
||||
units[math::min(3, (int)math::floor(math::log((float)running_timer, 1_000)))],
|
||||
);
|
||||
}
|
||||
|
||||
io::printfn("\n%d benchmark%s run.\n", benchmarks.len, benchmarks.len > 1 ? "s" : "");
|
||||
@@ -96,5 +175,12 @@ fn bool run_benchmarks(BenchmarkUnit[] benchmarks)
|
||||
|
||||
fn bool default_benchmark_runner(String[] args) => @pool()
|
||||
{
|
||||
benchmark_log.init(mem);
|
||||
defer
|
||||
{
|
||||
if (benchmark_log.len()) io::printfn("\n---------- BENCHMARK LOG ----------\n%s\n", benchmark_log.str_view());
|
||||
benchmark_log.free();
|
||||
}
|
||||
|
||||
return run_benchmarks(benchmark_collection_create(tmem));
|
||||
}
|
||||
|
||||
@@ -14,9 +14,9 @@ TestContext* test_context @private;
|
||||
struct TestContext
|
||||
{
|
||||
JmpBuf buf;
|
||||
// Allows filtering test cased or modules by substring, e.g. 'foo::', 'foo::test_add'
|
||||
<* Allows filtering test cased or modules by substring, e.g. 'foo::', 'foo::test_add' *>
|
||||
String test_filter;
|
||||
// Triggers debugger breakpoint when assert or test:: checks failed
|
||||
<* Triggers debugger breakpoint when assert or test:: checks failed *>
|
||||
bool breakpoint_on_assert;
|
||||
|
||||
// internal state
|
||||
@@ -140,7 +140,6 @@ fn void unmute_output(bool has_error) @local
|
||||
usz log_size = test_context.fake_stdout.seek(0, Seek.CURSOR)!!;
|
||||
if (has_error)
|
||||
{
|
||||
io::printf("\nTesting %s ", test_context.current_test_name);
|
||||
io::printn(test_context.has_ansi_codes ? "[\e[0;31mFAIL\e[0m]" : "[FAIL]");
|
||||
}
|
||||
|
||||
@@ -171,6 +170,11 @@ fn bool run_tests(String[] args, TestUnit[] tests) @private
|
||||
usz max_name;
|
||||
bool sort_tests = true;
|
||||
bool check_leaks = true;
|
||||
if (!tests.len)
|
||||
{
|
||||
io::printn("There are no test units to run.");
|
||||
return true; // no tests == technically a pass
|
||||
}
|
||||
foreach (&unit : tests)
|
||||
{
|
||||
if (max_name < unit.name.len) max_name = unit.name.len;
|
||||
@@ -305,7 +309,7 @@ fn bool run_tests(String[] args, TestUnit[] tests) @private
|
||||
}
|
||||
mem.free();
|
||||
}
|
||||
io::printfn("\n%d test%s run.\n", test_count-tests_skipped, test_count > 1 ? "s" : "");
|
||||
io::printfn("\n%d test%s run.\n", test_count-tests_skipped, test_count != 1 ? "s" : "");
|
||||
|
||||
int n_failed = test_count - tests_passed - tests_skipped;
|
||||
io::printf("Test Result: %s%s%s: ",
|
||||
|
||||
@@ -29,11 +29,11 @@ alias ErrorCallback = fn void (ZString);
|
||||
@param addr : "Start of memory region."
|
||||
@param size : "Size of memory region."
|
||||
*>
|
||||
macro poison_memory_region(void* addr, usz size)
|
||||
macro void poison_memory_region(void* addr, usz size)
|
||||
{
|
||||
$if env::ADDRESS_SANITIZER:
|
||||
$if env::ADDRESS_SANITIZER:
|
||||
__asan_poison_memory_region(addr, size);
|
||||
$endif
|
||||
$endif
|
||||
}
|
||||
|
||||
<*
|
||||
@@ -50,11 +50,11 @@ macro poison_memory_region(void* addr, usz size)
|
||||
@param addr : "Start of memory region."
|
||||
@param size : "Size of memory region."
|
||||
*>
|
||||
macro unpoison_memory_region(void* addr, usz size)
|
||||
macro void unpoison_memory_region(void* addr, usz size)
|
||||
{
|
||||
$if env::ADDRESS_SANITIZER:
|
||||
$if env::ADDRESS_SANITIZER:
|
||||
__asan_unpoison_memory_region(addr, size);
|
||||
$endif
|
||||
$endif
|
||||
}
|
||||
|
||||
<*
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
module std::core::string;
|
||||
import std::io;
|
||||
import std::core::mem::allocator;
|
||||
import std::io, std::ascii;
|
||||
|
||||
|
||||
typedef String @if(!$defined(String)) = inline char[];
|
||||
@@ -110,16 +109,25 @@ fn String format(Allocator allocator, String fmt, args...) @format(1) => @pool()
|
||||
}
|
||||
|
||||
<*
|
||||
Return a new String created using the formatting function.
|
||||
Return a new String created using the formatting function, the resulting string must fit the buffer.
|
||||
|
||||
@param [inout] buffer : `The buffer to use`
|
||||
@param [in] fmt : `The formatting string`
|
||||
*>
|
||||
fn String bformat(char[] buffer, String fmt, args...) @format(1)
|
||||
{
|
||||
DString str = dstring::new_with_capacity(allocator::wrap(buffer), fmt.len + args.len * 8);
|
||||
str.appendf(fmt, ...args);
|
||||
return str.str_view();
|
||||
Formatter f;
|
||||
OutputFn format_fn = fn void?(void* buf, char c) {
|
||||
char[]* buffer_ref = buf;
|
||||
char[] buffer = *buffer_ref;
|
||||
if (buffer.len == 0) return io::BUFFER_EXCEEDED?;
|
||||
buffer[0] = c;
|
||||
*buffer_ref = buffer[1..];
|
||||
};
|
||||
char[] buffer_copy = buffer;
|
||||
f.init(format_fn, &buffer_copy);
|
||||
usz len = f.vprintf(fmt, args)!!;
|
||||
return (String)buffer[:len];
|
||||
}
|
||||
|
||||
<*
|
||||
@@ -138,7 +146,7 @@ fn String tformat(String fmt, args...) @format(0)
|
||||
Check if a character is in a set.
|
||||
|
||||
@param c : `the character to check`
|
||||
@param [in] set : `The formatting string`
|
||||
@param [in] set : `String containing the characters`
|
||||
@pure
|
||||
@return `True if a character is in the set`
|
||||
*>
|
||||
@@ -187,7 +195,7 @@ fn String String.replace(self, Allocator allocator, String needle, String new_st
|
||||
@pool()
|
||||
{
|
||||
String[] split = self.tsplit(needle);
|
||||
return dstring::join(tmem, split, new_str).copy_str(mem);
|
||||
return dstring::join(tmem, split, new_str).copy_str(allocator);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -214,11 +222,28 @@ fn String String.treplace(self, String needle, String new_str)
|
||||
@pure
|
||||
@return `a substring of the string passed in`
|
||||
*>
|
||||
fn String String.trim(self, String to_trim = "\t\n\r ")
|
||||
fn String String.trim(self, String to_trim = " \n\t\r\f\v")
|
||||
{
|
||||
return self.trim_left(to_trim).trim_right(to_trim);
|
||||
}
|
||||
|
||||
<*
|
||||
Remove characters from the front and end of a string.
|
||||
|
||||
@param [in] self : `The string to trim`
|
||||
@param to_trim : `The set of characters to trim, defaults to whitespace`
|
||||
@pure
|
||||
@return `a substring of the string passed in`
|
||||
*>
|
||||
fn String String.trim_charset(self, AsciiCharset to_trim = ascii::WHITESPACE_SET)
|
||||
{
|
||||
usz start = 0;
|
||||
usz len = self.len;
|
||||
while (start < len && to_trim.contains(self[start])) start++;
|
||||
while (len > start && to_trim.contains(self[len - 1])) len--;
|
||||
return self[start..len - 1];
|
||||
}
|
||||
|
||||
<*
|
||||
Remove characters from the front of a string.
|
||||
|
||||
@@ -227,7 +252,7 @@ fn String String.trim(self, String to_trim = "\t\n\r ")
|
||||
@pure
|
||||
@return `a substring of the string passed in`
|
||||
*>
|
||||
fn String String.trim_left(self, String to_trim = "\t\n\r ")
|
||||
fn String String.trim_left(self, String to_trim = " \n\t\r\f\v")
|
||||
{
|
||||
usz start = 0;
|
||||
usz len = self.len;
|
||||
@@ -244,7 +269,7 @@ fn String String.trim_left(self, String to_trim = "\t\n\r ")
|
||||
@pure
|
||||
@return `a substring of the string passed in`
|
||||
*>
|
||||
fn String String.trim_right(self, String to_trim = "\t\n\r ")
|
||||
fn String String.trim_right(self, String to_trim = " \n\t\r\f\v")
|
||||
{
|
||||
usz len = self.len;
|
||||
while (len > 0 && char_in_set(self[len - 1], to_trim)) len--;
|
||||
@@ -427,6 +452,19 @@ fn bool String.contains(s, String substr)
|
||||
return @ok(s.index_of(substr));
|
||||
}
|
||||
|
||||
<*
|
||||
Check if a character is found in the string.
|
||||
|
||||
@param [in] s
|
||||
@param character : "The character to look for."
|
||||
@pure
|
||||
@return "true if the string contains the character, false otherwise"
|
||||
*>
|
||||
fn bool String.contains_char(s, char character)
|
||||
{
|
||||
return @ok(s.index_of_char(character));
|
||||
}
|
||||
|
||||
<*
|
||||
Check how many non-overlapping instances of a substring there is.
|
||||
|
||||
|
||||
@@ -115,7 +115,9 @@ fn bool TypeKind.is_int(kind) @inline
|
||||
return kind == TypeKind.SIGNED_INT || kind == TypeKind.UNSIGNED_INT;
|
||||
}
|
||||
|
||||
macro bool is_slice_convertable($Type)
|
||||
macro bool is_slice_convertable($Type) @deprecated("Use is_slice_convertible") => is_slice_convertible($Type);
|
||||
|
||||
macro bool is_slice_convertible($Type)
|
||||
{
|
||||
$switch $Type.kindof:
|
||||
$case SLICE:
|
||||
@@ -339,6 +341,8 @@ macro bool is_same_vector_type($Type1, $Type2) @const
|
||||
$endif
|
||||
}
|
||||
|
||||
macro bool has_equals($Type) @const => $defined(($Type){} == ($Type){});
|
||||
|
||||
macro bool is_equatable_type($Type) @const
|
||||
{
|
||||
$if $defined($Type.less) || $defined($Type.compare_to) || $defined($Type.equals):
|
||||
|
||||
@@ -2,10 +2,11 @@ module std::core::values;
|
||||
import std::core::types;
|
||||
|
||||
|
||||
macro bool @typematch(#value1, #value2) @builtin @const => $typeof(#value1) == $typeof(#value2);
|
||||
<*
|
||||
Return true if two values have the same type before any conversions.
|
||||
*>
|
||||
macro bool @is_same_type(#value1, #value2) @const => $typeof(#value1).typeid == $typeof(#value2).typeid;
|
||||
macro bool @is_same_type(#value1, #value2) @const @deprecated("Use @typematch") => $typeof(#value1).typeid == $typeof(#value2).typeid;
|
||||
macro bool @is_bool(#value) @const => types::is_bool($typeof(#value));
|
||||
macro bool @is_int(#value) @const => types::is_int($typeof(#value));
|
||||
macro bool @is_flat_intlike(#value) @const => types::is_flat_intlike($typeof(#value));
|
||||
@@ -15,12 +16,11 @@ macro bool @is_promotable_to_floatlike(#value) @const => types::is_promotable_to
|
||||
macro bool @is_promotable_to_float(#value) @const => types::is_promotable_to_float($typeof(#value));
|
||||
macro bool @is_vector(#value) @const => types::is_vector($typeof(#value));
|
||||
macro bool @is_same_vector_type(#value1, #value2) @const => types::is_same_vector_type($typeof(#value1), $typeof(#value2));
|
||||
macro bool @assign_to(#value1, #value2) @const => @assignable_to(#value1, $typeof(#value2));
|
||||
macro bool @is_lvalue(#value) => $defined(#value = #value);
|
||||
macro bool @is_const(#foo) @const @builtin
|
||||
macro bool @assign_to(#value1, #value2) @const @deprecated("use '$defined(#value1 = #value2)'") => @assignable_to(#value1, $typeof(#value2));
|
||||
macro bool @is_lvalue(#value) @deprecated("use '$defined(#value = #value)'")=> $defined(#value = #value);
|
||||
macro bool @is_const(#foo) @const @builtin @deprecated("use '$defined(var $v = expr)'")
|
||||
{
|
||||
var $v;
|
||||
return $defined($v = #foo);
|
||||
return $defined(var $v = #foo);
|
||||
}
|
||||
|
||||
macro promote_int(x)
|
||||
@@ -43,7 +43,7 @@ macro promote_int(x)
|
||||
@param #value_2
|
||||
@returns `The selected value.`
|
||||
*>
|
||||
macro @select(bool $bool, #value_1, #value_2) @builtin
|
||||
macro @select(bool $bool, #value_1, #value_2) @builtin @deprecated("Use '$bool ? #value_1 : #value_2' instead.")
|
||||
{
|
||||
$if $bool:
|
||||
return #value_1;
|
||||
@@ -51,6 +51,7 @@ macro @select(bool $bool, #value_1, #value_2) @builtin
|
||||
return #value_2;
|
||||
$endif
|
||||
}
|
||||
|
||||
macro promote_int_same(x, y)
|
||||
{
|
||||
$if @is_int(x):
|
||||
|
||||
650
lib/std/crypto/aes.c3
Normal file
650
lib/std/crypto/aes.c3
Normal file
@@ -0,0 +1,650 @@
|
||||
<*
|
||||
This is an implementation of the AES algorithm with the ECB, CTR and CBC
|
||||
modes. The key size can be chosen among AES128, AES192, AES256.
|
||||
|
||||
Ported from github.com/kokke/tiny-aes-c by Koni Marti.
|
||||
|
||||
The implementation is verified against the test vectors from the National
|
||||
Institute of Standards and Technology Special Publication 800-38A 2001 ED.
|
||||
|
||||
Data length must be evenly divisible by 16 bytes (len % 16 == 0) unless CTR is
|
||||
used. You should pad the end of the string with zeros or use PKCS7 if this is not the case.
|
||||
For AES192/256 the key size is proportionally larger.
|
||||
|
||||
The following example demonstrates the AES encryption of a plaintext string
|
||||
with an AES 128-bit key:
|
||||
|
||||
```
|
||||
module app;
|
||||
import std::crypto::aes, std::io;
|
||||
fn void main()
|
||||
{
|
||||
char[] key = x"2b7e151628aed2a6abf7158809cf4f3c";
|
||||
char[] text = x"6bc1bee22e409f96e93d7e117393172a";
|
||||
char[16] iv = x"f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff";
|
||||
Aes aes;
|
||||
aes.init(AES128, key, iv);
|
||||
defer aes.destroy();
|
||||
char[] cipher = aes.encrypt(mem, text);
|
||||
defer free(cipher);
|
||||
assert(cipher == x"874d6191b620e3261bef6864990db6ce");
|
||||
}
|
||||
```
|
||||
|
||||
*>
|
||||
module std::crypto::aes;
|
||||
|
||||
<* Block length in bytes. AES is 128-bit blocks only. *>
|
||||
const BLOCKLEN = 16;
|
||||
|
||||
<* Number of columns of a AES state. *>
|
||||
const COLNUM = 4;
|
||||
|
||||
<*
|
||||
Block modes:
|
||||
ECB - Electronic Code Book (Not recommended, indata be 16 byte multiple)
|
||||
CBC - Cipher Block Chaining (Indata be 16 byte multiple)
|
||||
CTR - Counter Mode (Recommended, data may be any size)
|
||||
*>
|
||||
enum BlockMode
|
||||
{
|
||||
ECB,
|
||||
CBC,
|
||||
CTR,
|
||||
}
|
||||
|
||||
<* AES type: 128, 192 or 256 bits *>
|
||||
enum AesType : (AesKey key)
|
||||
{
|
||||
AES128 = { 128, 16, 176, 4, 10 },
|
||||
AES192 = { 192, 24, 208, 6, 12 },
|
||||
AES256 = { 256, 32, 240, 8, 14 }
|
||||
}
|
||||
|
||||
struct AesKey
|
||||
{
|
||||
<* Size of key in bits *>
|
||||
usz key_size;
|
||||
<* Size of key in bytes *>
|
||||
int key_len;
|
||||
<* Size of the expanded round_key *>
|
||||
int key_exp_size; // expected size of round_key
|
||||
<* Number of 32 bit words in key *>
|
||||
usz nk;
|
||||
<* Number of rounds in the cipher *>
|
||||
usz nr;
|
||||
}
|
||||
|
||||
struct Aes
|
||||
{
|
||||
<* The type, AES128, AES192 or AES256 *>
|
||||
AesKey type;
|
||||
<* Block mode: ECB, CBC or CTR *>
|
||||
BlockMode mode;
|
||||
<* Initialization Vector *>
|
||||
char[BLOCKLEN] iv;
|
||||
<* Internal key state *>
|
||||
char[256] round_key;
|
||||
<* Internal state *>
|
||||
AesState state;
|
||||
}
|
||||
alias AesState = char[COLNUM][COLNUM];
|
||||
|
||||
<*
|
||||
Initializes the AES crypto. The initialization vector should be securely random for each encryption
|
||||
to mitigate things like replay attacks.
|
||||
|
||||
@param type : "The type or AES: 128, 192 or 256 bits"
|
||||
@param [in] key : "The key to use, should be the same bit size as the type, so 16, 24 or 32 bytes"
|
||||
@param iv : "The initialization vector"
|
||||
@param mode : "The block mode: EBC, CBC, CTR. Defaults to CTR"
|
||||
|
||||
@require key.len == type.key.key_len : "Key does not match expected length."
|
||||
*>
|
||||
fn Aes* Aes.init(&self, AesType type, char[] key, char[BLOCKLEN] iv, BlockMode mode = CTR)
|
||||
{
|
||||
*self = { .type = type.key, .mode = mode, .iv = iv };
|
||||
key_expansion(type, key, &self.round_key);
|
||||
return self;
|
||||
}
|
||||
|
||||
<*
|
||||
Completely erases data stored in the context.
|
||||
*>
|
||||
fn void Aes.destroy(&self)
|
||||
{
|
||||
*self = {};
|
||||
}
|
||||
|
||||
<*
|
||||
Check if the length is valid using the given block mode. It has to be a multiple of 16 bytes unless CTR is used.
|
||||
*>
|
||||
macro bool is_valid_encryption_len(BlockMode mode, usz len)
|
||||
{
|
||||
switch (mode)
|
||||
{
|
||||
case CTR:
|
||||
return true;
|
||||
case ECB:
|
||||
case CBC:
|
||||
return len % BLOCKLEN == 0;
|
||||
}
|
||||
}
|
||||
|
||||
<*
|
||||
@param [in] in : "Plaintext input."
|
||||
@param [out] out : "Cipher output."
|
||||
@require is_valid_encryption_len(self.mode, in.len) : "The input must be a multiple of 16 unless CTR is used"
|
||||
@require out.len >= in.len : "Out buffer must be sufficiently large to hold the data"
|
||||
*>
|
||||
fn void Aes.encrypt_buffer(&self, char[] in, char[] out)
|
||||
{
|
||||
switch (self.mode)
|
||||
{
|
||||
case CTR: ctr_xcrypt_buffer(self, in, out);
|
||||
case ECB: ecb_encrypt_buffer(self, in, out);
|
||||
case CBC: cbc_encrypt_buffer(self, in, out);
|
||||
}
|
||||
}
|
||||
|
||||
<*
|
||||
@param [in] in : "Cipher input."
|
||||
@param [out] out : "Plaintext output."
|
||||
@require is_valid_encryption_len(self.mode, in.len) : "The encrypted data must be a multiple of 16 unless CTR is used"
|
||||
@require out.len >= in.len : "Out buffer must be sufficiently large to hold the data"
|
||||
*>
|
||||
fn void Aes.decrypt_buffer(&self, char[] in, char[] out)
|
||||
{
|
||||
switch (self.mode)
|
||||
{
|
||||
case ECB: ecb_decrypt_buffer(self, in, out);
|
||||
case CBC: cbc_decrypt_buffer(self, in, out);
|
||||
case CTR: ctr_xcrypt_buffer(self, in, out);
|
||||
}
|
||||
}
|
||||
|
||||
<*
|
||||
Encrypt the data, allocating memory for the encrypted data.
|
||||
|
||||
@param [in] in : "Plaintext input."
|
||||
@param [&inout] allocator : "The allocator to use for the output"
|
||||
@require is_valid_encryption_len(self.mode, in.len) : "The in-data needs to be a multiple of 16 unless CTR is used"
|
||||
*>
|
||||
fn char[] Aes.encrypt(&self, Allocator allocator, char[] in)
|
||||
{
|
||||
char[] out = allocator::alloc_array(allocator, char, in.len);
|
||||
self.encrypt_buffer(in, out) @inline;
|
||||
return out;
|
||||
}
|
||||
|
||||
<*
|
||||
Encrypt the data, allocating temp memory for the encrypted data.
|
||||
|
||||
@param [in] in : "Plaintext input."
|
||||
@require is_valid_encryption_len(self.mode, in.len) : "The in-data needs to be a multiple of 16 unless CTR is used"
|
||||
*>
|
||||
fn char[] Aes.tencrypt(&self, char[] in)
|
||||
{
|
||||
return self.encrypt(tmem, in);
|
||||
}
|
||||
|
||||
<*
|
||||
Decrypt the data, allocating memory for the decrypted data.
|
||||
|
||||
@param [in] in : "Encrypted input."
|
||||
@param [&inout] allocator : "The allocator to use for the output"
|
||||
@require is_valid_encryption_len(self.mode, in.len) : "The in-data needs to be a multiple of 16 unless CTR is used"
|
||||
*>
|
||||
fn char[] Aes.decrypt(&self, Allocator allocator, char[] in)
|
||||
{
|
||||
char[] out = allocator::alloc_array(allocator, char, in.len);
|
||||
self.decrypt_buffer(in, out) @inline;
|
||||
return out;
|
||||
}
|
||||
|
||||
<*
|
||||
Decrypt the data, allocating temp memory for the decrypted data.
|
||||
|
||||
@param [in] in : "Encrypted input."
|
||||
@require is_valid_encryption_len(self.mode, in.len) : "The in-data needs to be a multiple of 16 unless CTR is used"
|
||||
|
||||
*>
|
||||
fn char[] Aes.tdecrypt(&self, char[] in)
|
||||
{
|
||||
return self.decrypt(tmem, in);
|
||||
}
|
||||
|
||||
module std::crypto::aes @private;
|
||||
|
||||
<*
|
||||
@param [&inout] aes : "AES context."
|
||||
@param [in] in : "Plaintext input."
|
||||
@param [out] out : "Cipher output."
|
||||
*>
|
||||
fn void ecb_encrypt_block(Aes *aes, char[BLOCKLEN]* in, char[BLOCKLEN]* out)
|
||||
{
|
||||
for (usz i = 0; i < 4; i++)
|
||||
{
|
||||
for (usz j = 0; j < 4; j++)
|
||||
{
|
||||
aes.state[i][j] = (*in)[i * 4 + j];
|
||||
}
|
||||
}
|
||||
aes_cipher(aes, &aes.round_key);
|
||||
for (usz i = 0; i < 4; i++)
|
||||
{
|
||||
for (usz j = 0; j < 4; j++)
|
||||
{
|
||||
(*out)[i * 4 + j] = aes.state[i][j];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&inout] aes : "AES context."
|
||||
@param [in] in : "Cipher input."
|
||||
@param [out] out : "Plaintext output."
|
||||
*>
|
||||
fn void ecb_decrypt_block(Aes *aes, char[BLOCKLEN]* in, char[BLOCKLEN]* out)
|
||||
{
|
||||
for (usz i = 0; i < 4; i++)
|
||||
{
|
||||
for (usz j = 0; j < 4; j++)
|
||||
{
|
||||
aes.state[i][j] = (*in)[i * 4 + j];
|
||||
}
|
||||
}
|
||||
inv_cipher(aes, &aes.round_key);
|
||||
for (usz i = 0; i < 4; i++)
|
||||
{
|
||||
for (usz j = 0; j < 4; j++)
|
||||
{
|
||||
(*out)[i * 4 + j] = aes.state[i][j];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&inout] aes : "AES context."
|
||||
@param [in] in : "Cipher input."
|
||||
@param [out] out : "Plaintext output."
|
||||
@require out.len >= in.len : "out must be at least as large as buf"
|
||||
*>
|
||||
fn void ecb_decrypt_buffer(Aes *aes, char[] in, char[] out)
|
||||
{
|
||||
usz len = in.len;
|
||||
for (usz i = 0; i < len; i += 4)
|
||||
{
|
||||
ecb_decrypt_block(aes, in[:BLOCKLEN], out[:BLOCKLEN]) @inline;
|
||||
}
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&inout] aes : "AES context."
|
||||
@param [in] in : "Plaintext input."
|
||||
@param [out] out : "Cipher output."
|
||||
*>
|
||||
fn void ecb_encrypt_buffer(Aes *aes, char[] in, char[] out)
|
||||
{
|
||||
usz len = in.len;
|
||||
for (usz i = 0; i < len; i += BLOCKLEN)
|
||||
{
|
||||
ecb_encrypt_block(aes, in[i:BLOCKLEN], out[i:BLOCKLEN]) @inline;
|
||||
}
|
||||
}
|
||||
|
||||
fn void xor_with_iv(char[] buf, char[BLOCKLEN]* iv) @local
|
||||
{
|
||||
foreach (i, b : *iv)
|
||||
{
|
||||
buf[i] ^= b;
|
||||
}
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&inout] aes : "AES context."
|
||||
@param [in] in : "Plaintext input."
|
||||
@param [out] out : "Cipher output."
|
||||
*>
|
||||
fn void cbc_encrypt_buffer(Aes *aes, char[] in, char[] out)
|
||||
{
|
||||
char[] iv = aes.iv[..];
|
||||
usz len = in.len;
|
||||
char[BLOCKLEN] tmp;
|
||||
char[BLOCKLEN] tmp2;
|
||||
for (usz i = 0; i < len; i += BLOCKLEN)
|
||||
{
|
||||
tmp[:BLOCKLEN] = in[i:BLOCKLEN];
|
||||
xor_with_iv(&tmp, iv);
|
||||
ecb_encrypt_block(aes, &tmp, &tmp2);
|
||||
out[i:BLOCKLEN] = tmp2[..];
|
||||
iv = tmp2[..];
|
||||
}
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&inout] aes : "AES context."
|
||||
@param [in] in : "Cipher input."
|
||||
@param [out] out : "Plaintext output."
|
||||
*>
|
||||
fn void cbc_decrypt_buffer(Aes *aes, char[] in, char[] out)
|
||||
{
|
||||
char[BLOCKLEN] tmp;
|
||||
usz len = in.len;
|
||||
for (usz i = 0; i < len; i += BLOCKLEN)
|
||||
{
|
||||
ecb_decrypt_block(aes, in[i:BLOCKLEN], &tmp);
|
||||
xor_with_iv(&tmp, aes.iv[..]);
|
||||
aes.iv[:BLOCKLEN] = in[i:BLOCKLEN];
|
||||
out[i:BLOCKLEN] = tmp[..];
|
||||
}
|
||||
}
|
||||
|
||||
<*
|
||||
@param [&inout] aes : "AES context."
|
||||
@param [in] in : "Plaintext/cipher input."
|
||||
@param [out] out : "Cipher/plaintext output."
|
||||
*>
|
||||
fn void ctr_xcrypt_buffer(Aes *aes, char[] in, char[] out)
|
||||
{
|
||||
char[BLOCKLEN] buffer @noinit;
|
||||
usz len = in.len;
|
||||
for (int bi = BLOCKLEN, usz i = 0; i < len; i++)
|
||||
{
|
||||
if (bi == BLOCKLEN)
|
||||
{
|
||||
buffer = aes.iv;
|
||||
ecb_encrypt_block(aes, &buffer, &buffer);
|
||||
|
||||
for LOOP: (bi = (BLOCKLEN - 1); bi >= 0; bi--)
|
||||
{
|
||||
if (aes.iv[bi] == 255)
|
||||
{
|
||||
aes.iv[bi] = 0;
|
||||
continue;
|
||||
}
|
||||
aes.iv[bi]++;
|
||||
break LOOP;
|
||||
}
|
||||
bi = 0;
|
||||
}
|
||||
out[i] = in[i] ^ buffer[bi];
|
||||
bi++;
|
||||
}
|
||||
}
|
||||
|
||||
macro char get_sbox_value(num) => SBOX[num];
|
||||
macro char get_sbox_invert(num) => RSBOX[num];
|
||||
|
||||
const char[256] SBOX =
|
||||
x`637c777bf26b6fc53001672bfed7ab76
|
||||
ca82c97dfa5947f0add4a2af9ca472c0
|
||||
b7fd9326363ff7cc34a5e5f171d83115
|
||||
04c723c31896059a071280e2eb27b275
|
||||
09832c1a1b6e5aa0523bd6b329e32f84
|
||||
53d100ed20fcb15b6acbbe394a4c58cf
|
||||
d0efaafb434d338545f9027f503c9fa8
|
||||
51a3408f929d38f5bcb6da2110fff3d2
|
||||
cd0c13ec5f974417c4a77e3d645d1973
|
||||
60814fdc222a908846eeb814de5e0bdb
|
||||
e0323a0a4906245cc2d3ac629195e479
|
||||
e7c8376d8dd54ea96c56f4ea657aae08
|
||||
ba78252e1ca6b4c6e8dd741f4bbd8b8a
|
||||
703eb5664803f60e613557b986c11d9e
|
||||
e1f8981169d98e949b1e87e9ce5528df
|
||||
8ca1890dbfe6426841992d0fb054bb16`;
|
||||
|
||||
const char[256] RSBOX =
|
||||
x`52096ad53036a538bf40a39e81f3d7fb
|
||||
7ce339829b2fff87348e4344c4dee9cb
|
||||
547b9432a6c2233dee4c950b42fac34e
|
||||
082ea16628d924b2765ba2496d8bd125
|
||||
72f8f66486689816d4a45ccc5d65b692
|
||||
6c704850fdedb9da5e154657a78d9d84
|
||||
90d8ab008cbcd30af7e45805b8b34506
|
||||
d02c1e8fca3f0f02c1afbd0301138a6b
|
||||
3a9111414f67dcea97f2cfcef0b4e673
|
||||
96ac7422e7ad3585e2f937e81c75df6e
|
||||
47f11a711d29c5896fb7620eaa18be1b
|
||||
fc563e4bc6d279209adbc0fe78cd5af4
|
||||
1fdda8338807c731b11210592780ec5f
|
||||
60517fa919b54a0d2de57a9f93c99cef
|
||||
a0e03b4dae2af5b0c8ebbb3c83539961
|
||||
172b047eba77d626e169146355210c7d`;
|
||||
|
||||
const char[11] RCON = x`8d01020408102040801b36`;
|
||||
|
||||
fn void add_round_key(Aes* aes, usz round, char[] round_key)
|
||||
{
|
||||
usz i, j;
|
||||
for (i = 0; i < 4; i++)
|
||||
{
|
||||
for (j = 0; j < 4; j++)
|
||||
{
|
||||
aes.state[i][j] ^= round_key[(round * COLNUM * 4) + (i * COLNUM) + j];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn void sub_bytes(Aes* aes)
|
||||
{
|
||||
for (usz i = 0; i < 4; i++)
|
||||
{
|
||||
for (usz j = 0; j < 4; j++)
|
||||
{
|
||||
aes.state[j][i] = get_sbox_value(aes.state[j][i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn void shift_rows(Aes* aes)
|
||||
{
|
||||
char temp;
|
||||
|
||||
temp = aes.state[0][1];
|
||||
aes.state[0][1] = aes.state[1][1];
|
||||
aes.state[1][1] = aes.state[2][1];
|
||||
aes.state[2][1] = aes.state[3][1];
|
||||
aes.state[3][1] = temp;
|
||||
|
||||
temp = aes.state[0][2];
|
||||
aes.state[0][2] = aes.state[2][2];
|
||||
aes.state[2][2] = temp;
|
||||
|
||||
temp = aes.state[1][2];
|
||||
aes.state[1][2] = aes.state[3][2];
|
||||
aes.state[3][2] = temp;
|
||||
|
||||
temp = aes.state[0][3];
|
||||
aes.state[0][3] = aes.state[3][3];
|
||||
aes.state[3][3] = aes.state[2][3];
|
||||
aes.state[2][3] = aes.state[1][3];
|
||||
aes.state[1][3] = temp;
|
||||
}
|
||||
|
||||
fn char xtime(char x) @local
|
||||
{
|
||||
return ((x << 1) ^ (((x >> 7) & 1) * 0x1b));
|
||||
}
|
||||
|
||||
fn void mix_columns(Aes* aes)
|
||||
{
|
||||
for (usz i = 0; i < 4; i++)
|
||||
{
|
||||
char t = aes.state[i][0];
|
||||
char tmp = aes.state[i][0] ^ aes.state[i][1] ^ aes.state[i][2] ^ aes.state[i][3];
|
||||
|
||||
char tm = aes.state[i][0] ^ aes.state[i][1];
|
||||
tm = xtime(tm);
|
||||
aes.state[i][0] ^= tm ^ tmp;
|
||||
|
||||
tm = aes.state[i][1] ^ aes.state[i][2];
|
||||
tm = xtime(tm);
|
||||
aes.state[i][1] ^= tm ^ tmp;
|
||||
|
||||
tm = aes.state[i][2] ^ aes.state[i][3];
|
||||
tm = xtime(tm);
|
||||
aes.state[i][2] ^= tm ^ tmp;
|
||||
|
||||
tm = aes.state[i][3] ^ t;
|
||||
tm = xtime(tm);
|
||||
aes.state[i][3] ^= tm ^ tmp;
|
||||
}
|
||||
}
|
||||
|
||||
fn char multiply(char x, char y) @local
|
||||
{
|
||||
return (((y & 1) * x) ^
|
||||
(((y>>1) & 1) * xtime(x)) ^
|
||||
(((y>>2) & 1) * xtime(xtime(x))) ^
|
||||
(((y>>3) & 1) * xtime(xtime(xtime(x)))) ^
|
||||
(((y>>4) & 1) * xtime(xtime(xtime(xtime(x))))));
|
||||
}
|
||||
|
||||
fn void inv_mix_columns(Aes* aes)
|
||||
{
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
char a = aes.state[i][0];
|
||||
char b = aes.state[i][1];
|
||||
char c = aes.state[i][2];
|
||||
char d = aes.state[i][3];
|
||||
|
||||
aes.state[i][0] = multiply(a, 0x0e) ^ multiply(b, 0x0b) ^ multiply(c, 0x0d) ^ multiply(d, 0x09);
|
||||
aes.state[i][1] = multiply(a, 0x09) ^ multiply(b, 0x0e) ^ multiply(c, 0x0b) ^ multiply(d, 0x0d);
|
||||
aes.state[i][2] = multiply(a, 0x0d) ^ multiply(b, 0x09) ^ multiply(c, 0x0e) ^ multiply(d, 0x0b);
|
||||
aes.state[i][3] = multiply(a, 0x0b) ^ multiply(b, 0x0d) ^ multiply(c, 0x09) ^ multiply(d, 0x0e);
|
||||
}
|
||||
}
|
||||
|
||||
fn void inv_sub_bytes(Aes* aes)
|
||||
{
|
||||
for (usz i = 0; i < 4; i++)
|
||||
{
|
||||
for (usz j = 0; j < 4; j++)
|
||||
{
|
||||
aes.state[j][i] = get_sbox_invert(aes.state[j][i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn void inv_shift_rows(Aes* aes)
|
||||
{
|
||||
char temp;
|
||||
|
||||
temp = aes.state[3][1];
|
||||
aes.state[3][1] = aes.state[2][1];
|
||||
aes.state[2][1] = aes.state[1][1];
|
||||
aes.state[1][1] = aes.state[0][1];
|
||||
aes.state[0][1] = temp;
|
||||
|
||||
temp = aes.state[0][2];
|
||||
aes.state[0][2] = aes.state[2][2];
|
||||
aes.state[2][2] = temp;
|
||||
|
||||
temp = aes.state[1][2];
|
||||
aes.state[1][2] = aes.state[3][2];
|
||||
aes.state[3][2] = temp;
|
||||
|
||||
temp = aes.state[0][3];
|
||||
aes.state[0][3] = aes.state[1][3];
|
||||
aes.state[1][3] = aes.state[2][3];
|
||||
aes.state[2][3] = aes.state[3][3];
|
||||
aes.state[3][3] = temp;
|
||||
}
|
||||
|
||||
fn void aes_cipher(Aes* aes, char[] round_key)
|
||||
{
|
||||
usz round = 0;
|
||||
add_round_key(aes, 0, round_key);
|
||||
|
||||
for LOOP: (round = 1;; round++)
|
||||
{
|
||||
sub_bytes(aes);
|
||||
shift_rows(aes);
|
||||
if (round == aes.type.nr) break LOOP;
|
||||
mix_columns(aes);
|
||||
add_round_key(aes, round, round_key);
|
||||
}
|
||||
add_round_key(aes, aes.type.nr, round_key);
|
||||
}
|
||||
|
||||
fn void inv_cipher(Aes* aes, char[] round_key)
|
||||
{
|
||||
add_round_key(aes, aes.type.nr, round_key);
|
||||
for (usz round = aes.type.nr - 1; ; round--)
|
||||
{
|
||||
inv_shift_rows(aes);
|
||||
inv_sub_bytes(aes);
|
||||
add_round_key(aes, round, round_key);
|
||||
if (!round) return;
|
||||
inv_mix_columns(aes);
|
||||
}
|
||||
}
|
||||
|
||||
<*¨
|
||||
@param type : "The AES variant to expant the key for"
|
||||
@param [inout] round_key : "Key to expand into"
|
||||
@param [in] key : "The key to expand"
|
||||
@require key.len == type.key.key_len : "Key does not match expected length."
|
||||
*>
|
||||
fn void key_expansion(AesType type, char[] key, char[] round_key) @private
|
||||
{
|
||||
usz nk = type.key.nk;
|
||||
for (usz i = 0; i < nk; i++)
|
||||
{
|
||||
round_key[(i * 4) + 0] = key[(i * 4) + 0];
|
||||
round_key[(i * 4) + 1] = key[(i * 4) + 1];
|
||||
round_key[(i * 4) + 2] = key[(i * 4) + 2];
|
||||
round_key[(i * 4) + 3] = key[(i * 4) + 3];
|
||||
}
|
||||
|
||||
for (usz i = nk; i < COLNUM * (type.key.nr + 1); i++)
|
||||
{
|
||||
usz k = (i - 1) * 4;
|
||||
|
||||
char[4] tempa @noinit;
|
||||
|
||||
tempa[0] = round_key[k + 0];
|
||||
tempa[1] = round_key[k + 1];
|
||||
tempa[2] = round_key[k + 2];
|
||||
tempa[3] = round_key[k + 3];
|
||||
|
||||
if (i % nk == 0)
|
||||
{
|
||||
// rotword
|
||||
char tmp = tempa[0];
|
||||
tempa[0] = tempa[1];
|
||||
tempa[1] = tempa[2];
|
||||
tempa[2] = tempa[3];
|
||||
tempa[3] = tmp;
|
||||
|
||||
// subword
|
||||
tempa[0] = get_sbox_value(tempa[0]);
|
||||
tempa[1] = get_sbox_value(tempa[1]);
|
||||
tempa[2] = get_sbox_value(tempa[2]);
|
||||
tempa[3] = get_sbox_value(tempa[3]);
|
||||
|
||||
tempa[0] = tempa[0] ^ RCON[i / nk];
|
||||
}
|
||||
|
||||
if (type.key.key_size == 256)
|
||||
{
|
||||
if (i % nk == 4)
|
||||
{
|
||||
// subword
|
||||
tempa[0] = get_sbox_value(tempa[0]);
|
||||
tempa[1] = get_sbox_value(tempa[1]);
|
||||
tempa[2] = get_sbox_value(tempa[2]);
|
||||
tempa[3] = get_sbox_value(tempa[3]);
|
||||
}
|
||||
}
|
||||
usz j = i * 4;
|
||||
k = (i - nk) * 4;
|
||||
round_key[j + 0] = round_key[k + 0] ^ tempa[0];
|
||||
round_key[j + 1] = round_key[k + 1] ^ tempa[1];
|
||||
round_key[j + 2] = round_key[k + 2] ^ tempa[2];
|
||||
round_key[j + 3] = round_key[k + 3] ^ tempa[3];
|
||||
}
|
||||
}
|
||||
|
||||
87
lib/std/crypto/aes_128_192_256.c3
Normal file
87
lib/std/crypto/aes_128_192_256.c3
Normal file
@@ -0,0 +1,87 @@
|
||||
// Experimental implementation
|
||||
module std::crypto::aes128;
|
||||
import std::crypto::aes;
|
||||
|
||||
fn char[] encrypt(Allocator allocator, char[16]* key, char[aes::BLOCKLEN] iv, char[] data)
|
||||
{
|
||||
Aes aes @noinit;
|
||||
aes.init(AES128, key, iv, CTR);
|
||||
defer aes.destroy();
|
||||
return aes.encrypt(allocator, data);
|
||||
}
|
||||
|
||||
fn char[] tencrypt(char[16]* key, char[aes::BLOCKLEN] iv, char[] data)
|
||||
{
|
||||
return encrypt(tmem, key, iv, data);
|
||||
}
|
||||
|
||||
fn char[] decrypt(Allocator allocator, char[16]* key, char[aes::BLOCKLEN] iv, char[] data)
|
||||
{
|
||||
Aes aes @noinit;
|
||||
aes.init(AES128, key, iv, CTR);
|
||||
defer aes.destroy();
|
||||
return aes.decrypt(allocator, data);
|
||||
}
|
||||
|
||||
fn char[] tdecrypt(char[16]* key, char[aes::BLOCKLEN] iv, char[] data)
|
||||
{
|
||||
return decrypt(tmem, key, iv, data);
|
||||
}
|
||||
|
||||
module std::crypto::aes192;
|
||||
import std::crypto::aes;
|
||||
|
||||
fn char[] encrypt(Allocator allocator, char[24]* key, char[aes::BLOCKLEN] iv, char[] data)
|
||||
{
|
||||
Aes aes @noinit;
|
||||
aes.init(AES192, key, iv, CTR);
|
||||
defer aes.destroy();
|
||||
return aes.encrypt(allocator, data);
|
||||
}
|
||||
|
||||
fn char[] tencrypt(char[24]* key, char[aes::BLOCKLEN] iv, char[] data)
|
||||
{
|
||||
return encrypt(tmem, key, iv, data);
|
||||
}
|
||||
|
||||
fn char[] decrypt(Allocator allocator, char[24]* key, char[aes::BLOCKLEN] iv, char[] data)
|
||||
{
|
||||
Aes aes @noinit;
|
||||
aes.init(AES192, key, iv, CTR);
|
||||
defer aes.destroy();
|
||||
return aes.decrypt(allocator, data);
|
||||
}
|
||||
|
||||
fn char[] tdecrypt(char[24]* key, char[aes::BLOCKLEN] iv, char[] data)
|
||||
{
|
||||
return decrypt(tmem, key, iv, data);
|
||||
}
|
||||
|
||||
module std::crypto::aes256;
|
||||
import std::crypto::aes;
|
||||
|
||||
fn char[] encrypt(Allocator allocator, char[32]* key, char[aes::BLOCKLEN] iv, char[] data)
|
||||
{
|
||||
Aes aes @noinit;
|
||||
aes.init(AES256, key, iv, CTR);
|
||||
defer aes.destroy();
|
||||
return aes.encrypt(allocator, data);
|
||||
}
|
||||
|
||||
fn char[] tencrypt(char[32]* key, char[aes::BLOCKLEN] iv, char[] data)
|
||||
{
|
||||
return encrypt(tmem, key, iv, data);
|
||||
}
|
||||
|
||||
fn char[] decrypt(Allocator allocator, char[32]* key, char[aes::BLOCKLEN] iv, char[] data)
|
||||
{
|
||||
Aes aes @noinit;
|
||||
aes.init(AES256, key, iv, CTR);
|
||||
defer aes.destroy();
|
||||
return aes.decrypt(allocator, data);
|
||||
}
|
||||
|
||||
fn char[] tdecrypt(char[32]* key, char[aes::BLOCKLEN] iv, char[] data)
|
||||
{
|
||||
return decrypt(tmem, key, iv, data);
|
||||
}
|
||||
@@ -9,4 +9,3 @@ fn bool safe_compare(void* data1, void* data2, usz len)
|
||||
}
|
||||
return match == 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -212,7 +212,8 @@ fn F25519Int pack(Point* p)
|
||||
struct Unpacking
|
||||
{
|
||||
Point point;
|
||||
char on_curve; // Non-zero if true.
|
||||
<* Non-zero if true. *>
|
||||
char on_curve;
|
||||
}
|
||||
|
||||
<*
|
||||
@@ -365,7 +366,7 @@ fn void F25519Int.normalize(&s)
|
||||
{
|
||||
s.reduce_carry((*s)[^1] >> 7);
|
||||
|
||||
// Substract p
|
||||
// Subtract p
|
||||
F25519Int sub @noinit;
|
||||
ushort c = 19;
|
||||
foreach (i, v : (*s)[:^1])
|
||||
@@ -399,7 +400,7 @@ fn char eq(F25519Int* a, F25519Int* b)
|
||||
}
|
||||
|
||||
<*
|
||||
Constant-time conditonal selection. Result is undefined if condition is neither 0 nor 1.
|
||||
Constant-time conditional selection. Result is undefined if condition is neither 0 nor 1.
|
||||
|
||||
@param [&in] zero : "selected if condition is 0"
|
||||
@param [&in] one : "selected if condition is 1"
|
||||
@@ -441,7 +442,7 @@ fn F25519Int F25519Int.add(&s, F25519Int* n) @operator(+)
|
||||
macro F25519Int F25519Int.@sub(&s, F25519Int #n) @operator(-) => s.sub(@addr(#n));
|
||||
|
||||
<*
|
||||
Substraction.
|
||||
Subtraction.
|
||||
|
||||
@param [&in] s
|
||||
@param [&in] n
|
||||
@@ -638,7 +639,7 @@ fn FBaseInt from_bytes(char[] bytes)
|
||||
}
|
||||
|
||||
<*
|
||||
Constant-time conditonal selection. Result is undefined if condition is neither 0 nor 1.
|
||||
Constant-time conditional selection. Result is undefined if condition is neither 0 nor 1.
|
||||
|
||||
@param [&in] zero : "selected if condition is 0"
|
||||
@param [&in] one : "selected if condition is 1"
|
||||
@@ -676,7 +677,7 @@ fn FBaseInt FBaseInt.add(&s, FBaseInt* n) @operator(+)
|
||||
}
|
||||
|
||||
<*
|
||||
Substraction if RHS is less than LHS else identity.
|
||||
Subtraction if RHS is less than LHS else identity.
|
||||
|
||||
@param [&in] s
|
||||
@param [&in] n
|
||||
|
||||
@@ -26,15 +26,7 @@ fn Object*? parse(Allocator allocator, InStream s)
|
||||
JsonContext context = { .last_string = dstring::new_with_capacity(smem, 64), .stream = s, .allocator = allocator };
|
||||
@pool()
|
||||
{
|
||||
Object* o = parse_any(&context)!;
|
||||
defer catch o.free();
|
||||
while (char c = read_next(&context)!, c != 0)
|
||||
{
|
||||
if (c.is_space()) continue;
|
||||
return UNEXPECTED_CHARACTER?;
|
||||
}
|
||||
if (!@catch(context.stream.read_byte())) return UNEXPECTED_CHARACTER?;
|
||||
return o;
|
||||
return parse_any(&context)!;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
module std::hash::a5hash;
|
||||
|
||||
|
||||
macro @a5mul(#u, #v, #lo, #hi) @local
|
||||
macro void @a5mul(#u, #v, #lo, #hi) @local
|
||||
{
|
||||
uint128 imd = (uint128)#u * (uint128)#v;
|
||||
#lo = (ulong)imd;
|
||||
|
||||
@@ -83,7 +83,7 @@ fn char[HASH_BYTES] Hmac.final(&self)
|
||||
const IPAD @private = 0x36;
|
||||
const OPAD @private = 0x5C;
|
||||
|
||||
macro @derive(Hmac *hmac_start, char[] salt, uint iterations, usz index, char[] out)
|
||||
macro void @derive(Hmac *hmac_start, char[] salt, uint iterations, usz index, char[] out)
|
||||
{
|
||||
assert(out.len == HASH_BYTES);
|
||||
char[HASH_BYTES] tmp @noinit;
|
||||
@@ -104,4 +104,4 @@ macro @derive(Hmac *hmac_start, char[] salt, uint iterations, usz index, char[]
|
||||
out[i] ^= v;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
module std::hash::komi;
|
||||
|
||||
|
||||
macro @komimul(#u, #v, #lo, #hi) @local
|
||||
macro void @komimul(#u, #v, #lo, #hi) @local
|
||||
{
|
||||
uint128 imd = (uint128)#u * (uint128)#v;
|
||||
#lo = (ulong)imd;
|
||||
|
||||
@@ -106,7 +106,7 @@ macro @h(x, y, z) => (x ^ y) ^ z;
|
||||
macro @h2(x, y, z) => x ^ (y ^ z);
|
||||
macro @i(x, y, z) => y ^ (x | ~z);
|
||||
|
||||
macro @step(#f, a, b, c, d, ptr, n, t, s)
|
||||
macro void @step(#f, a, b, c, d, ptr, n, t, s)
|
||||
{
|
||||
*a += #f(b, c, d) + @unaligned_load(*(uint *)&ptr[n * 4], 2) + t;
|
||||
*a = (*a << s) | ((*a & 0xffffffff) >> (32 - s));
|
||||
|
||||
@@ -103,13 +103,13 @@ union Long16 @local
|
||||
uint[16] l;
|
||||
}
|
||||
|
||||
macro blk(Long16* block, i) @local
|
||||
macro uint blk(Long16* block, i) @local
|
||||
{
|
||||
return (block.l[i & 15] = (block.l[(i + 13) & 15] ^ block.l[(i + 8) & 15]
|
||||
^ block.l[(i + 2) & 15] ^ block.l[i & 15]).rotl(1));
|
||||
}
|
||||
|
||||
macro blk0(Long16* block, i) @local
|
||||
macro uint blk0(Long16* block, i) @local
|
||||
{
|
||||
$if env::BIG_ENDIAN:
|
||||
return block.l[i];
|
||||
@@ -119,35 +119,35 @@ macro blk0(Long16* block, i) @local
|
||||
$endif
|
||||
}
|
||||
|
||||
macro r0(Long16* block, uint v, uint* wref, uint x, uint y, uint* z, uint i) @local
|
||||
macro void r0(Long16* block, uint v, uint* wref, uint x, uint y, uint* z, uint i) @local
|
||||
{
|
||||
var w = *wref;
|
||||
*z += ((w & (x ^ y)) ^ y) + blk0(block, i) + 0x5A827999 + v.rotl(5);
|
||||
*wref = w.rotl(30);
|
||||
}
|
||||
|
||||
macro r1(Long16* block, uint v, uint* wref, uint x, uint y, uint* z, uint i) @local
|
||||
macro void r1(Long16* block, uint v, uint* wref, uint x, uint y, uint* z, uint i) @local
|
||||
{
|
||||
var w = *wref;
|
||||
*z += ((w & (x ^ y)) ^ y) + blk(block, i) + 0x5A827999 + v.rotl(5);
|
||||
*wref = w.rotl(30);
|
||||
}
|
||||
|
||||
macro r2(Long16* block, uint v, uint* wref, uint x, uint y, uint* z, uint i) @local
|
||||
macro void r2(Long16* block, uint v, uint* wref, uint x, uint y, uint* z, uint i) @local
|
||||
{
|
||||
var w = *wref;
|
||||
*z += (w ^ x ^ y) + blk(block, i) + 0x6ED9EBA1 + v.rotl(5);
|
||||
*wref = w.rotl(30);
|
||||
}
|
||||
|
||||
macro r3(Long16* block, uint v, uint* wref, uint x, uint y, uint* z, uint i) @local
|
||||
macro void r3(Long16* block, uint v, uint* wref, uint x, uint y, uint* z, uint i) @local
|
||||
{
|
||||
var w = *wref;
|
||||
*z += (((w | x) & y) | (w & x)) + blk(block, i) + 0x8F1BBCDC + v.rotl(5);
|
||||
*wref = w.rotl(30);
|
||||
}
|
||||
|
||||
macro r4(Long16* block, uint v, uint* wref, uint x, uint y, uint* z, uint i) @local
|
||||
macro void r4(Long16* block, uint v, uint* wref, uint x, uint y, uint* z, uint i) @local
|
||||
{
|
||||
var w = *wref;
|
||||
*z += (w ^ x ^ y) + blk(block, i) + 0xCA62C1D6 + v.rotl(5);
|
||||
@@ -254,4 +254,4 @@ fn void sha1_transform(uint[5]* state, char* buffer) @local
|
||||
(*state)[4] += e;
|
||||
a = b = c = d = e = 0;
|
||||
block = {};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,52 +1,52 @@
|
||||
// Copyright (c) 2025 Zack Puhl <github@xmit.xyz>. 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.
|
||||
//
|
||||
// SipHash is a secure pseudorandom function (PRF) which digests a 128-bit key
|
||||
// and a variable-length message to produce a 64- or 128-bit hash value.
|
||||
//
|
||||
// SipHash can be employed in numerous useful ways and structures, e.g.:
|
||||
// - Hash Tables
|
||||
// - Message Authentication Codes
|
||||
// - Denial of Service (hash flooding) resistance
|
||||
// - Bloom filters
|
||||
// - Keyed runtime identifier derivation
|
||||
//
|
||||
// Read more: https://en.wikipedia.org/wiki/SipHash
|
||||
//
|
||||
//
|
||||
|
||||
// COMMON HASH VARIANTS.
|
||||
// These two forms of SipHash (24 and 48) are the most widely
|
||||
// used by many implementations.
|
||||
|
||||
// These provide typical 64-bit hash results.
|
||||
// -- Best for performance-critical applications.
|
||||
<*
|
||||
Best for performance-critical applications.
|
||||
See std::hash::siphash for more information.
|
||||
*>
|
||||
module std::hash::siphash24;
|
||||
import std::hash::siphash;
|
||||
alias SipHash24 = SipHash { ulong, 2, 4 };
|
||||
alias hash = siphash::hash { ulong, 2, 4 };
|
||||
|
||||
// -- Best for conservative security applications.
|
||||
<*
|
||||
Best for security-focused applications.
|
||||
See std::hash::siphash for more information.
|
||||
*>
|
||||
module std::hash::siphash48;
|
||||
import std::hash::siphash;
|
||||
alias SipHash48 = SipHash { ulong, 4, 8 };
|
||||
alias hash = siphash::hash { ulong, 4, 8 };
|
||||
|
||||
|
||||
// Exact same as above, but for 128-bit outputs. Algorithm internally changes slightly.
|
||||
<* Exact same as siphash24, but for 128-bit outputs. Algorithm internally changes slightly. *>
|
||||
module std::hash::siphash24_128;
|
||||
import std::hash::siphash;
|
||||
alias SipHash24_128 = SipHash { uint128, 2, 4 };
|
||||
alias hash = siphash::hash { uint128, 2, 4 };
|
||||
|
||||
<* Exact same as siphash48, but for 128-bit outputs. Algorithm internally changes slightly. *>
|
||||
module std::hash::siphash48_128;
|
||||
import std::hash::siphash;
|
||||
alias SipHash48_128 = SipHash { uint128, 4, 8 };
|
||||
alias hash = siphash::hash { uint128, 4, 8 };
|
||||
|
||||
<*
|
||||
@require OutType.typeid == uint128.typeid || OutType.typeid == ulong.typeid : "Module OutType must be either uint128 or ulong."
|
||||
SipHash is a secure pseudorandom function (PRF) which digests a 128-bit key
|
||||
and a variable-length message to produce a 64- or 128-bit hash value.
|
||||
|
||||
SipHash can be employed in numerous useful ways and structures, e.g.:
|
||||
- Hash Tables
|
||||
- Message Authentication Codes
|
||||
- Denial of Service (hash flooding) resistance
|
||||
- Bloom filters
|
||||
- Keyed runtime identifier derivation
|
||||
|
||||
Read more: https://en.wikipedia.org/wiki/SipHash
|
||||
|
||||
@require OutType.typeid == uint128.typeid || OutType.typeid == ulong.typeid : "Module OutType must be either uint128 or ulong."
|
||||
*>
|
||||
module std::hash::siphash { OutType, BLOCK_ROUNDS, FINALIZE_ROUNDS };
|
||||
|
||||
|
||||
92
lib/std/io/file_mmap.c3
Normal file
92
lib/std/io/file_mmap.c3
Normal file
@@ -0,0 +1,92 @@
|
||||
module std::io::file::mmap @if(env::LIBC &&& env::POSIX);
|
||||
import std::core::mem::vm, std::io::file;
|
||||
|
||||
struct FileMmap
|
||||
{
|
||||
File file;
|
||||
VirtualMemory vm;
|
||||
usz offset;
|
||||
usz len;
|
||||
}
|
||||
|
||||
<*
|
||||
Provides a slice of bytes to the expected mapped range discarding the extra bytes due to misaligment of offset and/or size.
|
||||
|
||||
@return "Slice of the mapped range where the first byte matches the file's byte at the offset specified to File::file_mmap()"
|
||||
*>
|
||||
fn char[] FileMmap.bytes(&self)
|
||||
{
|
||||
return self.vm.ptr[self.offset:self.len];
|
||||
}
|
||||
|
||||
<*
|
||||
Destroys the underlying VirtualMemory object ie. calls munmap()"
|
||||
*>
|
||||
fn void? FileMmap.destroy(&self) @maydiscard
|
||||
{
|
||||
fault err1 = @catch(self.file.close());
|
||||
fault err2 = @catch(self.vm.destroy());
|
||||
if (err1) return err1?;
|
||||
if (err2) return err2?;
|
||||
}
|
||||
|
||||
module std::io::file @if(env::LIBC &&& env::POSIX);
|
||||
|
||||
<*
|
||||
Maps a region of an already-opened file into memory
|
||||
|
||||
@param file : "Already opened file created on the caller scope"
|
||||
@param offset : "Byte offset in file, will be rounded down to page size"
|
||||
@param len : "Size in bytes to map starting from offset, will be rounded up to page size"
|
||||
@return? mem::OUT_OF_MEMORY, vm::ACCESS_DENIED, vm::RANGE_OVERFLOW, vm::INVALID_ARGS, vm::UNKNOWN_ERROR, io::NO_PERMISSION, io::FILE_NOT_VALID, io::WOULD_BLOCK, io::FILE_NOT_FOUND
|
||||
@return "Memory mapped region. Must be released with FileMmap.destroy(). Provided File will not be closed"
|
||||
*>
|
||||
fn FileMmap? mmap_file(File file, usz offset = 0, usz len = 0, VirtualMemoryAccess access = READ, bool shared = false)
|
||||
{
|
||||
if (len == 0)
|
||||
{
|
||||
usz cur = file.seek(0, CURSOR)!;
|
||||
defer file.seek(cur, SET)!!;
|
||||
usz file_size = file.seek(0, END)!;
|
||||
len = file_size - offset;
|
||||
}
|
||||
|
||||
// get the page size
|
||||
usz page_size = vm::aligned_alloc_size(0);
|
||||
|
||||
// align the offset specified by the user (might be not aligned)
|
||||
usz page_offset = offset & (page_size - 1);
|
||||
usz map_offset = offset - page_offset;
|
||||
|
||||
// adjust map length (both the region start and the region end might be not aligned)
|
||||
usz map_len = len + page_offset; // when region start not aligned
|
||||
map_len = vm::aligned_alloc_size(map_len); // when region end not aligned
|
||||
|
||||
void* ptr = vm::mmap_file(file.fd(), map_len, map_offset, access, shared)!;
|
||||
|
||||
// FileMmap does not own the supplied file
|
||||
return {{}, {ptr, map_len, access}, page_offset, len};
|
||||
}
|
||||
|
||||
<*
|
||||
Maps a region of the given file into memory
|
||||
|
||||
@param filename : "File path"
|
||||
@param mode : "File opening mode"
|
||||
@param offset : "Byte offset in file, will be rounded down to page size"
|
||||
@param len : "Size in bytes to map starting from offset, will be rounded up to page size"
|
||||
@return? mem::OUT_OF_MEMORY, vm::ACCESS_DENIED, vm::RANGE_OVERFLOW, vm::INVALID_ARGS, vm::UNKNOWN_ERROR, io::NO_PERMISSION, io::FILE_NOT_VALID, io::WOULD_BLOCK, io::FILE_NOT_FOUND
|
||||
@return "Memory mapped region. Must be released with FileMmap.destroy()"
|
||||
*>
|
||||
fn FileMmap? mmap_open(String filename, String mode, usz offset = 0, usz len = 0, VirtualMemoryAccess access = READ, bool shared = false)
|
||||
{
|
||||
File file = open(filename, mode)!;
|
||||
defer catch (void)file.close();
|
||||
FileMmap mm = mmap_file(file, offset, len, access, shared)!;
|
||||
|
||||
// FileMmap owns the file and it will close it on destroy()
|
||||
mm.file = file;
|
||||
|
||||
return mm;
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ macro bool is_struct_with_default_print($Type)
|
||||
<*
|
||||
Introspect a struct and print it to a formatter
|
||||
|
||||
@require @typekind(value) == STRUCT || @typekind(value) == BITSTRUCT : `This macro is only valid on macros`
|
||||
@require $kindof(value) == STRUCT || $kindof(value) == BITSTRUCT : `This macro is only valid on macros`
|
||||
*>
|
||||
macro usz? struct_to_format(value, Formatter* f, bool $force_dump)
|
||||
{
|
||||
@@ -146,10 +146,14 @@ fn usz? Formatter.out_str(&self, any arg) @private
|
||||
case VOID:
|
||||
return self.out_substr("void");
|
||||
case FAULT:
|
||||
return self.out_substr((*(fault*)arg.ptr).nameof);
|
||||
fault f = *(fault*)arg.ptr;
|
||||
return self.out_substr(f ? f.nameof : "(empty-fault)");
|
||||
case INTERFACE:
|
||||
any a = *(any*)arg;
|
||||
return a ? self.out_str(a) : self.out_substr("(empty-interface)");
|
||||
case ANY:
|
||||
return self.out_str(*(any*)arg);
|
||||
any a = *(any*)arg;
|
||||
return a ? self.out_str(a) : self.out_substr("(empty-any)");
|
||||
case OPTIONAL:
|
||||
unreachable();
|
||||
case SIGNED_INT:
|
||||
|
||||
@@ -22,7 +22,7 @@ fn usz? print_hex_chars(Formatter* f, char[] out, bool uppercase) @inline
|
||||
return len;
|
||||
}
|
||||
|
||||
macro Formatter.first_err(&self, fault f)
|
||||
macro fault Formatter.first_err(&self, fault f)
|
||||
{
|
||||
if (self.first_fault) return self.first_fault;
|
||||
self.first_fault = f;
|
||||
|
||||
@@ -35,6 +35,7 @@ faultdef
|
||||
NO_PERMISSION,
|
||||
OUT_OF_SPACE,
|
||||
OVERFLOW,
|
||||
PATH_COULD_NOT_BE_FOUND,
|
||||
READ_ONLY,
|
||||
SYMLINK_FAILED,
|
||||
TOO_MANY_DESCRIPTORS,
|
||||
@@ -50,7 +51,7 @@ faultdef
|
||||
"\r" will be filtered from the String.
|
||||
|
||||
@param stream : `The stream to read from.`
|
||||
@require !($defined(&stream) &&& @is_instream(&stream)) : "The value for 'stream' should have been passed as a pointer and not as a value, please add '&'."
|
||||
@require @is_not_instream_if_ptr(stream) : "The value for 'stream' should have been passed as a pointer and not as a value, please add '&'."
|
||||
@require @is_instream(stream) : `Make sure that the stream is actually an InStream.`
|
||||
@param [inout] allocator : `the allocator to use.`
|
||||
@return `The string containing the data read.`
|
||||
@@ -76,7 +77,7 @@ macro String? readline(Allocator allocator, stream = io::stdin())
|
||||
on the temporary allocator and does not need to be freed.
|
||||
|
||||
@param stream : `The stream to read from.`
|
||||
@require !($defined(&stream) &&& @is_instream(&stream)) : "The value for 'stream' should have been passed as a pointer and not as a value, please add '&'."
|
||||
@require @is_not_instream_if_ptr(stream) : "The value for 'stream' should have been passed as a pointer and not as a value, please add '&'."
|
||||
@require @is_instream(stream) : `The stream must implement InStream.`
|
||||
@return `The temporary string containing the data read.`
|
||||
*>
|
||||
@@ -90,22 +91,22 @@ macro String? treadline(stream = io::stdin())
|
||||
|
||||
@param out_stream : `The stream to write to`
|
||||
@param in_stream : `The stream to read from.`
|
||||
@require !($defined(&in_stream) &&& @is_instream(&in_stream)) : "The value for 'in_stream' should have been passed as a pointer and not as a value, please add '&'."
|
||||
@require !($defined(&out_stream) &&& @is_outstream(&out_stream)) : "The value for 'out_stream' should have been passed as a pointer and not as a value, please add '&'."
|
||||
@require @is_not_instream_if_ptr(in_stream) : "The value for 'in_stream' should have been passed as a pointer and not as a value, please add '&'."
|
||||
@require @is_not_outstream_if_ptr(out_stream) : "The value for 'out_stream' should have been passed as a pointer and not as a value, please add '&'."
|
||||
@require @is_instream(in_stream) : `The in_stream must implement InStream.`
|
||||
@require @is_outstream(out_stream) : `The out_stream must implement OutStream.`
|
||||
@return `The number of bytes written`
|
||||
*>
|
||||
macro usz? readline_to_stream(out_stream, in_stream = io::stdin())
|
||||
{
|
||||
bool $is_stream = @typeis(in_stream, InStream);
|
||||
bool $is_stream = $typeof(in_stream) == InStream;
|
||||
$if $is_stream:
|
||||
var func = &in_stream.read_byte;
|
||||
char val = func((void*)in_stream)!;
|
||||
$else
|
||||
char val = in_stream.read_byte()!;
|
||||
$endif
|
||||
bool $is_out_stream = @typeis(out_stream, OutStream);
|
||||
bool $is_out_stream = $typeof(out_stream) == OutStream;
|
||||
$if $is_out_stream:
|
||||
var out_func = &out_stream.write_byte;
|
||||
$endif
|
||||
@@ -160,7 +161,7 @@ macro usz? fprint(out, x)
|
||||
$case ZString: return out.write(x.str_view());
|
||||
$case DString: return out.write(x.str_view());
|
||||
$default:
|
||||
$if @assignable_to(x, String):
|
||||
$if $defined(String a = x):
|
||||
return out.write((String)x);
|
||||
$else
|
||||
$if is_struct_with_default_print($Type):
|
||||
@@ -215,7 +216,7 @@ macro usz? fprintn(out, x = "")
|
||||
usz len = fprint(out, x)!;
|
||||
out.write_byte('\n')!;
|
||||
$switch:
|
||||
$case @typeid(out) == OutStream.typeid:
|
||||
$case $typeof(out) == OutStream:
|
||||
if (&out.flush) out.flush()!;
|
||||
$case $defined(out.flush):
|
||||
out.flush()!;
|
||||
@@ -254,7 +255,7 @@ macro void eprint(x)
|
||||
|
||||
@param x : "The value to print"
|
||||
*>
|
||||
macro void eprintn(x)
|
||||
macro void eprintn(x = "")
|
||||
{
|
||||
(void)fprintn(io::stderr(), x);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,112 @@
|
||||
module std::io::os;
|
||||
|
||||
enum NativeSystemDir
|
||||
{
|
||||
DESKTOP,
|
||||
DOCUMENTS,
|
||||
VIDEOS,
|
||||
MUSIC,
|
||||
DOWNLOADS,
|
||||
PICTURES,
|
||||
TEMPLATES,
|
||||
PUBLIC_SHARE,
|
||||
SAVED_GAMES,
|
||||
SCREENSHOTS
|
||||
}
|
||||
|
||||
module std::io::os @if(env::LIBC);
|
||||
import std::io::path, std::os;
|
||||
import std::io, std::os;
|
||||
|
||||
fn String? win32_get_known_folder_temp(Win32_REFKNOWNFOLDERID rfid) @private @if(env::WIN32)
|
||||
{
|
||||
Win32_PWSTR path;
|
||||
Win32_HRESULT res = win32::shGetKnownFolderPath(rfid, 0x00008000 /* KF_FLAG_CREATE */, null, &path);
|
||||
if (res) return io::PATH_COULD_NOT_BE_FOUND?;
|
||||
return string::from_wstring(tmem, (WString)path);
|
||||
}
|
||||
|
||||
fn Path? native_home_directory(Allocator allocator) => @pool()
|
||||
{
|
||||
$switch env::OS_TYPE:
|
||||
$case IOS:
|
||||
$case MACOS:
|
||||
$case TVOS:
|
||||
$case WATCHOS:
|
||||
$case FREEBSD:
|
||||
$case KFREEBSD:
|
||||
$case LINUX:
|
||||
$case NETBSD:
|
||||
$case OPENBSD:
|
||||
$case HAIKU:
|
||||
return path::new(allocator, env::tget_var("HOME")) ?? io::PATH_COULD_NOT_BE_FOUND?;
|
||||
$case WIN32:
|
||||
return path::new(allocator, win32_get_known_folder_temp(&win32::FOLDERID_PROFILE));
|
||||
$default:
|
||||
return io::PATH_COULD_NOT_BE_FOUND?;
|
||||
$endswitch
|
||||
}
|
||||
|
||||
fn Path? native_user_directory(Allocator allocator, NativeSystemDir dir) => @pool()
|
||||
{
|
||||
$switch env::OS_TYPE:
|
||||
$case FREEBSD:
|
||||
$case KFREEBSD:
|
||||
$case LINUX:
|
||||
$case NETBSD:
|
||||
$case OPENBSD:
|
||||
$case HAIKU:
|
||||
switch (dir)
|
||||
{
|
||||
case DESKTOP: return path::new(allocator, posix::xdg_user_dir_lookup(tmem, "DESKTOP"));
|
||||
case DOWNLOADS: return path::new(allocator, posix::xdg_user_dir_lookup(tmem, "DOWNLOAD"));
|
||||
case DOCUMENTS: return path::new(allocator, posix::xdg_user_dir_lookup(tmem, "DOCUMENTS"));
|
||||
case MUSIC: return path::new(allocator, posix::xdg_user_dir_lookup(tmem, "MUSIC"));
|
||||
case VIDEOS: return path::new(allocator, posix::xdg_user_dir_lookup(tmem, "VIDEOS"));
|
||||
case PICTURES: return path::new(allocator, posix::xdg_user_dir_lookup(tmem, "PICTURES"));
|
||||
case PUBLIC_SHARE: return path::new(allocator, posix::xdg_user_dir_lookup(tmem, "PUBLICSHARE"));
|
||||
case TEMPLATES: return path::new(allocator, posix::xdg_user_dir_lookup(tmem, "TEMPLATES"));
|
||||
case SAVED_GAMES:
|
||||
case SCREENSHOTS: nextcase;
|
||||
default: return io::PATH_COULD_NOT_BE_FOUND?;
|
||||
}
|
||||
$case IOS:
|
||||
$case MACOS:
|
||||
$case WATCHOS:
|
||||
$case TVOS:
|
||||
switch (dir)
|
||||
{
|
||||
case DESKTOP: return path::new(allocator, darwin::find_first_directory_temp(DESKTOP, USER));
|
||||
case DOWNLOADS: return path::new(allocator, darwin::find_first_directory_temp(DOWNLOADS, USER));
|
||||
case DOCUMENTS: return path::new(allocator, darwin::find_first_directory_temp(DOCUMENT, USER));
|
||||
case MUSIC: return path::new(allocator, darwin::find_first_directory_temp(MUSIC, USER));
|
||||
case VIDEOS: return path::new(allocator, darwin::find_first_directory_temp(MOVIES, USER));
|
||||
case PICTURES: return path::new(allocator, darwin::find_first_directory_temp(PICTURES, USER));
|
||||
case PUBLIC_SHARE: return path::new(allocator, darwin::find_first_directory_temp(SHARED_PUBLIC, USER));
|
||||
case SAVED_GAMES:
|
||||
case SCREENSHOTS:
|
||||
case TEMPLATES: nextcase;
|
||||
default: return io::PATH_COULD_NOT_BE_FOUND?;
|
||||
}
|
||||
$case WIN32:
|
||||
switch (dir)
|
||||
{
|
||||
case DOWNLOADS: return path::new(allocator, win32_get_known_folder_temp(&win32::FOLDERID_DOWNLOADS));
|
||||
case DOCUMENTS: return path::new(allocator, win32_get_known_folder_temp(&win32::FOLDERID_DOCUMENTS));
|
||||
case DESKTOP: return path::new(allocator, win32_get_known_folder_temp(&win32::FOLDERID_DESKTOP));
|
||||
case MUSIC: return path::new(allocator, win32_get_known_folder_temp(&win32::FOLDERID_MUSIC));
|
||||
case VIDEOS: return path::new(allocator, win32_get_known_folder_temp(&win32::FOLDERID_VIDEOS));
|
||||
case PICTURES: return path::new(allocator, win32_get_known_folder_temp(&win32::FOLDERID_PICTURES));
|
||||
case SAVED_GAMES: return path::new(allocator, win32_get_known_folder_temp(&win32::FOLDERID_SAVED_GAMES));
|
||||
case SCREENSHOTS: return path::new(allocator, win32_get_known_folder_temp(&win32::FOLDERID_SCREENSHOTS));
|
||||
case TEMPLATES: return path::new(allocator, win32_get_known_folder_temp(&win32::FOLDERID_TEMPLATES));
|
||||
case PUBLIC_SHARE: nextcase;
|
||||
default: return io::PATH_COULD_NOT_BE_FOUND?;
|
||||
}
|
||||
$default:
|
||||
return io::PATH_COULD_NOT_BE_FOUND?;
|
||||
$endswitch
|
||||
}
|
||||
|
||||
|
||||
fn Path? native_temp_directory(Allocator allocator) @if(!env::WIN32)
|
||||
{
|
||||
@@ -23,7 +130,6 @@ fn Path? native_temp_directory(Allocator allocator) @if(env::WIN32) => @pool()
|
||||
module std::io::os @if(env::NO_LIBC);
|
||||
import std::io::path;
|
||||
|
||||
macro Path? native_temp_directory(Allocator allocator)
|
||||
{
|
||||
return io::UNSUPPORTED_OPERATION?;
|
||||
}
|
||||
macro Path? native_home_directory(Allocator allocator) => io::PATH_COULD_NOT_BE_FOUND?;
|
||||
macro Path? native_temp_directory(Allocator allocator) => io::PATH_COULD_NOT_BE_FOUND?;
|
||||
fn Path? native_user_directory(Allocator allocator, NativeSystemDir dir) => io::PATH_COULD_NOT_BE_FOUND?;
|
||||
|
||||
@@ -45,7 +45,7 @@ fn Path? tcwd() => cwd(tmem) @inline;
|
||||
*>
|
||||
macro void? chdir(path)
|
||||
{
|
||||
$if @typeis(path, String):
|
||||
$if $typeof(path) == String:
|
||||
@pool()
|
||||
{
|
||||
return os::native_chdir(temp(path));
|
||||
@@ -57,9 +57,21 @@ macro void? chdir(path)
|
||||
|
||||
fn Path? temp_directory(Allocator allocator) => os::native_temp_directory(allocator);
|
||||
|
||||
fn Path? home_directory(Allocator allocator) => os::native_home_directory(allocator);
|
||||
fn Path? desktop_directory(Allocator allocator) => os::native_user_directory(allocator, DESKTOP);
|
||||
fn Path? videos_directory(Allocator allocator) => os::native_user_directory(allocator, VIDEOS);
|
||||
fn Path? music_directory(Allocator allocator) => os::native_user_directory(allocator, MUSIC);
|
||||
fn Path? documents_directory(Allocator allocator) => os::native_user_directory(allocator, DOCUMENTS);
|
||||
fn Path? screenshots_directory(Allocator allocator) => os::native_user_directory(allocator, SCREENSHOTS);
|
||||
fn Path? saved_games_directory(Allocator allocator) => os::native_user_directory(allocator, SAVED_GAMES);
|
||||
fn Path? downloads_directory(Allocator allocator) => os::native_user_directory(allocator, DOWNLOADS);
|
||||
fn Path? pictures_directory(Allocator allocator) => os::native_user_directory(allocator, PICTURES);
|
||||
fn Path? templates_directory(Allocator allocator) => os::native_user_directory(allocator, TEMPLATES);
|
||||
fn Path? public_share_directory(Allocator allocator) => os::native_user_directory(allocator, PUBLIC_SHARE);
|
||||
|
||||
fn void? delete(Path path) => os::native_remove(path.str_view()) @inline;
|
||||
|
||||
macro bool @is_pathlike(#path) => @typeis(#path, String) || @typeis(#path, Path);
|
||||
macro bool @is_pathlike(#path) @const => $typeof(#path) == String ||| $typeof(#path) == Path;
|
||||
|
||||
macro bool is_separator(char c, PathEnv path_env = DEFAULT_ENV)
|
||||
{
|
||||
@@ -95,7 +107,7 @@ enum MkdirPermissions
|
||||
*>
|
||||
macro bool? mkdir(path, bool recursive = false, MkdirPermissions permissions = NORMAL)
|
||||
{
|
||||
$if @typeis(path, String):
|
||||
$if $typeof(path) == String:
|
||||
@pool() { return _mkdir(temp(path), recursive, permissions); };
|
||||
$else
|
||||
return _mkdir(path, recursive, permissions);
|
||||
@@ -113,7 +125,7 @@ macro bool? mkdir(path, bool recursive = false, MkdirPermissions permissions = N
|
||||
*>
|
||||
macro bool? rmdir(path)
|
||||
{
|
||||
$if @typeis(path, String):
|
||||
$if $typeof(path) == String:
|
||||
@pool() { return _rmdir(temp(path)); };
|
||||
$else
|
||||
return _mkdir(path);
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
module std::io;
|
||||
import std::math;
|
||||
import std::core::env;
|
||||
|
||||
|
||||
interface InStream
|
||||
{
|
||||
@@ -37,14 +39,24 @@ fn usz? available(InStream s)
|
||||
return 0;
|
||||
}
|
||||
|
||||
macro bool @is_instream(#expr)
|
||||
macro bool @is_instream(#expr) @const
|
||||
{
|
||||
return @assignable_to(#expr, InStream);
|
||||
return $defined(InStream i = #expr);
|
||||
}
|
||||
|
||||
macro bool @is_outstream(#expr)
|
||||
macro bool @is_not_instream_if_ptr(#expr) @const
|
||||
{
|
||||
return @assignable_to(#expr, OutStream);
|
||||
return !$defined(&#expr) ||| !@is_instream(&#expr);
|
||||
}
|
||||
|
||||
macro bool @is_outstream(#expr) @const
|
||||
{
|
||||
return $defined(OutStream s = #expr);
|
||||
}
|
||||
|
||||
macro bool @is_not_outstream_if_ptr(#expr) @const
|
||||
{
|
||||
return !$defined(&#expr) ||| !@is_outstream(&#expr);
|
||||
}
|
||||
|
||||
<*
|
||||
@@ -187,7 +199,7 @@ 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()
|
||||
@require $kindof(x_ptr) == POINTER && $typeof(x_ptr).inner.kindof.is_int()
|
||||
*>
|
||||
macro usz? read_varint(stream, x_ptr)
|
||||
{
|
||||
@@ -222,7 +234,7 @@ macro usz? read_varint(stream, x_ptr)
|
||||
}
|
||||
<*
|
||||
@require @is_outstream(stream)
|
||||
@require @typekind(x).is_int()
|
||||
@require $kindof(x).is_int()
|
||||
*>
|
||||
macro usz? write_varint(stream, x)
|
||||
{
|
||||
@@ -250,6 +262,16 @@ macro ushort? read_be_ushort(stream)
|
||||
return (ushort)(hi_byte << 8 | lo_byte);
|
||||
}
|
||||
|
||||
<*
|
||||
@require @is_instream(stream)
|
||||
*>
|
||||
macro ushort? read_le_ushort(stream)
|
||||
{
|
||||
char lo_byte = stream.read_byte()!;
|
||||
char hi_byte = stream.read_byte()!;
|
||||
return (ushort)(hi_byte << 8 | lo_byte);
|
||||
}
|
||||
|
||||
<*
|
||||
@require @is_instream(stream)
|
||||
*>
|
||||
@@ -258,6 +280,14 @@ macro short? read_be_short(stream)
|
||||
return read_be_ushort(stream);
|
||||
}
|
||||
|
||||
<*
|
||||
@require @is_instream(stream)
|
||||
*>
|
||||
macro short? read_le_short(stream)
|
||||
{
|
||||
return read_le_ushort(stream);
|
||||
}
|
||||
|
||||
<*
|
||||
@require @is_outstream(stream)
|
||||
*>
|
||||
@@ -267,6 +297,15 @@ macro void? write_be_short(stream, ushort s)
|
||||
stream.write_byte((char)s)!;
|
||||
}
|
||||
|
||||
<*
|
||||
@require @is_outstream(stream)
|
||||
*>
|
||||
macro void? write_le_short(stream, ushort s)
|
||||
{
|
||||
stream.write_byte((char)s)!;
|
||||
stream.write_byte((char)(s >> 8))!;
|
||||
}
|
||||
|
||||
<*
|
||||
@require @is_instream(stream)
|
||||
*>
|
||||
@@ -278,6 +317,17 @@ macro uint? read_be_uint(stream)
|
||||
return val + stream.read_byte()!;
|
||||
}
|
||||
|
||||
<*
|
||||
@require @is_instream(stream)
|
||||
*>
|
||||
macro uint? read_le_uint(stream)
|
||||
{
|
||||
uint val = stream.read_byte()!;
|
||||
val += stream.read_byte()! << 8;
|
||||
val += stream.read_byte()! << 16;
|
||||
return val + stream.read_byte()! << 24;
|
||||
}
|
||||
|
||||
<*
|
||||
@require @is_instream(stream)
|
||||
*>
|
||||
@@ -286,6 +336,14 @@ macro int? read_be_int(stream)
|
||||
return read_be_uint(stream);
|
||||
}
|
||||
|
||||
<*
|
||||
@require @is_instream(stream)
|
||||
*>
|
||||
macro int? read_le_int(stream)
|
||||
{
|
||||
return read_le_uint(stream);
|
||||
}
|
||||
|
||||
<*
|
||||
@require @is_outstream(stream)
|
||||
*>
|
||||
@@ -297,6 +355,17 @@ macro void? write_be_int(stream, uint s)
|
||||
stream.write_byte((char)s)!;
|
||||
}
|
||||
|
||||
<*
|
||||
@require @is_outstream(stream)
|
||||
*>
|
||||
macro void? write_le_int(stream, uint s)
|
||||
{
|
||||
stream.write_byte((char)s)!;
|
||||
stream.write_byte((char)(s >> 8))!;
|
||||
stream.write_byte((char)(s >> 16))!;
|
||||
stream.write_byte((char)(s >> 24))!;
|
||||
}
|
||||
|
||||
<*
|
||||
@require @is_instream(stream)
|
||||
*>
|
||||
@@ -312,6 +381,21 @@ macro ulong? read_be_ulong(stream)
|
||||
return val + stream.read_byte()!;
|
||||
}
|
||||
|
||||
<*
|
||||
@require @is_instream(stream)
|
||||
*>
|
||||
macro ulong? read_le_ulong(stream)
|
||||
{
|
||||
ulong val = (ulong)stream.read_byte()!;
|
||||
val += (ulong)stream.read_byte()! << 8;
|
||||
val += (ulong)stream.read_byte()! << 16;
|
||||
val += (ulong)stream.read_byte()! << 24;
|
||||
val += (ulong)stream.read_byte()! << 32;
|
||||
val += (ulong)stream.read_byte()! << 40;
|
||||
val += (ulong)stream.read_byte()! << 48;
|
||||
return val + (ulong)stream.read_byte()! << 56;
|
||||
}
|
||||
|
||||
<*
|
||||
@require @is_instream(stream)
|
||||
*>
|
||||
@@ -320,6 +404,14 @@ macro long? read_be_long(stream)
|
||||
return read_be_ulong(stream);
|
||||
}
|
||||
|
||||
<*
|
||||
@require @is_instream(stream)
|
||||
*>
|
||||
macro long? read_le_long(stream)
|
||||
{
|
||||
return read_le_ulong(stream);
|
||||
}
|
||||
|
||||
<*
|
||||
@require @is_outstream(stream)
|
||||
*>
|
||||
@@ -335,6 +427,21 @@ macro void? write_be_long(stream, ulong s)
|
||||
stream.write_byte((char)s)!;
|
||||
}
|
||||
|
||||
<*
|
||||
@require @is_outstream(stream)
|
||||
*>
|
||||
macro void? write_le_long(stream, ulong s)
|
||||
{
|
||||
stream.write_byte((char)s)!;
|
||||
stream.write_byte((char)(s >> 8))!;
|
||||
stream.write_byte((char)(s >> 16))!;
|
||||
stream.write_byte((char)(s >> 24))!;
|
||||
stream.write_byte((char)(s >> 32))!;
|
||||
stream.write_byte((char)(s >> 40))!;
|
||||
stream.write_byte((char)(s >> 48))!;
|
||||
stream.write_byte((char)(s >> 56))!;
|
||||
}
|
||||
|
||||
<*
|
||||
@require @is_instream(stream)
|
||||
*>
|
||||
@@ -358,6 +465,29 @@ macro uint128? read_be_uint128(stream)
|
||||
return val + stream.read_byte()!;
|
||||
}
|
||||
|
||||
<*
|
||||
@require @is_instream(stream)
|
||||
*>
|
||||
macro uint128? read_le_uint128(stream)
|
||||
{
|
||||
uint128 val = stream.read_byte()!;
|
||||
val += (uint128)stream.read_byte()! << 8;
|
||||
val += (uint128)stream.read_byte()! << 16;
|
||||
val += (uint128)stream.read_byte()! << 24;
|
||||
val += (uint128)stream.read_byte()! << 32;
|
||||
val += (uint128)stream.read_byte()! << 40;
|
||||
val += (uint128)stream.read_byte()! << 48;
|
||||
val += (uint128)stream.read_byte()! << 56;
|
||||
val += (uint128)stream.read_byte()! << 64;
|
||||
val += (uint128)stream.read_byte()! << 72;
|
||||
val += (uint128)stream.read_byte()! << 80;
|
||||
val += (uint128)stream.read_byte()! << 88;
|
||||
val += (uint128)stream.read_byte()! << 96;
|
||||
val += (uint128)stream.read_byte()! << 104;
|
||||
val += (uint128)stream.read_byte()! << 112;
|
||||
return val + (uint128)stream.read_byte()! << 120;
|
||||
}
|
||||
|
||||
<*
|
||||
@require @is_instream(stream)
|
||||
*>
|
||||
@@ -366,6 +496,14 @@ macro int128? read_be_int128(stream)
|
||||
return read_be_uint128(stream);
|
||||
}
|
||||
|
||||
<*
|
||||
@require @is_instream(stream)
|
||||
*>
|
||||
macro int128? read_le_int128(stream)
|
||||
{
|
||||
return read_le_uint128(stream);
|
||||
}
|
||||
|
||||
<*
|
||||
@require @is_outstream(stream)
|
||||
*>
|
||||
@@ -389,6 +527,30 @@ macro void? write_be_int128(stream, uint128 s)
|
||||
stream.write_byte((char)s)!;
|
||||
}
|
||||
|
||||
<*
|
||||
@require @is_outstream(stream)
|
||||
*>
|
||||
macro void? write_le_int128(stream, uint128 s)
|
||||
{
|
||||
stream.write_byte((char)s)!;
|
||||
stream.write_byte((char)(s >> 8))!;
|
||||
stream.write_byte((char)(s >> 16))!;
|
||||
stream.write_byte((char)(s >> 24))!;
|
||||
stream.write_byte((char)(s >> 32))!;
|
||||
stream.write_byte((char)(s >> 40))!;
|
||||
stream.write_byte((char)(s >> 48))!;
|
||||
stream.write_byte((char)(s >> 56))!;
|
||||
stream.write_byte((char)(s >> 64))!;
|
||||
stream.write_byte((char)(s >> 72))!;
|
||||
stream.write_byte((char)(s >> 80))!;
|
||||
stream.write_byte((char)(s >> 88))!;
|
||||
stream.write_byte((char)(s >> 96))!;
|
||||
stream.write_byte((char)(s >> 104))!;
|
||||
stream.write_byte((char)(s >> 112))!;
|
||||
stream.write_byte((char)(s >> 120))!;
|
||||
|
||||
}
|
||||
|
||||
<*
|
||||
@require @is_outstream(stream)
|
||||
@require data.len < 256 : "Data exceeded 255"
|
||||
@@ -433,6 +595,34 @@ macro char[]? read_short_bytearray(stream, Allocator allocator)
|
||||
return data;
|
||||
}
|
||||
|
||||
<*
|
||||
@require @is_instream(stream)
|
||||
*>
|
||||
macro void? skip(stream, usz bytes)
|
||||
{
|
||||
if (!bytes) return;
|
||||
$switch:
|
||||
$case !$defined(stream.seek):
|
||||
for (usz i = 0; i < bytes; i++)
|
||||
{
|
||||
stream.read()!;
|
||||
}
|
||||
return;
|
||||
$case $typeof(stream) == InStream:
|
||||
if (!&stream.seek)
|
||||
{
|
||||
for (usz i = 0; i < bytes; i++)
|
||||
{
|
||||
stream.read()!;
|
||||
}
|
||||
return;
|
||||
}
|
||||
stream.seek(bytes, CURSOR)!;
|
||||
$default:
|
||||
stream.seek(bytes, CURSOR)!;
|
||||
$endswitch
|
||||
}
|
||||
|
||||
<*
|
||||
Wrap bytes for reading using io functions.
|
||||
*>
|
||||
|
||||
@@ -13,7 +13,7 @@ struct ByteBuffer (InStream, OutStream)
|
||||
|
||||
<*
|
||||
ByteBuffer provides a streamable read/write buffer.
|
||||
max_read defines how many bytes might be kept before its internal buffer is shrinked.
|
||||
max_read defines how many bytes might be kept before its internal buffer is shrunk.
|
||||
@require self.bytes.len == 0 : "Buffer already initialized."
|
||||
*>
|
||||
fn ByteBuffer* ByteBuffer.init(&self, Allocator allocator, usz max_read, usz initial_capacity = 16)
|
||||
@@ -135,7 +135,7 @@ fn void ByteBuffer.grow(&self, usz n)
|
||||
self.bytes = p[:n];
|
||||
}
|
||||
|
||||
macro ByteBuffer.shrink(&self)
|
||||
macro void ByteBuffer.shrink(&self)
|
||||
{
|
||||
if (self.read_idx >= self.max_read)
|
||||
{
|
||||
@@ -145,4 +145,4 @@ macro ByteBuffer.shrink(&self)
|
||||
self.write_idx = 1 + readable;
|
||||
self.read_idx = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -259,6 +259,8 @@ macro CFile stdout() { return (CFile*)(uptr)STDOUT_FD; }
|
||||
macro CFile stderr() { return (CFile*)(uptr)STDERR_FD; }
|
||||
|
||||
module libc @if(!env::LIBC);
|
||||
import std::core::mem;
|
||||
|
||||
|
||||
fn void longjmp(JmpBuf* buffer, CInt value) @weak @extern("longjmp") @nostrip
|
||||
{
|
||||
@@ -288,22 +290,9 @@ fn void* realloc(void* ptr, usz size) @weak @extern("realloc") @nostrip
|
||||
unreachable("realloc unavailable");
|
||||
}
|
||||
|
||||
fn void* memcpy(void* dest, void* src, usz n) @weak @extern("memcpy") @nostrip
|
||||
{
|
||||
for (usz i = 0; i < n; i++) ((char*)dest)[i] = ((char*)src)[i];
|
||||
return dest;
|
||||
}
|
||||
|
||||
fn void* memmove(void* dest, void* src, usz n) @weak @extern("memmove") @nostrip
|
||||
{
|
||||
return memcpy(dest, src, n) @inline;
|
||||
}
|
||||
|
||||
fn void* memset(void* dest, CInt value, usz n) @weak @extern("memset") @nostrip
|
||||
{
|
||||
for (usz i = 0; i < n; i++) ((char*)dest)[i] = (char)value;
|
||||
return dest;
|
||||
}
|
||||
alias memcpy = mem::__memcpy;
|
||||
alias memmove = mem::__memcpy;
|
||||
alias memset = mem::__memset;
|
||||
|
||||
fn int fseek(CFile stream, SeekIndex offset, int whence) @weak @extern("fseek") @nostrip
|
||||
{
|
||||
@@ -418,17 +407,28 @@ alias SeekIndex = CLong;
|
||||
|
||||
struct Tm
|
||||
{
|
||||
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 */
|
||||
<* seconds after the minute [0-60] *>
|
||||
CInt tm_sec;
|
||||
<* minutes after the hour [0-59] *>
|
||||
CInt tm_min;
|
||||
<* hours since midnight [0-23] *>
|
||||
CInt tm_hour;
|
||||
<* day of the month [1-31] *>
|
||||
CInt tm_mday;
|
||||
<* months since January [0-11] *>
|
||||
CInt tm_mon;
|
||||
<* years since 1900 *>
|
||||
CInt tm_year;
|
||||
<* days since Sunday [0-6] *>
|
||||
CInt tm_wday;
|
||||
<* days since January 1 [0-365] *>
|
||||
CInt tm_yday;
|
||||
<* Daylight Savings Time flag *>
|
||||
CInt tm_isdst;
|
||||
<* offset from UTC in seconds *>
|
||||
TimeOffset tm_gmtoff @if(!env::WIN32);
|
||||
<* timezone abbreviation *>
|
||||
char* tm_zone @if(!env::WIN32);
|
||||
CInt tm_nsec @if(env::WASI);
|
||||
}
|
||||
|
||||
@@ -444,12 +444,12 @@ alias Clock_t @if(env::WIN32) = int;
|
||||
alias Clock_t @if(!env::WIN32) = CLong;
|
||||
|
||||
alias TimeOffset @if(env::WASI) = int;
|
||||
alias TimeOffset @if(!env::WASI) = CLong ;
|
||||
alias TimeOffset @if(!env::WASI) = CLong;
|
||||
|
||||
const int TIME_UTC = 1;
|
||||
|
||||
|
||||
// This is a best-effort aproximation, but the C standard does not enforce
|
||||
// This is a best-effort approximation, but the C standard does not enforce
|
||||
// that this is a compile-time standard.
|
||||
const CLOCKS_PER_SEC @if(env::WIN32) = 1000;
|
||||
const CLOCKS_PER_SEC @if(!env::WIN32) = 1000000;
|
||||
|
||||
@@ -17,17 +17,28 @@ struct Stat
|
||||
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
|
||||
<* time of last access *>
|
||||
TimeSpec st_atimespec;
|
||||
<* time of last data modification *>
|
||||
TimeSpec st_mtimespec;
|
||||
<* time of last status change *>
|
||||
TimeSpec st_ctimespec;
|
||||
<* time of file creation(birth) *>
|
||||
TimeSpec st_birthtimespec;
|
||||
<* file size, in bytes *>
|
||||
Off_t st_size;
|
||||
<* blocks allocated for file *>
|
||||
Blkcnt_t st_blocks;
|
||||
<* optimal blocksize for I/O *>
|
||||
Blksize_t st_blocksize;
|
||||
<* user defined flags for file *>
|
||||
uint st_flags;
|
||||
<* file generation number *>
|
||||
uint st_gen;
|
||||
<* RESERVED *>
|
||||
int st_lspare;
|
||||
<* RESERVED *>
|
||||
long[2] st_qspare;
|
||||
}
|
||||
|
||||
extern fn int stat(ZString str, Stat* stat) @extern("stat64");
|
||||
|
||||
@@ -56,5 +56,5 @@ struct Stat @if(!env::X86_64)
|
||||
|
||||
extern fn CInt stat(ZString path, Stat* stat);
|
||||
|
||||
extern fn CInt get_nprocs();
|
||||
extern fn CInt get_nprocs_conf();
|
||||
extern fn CInt sysctl(CInt *name, CUInt namelen, void *oldp, usz *oldlenp, void *newp, usz newlen);
|
||||
|
||||
|
||||
@@ -64,116 +64,194 @@ extern fn CInt sigaction(CInt signum, Sigaction *action, Sigaction *oldaction);
|
||||
|
||||
module libc::termios @if(env::LIBC &&& env::POSIX);
|
||||
|
||||
typedef Cc = char;
|
||||
typedef Speed = CUInt;
|
||||
typedef Tcflags = CUInt;
|
||||
typedef Tcactions = CInt;
|
||||
bitstruct Tc_iflags : CUInt
|
||||
{
|
||||
bool ignbrk;
|
||||
bool brkint;
|
||||
bool ignpar;
|
||||
bool parmrk;
|
||||
bool inpck;
|
||||
bool istrip;
|
||||
bool inlcr;
|
||||
bool igncr;
|
||||
bool icrnl;
|
||||
bool iuclc;
|
||||
bool ixon;
|
||||
bool ixany;
|
||||
bool ixoff;
|
||||
bool imaxbel;
|
||||
bool iutf8;
|
||||
}
|
||||
|
||||
const Tcactions TCOOFF = 0;
|
||||
const Tcactions TCOON = 1;
|
||||
const Tcactions TCIOFF = 2;
|
||||
const Tcactions TCION = 3;
|
||||
const Tcactions TCIFLUSH = 0;
|
||||
const Tcactions TCOFLUSH = 1;
|
||||
const Tcactions TCIOFLUSH = 2;
|
||||
const Tcactions TCSANOW = 0;
|
||||
const Tcactions TCSADRAIN = 1;
|
||||
const Tcactions TCSAFLUSH = 2;
|
||||
const Speed B0 = 0000000;
|
||||
const Speed B50 = 0000001;
|
||||
const Speed B75 = 0000002;
|
||||
const Speed B110 = 0000003;
|
||||
const Speed B134 = 0000004;
|
||||
const Speed B150 = 0000005;
|
||||
const Speed B200 = 0000006;
|
||||
const Speed B300 = 0000007;
|
||||
const Speed B600 = 0000010;
|
||||
const Speed B1200 = 0000011;
|
||||
const Speed B1800 = 0000012;
|
||||
const Speed B2400 = 0000013;
|
||||
const Speed B4800 = 0000014;
|
||||
const Speed B9600 = 0000015;
|
||||
const Speed B19200 = 0000016;
|
||||
const Speed B38400 = 0000017;
|
||||
const Speed B57600 = 0010001;
|
||||
const Speed B115200 = 0010002;
|
||||
const Speed B230400 = 0010003;
|
||||
const Speed B460800 = 0010004;
|
||||
const Speed B500000 = 0010005;
|
||||
const Speed B576000 = 0010006;
|
||||
const Speed B921600 = 0010007;
|
||||
const Speed B1000000 = 0010010;
|
||||
const Speed B1152000 = 0010011;
|
||||
const Speed B1500000 = 0010012;
|
||||
const Speed B2000000 = 0010013;
|
||||
const Speed B2500000 = 0010014;
|
||||
const Speed B3000000 = 0010015;
|
||||
const Speed B3500000 = 0010016;
|
||||
const Speed B4000000 = 0010017;
|
||||
const Speed MAX_BAUD = B4000000;
|
||||
const Tcflags VINTR = 0;
|
||||
const Tcflags VQUIT = 1;
|
||||
const Tcflags VERASE = 2;
|
||||
const Tcflags VKILL = 3;
|
||||
const Tcflags VEOF = 4;
|
||||
const Tcflags VTIME = 5;
|
||||
const Tcflags VMIN = 6;
|
||||
const Tcflags VSWTC = 7;
|
||||
const Tcflags VSTART = 8;
|
||||
const Tcflags VSTOP = 9;
|
||||
const Tcflags VSUSP = 10;
|
||||
const Tcflags VEOL = 11;
|
||||
const Tcflags VREPRINT = 12;
|
||||
const Tcflags VDISCARD = 13;
|
||||
const Tcflags VWERASE = 14;
|
||||
const Tcflags VLNEXT = 15;
|
||||
const Tcflags VEOL2 = 16;
|
||||
const Tcflags ISIG = 0000001;
|
||||
const Tcflags ICANON = 0000002;
|
||||
const Tcflags ECHO = 0000010;
|
||||
const Tcflags ECHOE = 0000020;
|
||||
const Tcflags ECHOK = 0000040;
|
||||
const Tcflags ECHONL = 0000100;
|
||||
const Tcflags NOFLSH = 0000200;
|
||||
const Tcflags TOSTOP = 0000400;
|
||||
const Tcflags IEXTEN = 0100000;
|
||||
const Tcflags CSIZE = 0000060;
|
||||
const Tcflags CS5 = 0000000;
|
||||
const Tcflags CS6 = 0000020;
|
||||
const Tcflags CS7 = 0000040;
|
||||
const Tcflags CS8 = 0000060;
|
||||
const Tcflags CSTOPB = 0000100;
|
||||
const Tcflags CREAD = 0000200;
|
||||
const Tcflags PARENB = 0000400;
|
||||
const Tcflags PARODD = 0001000;
|
||||
const Tcflags HUPCL = 0002000;
|
||||
const Tcflags CLOCAL = 0004000;
|
||||
const Tcflags OPOST = 0000001;
|
||||
const Tcflags OLCUC = 0000002;
|
||||
const Tcflags ONLCR = 0000004;
|
||||
const Tcflags OCRNL = 0000010;
|
||||
const Tcflags ONOCR = 0000020;
|
||||
const Tcflags ONLRET = 0000040;
|
||||
const Tcflags OFILL = 0000100;
|
||||
const Tcflags OFDEL = 0000200;
|
||||
const Tcflags VTDLY = 0040000;
|
||||
const Tcflags VT0 = 0000000;
|
||||
const Tcflags VT1 = 0040000;
|
||||
const Tcflags IGNBRK = 0000001;
|
||||
const Tcflags BRKINT = 0000002;
|
||||
const Tcflags IGNPAR = 0000004;
|
||||
const Tcflags PARMRK = 0000010;
|
||||
const Tcflags INPCK = 0000020;
|
||||
const Tcflags ISTRIP = 0000040;
|
||||
const Tcflags INLCR = 0000100;
|
||||
const Tcflags IGNCR = 0000200;
|
||||
const Tcflags ICRNL = 0000400;
|
||||
const Tcflags IUCLC = 0001000;
|
||||
const Tcflags IXON = 0002000;
|
||||
const Tcflags IXANY = 0004000;
|
||||
const Tcflags IXOFF = 0010000;
|
||||
const Tcflags IMAXBEL = 0020000;
|
||||
const Tcflags IUTF8 = 0040000;
|
||||
bitstruct Tc_oflags : CUInt
|
||||
{
|
||||
bool opost : 0;
|
||||
bool olcuc : 1;
|
||||
bool onlcr : 2;
|
||||
bool ocrnl : 3;
|
||||
bool onocr : 4;
|
||||
bool onlret : 5;
|
||||
bool ofill : 6;
|
||||
bool ofdel : 7;
|
||||
T_nldly nldly : 8..8;
|
||||
T_crdly crdly : 9..10;
|
||||
T_tabdly tabdly : 11..12;
|
||||
T_bsdly bsdly : 13..13;
|
||||
T_vtdly vtdly : 14..14;
|
||||
T_ffdly ffdly : 15..15;
|
||||
}
|
||||
|
||||
bitstruct Tc_cflags : CUInt
|
||||
{
|
||||
T_csize csize : 4..5;
|
||||
bool cstopb : 6;
|
||||
bool cread : 7;
|
||||
bool parenb : 8;
|
||||
bool parodd : 9;
|
||||
bool hupcl : 10;
|
||||
bool clocal : 11;
|
||||
bool addrb : 29;
|
||||
}
|
||||
|
||||
bitstruct Tc_lflags : CUInt
|
||||
{
|
||||
bool isig : 0;
|
||||
bool icanon : 1;
|
||||
bool xcase : 2;
|
||||
bool echo : 3;
|
||||
bool echoe : 4;
|
||||
bool echok : 5;
|
||||
bool echonl : 6;
|
||||
bool noflsh : 7;
|
||||
bool tostop : 8;
|
||||
bool echoctl : 9;
|
||||
bool echoprt : 10;
|
||||
bool echoke : 11;
|
||||
bool flusho : 12;
|
||||
bool pendin : 14;
|
||||
bool iexten : 15;
|
||||
bool extproc : 16;
|
||||
}
|
||||
|
||||
enum T_nldly : const char
|
||||
{
|
||||
NL0 = 0b0,
|
||||
NL1 = 0b1,
|
||||
}
|
||||
|
||||
enum T_crdly : const char
|
||||
{
|
||||
CR0 = 0b00,
|
||||
CR1 = 0b01,
|
||||
CR2 = 0b10,
|
||||
CR3 = 0b11,
|
||||
}
|
||||
|
||||
enum T_tabdly : const char
|
||||
{
|
||||
TAB0 = 0b00,
|
||||
TAB1 = 0b01,
|
||||
TAB2 = 0b10,
|
||||
TAB3 = 0b11,
|
||||
XTABS = TAB3,
|
||||
}
|
||||
|
||||
enum T_bsdly : const char
|
||||
{
|
||||
BS0 = 0b0,
|
||||
BS1 = 0b1,
|
||||
}
|
||||
|
||||
enum T_ffdly : const char
|
||||
{
|
||||
FF0 = 0b0,
|
||||
FF1 = 0b1,
|
||||
}
|
||||
|
||||
enum T_vtdly : const char
|
||||
{
|
||||
VT0 = 0b0,
|
||||
VT1 = 0b1,
|
||||
}
|
||||
|
||||
enum T_csize : const char
|
||||
{
|
||||
CS5 = 0b00,
|
||||
CS6 = 0b01,
|
||||
CS7 = 0b10,
|
||||
CS8 = 0b11,
|
||||
}
|
||||
|
||||
enum Speed : const CUInt
|
||||
{
|
||||
B0 = 0o0000000,
|
||||
B50 = 0o0000001,
|
||||
B75 = 0o0000002,
|
||||
B110 = 0o0000003,
|
||||
B134 = 0o0000004,
|
||||
B150 = 0o0000005,
|
||||
B200 = 0o0000006,
|
||||
B300 = 0o0000007,
|
||||
B600 = 0o0000010,
|
||||
B1200 = 0o0000011,
|
||||
B1800 = 0o0000012,
|
||||
B2400 = 0o0000013,
|
||||
B4800 = 0o0000014,
|
||||
B9600 = 0o0000015,
|
||||
B19200 = 0o0000016,
|
||||
B38400 = 0o0000017,
|
||||
B57600 = 0o0010001,
|
||||
B115200 = 0o0010002,
|
||||
B230400 = 0o0010003,
|
||||
B460800 = 0o0010004,
|
||||
B500000 = 0o0010005,
|
||||
B576000 = 0o0010006,
|
||||
B921600 = 0o0010007,
|
||||
B1000000 = 0o0010010,
|
||||
B1152000 = 0o0010011,
|
||||
B1500000 = 0o0010012,
|
||||
B2000000 = 0o0010013,
|
||||
B2500000 = 0o0010014,
|
||||
B3000000 = 0o0010015,
|
||||
B3500000 = 0o0010016,
|
||||
B4000000 = 0o0010017,
|
||||
MAX_BAUD = B4000000,
|
||||
}
|
||||
|
||||
enum Cc : const inline char
|
||||
{
|
||||
VINTR = 0,
|
||||
VQUIT = 1,
|
||||
VERASE = 2,
|
||||
VKILL = 3,
|
||||
VEOF = 4,
|
||||
VTIME = 5,
|
||||
VMIN = 6,
|
||||
VSWTC = 7,
|
||||
VSTART = 8,
|
||||
VSTOP = 9,
|
||||
VSUSP = 10,
|
||||
VEOL = 11,
|
||||
VREPRINT = 12,
|
||||
VDISCARD = 13,
|
||||
VWERASE = 14,
|
||||
VLNEXT = 15,
|
||||
VEOL2 = 16,
|
||||
}
|
||||
|
||||
enum Tcactions : const CInt
|
||||
{
|
||||
TCOOFF = 0,
|
||||
TCOON = 1,
|
||||
TCIOFF = 2,
|
||||
TCION = 3,
|
||||
TCIFLUSH = 0,
|
||||
TCOFLUSH = 1,
|
||||
TCIOFLUSH = 2,
|
||||
TCSANOW = 0,
|
||||
TCSADRAIN = 1,
|
||||
TCSAFLUSH = 2,
|
||||
}
|
||||
|
||||
extern fn CInt tcgetattr(Fd fd, Termios* self);
|
||||
extern fn CInt tcsetattr(Fd fd, Tcactions optional_actions, Termios* self);
|
||||
@@ -189,13 +267,119 @@ extern fn CInt cfsetispeed(Termios* self, Speed speed);
|
||||
const CInt NCCS = 32;
|
||||
struct Termios
|
||||
{
|
||||
Tcflags c_iflag;
|
||||
Tcflags c_oflag;
|
||||
Tcflags c_cflag;
|
||||
Tcflags c_lflag;
|
||||
Tc_iflags c_iflag;
|
||||
Tc_oflags c_oflag;
|
||||
Tc_cflags c_cflag;
|
||||
Tc_lflags c_lflag;
|
||||
Cc c_line;
|
||||
Cc[NCCS] c_cc;
|
||||
Speed c_ispeed;
|
||||
Speed c_ospeed;
|
||||
}
|
||||
}
|
||||
|
||||
const Tcactions TCOOFF @deprecated = 0;
|
||||
const Tcactions TCOON @deprecated = 1;
|
||||
const Tcactions TCIOFF @deprecated = 2;
|
||||
const Tcactions TCION @deprecated = 3;
|
||||
const Tcactions TCIFLUSH @deprecated = 0;
|
||||
const Tcactions TCOFLUSH @deprecated = 1;
|
||||
const Tcactions TCIOFLUSH @deprecated = 2;
|
||||
const Tcactions TCSANOW @deprecated = 0;
|
||||
const Tcactions TCSADRAIN @deprecated = 1;
|
||||
const Tcactions TCSAFLUSH @deprecated = 2;
|
||||
const Speed B0 @deprecated = 0o0000000;
|
||||
const Speed B50 @deprecated = 0o0000001;
|
||||
const Speed B75 @deprecated = 0o0000002;
|
||||
const Speed B110 @deprecated = 0o0000003;
|
||||
const Speed B134 @deprecated = 0o0000004;
|
||||
const Speed B150 @deprecated = 0o0000005;
|
||||
const Speed B200 @deprecated = 0o0000006;
|
||||
const Speed B300 @deprecated = 0o0000007;
|
||||
const Speed B600 @deprecated = 0o0000010;
|
||||
const Speed B1200 @deprecated = 0o0000011;
|
||||
const Speed B1800 @deprecated = 0o0000012;
|
||||
const Speed B2400 @deprecated = 0o0000013;
|
||||
const Speed B4800 @deprecated = 0o0000014;
|
||||
const Speed B9600 @deprecated = 0o0000015;
|
||||
const Speed B19200 @deprecated = 0o0000016;
|
||||
const Speed B38400 @deprecated = 0o0000017;
|
||||
const Speed B57600 @deprecated = 0o0010001;
|
||||
const Speed B115200 @deprecated = 0o0010002;
|
||||
const Speed B230400 @deprecated = 0o0010003;
|
||||
const Speed B460800 @deprecated = 0o0010004;
|
||||
const Speed B500000 @deprecated = 0o0010005;
|
||||
const Speed B576000 @deprecated = 0o0010006;
|
||||
const Speed B921600 @deprecated = 0o0010007;
|
||||
const Speed B1000000 @deprecated = 0o0010010;
|
||||
const Speed B1152000 @deprecated = 0o0010011;
|
||||
const Speed B1500000 @deprecated = 0o0010012;
|
||||
const Speed B2000000 @deprecated = 0o0010013;
|
||||
const Speed B2500000 @deprecated = 0o0010014;
|
||||
const Speed B3000000 @deprecated = 0o0010015;
|
||||
const Speed B3500000 @deprecated = 0o0010016;
|
||||
const Speed B4000000 @deprecated = 0o0010017;
|
||||
const Speed MAX_BAUD @deprecated = B4000000;
|
||||
const Cc VINTR @deprecated = 0;
|
||||
const Cc VQUIT @deprecated = 1;
|
||||
const Cc VERASE @deprecated = 2;
|
||||
const Cc VKILL @deprecated = 3;
|
||||
const Cc VEOF @deprecated = 4;
|
||||
const Cc VTIME @deprecated = 5;
|
||||
const Cc VMIN @deprecated = 6;
|
||||
const Cc VSWTC @deprecated = 7;
|
||||
const Cc VSTART @deprecated = 8;
|
||||
const Cc VSTOP @deprecated = 9;
|
||||
const Cc VSUSP @deprecated = 10;
|
||||
const Cc VEOL @deprecated = 11;
|
||||
const Cc VREPRINT @deprecated = 12;
|
||||
const Cc VDISCARD @deprecated = 13;
|
||||
const Cc VWERASE @deprecated = 14;
|
||||
const Cc VLNEXT @deprecated = 15;
|
||||
const Cc VEOL2 @deprecated = 16;
|
||||
const Tc_lflags ISIG @deprecated = {.isig};
|
||||
const Tc_lflags ICANON @deprecated = {.icanon};
|
||||
const Tc_lflags ECHO @deprecated = {.echo};
|
||||
const Tc_lflags ECHOE @deprecated = {.echoe};
|
||||
const Tc_lflags ECHOK @deprecated = {.echok};
|
||||
const Tc_lflags ECHONL @deprecated = {.echonl};
|
||||
const Tc_lflags NOFLSH @deprecated = {.noflsh};
|
||||
const Tc_lflags TOSTOP @deprecated = {.tostop};
|
||||
const Tc_lflags IEXTEN @deprecated = {.iexten};
|
||||
const Tc_cflags CSIZE @deprecated = {.csize = CS8};
|
||||
const Tc_cflags CS5 @deprecated = {.csize = CS5};
|
||||
const Tc_cflags CS6 @deprecated = {.csize = CS6};
|
||||
const Tc_cflags CS7 @deprecated = {.csize = CS7};
|
||||
const Tc_cflags CS8 @deprecated = {.csize = CS8};
|
||||
const Tc_cflags CSTOPB @deprecated = {.cstopb};
|
||||
const Tc_cflags CREAD @deprecated = {.cread};
|
||||
const Tc_cflags PARENB @deprecated = {.parenb};
|
||||
const Tc_cflags PARODD @deprecated = {.parodd};
|
||||
const Tc_cflags HUPCL @deprecated = {.hupcl};
|
||||
const Tc_cflags CLOCAL @deprecated = {.clocal};
|
||||
const Tc_oflags OPOST @deprecated = {.opost};
|
||||
const Tc_oflags OLCUC @deprecated = {.olcuc};
|
||||
const Tc_oflags ONLCR @deprecated = {.onlcr};
|
||||
const Tc_oflags OCRNL @deprecated = {.ocrnl};
|
||||
const Tc_oflags ONOCR @deprecated = {.onocr};
|
||||
const Tc_oflags ONLRET @deprecated = {.onlret};
|
||||
const Tc_oflags OFILL @deprecated = {.ofill};
|
||||
const Tc_oflags OFDEL @deprecated = {.ofdel};
|
||||
const Tc_oflags VTDLY @deprecated = {.vtdly = VT1};
|
||||
const Tc_oflags VT0 @deprecated = {.vtdly = VT0};
|
||||
const Tc_oflags VT1 @deprecated = {.vtdly = VT1};
|
||||
const Tc_iflags IGNBRK @deprecated = {.ignbrk};
|
||||
const Tc_iflags BRKINT @deprecated = {.brkint};
|
||||
const Tc_iflags IGNPAR @deprecated = {.ignpar};
|
||||
const Tc_iflags PARMRK @deprecated = {.parmrk};
|
||||
const Tc_iflags INPCK @deprecated = {.inpck};
|
||||
const Tc_iflags ISTRIP @deprecated = {.istrip};
|
||||
const Tc_iflags INLCR @deprecated = {.inlcr};
|
||||
const Tc_iflags IGNCR @deprecated = {.igncr};
|
||||
const Tc_iflags ICRNL @deprecated = {.icrnl};
|
||||
const Tc_iflags IUCLC @deprecated = {.iuclc};
|
||||
const Tc_iflags IXON @deprecated = {.ixon};
|
||||
const Tc_iflags IXANY @deprecated = {.ixany};
|
||||
const Tc_iflags IXOFF @deprecated = {.ixoff};
|
||||
const Tc_iflags IMAXBEL @deprecated = {.imaxbel};
|
||||
const Tc_iflags IUTF8 @deprecated = {.iutf8};
|
||||
|
||||
|
||||
@@ -1,15 +1,23 @@
|
||||
module libc::termios @if(env::LIBC &&& env::POSIX);
|
||||
|
||||
fn int sendBreak(Fd fd, int duration) => tcsendbreak(fd, duration);
|
||||
fn int send_break(Fd fd, int duration) => tcsendbreak(fd, duration);
|
||||
fn int drain(Fd fd) => tcdrain(fd);
|
||||
fn int flush(Fd fd, int queue_selector) => tcflush(fd, queue_selector);
|
||||
fn int flow(Fd fd, int action) => tcflow(fd, action);
|
||||
fn Speed Termios.getOSpeed(Termios* self) => cfgetospeed(self);
|
||||
fn Speed Termios.getISpeed(Termios* self) => cfgetispeed(self);
|
||||
fn int Termios.setOSpeed(Termios* self, Speed speed) => cfsetospeed(self, speed);
|
||||
fn int Termios.setISpeed(Termios* self, Speed speed) => cfsetispeed(self, speed);
|
||||
fn int Termios.getAttr(Termios* self, Fd fd) => tcgetattr(fd, self);
|
||||
fn int Termios.setAttr(Termios* self, Fd fd, Tcactions optional_actions) => tcsetattr(fd, optional_actions, self);
|
||||
fn Speed Termios.get_ospeed(&self) => cfgetospeed(self);
|
||||
fn Speed Termios.get_ispeed(&self) => cfgetispeed(self);
|
||||
fn int Termios.set_ospeed(&self, Speed speed) => cfsetospeed(self, speed);
|
||||
fn int Termios.set_ispeed(&self, Speed speed) => cfsetispeed(self, speed);
|
||||
fn int Termios.get_attr(&self, Fd fd) => tcgetattr(fd, self);
|
||||
fn int Termios.set_attr(&self, Fd fd, Tcactions optional_actions) => tcsetattr(fd, optional_actions, self);
|
||||
|
||||
fn int sendBreak(Fd fd, int duration) @deprecated => send_break(fd, duration);
|
||||
fn Speed Termios.getOSpeed(&self) @deprecated => self.get_ospeed();
|
||||
fn Speed Termios.getISpeed(&self) @deprecated => self.get_ispeed();
|
||||
fn int Termios.setOSpeed(&self, Speed speed) @deprecated => self.set_ospeed(speed);
|
||||
fn int Termios.setISpeed(&self, Speed speed) @deprecated => self.set_ispeed(speed);
|
||||
fn int Termios.getAttr(&self, Fd fd) @deprecated => self.get_attr(fd);
|
||||
fn int Termios.setAttr(&self, Fd fd, Tcactions optional_actions) @deprecated => self.set_attr(fd, optional_actions);
|
||||
|
||||
module libc::termios @if(!env::LIBC ||| !env::POSIX);
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ module std::math;
|
||||
|
||||
// Complex number aliases.
|
||||
|
||||
alias Complexf = Complex {float};
|
||||
alias Complex = Complex {double};
|
||||
alias Complexf = ComplexNumber {float};
|
||||
alias Complex = ComplexNumber {double};
|
||||
alias COMPLEX_IDENTITY @builtin = complex::IDENTITY {double};
|
||||
alias COMPLEXF_IDENTITY @builtin = complex::IDENTITY {float};
|
||||
alias IMAGINARY @builtin @deprecated("Use I") = complex::IMAGINARY { double };
|
||||
@@ -19,7 +19,7 @@ alias I_F @builtin = complex::IMAGINARY { float };
|
||||
module std::math::complex {Real};
|
||||
import std::io;
|
||||
|
||||
union Complex (Printable)
|
||||
union ComplexNumber (Printable)
|
||||
{
|
||||
struct
|
||||
{
|
||||
@@ -28,39 +28,39 @@ union Complex (Printable)
|
||||
Real[<2>] v;
|
||||
}
|
||||
|
||||
const Complex IDENTITY = { 1, 0 };
|
||||
const Complex IMAGINARY = { 0, 1 };
|
||||
const ComplexNumber IDENTITY = { 1, 0 };
|
||||
const ComplexNumber IMAGINARY = { 0, 1 };
|
||||
|
||||
macro Complex Complex.add(self, Complex b) @operator(+) => { .v = self.v + b.v };
|
||||
macro Complex Complex.add_this(&self, Complex b) @operator(+=) => { .v = self.v += b.v };
|
||||
macro Complex Complex.add_real(self, Real r) @operator_s(+) => { .v = self.v + (Real[<2>]) { r, 0 } };
|
||||
macro Complex Complex.add_each(self, Real b) => { .v = self.v + b };
|
||||
macro Complex Complex.sub(self, Complex b) @operator(-) => { .v = self.v - b.v };
|
||||
macro Complex Complex.sub_this(&self, Complex b) @operator(-=) => { .v = self.v -= b.v };
|
||||
macro Complex Complex.sub_real(self, Real r) @operator(-) => { .v = self.v - (Real[<2>]) { r, 0 } };
|
||||
macro Complex Complex.sub_real_inverse(self, Real r) @operator_r(-) => { .v = (Real[<2>]) { r, 0 } - self.v };
|
||||
macro Complex Complex.sub_each(self, Real b) => { .v = self.v - b };
|
||||
macro Complex Complex.scale(self, Real r) @operator_s(*) => { .v = self.v * r };
|
||||
macro Complex Complex.mul(self, Complex b)@operator(*) => { self.r * b.r - self.c * b.c, self.r * b.c + b.r * self.c };
|
||||
macro Complex Complex.div_real(self, Real r) @operator(/) => { .v = self.v / r };
|
||||
macro Complex Complex.div_real_inverse(Complex c, Real r) @operator_r(/) => ((Complex) { .r = r }).div(c);
|
||||
macro Complex Complex.div(self, Complex b) @operator(/)
|
||||
macro ComplexNumber ComplexNumber.add(self, ComplexNumber b) @operator(+) => { .v = self.v + b.v };
|
||||
macro ComplexNumber ComplexNumber.add_this(&self, ComplexNumber b) @operator(+=) => { .v = self.v += b.v };
|
||||
macro ComplexNumber ComplexNumber.add_real(self, Real r) @operator_s(+) => { .v = self.v + (Real[<2>]) { r, 0 } };
|
||||
macro ComplexNumber ComplexNumber.add_each(self, Real b) => { .v = self.v + b };
|
||||
macro ComplexNumber ComplexNumber.sub(self, ComplexNumber b) @operator(-) => { .v = self.v - b.v };
|
||||
macro ComplexNumber ComplexNumber.sub_this(&self, ComplexNumber b) @operator(-=) => { .v = self.v -= b.v };
|
||||
macro ComplexNumber ComplexNumber.sub_real(self, Real r) @operator(-) => { .v = self.v - (Real[<2>]) { r, 0 } };
|
||||
macro ComplexNumber ComplexNumber.sub_real_inverse(self, Real r) @operator_r(-) => { .v = (Real[<2>]) { r, 0 } - self.v };
|
||||
macro ComplexNumber ComplexNumber.sub_each(self, Real b) => { .v = self.v - b };
|
||||
macro ComplexNumber ComplexNumber.scale(self, Real r) @operator_s(*) => { .v = self.v * r };
|
||||
macro ComplexNumber ComplexNumber.mul(self, ComplexNumber b)@operator(*) => { self.r * b.r - self.c * b.c, self.r * b.c + b.r * self.c };
|
||||
macro ComplexNumber ComplexNumber.div_real(self, Real r) @operator(/) => { .v = self.v / r };
|
||||
macro ComplexNumber ComplexNumber.div_real_inverse(ComplexNumber c, Real r) @operator_r(/) => ((ComplexNumber) { .r = r }).div(c);
|
||||
macro ComplexNumber ComplexNumber.div(self, ComplexNumber b) @operator(/)
|
||||
{
|
||||
Real div = b.v.dot(b.v);
|
||||
return { (self.r * b.r + self.c * b.c) / div, (self.c * b.r - self.r * b.c) / div };
|
||||
}
|
||||
macro Complex Complex.inverse(self)
|
||||
macro ComplexNumber ComplexNumber.inverse(self)
|
||||
{
|
||||
Real sqr = self.v.dot(self.v);
|
||||
return { self.r / sqr, -self.c / sqr };
|
||||
}
|
||||
macro Complex Complex.conjugate(self) => { .r = self.r, .c = -self.c };
|
||||
macro Complex Complex.negate(self) @operator(-) => { .v = -self.v };
|
||||
macro bool Complex.equals(self, Complex b) @operator(==) => self.v == b.v;
|
||||
macro bool Complex.equals_real(self, Real r) @operator_s(==) => self.v == { r, 0 };
|
||||
macro bool Complex.not_equals(self, Complex b) @operator(!=) => self.v != b.v;
|
||||
macro ComplexNumber ComplexNumber.conjugate(self) => { .r = self.r, .c = -self.c };
|
||||
macro ComplexNumber ComplexNumber.negate(self) @operator(-) => { .v = -self.v };
|
||||
macro bool ComplexNumber.equals(self, ComplexNumber b) @operator(==) => self.v == b.v;
|
||||
macro bool ComplexNumber.equals_real(self, Real r) @operator_s(==) => self.v == { r, 0 };
|
||||
macro bool ComplexNumber.not_equals(self, ComplexNumber b) @operator(!=) => self.v != b.v;
|
||||
|
||||
fn usz? Complex.to_format(&self, Formatter* f) @dynamic
|
||||
fn usz? ComplexNumber.to_format(&self, Formatter* f) @dynamic
|
||||
{
|
||||
return f.printf("%g%+gi", self.r, self.c);
|
||||
}
|
||||
@@ -82,7 +82,7 @@ macro abs(x) => $$abs(x);
|
||||
@require values::@is_int(x) || values::@is_float(x) : "Expected an integer or floating point value"
|
||||
@require values::@is_int(y) || values::@is_float(y) : "Expected an integer or floating point value"
|
||||
*>
|
||||
macro is_approx(x, y, eps)
|
||||
macro bool is_approx(x, y, eps)
|
||||
{
|
||||
if (x == y) return true;
|
||||
if (is_nan(x) || is_nan(y)) return false;
|
||||
@@ -93,7 +93,7 @@ macro is_approx(x, y, eps)
|
||||
@require values::@is_int(x) || values::@is_float(x) : "Expected an integer or floating point value"
|
||||
@require values::@is_int(y) || values::@is_float(y) : "Expected an integer or floating point value"
|
||||
*>
|
||||
macro is_approx_rel(x, y, eps)
|
||||
macro bool is_approx_rel(x, y, eps)
|
||||
{
|
||||
if (x == y) return true;
|
||||
if (is_nan(x) || is_nan(y)) return false;
|
||||
@@ -101,7 +101,7 @@ macro is_approx_rel(x, y, eps)
|
||||
}
|
||||
|
||||
<*
|
||||
@require values::@is_int(x) : `The input must be an integer`
|
||||
@require $kindof(x).is_int() : `The input must be an integer`
|
||||
*>
|
||||
macro sign(x)
|
||||
{
|
||||
@@ -119,7 +119,7 @@ macro sign(x)
|
||||
*>
|
||||
macro atan2(x, y)
|
||||
{
|
||||
$if @typeis(x, float) && @typeis(y, float):
|
||||
$if $typeof(x) == float &&& $typeof(y) == float:
|
||||
return _atan2f(x, y);
|
||||
$else
|
||||
return _atan2(x, y);
|
||||
@@ -128,16 +128,16 @@ macro atan2(x, y)
|
||||
|
||||
<*
|
||||
@require values::@is_int(x) || values::@is_float(x) : "Expected an integer or floating point value"
|
||||
@require @typekind(sinp) == POINTER : "Expected sinp to be a pointer"
|
||||
@require values::@is_same_type(sinp, cosp) : "Expected sinp and cosp to have the same type"
|
||||
@require @assignable_to(x, $typeof(*sinp)) : "Expected x and sinp/cosp to have the same type"
|
||||
@require $kindof(sinp) == POINTER : "Expected sinp to be a pointer"
|
||||
@require @typematch(sinp, cosp) : "Expected sinp and cosp to have the same type"
|
||||
@require $defined(*sinp = x) : "Expected x and *sinp/*cosp to have the same type"
|
||||
*>
|
||||
macro sincos_ref(x, sinp, cosp)
|
||||
macro void sincos_ref(x, sinp, cosp)
|
||||
{
|
||||
$if @typeis(sinp, float*.typeid):
|
||||
return _sincosf(x, sinp, cosp);
|
||||
$if $typeof(sinp) == float*:
|
||||
_sincosf(x, sinp, cosp);
|
||||
$else
|
||||
return _sincos(x, sinp, cosp);
|
||||
_sincos(x, sinp, cosp);
|
||||
$endif
|
||||
}
|
||||
|
||||
@@ -149,7 +149,7 @@ macro sincos_ref(x, sinp, cosp)
|
||||
*>
|
||||
macro sincos(x)
|
||||
{
|
||||
$if @typeis(x, float):
|
||||
$if $typeof(x) == float:
|
||||
float[<2>] v @noinit;
|
||||
_sincosf(x, &v[0], &v[1]);
|
||||
$else
|
||||
@@ -164,7 +164,7 @@ macro sincos(x)
|
||||
*>
|
||||
macro atan(x)
|
||||
{
|
||||
$if @typeis(x, float):
|
||||
$if $typeof(x) == float:
|
||||
return _atanf(x);
|
||||
$else
|
||||
return _atan(x);
|
||||
@@ -176,7 +176,7 @@ macro atan(x)
|
||||
*>
|
||||
macro atanh(x)
|
||||
{
|
||||
$if @typeis(x, float):
|
||||
$if $typeof(x) == float:
|
||||
return _atanhf(x);
|
||||
$else
|
||||
return _atanh(x);
|
||||
@@ -188,7 +188,7 @@ macro atanh(x)
|
||||
*>
|
||||
macro acos(x)
|
||||
{
|
||||
$if @typeis(x, float):
|
||||
$if $typeof(x) == float:
|
||||
return _acosf(x);
|
||||
$else
|
||||
return _acos(x);
|
||||
@@ -200,7 +200,7 @@ macro acos(x)
|
||||
*>
|
||||
macro acosh(x)
|
||||
{
|
||||
$if @typeis(x, float):
|
||||
$if $typeof(x) == float:
|
||||
return _acoshf(x);
|
||||
$else
|
||||
return _acosh(x);
|
||||
@@ -212,7 +212,7 @@ macro acosh(x)
|
||||
*>
|
||||
macro asin(x)
|
||||
{
|
||||
$if @typeis(x, float):
|
||||
$if $typeof(x) == float:
|
||||
return _asinf(x);
|
||||
$else
|
||||
return _asin(x);
|
||||
@@ -224,7 +224,7 @@ macro asin(x)
|
||||
*>
|
||||
macro asinh(x)
|
||||
{
|
||||
$if @typeis(x, float):
|
||||
$if $typeof(x) == float:
|
||||
return _asinhf(x);
|
||||
$else
|
||||
return _asinh(x);
|
||||
@@ -239,7 +239,7 @@ macro ceil(x) => $$ceil(x);
|
||||
<*
|
||||
Ceil for compile time evaluation.
|
||||
|
||||
@require @typeis($input, double) || @typeis($input, float) : "Only float and double may be used"
|
||||
@require $kindof($input) == FLOAT : "Only float and double may be used"
|
||||
*>
|
||||
macro @ceil($input) @const => $$ceil($input);
|
||||
|
||||
@@ -252,8 +252,8 @@ macro @ceil($input) @const => $$ceil($input);
|
||||
@return "lower if x < lower, upper if x > upper, otherwise return x."
|
||||
|
||||
@require types::is_numerical($typeof(x)) : `The input must be a numerical value or numerical vector`
|
||||
@require values::@assign_to(lower, x) : `The lower bound must be convertable to the value type.`
|
||||
@require values::@assign_to(upper, x) : `The upper bound must be convertable to the value type.`
|
||||
@require $defined(x = lower) : `The lower bound must be convertible to the value type.`
|
||||
@require $defined(x = upper) : `The upper bound must be convertible to the value type.`
|
||||
*>
|
||||
macro clamp(x, lower, upper) => $$max(($typeof(x))lower, $$min(x, ($typeof(x))upper));
|
||||
|
||||
@@ -344,6 +344,23 @@ macro log(x, base)
|
||||
*>
|
||||
macro log2(x) => $$log2(values::promote_int(x));
|
||||
|
||||
<*
|
||||
@require values::@is_int($x) : `The input value must be an integer.`
|
||||
@require $x >= 0 : `The input value must be a positive integer.`
|
||||
@return `A floored base-2 log of an input integer value.`
|
||||
*>
|
||||
macro @intlog2($x)
|
||||
{
|
||||
$if $x <= 1:
|
||||
return 0;
|
||||
$endif
|
||||
|
||||
$typeof($x) $z = 0;
|
||||
$for var $y = $x; $y > 0; $y >>= 1, ++$z: $endfor
|
||||
|
||||
return $z - 1;
|
||||
}
|
||||
|
||||
<*
|
||||
@require values::@is_promotable_to_floatlike(x) : `The input must be a number or a float vector`
|
||||
*>
|
||||
@@ -351,7 +368,7 @@ macro log10(x) => $$log10(values::promote_int(x));
|
||||
|
||||
<*
|
||||
@require types::is_numerical($typeof(x)) : `The input must be a floating point value or float vector`
|
||||
@require types::is_same($typeof(x), $typeof(y)) : `The input types must be equal`
|
||||
@require @typematch(x, y) : `The input types must be equal`
|
||||
*>
|
||||
macro max(x, y, ...)
|
||||
{
|
||||
@@ -396,7 +413,7 @@ macro nearbyint(x) => $$nearbyint(x);
|
||||
|
||||
<*
|
||||
@require values::@is_promotable_to_floatlike(x) : `The input must be a number or a float vector`
|
||||
@require @assignable_to(exp, $typeof(values::promote_int(x))) || values::@is_int(exp) : `The input must be an integer, castable to the type of x`
|
||||
@require $defined($typeof(values::promote_int(x)) v = exp) || values::@is_int(exp) : `The input must be an integer, castable to the type of x`
|
||||
*>
|
||||
macro pow(x, exp)
|
||||
{
|
||||
@@ -523,7 +540,7 @@ macro bool is_finite(x)
|
||||
<*
|
||||
@require values::@is_promotable_to_float(x) : `The input must be a float`
|
||||
*>
|
||||
macro is_nan(x)
|
||||
macro bool is_nan(x)
|
||||
{
|
||||
$switch $typeof(x):
|
||||
$case float:
|
||||
@@ -537,7 +554,7 @@ macro is_nan(x)
|
||||
<*
|
||||
@require values::@is_promotable_to_float(x) : `The input must be a float`
|
||||
*>
|
||||
macro is_inf(x)
|
||||
macro bool is_inf(x)
|
||||
{
|
||||
$switch $typeof(x):
|
||||
$case float:
|
||||
@@ -578,7 +595,7 @@ macro normalize(x) @private
|
||||
@param then_value : "The vector to get elements from where the mask is 'true'"
|
||||
@param else_value : "The vector to get elements from where the mask is 'false'"
|
||||
@require values::@is_vector(then_value) && values::@is_vector(else_value) : "'Then' and 'else' must be vectors."
|
||||
@require values::@is_same_type(then_value, else_value) : "'Then' and 'else' vectors must be of the same type."
|
||||
@require @typematch(then_value, else_value) : "'Then' and 'else' vectors must be of the same type."
|
||||
@require then_value.len == mask.len : "Mask and selected vectors must be of the same width."
|
||||
|
||||
@return "a vector of the same type as then/else"
|
||||
@@ -1118,27 +1135,27 @@ macro overflow_mul_helper(x, y) @local
|
||||
<*
|
||||
@param [&out] out : "Where the result of the addition is stored"
|
||||
@return "Whether the addition resulted in an integer overflow"
|
||||
@require values::@is_same_type(a, b) : "a and b must be the same type"
|
||||
@require @typematch(a, b) : "a and b must be the same type"
|
||||
@require values::@is_flat_intlike(a) &&& values::@is_flat_intlike(b) : "a and b must both be integer or integer vector based"
|
||||
@require $defined(*out) &&& values::@is_same_type(*out, a) : "out must be a pointer of the same type as a and b"
|
||||
@require $defined(*out) &&& @typematch(*out, a) : "out must be a pointer of the same type as a and b"
|
||||
*>
|
||||
macro bool overflow_add(a, b, out) => $$overflow_add(a, b, out);
|
||||
|
||||
<*
|
||||
@param [&out] out : "Where the result of the subtraction is stored"
|
||||
@return "Whether the subtraction resulted in an integer overflow"
|
||||
@require values::@is_same_type(a, b) : "a and b must be the same type"
|
||||
@require @typematch(a, b) : "a and b must be the same type"
|
||||
@require values::@is_flat_intlike(a) &&& values::@is_flat_intlike(b) : "a and b must both be integer or integer vector based"
|
||||
@require $defined(*out) &&& values::@is_same_type(*out, a) : "out must be a pointer of the same type as a and b"
|
||||
@require $defined(*out) &&& @typematch(*out, a) : "out must be a pointer of the same type as a and b"
|
||||
*>
|
||||
macro bool overflow_sub(a, b, out) => $$overflow_sub(a, b, out);
|
||||
|
||||
<*
|
||||
@param [&out] out : "Where the result of the multiplication is stored"
|
||||
@return "Whether the multiplication resulted in an integer overflow"
|
||||
@require values::@is_same_type(a, b) : "a and b must be the same type"
|
||||
@require @typematch(a, b) : "a and b must be the same type"
|
||||
@require values::@is_flat_intlike(a) &&& values::@is_flat_intlike(b) : "a and b must both be integer or integer vector based"
|
||||
@require $defined(*out) &&& values::@is_same_type(*out, a) : "out must be a pointer of the same type as a and b"
|
||||
@require $defined(*out) &&& @typematch(*out, a) : "out must be a pointer of the same type as a and b"
|
||||
*>
|
||||
macro bool overflow_mul(a, b, out) => $$overflow_mul(a, b, out);
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ const float EXPF_P2 = -2.7777778450e-03f;
|
||||
const float EXPF_P3 = 6.6137559770e-05f;
|
||||
const float EXPF_P4 = -1.6533901999e-06f;
|
||||
|
||||
fn double exp(double x) @extern("exp")
|
||||
fn double exp(double x) @extern("exp") @nostrip @weak
|
||||
{
|
||||
if (x != x) return x;
|
||||
if (x == double.inf) return double.inf;
|
||||
@@ -38,7 +38,7 @@ fn double exp(double x) @extern("exp")
|
||||
return ldexp(exp_r, (int)k);
|
||||
}
|
||||
|
||||
fn float expf(float x) @extern("expf")
|
||||
fn float expf(float x) @extern("expf") @nostrip @weak
|
||||
{
|
||||
if (x != x) return x;
|
||||
if (x == float.inf) return float.inf;
|
||||
|
||||
@@ -19,7 +19,7 @@ const float LOGF_L4 = 2.4279078841e-01f;
|
||||
const double SQRT2 = 1.41421356237309504880;
|
||||
const float SQRT2F = 1.41421356237309504880f;
|
||||
|
||||
fn double log(double x) @extern("log")
|
||||
fn double log(double x) @extern("log") @nostrip @weak
|
||||
{
|
||||
if (x != x) return x;
|
||||
if (x < 0.0) return double.nan;
|
||||
@@ -50,7 +50,7 @@ fn double log(double x) @extern("log")
|
||||
return k * LOG_LN2_HI - ((hfsq - (s * (hfsq + r) + k * LOG_LN2_LO)) - f);
|
||||
}
|
||||
|
||||
fn float logf(float x) @extern("logf")
|
||||
fn float logf(float x) @extern("logf") @nostrip @weak
|
||||
{
|
||||
if (x != x) return x;
|
||||
if (x < 0.0f) return float.nan;
|
||||
|
||||
@@ -55,7 +55,8 @@ struct Exp2Data @private
|
||||
double shift;
|
||||
double negln2hiN;
|
||||
double negln2loN;
|
||||
double[4] poly; // Last four coefficients.
|
||||
<* Last four coefficients. *>
|
||||
double[4] poly;
|
||||
double exp2_shift;
|
||||
double[EXP2_POLY_ORDER] exp2_poly;
|
||||
ulong[2 * EXP_DATA_WIDTH] tab;
|
||||
@@ -253,4 +254,4 @@ macro force_eval_add(x, v)
|
||||
{
|
||||
$typeof(x) temp @noinit;
|
||||
@volatile_store(temp, x + v);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,8 @@ fn float _roundf(float x) @extern("roundf") @weak @nostrip
|
||||
uint u = bitcast(x, uint);
|
||||
int e = (u >> 23) & 0xff;
|
||||
if (e >= 0x7f + 23) return x;
|
||||
if (u >> 31) x = -x;
|
||||
bool signed = u >> 31 != 0;
|
||||
if (signed) x = -x;
|
||||
if (e < 0x7f - 1)
|
||||
{
|
||||
force_eval_add(x, TOINTF);
|
||||
@@ -47,7 +48,7 @@ fn float _roundf(float x) @extern("roundf") @weak @nostrip
|
||||
default:
|
||||
y = y + x;
|
||||
}
|
||||
if (u >> 31) y = -y;
|
||||
if (signed) y = -y;
|
||||
return y;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ module std::math;
|
||||
|
||||
// Predefined quaternion aliases.
|
||||
|
||||
alias Quaternionf = Quaternion {float};
|
||||
alias Quaternion = Quaternion {double};
|
||||
alias Quaternionf = QuaternionNumber {float};
|
||||
alias Quaternion = QuaternionNumber {double};
|
||||
alias QUATERNION_IDENTITY @builtin = quaternion::IDENTITY {double};
|
||||
alias QUATERNIONF_IDENTITY @builtin = quaternion::IDENTITY {float};
|
||||
|
||||
@@ -15,7 +15,7 @@ alias QUATERNIONF_IDENTITY @builtin = quaternion::IDENTITY {float};
|
||||
|
||||
module std::math::quaternion {Real};
|
||||
import std::math::vector;
|
||||
union Quaternion
|
||||
union QuaternionNumber
|
||||
{
|
||||
struct
|
||||
{
|
||||
@@ -24,22 +24,22 @@ union Quaternion
|
||||
Real[<4>] v;
|
||||
}
|
||||
|
||||
const Quaternion IDENTITY = { 0, 0, 0, 1 };
|
||||
const QuaternionNumber IDENTITY = { 0, 0, 0, 1 };
|
||||
|
||||
macro Quaternion Quaternion.add(self, Quaternion b) @operator(+) => { .v = self.v + b.v };
|
||||
macro Quaternion Quaternion.add_each(self, Real b) => { .v = self.v + b };
|
||||
macro Quaternion Quaternion.sub(self, Quaternion b) @operator(-) => { .v = self.v - b.v };
|
||||
macro Quaternion Quaternion.negate(self) @operator(-) => { .v = -self.v };
|
||||
macro Quaternion Quaternion.sub_each(self, Real b) => { .v = self.v - b };
|
||||
macro Quaternion Quaternion.scale(self, Real s) @operator_s(*) => { .v = self.v * s };
|
||||
macro Quaternion Quaternion.normalize(self) => { .v = self.v.normalize() };
|
||||
macro Real Quaternion.length(self) => self.v.length();
|
||||
macro Quaternion Quaternion.lerp(self, Quaternion q2, Real amount) => { .v = self.v.lerp(q2.v, amount) };
|
||||
macro Matrix4f Quaternion.to_matrixf(&self) => into_matrix(self, Matrix4f);
|
||||
macro Matrix4 Quaternion.to_matrix(&self) => into_matrix(self, Matrix4);
|
||||
fn Quaternion Quaternion.nlerp(self, Quaternion q2, Real amount) => { .v = self.v.lerp(q2.v, amount).normalize() };
|
||||
macro QuaternionNumber QuaternionNumber.add(self, QuaternionNumber b) @operator(+) => { .v = self.v + b.v };
|
||||
macro QuaternionNumber QuaternionNumber.add_each(self, Real b) => { .v = self.v + b };
|
||||
macro QuaternionNumber QuaternionNumber.sub(self, QuaternionNumber b) @operator(-) => { .v = self.v - b.v };
|
||||
macro QuaternionNumber QuaternionNumber.negate(self) @operator(-) => { .v = -self.v };
|
||||
macro QuaternionNumber QuaternionNumber.sub_each(self, Real b) => { .v = self.v - b };
|
||||
macro QuaternionNumber QuaternionNumber.scale(self, Real s) @operator_s(*) => { .v = self.v * s };
|
||||
macro QuaternionNumber QuaternionNumber.normalize(self) => { .v = self.v.normalize() };
|
||||
macro Real QuaternionNumber.length(self) => self.v.length();
|
||||
macro QuaternionNumber QuaternionNumber.lerp(self, QuaternionNumber q2, Real amount) => { .v = self.v.lerp(q2.v, amount) };
|
||||
macro Matrix4f QuaternionNumber.to_matrixf(&self) => into_matrix(self, Matrix4f);
|
||||
macro Matrix4 QuaternionNumber.to_matrix(&self) => into_matrix(self, Matrix4);
|
||||
fn QuaternionNumber QuaternionNumber.nlerp(self, QuaternionNumber q2, Real amount) => { .v = self.v.lerp(q2.v, amount).normalize() };
|
||||
|
||||
fn Quaternion Quaternion.invert(self)
|
||||
fn QuaternionNumber QuaternionNumber.invert(self)
|
||||
{
|
||||
Real length_sq = self.v.dot(self.v);
|
||||
if (length_sq <= 0) return self;
|
||||
@@ -47,9 +47,9 @@ fn Quaternion Quaternion.invert(self)
|
||||
return { self.v[0] * -inv_length, self.v[1] * -inv_length, self.v[2] * -inv_length, self.v[3] * inv_length };
|
||||
}
|
||||
|
||||
fn Quaternion Quaternion.slerp(self, Quaternion q2, Real amount)
|
||||
fn QuaternionNumber QuaternionNumber.slerp(self, QuaternionNumber q2, Real amount)
|
||||
{
|
||||
Quaternion result = {};
|
||||
QuaternionNumber result = {};
|
||||
|
||||
Real[<4>] q2v = q2.v;
|
||||
Real cos_half_theta = self.v.dot(q2v);
|
||||
@@ -76,7 +76,7 @@ fn Quaternion Quaternion.slerp(self, Quaternion q2, Real amount)
|
||||
return { .v = q1v * ratio_a + q2v * ratio_b };
|
||||
}
|
||||
|
||||
fn Quaternion Quaternion.mul(self, Quaternion b) @operator(*)
|
||||
fn QuaternionNumber QuaternionNumber.mul(self, QuaternionNumber b) @operator(*)
|
||||
{
|
||||
return { self.i * b.l + self.l * b.i + self.j * b.k - self.k * b.j,
|
||||
self.j * b.l + self.l * b.j + self.k * b.i - self.i * b.k,
|
||||
@@ -84,9 +84,9 @@ fn Quaternion Quaternion.mul(self, Quaternion b) @operator(*)
|
||||
self.l * b.l - self.i * b.i - self.j * self.j - self.k * self.k };
|
||||
}
|
||||
|
||||
macro into_matrix(Quaternion* q, $Type) @private
|
||||
macro into_matrix(QuaternionNumber* q, $Type) @private
|
||||
{
|
||||
Quaternion rotation = q.normalize();
|
||||
QuaternionNumber rotation = q.normalize();
|
||||
var x = rotation.i;
|
||||
var y = rotation.j;
|
||||
var z = rotation.k;
|
||||
|
||||
@@ -133,14 +133,14 @@ macro double next_double(random)
|
||||
}
|
||||
|
||||
// True if the value is a Random.
|
||||
macro bool is_random(random) => @assignable_to(random, Random);
|
||||
macro bool is_random(random) => $defined(Random r = random);
|
||||
|
||||
macro uint128 @long_to_int128(#function) => (uint128)#function << 64 + #function;
|
||||
macro ulong @int_to_long(#function) => (ulong)#function << 32 + #function;
|
||||
macro uint @short_to_int(#function) => (uint)#function << 16 + #function;
|
||||
macro ushort @char_to_short(#function) => (ushort)#function << 8 + #function;
|
||||
|
||||
macro @random_value_to_bytes(#function, char[] bytes)
|
||||
macro void @random_value_to_bytes(#function, char[] bytes)
|
||||
{
|
||||
var $byte_size = $sizeof(#function());
|
||||
usz len = bytes.len;
|
||||
@@ -174,7 +174,7 @@ interface Random
|
||||
}
|
||||
|
||||
|
||||
macro init_default_random() @private
|
||||
macro void init_default_random() @private
|
||||
{
|
||||
if (!default_random_initialized)
|
||||
{
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
module std::math::random;
|
||||
import std::hash::fnv32a, std::time;
|
||||
import std::hash::a5hash, std::time;
|
||||
|
||||
const ODD_PHI64 @local = 0x9e3779b97f4a7c15;
|
||||
const MUL_MCG64 @local = 0xf1357aea2e62a9c5;
|
||||
@@ -69,12 +69,11 @@ fn void seeder(char[] input, char[] out_buffer)
|
||||
|
||||
macro uint hash(value) @local
|
||||
{
|
||||
return fnv32a::hash(&&bitcast(value, char[$sizeof(value)]));
|
||||
return (uint)a5hash::hash(&&bitcast(value, char[$sizeof(value)]));
|
||||
}
|
||||
|
||||
fn char[8 * 4] entropy() @if(!env::WASM_NOLIBC)
|
||||
fn char[8 * 4] entropy() @if(!env::FREESTANDING_WASM)
|
||||
{
|
||||
|
||||
void* addr = malloc(1);
|
||||
free(addr);
|
||||
static uint random_int;
|
||||
@@ -92,7 +91,7 @@ fn char[8 * 4] entropy() @if(!env::WASM_NOLIBC)
|
||||
return bitcast(entropy_data, char[8 * 4]);
|
||||
}
|
||||
|
||||
fn char[8 * 4] entropy() @if(env::WASM_NOLIBC)
|
||||
fn char[8 * 4] entropy() @if(env::FREESTANDING_WASM)
|
||||
{
|
||||
static uint random_int;
|
||||
random_int += 0xedf19156;
|
||||
|
||||
@@ -1,15 +1,78 @@
|
||||
module std::math::math_rt;
|
||||
|
||||
fn float __roundevenf(float f) @extern("roundevenf") @weak @nostrip
|
||||
const double TOINT = 1 / math::DOUBLE_EPSILON;
|
||||
const float TOINTF = (float)(1 / math::FLOAT_EPSILON);
|
||||
macro force_eval_add(x, v)
|
||||
{
|
||||
// Slow implementation
|
||||
return math::round(f / 2) * 2;
|
||||
$typeof(x) temp @noinit;
|
||||
@volatile_store(temp, x + v);
|
||||
}
|
||||
|
||||
fn double __roundeven(double d) @extern("roundeven") @weak @nostrip
|
||||
fn double __roundeven(double x) @extern("roundeven") @weak @nostrip
|
||||
{
|
||||
// Slow implementation
|
||||
return math::round(d / 2) * 2;
|
||||
ulong u = bitcast(x, ulong);
|
||||
int e = (int)((u >> 52) & 0x7ff);
|
||||
if (e >= 0x3ff + 52) return x;
|
||||
bool signed = u >> 63 != 0;
|
||||
if (signed) x = -x;
|
||||
if (e < 0x3ff - 1)
|
||||
{
|
||||
/* raise inexact if x!=0 */
|
||||
force_eval_add(x, TOINT);
|
||||
return 0 * x;
|
||||
}
|
||||
double y = (x + TOINT) - TOINT - x;
|
||||
switch
|
||||
{
|
||||
case y > 0.5:
|
||||
y = y + x - 1;
|
||||
case y < -0.5:
|
||||
y = y + x + 1;
|
||||
case y == 0.5 || y == -0.5:
|
||||
if (u & 1)
|
||||
{
|
||||
y = x + (y > 0 ? y + 1 : y - 1);
|
||||
break;
|
||||
}
|
||||
nextcase;
|
||||
default:
|
||||
y = y + x;
|
||||
}
|
||||
if (signed) y = -y;
|
||||
return y;
|
||||
}
|
||||
|
||||
fn float __roundevenf(float x) @extern("roundevenf") @weak @nostrip
|
||||
{
|
||||
uint u = bitcast(x, uint);
|
||||
int e = (u >> 23) & 0xff;
|
||||
if (e >= 0x7f + 23) return x;
|
||||
bool signed = u >> 31 != 0;
|
||||
if (signed) x = -x;
|
||||
if (e < 0x7f - 1)
|
||||
{
|
||||
force_eval_add(x, TOINTF);
|
||||
return 0 * x;
|
||||
}
|
||||
float y = (x + TOINTF) - TOINTF - x;
|
||||
switch
|
||||
{
|
||||
case y > 0.5f:
|
||||
y = y + x - 1;
|
||||
case y < -0.5f:
|
||||
y = y + x + 1;
|
||||
case y == 0.5f || y == -0.5f:
|
||||
if (u & 1)
|
||||
{
|
||||
y = x + (y > 0.0f ? y + 1.0f : y - 1.0f);
|
||||
break;
|
||||
}
|
||||
nextcase;
|
||||
default:
|
||||
y = y + x;
|
||||
}
|
||||
if (signed) y = -y;
|
||||
return y;
|
||||
}
|
||||
|
||||
fn double __powidf2(double a, int b) @extern("__powidf2") @weak @nostrip
|
||||
|
||||
@@ -3,25 +3,25 @@
|
||||
module std::math::vector;
|
||||
import std::math;
|
||||
|
||||
macro double[<*>].sq_magnitude(self) => self.dot(self);
|
||||
macro float[<*>].sq_magnitude(self) => self.dot(self);
|
||||
macro double double[<*>].sq_magnitude(self) => self.dot(self);
|
||||
macro float float[<*>].sq_magnitude(self) => self.dot(self);
|
||||
|
||||
macro double[<*>].distance_sq(self, double[<*>] v2) => (self - v2).sq_magnitude();
|
||||
macro float[<*>].distance_sq(self, float[<*>] v2) => (self - v2).sq_magnitude();
|
||||
macro double double[<*>].distance_sq(self, double[<*>] v2) => (self - v2).sq_magnitude();
|
||||
macro float float[<*>].distance_sq(self, float[<*>] v2) => (self - v2).sq_magnitude();
|
||||
|
||||
macro float[<2>].transform(self, Matrix4f mat) => transform2(self, mat);
|
||||
macro float[<2>].rotate(self, float angle) => rotate(self, angle);
|
||||
macro float[<2>].angle(self, float[<2>] v2) => math::atan2(v2.y, v2.x) - math::atan2(self.y, self.x);
|
||||
macro float[<2>] float[<2>].transform(self, Matrix4f mat) => transform2(self, mat);
|
||||
macro float[<2>] float[<2>].rotate(self, float angle) => rotate(self, angle);
|
||||
macro float[<2>] float[<2>].angle(self, float[<2>] v2) => math::atan2(v2.y, v2.x) - math::atan2(self.y, self.x);
|
||||
|
||||
macro double[<2>].transform(self, Matrix4 mat) => transform2(self, mat);
|
||||
macro double[<2>].rotate(self, double angle) => rotate(self, angle);
|
||||
macro double[<2>].angle(self, double[<2>] v2) => math::atan2(v2.y, v2.x) - math::atan2(self.y, self.x);
|
||||
macro double[<2>] double[<2>].transform(self, Matrix4 mat) => transform2(self, mat);
|
||||
macro double[<2>] double[<2>].rotate(self, double angle) => rotate(self, angle);
|
||||
macro double[<2>] double[<2>].angle(self, double[<2>] v2) => math::atan2(v2.y, v2.x) - math::atan2(self.y, self.x);
|
||||
|
||||
macro float[<*>].clamp_mag(self, float min, float max) => clamp_magnitude(self, min, max);
|
||||
macro double[<*>].clamp_mag(self, double min, double max) => clamp_magnitude(self, min, max);
|
||||
macro float[<*>] float[<*>].clamp_mag(self, float min, float max) => clamp_magnitude(self, min, max);
|
||||
macro double[<*>] double[<*>].clamp_mag(self, double min, double max) => clamp_magnitude(self, min, max);
|
||||
|
||||
macro float[<*>].towards(self, float[<*>] target, float max_distance) => towards(self, target, max_distance);
|
||||
macro double[<*>].towards(self, double[<*>] target, double max_distance) => towards(self, target, max_distance);
|
||||
macro float[<*>] float[<*>].towards(self, float[<*>] target, float max_distance) => towards(self, target, max_distance);
|
||||
macro double[<*>] double[<*>].towards(self, double[<*>] target, double max_distance) => towards(self, target, max_distance);
|
||||
|
||||
fn float[<3>] float[<3>].cross(self, float[<3>] v2) => cross3(self, v2);
|
||||
fn double[<3>] double[<3>].cross(self, double[<3>] v2) => cross3(self, v2);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
module std::net::os;
|
||||
const bool SUPPORTS_INET = env::LIBC && (env::WIN32 || env::DARWIN || env::LINUX || env::ANDROID);
|
||||
const bool SUPPORTS_INET = env::LIBC && (env::WIN32 || env::DARWIN || env::LINUX || env::ANDROID || env::OPENBSD);
|
||||
|
||||
typedef AIFamily = CInt;
|
||||
typedef AIProtocol = CInt;
|
||||
@@ -23,7 +23,7 @@ struct AddrInfo
|
||||
ZString ai_canonname;
|
||||
SockAddrPtr ai_addr;
|
||||
}
|
||||
struct @if(env::LINUX)
|
||||
struct @if(env::LINUX || env::OPENBSD)
|
||||
{
|
||||
SockAddrPtr ai_addr;
|
||||
ZString ai_canonname;
|
||||
@@ -53,12 +53,41 @@ const AIFamily AF_APPLETALK = PLATFORM_AF_APPLETALK;
|
||||
|
||||
const O_NONBLOCK = PLATFORM_O_NONBLOCK;
|
||||
|
||||
extern fn CInt getaddrinfo(ZString nodename, ZString servname, AddrInfo* hints, AddrInfo** res) @if(SUPPORTS_INET);
|
||||
extern fn void freeaddrinfo(AddrInfo* res) @if(SUPPORTS_INET);
|
||||
extern fn CInt setsockopt(NativeSocket socket, CInt level, CInt optname, void* optval, Socklen_t optlen) @if(SUPPORTS_INET);
|
||||
extern fn CInt getsockopt(NativeSocket socket, CInt level, CInt optname, void* optval, Socklen_t optlen) @if(SUPPORTS_INET);
|
||||
|
||||
module std::net::os @if(!env::LIBC || !(env::WIN32 || env::DARWIN || env::LINUX || env::ANDROID));
|
||||
<*
|
||||
The getaddrinfo() function is used to get a list of IP addresses and port numbers for host hostname and service servname.
|
||||
|
||||
@param [in] nodename
|
||||
@param [in] servname
|
||||
@param [in] hints
|
||||
@param [out] res
|
||||
@require (void*)nodename || (void*)servname : "One the names must be non-null"
|
||||
*>
|
||||
extern fn CInt getaddrinfo(ZString nodename, ZString servname, AddrInfo* hints, AddrInfo** res) @if(SUPPORTS_INET);
|
||||
|
||||
<*
|
||||
freeaddrinfo() frees an AddrInfo created by getaddrinfo.
|
||||
|
||||
@param [&in] res
|
||||
*>
|
||||
extern fn void freeaddrinfo(AddrInfo* res) @if(SUPPORTS_INET);
|
||||
|
||||
<*
|
||||
Set options on a socket.
|
||||
|
||||
@param [out] optval
|
||||
*>
|
||||
extern fn CInt setsockopt(NativeSocket socket, CInt level, CInt optname, void* optval, Socklen_t optlen) @if(SUPPORTS_INET);
|
||||
|
||||
<*
|
||||
Get options on a socket
|
||||
|
||||
@param [in] optval
|
||||
@param [inout] optlen
|
||||
*>
|
||||
extern fn CInt getsockopt(NativeSocket socket, CInt level, CInt optname, void* optval, Socklen_t* optlen) @if(SUPPORTS_INET);
|
||||
|
||||
module std::net::os @if(!env::LIBC || !(env::WIN32 || env::DARWIN || env::LINUX || env::ANDROID || env::OPENBSD));
|
||||
|
||||
const AIFamily PLATFORM_AF_INET6 = 0;
|
||||
const AIFamily PLATFORM_AF_IPX = 0;
|
||||
|
||||
@@ -67,7 +67,7 @@ const int SO_WANTMORE = 0x4000; // Apple: Give hint when more data re
|
||||
const int SO_WANTOOBFLAG = 0x8000; // Apple: Want OOB in MSG_FLAG on receive
|
||||
|
||||
const int SO_SNDBUF = 0x1001; // Send buffer size
|
||||
const int SO_RCVBUF = 0x1002; // Recieve buffer size
|
||||
const int SO_RCVBUF = 0x1002; // Receive buffer size
|
||||
const int SO_SNDLOWAT = 0x1003; // Send low-water mark
|
||||
const int SO_RCVLOWAT = 0x1004; // Receive low-water mark
|
||||
const int SO_SNDTIMEO = 0x1005; // Send timeout
|
||||
@@ -94,4 +94,4 @@ const CShort POLLATTRIB = 0x0400; // file attributes may have changed
|
||||
const CShort POLLNLINK = 0x0800; // (un)link/rename may have happened
|
||||
const CShort POLLWRITE = 0x1000; // file's contents may have changed
|
||||
|
||||
const CInt MSG_PEEK = 0x0002;
|
||||
const CInt MSG_PEEK = 0x0002;
|
||||
|
||||
109
lib/std/net/os/openbsd.c3
Normal file
109
lib/std/net/os/openbsd.c3
Normal file
@@ -0,0 +1,109 @@
|
||||
module std::net::os @if(env::OPENBSD);
|
||||
import libc;
|
||||
|
||||
// for most of this, see OpenBSD /usr/include/sys/socket.h
|
||||
const AIFlags AI_EXT = 0x8;
|
||||
const AIFlags AI_NUMERICSERV = 0x10;
|
||||
const AIFlags AI_FQDN = 0x20;
|
||||
const AIFlags AI_ADDRCONFIG = 0x40;
|
||||
|
||||
const AIFamily PLATFORM_AF_LOCAL = AF_UNIX; // draft POSIX compatibility
|
||||
const AIFamily PLATFORM_AF_IMPLINK = 3; // arpanet imp addresses
|
||||
const AIFamily PLATFORM_AF_PUP = 4; // pup protocols: e.g. BSP
|
||||
const AIFamily PLATFORM_AF_CHAOS = 5; // mit CHAOS protocols
|
||||
const AIFamily PLATFORM_AF_NS = 6; // XEROX NS protocols
|
||||
const AIFamily PLATFORM_AF_ISO = 7; // ISO protocols
|
||||
const AIFamily PLATFORM_AF_OSI = PLATFORM_AF_ISO;
|
||||
const AIFamily PLATFORM_AF_ECMA = 8; // european computer manufacturers
|
||||
const AIFamily PLATFORM_AF_DATAKIT = 9; // datakit protocols
|
||||
const AIFamily PLATFORM_AF_CCITT = 10; // CCITT protocols, X.25 etc
|
||||
const AIFamily PLATFORM_AF_SNA = 11; // IBM SNA
|
||||
const AIFamily PLATFORM_AF_DECNET = 12; // DECnet
|
||||
const AIFamily PLATFORM_AF_DLI = 13; // DEC Direct data link interface
|
||||
const AIFamily PLATFORM_AF_LAT = 14; // LAT
|
||||
const AIFamily PLATFORM_AF_HYLINK = 15; // NSC Hyperchannel
|
||||
const AIFamily PLATFORM_AF_APPLETALK = 16; // Apple Talk
|
||||
const AIFamily PLATFORM_AF_ROUTE = 17; // Internal Routing Protocol
|
||||
const AIFamily PLATFORM_AF_LINK = 18; // Link layer interface
|
||||
const AIFamily PLATFORM_PSEUDO_AF_XTP = 19; // eXpress Transfer Protocol (no AF)
|
||||
const AIFamily PLATFORM_AF_COIP = 20; // connection-oriented IP, aka ST II
|
||||
const AIFamily PLATFORM_AF_CNT = 21; // Computer Network Technology
|
||||
const AIFamily PLATFORM_PSEUDO_AF_RTIP = 22; // Help Identify RTIP packets
|
||||
const AIFamily PLATFORM_AF_IPX = 23; // Novell Internet Protocol
|
||||
const AIFamily PLATFORM_AF_INET6 = 24; // IPv6
|
||||
const AIFamily PLATFORM_PSEUDO_AF_PIP = 25; // Help Identify PIP packets
|
||||
const AIFamily PLATFORM_AF_ISDN = 26; // Integrated Services Digital Network*/
|
||||
const AIFamily PLATFORM_AF_E164 = PLATFORM_AF_ISDN; // CCITT E.164 recommendation
|
||||
const AIFamily PLATFORM_AF_NATM = 27; // native ATM access
|
||||
const AIFamily PLATFORM_AF_ENCAP = 28;
|
||||
const AIFamily PLATFORM_AF_SIP = 29; // Simple Internet Protocol
|
||||
const AIFamily PLATFORM_AF_KEY = 30;
|
||||
const AIFamily PLATFORM_PSEUDO_AF_HDRCMPLT = 31; // Used by BPF to not rewrite headers in interface output routine
|
||||
const AIFamily PLATFORM_AF_BLUETOOTH = 32; // Bluetooth
|
||||
const AIFamily PLATFORM_AF_MPLS = 33; // MPLS
|
||||
const AIFamily PLATFORM_PSEUDO_AF_PFLOW = 34; // pflow
|
||||
const AIFamily PLATFORM_PSEUDO_AF_PIPEX = 35; // PIPEX
|
||||
const AIFamily PLATFORM_AF_FRAME = 36; // frame (Ethernet) sockets
|
||||
const AIFamily PLATFORM_AF_MAX = 37;
|
||||
|
||||
// see: https://github.com/openbsd/src/blob/1ca4dc792538d17b4c2bd71550f68070505fd7b9/sys/sys/socket.h#L157
|
||||
const int SOL_SOCKET = 0xFFFF;
|
||||
|
||||
const int SO_DEBUG = 0x0001; // turn on debugging info recording
|
||||
const int SO_ACCEPTCONN = 0x0002; // socket has had listen()
|
||||
const int SO_REUSEADDR = 0x0004; // allow local address reuse
|
||||
const int SO_KEEPALIVE = 0x0008; // keep connections alive
|
||||
const int SO_DONTROUTE = 0x0010; // just use interface addresses
|
||||
const int SO_BROADCAST = 0x0020; // permit sending of broadcast msgs
|
||||
const int SO_USELOOPBACK = 0x0040; // bypass hardware when possible
|
||||
const int SO_LINGER = 0x0080; // linger on close if data present
|
||||
const int SO_OOBINLINE = 0x0100; // leave received OOB data in line
|
||||
const int SO_REUSEPORT = 0x0200; // allow local address & port reuse
|
||||
const int SO_TIMESTAMP = 0x0800; // timestamp received dgram traffic
|
||||
const int SO_BINDANY = 0x1000; // allow bind to any address
|
||||
const int SO_ZEROIZE = 0x2000; // zero out all mbufs sent over socket
|
||||
|
||||
// additional
|
||||
const int SO_SNDBUF = 0x1001; // send buffer size
|
||||
const int SO_RCVBUF = 0x1002; // receive buffer size
|
||||
const int SO_SNDLOWAT = 0x1003; // send low-water mark
|
||||
const int SO_RCVLOWAT = 0x1004; // receive low-water mark
|
||||
const int SO_SNDTIMEO = 0x1005; // send timeout
|
||||
const int SO_RCVTIMEO = 0x1006; // receive timeout
|
||||
const int SO_ERROR = 0x1007; // get error status and clear
|
||||
const int SO_TYPE = 0x1008; // get socket type
|
||||
const int SO_NETPROC = 0x1020; // multiplex; network processing
|
||||
const int SO_RTABLE = 0x1021; // routing table to be used
|
||||
const int SO_PEERCRED = 0x1022; // get connect-time credentials
|
||||
const int SO_SPLICE = 0x1023; // splice data to other socket
|
||||
const int SO_DOMAIN = 0x1024; // get socket domain
|
||||
const int SO_PROTOCOL = 0x1025; // get socket protocol
|
||||
|
||||
// POLLIN through POLLNVAL are predefined by lib/std/net/os/posix.c3
|
||||
const CUShort POLLRDNORM = 0x0040;
|
||||
const CUShort POLLNORM = POLLRDNORM;
|
||||
const CUShort POLLWRNORM = POLLOUT;
|
||||
const CUShort POLLRDBAND = 0x0080;
|
||||
const CUShort POLLWRBAND = 0x0100;
|
||||
|
||||
const CInt MSG_OOB = 0x1; // process out-of-band data
|
||||
const CInt MSG_PEEK = 0x2; // peek at incoming message
|
||||
const CInt MSG_DONTROUTE = 0x4; // send without using routing tables
|
||||
const CInt MSG_EOR = 0x8; // data completes record
|
||||
const CInt MSG_TRUNC = 0x10; // data discarded before delivery
|
||||
const CInt MSG_CTRUNC = 0x20; // control data lost before delivery
|
||||
const CInt MSG_WAITALL = 0x40; // wait for full request or error
|
||||
const CInt MSG_DONTWAIT = 0x80; // this message should be nonblocking
|
||||
const CInt MSG_BCAST = 0x100; // this message rec'd as broadcast
|
||||
const CInt MSG_MCAST = 0x200; // this message rec'd as multicast
|
||||
const CInt MSG_NOSIGNAL = 0x400; // do not send SIGPIPE
|
||||
const CInt MSG_CMSG_CLOEXEC = 0x800; // set FD_CLOEXEC on received fds
|
||||
const CInt MSG_WAITFORONE = 0x1000; // nonblocking but wait for one msg
|
||||
|
||||
// socket creation options
|
||||
const SOCK_CLOEXEC = 0x8000; // set FD_CLOEXEC
|
||||
const SOCK_NONBLOCK = 0x4000; // set O_NONBLOCK
|
||||
const SOCK_NONBLOCK_INHERIT = 0x2000; // inherit O_NONBLOCK from listener
|
||||
const SOCK_DNS = 0x1000; // set SS_DNS
|
||||
|
||||
const PLATFORM_O_NONBLOCK = SOCK_NONBLOCK;
|
||||
@@ -25,31 +25,54 @@ macro void @loop_over_ai(AddrInfo* ai; @body(NativeSocket fd, AddrInfo* ai))
|
||||
|
||||
const Duration POLL_FOREVER = (Duration)-1;
|
||||
|
||||
typedef PollSubscribes = ushort;
|
||||
typedef PollEvents = ushort;
|
||||
enum PollSubscribe : const ushort
|
||||
{
|
||||
ANY_READ = os::POLLIN,
|
||||
PRIO_READ = os::POLLPRI,
|
||||
OOB_READ = os::POLLRDBAND,
|
||||
READ = os::POLLRDNORM,
|
||||
ANY_WRITE = os::POLLOUT,
|
||||
OOB_WRITE = os::POLLWRBAND,
|
||||
WRITE = os::POLLWRNORM,
|
||||
}
|
||||
|
||||
const PollSubscribes SUBSCRIBE_ANY_READ = os::POLLIN;
|
||||
const PollSubscribes SUBSCRIBE_PRIO_READ = os::POLLPRI;
|
||||
const PollSubscribes SUBSCRIBE_OOB_READ = os::POLLRDBAND;
|
||||
const PollSubscribes SUBSCRIBE_READ = os::POLLRDNORM;
|
||||
const PollSubscribes SUBSCRIBE_ANY_WRITE = os::POLLOUT;
|
||||
const PollSubscribes SUBSCRIBE_OOB_WRITE = os::POLLWRBAND;
|
||||
const PollSubscribes SUBSCRIBE_WRITE = os::POLLWRNORM;
|
||||
const PollSubscribe SUBSCRIBE_ANY_READ = (PollSubscribe)os::POLLIN;
|
||||
const PollSubscribe SUBSCRIBE_PRIO_READ = (PollSubscribe)os::POLLPRI;
|
||||
const PollSubscribe SUBSCRIBE_OOB_READ = (PollSubscribe)os::POLLRDBAND;
|
||||
const PollSubscribe SUBSCRIBE_READ = (PollSubscribe)os::POLLRDNORM;
|
||||
const PollSubscribe SUBSCRIBE_ANY_WRITE = (PollSubscribe)os::POLLOUT;
|
||||
const PollSubscribe SUBSCRIBE_OOB_WRITE = (PollSubscribe)os::POLLWRBAND;
|
||||
const PollSubscribe SUBSCRIBE_WRITE = (PollSubscribe)os::POLLWRNORM;
|
||||
|
||||
const PollEvents POLL_EVENT_READ_PRIO = os::POLLPRI;
|
||||
const PollEvents POLL_EVENT_READ_OOB = os::POLLRDBAND;
|
||||
const PollEvents POLL_EVENT_READ = os::POLLRDNORM;
|
||||
const PollEvents POLL_EVENT_WRITE_OOB = os::POLLWRBAND;
|
||||
const PollEvents POLL_EVENT_WRITE = os::POLLWRNORM;
|
||||
const PollEvents POLL_EVENT_DISCONNECT = os::POLLHUP;
|
||||
const PollEvents POLL_EVENT_ERROR = os::POLLERR;
|
||||
const PollEvents POLL_EVENT_INVALID = os::POLLNVAL;
|
||||
enum PollEvent : const ushort
|
||||
{
|
||||
READ_PRIO = os::POLLPRI,
|
||||
READ_OOB = os::POLLRDBAND,
|
||||
READ = os::POLLRDNORM,
|
||||
WRITE_OOB = os::POLLWRBAND,
|
||||
WRITE = os::POLLWRNORM,
|
||||
DISCONNECT = os::POLLHUP,
|
||||
ERROR = os::POLLERR,
|
||||
INVALID = os::POLLNVAL,
|
||||
}
|
||||
|
||||
const PollEvent POLL_EVENT_READ_PRIO = (PollEvent)os::POLLPRI;
|
||||
const PollEvent POLL_EVENT_READ_OOB = (PollEvent)os::POLLRDBAND;
|
||||
const PollEvent POLL_EVENT_READ = (PollEvent)os::POLLRDNORM;
|
||||
const PollEvent POLL_EVENT_WRITE_OOB = (PollEvent)os::POLLWRBAND;
|
||||
const PollEvent POLL_EVENT_WRITE = (PollEvent)os::POLLWRNORM;
|
||||
const PollEvent POLL_EVENT_DISCONNECT = (PollEvent)os::POLLHUP;
|
||||
const PollEvent POLL_EVENT_ERROR = (PollEvent)os::POLLERR;
|
||||
const PollEvent POLL_EVENT_INVALID = (PollEvent)os::POLLNVAL;
|
||||
|
||||
alias PollSubscribes @deprecated("Use PollSubscribe") = PollSubscribe;
|
||||
alias PollEvents @deprecated("Use PollEvent") = PollEvent;
|
||||
|
||||
struct Poll
|
||||
{
|
||||
NativeSocket socket;
|
||||
PollSubscribes events;
|
||||
PollEvents revents;
|
||||
PollSubscribe events;
|
||||
PollEvent revents;
|
||||
}
|
||||
|
||||
<*
|
||||
@@ -116,7 +139,8 @@ fn void? Socket.set_option(&self, SocketOption option, bool value)
|
||||
fn bool? Socket.get_option(&self, SocketOption option)
|
||||
{
|
||||
CInt flag;
|
||||
int errcode = os::setsockopt(self.sock, os::SOL_SOCKET, option.value, &flag, CInt.sizeof);
|
||||
Socklen_t socklen = CInt.sizeof;
|
||||
int errcode = os::getsockopt(self.sock, os::SOL_SOCKET, option.value, &flag, &socklen);
|
||||
if (errcode != 0) return SOCKOPT_FAILED?;
|
||||
return (bool)flag;
|
||||
}
|
||||
@@ -169,9 +193,9 @@ fn usz? Socket.peek(&self, char[] bytes) @dynamic
|
||||
|
||||
enum SocketShutdownHow : (CInt native_value)
|
||||
{
|
||||
RECEIVE = @select(env::WIN32, libc::SD_RECEIVE, libc::SHUT_RD),
|
||||
SEND = @select(env::WIN32, libc::SD_SEND, libc::SHUT_WR),
|
||||
BOTH = @select(env::WIN32, libc::SD_BOTH, libc::SHUT_RDWR),
|
||||
RECEIVE = env::WIN32 ??? libc::SD_RECEIVE : libc::SHUT_RD,
|
||||
SEND = env::WIN32 ??? libc::SD_SEND : libc::SHUT_WR,
|
||||
BOTH = env::WIN32 ??? libc::SD_BOTH : libc::SHUT_RDWR,
|
||||
}
|
||||
|
||||
fn void? Socket.shutdown(&self, SocketShutdownHow how)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
module std::net @if(os::SUPPORTS_INET);
|
||||
import std::time, libc, std::os;
|
||||
|
||||
macro apply_sockoptions(sockfd, options) @private
|
||||
macro void? apply_sockoptions(sockfd, options) @private
|
||||
{
|
||||
Socket sock = { .sock = sockfd };
|
||||
foreach (o : options) sock.set_option(o, true)!;
|
||||
@@ -96,7 +96,7 @@ fn Socket? connect_async_from_addrinfo(AddrInfo* addrinfo, SocketOption[] option
|
||||
return os::socket_error()?;
|
||||
}
|
||||
|
||||
macro @network_loop_over_ai(network, host, port; @body(fd, ai)) @private
|
||||
macro void @network_loop_over_ai(network, host, port; @body(fd, ai)) @private
|
||||
{
|
||||
AddrInfo* ai = network.addrinfo(host, port)!;
|
||||
AddrInfo* first = ai;
|
||||
|
||||
141
lib/std/os/linux/epoll.c3
Normal file
141
lib/std/os/linux/epoll.c3
Normal file
@@ -0,0 +1,141 @@
|
||||
module std::os::linux @if(env::LINUX);
|
||||
import libc;
|
||||
|
||||
// https://github.com/bminor/glibc/blob/master/sysdeps/unix/sysv/linux/sys/epoll.h
|
||||
const uint EPOLLIN = EpollEvents.EPOLLIN;
|
||||
const uint EPOLLPRI = EpollEvents.EPOLLPRI;
|
||||
const uint EPOLLOUT = EpollEvents.EPOLLOUT;
|
||||
const uint EPOLLRDNORM = EpollEvents.EPOLLRDNORM;
|
||||
const uint EPOLLRDBAND = EpollEvents.EPOLLRDBAND;
|
||||
const uint EPOLLWRNORM = EpollEvents.EPOLLWRNORM;
|
||||
const uint EPOLLWRBAND = EpollEvents.EPOLLWRBAND;
|
||||
const uint EPOLLMSG = EpollEvents.EPOLLMSG;
|
||||
const uint EPOLLERR = EpollEvents.EPOLLERR;
|
||||
const uint EPOLLHUP = EpollEvents.EPOLLHUP;
|
||||
const uint EPOLLRDHUP = EpollEvents.EPOLLRDHUP;
|
||||
const uint EPOLLEXCLUSIVE = EpollEvents.EPOLLEXCLUSIVE;
|
||||
const uint EPOLLWAKEUP = EpollEvents.EPOLLWAKEUP;
|
||||
const uint EPOLLONESHOT = EpollEvents.EPOLLONESHOT;
|
||||
const uint EPOLLET = EpollEvents.EPOLLET;
|
||||
|
||||
enum EpollEvents: const inline uint
|
||||
{
|
||||
EPOLLIN = 0x001,
|
||||
EPOLLPRI = 0x002,
|
||||
EPOLLOUT = 0x004,
|
||||
EPOLLRDNORM = 0x040,
|
||||
EPOLLRDBAND = 0x080,
|
||||
EPOLLWRNORM = 0x100,
|
||||
EPOLLWRBAND = 0x200,
|
||||
EPOLLMSG = 0x400,
|
||||
EPOLLERR = 0x008,
|
||||
EPOLLHUP = 0x010,
|
||||
EPOLLRDHUP = 0x2000,
|
||||
EPOLLEXCLUSIVE = 1u << 28,
|
||||
EPOLLWAKEUP = 1u << 29,
|
||||
EPOLLONESHOT = 1u << 30,
|
||||
EPOLLET = 1u << 31
|
||||
}
|
||||
|
||||
/* Valid opcodes ( "op" parameter ) to issue to epoll_ctl(). */
|
||||
const uint EPOLL_CTL_ADD = 1; /* Add a file descriptor to the interface. */
|
||||
const uint EPOLL_CTL_DEL = 2; /* Remove a file descriptor from the interface. */
|
||||
const uint EPOLL_CTL_MOD = 3; /* Change file descriptor epoll_event structure. */
|
||||
|
||||
union EpollData
|
||||
{
|
||||
void* ptr;
|
||||
int fd;
|
||||
uint u32;
|
||||
ulong u64;
|
||||
}
|
||||
|
||||
struct EpollEvent @packed
|
||||
{
|
||||
<* Epoll events *>
|
||||
uint events;
|
||||
<* User data variable *>
|
||||
EpollData data;
|
||||
}
|
||||
|
||||
struct EpollParams
|
||||
{
|
||||
uint busy_poll_usecs;
|
||||
ushort busy_poll_budget;
|
||||
char prefer_busy_poll;
|
||||
|
||||
<* pad the struct to a multiple of 64bits *>
|
||||
char __pad;
|
||||
}
|
||||
|
||||
// https://github.com/MatthiasWM/mosrun/blob/master/scratchpad.txt#L330-L348
|
||||
/*
|
||||
* Ioctl's have the command encoded in the lower word,
|
||||
* and the size of any in or out parameters in the upper
|
||||
* word. The high 2 bits of the upper word are used
|
||||
* to encode the in/out status of the parameter; for now
|
||||
* we restrict parameters to at most 256 bytes (disklabels are 216 bytes).
|
||||
*/
|
||||
const IOCPARM_MASK = 0xff; /* parameters must be < 256 bytes */
|
||||
const IOC_VOID = 0x20000000; /* no parameters */
|
||||
const IOC_OUT = 0x40000000; /* copy out parameters */
|
||||
const IOC_IN = 0x80000000; /* copy in parameters */
|
||||
const IOC_INOUT = (IOC_IN|IOC_OUT);
|
||||
|
||||
macro ulong @ioctl_IO ($x,$y) @const => (IOC_VOID | (($x)<<8)|y);
|
||||
macro ulong @ioctl_IOR ($x,$y,$Type) @const => (IOC_OUT |(($Type.sizeof&IOCPARM_MASK)<<16)|(($x)<<8)|$y);
|
||||
macro ulong @ioctl_IOW ($x,$y,$Type) @const => (IOC_IN |(($Type.sizeof&IOCPARM_MASK)<<16)|(($x)<<8)|$y);
|
||||
/* this should be _IORW, but stdio got there first */
|
||||
macro ulong @ioctl_IOWR ($x,$y,$Type) @const => (IOC_INOUT|(($Type.sizeof&IOCPARM_MASK)<<16)|(($x)<<8)|$y);
|
||||
macro ulong @ioctl_ION ($x,$y,$n) @const => (IOC_INOUT|((($n)&IOCPARM_MASK)<<16)|(($x)<<8)|y);
|
||||
|
||||
// https://github.com/bminor/glibc/blob/master/sysdeps/unix/sysv/linux/sys/epoll.h
|
||||
const EPOLL_IOC_TYPE = 0x8A;
|
||||
const EPIOCSPARAMS = @ioctl_IOW(EPOLL_IOC_TYPE, 0x01, EpollParams);
|
||||
const EPIOCGPARAMS = @ioctl_IOR(EPOLL_IOC_TYPE, 0x02, EpollParams);
|
||||
|
||||
<*
|
||||
* Creates an epoll instance. Returns an fd for the new instance.
|
||||
* The "size" parameter is a hint specifying the number of file
|
||||
* descriptors to be associated with the new instance.
|
||||
* The fd returned by epoll_create() should be closed with close().
|
||||
*>
|
||||
extern fn int epoll_create(int);
|
||||
|
||||
<*
|
||||
* Same as epoll_create but with an FLAGS parameter.
|
||||
* The unused SIZE parameter has been dropped.
|
||||
*>
|
||||
extern fn int epoll_create1(int);
|
||||
|
||||
<*
|
||||
* Manipulate an epoll instance "epfd". Returns 0 in case of success,
|
||||
* -1 in case of error ( the "errno" variable will contain the
|
||||
* specific error code ) The "op" parameter is one of the EPOLL_CTL_*
|
||||
* constants defined above. The "fd" parameter is the target of the
|
||||
* operation. The "event" parameter describes which events the caller
|
||||
* is interested in and any associated user data.
|
||||
*>
|
||||
extern fn int epoll_ctl (int, int, int, EpollEvent*);
|
||||
|
||||
<*
|
||||
* Wait for events on an epoll instance "epfd". Returns the number of
|
||||
* triggered events returned in "events" buffer. Or -1 in case of
|
||||
* error with the "errno" variable set to the specific error code. The
|
||||
* "events" parameter is a buffer that will contain triggered
|
||||
* events. The "maxevents" is the maximum number of events to be
|
||||
* returned ( usually size of "events" ). The "timeout" parameter
|
||||
* specifies the maximum wait time in milliseconds (-1 == infinite).
|
||||
*>
|
||||
extern fn int epoll_wait (int, EpollEvent*, int, int);
|
||||
|
||||
<*
|
||||
* Same as epoll_wait, but the thread's signal mask is temporarily
|
||||
* and atomically replaced with the one provided as parameter.
|
||||
*>
|
||||
extern fn int epoll_pwait (int, EpollEvent*, int, int, Sigset_t*);
|
||||
|
||||
<*
|
||||
* Same as epoll_pwait, but the timeout as a timespec.
|
||||
*>
|
||||
extern fn int epoll_pwait2 (int, EpollEvent*, int, TimeSpec*, Sigset_t*);
|
||||
@@ -1,5 +1,34 @@
|
||||
module std::os::linux @if(env::LINUX);
|
||||
import libc, std::os, std::io, std::collections::list;
|
||||
import libc, std::os, std::io, std::collections::list, std::net::os;
|
||||
|
||||
// https://man7.org/linux/man-pages/man3/inet_ntop.3.html
|
||||
extern fn char** inet_ntop(int, void*, char*, Socklen_t);
|
||||
|
||||
// https://linux.die.net/man/3/ntohs
|
||||
<*
|
||||
* The htonl() function converts the unsigned integer hostlong from host byte order to network byte order.
|
||||
*>
|
||||
extern fn uint htonl(uint hostlong);
|
||||
<*
|
||||
* The htons() function converts the unsigned short integer hostshort from host byte order to network byte order.
|
||||
*>
|
||||
extern fn ushort htons(ushort hostshort);
|
||||
<*
|
||||
* The ntohl() function converts the unsigned integer netlong from network byte order to host byte order.
|
||||
*>
|
||||
extern fn uint ntohl(uint netlong);
|
||||
<*
|
||||
* The ntohs() function converts the unsigned short integer netshort from network byte order to host byte order.
|
||||
*>
|
||||
extern fn ushort ntohs(ushort netshort);
|
||||
|
||||
// https://man7.org/linux/man-pages/man3/bzero.3.html
|
||||
<*
|
||||
* The bzero() function erases the data in the n bytes of the memory
|
||||
* starting at the location pointed to by s, by writing zeros (bytes
|
||||
* containing '\0') to that area.
|
||||
*>
|
||||
extern fn void bzero(char*, usz);
|
||||
|
||||
extern fn isz readlink(ZString path, char* buf, usz bufsize);
|
||||
|
||||
@@ -89,6 +118,35 @@ struct Linux_Dl_info
|
||||
void* dli_saddr; /* Address of nearest symbol */
|
||||
}
|
||||
|
||||
alias Dl_iterate_phdr_callback64 = fn CInt(Linux_dl_phdr_info_64*, usz, void*);
|
||||
alias Dl_iterate_phdr_callback32 = fn CInt(Linux_dl_phdr_info_32*, usz, void*);
|
||||
extern fn CInt dl_iterate_phdr64(Dl_iterate_phdr_callback64 callback, void* data);
|
||||
extern fn CInt dl_iterate_phdr32(Dl_iterate_phdr_callback32 callback, void* data);
|
||||
|
||||
struct Linux_dl_phdr_info_64
|
||||
{
|
||||
Elf64_Addr dlpi_addr;
|
||||
ZString dlpi_name;
|
||||
Elf64_Phdr* dlpi_phdr;
|
||||
Elf64_Half dlpi_phnum;
|
||||
ulong dlpi_adds;
|
||||
ulong dlpi_subs;
|
||||
usz dlpi_tsl_modid;
|
||||
void* dlpi_tls_data;
|
||||
}
|
||||
|
||||
struct Linux_dl_phdr_info_32
|
||||
{
|
||||
Elf32_Addr dlpi_addr;
|
||||
ZString dlpi_name;
|
||||
Elf32_Phdr* dlpi_phdr;
|
||||
Elf32_Half dlpi_phnum;
|
||||
ulong dlpi_adds;
|
||||
ulong dlpi_subs;
|
||||
usz dlpi_tsl_modid;
|
||||
void* dlpi_tls_data;
|
||||
}
|
||||
|
||||
fn ulong? elf_module_image_base(String path) @local
|
||||
{
|
||||
File file = file::open(path, "rb")!;
|
||||
|
||||
@@ -1,11 +1,18 @@
|
||||
module std::os::macos::cf @if(env::DARWIN) @link(env::DARWIN, "CoreFoundation.framework");
|
||||
|
||||
typedef CFArrayRef = void*;
|
||||
typedef CFArray = inline CFType;
|
||||
alias CFArrayRef = CFArray*;
|
||||
typedef CFArrayCallBacksRef = void*;
|
||||
typedef CFMutableArrayRef = void*;
|
||||
typedef CFMutableArray = inline CFArray;
|
||||
typedef CFMutableArrayRef = CFMutableArray*;
|
||||
|
||||
extern fn CFIndex CFArray.getCount(&self) @extern("CFArrayGetCount");
|
||||
extern fn void* CFArray.getValueAtIndex(&self, CFIndex i) @extern("CFArrayGetValueAtIndex");
|
||||
|
||||
extern fn CFArrayRef macos_CFArrayCreate(CFAllocatorRef allocator, void** values, CFIndex num_values, CFArrayCallBacksRef callBacks) @extern("CFArrayCreate") @builtin;
|
||||
extern fn CFArrayRef macos_CFArrayCopy(CFAllocatorRef allocator, CFArrayRef array) @extern("CFArrayCopy") @builtin;
|
||||
extern fn CFIndex macos_CFArrayGetCount(CFArrayRef array) @extern("CFArrayGetCount") @builtin;
|
||||
extern fn void macos_CFArrayAppendArray(CFMutableArrayRef theArray, CFArrayRef otherArray, CFRange otherRange) @extern("CFArrayAppendArray") @builtin;
|
||||
extern fn void CFMutableArray.appendArray(&self, CFArrayRef otherArray, CFRange otherRange) @extern("CFArrayAppendArray");
|
||||
extern fn void CFMutableArray.appendValue(&self, void *value) @extern("CFArrayAppendValue");
|
||||
|
||||
extern fn CFMutableArrayRef macos_CFArrayCreateMutable(CFAllocatorRef allocator, CFIndex capacity, CFArrayCallBacksRef callBacks) @extern("CFArrayCreateMutable") @builtin;
|
||||
extern fn void macos_CFArrayAppendValue(CFMutableArrayRef theArray, void *value) @extern("CFArrayAppendValue") @builtin;
|
||||
|
||||
|
||||
@@ -1,13 +1,42 @@
|
||||
module std::os::macos::cf @if(env::DARWIN) @link(env::DARWIN, "CoreFoundation.framework");
|
||||
|
||||
typedef CFTypeRef = void*;
|
||||
typedef CFType = void;
|
||||
typedef CFTypeRef = CFType*;
|
||||
alias CFIndex = isz;
|
||||
|
||||
typedef CFString = inline CFType;
|
||||
alias CFStringRef = CFString*;
|
||||
|
||||
struct CFRange
|
||||
{
|
||||
CFIndex location;
|
||||
CFIndex length;
|
||||
}
|
||||
|
||||
extern fn CFTypeRef macos_CFRetain(CFTypeRef cf) @extern("CFRetain") @builtin;
|
||||
extern fn void macos_CFRelease(CFTypeRef cf) @extern("CFRelease") @builtin;
|
||||
extern fn ZString CFString.getCStringPtr(&self, CFStringEncoding encoding) @extern("CFStringGetCStringPtr");
|
||||
extern fn ZString CFString.getCString(&self, char* buffer, usz len, CFStringEncoding encoding) @extern("CFStringGetCString");
|
||||
|
||||
extern fn CFTypeRef CFType.retain(&self) @extern("CFRetain");
|
||||
extern fn void CFType.release(&self) @extern("CFRelease");
|
||||
extern fn CFIndex CFType.getRetainCount(&self) @extern("CFGetRetainCount");
|
||||
|
||||
enum CFStringEncoding : const uint
|
||||
{
|
||||
INVALID_ID = 0xffffffffU,
|
||||
MAC_ROMAN = 0,
|
||||
WINDOWS_LATIN_1 = 0x0500,
|
||||
ISO_LATIM_1 = 0x0201,
|
||||
NEXT_STEP_LATIN = 0x0B01,
|
||||
ASCII = 0x0600,
|
||||
UNICODE = 0x0100,
|
||||
UTF8 = 0x08000100,
|
||||
NON_LOSSY_ASCII = 0x0BFF,
|
||||
|
||||
UTF16 = 0x0100,
|
||||
UTF16BE = 0x10000100,
|
||||
UTF16LE = 0x14000100,
|
||||
|
||||
UTF32 = 0x0c000100,
|
||||
UTF32BE = 0x18000100,
|
||||
UTF32LE = 0x1c000100
|
||||
}
|
||||
|
||||
@@ -1,2 +1,60 @@
|
||||
module std::os::darwin @if(env::DARWIN);
|
||||
module std::os::darwin @if(env::DARWIN) @link("Foundation.framework");
|
||||
import std::os::macos::cf, std::os::macos::objc, std::io;
|
||||
|
||||
enum NSSearchPathDomainMask : const NSUInteger
|
||||
{
|
||||
USER = 1,
|
||||
LOCAL = 2,
|
||||
NETWORK = 4,
|
||||
SYSTEM = 8,
|
||||
ALL = 0x0ffff
|
||||
}
|
||||
|
||||
enum NSSearchPathDirectory : const NSUInteger
|
||||
{
|
||||
APPLICATION = 1,
|
||||
DEMO_APPLICATION,
|
||||
DEVELOPER_APPLICATION,
|
||||
ADMIN_APPLICATION,
|
||||
LIBRARY,
|
||||
DEVELOPER,
|
||||
USER,
|
||||
DOCUMENTATION,
|
||||
DOCUMENT,
|
||||
CORE_SERVICE,
|
||||
AUTOSAVED_INFORMATION,
|
||||
DESKTOP = 12,
|
||||
CACHES = 13,
|
||||
APPLICATION_SUPPORT = 14,
|
||||
DOWNLOADS = 15,
|
||||
INPUT_METHODS = 16,
|
||||
MOVIES = 17,
|
||||
MUSIC = 18,
|
||||
PICTURES = 19,
|
||||
PRINTER_DESCRIPTION = 20,
|
||||
SHARED_PUBLIC = 21,
|
||||
PREFERENCE_PANES = 22,
|
||||
APPLICATION_SCRIPTS = 23,
|
||||
ITEM_REPLACEMENT = 99,
|
||||
ALL_APPLICATIONS = 100,
|
||||
ALL_LIBRARIES = 101,
|
||||
TRASH = 102,
|
||||
}
|
||||
|
||||
// real signature in Foundation
|
||||
extern fn CFArrayRef nsSearchPathForDirectoriesInDomains(NSSearchPathDirectory directory, NSSearchPathDomainMask domainMask, bool expandTilde) @extern("NSSearchPathForDirectoriesInDomains");
|
||||
|
||||
|
||||
|
||||
fn String? find_first_directory_temp(NSSearchPathDirectory directory, NSSearchPathDomainMask domainMask)
|
||||
{
|
||||
objc::@autoreleasepool()
|
||||
{
|
||||
CFArrayRef arr = nsSearchPathForDirectoriesInDomains(directory, domainMask, true);
|
||||
if (!arr.getCount()) return io::PATH_COULD_NOT_BE_FOUND?;
|
||||
CFStringRef str = (CFStringRef)arr.getValueAtIndex(0);
|
||||
char* buffer = tmalloc(2048);
|
||||
if (!str.getCString(buffer, 2048, UTF8)) return io::PATH_COULD_NOT_BE_FOUND?;
|
||||
return ((ZString)buffer).str_view();
|
||||
};
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user