Compare commits

..

1 Commits

Author SHA1 Message Date
Christoffer Lerno
ba785c3cb0 Const Enums From / to ordinal using casts is back. Add "--use-old-enums", deprecating lookup 2025-06-29 01:20:32 +02:00
642 changed files with 10112 additions and 34987 deletions

View File

@@ -8,11 +8,10 @@ on:
env:
LLVM_RELEASE_VERSION_WINDOWS: 18
LLVM_RELEASE_VERSION_MAC: 18
LLVM_RELEASE_VERSION_LINUX: 19
LLVM_RELEASE_VERSION_OPENBSD: 19
LLVM_RELEASE_VERSION_UBUNTU22: 19
LLVM_DEV_VERSION: 22
LLVM_RELEASE_VERSION_MAC: 17
LLVM_RELEASE_VERSION_LINUX: 17
LLVM_RELEASE_VERSION_UBUNTU22: 17
LLVM_DEV_VERSION: 21
jobs:
build-msvc:
@@ -53,9 +52,9 @@ jobs:
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" x64
cd resources/testproject
..\..\build\${{ matrix.build_type }}\c3c.exe -vvv --emit-llvm run hello_world_win32 --trust=full
dir out\llvm\windows-x64
dir build\llvm\windows-x64
..\..\build\${{ matrix.build_type }}\c3c.exe clean
dir out\llvm\windows-x64
dir build\llvm\windows-x64
- name: Build testproject lib
@@ -88,15 +87,15 @@ 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
..\build\${{ matrix.build_type }}\c3c.exe compile-test unit -O1
- 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: |
@@ -230,14 +229,13 @@ jobs:
fail-fast: false
matrix:
build_type: [Release, Debug]
llvm_version: [17, 18, 19, 20, 21, 22]
llvm_version: [17, 18, 19, 20]
steps:
- uses: actions/checkout@v4
- name: Install common deps
run: |
sudo apt-get update
sudo apt-get install zlib1g zlib1g-dev python3 ninja-build curl libcurl4-openssl-dev
sudo apt-get install zlib1g zlib1g-dev python3 ninja-build curl
- name: Install Clang ${{matrix.llvm_version}}
run: |
@@ -275,7 +273,6 @@ jobs:
-DCMAKE_OBJCOPY=llvm-objcopy-${{matrix.llvm_version}} \
-DCMAKE_STRIP=llvm-strip-${{matrix.llvm_version}} \
-DCMAKE_DLLTOOL=llvm-dlltool-${{matrix.llvm_version}} \
-DLLVM_ENABLE_LIBXML2=OFF \
-DC3_LLVM_VERSION=${{matrix.llvm_version}}
cmake --build build
- name: CMake18
@@ -290,7 +287,6 @@ jobs:
-DCMAKE_OBJCOPY=llvm-objcopy-${{matrix.llvm_version}} \
-DCMAKE_STRIP=llvm-strip-${{matrix.llvm_version}} \
-DCMAKE_DLLTOOL=llvm-dlltool-${{matrix.llvm_version}} \
-DLLVM_ENABLE_LIBXML2=OFF \
-DC3_LLVM_VERSION=${{matrix.llvm_version}}.1
cmake --build build
@@ -349,7 +345,7 @@ jobs:
- name: Compile run unit tests
run: |
cd test
../build/c3c compile-test unit -D SLOW_TESTS
../build/c3c compile-test unit
- name: Build testproject
run: |
@@ -382,10 +378,6 @@ jobs:
./build/c3c init myproject
ls myproject
- name: Vendor-fetch
run: |
./build/c3c vendor-fetch raylib55
- name: run compiler tests
run: |
cd test
@@ -421,8 +413,7 @@ jobs:
- uses: actions/checkout@v4
- name: Install common deps
run: |
sudo apt-get update
sudo apt-get install zlib1g zlib1g-dev python3 ninja-build curl libcurl4-openssl-dev
sudo apt-get install zlib1g zlib1g-dev python3 ninja-build curl
- name: Install Clang ${{matrix.llvm_version}}
run: |
@@ -448,7 +439,6 @@ jobs:
-DCMAKE_OBJCOPY=llvm-objcopy-${{matrix.llvm_version}} \
-DCMAKE_STRIP=llvm-strip-${{matrix.llvm_version}} \
-DCMAKE_DLLTOOL=llvm-dlltool-${{matrix.llvm_version}} \
-DLLVM_ENABLE_LIBXML2=OFF \
-DC3_LLVM_VERSION=${{matrix.llvm_version}}
cmake --build build
- name: CMake
@@ -463,10 +453,8 @@ jobs:
-DCMAKE_OBJCOPY=llvm-objcopy-${{matrix.llvm_version}} \
-DCMAKE_STRIP=llvm-strip-${{matrix.llvm_version}} \
-DCMAKE_DLLTOOL=llvm-dlltool-${{matrix.llvm_version}} \
-DLLVM_ENABLE_LIBXML2=OFF \
-DC3_LLVM_VERSION=${{matrix.llvm_version}}.1
cmake --build build
- name: Compile and run some examples
run: |
cd resources
@@ -501,7 +489,7 @@ jobs:
- name: Compile run unit tests
run: |
cd test
../build/c3c compile-test unit --sanitize=address -D SLOW_TESTS
../build/c3c compile-test unit --sanitize=address
- name: Build testproject
run: |
@@ -513,10 +501,6 @@ 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
@@ -599,7 +583,7 @@ jobs:
- name: Compile run unit tests
run: |
cd test
../build/c3c compile-test unit -D SLOW_TESTS
../build/c3c compile-test unit
- name: Build testproject
run: |
@@ -681,7 +665,7 @@ jobs:
- name: Compile run unit tests
run: |
cd test
../build/c3c compile-test unit -O1 -D SLOW_TESTS
../build/c3c compile-test unit -O1
- name: Test WASM
run: |
@@ -763,119 +747,6 @@ jobs:
nix build -L ".#c3c-checks"
fi
build-openbsd:
runs-on: ubuntu-latest
strategy:
# Don't abort runners if a single one fails
fail-fast: false
matrix:
build_type: [Release, Debug]
steps:
- uses: actions/checkout@v4
- name: OpenBSD VM
uses: vmactions/openbsd-vm@main
with:
prepare: |
pkg_add cmake llvm-20.1.8p1 ninja
run: |
echo "CMake"
cmake -B build \
-G Ninja \
-DCMAKE_BUILD_TYPE=${{matrix.build_type}} \
-DLLVM_ENABLE_LIBXML2=OFF \
-DC3_LLVM_VERSION=${LLVM_RELEASE_VERSION_OPENBSD}
cmake --build build
echo "Compile and run some examples"
cd resources
../build/c3c compile examples/base64.c3
../build/c3c compile examples/binarydigits.c3
../build/c3c compile examples/brainfk.c3
../build/c3c compile examples/factorial_macro.c3
../build/c3c compile examples/fasta.c3
../build/c3c compile examples/gameoflife.c3
../build/c3c compile examples/hash.c3
../build/c3c compile-only examples/levenshtein.c3
../build/c3c compile examples/load_world.c3
../build/c3c compile-only examples/map.c3
../build/c3c compile examples/mandelbrot.c3
../build/c3c compile examples/plus_minus.c3
../build/c3c compile examples/nbodies.c3
../build/c3c compile examples/spectralnorm.c3
../build/c3c compile examples/swap.c3
../build/c3c compile examples/contextfree/boolerr.c3
../build/c3c compile examples/contextfree/dynscope.c3
../build/c3c compile examples/contextfree/guess_number.c3
../build/c3c compile examples/contextfree/multi.c3
../build/c3c compile examples/contextfree/cleanup.c3
../build/c3c compile-run examples/hello_world_many.c3
../build/c3c compile-run examples/time.c3
../build/c3c compile-run examples/fannkuch-redux.c3
../build/c3c compile-run examples/contextfree/boolerr.c3
../build/c3c compile-run examples/load_world.c3
../build/c3c compile-run examples/process.c3
../build/c3c compile-run examples/ls.c3
../build/c3c compile-run examples/args.c3 -- foo -bar "baz baz"
cd ..
echo "Compile and run dynlib-test"
cd resources/examples/dynlib-test
../../../build/c3c -vv dynamic-lib add.c3
mv add.so libadd.so
cc test.c -L. -ladd -Wl,-rpath=.
./a.out
../../../build/c3c compile-run test.c3 -L . -l add -z -Wl,-rpath=.
cd ../../../
echo "Compile and run staticlib-test"
cd resources/examples/staticlib-test
../../../build/c3c -vv static-lib add.c3
mv add.a libadd.a
cc test.c -L. -ladd
./a.out
../../../build/c3c compile-run test.c3 -L . -l add
cd ../../../
echo "Compile run unit tests"
cd test
../build/c3c --max-mem 128 compile-test unit -D SLOW_TESTS
cd ..
echo "Build testproject"
cd resources/testproject
../../build/c3c run -vvv --trust=full
cd ../../
echo "Test WASM"
cd resources/testfragments
../../build/c3c compile --target wasm32 -g0 --no-entry -Os wasm4.c3
cd ../../
echo "Build testproject direct linker"
cd resources/testproject
../../build/c3c run -vvv --linker=builtin --trust=full
cd ../../
echo "Init a library & a project"
./build/c3c init-lib mylib
ls mylib.c3l
./build/c3c init myproject
ls myproject
echo "run compiler tests"
cd test
../build/c3c --max-mem 128 compile-run -O1 src/test_suite_runner.c3 -- ../build/c3c test_suite/
cd ..
- name: bundle_output
run: |
mkdir c3
cp -r lib c3
cp msvc_build_libraries.py c3
cp build/c3c c3
cp README.md c3
cp releasenotes.md c3
tar czf c3-openbsd-${{matrix.build_type}}.tar.gz c3
- name: upload artifacts
uses: actions/upload-artifact@v4
with:
name: c3-openbsd-${{matrix.build_type}}
path: c3-openbsd-${{matrix.build_type}}.tar.gz
release:
runs-on: ubuntu-22.04
needs: [build-msvc, build-linux, build-mac, build-linux-ubuntu22]
@@ -898,13 +769,11 @@ jobs:
- run: zip -r c3-windows-debug.zip c3-windows-Debug
- run: mv c3-linux-Release/c3-linux-Release.tar.gz c3-linux-Release/c3-linux.tar.gz
- run: mv c3-linux-Debug/c3-linux-Debug.tar.gz c3-linux-Debug/c3-linux-debug.tar.gz
- run: mv c3-openbsd-Release/c3-openbsd-Release.tar.gz c3-openbsd-Release/c3-openbsd.tar.gz
- run: mv c3-openbsd-Debug/c3-openbsd-Debug.tar.gz c3-openbsd-Debug/c3-openbsd-debug.tar.gz
- run: mv c3-ubuntu-22-Release/c3-ubuntu-22-Release.tar.gz c3-ubuntu-22-Release/c3-ubuntu-22.tar.gz
- run: mv c3-ubuntu-22-Debug/c3-ubuntu-22-Debug.tar.gz c3-ubuntu-22-Debug/c3-ubuntu-22-debug.tar.gz
- run: mv c3-macos-Release/c3-macos-Release.zip c3-macos-Release/c3-macos.zip
- run: mv c3-macos-Debug/c3-macos-Debug.zip c3-macos-Debug/c3-macos-debug.zip
- run: gh release delete latest-prerelease-tag --cleanup-tag -y || true
- run: gh release delete latest-prerelease --cleanup-tag -y || true
- run: echo "RELEASE_NAME=latest-prerelease-$(date +'%Y%m%d-%H%M')" >> $GITHUB_ENV
- id: create_release
@@ -912,7 +781,7 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: latest-prerelease-tag
tag_name: latest-prerelease
name: ${{ env.RELEASE_NAME }}
draft: false
prerelease: true
@@ -921,8 +790,6 @@ jobs:
c3-windows-debug.zip
c3-linux-Release/c3-linux.tar.gz
c3-linux-Debug/c3-linux-debug.tar.gz
c3-openbsd-Release/c3-openbsd.tar.gz
c3-openbsd-Debug/c3-openbsd-debug.tar.gz
c3-ubuntu-22-Release/c3-ubuntu-22.tar.gz
c3-ubuntu-22-Debug/c3-ubuntu-22-debug.tar.gz
c3-macos-Release/c3-macos.zip

10
.gitignore vendored
View File

@@ -1,7 +1,5 @@
# Prerequisites
*.d
testrun
benchmarkrun
# Object files
*.o
@@ -9,8 +7,6 @@ benchmarkrun
*.obj
*.elf
*.ll
*.wasm
*.s
# Linker output
*.ilk
@@ -80,10 +76,8 @@ TAGS
/.cache/
/compile_commands.json
# Nix
# 'nix build' resulting symlink
result
/.envrc
/.direnv/
# macOS
.DS_Store
@@ -91,4 +85,4 @@ result
# tests
/test/tmp/*
/test/testrun
/test/test_suite_runner
/test/test_suite_runner

View File

@@ -1,13 +1,5 @@
cmake_minimum_required(VERSION 3.20)
set(C3_LLVM_MIN_VERSION 17)
set(C3_LLVM_MAX_VERSION 22)
set(C3_LLVM_DEFAULT_VERSION 19)
if (CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_CURRENT_BINARY_DIR)
message(FATAL_ERROR "In-tree build detected, please build in a separate directory")
endif()
# Grab the version
file(READ "src/version.h" ver)
if (NOT ${ver} MATCHES "COMPILER_VERSION \"([0-9]+.[0-9]+.[0-9]+)\"")
@@ -15,20 +7,8 @@ if (NOT ${ver} MATCHES "COMPILER_VERSION \"([0-9]+.[0-9]+.[0-9]+)\"")
endif()
# Set the project and version
project(c3c VERSION ${CMAKE_MATCH_1} LANGUAGES C CXX)
message("Configuring C3C ${CMAKE_PROJECT_VERSION} for ${CMAKE_SYSTEM_NAME}")
# Helper functions
function(c3_print_variables)
set(msg "")
foreach(var ${ARGN})
if(msg)
string(APPEND msg " ; ")
endif()
string(APPEND msg "${c3_print_prefix}${var}=\"${${var}}\"")
endforeach()
message(STATUS "${msg}")
endfunction()
project(c3c VERSION ${CMAKE_MATCH_1})
message("C3C version: ${CMAKE_PROJECT_VERSION}")
# Avoid warning for FetchContent
if (CMAKE_VERSION VERSION_GREATER_EQUAL "3.24.0")
@@ -36,7 +16,7 @@ if (CMAKE_VERSION VERSION_GREATER_EQUAL "3.24.0")
endif()
if (NOT DEFINED CMAKE_INSTALL_LIBDIR)
if (WIN32)
if (MSVC)
set(CMAKE_INSTALL_LIBDIR "c:\\c3c\\lib")
set(CMAKE_INSTALL_BINDIR "c:\\c3c")
else ()
@@ -56,42 +36,36 @@ set(CMAKE_FIND_PACKAGE_SORT_DIRECTION DEC)
set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 17)
# Use /MT or /MTd
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
if(MSVC)
message(STATUS "MSVC version ${MSVC_VERSION}")
add_compile_options(/utf-8)
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /O2 /EHsc /utf-8")
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} /O2 /EHsc /utf-8")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /Od /Zi /EHa /utf-8")
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} /Od /Zi /EHa /utf-8")
else()
add_compile_options(-gdwarf-3 -fno-exceptions)
#add_compile_options(-fsanitize=address,undefined)
#add_link_options(-fsanitize=address,undefined)
if (true)
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O0 -fno-exceptions")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -gdwarf-3 -O0 -fno-exceptions")
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -gdwarf-3 -O3 -fno-exceptions")
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -gdwarf-3 -fno-exceptions")
else()
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -gdwarf-3 -O3 -fsanitize=undefined,address -fno-exceptions")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -gdwarf-3 -O1 -fsanitize=undefined,address -fno-exceptions")
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -gdwarf-3 -O3 -fsanitize=undefined,address -fno-exceptions")
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -gdwarf-3 -O1 -fsanitize=undefined,address -fno-exceptions")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=undefined,address -fno-exceptions")
endif()
endif()
# 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(TCC_LIB_PATH "/usr/lib/tcc/libtcc1.a" CACHE STRING "Use custom libtcc1.a path")
option(C3_LINK_DYNAMIC "link dynamically with LLVM/LLD libs")
set(C3_OPTIONS
C3_LINK_DYNAMIC
C3_WITH_LLVM
C3_LLVM_VERSION
C3_USE_MIMALLOC
C3_MIMALLOC_TAG
C3_USE_TB
C3_LLD_DIR
C3_ENABLE_CLANGD_LSP
LLVM_CRT_LIBRARY_DIR
)
set(C3_LLVM_VERSION "auto" CACHE STRING "Use LLVM version [default: auto]")
option(C3_USE_MIMALLOC "Use built-in mimalloc" OFF)
option(C3_USE_TB "Use TB" OFF)
set(C3_MIMALLOC_TAG "v1.7.3" CACHE STRING "Used version of mimalloc")
option(C3_WITH_LLVM "Build with LLVM" ON)
option(C3_LLD_DIR "Use custom LLD directory" "")
option(LLVM_CRT_LIBRARY_DIR "Use custom llvm's compiler-rt directory" "")
set(C3_USE_MIMALLOC OFF)
if(C3_USE_MIMALLOC)
@@ -109,6 +83,13 @@ endif()
if (NOT WIN32)
find_package(CURL)
endif()
if(C3_WITH_LLVM)
if (NOT C3_LLVM_VERSION STREQUAL "auto")
if (${C3_LLVM_VERSION} VERSION_LESS 17 OR ${C3_LLVM_VERSION} VERSION_GREATER 21)
message(FATAL_ERROR "LLVM ${C3_LLVM_VERSION} is not supported!")
endif()
endif()
endif()
find_package(Git QUIET)
if(C3_USE_TB AND GIT_FOUND AND EXISTS "${CMAKE_SOURCE_DIR}/.git")
@@ -126,6 +107,7 @@ if(C3_USE_TB AND GIT_FOUND AND EXISTS "${CMAKE_SOURCE_DIR}/.git")
endif()
# Clangd LSP support
option(C3_ENABLE_CLANGD_LSP "Enable/Disable output of compile commands during generation." OFF)
if(C3_ENABLE_CLANGD_LSP)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
execute_process(
@@ -138,7 +120,7 @@ endif(C3_ENABLE_CLANGD_LSP)
if(C3_WITH_LLVM)
if(CMAKE_C_COMPILER_ID STREQUAL "MSVC")
if (C3_LLVM_VERSION STREQUAL "auto")
set(C3_LLVM_VERSION ${C3_LLVM_DEFAULT_VERSION})
set(C3_LLVM_VERSION "19")
endif()
FetchContent_Declare(
LLVM_Windows
@@ -157,7 +139,6 @@ if(C3_WITH_LLVM)
FetchContent_MakeAvailable(LLVM_Windows)
set(llvm_dir ${llvm_windows_SOURCE_DIR})
endif()
message("Loaded Windows LLVM libraries into ${llvm_dir}")
set(CMAKE_SYSTEM_PREFIX_PATH ${llvm_dir} ${CMAKE_SYSTEM_PREFIX_PATH})
find_package(LLVM REQUIRED CONFIG)
@@ -168,7 +149,7 @@ if(C3_WITH_LLVM)
#
# Because of CMAKE_FIND_PACKAGE_SORT_ORDER CMAKE_FIND_PACKAGE_SORT_DIRECTION,
# the newest version will always be found first.
c3_print_variables(CMAKE_PREFIX_PATH)
message(STATUS "CMAKE_PREFIX_PATH: ${CMAKE_PREFIX_PATH}")
if (DEFINED LLVM_DIR)
message(STATUS "Looking for LLVM CMake files in user-specified directory ${LLVM_DIR}")
else()
@@ -195,15 +176,12 @@ if(C3_WITH_LLVM)
list(APPEND LLVM_LIBRARY_DIRS /usr/lib)
endif()
list(REMOVE_DUPLICATES LLVM_LIBRARY_DIRS)
message(STATUS "Found LLVM ${LLVM_PACKAGE_VERSION}")
message(STATUS "Using LLVMConfig.cmake in: ${LLVM_DIR}")
message(STATUS "LLVM libraries located in: ${LLVM_LIBRARY_DIRS}")
message(STATUS "Libraries located in: ${LLVM_LIBRARY_DIRS}")
if (${LLVM_PACKAGE_VERSION} VERSION_LESS C3_LLVM_MIN_VERSION OR
${LLVM_PACKAGE_VERSION} VERSION_GREATER C3_LLVM_MAX_VERSION)
message(FATAL_ERROR "LLVM ${LLVM_PACKAGE_VERSION} is not supported! LLVM version between ${C3_LLVM_MIN_VERSION} and ${C3_LLVM_MAX_VERSION} is required.")
if (NOT LLVM_PACKAGE_VERSION VERSION_GREATER_EQUAL 15.0)
message(FATAL_ERROR "LLVM version 15.0 or later is required.")
endif()
if(LLVM_ENABLE_RTTI)
@@ -253,43 +231,44 @@ if(C3_WITH_LLVM)
llvm_map_components_to_libnames(llvm_libs ${LLVM_LINK_COMPONENTS})
if(NOT ${C3_LLD_DIR} EQUAL "" AND EXISTS ${C3_LLD_DIR})
list(APPEND LLVM_LIBRARY_DIRS ${C3_LLD_DIR})
message("C3_LLD_DIR: " ${C3_LLD_DIR})
set(LLVM_LIBRARY_DIRS
"${LLVM_LIBRARY_DIRS}"
"${C3_LLD_DIR}"
)
list(REMOVE_DUPLICATES LLVM_LIBRARY_DIRS)
endif()
message(STATUS "Looking for static lld libraries in ${LLVM_LIBRARY_DIRS}")
# These don't seem to be reliable on windows.
find_library(LLD_COFF NAMES liblldCOFF.dylib lldCOFF.lib lldCOFF.a liblldCOFF.dll.a liblldCOFF.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH REQUIRED)
find_library(LLD_COMMON NAMES liblldCommon.dylib lldCommon.lib lldCommon.a liblldCommon.dll.a liblldCommon.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH REQUIRED)
find_library(LLD_ELF NAMES liblldELF.dylib lldELF.lib lldELF.a liblldELF.dll.a liblldELF.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH REQUIRED)
if (NOT ${CMAKE_SYSTEM_NAME} STREQUAL "OpenBSD")
find_library(LLD_MACHO NAMES liblldMachO.dylib lldMachO.lib lldMachO.a liblldMachO.dll.a liblldMachO.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH REQUIRED)
else()
set(LLD_MACHO "")
endif()
find_library(LLD_MINGW NAMES liblldMinGW.dylib lldMinGW.lib lldMinGW.a liblldMinGW.dll.a liblldMinGW.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH REQUIRED)
find_library(LLD_WASM NAMES liblldWasm.dylib lldWasm.lib lldWasm.a liblldWasm.dll.a liblldWasm.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH REQUIRED)
message(STATUS "using find_library")
find_library(LLD_COFF NAMES liblldCOFF.dylib lldCOFF.lib lldCOFF.a liblldCOFF.dll.a liblldCOFF.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
find_library(LLD_COMMON NAMES liblldCommon.dylib lldCommon.lib lldCommon.a liblldCommon.dll.a liblldCommon.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
find_library(LLD_ELF NAMES liblldELF.dylib lldELF.lib lldELF.a liblldELF.dll.a liblldELF.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
find_library(LLD_MACHO NAMES liblldMachO.dylib lldMachO.lib lldMachO.a liblldMachO.dll.a liblldMachO.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
find_library(LLD_MINGW NAMES liblldMinGW.dylib lldMinGW.lib lldMinGW.a liblldMinGW.dll.a liblldMinGW.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
find_library(LLD_WASM NAMES liblldWasm.dylib lldWasm.lib lldWasm.a liblldWasm.dll.a liblldWasm.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
else()
message(STATUS "Looking for shared lld libraries in ${LLVM_LIBRARY_DIRS}")
find_library(LLVM NAMES libLLVM.so PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH REQUIRED)
find_library(LLVM NAMES libLLVM.so PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
set(llvm_libs ${LLVM})
# These don't seem to be reliable on windows.
find_library(LLD_COFF NAMES liblldCOFF.so PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH REQUIRED)
find_library(LLD_COMMON NAMES liblldCommon.so PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH REQUIRED)
find_library(LLD_ELF NAMES liblldELF.so PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH REQUIRED)
if (NOT ${CMAKE_SYSTEM_NAME} STREQUAL "OpenBSD")
find_library(LLD_MACHO NAMES liblldMachO.so PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH REQUIRED)
else()
set(LLD_MACHO "")
endif()
find_library(LLD_MINGW NAMES liblldMinGW.so PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH REQUIRED)
find_library(LLD_WASM NAMES liblldWasm.so PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH REQUIRED)
message(STATUS "using find_library")
find_library(LLD_COFF NAMES liblldCOFF.so PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
find_library(LLD_COMMON NAMES liblldCommon.so PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
find_library(LLD_ELF NAMES liblldELF.so PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
find_library(LLD_MACHO NAMES liblldMachO.so PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
find_library(LLD_MINGW NAMES liblldMinGW.so PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
find_library(LLD_WASM NAMES liblldWasm.so PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
endif()
endif()
# find_library(LLD_LOONG NAMES libLLVMLoongArchCodeGen.lib libLLVMLoongArchAsmParser.lib libLLVMLoongArchCodeGen.a libLLVMLoongArchAsmParser.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
if (NOT(${CMAKE_BINARY_DIR} EQUAL ${CMAKE_SOURCE_DIR}))
file(REMOVE_RECURSE ${CMAKE_BINARY_DIR}/lib)
file(COPY ${CMAKE_SOURCE_DIR}/lib DESTINATION ${CMAKE_BINARY_DIR})
endif()
if(C3_WITH_LLVM)
find_library(LLD_LOONG NAMES libLLVMLoongArchCodeGen.lib libLLVMLoongArchAsmParser.lib libLLVMLoongArchCodeGen.a libLLVMLoongArchAsmParser.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
set(lld_libs
${LLD_COFF}
${LLD_WASM}
@@ -314,8 +293,8 @@ if(C3_WITH_LLVM)
)
endif()
message(STATUS "Linking to llvm libs ${llvm_libs}")
message(STATUS "Linking to lld libs ${lld_libs}")
message(STATUS "linking to llvm libs ${lld_libs}")
message(STATUS "Found lld libs ${lld_libs}")
endif()
add_library(miniz STATIC dependencies/miniz/miniz.c)
@@ -391,7 +370,6 @@ 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
@@ -434,11 +412,6 @@ if(C3_WITH_LLVM)
target_compile_definitions(c3c PUBLIC LLVM_AVAILABLE=1)
add_library(c3c_wrappers STATIC wrapper/src/wrapper.cpp)
if (MSVC)
target_compile_options(c3c PRIVATE
"$<$<CONFIG:Debug>:/EHa>"
"$<$<CONFIG:Release>:/EHsc>")
endif()
else()
target_sources(c3c PRIVATE src/utils/hostinfo.c)
target_compile_definitions(c3c PUBLIC LLVM_AVAILABLE=0)
@@ -522,7 +495,7 @@ endif ()
if (CURL_FOUND)
target_link_libraries(c3c ${CURL_LIBRARIES})
target_include_directories(c3c PRIVATE ${CURL_INCLUDE_DIRS})
target_include_directories(c3c PRIVATE ${CURL_INCLUDES})
target_compile_definitions(c3c PUBLIC CURL_FOUND=1)
else()
target_compile_definitions(c3c PUBLIC CURL_FOUND=0)
@@ -530,27 +503,34 @@ endif()
if(MSVC)
target_compile_options(c3c PRIVATE
/wd4068
/wd4090
/WX
/Wv:18
)
message("Adding MSVC options")
target_compile_options(c3c PRIVATE /wd4068 /wd4090 /WX /Wv:18)
if(C3_WITH_LLVM)
target_compile_options(c3c_wrappers PUBLIC
/wd4624
/wd4267
/wd4244
/WX
/Wv:18
)
target_compile_options(c3c_wrappers PUBLIC /wd4624 /wd4267 /wd4244 /WX /Wv:18)
if(NOT LLVM_ENABLE_RTTI)
target_compile_options(c3c_wrappers PUBLIC /GR-)
endif()
target_link_options(c3c_wrappers PUBLIC /ignore:4099)
endif()
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
target_compile_options(c3c PUBLIC /MTd)
if (C3_WITH_LLVM)
target_compile_options(c3c_wrappers PUBLIC /MTd)
endif()
target_compile_options(miniz PUBLIC /MTd)
if (C3_USE_TB)
target_compile_options(tilde-backend PUBLIC /MTd)
endif()
else()
target_compile_options(c3c PUBLIC /MT)
if (C3_WITH_LLVM)
target_compile_options(c3c_wrappers PUBLIC /MT)
endif()
target_compile_options(miniz PUBLIC /MT)
if (C3_USE_TB)
target_compile_options(tilde-backend PUBLIC /MT)
endif()
endif()
if(C3_WITH_LLVM)
set(clang_lib_dir ${llvm_dir}/lib/clang/${C3_LLVM_VERSION}/lib/windows)
set(sanitizer_runtime_libraries
@@ -560,31 +540,13 @@ if(MSVC)
${clang_lib_dir}/clang_rt.asan_dynamic_runtime_thunk-x86_64.lib)
endif()
else()
message(STATUS "using gcc/clang warning switches")
target_link_options(c3c PRIVATE -pthread)
if (C3_WITH_LLVM AND NOT LLVM_ENABLE_RTTI)
target_compile_options(c3c_wrappers PRIVATE -fno-rtti)
endif()
target_compile_options(c3c PRIVATE
-pthread
-Wall
-Werror
-Wno-unknown-pragmas
-Wno-unused-result
-Wno-unused-function
-Wno-unused-variable
-Wno-unused-parameter
)
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()
target_compile_options(c3c PRIVATE -pthread -Wall -Werror -Wno-unknown-pragmas -Wno-unused-result
-Wno-unused-function -Wno-unused-variable -Wno-unused-parameter)
endif()
install(TARGETS c3c DESTINATION bin)
@@ -595,12 +557,6 @@ if (NOT WIN32)
install(FILES c3c.1 DESTINATION "share/man/man1")
endif()
# Copy stdlib
if (NOT ${CMAKE_BINARY_DIR} EQUAL ${CMAKE_SOURCE_DIR})
file(REMOVE_RECURSE ${CMAKE_BINARY_DIR}/lib)
file(COPY ${CMAKE_SOURCE_DIR}/lib DESTINATION ${CMAKE_BINARY_DIR})
endif()
if (C3_WITH_LLVM AND DEFINED sanitizer_runtime_libraries)
add_custom_command(TARGET c3c POST_BUILD
COMMAND "${CMAKE_COMMAND}" -E rm -rf -- $<TARGET_FILE_DIR:c3c>/c3c_rt
@@ -620,35 +576,3 @@ if (C3_WITH_LLVM AND DEFINED sanitizer_runtime_libraries)
endif()
feature_summary(WHAT ALL)
message(STATUS "Building ${CMAKE_PROJECT_NAME} with the following configuration:")
set(c3_print_prefix " ")
foreach(option IN LISTS C3_OPTIONS)
if (DEFINED ${option})
c3_print_variables(${option})
endif()
endforeach()
foreach(flag_var
CMAKE_BUILD_TYPE
CMAKE_C_COMPILER
CMAKE_CXX_COMPILER
CMAKE_LINKER
CMAKE_OBJCOPY
CMAKE_STRIP
CMAKE_DLLTOOL)
c3_print_variables(${flag_var})
endforeach()
message(STATUS "Build flags:")
foreach(flag_var
CMAKE_C_FLAGS CMAKE_C_FLAGS_DEBUG CMAKE_C_FLAGS_RELEASE
CMAKE_C_FLAGS_MINSIZEREL CMAKE_C_FLAGS_RELWITHDEBINFO
CMAKE_CXX_FLAGS CMAKE_CXX_FLAGS_DEBUG CMAKE_CXX_FLAGS_RELEASE
CMAKE_CXX_FLAGS_MINSIZEREL CMAKE_CXX_FLAGS_RELWITHDEBINFO)
c3_print_variables(${flag_var})
endforeach()
message(STATUS "Output to: \"${CMAKE_BINARY_DIR}\"")

View File

@@ -1,57 +0,0 @@
{
"version": 3,
"configurePresets": [
{
"name": "windows-base",
"hidden": true,
"architecture": {
"value": "x64"
},
"toolset": {
"value": "host=x64"
}
},
{
"name": "windows-vs-2022-release",
"generator": "Visual Studio 17 2022",
"displayName": "Windows x64 Visual Studio 17 2022",
"inherits": "windows-base",
"binaryDir": "build",
"cacheVariables": {
"CMAKE_CONFIGURATION_TYPES": "Release;RelWithDebInfo",
"CMAKE_BUILD_TYPE": "Release"
}
},
{
"name": "windows-vs-2022-debug",
"generator": "Visual Studio 17 2022",
"displayName": "Windows x64 Visual Studio 17 2022 (Debug)",
"inherits": "windows-base",
"binaryDir": "build-debug",
"cacheVariables": {
"CMAKE_CONFIGURATION_TYPES": "Debug",
"CMAKE_BUILD_TYPE": "Debug"
}
}
],
"buildPresets": [
{
"name": "windows-vs-2022-debug",
"displayName": "Debug",
"configurePreset": "windows-vs-2022-debug",
"configuration": "Debug"
},
{
"name": "windows-vs-2022-release",
"displayName": "Release",
"configurePreset": "windows-vs-2022-release",
"configuration": "Release"
},
{
"name": "windows-vs-2022-release-with-debug-info",
"displayName": "RelWithDebInfo",
"configurePreset": "windows-vs-2022-release",
"configuration": "RelWithDebInfo"
}
]
}

View File

@@ -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 info@c3-lang.org. All
reported by contacting the project team at . 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
View File

@@ -1,20 +1,165 @@
Copyright (c) 2022-2025 Christoffer Lernö and contributors
GNU LESSER GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
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:
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.
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.
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.

View File

@@ -1,165 +0,0 @@
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.

20
LICENSE_STDLIB Normal file
View File

@@ -0,0 +1,20 @@
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.

202
README.md
View File

@@ -12,7 +12,6 @@ Precompiled binaries for the following operating systems are available:
- Debian x64 [download](https://github.com/c3lang/c3c/releases/download/latest-prerelease/c3-linux.tar.gz), [install instructions](#installing-on-debian-with-precompiled-binaries).
- Ubuntu x86 [download](https://github.com/c3lang/c3c/releases/download/latest-prerelease/c3-ubuntu-20.tar.gz), [install instructions](#installing-on-ubuntu-with-precompiled-binaries).
- MacOS Arm64 [download](https://github.com/c3lang/c3c/releases/download/latest-prerelease/c3-macos.zip), [install instructions](#installing-on-macos-with-precompiled-binaries).
- OpenBSD x64 [download](https://github.com/c3lang/c3c/releases/download/latest-prerelease/c3-openbsd.tar.gz), [install instructions](#installing-on-openbsd-with-precompiled-binaries).
The manual for C3 can be found at [www.c3-lang.org](http://www.c3-lang.org).
@@ -142,7 +141,7 @@ fn void main()
### Current status
The current stable version of the compiler is **version 0.7.7**.
The current stable version of the compiler is **version 0.7.2**.
The upcoming 0.7.x releases will focus on expanding the standard library,
fixing bugs and improving compile time analysis.
@@ -151,7 +150,7 @@ Follow the issues [here](https://github.com/c3lang/c3c/issues).
If you have suggestions on how to improve the language, either [file an issue](https://github.com/c3lang/c3c/issues)
or discuss C3 on its dedicated Discord: [https://discord.gg/qN76R87](https://discord.gg/qN76R87).
The compiler is currently verified to compile on Linux, OpenBSD, Windows and MacOS.
The compiler is currently verified to compile on Linux, Windows and MacOS.
**Support matrix**
@@ -177,14 +176,12 @@ The compiler is currently verified to compile on Linux, OpenBSD, Windows and Mac
| NetBSD x86 | Untested | Untested | No | Yes | Untested | Yes* |
| NetBSD x64 | Untested | Untested | No | Yes | Untested | Yes* |
| OpenBSD x86 | Untested | Untested | No | Yes | Untested | Yes* |
| OpenBSD x64 | Yes* | Yes | Yes* | Yes | Untested | Yes* |
| OpenBSD x64 | Untested | Untested | No | Yes | Untested | Yes* |
| MCU x86 | No | Untested | No | No | No | Yes* |
| Wasm32 | No | Yes | No | No | No | No |
| Wasm64 | No | Untested | No | No | No | No |
*\* Inline asm is still a work in progress*<br>
*\* OpenBSD 7.7 is the only tested version*<br>
*\* OpenBSD has limited stacktrace, needs to be tested further*
*\* Inline asm is still a work in progress*
More platforms will be supported in the future.
@@ -209,43 +206,12 @@ 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))
@@ -261,14 +227,6 @@ curl -fsSL https://raw.githubusercontent.com/c3lang/c3c/refs/heads/master/instal
(*Note that there is a known issue with debug symbol generation on MacOS 13, see [issue #1086](https://github.com/c3lang/c3c/issues/1086))
#### Installing on OpenBSD with precompiled binaries
1. Download tar file: [https://github.com/c3lang/c3c/releases/download/latest-prerelease/c3-openbsd.tar.gz](https://github.com/c3lang/c3c/releases/download/latest-prerelease/c3-openbsd.tar.gz)
(debug version [here](https://github.com/c3lang/c3c/releases/download/latest-prerelease/c3-openbsd-debug.tar.gz))
2. Unpack executable and standard lib.
3. Run `./c3c`.
(*Note that this is specifically for OpenBSD 7.7, running it on any other version is prone to ABI breaks)
#### Installing on Arch Linux
Arch includes c3c in the official 'extra' repo. It can be easily installed the usual way:
@@ -295,60 +253,6 @@ 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.
@@ -361,15 +265,14 @@ See the `build-with-docker.sh` script for more information on other configurable
#### Installing on OS X using Homebrew
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`
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 .`
#### Installing on Windows using Scoop
@@ -410,35 +313,28 @@ called `hello_world` or `hello_world.exe`depending on platform.
1. Make sure you have Visual Studio 17 2022 installed or alternatively install the "Buildtools for Visual Studio" (https://aka.ms/vs/17/release/vs_BuildTools.exe) and then select "Desktop development with C++"
2. Install CMake
3. Clone the C3C github repository: `git clone https://github.com/c3lang/c3c.git`
4. Enter the C3C directory: `cd c3c`.
5. Set up the CMake build: `cmake --preset windows-vs-2022-release`
6. Build: `cmake --build --preset windows-vs-2022-release`
4. Enter the C3C directory `cd c3c`.
5. Set up the CMake build `cmake -B build -G "Visual Studio 17 2022" -A x64 -DCMAKE_BUILD_TYPE=Release`
6. Build: `cmake --build build --config Release`
7. You should now have the c3c.exe
You should now have a `c3c` executable in `build\Release`.
You should now have a `c3c` executable.
You can try it out by running some sample code: `c3c.exe compile ../../resources/examples/hash.c3`
Building `c3c` using Visual Studio Code is also supported when using the `CMake Tools` extension. Simply select the `Windows x64 Visual Studio 17 2022` configure preset and build.
You can try it out by running some sample code: `c3c.exe compile ../resources/examples/hash.c3`
*Note that if you run into linking issues when building, make sure that you are using the latest version of VS17.*
#### Compiling on Windows (Debug)
Debug build requires a different set of LLVM libraries to be loaded for which a separate CMake configuration is used to avoid conflicts.
1. Configure: `cmake --preset windows-vs-2022-debug`
2. Build: `cmake --build --preset windows-vs-2022-debug`
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`. If you're using Ubuntu 25.04, also install `libpolly-20-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`
3. Clone the C3C github repository: `git clone https://github.com/c3lang/c3c.git`
4. Enter the C3C directory `cd c3c`.
5. Set up CMake build: `cmake -B build -S .`
6. Build: `cmake --build build`
7. Change directory to the build directory `cd build`
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 .`
You should now have a `c3c` executable.
@@ -451,12 +347,13 @@ 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 the CMake build cache: `cmake -B build -S .`
5. Build: `cmake --build build`
6. Enter the build directory: `cd build`
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 .`
Your c3c executable should have compiled properly. You may want to test it: `./c3c compile ../resources/examples/hash.c3`
For a system-wide installation, run the following as root: `cmake --install .`
For a sytem-wide installation, run the following as root: `cmake --install .`
#### Compiling on Fedora
@@ -466,15 +363,15 @@ For a system-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 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`
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 .`
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 libxml2`
1. Install required project dependencies: `sudo pacman -S curl lld llvm-libs clang cmake git libedit llvm`
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`
@@ -484,30 +381,23 @@ cmake -B build \
-D C3_LINK_DYNAMIC=ON \
-D CMAKE_BUILD_TYPE=Release
```
5. Build the project: `cmake --build build`.
5. Build the project: `make -C 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. 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`
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 .`
*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
@@ -515,13 +405,8 @@ this functionality is non-essential and it is perfectly fine to user the compile
#### Licensing
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.
The C3 compiler is licensed under LGPL 3.0, the standard library itself is
MIT licensed.
#### Editor plugins
@@ -540,12 +425,7 @@ Editor plugins can be found at https://github.com/c3lang/editor-plugins.
A huge **THANK YOU** goes out to all contributors and sponsors.
A special thank you to sponsors [Zack Puhl](https://github.com/NotsoanoNimus) and [konimarti](https://github.com/konimarti) for going the extra mile.
And honorable mention goes to past sponsors:
[Ygor Pontelo](https://github.com/ygorpontelo), [Simone Raimondi](https://github.com/SRaimondi),
[Jan Válek](https://github.com/jan-valek), [Pierre Curto](https://github.com/pierrec),
[Caleb-o](https://github.com/Caleb-o) and [devdad](https://github.com/devdad)
A special thank you to sponsors [Caleb-o](https://github.com/Caleb-o) and [devdad](https://github.com/devdad) for going the extra mile.
## Star History

View File

@@ -1,219 +0,0 @@
// 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();
}

View File

@@ -1,38 +0,0 @@
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);
}

View File

@@ -1,46 +0,0 @@
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

View File

@@ -1,31 +0,0 @@
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(aes, key, iv);
ctx.encrypt_buffer(text, &out);
// decrypt
ctx.init(aes, key, iv);
ctx.decrypt_buffer(cipher, &out);
}

View File

@@ -1,94 +0,0 @@
// 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.
module non_crypto_benchmarks;
const usz COMMON_ITERATIONS = 1 << 18;
const char[] COMMON_1 = { 0xA5 };
const char[] COMMON_4 = { 0xA5, 0xA5, 0xA5, 0xA5, };
const char[] COMMON_8 = { [0..7] = 0xA5 };
const char[] COMMON_16 = { [0..15] = 0xA5 };
const char[] COMMON_32 = { [0..31] = 0xA5 };
const char[] COMMON_64 = { [0..63] = 0xA5 };
const char[] COMMON_128 = { [0..127] = 0xA5 };
const char[] COMMON_1024 = { [0..1023] = 0xA5 };
fn void initialize_bench() @init
{
set_benchmark_warmup_iterations(3);
set_benchmark_max_iterations(COMMON_ITERATIONS + 3);
}
// =======================================================================================
module non_crypto_benchmarks @benchmark;
import std::hash;
fn void fnv64a_1() => fnv64a::hash(COMMON_1);
fn void fnv32a_1() => fnv32a::hash(COMMON_1);
fn void wyhash2_1() => wyhash2::hash(COMMON_1);
fn void metro64_1() => metro64::hash(COMMON_1);
fn void metro128_1() => metro128::hash(COMMON_1);
fn void a5hash_1() => a5hash::hash(COMMON_1);
fn void komi_1() => komi::hash(COMMON_1);
fn void fnv64a_4() => fnv64a::hash(COMMON_4);
fn void fnv32a_4() => fnv32a::hash(COMMON_4);
fn void wyhash2_4() => wyhash2::hash(COMMON_4);
fn void metro64_4() => metro64::hash(COMMON_4);
fn void metro128_4() => metro128::hash(COMMON_4);
fn void a5hash_4() => a5hash::hash(COMMON_4);
fn void komi_4() => komi::hash(COMMON_4);
fn void fnv64a_8() => fnv64a::hash(COMMON_8);
fn void fnv32a_8() => fnv32a::hash(COMMON_8);
fn void wyhash2_8() => wyhash2::hash(COMMON_8);
fn void metro64_8() => metro64::hash(COMMON_8);
fn void metro128_8() => metro128::hash(COMMON_8);
fn void a5hash_8() => a5hash::hash(COMMON_8);
fn void komi_8() => komi::hash(COMMON_8);
fn void fnv64a_16() => fnv64a::hash(COMMON_16);
fn void fnv32a_16() => fnv32a::hash(COMMON_16);
fn void wyhash2_16() => wyhash2::hash(COMMON_16);
fn void metro64_16() => metro64::hash(COMMON_16);
fn void metro128_16() => metro128::hash(COMMON_16);
fn void a5hash_16() => a5hash::hash(COMMON_16);
fn void komi_16() => komi::hash(COMMON_16);
fn void fnv64a_32() => fnv64a::hash(COMMON_32);
fn void fnv32a_32() => fnv32a::hash(COMMON_32);
// NOTE: wyhash2 cannot be used on inputs > 16 bytes.
fn void metro64_32() => metro64::hash(COMMON_32);
fn void metro128_32() => metro128::hash(COMMON_32);
fn void a5hash_32() => a5hash::hash(COMMON_32);
fn void komi_32() => komi::hash(COMMON_32);
fn void fnv64a_64() => fnv64a::hash(COMMON_64);
fn void fnv32a_64() => fnv32a::hash(COMMON_64);
// NOTE: wyhash2 cannot be used on inputs > 16 bytes.
fn void metro64_64() => metro64::hash(COMMON_64);
fn void metro128_64() => metro128::hash(COMMON_64);
fn void a5hash_64() => a5hash::hash(COMMON_64);
fn void komi_64() => komi::hash(COMMON_64);
fn void fnv64a_128() => fnv64a::hash(COMMON_128);
fn void fnv32a_128() => fnv32a::hash(COMMON_128);
// NOTE: wyhash2 cannot be used on inputs > 16 bytes.
fn void metro64_128() => metro64::hash(COMMON_128);
fn void metro128_128() => metro128::hash(COMMON_128);
fn void a5hash_128() => a5hash::hash(COMMON_128);
fn void komi_128() => komi::hash(COMMON_128);
fn void fnv64a_1024() => fnv64a::hash(COMMON_1024);
fn void fnv32a_1024() => fnv32a::hash(COMMON_1024);
// NOTE: wyhash2 cannot be used on inputs > 16 bytes.
fn void metro64_1024() => metro64::hash(COMMON_1024);
fn void metro128_1024() => metro128::hash(COMMON_1024);
fn void a5hash_1024() => a5hash::hash(COMMON_1024);
fn void komi_1024() => komi::hash(COMMON_1024);

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env bash
#!/bin/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"

View File

@@ -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++ libcurl4-openssl-dev && \
RUN apt-get update && apt-get install -y wget gnupg software-properties-common zlib1g zlib1g-dev python3 ninja-build curl g++ && \
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

View File

@@ -6,27 +6,29 @@
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; };
c3cBuild = set: pkgs.callPackage ./nix/default.nix (set // {
rev = self.rev or "unknown";
});
call = set: pkgs.callPackage ./nix/default.nix (
set // {
rev = self.rev or "unknown";
}
);
in {
packages = {
default = self.packages.${system}.c3c;
c3c = c3cBuild {};
c3c = call {};
c3c-checks = c3cBuild {
c3c-checks = pkgs.callPackage ./nix/default.nix {
checks = true;
};
c3c-debug = c3cBuild {
c3c-debug = pkgs.callPackage ./nix/default.nix {
debug = true;
};
c3c-debug-checks = c3cBuild {
c3c-debug-checks = pkgs.callPackage ./nix/default.nix {
debug = true;
checks = true;
};

View File

@@ -1,189 +0,0 @@
<#
.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"
}

View File

@@ -1,137 +0,0 @@
#!/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__

View File

@@ -91,7 +91,7 @@ macro Type Atomic.or(&self, Type value, AtomicOrdering ordering = SEQ_CONSISTENT
return @atomic_exec(atomic::fetch_or, data, value, ordering);
}
macro Type Atomic.xor(&self, Type value, AtomicOrdering ordering = SEQ_CONSISTENT) @if(types::flat_kind(Type) != FLOAT)
fn Type Atomic.xor(&self, Type value, AtomicOrdering ordering = SEQ_CONSISTENT) @if(types::flat_kind(Type) != FLOAT)
{
Type* data = &self.data;
return @atomic_exec(atomic::fetch_xor, data, value, ordering);
@@ -171,7 +171,6 @@ macro bool is_native_atomic_type($Type)
$case SIGNED_INT:
$case UNSIGNED_INT:
$case POINTER:
$case FUNC:
$case FLOAT:
$case BOOL:
return true;
@@ -194,7 +193,7 @@ macro bool is_native_atomic_type($Type)
@require $defined(*ptr) : "Expected a pointer"
@require @is_native_atomic_value(*ptr) : "Only types that are native atomic may be used."
@require $defined(*ptr + y) : "+ must be defined between the values."
@require $ordering != NOT_ATOMIC && $ordering != UNORDERED : "Acquire ordering is not valid."
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED : "Acquire ordering is not valid."
*>
macro fetch_add(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile = false, usz $alignment = 0)
{
@@ -214,7 +213,7 @@ macro fetch_add(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatil
@require $defined(*ptr) : "Expected a pointer"
@require @is_native_atomic_value(*ptr) : "Only types that are native atomic may be used."
@require $defined(*ptr - y) : "- must be defined between the values."
@require $ordering != NOT_ATOMIC && $ordering != UNORDERED : "Acquire ordering is not valid."
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED : "Acquire ordering is not valid."
*>
macro fetch_sub(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile = false, usz $alignment = 0)
{
@@ -233,13 +232,13 @@ macro fetch_sub(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatil
@require $defined(*ptr) : "Expected a pointer"
@require @is_native_atomic_value(*ptr) : "Only types that are native atomic may be used."
@require $defined(*ptr * y) : "* must be defined between the values."
@require $ordering != NOT_ATOMIC && $ordering != UNORDERED : "Acquire ordering is not valid."
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED : "Acquire ordering is not valid."
*>
macro fetch_mul(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT)
{
var $load_ordering = $ordering;
$if $ordering == RELEASE || $ordering == ACQUIRE_RELEASE:
$load_ordering = SEQ_CONSISTENT;
$load_ordering = AtomicOrdering.SEQ_CONSISTENT;
$endif
var $StorageType = types::lower_to_atomic_compatible_type($typeof(*ptr));
@@ -273,13 +272,13 @@ macro fetch_mul(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT)
@require $defined(*ptr) : "Expected a pointer"
@require @is_native_atomic_value(*ptr) : "Only types that are native atomic may be used."
@require $defined(*ptr * y) : "/ must be defined between the values."
@require $ordering != NOT_ATOMIC && $ordering != UNORDERED : "Acquire ordering is not valid."
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED : "Acquire ordering is not valid."
*>
macro fetch_div(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT)
{
var $load_ordering = $ordering;
$if $ordering == RELEASE || $ordering == ACQUIRE_RELEASE:
$load_ordering = SEQ_CONSISTENT;
$load_ordering = AtomicOrdering.SEQ_CONSISTENT;
$endif
var $StorageType = types::lower_to_atomic_compatible_type($typeof(*ptr));
@@ -314,7 +313,7 @@ macro fetch_div(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT)
@require $defined(*ptr) : "Expected a pointer"
@require @is_native_atomic_value(*ptr) : "Only types that are native atomic may be used."
@require $defined(*ptr | y) : "| must be defined between the values."
@require $ordering != NOT_ATOMIC && $ordering != UNORDERED : "Acquire ordering is not valid."
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED : "Acquire ordering is not valid."
*>
macro fetch_or(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile = false, usz $alignment = 0)
{
@@ -331,7 +330,7 @@ macro fetch_or(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile
@require $defined(*ptr) : "Expected a pointer"
@require @is_native_atomic_value(*ptr) : "Only types that are native atomic may be used."
@require $defined(*ptr ^ y) : "^ must be defined between the values."
@require $ordering != NOT_ATOMIC && $ordering != UNORDERED : "Acquire ordering is not valid."
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED : "Acquire ordering is not valid."
*>
macro fetch_xor(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile = false, usz $alignment = 0)
{
@@ -348,7 +347,7 @@ macro fetch_xor(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatil
@require $defined(*ptr) : "Expected a pointer"
@require @is_native_atomic_value(*ptr) : "Only types that are native atomic may be used."
@require $defined(*ptr ^ y) : "& must be defined between the values."
@require $ordering != NOT_ATOMIC && $ordering != UNORDERED : "Acquire ordering is not valid."
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED : "Acquire ordering is not valid."
*>
macro fetch_and(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile = false, usz $alignment = 0)
{
@@ -365,13 +364,13 @@ macro fetch_and(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatil
@require @is_native_atomic_value(*ptr) : "Only types that are native atomic may be used."
@require types::is_int($typeof(*ptr)) : "Only integer pointers may be used."
@require types::is_int($typeof(y)) : "The value for shift right must be an integer"
@require $ordering != NOT_ATOMIC && $ordering != UNORDERED : "Acquire ordering is not valid."
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED : "Acquire ordering is not valid."
*>
macro fetch_shift_right(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT)
{
var $load_ordering = $ordering;
$if $ordering == RELEASE || $ordering == ACQUIRE_RELEASE:
$load_ordering = SEQ_CONSISTENT;
$load_ordering = AtomicOrdering.SEQ_CONSISTENT;
$endif
var $StorageType = types::lower_to_atomic_compatible_type($typeof(*ptr));
@@ -407,13 +406,13 @@ macro fetch_shift_right(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT)
@require @is_native_atomic_value(*ptr) : "Only types that are native atomic may be used."
@require types::is_int($typeof(*ptr)) : "Only integer pointers may be used."
@require types::is_int($typeof(y)) : "The value for shift left must be an integer"
@require $ordering != NOT_ATOMIC && $ordering != UNORDERED : "Acquire ordering is not valid."
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED : "Acquire ordering is not valid."
*>
macro fetch_shift_left(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT)
{
var $load_ordering = $ordering;
$if $ordering == RELEASE || $ordering == ACQUIRE_RELEASE:
$load_ordering = SEQ_CONSISTENT;
$load_ordering = AtomicOrdering.SEQ_CONSISTENT;
$endif
var $StorageType = types::lower_to_atomic_compatible_type($typeof(*ptr));
@@ -448,7 +447,7 @@ macro fetch_shift_left(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT)
@require @is_native_atomic_value(*ptr) : "Only types that are native atomic may be used."
@require types::flat_kind($typeof(*ptr)) == BOOL : "Only bool pointers may be used."
@require $ordering != NOT_ATOMIC && $ordering != UNORDERED : "Acquire ordering is not valid."
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED : "Acquire ordering is not valid."
*>
macro flag_set(ptr, AtomicOrdering $ordering = SEQ_CONSISTENT)
{
@@ -456,7 +455,7 @@ macro flag_set(ptr, AtomicOrdering $ordering = SEQ_CONSISTENT)
$typeof(*ptr) new_value = true;
var $load_ordering = $ordering;
$if $ordering == RELEASE || $ordering == ACQUIRE_RELEASE:
$load_ordering = SEQ_CONSISTENT;
$load_ordering = AtomicOrdering.SEQ_CONSISTENT;
$endif
do
{
@@ -475,7 +474,7 @@ macro flag_set(ptr, AtomicOrdering $ordering = SEQ_CONSISTENT)
@require $defined(*ptr) : "Expected a pointer"
@require @is_native_atomic_value(*ptr) : "Only types that are native atomic may be used."
@require types::flat_kind($typeof(*ptr)) == BOOL : "Only bool pointers may be used."
@require $ordering != NOT_ATOMIC && $ordering != UNORDERED : "Acquire ordering is not valid."
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED : "Acquire ordering is not valid."
*>
macro flag_clear(ptr, AtomicOrdering $ordering = SEQ_CONSISTENT)
{
@@ -483,7 +482,7 @@ macro flag_clear(ptr, AtomicOrdering $ordering = SEQ_CONSISTENT)
$typeof(*ptr) new_value = false;
var $load_ordering = $ordering;
$if $ordering == RELEASE || $ordering == ACQUIRE_RELEASE:
$load_ordering = SEQ_CONSISTENT;
$load_ordering = AtomicOrdering.SEQ_CONSISTENT;
$endif
do
{
@@ -503,7 +502,7 @@ macro flag_clear(ptr, AtomicOrdering $ordering = SEQ_CONSISTENT)
@require $defined(*ptr) : "Expected a pointer"
@require @is_native_atomic_value(*ptr) : "Only types that are native atomic may be used."
@require $defined(*ptr > y) : "Only values that are comparable with > may be used"
@require $ordering != NOT_ATOMIC && $ordering != UNORDERED : "Acquire ordering is not valid."
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED : "Acquire ordering is not valid."
*>
macro fetch_max(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile = false, usz $alignment = 0)
{
@@ -522,7 +521,7 @@ macro fetch_max(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatil
@require $defined(*ptr) : "Expected a pointer"
@require @is_native_atomic_value(*ptr) : "Only types that are native atomic may be used."
@require $defined(*ptr > y) : "Only values that are comparable with > may be used"
@require $ordering != NOT_ATOMIC && $ordering != UNORDERED : "Acquire ordering is not valid."
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED : "Acquire ordering is not valid."
*>
macro fetch_min(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile = false, usz $alignment = 0)
{

View File

@@ -58,7 +58,7 @@ fn CInt __atomic_compare_exchange(CInt size, any ptr, any expected, any desired,
nextcase;
$endif
default:
unreachable("Unsupported size (%d) for atomic_compare_exchange", size);
unreachable("Unsuported size (%d) for atomic_compare_exchange", size);
}
return 0;
}
}

View File

@@ -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::collections::interfacelist;
import std::io,std::math;
alias AnyPredicate = InterfacePredicate {any};
alias AnyTest = InterfaceTest {any};
alias AnyPredicate = fn bool(any value);
alias AnyTest = fn bool(any type, any context);
<*
The AnyList contains a heterogenous set of types. Anything placed in the
@@ -18,7 +18,282 @@ alias AnyTest = InterfaceTest {any};
If we're not doing pop, then things are easier, since we can just hand over
the existing any.
*>
typedef AnyList = inline InterfaceList {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);
}
<*
Return the first element by value, assuming it is the given type.
@@ -38,7 +313,10 @@ macro AnyList.first(&self, $Type)
@return "The first element"
@return? NO_MORE_ELEMENT
*>
fn any? AnyList.first_any(&self) @inline => InterfaceList {any}.first(self);
fn any? AnyList.first_any(&self) @inline
{
return self.size ? self.entries[0] : NO_MORE_ELEMENT?;
}
<*
Return the last element by value, assuming it is the given type.
@@ -58,36 +336,29 @@ macro AnyList.last(&self, $Type)
@return "The last element"
@return? NO_MORE_ELEMENT
*>
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)
fn any? AnyList.last_any(&self) @inline
{
if (!self.size) return NO_MORE_ELEMENT?;
defer self.free_element(self.entries[self.size]);
return *anycast(self.entries[--self.size], $Type);
return self.size ? self.entries[self.size - 1] : NO_MORE_ELEMENT?;
}
<*
Pop a value who's type is known. If the type is incorrect, this
will still pop the element.
Return whether 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
@return "True if the list is empty"
*>
macro AnyList.pop_first(&self, $Type)
fn bool AnyList.is_empty(&self) @inline
{
if (!self.size) return NO_MORE_ELEMENT?;
defer self.remove_at(0);
return *anycast(self.entries[0], $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;
}
<*
@@ -112,11 +383,222 @@ 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([]) => InterfaceList {any}.get(self, index);
fn any AnyList.get_any(&self, usz index) @inline @operator([])
{
return self.entries[index];
}
<*
Return the length of the list.
@return "The number of elements in the list"
Completely free and clear a list.
*>
fn usz AnyList.len(&self) @operator(len) @inline => InterfaceList {any}.len(self);
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;
}

View File

@@ -136,7 +136,6 @@ 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
{
@@ -145,11 +144,6 @@ 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;

View File

@@ -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 bool type_is_overaligned() => Type.alignof > mem::DEFAULT_MEM_ALIGNMENT;
macro type_is_overaligned() => Type.alignof > mem::DEFAULT_MEM_ALIGNMENT;
struct ElasticArray (Printable)
{
@@ -121,24 +121,7 @@ fn usz ElasticArray.add_all_to_limit(&self, ElasticArray* other_list)
@param [in] 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)
fn usz ElasticArray.add_array_to_limit(&self, Type[] array)
{
if (!array.len) return 0;
foreach (i, &value : array)
@@ -156,7 +139,7 @@ fn usz ElasticArray.push_all_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) @deprecated("Use push_all")
fn void ElasticArray.add_array(&self, Type[] array)
{
if (!array.len) return;
foreach (&value : array)
@@ -165,21 +148,6 @@ fn void ElasticArray.add_array(&self, Type[] array) @deprecated("Use push_all")
}
}
<*
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;
}
}
<*
@@ -458,4 +426,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);
}
}

View File

@@ -30,10 +30,8 @@ struct HashMap (Printable)
{
Entry*[] table;
Allocator allocator;
<* Last inserted LinkedEntry *>
uint count;
<* Resize limit *>
uint threshold;
uint count; // Number of elements
uint threshold; // Resize limit
float load_factor;
}
@@ -92,7 +90,7 @@ macro HashMap* HashMap.init_with_key_values(&self, Allocator allocator, ..., uin
*>
macro HashMap* HashMap.tinit_with_key_values(&self, ..., uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
{
return self.init_with_key_values(tmem, $vasplat, capacity: capacity, load_factor: load_factor);
return self.tinit_with_key_values(tmem, capacity, load_factor);
}
<*
@@ -184,24 +182,6 @@ 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?;
@@ -214,9 +194,8 @@ fn Entry*? HashMap.get_entry(&map, Key key)
}
<*
Get the value or set it to the value
@require $defined(Value val = #expr)
Get the value or update and
@require $assignable(#expr, Value)
*>
macro Value HashMap.@get_or_set(&map, Key key, Value #expr)
{
@@ -250,15 +229,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)
@@ -353,7 +332,10 @@ macro HashMap.@each_entry(map; @body(entry))
}
}
fn Value[] HashMap.tvalues(&self) => self.values(tmem) @inline;
fn Value[] HashMap.tvalues(&map)
{
return map.values(tmem) @inline;
}
fn Value[] HashMap.values(&self, Allocator allocator)
{
@@ -439,7 +421,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(" }");
}
@@ -607,4 +589,4 @@ macro uint index_for(uint hash, uint capacity) @private
return hash & (capacity - 1);
}
int dummy @local;
int dummy @local;

View File

@@ -1,654 +0,0 @@
<*
@require $defined((Value){}.hash()) : `No .hash function found on the value`
*>
module std::collections::set {Value};
import std::math;
import std::io @norecurse;
const uint DEFAULT_INITIAL_CAPACITY = 16;
const uint MAXIMUM_CAPACITY = 1u << 31;
const float DEFAULT_LOAD_FACTOR = 0.75;
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
{
uint hash;
Value value;
Entry* next;
}
struct HashSet (Printable)
{
Entry*[] table;
Allocator allocator;
<* Number of elements *>
usz count;
<* Resize limit *>
usz threshold;
float load_factor;
}
fn int HashSet.len(&self) @operator(len) => (int) self.count;
<*
@param [&inout] allocator : "The allocator to use"
@require capacity > 0 : "The capacity must be 1 or higher"
@require load_factor > 0.0 : "The load factor must be higher than 0"
@require !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)
{
capacity = math::next_power_of_2(capacity);
self.allocator = allocator;
self.threshold = (usz) (capacity * load_factor);
self.load_factor = load_factor;
self.table = allocator::new_array(allocator, Entry*, capacity);
return self;
}
<*
@require capacity > 0 : "The capacity must be 1 or higher"
@require load_factor > 0.0 : "The load factor must be higher than 0"
@require !self.is_initialized() : "Set was already initialized"
@require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum"
*>
fn HashSet* HashSet.tinit(&self, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
{
return self.init(tmem, capacity, load_factor) @inline;
}
<*
@param [&inout] allocator : "The allocator to use"
@require capacity > 0 : "The capacity must be 1 or higher"
@require load_factor > 0.0 : "The load factor must be higher than 0"
@require !self.is_initialized() : "Set was already initialized"
@require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum"
*>
macro HashSet* HashSet.init_with_values(&self, Allocator allocator, ..., uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
{
self.init(allocator, capacity, load_factor);
$for var $i = 0; $i < $vacount; $i++:
self.add($vaarg[$i]);
$endfor
return self;
}
<*
@require capacity > 0 : "The capacity must be 1 or higher"
@require load_factor > 0.0 : "The load factor must be higher than 0"
@require !self.is_initialized() : "Set was already initialized"
@require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum"
*>
macro HashSet* HashSet.tinit_with_values(&self, ..., uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
{
return self.init_with_values(tmem, $vasplat, capacity: capacity, load_factor: load_factor);
}
<*
@param [in] values : "The values for the HashSet"
@param [&inout] allocator : "The allocator to use"
@require capacity > 0 : "The capacity must be 1 or higher"
@require load_factor > 0.0 : "The load factor must be higher than 0"
@require !self.is_initialized() : "Set was already initialized"
@require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum"
*>
fn HashSet* HashSet.init_from_values(&self, Allocator allocator, Value[] values, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
{
self.init(allocator, capacity, load_factor);
foreach (v : values) self.add(v);
return self;
}
<*
@param [in] values : "The values for the HashSet entries"
@require capacity > 0 : "The capacity must be 1 or higher"
@require load_factor > 0.0 : "The load factor must be higher than 0"
@require !self.is_initialized() : "Set was already initialized"
@require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum"
*>
fn HashSet* HashSet.tinit_from_values(&self, Value[] values, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
{
return self.init_from_values(tmem, values, capacity, load_factor);
}
<*
Has this hash set been initialized yet?
@param [&in] set : "The hash set we are testing"
@return "Returns true if it has been initialized, false otherwise"
*>
fn bool HashSet.is_initialized(&set)
{
return set.allocator && set.allocator.ptr != &dummy;
}
<*
@param [&inout] allocator : "The allocator to use"
@param [&in] other_set : "The set to copy from."
@require !self.is_initialized() : "Set was already initialized"
*>
fn HashSet* HashSet.init_from_set(&self, Allocator allocator, HashSet* other_set)
{
self.init(allocator, other_set.table.len, other_set.load_factor);
self.put_all_for_create(other_set);
return self;
}
<*
@param [&in] other_set : "The set to copy from."
@require !set.is_initialized() : "Set was already initialized"
*>
fn HashSet* HashSet.tinit_from_set(&set, HashSet* other_set)
{
return set.init_from_set(tmem, other_set) @inline;
}
<*
Check if the set is empty
@return "true if it is empty"
@pure
*>
fn bool HashSet.is_empty(&set) @inline
{
return !set.count;
}
<*
Add all elements in the slice to the set.
@param [in] list
@return "The number of new elements added"
@ensure total <= list.len
*>
fn usz HashSet.add_all(&set, Value[] list)
{
usz total;
foreach (v : list)
{
if (set.add(v)) total++;
}
return total;
}
<*
@param [&in] other
@return "The number of new elements added"
@ensure return <= other.count
*>
fn usz HashSet.add_all_from(&set, HashSet* other)
{
usz total;
other.@each(;Value value)
{
if (set.add(value)) total++;
};
return total;
}
<*
@param value : "The value to add"
@return "true if the value didn't exist in the set"
*>
fn bool HashSet.add(&set, Value value)
{
// If the set isn't initialized, use the defaults to initialize it.
switch (set.allocator.ptr)
{
case &dummy:
set.init(mem);
case null:
set.tinit();
default:
break;
}
uint hash = rehash(value.hash());
uint index = index_for(hash, set.table.len);
for (Entry *e = set.table[index]; e != null; e = e.next)
{
if (e.hash == hash && equals(value, e.value)) return false;
}
set.add_entry(hash, value, index);
return true;
}
<*
Iterate over all the values in the set
*>
macro HashSet.@each(set; @body(value))
{
if (!set.count) return;
foreach (Entry* entry : set.table)
{
while (entry)
{
@body(entry.value);
entry = entry.next;
}
}
}
<*
Check if the set contains the given value.
@param value : "The value to check"
@return "true if it exists in the set"
*>
fn bool HashSet.contains(&set, Value value)
{
if (!set.count) return false;
uint hash = rehash(value.hash());
for (Entry *e = set.table[index_for(hash, set.table.len)]; e != null; e = e.next)
{
if (e.hash == hash && equals(value, e.value)) return true;
}
return false;
}
<*
Remove a single value from the set.
@param value : "The value to remove"
@return? NOT_FOUND : "If the entry is not found"
*>
fn void? HashSet.remove(&set, Value value) @maydiscard
{
if (!set.remove_entry_for_value(value)) return NOT_FOUND?;
}
fn usz HashSet.remove_all(&set, Value[] values)
{
usz total;
foreach (v : values)
{
if (set.remove_entry_for_value(v)) total++;
}
return total;
}
<*
@param [&in] other : "Other set"
*>
fn usz HashSet.remove_all_from(&set, HashSet* other)
{
usz total;
other.@each(;Value val)
{
if (set.remove_entry_for_value(val)) total++;
};
return total;
}
<*
Free all memory allocated by the hash set.
*>
fn void HashSet.free(&set)
{
if (!set.is_initialized()) return;
set.clear();
set.free_internal(set.table.ptr);
*set = {};
}
<*
Clear all elements from the set while keeping the underlying storage
@ensure set.count == 0
*>
fn void HashSet.clear(&set)
{
if (!set.count) return;
foreach (Entry** &entry_ref : set.table)
{
Entry* entry = *entry_ref;
if (!entry) continue;
Entry *next = entry.next;
while (next)
{
Entry *to_delete = next;
next = next.next;
set.free_entry(to_delete);
}
set.free_entry(entry);
*entry_ref = null;
}
set.count = 0;
}
fn void HashSet.reserve(&set, usz capacity)
{
if (capacity > set.threshold)
{
set.resize(math::next_power_of_2(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 ---
<*
Returns the union of two sets (A | B)
@param [&in] other : "The other set to union with"
@param [&inout] allocator : "Allocator for the new set"
@return "A new set containing the union of both sets"
*>
fn HashSet HashSet.set_union(&self, Allocator allocator, HashSet* other)
{
usz new_capacity = math::next_power_of_2(self.count + other.count);
HashSet result;
result.init(allocator, new_capacity, self.load_factor);
result.add_all_from(self);
result.add_all_from(other);
return result;
}
fn HashSet HashSet.tset_union(&self, HashSet* other) => self.set_union(tmem, other);
<*
Returns the intersection of the two sets (A & B)
@param [&in] other : "The other set to intersect with"
@param [&inout] allocator : "Allocator for the new set"
@return "A new set containing the intersection of both sets"
*>
fn HashSet HashSet.intersection(&self, Allocator allocator, HashSet* other)
{
HashSet result;
result.init(allocator, math::min(self.table.len, other.table.len), self.load_factor);
// Iterate through the smaller set for efficiency
HashSet* smaller = self.count <= other.count ? self : other;
HashSet* larger = self.count > other.count ? self : other;
smaller.@each(;Value value)
{
if (larger.contains(value)) result.add(value);
};
return result;
}
fn HashSet HashSet.tintersection(&self, HashSet* other) => self.intersection(tmem, other);
<*
Return this set - other, so (A & ~B)
@param [&in] other : "The other set to compare with"
@param [&inout] allocator : "Allocator for the new set"
@return "A new set containing elements in this set but not in the other"
*>
fn HashSet HashSet.difference(&self, Allocator allocator, HashSet* other)
{
HashSet result;
result.init(allocator, self.table.len, self.load_factor);
self.@each(;Value value)
{
if (!other.contains(value))
{
result.add(value);
}
};
return result;
}
fn HashSet HashSet.tdifference(&self, HashSet* other) => self.difference(tmem, other) @inline;
<*
Return (A ^ B)
@param [&in] other : "The other set to compare with"
@param [&inout] allocator : "Allocator for the new set"
@return "A new set containing elements in this set or the other, but not both"
*>
fn HashSet HashSet.symmetric_difference(&self, Allocator allocator, HashSet* other)
{
HashSet result;
result.init(allocator, self.table.len, self.load_factor);
result.add_all_from(self);
other.@each(;Value value)
{
if (!result.add(value))
{
result.remove(value);
}
};
return result;
}
fn HashSet HashSet.tsymmetric_difference(&self, HashSet* other) => self.symmetric_difference(tmem, other) @inline;
<*
Check if this hash set is a subset of another set.
@param [&in] other : "The other set to check against"
@return "True if all elements of this set are in the other set"
*>
fn bool HashSet.is_subset(&self, HashSet* other)
{
if (self.count == 0) return true;
if (self.count > other.count) return false;
self.@each(;Value value)
{
if (!other.contains(value)) return false;
};
return true;
}
// --- private methods
fn void HashSet.add_entry(&set, uint hash, Value value, uint bucket_index) @private
{
Entry* entry = allocator::new(set.allocator, Entry, { .hash = hash, .value = value, .next = set.table[bucket_index] });
set.table[bucket_index] = entry;
if (set.count++ >= set.threshold)
{
set.resize(set.table.len * 2);
}
}
fn void HashSet.resize(&self, usz new_capacity) @private
{
Entry*[] old_table = self.table;
usz old_capacity = old_table.len;
if (old_capacity == MAXIMUM_CAPACITY)
{
self.threshold = uint.max;
return;
}
Entry*[] new_table = allocator::new_array(self.allocator, Entry*, new_capacity);
self.transfer(new_table);
self.table = new_table;
self.free_internal(old_table.ptr);
self.threshold = (uint)(new_capacity * self.load_factor);
}
fn usz? HashSet.to_format(&self, Formatter* f) @dynamic
{
usz len;
len += f.print("{ ")!;
self.@each(; Value value)
{
if (len > 2) len += f.print(", ")!;
len += f.printf("%s", value)!;
};
return len + f.print(" }");
}
fn void HashSet.transfer(&self, Entry*[] new_table) @private
{
Entry*[] src = self.table;
uint new_capacity = new_table.len;
foreach (uint j, Entry *e : src)
{
if (!e) continue;
do
{
Entry* next = e.next;
uint i = index_for(e.hash, new_capacity);
e.next = new_table[i];
new_table[i] = e;
e = next;
}
while (e);
}
}
fn void HashSet.put_all_for_create(&set, HashSet* other_set) @private
{
if (!other_set.count) return;
foreach (Entry *e : other_set.table)
{
while (e)
{
set.put_for_create(e.value);
e = e.next;
}
}
}
fn void HashSet.put_for_create(&set, Value value) @private
{
uint hash = rehash(value.hash());
uint i = index_for(hash, set.table.len);
for (Entry *e = set.table[i]; e != null; e = e.next)
{
if (e.hash == hash && equals(value, e.value))
{
// Value already exists, no need to do anything
return;
}
}
set.create_entry(hash, value, i);
}
fn void HashSet.free_internal(&self, void* ptr) @inline @private
{
allocator::free(self.allocator, ptr);
}
fn void HashSet.create_entry(&set, uint hash, Value value, int bucket_index) @private
{
Entry* entry = allocator::new(set.allocator, Entry, {
.hash = hash,
.value = value,
.next = set.table[bucket_index]
});
set.table[bucket_index] = entry;
set.count++;
}
<*
Removes the entry for the specified value if present
@return "true if found and removed, false otherwise"
*>
fn bool HashSet.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);
Entry* prev = set.table[i];
Entry* e = prev;
while (e)
{
Entry *next = e.next;
if (e.hash == hash && equals(value, e.value))
{
set.count--;
if (prev == e)
{
set.table[i] = next;
}
else
{
prev.next = next;
}
set.free_entry(e);
return true;
}
prev = e;
e = next;
}
return false;
}
fn void HashSet.free_entry(&set, Entry *entry) @private
{
allocator::free(set.allocator, entry);
}
struct HashSetIterator
{
HashSet* set;
usz bucket_index;
Entry* current;
}
fn HashSetIterator HashSet.iter(&set) => { .set = set, .bucket_index = 0, .current = null };
fn Value? HashSetIterator.next(&self)
{
if (self.current)
{
Value value = self.current.value;
self.current = self.current.next;
return value;
}
while (self.bucket_index < self.set.table.len)
{
self.current = self.set.table[self.bucket_index++];
if (self.current)
{
Value value = self.current.value;
self.current = self.current.next;
return value;
}
}
return NOT_FOUND?;
}
fn usz HashSetIterator.len(&self) @operator(len)
{
return self.set.count;
}
<* @pure *>
fn uint rehash(uint hash) @inline @private
{
hash ^= (hash >> 20) ^ (hash >> 12);
return hash ^ ((hash >> 7) ^ (hash >> 4));
}
macro uint index_for(uint hash, uint capacity) @private => hash & (capacity - 1);
int dummy @local;

View File

@@ -1,539 +0,0 @@
// 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;
}

View File

@@ -1,333 +0,0 @@
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;
}

View File

@@ -1,651 +0,0 @@
// Copyright (c) 2023 Christoffer Lerno. All rights reserved.
// Use of this source code is governed by the MIT license
// a copy of which can be found in the LICENSE_STDLIB file.
<*
@require $defined((Key){}.hash()) : `No .hash function found on the key`
*>
module std::collections::map{Key, Value};
import std::math;
import std::io @norecurse;
const LinkedHashMap LINKEDONHEAP = { .allocator = MAP_HEAP_ALLOCATOR };
struct LinkedEntry
{
uint hash;
Key key;
Value value;
<* For bucket chain *>
LinkedEntry* next;
<* Previous in insertion order *>
LinkedEntry* before;
<* Next in insertion order *>
LinkedEntry* after;
}
struct LinkedHashMap (Printable)
{
LinkedEntry*[] table;
Allocator allocator;
usz count;
usz threshold;
float load_factor;
<* First inserted LinkedEntry *>
LinkedEntry* head;
<* Last inserted LinkedEntry *>
LinkedEntry* tail;
}
<*
@param [&inout] allocator : "The allocator to use"
@require capacity > 0 : "The capacity must be 1 or higher"
@require load_factor > 0.0 : "The load factor must be higher than 0"
@require !self.is_initialized() : "Map was already initialized"
@require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum"
*>
fn LinkedHashMap* LinkedHashMap.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;
self.load_factor = load_factor;
self.threshold = (usz)(capacity * load_factor);
self.table = allocator::new_array(allocator, LinkedEntry*, capacity);
self.head = null;
self.tail = null;
return self;
}
<*
@require capacity > 0 : "The capacity must be 1 or higher"
@require load_factor > 0.0 : "The load factor must be higher than 0"
@require !self.is_initialized() : "Map was already initialized"
@require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum"
*>
fn LinkedHashMap* LinkedHashMap.tinit(&self, usz capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
{
return self.init(tmem, capacity, load_factor) @inline;
}
<*
@param [&inout] allocator : "The allocator to use"
@require $vacount % 2 == 0 : "There must be an even number of arguments provided for keys and values"
@require capacity > 0 : "The capacity must be 1 or higher"
@require load_factor > 0.0 : "The load factor must be higher than 0"
@require !self.is_initialized() : "Map was already initialized"
@require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum"
*>
macro LinkedHashMap* LinkedHashMap.init_with_key_values(&self, Allocator allocator, ..., uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
{
self.init(allocator, capacity, load_factor);
$for var $i = 0; $i < $vacount; $i += 2:
self.set($vaarg[$i], $vaarg[$i + 1]);
$endfor
return self;
}
<*
@require $vacount % 2 == 0 : "There must be an even number of arguments provided for keys and values"
@require capacity > 0 : "The capacity must be 1 or higher"
@require load_factor > 0.0 : "The load factor must be higher than 0"
@require !self.is_initialized() : "Map was already initialized"
@require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum"
*>
macro LinkedHashMap* LinkedHashMap.tinit_with_key_values(&self, ..., uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
{
return self.init_with_key_values(tmem, $vasplat, capacity: capacity, load_factor: load_factor);
}
<*
@param [in] keys : "The keys for the LinkedHashMap entries"
@param [in] values : "The values for the LinkedHashMap entries"
@param [&inout] allocator : "The allocator to use"
@require keys.len == values.len : "Both keys and values arrays must be the same length"
@require capacity > 0 : "The capacity must be 1 or higher"
@require load_factor > 0.0 : "The load factor must be higher than 0"
@require !self.is_initialized() : "Map was already initialized"
@require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum"
*>
fn LinkedHashMap* LinkedHashMap.init_from_keys_and_values(&self, Allocator allocator, Key[] keys, Value[] values, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
{
assert(keys.len == values.len);
self.init(allocator, capacity, load_factor);
for (usz i = 0; i < keys.len; i++)
{
self.set(keys[i], values[i]);
}
return self;
}
<*
@param [in] keys : "The keys for the LinkedHashMap entries"
@param [in] values : "The values for the LinkedHashMap entries"
@require keys.len == values.len : "Both keys and values arrays must be the same length"
@require capacity > 0 : "The capacity must be 1 or higher"
@require load_factor > 0.0 : "The load factor must be higher than 0"
@require !self.is_initialized() : "Map was already initialized"
@require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum"
*>
fn LinkedHashMap* LinkedHashMap.tinit_from_keys_and_values(&self, Key[] keys, Value[] values, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
{
return self.init_from_keys_and_values(tmem, keys, values, capacity, load_factor);
}
<*
Has this hash map been initialized yet?
@param [&in] map : "The hash map we are testing"
@return "Returns true if it has been initialized, false otherwise"
*>
fn bool LinkedHashMap.is_initialized(&map)
{
return map.allocator && map.allocator.ptr != &dummy;
}
<*
@param [&inout] allocator : "The allocator to use"
@param [&in] other_map : "The map to copy from."
@require !self.is_initialized() : "Map was already initialized"
*>
fn LinkedHashMap* LinkedHashMap.init_from_map(&self, Allocator allocator, LinkedHashMap* other_map)
{
self.init(allocator, other_map.table.len, other_map.load_factor);
self.put_all_for_create(other_map);
return self;
}
<*
@param [&in] other_map : "The map to copy from."
@require !map.is_initialized() : "Map was already initialized"
*>
fn LinkedHashMap* LinkedHashMap.tinit_from_map(&map, LinkedHashMap* other_map)
{
return map.init_from_map(tmem, other_map) @inline;
}
fn bool LinkedHashMap.is_empty(&map) @inline
{
return !map.count;
}
fn usz LinkedHashMap.len(&map) @inline => map.count;
fn Value*? LinkedHashMap.get_ref(&map, Key key)
{
if (!map.count) return NOT_FOUND?;
uint hash = rehash(key.hash());
for (LinkedEntry *e = map.table[index_for(hash, map.table.len)]; e != null; e = e.next)
{
if (e.hash == hash && equals(key, e.key)) return &e.value;
}
return NOT_FOUND?;
}
fn LinkedEntry*? LinkedHashMap.get_entry(&map, Key key)
{
if (!map.count) return NOT_FOUND?;
uint hash = rehash(key.hash());
for (LinkedEntry *e = map.table[index_for(hash, map.table.len)]; e != null; e = e.next)
{
if (e.hash == hash && equals(key, e.key)) return e;
}
return NOT_FOUND?;
}
<*
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)
{
if (!map.count)
{
Value val = #expr;
map.set(key, val);
return val;
}
uint hash = rehash(key.hash());
uint index = index_for(hash, map.table.len);
for (LinkedEntry *e = map.table[index]; e != null; e = e.next)
{
if (e.hash == hash && equals(key, e.key)) return e.value;
}
Value val = #expr;
map.add_entry(hash, key, val, index);
return val;
}
fn Value? LinkedHashMap.get(&map, Key key) @operator([]) => *map.get_ref(key) @inline;
fn bool LinkedHashMap.has_key(&map, Key key) => @ok(map.get_ref(key));
fn bool LinkedHashMap.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;
}
uint hash = rehash(key.hash());
uint index = index_for(hash, map.table.len);
for (LinkedEntry *e = map.table[index]; e != null; e = e.next)
{
if (e.hash == hash && equals(key, e.key))
{
e.value = value;
return true;
}
}
map.add_entry(hash, key, value, index);
return false;
}
fn void? LinkedHashMap.remove(&map, Key key) @maydiscard
{
if (!map.remove_entry_for_key(key)) return NOT_FOUND?;
}
fn void LinkedHashMap.clear(&map)
{
if (!map.count) return;
LinkedEntry* entry = map.head;
while (entry)
{
LinkedEntry* next = entry.after;
map.free_entry(entry);
entry = next;
}
foreach (LinkedEntry** &bucket : map.table)
{
*bucket = null;
}
map.count = 0;
map.head = null;
map.tail = null;
}
fn void LinkedHashMap.free(&map)
{
if (!map.is_initialized()) return;
map.clear();
map.free_internal(map.table.ptr);
map.table = {};
}
fn Key[] LinkedHashMap.tkeys(&self)
{
return self.keys(tmem) @inline;
}
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)
{
$if COPY_KEYS:
list[index++] = entry.key.copy(allocator);
$else
list[index++] = entry.key;
$endif
entry = entry.after;
}
return list;
}
macro LinkedHashMap.@each(map; @body(key, value))
{
map.@each_entry(; LinkedEntry* entry)
{
@body(entry.key, entry.value);
};
}
macro LinkedHashMap.@each_entry(map; @body(entry))
{
LinkedEntry* entry = map.head;
while (entry)
{
@body(entry);
entry = entry.after;
}
}
fn Value[] LinkedHashMap.tvalues(&map) => map.values(tmem) @inline;
fn Value[] LinkedHashMap.values(&self, Allocator allocator)
{
if (!self.count) return {};
Value[] list = allocator::alloc_array(allocator, Value, self.count);
usz index = 0;
LinkedEntry* entry = self.head;
while (entry)
{
list[index++] = entry.value;
entry = entry.after;
}
return list;
}
fn bool LinkedHashMap.has_value(&map, Value v) @if(VALUE_IS_EQUATABLE)
{
if (!map.count) return false;
LinkedEntry* entry = map.head;
while (entry)
{
if (equals(v, entry.value)) return true;
entry = entry.after;
}
return false;
}
fn LinkedHashMapIterator LinkedHashMap.iter(&self) => { .map = self, .current = self.head, .started = false };
fn LinkedHashMapValueIterator LinkedHashMap.value_iter(&self) => { .map = self, .current = self.head, .started = false };
fn LinkedHashMapKeyIterator LinkedHashMap.key_iter(&self) => { .map = self, .current = self.head, .started = false };
fn bool LinkedHashMapIterator.next(&self)
{
if (!self.started)
{
self.current = self.map.head;
self.started = true;
}
else if (self.current)
{
self.current = self.current.after;
}
return self.current != null;
}
fn LinkedEntry*? LinkedHashMapIterator.get(&self)
{
return self.current ? self.current : NOT_FOUND?;
}
fn Value*? LinkedHashMapValueIterator.get(&self)
{
return self.current ? &self.current.value : NOT_FOUND?;
}
fn Key*? LinkedHashMapKeyIterator.get(&self)
{
return self.current ? &self.current.key : NOT_FOUND?;
}
fn bool LinkedHashMapIterator.has_next(&self)
{
if (!self.started) return self.map.head != null;
return self.current && self.current.after != null;
}
// --- private methods
fn void LinkedHashMap.add_entry(&map, uint hash, Key key, Value value, uint bucket_index) @private
{
$if COPY_KEYS:
key = key.copy(map.allocator);
$endif
LinkedEntry* entry = allocator::new(map.allocator, LinkedEntry, {
.hash = hash,
.key = key,
.value = value,
.next = map.table[bucket_index],
.before = map.tail,
.after = null
});
// Update bucket chain
map.table[bucket_index] = entry;
// Update linked list
if (map.tail)
{
map.tail.after = entry;
entry.before = map.tail;
}
else
{
map.head = entry;
}
map.tail = entry;
if (map.count++ >= map.threshold)
{
map.resize(map.table.len * 2);
}
}
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;
if ((e.hash & old_capacity) == 0)
{
if (!lo_tail)
{
lo_head = e;
}
else
{
lo_tail.next = e;
}
lo_tail = e;
}
else
{
if (!hi_tail)
{
hi_head = e;
}
else
{
hi_tail.next = e;
}
hi_tail = e;
}
e.next = null;
e = next;
}
while (e);
if (lo_tail)
{
lo_tail.next = null;
new_table[i] = lo_head;
}
if (hi_tail)
{
hi_tail.next = null;
new_table[i + old_capacity] = hi_head;
}
}
map.free_internal(old_table.ptr);
}
fn usz? LinkedHashMap.to_format(&self, Formatter* f) @dynamic
{
usz len;
len += f.print("{ ")!;
self.@each_entry(; LinkedEntry* entry)
{
if (len > 2) len += f.print(", ")!;
len += f.printf("%s: %s", entry.key, entry.value)!;
};
return len + f.print(" }");
}
fn void LinkedHashMap.transfer(&map, LinkedEntry*[] new_table) @private
{
LinkedEntry*[] src = map.table;
uint new_capacity = new_table.len;
foreach (uint j, LinkedEntry *e : src)
{
if (!e) continue;
do
{
LinkedEntry* next = e.next;
uint i = index_for(e.hash, new_capacity);
e.next = new_table[i];
new_table[i] = e;
e = next;
}
while (e);
}
}
fn void LinkedHashMap.put_all_for_create(&map, LinkedHashMap* other_map) @private
{
if (!other_map.count) return;
other_map.@each(; Key key, Value value) {
map.set(key, value);
};
}
fn void LinkedHashMap.put_for_create(&map, Key key, Value value) @private
{
uint hash = rehash(key.hash());
uint i = index_for(hash, map.table.len);
for (LinkedEntry *e = map.table[i]; e != null; e = e.next)
{
if (e.hash == hash && equals(key, e.key))
{
e.value = value;
return;
}
}
map.create_entry(hash, key, value, i);
}
fn void LinkedHashMap.free_internal(&map, void* ptr) @inline @private
{
allocator::free(map.allocator, ptr);
}
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))
{
if (prev)
{
prev.next = e.next;
}
else
{
map.table[i] = e.next;
}
if (e.before)
{
e.before.after = e.after;
}
else
{
map.head = e.after;
}
if (e.after)
{
e.after.before = e.before;
}
else
{
map.tail = e.before;
}
map.count--;
map.free_entry(e);
return true;
}
prev = e;
e = e.next;
}
return false;
}
fn void LinkedHashMap.create_entry(&map, uint hash, Key key, Value value, int bucket_index) @private
{
LinkedEntry *e = map.table[bucket_index];
$if COPY_KEYS:
key = key.copy(map.allocator);
$endif
LinkedEntry* entry = allocator::new(map.allocator, LinkedEntry, { .hash = hash, .key = key, .value = value, .next = map.table[bucket_index] });
map.table[bucket_index] = entry;
map.count++;
}
fn void LinkedHashMap.free_entry(&self, LinkedEntry *entry) @local
{
$if COPY_KEYS:
allocator::free(self.allocator, entry.key);
$endif
self.free_internal(entry);
}
struct LinkedHashMapIterator
{
LinkedHashMap* map;
LinkedEntry* current;
bool started;
}
typedef LinkedHashMapValueIterator = inline LinkedHashMapIterator;
typedef LinkedHashMapKeyIterator = inline LinkedHashMapIterator;
fn usz LinkedHashMapValueIterator.len(self) @operator(len) => self.map.count;
fn usz LinkedHashMapKeyIterator.len(self) @operator(len) => self.map.count;
fn usz LinkedHashMapIterator.len(self) @operator(len) => self.map.count;
int dummy @local;

View File

@@ -1,730 +0,0 @@
<*
@require $defined((Value){}.hash()) : `No .hash function found on the value`
*>
module std::collections::set {Value};
import std::math;
import std::io @norecurse;
const LinkedHashSet LINKEDONHEAP = { .allocator = SET_HEAP_ALLOCATOR };
struct LinkedEntry
{
uint hash;
Value value;
<* For bucket chain *>
LinkedEntry* next;
<* Previous in insertion order *>
LinkedEntry* before;
<* Next in insertion order *>
LinkedEntry* after;
}
struct LinkedHashSet (Printable)
{
LinkedEntry*[] table;
Allocator allocator;
<* Number of elements *>
usz count;
<* Resize limit *>
usz threshold;
float load_factor;
<* Resize limit *>
LinkedEntry* head;
<* First inserted LinkedEntry *>
LinkedEntry* tail;
}
fn int LinkedHashSet.len(&self) @operator(len) => (int) self.count;
<*
@param [&inout] allocator : "The allocator to use"
@require capacity > 0 : "The capacity must be 1 or higher"
@require load_factor > 0.0 : "The load factor must be higher than 0"
@require !self.is_initialized() : "Set was already initialized"
@require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum"
*>
fn LinkedHashSet* LinkedHashSet.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;
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;
}
<*
@require capacity > 0 : "The capacity must be 1 or higher"
@require load_factor > 0.0 : "The load factor must be higher than 0"
@require !self.is_initialized() : "Set was already initialized"
@require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum"
*>
fn LinkedHashSet* LinkedHashSet.tinit(&self, usz capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
{
return self.init(tmem, capacity, load_factor) @inline;
}
<*
@param [&inout] allocator : "The allocator to use"
@require capacity > 0 : "The capacity must be 1 or higher"
@require load_factor > 0.0 : "The load factor must be higher than 0"
@require !self.is_initialized() : "Set was already initialized"
@require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum"
*>
macro LinkedHashSet* LinkedHashSet.init_with_values(&self, Allocator allocator, ..., uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
{
self.init(allocator, capacity, load_factor);
$for var $i = 0; $i < $vacount; $i++:
self.add($vaarg[$i]);
$endfor
return self;
}
<*
@require capacity > 0 : "The capacity must be 1 or higher"
@require load_factor > 0.0 : "The load factor must be higher than 0"
@require !self.is_initialized() : "Set was already initialized"
@require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum"
*>
macro LinkedHashSet* LinkedHashSet.tinit_with_values(&self, ..., uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
{
return self.init_with_values(tmem, $vasplat, capacity: capacity, load_factor: load_factor);
}
<*
@param [in] values : "The values for the LinkedHashSet"
@param [&inout] allocator : "The allocator to use"
@require capacity > 0 : "The capacity must be 1 or higher"
@require load_factor > 0.0 : "The load factor must be higher than 0"
@require !self.is_initialized() : "Set was already initialized"
@require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum"
*>
fn LinkedHashSet* LinkedHashSet.init_from_values(&self, Allocator allocator, Value[] values, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
{
self.init(allocator, capacity, load_factor);
foreach (v : values) self.add(v);
return self;
}
<*
@param [in] values : "The values for the LinkedHashSet entries"
@require capacity > 0 : "The capacity must be 1 or higher"
@require load_factor > 0.0 : "The load factor must be higher than 0"
@require !self.is_initialized() : "Set was already initialized"
@require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum"
*>
fn LinkedHashSet* LinkedHashSet.tinit_from_values(&self, Value[] values, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
{
return self.init_from_values(tmem, values, capacity, load_factor);
}
<*
Has this linked hash set been initialized yet?
@param [&in] set : "The linked hash set we are testing"
@return "Returns true if it has been initialized, false otherwise"
*>
fn bool LinkedHashSet.is_initialized(&set)
{
return set.allocator && set.allocator.ptr != &dummy;
}
<*
@param [&inout] allocator : "The allocator to use"
@param [&in] other_set : "The set to copy from."
@require !self.is_initialized() : "Set was already initialized"
*>
fn LinkedHashSet* LinkedHashSet.init_from_set(&self, Allocator allocator, LinkedHashSet* other_set)
{
self.init(allocator, other_set.table.len, other_set.load_factor);
LinkedEntry* entry = other_set.head;
while (entry) // Save insertion order
{
self.put_for_create(entry.value);
entry = entry.after;
}
return self;
}
<*
@param [&in] other_set : "The set to copy from."
@require !set.is_initialized() : "Set was already initialized"
*>
fn LinkedHashSet* LinkedHashSet.tinit_from_set(&set, LinkedHashSet* other_set)
{
return set.init_from_set(tmem, other_set) @inline;
}
<*
Check if the set is empty
@return "true if it is empty"
@pure
*>
fn bool LinkedHashSet.is_empty(&set) @inline
{
return !set.count;
}
<*
Add all elements in the slice to the set.
@param [in] list
@return "The number of new elements added"
@ensure total <= list.len
*>
fn usz LinkedHashSet.add_all(&set, Value[] list)
{
usz total;
foreach (v : list)
{
if (set.add(v)) total++;
}
return total;
}
<*
@param [&in] other
@return "The number of new elements added"
@ensure return <= other.count
*>
fn usz LinkedHashSet.add_all_from(&set, LinkedHashSet* other)
{
usz total;
other.@each(;Value value)
{
if (set.add(value)) total++;
};
return total;
}
<*
@param value : "The value to add"
@return "true if the value didn't exist in the set"
*>
fn bool LinkedHashSet.add(&set, Value value)
{
// If the set isn't initialized, use the defaults to initialize it.
switch (set.allocator.ptr)
{
case &dummy:
set.init(mem);
case null:
set.tinit();
default:
break;
}
uint hash = rehash(value.hash());
uint index = index_for(hash, set.table.len);
for (LinkedEntry *e = set.table[index]; e != null; e = e.next)
{
if (e.hash == hash && equals(value, e.value)) return false;
}
set.add_entry(hash, value, index);
return true;
}
<*
Iterate over all the values in the set
*>
macro LinkedHashSet.@each(set; @body(value))
{
if (!set.count) return;
LinkedEntry* entry = set.head;
while (entry)
{
@body(entry.value);
entry = entry.after;
}
}
<*
Check if the set contains the given value.
@param value : "The value to check"
@return "true if it exists in the set"
*>
fn bool LinkedHashSet.contains(&set, Value value)
{
if (!set.count) return false;
uint hash = rehash(value.hash());
for (LinkedEntry *e = set.table[index_for(hash, set.table.len)]; e != null; e = e.next)
{
if (e.hash == hash && equals(value, e.value)) return true;
}
return false;
}
<*
Remove a single value from the set.
@param value : "The value to remove"
@return? NOT_FOUND : "If the entry is not found"
*>
fn void? LinkedHashSet.remove(&set, Value value) @maydiscard
{
if (!set.remove_entry_for_value(value)) return NOT_FOUND?;
}
fn usz LinkedHashSet.remove_all(&set, Value[] values)
{
usz total;
foreach (v : values)
{
if (set.remove_entry_for_value(v)) total++;
}
return total;
}
<*
@param [&in] other : "Other set"
*>
fn usz LinkedHashSet.remove_all_from(&set, LinkedHashSet* other)
{
usz total;
other.@each(;Value val)
{
if (set.remove_entry_for_value(val)) total++;
};
return total;
}
<*
Free all memory allocated by the hash set.
*>
fn void LinkedHashSet.free(&set)
{
if (!set.is_initialized()) return;
set.clear();
set.free_internal(set.table.ptr);
set.table = {};
}
<*
Clear all elements from the set while keeping the underlying storage
@ensure set.count == 0
*>
fn void LinkedHashSet.clear(&set)
{
if (!set.count) return;
LinkedEntry* entry = set.head;
while (entry)
{
LinkedEntry* next = entry.after;
set.free_entry(entry);
entry = next;
}
foreach (LinkedEntry** &bucket : set.table)
{
*bucket = null;
}
set.count = 0;
set.head = null;
set.tail = null;
}
fn void LinkedHashSet.reserve(&set, usz capacity)
{
if (capacity > set.threshold)
{
set.resize(math::next_power_of_2(capacity));
}
}
// --- Set Operations ---
<*
Returns the union of two sets (A | B)
@param [&in] other : "The other set to union with"
@param [&inout] allocator : "Allocator for the new set"
@return "A new set containing the union of both sets"
*>
fn LinkedHashSet LinkedHashSet.set_union(&self, Allocator allocator, LinkedHashSet* other)
{
usz new_capacity = math::next_power_of_2(self.count + other.count);
LinkedHashSet result;
result.init(allocator, new_capacity, self.load_factor);
result.add_all_from(self);
result.add_all_from(other);
return result;
}
fn LinkedHashSet LinkedHashSet.tset_union(&self, LinkedHashSet* other) => self.set_union(tmem, other) @inline;
<*
Returns the intersection of the two sets (A & B)
@param [&in] other : "The other set to intersect with"
@param [&inout] allocator : "Allocator for the new set"
@return "A new set containing the intersection of both sets"
*>
fn LinkedHashSet LinkedHashSet.intersection(&self, Allocator allocator, LinkedHashSet* other)
{
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)
{
if (larger.contains(value)) result.add(value);
};
return result;
}
fn LinkedHashSet LinkedHashSet.tintersection(&self, LinkedHashSet* other) => self.intersection(tmem, other) @inline;
<*
Return this set - other, so (A & ~B)
@param [&in] other : "The other set to compare with"
@param [&inout] allocator : "Allocator for the new set"
@return "A new set containing elements in this set but not in the other"
*>
fn LinkedHashSet LinkedHashSet.difference(&self, Allocator allocator, LinkedHashSet* other)
{
LinkedHashSet result;
result.init(allocator, self.table.len, self.load_factor);
self.@each(;Value value)
{
if (!other.contains(value))
{
result.add(value);
}
};
return result;
}
fn LinkedHashSet LinkedHashSet.tdifference(&self, LinkedHashSet* other) => self.difference(tmem, other) @inline;
<*
Return (A ^ B)
@param [&in] other : "The other set to compare with"
@param [&inout] allocator : "Allocator for the new set"
@return "A new set containing elements in this set or the other, but not both"
*>
fn LinkedHashSet LinkedHashSet.symmetric_difference(&self, Allocator allocator, LinkedHashSet* other)
{
LinkedHashSet result;
result.init(allocator, self.table.len, self.load_factor);
result.add_all_from(self);
other.@each(;Value value)
{
if (!result.add(value))
{
result.remove(value);
}
};
return result;
}
fn LinkedHashSet LinkedHashSet.tsymmetric_difference(&self, LinkedHashSet* other) => self.symmetric_difference(tmem, other) @inline;
<*
Check if this hash set is a subset of another set.
@param [&in] other : "The other set to check against"
@return "True if all elements of this set are in the other set"
*>
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;
};
return true;
}
// --- private methods
fn void LinkedHashSet.add_entry(&set, uint hash, Value value, uint bucket_index) @private
{
LinkedEntry* entry = allocator::new(set.allocator, LinkedEntry, {
.hash = hash,
.value = value,
.next = set.table[bucket_index],
.before = set.tail,
.after = null
});
// Update bucket chain
set.table[bucket_index] = entry;
// Update linked list
if (set.tail)
{
set.tail.after = entry;
entry.before = set.tail;
}
else
{
set.head = entry;
}
set.tail = entry;
if (set.count++ >= set.threshold)
{
set.resize(set.table.len * 2);
}
}
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;
if ((e.hash & old_capacity) == 0)
{
if (!lo_tail)
{
lo_head = e;
}
else
{
lo_tail.next = e;
}
lo_tail = e;
}
else
{
if (!hi_tail)
{
hi_head = e;
}
else
{
hi_tail.next = e;
}
hi_tail = e;
}
e.next = null;
e = next;
}
while (e);
if (lo_tail)
{
lo_tail.next = null;
new_table[i] = lo_head;
}
if (hi_tail)
{
hi_tail.next = null;
new_table[i + old_capacity] = hi_head;
}
}
set.free_internal(old_table.ptr);
}
fn usz? LinkedHashSet.to_format(&self, Formatter* f) @dynamic
{
usz len;
len += f.print("{ ")!;
self.@each(; Value value)
{
if (len > 2) len += f.print(", ")!;
len += f.printf("%s", value)!;
};
return len + f.print(" }");
}
fn void LinkedHashSet.transfer(&set, LinkedEntry*[] new_table) @private
{
LinkedEntry*[] src = set.table;
uint new_capacity = new_table.len;
foreach (uint j, LinkedEntry *e : src)
{
if (!e) continue;
do
{
LinkedEntry* next = e.next;
uint i = index_for(e.hash, new_capacity);
e.next = new_table[i];
new_table[i] = e;
e = next;
}
while (e);
}
}
fn void LinkedHashSet.put_for_create(&set, Value value) @private
{
uint hash = rehash(value.hash());
uint i = index_for(hash, set.table.len);
for (LinkedEntry *e = set.table[i]; e != null; e = e.next)
{
if (e.hash == hash && equals(value, e.value))
{
// Value already exists, no need to do anything
return;
}
}
set.create_entry(hash, value, i);
}
fn void LinkedHashSet.free_internal(&set, void* ptr) @inline @private
{
allocator::free(set.allocator, ptr);
}
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,
.next = set.table[bucket_index],
.before = set.tail,
.after = null
});
set.table[bucket_index] = entry;
// Update linked list
if (set.tail)
{
set.tail.after = entry;
entry.before = set.tail;
}
else
{
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))
{
if (prev)
{
prev.next = e.next;
}
else
{
set.table[i] = e.next;
}
if (e.before)
{
e.before.after = e.after;
}
else
{
set.head = e.after;
}
if (e.after)
{
e.after.before = e.before;
}
else
{
set.tail = e.before;
}
set.count--;
set.free_entry(e);
return true;
}
prev = e;
e = e.next;
}
return false;
}
fn void LinkedHashSet.free_entry(&set, LinkedEntry *entry) @private
{
allocator::free(set.allocator, entry);
}
struct LinkedHashSetIterator
{
LinkedHashSet* set;
LinkedEntry* current;
bool started;
}
fn LinkedHashSetIterator LinkedHashSet.iter(&set) => { .set = set, .current = set.head, .started = false };
fn bool LinkedHashSetIterator.next(&self)
{
if (!self.started)
{
self.current = self.set.head;
self.started = true;
}
else if (self.current)
{
self.current = self.current.after;
}
return self.current != null;
}
fn Value*? LinkedHashSetIterator.get(&self)
{
return self.current ? &self.current.value : NOT_FOUND?;
}
fn bool LinkedHashSetIterator.has_next(&self)
{
if (!self.started) return self.set.head != null;
return self.current && self.current.after != null;
}
fn usz LinkedHashSetIterator.len(&self) @operator(len)
{
return self.set.count;
}
int dummy @local;

View File

@@ -2,14 +2,13 @@
// 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
struct Node @private
{
Node* next;
Node* prev;
Node *next;
Node *prev;
Type value;
}
@@ -17,31 +16,8 @@ struct LinkedList
{
Allocator allocator;
usz size;
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);
Node *_first;
Node *_last;
}
<*
@@ -92,11 +68,6 @@ 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;
@@ -114,11 +85,6 @@ 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;
@@ -167,7 +133,6 @@ macro Node* LinkedList.node_at_index(&self, usz index)
while (index--) node = node.next;
return node;
}
<*
@require index < self.size
*>
@@ -176,14 +141,6 @@ 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
*>
@@ -192,26 +149,6 @@ 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
*>
@@ -397,69 +334,3 @@ 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;
}

View File

@@ -13,7 +13,7 @@ const Allocator LIST_HEAP_ALLOCATOR = (Allocator)&dummy;
const List ONHEAP = { .allocator = LIST_HEAP_ALLOCATOR };
macro bool type_is_overaligned() => Type.alignof > mem::DEFAULT_MEM_ALIGNMENT;
macro 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.push_all(values) @inline;
self.add_array(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.push_all(values) @inline;
self.add_array(values) @inline;
return self;
}
@@ -137,10 +137,9 @@ fn Type? List.pop_first(&self)
*>
fn void List.remove_at(&self, usz index)
{
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];
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];
}
fn void List.add_all(&self, List* other_list)
@@ -199,21 +198,7 @@ fn Type[] List.array_view(&self)
@param [in] array
@ensure self.size >= array.len
*>
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)
fn void List.add_array(&self, Type[] array)
{
if (!array.len) return;
self.reserve(array.len);

View File

@@ -178,14 +178,17 @@ fn void Object.init_array_if_needed(&self) @private
fn void Object.set_object(&self, String key, Object* new_object) @private
{
self.init_map_if_needed();
Object*? val = self.map.get_entry(key).value;
defer (void)val.free();
ObjectInternalMapEntry*? entry = self.map.get_entry(key);
defer
{
(void)entry.value.free();
}
self.map.set(key, new_object);
}
<*
@require self.allocator != null : "This object is not properly initialized, was it really created using 'new'"
@require $typeof(value) != void* ||| value == null : "void pointers cannot be stored in an object"
@require !@typeis(value, void*) ||| value == null : "void pointers cannot be stored in an object"
*>
macro Object* Object.object_from_value(&self, value) @private
{
@@ -203,7 +206,7 @@ macro Object* Object.object_from_value(&self, value) @private
return value;
$case $Type.typeid == void*.typeid:
return &NULL_OBJECT;
$case $defined(String x = value):
$case $assignable(value, String):
return new_string(value, self.allocator);
$default:
$error "Unsupported object type.";

View File

@@ -1,75 +1,16 @@
module std::collections::pair{Type1, Type2};
import std::io;
module std::collections::tuple{Type1, Type2};
struct Pair (Printable)
struct Tuple
{
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 $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)
{
*a = self.first;
*b = self.second;
}
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;
}
module std::collections::triple{Type1, Type2, Type3};
import std::io;
struct Triple (Printable)
struct Triple
{
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 $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)
{
*a = self.first;
*b = self.second;
*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")
{
Type1 first;
Type2 second;
}
}

View File

@@ -7,12 +7,10 @@ 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 : const char
enum QOIColorspace : char (char id)
{
<* sRGB with linear alpha *>
SRGB = 0,
<* all channels linear *>
LINEAR = 1
SRGB = 0, // sRGB with linear alpha
LINEAR = 1 // all channels linear
}
<*
@@ -21,7 +19,7 @@ enum QOIColorspace : const char
AUTO can be used when decoding to automatically determine
the channels from the file's header.
*>
enum QOIChannels : const inline char
enum QOIChannels : char (char id)
{
AUTO = 0,
RGB = 3,
@@ -134,7 +132,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;
uint image_size = pixels * desc.channels.id;
if (image_size != input.len) return INVALID_DATA?;
// allocate memory for encoded data (output)
@@ -148,13 +146,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,
.colorspace = desc.colorspace
.channels = desc.channels.id,
.colorspace = desc.colorspace.id
};
uint pos = Header.sizeof; // Current position in output
uint loc; // Current position in image (top-left corner)
uint loc_end = image_size - desc.channels; // End of image data
uint loc_end = image_size - desc.channels.id; // End of image data
char run_length = 0; // Length of the current run
Pixel[64] palette; // Zero-initialized by default
@@ -165,7 +163,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)
for (loc = 0; loc < image_size; loc += desc.channels.id)
{
// set previous pixel
prev = p;
@@ -294,8 +292,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 = header.channels;
desc.colorspace = header.colorspace;
desc.channels = @enumcast(QOIChannels, header.channels)!; // Rethrow if invalid
desc.colorspace = @enumcast(QOIColorspace, header.colorspace)!; // Rethrow if invalid
if (desc.channels == AUTO) return INVALID_DATA?; // Channels must be specified in the header
// check width and height
@@ -316,11 +314,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;
usz image_size = (usz)pixels * channels.id;
char[] image = allocator::alloc_array(allocator, char, image_size);
defer catch allocator::free(allocator, image);
for (loc = 0; loc < image_size; loc += channels)
for (loc = 0; loc < image_size; loc += channels.id)
{
// get chunk tag
tag = data[pos];
@@ -393,22 +391,31 @@ const OP_RUN = 0b11;
struct Header @packed
{
<* magic bytes "qoif" *>
uint be_magic;
<* image width in pixels (BE) *>
uint be_width;
<* image height in pixels (BE) *>
uint be_height;
uint be_magic; // magic bytes "qoif"
uint be_width; // image width in pixels (BE)
uint be_height; // image height in pixels (BE)
// informative fields
<* 3 = RGB, 4 = RGB *>
QOIChannels channels;
<* 0 = sRGB with linear alpha, 1 = all channels linear *>
QOIColorspace colorspace;
char channels; // 3 = RGB, 4 = RGB
char colorspace; // 0 = sRGB with linear alpha, 1 = all channels linear
}
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)
{

View File

@@ -138,7 +138,7 @@ fn void*? ArenaAllocator.resize(&self, void *old_pointer, usz size, usz alignmen
}
// Otherwise just allocate new memory.
void* mem = self.acquire(size, NO_ZERO, alignment)!;
mem::copy(mem, old_pointer, math::min(size, old_size), mem::DEFAULT_MEM_ALIGNMENT, mem::DEFAULT_MEM_ALIGNMENT);
mem::copy(mem, old_pointer, old_size, mem::DEFAULT_MEM_ALIGNMENT, mem::DEFAULT_MEM_ALIGNMENT);
return mem;
}

View File

@@ -137,7 +137,7 @@ fn void*? BackedArenaAllocator.resize(&self, void* pointer, usz size, usz alignm
}
AllocChunk* data = self.acquire(size, NO_ZERO, alignment)!;
mem::copy(data, pointer, math::min(size, chunk.size), mem::DEFAULT_MEM_ALIGNMENT, mem::DEFAULT_MEM_ALIGNMENT);
mem::copy(data, pointer, chunk.size, mem::DEFAULT_MEM_ALIGNMENT, mem::DEFAULT_MEM_ALIGNMENT);
return data;
}

View File

@@ -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), whereas the BackedArenaAllocator will have heap allocator characteristics.
memory from that memory), wheras the BackedArenaAllocator will have heap allocator characteristics.
*>
struct DynamicArenaAllocator (Allocator)
{
@@ -117,7 +117,7 @@ fn void*? DynamicArenaAllocator.resize(&self, void* old_pointer, usz size, usz a
return old_pointer;
}
void* new_mem = self.acquire(size, NO_ZERO, alignment)!;
mem::copy(new_mem, old_pointer, math::min(old_size, size), mem::DEFAULT_MEM_ALIGNMENT);
mem::copy(new_mem, old_pointer, old_size, mem::DEFAULT_MEM_ALIGNMENT);
return new_mem;
}

View File

@@ -8,9 +8,12 @@ import libc;
<*
The LibcAllocator is a wrapper around malloc to conform to the Allocator interface.
*>
typedef LibcAllocator (Allocator) = uptr;
typedef LibcAllocator (Allocator, Printable) = uptr;
const LibcAllocator LIBC_ALLOCATOR = {};
fn String LibcAllocator.to_string(&self, Allocator allocator) @dynamic => "Libc allocator".copy(allocator);
fn usz? LibcAllocator.to_format(&self, Formatter *format) @dynamic => format.print("Libc allocator");
module std::core::mem::allocator @if(env::POSIX);
import std::os;
import libc;

View File

@@ -1,5 +1,5 @@
module std::core::mem::allocator;
import std::math;
<*
The OnStackAllocator is similar to the ArenaAllocator: it allocates from a chunk of memory
given to it.
@@ -124,7 +124,7 @@ fn void*? OnStackAllocator.resize(&self, void* old_pointer, usz size, usz alignm
OnStackAllocatorHeader* header = old_pointer - OnStackAllocatorHeader.sizeof;
usz old_size = header.size;
void* mem = self.acquire(size, NO_ZERO, alignment)!;
mem::copy(mem, old_pointer, math::min(old_size, size), mem::DEFAULT_MEM_ALIGNMENT, mem::DEFAULT_MEM_ALIGNMENT);
mem::copy(mem, old_pointer, old_size, mem::DEFAULT_MEM_ALIGNMENT, mem::DEFAULT_MEM_ALIGNMENT);
return mem;
}

View File

@@ -1,5 +1,6 @@
module std::core::mem::allocator @if(!(env::POSIX || env::WIN32) || !$feature(VMEM_TEMP));
module std::core::mem::allocator;
import std::io, std::math;
import std::core::sanitizer::asan;
// This implements the temp allocator.
// The temp allocator is a specialized allocator only intended for use where
@@ -326,81 +327,3 @@ fn void*? TempAllocator.acquire(&self, usz size, AllocInitType init_type, usz al
return &page.data[0];
}
module std::core::mem::allocator @if((env::POSIX || env::WIN32) && $feature(VMEM_TEMP));
import std::math;
tlocal VmemOptions temp_allocator_default_options = {
.shrink_on_reset = env::MEMORY_ENV != NORMAL,
.protect_unused_pages = env::COMPILER_OPT_LEVEL <= O1 || env::COMPILER_SAFE_MODE,
.scratch_released_data = env::COMPILER_SAFE_MODE
};
fn TempAllocator*? new_temp_allocator(Allocator allocator, usz size, usz reserve = temp_allocator_reserve_size, usz min_size = temp_allocator_min_size, usz realloc_size = temp_allocator_realloc_size)
{
Vmem mem;
TempAllocator* t = allocator::new(allocator, TempAllocator);
defer catch allocator::free(allocator, t);
t.vmem.init(preferred_size: isz.sizeof > 4 ? 4 * mem::GB : 512 * mem::MB,
reserve_page_size: isz.sizeof > 4 ? 256 * mem::KB : 0,
options: temp_allocator_default_options)!;
t.allocator = allocator;
return t;
}
struct TempAllocator (Allocator)
{
Vmem vmem;
TempAllocator* derived;
Allocator allocator;
}
<*
@require size > 0
@require !alignment || math::is_power_of_2(alignment)
@require alignment <= mem::MAX_MEMORY_ALIGNMENT : `alignment too big`
*>
fn void*? TempAllocator.acquire(&self, usz size, AllocInitType init_type, usz alignment) @dynamic
{
return self.vmem.acquire(size, init_type, alignment) @inline;
}
fn TempAllocator*? TempAllocator.derive_allocator(&self, usz reserve = 0)
{
if (self.derived) return self.derived;
return self.derived = new_temp_allocator(self.allocator, 0)!;
}
<*
Reset the entire temp allocator, destroying all children
*>
fn void TempAllocator.reset(&self)
{
TempAllocator* child = self.derived;
if (!child) return;
child.reset();
child.vmem.reset(0);
}
fn void TempAllocator.free(&self)
{
self.destroy();
}
fn void TempAllocator.destroy(&self) @local
{
TempAllocator* child = self.derived;
if (!child) return;
child.destroy();
self.vmem.free() @inline;
allocator::free(self.allocator, self) @inline;
}
fn void*? TempAllocator.resize(&self, void* pointer, usz size, usz alignment) @dynamic
{
return self.vmem.resize(pointer, size, alignment) @inline;
}
fn void TempAllocator.release(&self, void* old_pointer, bool b) @dynamic
{
self.vmem.release(old_pointer, b) @inline;
}

View File

@@ -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 embarrassingly single-threaded, so
// It is also embarassingly 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()
}
}
}
}
}

View File

@@ -1,255 +0,0 @@
module std::core::mem::allocator @if(env::POSIX || env::WIN32);
import std::math, std::os::posix, libc, std::bits;
import std::core::mem;
import std::core::env;
// Virtual Memory allocator
faultdef VMEM_RESERVE_FAILED;
struct Vmem (Allocator)
{
VirtualMemory memory;
usz allocated;
usz pagesize;
usz page_pot;
usz last_page;
usz high_water;
VmemOptions options;
}
bitstruct VmemOptions : int
{
<* 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;
}
<*
Implements the Allocator interface method.
@require !reserve_page_size || math::is_power_of_2(reserve_page_size)
@require reserve_page_size <= preferred_size : "The min reserve_page_size size must be less or equal to the preferred size"
@require preferred_size >= 1 * mem::KB : "The preferred size must exceed 1 KB"
@return? mem::INVALID_ALLOC_SIZE, mem::OUT_OF_MEMORY, VMEM_RESERVE_FAILED
*>
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?;
while (preferred_size >= min_size)
{
memory = vm::virtual_alloc(preferred_size, PROTECTED);
// It worked?
if (try memory) break;
switch (@catch(memory))
{
case mem::OUT_OF_MEMORY:
case vm::RANGE_OVERFLOW:
// Try a smaller size.
preferred_size /= 2;
continue;
default:
break;
}
}
if (catch memory) return VMEM_RESERVE_FAILED?;
if (page_size > preferred_size) page_size = preferred_size;
$if env::ADDRESS_SANITIZER:
asan::poison_memory_region(memory.ptr, memory.size);
$endif
*self = { .memory = memory,
.high_water = 0,
.pagesize = page_size,
.page_pot = page_size.ctz(),
.options = options,
};
}
<*
Implements the Allocator interface method.
@require !alignment || math::is_power_of_2(alignment)
@require alignment <= mem::MAX_MEMORY_ALIGNMENT : `alignment too big`
@require size > 0
@return? mem::INVALID_ALLOC_SIZE, mem::OUT_OF_MEMORY
*>
fn void*? Vmem.acquire(&self, usz size, AllocInitType init_type, usz alignment) @dynamic
{
alignment = alignment_for_allocation(alignment);
usz total_len = self.memory.size;
if (size > total_len) return mem::INVALID_ALLOC_SIZE?;
void* start_mem = self.memory.ptr;
void* unaligned_pointer_to_offset = start_mem + self.allocated + VmemHeader.sizeof;
void* mem = mem::aligned_pointer(unaligned_pointer_to_offset, alignment);
usz after = (usz)(mem - start_mem) + size;
if (after > total_len) return mem::OUT_OF_MEMORY?;
if (init_type == ZERO && self.high_water <= self.allocated)
{
init_type = NO_ZERO;
}
protect(self, after)!;
VmemHeader* header = mem - VmemHeader.sizeof;
header.size = size;
if (init_type == ZERO) mem::clear(mem, size, mem::DEFAULT_MEM_ALIGNMENT);
return mem;
}
fn bool Vmem.owns_pointer(&self, void* ptr) @inline
{
return (uptr)ptr >= (uptr)self.memory.ptr && (uptr)ptr < (uptr)self.memory.ptr + self.memory.size;
}
<*
Implements the Allocator interface method.
@require !alignment || math::is_power_of_2(alignment)
@require alignment <= mem::MAX_MEMORY_ALIGNMENT : `alignment too big`
@require old_pointer != null
@require size > 0
@return? mem::INVALID_ALLOC_SIZE, mem::OUT_OF_MEMORY
*>
fn void*? Vmem.resize(&self, void *old_pointer, usz size, usz alignment) @dynamic
{
if (size > self.memory.size) return mem::INVALID_ALLOC_SIZE?;
alignment = alignment_for_allocation(alignment);
assert(self.owns_pointer(old_pointer), "Pointer originates from a different allocator: %p, not in %p - %p", old_pointer, self.memory.ptr, self.memory.ptr + self.allocated);
VmemHeader* header = old_pointer - VmemHeader.sizeof;
usz old_size = header.size;
if (old_size == size) return old_pointer;
// Do last allocation and alignment match?
if (self.memory.ptr + self.allocated == old_pointer + old_size && mem::ptr_is_aligned(old_pointer, alignment))
{
if (old_size > size)
{
unprotect(self, self.allocated + size - old_size);
}
else
{
usz allocated = self.allocated + size - old_size;
if (allocated > self.memory.size) return mem::OUT_OF_MEMORY?;
protect(self, allocated)!;
}
header.size = size;
return old_pointer;
}
if (old_size > size)
{
$if env::ADDRESS_SANITIZER:
asan::poison_memory_region(old_pointer + size, old_size - size);
$endif
header.size = size;
return old_pointer;
}
// Otherwise just allocate new memory.
void* mem = self.acquire(size, NO_ZERO, alignment)!;
assert(size > old_size);
mem::copy(mem, old_pointer, old_size, mem::DEFAULT_MEM_ALIGNMENT, mem::DEFAULT_MEM_ALIGNMENT);
return mem;
}
<*
Implements the Allocator interface method.
@require ptr != null
*>
fn void Vmem.release(&self, void* ptr, bool) @dynamic
{
assert(self.owns_pointer(ptr), "Pointer originates from a different allocator %p.", ptr);
VmemHeader* header = ptr - VmemHeader.sizeof;
// Reclaim memory if it's the last element.
if (ptr + header.size == self.memory.ptr + self.allocated)
{
unprotect(self, self.allocated - header.size - VmemHeader.sizeof);
}
}
fn usz Vmem.mark(&self)
{
return self.allocated;
}
<*
@require mark <= self.allocated : "Invalid mark"
*>
fn void Vmem.reset(&self, usz mark)
{
if (mark == self.allocated) return;
unprotect(self, mark);
}
fn void Vmem.free(&self)
{
if (!self.memory.ptr) return;
$switch:
$case env::ADDRESS_SANITIZER:
asan::poison_memory_region(self.memory.ptr, self.memory.size);
$case env::COMPILER_SAFE_MODE:
((char*)self.memory.ptr)[0:self.allocated] = 0xAA;
$endswitch
(void)self.memory.destroy();
*self = {};
}
// Internal data
struct VmemHeader @local
{
usz size;
char[*] data;
}
macro void? protect(Vmem* mem, usz after) @local
{
usz shift = mem.page_pot;
usz page_after = (after + mem.pagesize - 1) >> shift;
usz last_page = mem.last_page;
bool over_high_water = mem.high_water < after;
if (page_after > last_page)
{
usz page_start = last_page << shift;
usz page_len = (page_after - last_page) << shift;
mem.memory.commit(page_start, page_len)!;
if (mem.options.protect_unused_pages || over_high_water)
{
mem.memory.protect(page_start, page_len, READWRITE)!;
}
mem.last_page = page_after;
}
$if env::ADDRESS_SANITIZER:
asan::unpoison_memory_region(mem.memory.ptr + mem.allocated, after - mem.allocated);
$endif
mem.allocated = after;
if (over_high_water) mem.high_water = after;
}
macro void unprotect(Vmem* mem, usz after) @local
{
usz shift = mem.page_pot;
usz last_page = mem.last_page;
usz page_after = mem.last_page = (after + mem.pagesize - 1) >> shift;
$if env::ADDRESS_SANITIZER:
asan::poison_memory_region(mem.memory.ptr + after, mem.allocated - after);
$else
if (mem.options.scratch_released_data)
{
mem::set(mem.memory.ptr + after, 0xAA, mem.allocated - after);
}
$endif
if ((mem.options.shrink_on_reset || mem.options.protect_unused_pages) && page_after < last_page)
{
usz start = page_after << shift;
usz len = (last_page - page_after) << shift;
if (mem.options.shrink_on_reset) (void)mem.memory.decommit(start, len, false);
if (mem.options.protect_unused_pages) (void)mem.memory.protect(start, len, PROTECTED);
}
mem.allocated = after;
}

View File

@@ -1,133 +0,0 @@
module std::core::string::ansi;
enum Ansi : const inline String
{
RESET = "\e[0m",
BOLD = "\e[1m",
DIM = "\e[2m",
ITALIC = "\e[3m",
UNDERLINE = "\e[4m",
BLINK = "\e[5m",
BLINK_FAST = "\e[6m",
INVERT = "\e[7m",
HIDDEN = "\e[8m",
STRIKETHROUGH = "\e[9m",
DOUBLE_UNDER = "\e[21m",
NO_DIM = "\e[22m",
NO_ITALIC = "\e[23m",
NO_UNDERLINE = "\e[24m",
NO_BLINK = "\e[25m",
NO_INVERT = "\e[27m",
NO_HIDDEN = "\e[28m",
NO_STRIKETHROUGH = "\e[29m",
BLACK = "\e[30m",
RED = "\e[31m",
GREEN = "\e[32m",
YELLOW = "\e[33m",
BLUE = "\e[34m",
MAGENTA = "\e[35m",
CYAN = "\e[36m",
WHITE = "\e[37m",
DEFAULT = "\e[39m",
BRIGHT_BLACK = "\e[90m",
BRIGHT_RED = "\e[91m",
BRIGHT_GREEN = "\e[92m",
BRIGHT_YELLOW = "\e[93m",
BRIGHT_BLUE = "\e[94m",
BRIGHT_MAGENTA = "\e[95m",
BRIGHT_CYAN = "\e[96m",
BRIGHT_WHITE = "\e[97m",
BG_BLACK = "\e[40m",
BG_RED = "\e[41m",
BG_GREEN = "\e[42m",
BG_YELLOW = "\e[43m",
BG_BLUE = "\e[44m",
BG_MAGENTA = "\e[45m",
BG_CYAN = "\e[46m",
BG_WHITE = "\e[47m",
BG_DEFAULT = "\e[49m",
BG_BRIGHT_BLACK = "\e[100m",
BG_BRIGHT_RED = "\e[101m",
BG_BRIGHT_GREEN = "\e[102m",
BG_BRIGHT_YELLOW = "\e[103m",
BG_BRIGHT_BLUE = "\e[104m",
BG_BRIGHT_MAGENTA = "\e[105m",
BG_BRIGHT_CYAN = "\e[106m",
BG_BRIGHT_WHITE = "\e[107m",
}
<*
8-bit color code
@return `the formatting char for the given background color`
*>
macro String color_8bit(char $index, bool $bg = false) @const
{
int $mode = $bg ? 4 : 3;
return @sprintf("\e[%s8;5;%sm", $mode, $index);
}
<*
24-bit color code
@return `the string for the given foreground color`
*>
macro String color_rgb(char $r, char $g, char $b, bool $bg = false) @const
{
int $mode = $bg ? 4 : 3;
return @sprintf("\e[%s8;2;%s;%s;%sm", $mode, $r, $g, $b);
}
<*
24-bit color code rgb
@require $rgb <= 0xFF_FF_FF : `Expected a 24 bit RGB value`
@return `the string char for the given foreground color`
*>
macro String color(uint $rgb, bool $bg = false) @const
{
int $mode = $bg ? 4 : 3;
return @sprintf("\e[%s8;2;%s;%s;%sm", $mode, $rgb >> 16, ($rgb & 0xFF00) >> 8, $rgb & 0xFF);
}
<*
24-bit color code rgb
@require rgb <= 0xFF_FF_FF : `Expected a 24 bit RGB value`
@return `the string char for the given foreground color`
*>
fn String make_color(Allocator mem, uint rgb, bool bg = false)
{
return make_color_rgb(mem, (char)(rgb >> 16), (char)((rgb & 0xFF00) >> 8), (char)rgb, bg);
}
<*
24-bit color code rgb
@require rgb <= 0xFF_FF_FF : `Expected a 24 bit RGB value`
@return `the string char for the given foreground color`
*>
fn String make_tcolor(uint rgb, bool bg = false)
{
return make_color_rgb(tmem, (char)(rgb >> 16), (char)((rgb & 0xFF00) >> 8), (char)rgb, bg);
}
<*
24-bit color code rgb
@return `the string char for the given foreground color`
*>
fn String make_color_rgb(Allocator mem, char r, char g, char b, bool bg = false)
{
return string::format(mem, "\e[%s8;2;%s;%s;%sm", bg ? 4 : 3, r, g, b);
}
<*
24-bit color code rgb
@return `the string char for the given foreground color`
*>
fn String make_tcolor_rgb(char r, char g, char b, bool bg = false)
{
return string::format(tmem, "\e[%s8;2;%s;%s;%sm", bg ? 4 : 3, r, g, b);
}

View File

@@ -1,13 +1,13 @@
module std::core::array;
import std::collections::pair, std::io;
import std::core::array::slice;
<*
Returns true if the array contains at least one element, else false
@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"
@require @typekind(array) == SLICE || @typekind(array) == ARRAY
@require @typeis(array[0], $typeof(element)) : "array and element must have the same type"
*>
macro bool contains(array, element)
{
@@ -15,21 +15,18 @@ 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 usz? index_of(array, element)
macro index_of(array, element)
{
foreach (i, &e : array)
{
@@ -38,7 +35,6 @@ macro usz? index_of(array, element)
return NOT_FOUND?;
}
<*
Slice a 2d array and create a Slice2d from it.
@@ -48,9 +44,9 @@ macro usz? 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 $kindof(array_ptr) == POINTER
@require $kindof(*array_ptr) == VECTOR || $kindof(*array_ptr) == ARRAY
@require $kindof((*array_ptr)[0]) == VECTOR || $kindof((*array_ptr)[0]) == 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
*>
macro slice2d(array_ptr, x = 0, xlen = 0, y = 0, ylen = 0)
{
@@ -63,13 +59,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 usz? rindex_of(array, element)
macro rindex_of(array, element)
{
foreach_r (i, &e : array)
{
@@ -78,16 +74,15 @@ macro usz? 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 $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"
@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"
@ensure result.len == arr1.len + arr2.len
*>
macro concat(Allocator allocator, arr1, arr2) @nodiscard
@@ -104,450 +99,15 @@ 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 $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"
@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"
@ensure return.len == arr1.len + arr2.len
*>
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
}
macro tconcat(arr1, arr2) @nodiscard => concat(tmem, arr1, arr2);

View File

@@ -112,27 +112,3 @@ 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");

View File

@@ -94,7 +94,7 @@ bitstruct UInt128LE : uint128 @littleendian
macro read(bytes, $Type)
{
char[] s;
$switch $kindof(bytes):
$switch @typekind(bytes):
$case POINTER:
s = (*bytes)[:$Type.sizeof];
$default:
@@ -110,7 +110,7 @@ macro read(bytes, $Type)
macro write(x, bytes, $Type)
{
char[] s;
$switch $kindof(bytes):
$switch @typekind(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 bool is_bitorder($Type)
macro 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
}
}

View File

@@ -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 convertible.
way. It relies on the fact that distinct types are not implicitly convertable.
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 bool @is_empty_macro_slot(#arg) @const @builtin => $typeof(#arg) == EmptySlot;
macro bool @is_valid_macro_slot(#arg) @const @builtin => $typeof(#arg) != EmptySlot;
macro @is_empty_macro_slot(#arg) @const @builtin => @typeis(#arg, EmptySlot);
macro @is_valid_macro_slot(#arg) @const @builtin => !@typeis(#arg, EmptySlot);
<*
Returns a random value at compile time.
@@ -39,28 +39,20 @@ macro bool @is_valid_macro_slot(#arg) @const @builtin => $typeof(#arg) != EmptyS
macro @rnd() @const @builtin => $$rnd();
/*
Use `NO_MORE_ELEMENT` when reading the end of an iterator, or accessing a result out of bounds.
Use `IteratorResult` when reading the end of an iterator, or accessing a result out of bounds.
*/
faultdef NO_MORE_ELEMENT @builtin;
/*
Use `NOT_FOUND` when trying to return a value from some collection but the element is missing.
Use `SearchResult` when trying to return a value from some collection but the element is missing.
*/
faultdef NOT_FOUND @builtin;
/*
Use `TYPE_MISMATCH` when an attempt at conversion fails.
Use `CastResult` 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();
@@ -69,7 +61,7 @@ alias VoidFn = fn void();
macro scope.
@param #variable : `the variable to store and restore`
@require $defined(#variable = #variable) : `Expected an actual variable`
@require values::@is_lvalue(#variable)
*>
macro void @scope(#variable; @body) @builtin
{
@@ -89,17 +81,13 @@ 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 $typeof(return) == $Type*
@ensure @typeis(return, $Type*)
@return? TYPE_MISMATCH
*>
macro anycast(any v, $Type) @builtin
@@ -108,80 +96,58 @@ macro anycast(any v, $Type) @builtin
return ($Type*)v.ptr;
}
macro bool @assignable_to(#foo, $Type) @const @builtin @deprecated("use '$defined($Type x = #foo)'") => $defined(*&&($Type){} = #foo);
macro @addr(#val) @builtin
{
$if $defined(&#val):
return &#val;
$else
return &&#val;
$endif
}
macro typeid @typeid(#value) @const @builtin
{
return $typeof(#value).typeid;
}
macro TypeKind @typekind(#value) @const @builtin @deprecated("Use `$kindof(#value)`.")
{
return $kindof(#value);
}
macro bool @typeis(#value, $Type) @const @builtin @deprecated("Use `$typeof(#value) == $Type` instead.")
{
return $typeof(#value).typeid == $Type.typeid;
}
fn bool print_backtrace(String message, int backtraces_to_ignore) @if (env::NATIVE_STACKTRACE) => @stack_mem(0x1100; Allocator smem)
fn bool print_backtrace(String message, int backtraces_to_ignore) @if(env::NATIVE_STACKTRACE) => @stack_mem(0x1100; Allocator smem)
{
Allocator t = allocator::current_temp;
TempAllocator* new_t = allocator::new_temp_allocator(smem, 0x1000)!!;
allocator::current_temp = new_t;
defer
{
allocator::current_temp = t;
new_t.free();
}
void*[256] buffer;
void*[] backtraces = backtrace::capture_current(&buffer);
backtraces_to_ignore++;
@stack_mem(2048; Allocator mem)
BacktraceList? backtrace = backtrace::symbolize_backtrace(tmem, backtraces);
if (catch backtrace) return false;
if (backtrace.len() <= backtraces_to_ignore) return false;
io::eprint("\nERROR: '");
io::eprint(message);
io::eprintn("'");
foreach (i, &trace : backtrace)
{
BacktraceList? backtrace = backtrace::symbolize_backtrace(mem, backtraces);
if (catch backtrace) return false;
if (backtrace.len() <= backtraces_to_ignore) return false;
io::eprint("\nERROR: '");
io::eprint(message);
io::eprintn("'");
foreach (i, &trace : backtrace)
if (i < backtraces_to_ignore) continue;
String inline_suffix = trace.is_inline ? " [inline]" : "";
if (trace.is_unknown())
{
if (i < backtraces_to_ignore) continue;
String inline_suffix = trace.is_inline ? " [inline]" : "";
if (trace.is_unknown())
{
io::eprintfn(" in ???%s", inline_suffix);
continue;
}
if (trace.has_file())
{
io::eprintfn(" in %s (%s:%d) [%s]%s", trace.function, trace.file, trace.line, trace.object_file, inline_suffix);
continue;
}
io::eprintfn(" in %s (source unavailable) [%s]%s", trace.function, trace.object_file, inline_suffix);
io::eprintfn(" in ???%s", inline_suffix);
continue;
}
};
if (trace.has_file())
{
io::eprintfn(" in %s (%s:%d) [%s]%s", trace.function, trace.file, trace.line, trace.object_file, inline_suffix);
continue;
}
io::eprintfn(" in %s (source unavailable) [%s]%s", trace.function, trace.object_file, inline_suffix);
}
return true;
}
fn void default_panic(String message, String file, String function, uint line) @if(env::NATIVE_STACKTRACE)
{
$if $defined(io::stderr) && env::PANIC_MSG:
if (!print_backtrace(message, 2))
{
io::eprintfn("\nERROR: '%s', in %s (%s:%d)", message, function, file, line);
}
$if $defined(io::stderr):
if (!print_backtrace(message, 2))
{
io::eprintfn("\nERROR: '%s', in %s (%s:%d)", message, function, file, line);
}
$endif
$$trap();
}
macro void abort(String string = "Unrecoverable error reached", ...) @format(0) @builtin @noreturn
macro void abort(String string = "Unrecoverable error reached", ...) @builtin @noreturn
{
panicf(string, $$FILE, $$FUNC, $$LINE, $vasplat);
$$trap();
@@ -189,22 +155,20 @@ macro void abort(String string = "Unrecoverable error reached", ...) @format(0)
bool in_panic @local = false;
fn void default_panic(String message, String file, String function, uint line) @if (!env::NATIVE_STACKTRACE)
fn void default_panic(String message, String file, String function, uint line) @if(!env::NATIVE_STACKTRACE)
{
$if $defined(io::stderr) && env::PANIC_MSG:
if (in_panic)
{
io::eprintn("Panic inside of panic.");
return;
}
in_panic = true;
$if $defined(io::stderr):
io::eprint("\nERROR: '");
io::eprint(message);
io::eprintfn("', in %s (%s:%d)", function, file, line);
$endif
in_panic = false;
if (in_panic)
{
io::eprintn("Panic inside of panic.");
return;
}
in_panic = true;
$if $defined(io::stderr):
io::eprint("\nERROR: '");
io::eprint(message);
io::eprintfn("', in %s (%s:%d)", function, file, line);
$endif
in_panic = false;
$$trap();
}
@@ -214,23 +178,21 @@ PanicFn panic = &default_panic;
fn void panicf(String fmt, String file, String function, uint line, args...)
{
$if $defined(io::stderr) && env::PANIC_MSG:
if (in_panic)
{
io::eprint("Panic inside of panic: ");
io::eprintn(fmt);
return;
}
in_panic = true;
@stack_mem(512; Allocator allocator)
{
DString s;
s.init(allocator);
s.appendf(fmt, ...args);
in_panic = false;
panic(s.str_view(), file, function, line);
};
$endif
if (in_panic)
{
io::eprint("Panic inside of panic: ");
io::eprintn(fmt);
return;
}
in_panic = true;
@stack_mem(512; Allocator allocator)
{
DString s;
s.init(allocator);
s.appendf(fmt, ...args);
in_panic = false;
panic(s.str_view(), file, function, line);
};
}
<*
@@ -285,7 +247,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 $typeof(return) == $Type*
@ensure @typeis(return, $Type)
*>
macro bitcast(expr, $Type) @builtin
{
@@ -302,7 +264,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 $typeof(return) == $Type*
@ensure @typeis(return, $Type)
@return? NOT_FOUND
*>
macro enum_by_name($Type, String enum_name) @builtin
@@ -319,8 +281,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 $defined($typeof(($Type){}.#value) v = value) : `Expected the value to match the type of the associated value`
@ensure $typeof(return) == $Type*
@require $assignable(value, $typeof(($Type){}.#value)) : `Expected the value to match the type of the associated value`
@ensure @typeis(return, $Type)
@return? NOT_FOUND
*>
macro @enum_from_value($Type, #value, value) @builtin @deprecated("Use Enum.lookup_field and Enum.lookup")
@@ -372,7 +334,7 @@ macro bool @unlikely(bool #value, $probability = 1.0) @builtin
<*
@require values::@is_int(#value) || values::@is_bool(#value)
@require $defined($typeof(#value) v = expected)
@require $assignable(expected, $typeof(#value))
@require $probability >= 0 && $probability <= 1.0
*>
macro @expect(#value, expected, $probability = 1.0) @builtin
@@ -422,39 +384,11 @@ 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 $kindof(#expr) == OPTIONAL : `@catch expects an Optional value`
@require @typekind(#expr) == OPTIONAL : `@catch expects an Optional value`
*>
macro fault @catch(#expr) @builtin
{
@@ -466,7 +400,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 $kindof(#expr) == OPTIONAL : `@ok expects an Optional value`
@require @typekind(#expr) == OPTIONAL : `@ok expects an Optional value`
*>
macro bool @ok(#expr) @builtin
{
@@ -474,67 +408,6 @@ macro bool @ok(#expr) @builtin
return true;
}
<*
Check if an Optional expression evaluates to a fault. If so, return it;
else, assign the result to an expression.
@require $defined(#v = #v) : "#v must be a variable"
@require $defined(#expr!) : "Expected an optional expression"
@require $defined(#v = #expr!!) : `Type of #expr must be an optional of #v's type`
*>
macro void? @try(#v, #expr) @builtin @maydiscard
{
var res = #expr;
if (catch err = res) return err?;
#v = res;
}
<*
Check if an Optional expression evaluates to a fault. If so, return true if it is the
expected fault, the optional if it is unexpected, or false if there was no fault and
the assign happened.
This can be used in like this:
while (true)
{
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
fault, we rethrow is. Without this macro, the code is instead written like:
while (true)
{
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 $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
{
var res = #expr;
if (catch err = res)
{
return err == expected_fault ? true : err?;
}
#v = res;
return false;
}
<*
@require $defined(&#value, (char*)&#value) : "This must be a value that can be viewed as a char array"
*>
@@ -547,27 +420,6 @@ macro isz @str_find(String $string, String $needle) @builtin => $$str_find($stri
macro String @str_upper(String $str) @builtin => $$str_upper($str);
macro String @str_lower(String $str) @builtin => $$str_lower($str);
macro uint @str_hash(String $str) @builtin => $$str_hash($str);
macro String @str_pascalcase(String $str) @builtin => $$str_pascalcase($str);
macro String @str_snakecase(String $str) @builtin => $$str_snakecase($str);
macro String @str_camelcase(String $str) @builtin => @str_capitalize($$str_pascalcase($str));
macro String @str_constantcase(String $str) @builtin => @str_upper($$str_snakecase($str));
macro String @str_replace(String $str, String $pattern, String $replace, uint $limit = 0) @builtin => $$str_replace($str, $pattern, $replace, $limit);
macro String @str_capitalize(String $str) @builtin
{
$switch $str.len:
$case 0: return $str;
$case 1: return $$str_upper($str);
$default: return $$str_upper($str[0:1]) +++ $str[1..];
$endswitch
}
macro String @str_uncapitalize(String $str) @builtin
{
$switch $str.len:
$case 0: return $str;
$case 1: return $$str_lower($str);
$default: return $$str_lower($str[0:1]) +++ $str[1..];
$endswitch
}
macro @generic_hash_core(h, value)
{
@@ -577,7 +429,7 @@ macro @generic_hash_core(h, value)
return h;
}
macro uint @generic_hash(value)
macro @generic_hash(value)
{
uint h = @generic_hash_core((uint)0x3efd4391, value);
$for var $cnt = 4; $cnt < $sizeof(value); $cnt += 4:
@@ -625,36 +477,24 @@ 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)a5hash::hash(c);
macro uint char[].hash(char[] c) => (uint)a5hash::hash(c);
macro uint String.hash(String c) => (uint)fnv32a::hash(c);
macro uint char[].hash(char[] c) => (uint)fnv32a::hash(c);
macro uint void*.hash(void* ptr) => @generic_hash(((ulong)(uptr)ptr));
<*
@require $kindof(array_ptr) == POINTER &&& $kindof(*array_ptr) == ARRAY
@require @typekind(array_ptr) == POINTER &&& @typekind(*array_ptr) == ARRAY
*>
macro uint hash_array(array_ptr) @local
{
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
return (uint)fnv32a::hash(((char*)array_ptr)[:$sizeof(*array_ptr)]);
}
<*
@require $kindof(vec) == VECTOR
@require @typekind(vec) == VECTOR
*>
macro uint hash_vec(vec) @local
{
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
return (uint)fnv32a::hash(((char*)&&vec)[:$sizeof(vec.len * $typeof(vec).inner.sizeof)]);
}
const MAX_FRAMEADDRESS = 128;

View File

@@ -6,7 +6,7 @@ module std::core::builtin;
<*
@require types::@comparable_value(a) && types::@comparable_value(b)
*>
macro bool less(a, b) @builtin
macro less(a, b) @builtin
{
$switch:
$case $defined(a.less):
@@ -21,7 +21,7 @@ macro bool less(a, b) @builtin
<*
@require types::@comparable_value(a) && types::@comparable_value(b)
*>
macro bool less_eq(a, b) @builtin
macro less_eq(a, b) @builtin
{
$switch:
$case $defined(a.less):
@@ -36,7 +36,7 @@ macro bool less_eq(a, b) @builtin
<*
@require types::@comparable_value(a) && types::@comparable_value(b)
*>
macro bool greater(a, b) @builtin
macro 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 bool greater_eq(a, b) @builtin
macro greater_eq(a, b) @builtin
{
$switch:
$case $defined(a.less):
@@ -126,36 +126,3 @@ 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
}

View File

@@ -2,8 +2,6 @@
// 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;
@@ -61,57 +59,3 @@ 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
}

View File

@@ -137,13 +137,12 @@ const bool BACKTRACE = $$BACKTRACE;
const usz LLVM_VERSION = $$LLVM_VERSION;
const bool BENCHMARKING = $$BENCHMARKING;
const bool TESTING = $$TESTING;
const bool PANIC_MSG = $$PANIC_MSG;
const MemoryEnvironment MEMORY_ENV = MemoryEnvironment.from_ordinal($$MEMORY_ENVIRONMENT);
const bool TRACK_MEMORY = DEBUG_SYMBOLS && (COMPILER_SAFE_MODE || TESTING);
const bool X86_64 = ARCH_TYPE == X86_64;
const bool X86 = ARCH_TYPE == X86;
const bool AARCH64 = ARCH_TYPE == AARCH64;
const bool NATIVE_STACKTRACE = LINUX || DARWIN || OPENBSD || WIN32;
const bool NATIVE_STACKTRACE = LINUX || DARWIN || WIN32;
const bool LINUX = LIBC && OS_TYPE == LINUX;
const bool DARWIN = LIBC && os_is_darwin();
const bool WIN32 = LIBC && OS_TYPE == WIN32;
@@ -154,18 +153,13 @@ 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 @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 WASM_NOLIBC @builtin = !LIBC && ARCH_TYPE == ArchType.WASM32 || ARCH_TYPE == ArchType.WASM64;
const bool ADDRESS_SANITIZER = $$ADDRESS_SANITIZER;
const bool MEMORY_SANITIZER = $$MEMORY_SANITIZER;
const bool THREAD_SANITIZER = $$THREAD_SANITIZER;
const bool ANY_SANITIZER = ADDRESS_SANITIZER || MEMORY_SANITIZER || THREAD_SANITIZER;
const int LANGUAGE_DEV_VERSION = $$LANGUAGE_DEV_VERSION;
const bool HAS_NATIVE_ERRNO = env::LINUX || env::ANDROID || env::OPENBSD || env::DARWIN || env::WIN32;
const bool HAS_NATIVE_ERRNO = env::LINUX || env::ANDROID || env::DARWIN || env::WIN32;
macro bool os_is_darwin() @const
{
@@ -204,8 +198,6 @@ macro bool os_is_posix() @const
return false;
$endswitch
}
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);

View File

@@ -1,239 +0,0 @@
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"
};

View File

@@ -3,15 +3,10 @@
// a copy of which can be found in the LICENSE_STDLIB file.
module std::core::mem;
import std::core::mem::allocator @public;
import std::os::posix, std::os::win32;
import std::math;
const MAX_MEMORY_ALIGNMENT = 0x1000_0000;
const DEFAULT_MEM_ALIGNMENT = (void*.alignof) * 2;
const ulong KB = 1024;
const ulong MB = KB * 1024;
const ulong GB = MB * 1024;
const ulong TB = GB * 1024;
faultdef OUT_OF_MEMORY, INVALID_ALLOC_SIZE;
@@ -20,35 +15,14 @@ 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:
$case env::POSIX:
static usz pagesize;
if (pagesize) return pagesize;
return pagesize = posix::getpagesize();
$case env::WIN32:
static usz pagesize;
if (pagesize) return pagesize;
Win32_SYSTEM_INFO info;
win32::getSystemInfo(&info);
return pagesize = info.dwPageSize;
$default:
return 4096;
$endswitch
}
<*
Load a vector from memory according to a mask assuming default alignment.
@param ptr : "The pointer address to load from."
@param mask : "The mask for the load"
@param passthru : "The value to use for non masked values"
@require $defined(*ptr = passthru) : "Pointer and passthru must match"
@require $kindof(passthru) == VECTOR : "Expected passthru to be a vector"
@require $assignable(&&passthru, $typeof(ptr)) : "Pointer and passthru must match"
@require @typekind(passthru) == VECTOR : "Expected passthru to be a vector"
@require passthru.len == mask.len : "Mask and passthru must have the same length"
@return "A vector with the loaded values where the mask is true, passthru where the mask is false"
@@ -66,13 +40,12 @@ 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 $defined(*ptr = passthru) : "Pointer and passthru must match"
@require $kindof(passthru) == VECTOR : "Expected passthru to be a vector"
@require $assignable(&&passthru, $typeof(ptr)) : "Pointer and passthru must match"
@require @typekind(passthru) == VECTOR : "Expected passthru to be a vector"
@require passthru.len == mask.len : "Mask and passthru must have the same length"
@require @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)
{
@@ -86,9 +59,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 $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 @typekind(ptrvec) == VECTOR : "Expected ptrvec to be a vector"
@require @typekind(passthru) == VECTOR : "Expected passthru to be a vector"
@require $assignable(&&passthru[0], $typeof(ptrvec[0])) : "Pointer and passthru must match"
@require passthru.len == mask.len : "Mask and passthru must have the same length"
@require mask.len == ptrvec.len : "Mask and ptrvec must have the same length"
@@ -108,9 +81,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 $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 @typekind(ptrvec) == VECTOR : "Expected ptrvec to be a vector"
@require @typekind(passthru) == VECTOR : "Expected passthru to be a vector"
@require $assignable(&&passthru[0], $typeof(ptrvec[0])) : "Pointer and passthru must match"
@require passthru.len == mask.len : "Mask and passthru must have the same length"
@require mask.len == ptrvec.len : "Mask and ptrvec must have the same length"
@require @constant_is_power_of_2($alignment) : "The alignment must be a power of two"
@@ -130,8 +103,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 $defined(*ptr = value) : "Pointer and value must match"
@require $kindof(value) == VECTOR : "Expected value to be a vector"
@require $assignable(&&value, $typeof(ptr)) : "Pointer and value must match"
@require @typekind(value) == VECTOR : "Expected value to be a vector"
@require value.len == mask.len : "Mask and value must have the same length"
*>
macro masked_store(ptr, value, bool[<*>] mask)
@@ -145,8 +118,8 @@ macro masked_store(ptr, value, bool[<*>] mask)
@param mask : "The mask for the store"
@param $alignment : "The alignment of the pointer"
@require $defined(*ptr = value) : "Pointer and value must match"
@require $kindof(value) == VECTOR : "Expected value to be a vector"
@require $assignable(&&value, $typeof(ptr)) : "Pointer and value must match"
@require @typekind(value) == VECTOR : "Expected value to be a vector"
@require value.len == mask.len : "Mask and value must have the same length"
@require @constant_is_power_of_2($alignment) : "The alignment must be a power of two"
@@ -160,9 +133,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 $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 @typekind(ptrvec) == VECTOR : "Expected ptrvec to be a vector"
@require @typekind(value) == VECTOR : "Expected value to be a vector"
@require $assignable(&&value[0], $typeof(ptrvec[0])) : "Pointer and value must match"
@require value.len == mask.len : "Mask and value must have the same length"
@require mask.len == ptrvec.len : "Mask and ptrvec must have the same length"
@@ -178,9 +151,9 @@ macro scatter(ptrvec, value, bool[<*>] mask)
@param mask : "The mask for the store"
@param $alignment : "The alignment of the load"
@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 @typekind(ptrvec) == VECTOR : "Expected ptrvec to be a vector"
@require @typekind(value) == VECTOR : "Expected value to be a vector"
@require $assignable(&&value[0], $typeof(ptrvec[0])) : "Pointer and value must match"
@require value.len == mask.len : "Mask and value must have the same length"
@require mask.len == ptrvec.len : "Mask and ptrvec must have the same length"
@require @constant_is_power_of_2($alignment) : "The alignment must be a power of two"
@@ -242,9 +215,6 @@ macro @volatile_store(#x, value) @builtin
return $$volatile_store(&#x, ($typeof(#x))value);
}
<*
All possible atomic orderings
*>
enum AtomicOrdering : int
{
NOT_ATOMIC, // Not atomic
@@ -312,7 +282,7 @@ macro compare_exchange_volatile(ptr, compare, value, AtomicOrdering $success = S
*>
fn usz aligned_offset(usz offset, usz alignment)
{
return (offset + alignment - 1) & ~(alignment - 1);
return alignment * ((offset + alignment - 1) / alignment);
}
macro void* aligned_pointer(void* ptr, usz alignment)
@@ -328,11 +298,6 @@ fn bool ptr_is_aligned(void* ptr, usz alignment) @inline
return (uptr)ptr & ((uptr)alignment - 1) == 0;
}
fn bool ptr_is_page_aligned(void* ptr) @inline
{
return (uptr)ptr & ((uptr)os_pagesize() - 1) == 0;
}
macro void zero_volatile(char[] data)
{
$$memset(data.ptr, (char)0, data.len, true, (usz)1);
@@ -440,7 +405,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 $defined(a = b, b = a)
@require values::@assign_to(a, b) && values::@assign_to(b, a)
*>
macro bool equals(a, b, isz len = -1, usz $align = 0)
{
@@ -653,7 +618,7 @@ macro void @pool(usz reserve = 0; @body) @builtin
@body();
}
module std::core::mem @if(env::FREESTANDING_WASM);
module std::core::mem @if(WASM_NOLIBC);
import std::core::mem::allocator @public;
SimpleHeapAllocator wasm_allocator @private;
extern int __heap_base;
@@ -691,12 +656,6 @@ 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"
@@ -719,12 +678,6 @@ 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);
@@ -746,69 +699,56 @@ fn void* tmalloc(usz size, usz alignment = 0) @builtin @inline @nodiscard
}
<*
@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 $vacount < 2 : "Too many arguments."
@require $vacount == 0 ||| $assignable($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"
@return "A pointer to data of type $Type."
*>
macro new($Type, #init = ...) @nodiscard @safemacro
macro new($Type, ...) @nodiscard
{
$if $defined(#init):
$Type* val = malloc($Type.sizeof);
*val = #init;
return val;
$else
$if $vacount == 0:
return ($Type*)calloc($Type.sizeof);
$else
$Type* val = malloc($Type.sizeof);
*val = $vaexpr[0];
return val;
$endif
}
<*
@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."
@require $vacount < 2 : "Too many arguments."
@require $vacount == 0 ||| $assignable($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"
*>
macro new_with_padding($Type, usz padding, #init = ...) @nodiscard @safemacro
macro new_with_padding($Type, usz padding, ...) @nodiscard
{
$if $defined(#init):
$Type* val = malloc($Type.sizeof + padding);
*val = #init;
return val;
$else
$if $vacount == 0:
return ($Type*)calloc($Type.sizeof + padding);
$else
$Type* val = malloc($Type.sizeof + padding);
*val = $vaexpr[0];
return val;
$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.
@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"
@require $vacount < 2 : "Too many arguments."
@require $vacount == 0 ||| $assignable($vaexpr[0], $Type) : "The second argument must be an initializer for the type"
*>
macro new_aligned($Type, #init = ...) @nodiscard @safemacro
macro new_aligned($Type, ...) @nodiscard
{
$if $defined(#init):
$Type* val = malloc_aligned($Type.sizeof, $Type.alignof);
*val = #init;
return val;
$else
$if $vacount == 0:
return ($Type*)calloc_aligned($Type.sizeof, $Type.alignof);
$else
$Type* val = malloc_aligned($Type.sizeof, $Type.alignof);
*val = $vaexpr[0];
return val;
$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
{
@@ -816,11 +756,7 @@ 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
{
@@ -828,12 +764,8 @@ 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
{
@@ -841,39 +773,32 @@ macro alloc_aligned($Type) @nodiscard
}
<*
@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."
@require $vacount < 2 : "Too many arguments."
@require $vacount == 0 ||| $assignable($vaexpr[0], $Type) : "The second argument must be an initializer for the type"
*>
macro tnew($Type, #init = ...) @nodiscard @safemacro
macro tnew($Type, ...) @nodiscard
{
$if $defined(#init):
$Type* val = tmalloc($Type.sizeof, $Type.alignof) @inline;
*val = #init;
return val;
$else
$if $vacount == 0:
return ($Type*)tcalloc($Type.sizeof, $Type.alignof) @inline;
$else
$Type* val = tmalloc($Type.sizeof, $Type.alignof) @inline;
*val = $vaexpr[0];
return val;
$endif
}
<*
@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."
@require $vacount < 2 : "Too many arguments."
@require $vacount == 0 ||| $assignable($vaexpr[0], $Type) : "The second argument must be an initializer for the type"
*>
macro temp_with_padding($Type, usz padding, #init = ...) @nodiscard @safemacro
macro temp_with_padding($Type, usz padding, ...) @nodiscard
{
$if $defined(#init):
$Type* val = tmalloc($Type.sizeof + padding, $Type.alignof) @inline;
*val = #init;
return val;
$else
$if $vacount == 0:
return ($Type*)tcalloc($Type.sizeof + padding, $Type.alignof) @inline;
$else
$Type* val = tmalloc($Type.sizeof + padding, $Type.alignof) @inline;
*val = $vaexpr[0];
return val;
$endif
}
@@ -978,18 +903,6 @@ fn void* trealloc(void* ptr, usz size, usz alignment = mem::DEFAULT_MEM_ALIGNMEN
return tmem.resize(ptr, size, alignment)!!;
}
<*
Takes the address of a possibly unaligned variable or member,
and offers safe access to that member, by constructing an UnalignedRef.
@require $defined(&#arg) : "It must be possible to take the address of the argument."
@return "An 'UnalignedRef' with the proper type and alignment, with a pointer to argument"
*>
macro @unaligned_addr(#arg) @builtin
{
return (UnalignedRef{$typeof(#arg), $alignof(#arg)})&#arg;
}
module std::core::mem @if(env::NO_LIBC);
fn CInt __memcmp(void* s1, void* s2, usz n) @weak @export("memcmp")
@@ -1027,39 +940,3 @@ fn void* __memcpy(void* dst, void* src, usz n) @weak @export("memcpy")
}
return dst;
}
module std::core::mem::volatile { Type };
typedef Volatile @structlike = Type;
macro Type Volatile.get(&self)
{
return @volatile_load(*(Type*)self);
}
macro Type Volatile.set(&self, Type val)
{
return @volatile_store(*(Type*)self, val);
}
<*
@require mem::@constant_is_power_of_2(ALIGNMENT) : "The alignment must be a power of 2"
*>
module std::core::mem::alignment { Type, ALIGNMENT };
import std::core::mem @public;
<*
An UnalignedRef offers correctly aligned access to addresses that may be unaligned or overaligned.
*>
typedef UnalignedRef = Type*;
macro Type UnalignedRef.get(self)
{
return @unaligned_load(*(Type*)self, ALIGNMENT);
}
macro Type UnalignedRef.set(&self, Type val)
{
return @unaligned_store(*(Type*)self, val, ALIGNMENT);
}

View File

@@ -1,7 +1,7 @@
module std::core::mem::allocator;
import std::math;
// C3 has several different allocators available:
// C3 has multiple different allocators available:
//
// Name Arena Uses buffer OOM Fallback? Mark? Reset?
// ArenaAllocator Yes Yes No Yes Yes
@@ -12,7 +12,6 @@ import std::math;
// OnStackAllocator Yes Yes Yes No No *Note: Used by @stack_mem
// TempAllocator Yes No Yes No* No* *Note: Mark/reset using @pool
// TrackingAllocator No No N/A No No *Note: Wraps other heap allocator
// Vmem Yes No No Yes Yes *Note: Can be set to huge sizes
const DEFAULT_SIZE_PREFIX = usz.sizeof;
const DEFAULT_SIZE_PREFIX_ALIGNMENT = usz.alignof;
@@ -65,7 +64,7 @@ alias MemoryAllocFn = fn char[]?(usz);
fn usz alignment_for_allocation(usz alignment) @inline
fn usz alignment_for_allocation(usz alignment) @inline @private
{
return alignment < mem::DEFAULT_MEM_ALIGNMENT ? mem::DEFAULT_MEM_ALIGNMENT : alignment;
}
@@ -167,7 +166,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 ||| $defined($Type t = $vaexpr[0]) : "The second argument must be an initializer for the type"
@require $vacount == 0 ||| $assignable($vaexpr[0], $Type) : "The second argument must be an initializer for the type"
*>
macro new(Allocator allocator, $Type, ...) @nodiscard
{
@@ -183,7 +182,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 ||| $defined($Type t = $vaexpr[0]) : "The second argument must be an initializer for the type"
@require $vacount == 0 ||| $assignable($vaexpr[0], $Type) : "The second argument must be an initializer for the type"
*>
macro new_try(Allocator allocator, $Type, ...) @nodiscard
{
@@ -200,7 +199,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 ||| $defined($Type t = $vaexpr[0]) : "The second argument must be an initializer for the type"
@require $vacount == 0 ||| $assignable($vaexpr[0], $Type) : "The second argument must be an initializer for the type"
*>
macro new_aligned(Allocator allocator, $Type, ...) @nodiscard
{
@@ -317,23 +316,6 @@ 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.
@@ -365,7 +347,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 $kindof(#alloc_fn(bytes)) == OPTIONAL:
$if @typekind(#alloc_fn(bytes)) == OPTIONAL:
void* data = #alloc_fn(alignsize)!;
$else
void* data = #alloc_fn(alignsize);
@@ -386,7 +368,7 @@ struct AlignedBlock
macro void? @aligned_free(#free_fn, void* old_pointer)
{
AlignedBlock* desc = (AlignedBlock*)old_pointer - 1;
$if $kindof(#free_fn(desc.start)) == OPTIONAL:
$if @typekind(#free_fn(desc.start)) == OPTIONAL:
#free_fn(desc.start)!;
$else
#free_fn(desc.start);
@@ -403,7 +385,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 $kindof(#free_fn(data_start)) == OPTIONAL:
$if @typekind(#free_fn(data_start)) == OPTIONAL:
#free_fn(data_start)!;
$else
#free_fn(data_start);
@@ -481,7 +463,7 @@ macro usz temp_allocator_default_reserve_size() @local
$endswitch
}
macro Allocator heap() @deprecated("Use 'mem' instead.") => thread_allocator;
macro Allocator heap() => thread_allocator;
<*
@require !top_temp : "This should never be called when temp already exists"
@@ -504,14 +486,14 @@ fn Allocator create_temp_allocator(Allocator allocator, usz size, usz reserve, u
return current_temp = top_temp = allocator::new_temp_allocator(allocator, size, reserve, min_size, realloc_size)!!;
}
macro Allocator temp() @deprecated("Use 'tmem' instead")
macro Allocator temp()
{
return current_temp;
}
alias tmem @builtin = current_temp;
fn void allow_implicit_temp_allocator_on_load_thread() @init(1) @local @if(env::LIBC || env::FREESTANDING_WASM)
fn void allow_implicit_temp_allocator_on_load_thread() @init(1) @local @if(env::LIBC || env::WASM_NOLIBC)
{
auto_create_temp = true;
}

View File

@@ -1,252 +0,0 @@
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;
}

View File

@@ -1,353 +0,0 @@
<*
The VM module holds code for working with virtual memory on supported platforms (currently Win32 and Posix)
*>
module std::core::mem::vm;
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
but will perform more checks and track its size (required to unmap the memory on Posix)
*>
struct VirtualMemory
{
void* ptr;
usz size;
VirtualMemoryAccess default_access;
}
faultdef RANGE_OVERFLOW, UNKNOWN_ERROR, ACCESS_DENIED, UNMAPPED_ACCESS, UNALIGNED_ADDRESS, RELEASE_FAILED, UPDATE_FAILED, INVALID_ARGS;
enum VirtualMemoryAccess
{
PROTECTED,
READ,
WRITE,
READWRITE,
EXEC,
EXECREAD,
EXECWRITE,
ANY
}
fn usz aligned_alloc_size(usz size)
{
$if env::WIN32:
return size > 0 ? mem::aligned_offset(size, win32::allocation_granularity()) : win32::allocation_granularity();
$else
return size > 0 ? mem::aligned_offset(size, mem::os_pagesize()) : mem::os_pagesize();
$endif
}
<*
Allocate virtual memory, size is rounded up to platform granularity (Win32) / page size (Posix).
@param size : "The size of the memory to allocate, will be rounded up"
@param access : "The initial access permissions."
@return? mem::OUT_OF_MEMORY, RANGE_OVERFLOW, UNKNOWN_ERROR, ACCESS_DENIED, INVALID_ARGS
@return "Pointer to the allocated memory, page aligned"
*>
fn void*? alloc(usz size, VirtualMemoryAccess access)
{
$switch:
$case env::POSIX:
void* ptr = posix::mmap(null, aligned_alloc_size(size), access.to_posix(), posix::MAP_PRIVATE | posix::MAP_ANONYMOUS, -1, 0);
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?;
default: return UNKNOWN_ERROR?;
}
$case env::WIN32:
void* ptr = win32::virtualAlloc(null, aligned_alloc_size(size), MEM_RESERVE, access.to_win32());
if (ptr) return ptr;
switch (win32::getLastError())
{
case win32::ERROR_NOT_ENOUGH_MEMORY:
case win32::ERROR_COMMITMENT_LIMIT: return mem::OUT_OF_MEMORY?;
default: return UNKNOWN_ERROR?;
}
$default:
unsupported("Virtual alloc only available on Win32 and Posix");
$endswitch
}
<*
Release memory allocated with "alloc".
@param [&inout] ptr : "Pointer to page to release, should be allocated using vm::alloc"
@param size : "The size of the allocated pointer"
@require mem::ptr_is_page_aligned(ptr) : "The pointer should be page aligned"
*>
fn void? release(void* ptr, usz size)
{
$switch:
$case env::POSIX:
if (posix::munmap(ptr, aligned_alloc_size(size)))
{
switch (libc::errno())
{
case errno::EINVAL: return INVALID_ARGS?; // Not a valid mapping or size
case errno::ENOMEM: return UNMAPPED_ACCESS?; // Address not mapped
default: return RELEASE_FAILED?;
}
}
$case env::WIN32:
if (win32::virtualFree(ptr, 0, MEM_RELEASE)) return;
switch (win32::getLastError())
{
case win32::ERROR_INVALID_ADDRESS: return INVALID_ARGS?;
case win32::ERROR_NOT_ENOUGH_MEMORY: return mem::OUT_OF_MEMORY?;
default: return RELEASE_FAILED?;
}
$default:
unsupported("Virtual free only available on Win32 and Posix");
$endswitch
}
<*
Change the access protection of a region in memory. The region must be page aligned.
@param [&inout] ptr : "Pointer to page to update, must be page aligned"
@param len : "To what len to update, must be page aligned"
@param access : "The new access"
@require mem::ptr_is_page_aligned(ptr) : "The pointer should be page aligned"
@require mem::ptr_is_page_aligned(ptr + len) : "The length must be page aligned"
@return? ACCESS_DENIED, UNALIGNED_ADDRESS, RANGE_OVERFLOW, UPDATE_FAILED, UNMAPPED_ACCESS, INVALID_ARGS
*>
fn void? protect(void* ptr, usz len, VirtualMemoryAccess access)
{
$switch:
$case env::POSIX:
if (!posix::mprotect(ptr, len, access.to_posix())) return;
switch (libc::errno())
{
case errno::EACCES: return ACCESS_DENIED?;
case errno::EINVAL: return UNALIGNED_ADDRESS?;
case errno::EOVERFLOW: return RANGE_OVERFLOW?;
case errno::ENOMEM: return UNMAPPED_ACCESS?;
default: return UPDATE_FAILED?;
}
$case env::WIN32:
Win32_Protect old;
if (win32::virtualProtect(ptr, len, access.to_win32(), &old)) return;
switch (win32::getLastError())
{
case win32::ERROR_INVALID_ADDRESS: return UNALIGNED_ADDRESS?;
case win32::ERROR_ACCESS_DENIED: return ACCESS_DENIED?;
case win32::ERROR_INVALID_PARAMETER: return INVALID_ARGS?;
default: return UPDATE_FAILED?;
}
$default:
unsupported("'virtual_protect' is only available on Win32 and Posix.");
$endswitch
}
<*
Makes a region of memory available that was previously retrieved using 'alloc'. This is necessary on Win32,
but optional on Posix.
@param [&inout] ptr : "Pointer to page to update, must be page aligned"
@param len : "To what len to commit, must be page aligned"
@require mem::ptr_is_page_aligned(ptr) : "The pointer should be page aligned"
@require mem::ptr_is_page_aligned(ptr + len) : "The length must be page aligned"
@return? UNKNOWN_ERROR, mem::OUT_OF_MEMORY, ACCESS_DENIED, UNALIGNED_ADDRESS, RANGE_OVERFLOW, UPDATE_FAILED, UNMAPPED_ACCESS, INVALID_ARGS
*>
fn void? commit(void* ptr, usz len, VirtualMemoryAccess access = READWRITE)
{
$switch:
$case env::POSIX:
return protect(ptr, len, READWRITE) @inline;
$case env::WIN32:
void* result = win32::virtualAlloc(ptr, len, MEM_COMMIT, access.to_win32());
if (result) return;
switch (win32::getLastError())
{
case win32::ERROR_INVALID_ADDRESS: return UNALIGNED_ADDRESS?;
case win32::ERROR_ACCESS_DENIED: return ACCESS_DENIED?;
case win32::ERROR_COMMITMENT_LIMIT:
case win32::ERROR_NOT_ENOUGH_MEMORY: return mem::OUT_OF_MEMORY?;
case win32::ERROR_INVALID_PARAMETER: return INVALID_ARGS?;
default: return UNKNOWN_ERROR?;
}
$default:
unsupported("'virtual_commit' is only available on Win32 and Posix.");
$endswitch
}
<*
Notifies that the memory in the region can be released back to the OS. On Win32 this decommits the region,
whereas on Posix it tells the system that it may be reused using madvise. The "block" parameter is only
respected on Posix, and protects the region from read/write/exec. On Win32 this always happens.
@param [&inout] ptr : "Pointer to page to update, must be page aligned"
@param len : "To what len to commit, must be page aligned"
@param block : "Set the released memory to protected"
@require mem::ptr_is_page_aligned(ptr) : "The pointer should be page aligned"
@require mem::ptr_is_page_aligned(ptr + len) : "The length must be page aligned"
@return? ACCESS_DENIED, UNALIGNED_ADDRESS, RANGE_OVERFLOW, UPDATE_FAILED, UNMAPPED_ACCESS, INVALID_ARGS
*>
fn void? decommit(void* ptr, usz len, bool block = true)
{
$switch:
$case env::POSIX:
if (posix::madvise(ptr, len, posix::MADV_DONTNEED))
{
switch (libc::errno())
{
case errno::EINVAL: return UNALIGNED_ADDRESS?;
case errno::ENOMEM: return UNMAPPED_ACCESS?;
default: return UPDATE_FAILED?;
}
}
if (block) (void)protect(ptr, len, PROTECTED) @inline;
$case env::WIN32:
if (!win32::virtualFree(ptr, len, MEM_DECOMMIT))
{
switch (win32::getLastError())
{
case win32::ERROR_INVALID_ADDRESS: return UNALIGNED_ADDRESS?;
case win32::ERROR_INVALID_PARAMETER: return INVALID_ARGS?;
case win32::ERROR_ACCESS_DENIED: return ACCESS_DENIED?;
default: return UPDATE_FAILED?;
}
}
$default:
unsupported("'virtual_decommit' is only available on Win32 and Posix.");
$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
@param size : "The size of the memory to allocate."
@require size > 0 : "The size must be non-zero"
@return? mem::OUT_OF_MEMORY, RANGE_OVERFLOW, UNKNOWN_ERROR, ACCESS_DENIED, INVALID_ARGS
*>
fn VirtualMemory? virtual_alloc(usz size, VirtualMemoryAccess access = PROTECTED)
{
size = aligned_alloc_size(size);
void* ptr = alloc(size, access)!;
return { ptr, size, access };
}
<*
Commits memory, using vm::commit
@param offset : "Starting from what offset to commit"
@param len : "To what len to commit"
@require mem::ptr_is_page_aligned(self.ptr + offset) : "The offset should be page aligned"
@require mem::ptr_is_page_aligned(self.ptr + offset + len) : "The length must be page aligned"
@require offset < self.size : "Offset out of range"
@require offset + len <= self.size : "Length out of range"
@return? UPDATE_FAILED, ACCESS_DENIED, UNALIGNED_ADDRESS, RANGE_OVERFLOW, UNKNOWN_ERROR
*>
macro void? VirtualMemory.commit(self, usz offset, usz len)
{
return commit(self.ptr + offset, len, self.default_access);
}
<*
Changes protection of a part of memory using vm::protect
@param offset : "Starting from what offset to update"
@param len : "To what len to update"
@require mem::ptr_is_page_aligned(self.ptr + offset) : "The offset should be page aligned"
@require mem::ptr_is_page_aligned(self.ptr + offset + len) : "The length must be page aligned"
@require offset < self.size : "Offset out of range"
@require offset + len < self.size : "Length out of range"
@return? UPDATE_FAILED, ACCESS_DENIED, UNALIGNED_ADDRESS, RANGE_OVERFLOW, UNKNOWN_ERROR
*>
macro void? VirtualMemory.protect(self, usz offset, usz len, VirtualMemoryAccess access)
{
return protect(self.ptr + offset, len, access);
}
<*
Decommits a part of memory using vm::decommit
@param offset : "Starting from what offset to decommit"
@param len : "To what len to decommit"
@param block : "Should the memory be blocked from access after decommit"
@require mem::ptr_is_page_aligned(self.ptr + offset) : "The offset should be page aligned"
@require mem::ptr_is_page_aligned(self.ptr + offset + len) : "The length must be page aligned"
@require offset < self.size : "Offset out of range"
@require offset + len < self.size : "Length out of range"
@return? UPDATE_FAILED
*>
fn void? VirtualMemory.decommit(self, usz offset, usz len, bool block = true)
{
return decommit(self.ptr + offset, len, block);
}
<*
Releases the memory region
@require self.ptr != null : "Virtual memory must be initialized to call destroy"
*>
fn void? VirtualMemory.destroy(&self)
{
return release(self.ptr, self.size);
}
fn CInt VirtualMemoryAccess.to_posix(self) @if(env::POSIX) @private
{
switch (self)
{
case PROTECTED: return posix::PROT_NONE;
case READ: return posix::PROT_READ;
case WRITE: return posix::PROT_WRITE;
case EXEC: return posix::PROT_EXEC;
case READWRITE: return posix::PROT_READ | posix::PROT_WRITE;
case EXECREAD: return posix::PROT_READ | posix::PROT_EXEC;
case EXECWRITE: return posix::PROT_WRITE | posix::PROT_EXEC;
case ANY: return posix::PROT_WRITE | posix::PROT_READ | posix::PROT_EXEC;
}
}
fn Win32_Protect VirtualMemoryAccess.to_win32(self) @if(env::WIN32) @private
{
switch (self)
{
case PROTECTED: return PAGE_NOACCESS;
case READ: return PAGE_READONLY;
case WRITE: return PAGE_READWRITE;
case EXEC: return PAGE_EXECUTE;
case READWRITE: return PAGE_READWRITE;
case EXECWRITE: return PAGE_EXECUTE_READWRITE;
case EXECREAD: return PAGE_EXECUTE_READ;
case ANY: return PAGE_EXECUTE_READWRITE;
}
}

View File

@@ -64,7 +64,7 @@ macro int @main_to_void_main_args(#m, int argc, char** argv)
module std::core::main_stub @if(env::WIN32);
extern fn Char16** _win_command_line_to_argv_w(ushort* cmd_line, int* argc_ptr) @cname("CommandLineToArgvW");
extern fn Char16** _win_command_line_to_argv_w(ushort* cmd_line, int* argc_ptr) @extern("CommandLineToArgvW");
macro String[] win_command_line_to_strings(ushort* cmd_line) @private
{

View File

@@ -1,132 +0,0 @@
<*
Ref provides a general *external* ref counted wrapper for a pointer. For convenience, a ref count of 0
means the reference is still valid.
When the rc drops to -1, it will first run the dealloc function on the underlying pointer (if it exists),
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) ||| $typeof((Type){}.dealloc()) == void : "'dealloc' must return 'void'"
*>
module std::core::mem::ref { Type };
import std::thread, std::atomic;
const OVERALIGNED @private = Type.alignof > mem::DEFAULT_MEM_ALIGNMENT;
alias DeallocFn = fn void(void*);
fn Ref wrap(Type* ptr, Allocator allocator = mem)
{
return { .refcount = allocator::new(allocator, Atomic{int}), .ptr = ptr, .allocator = allocator };
}
<*
@require $vacount < 2 : "Too many arguments."
@require $vacount == 0 ||| $defined(Type a = $vaexpr[0]) : "The first argument must be an initializer for the type"
*>
macro Ref new(..., Allocator allocator = mem)
{
$switch:
$case OVERALIGNED && !$vacount:
Type* ptr = allocator::calloc_aligned(allocator, Type.sizeof, Type.alignof)!!;
$case OVERALIGNED:
Type* ptr = allocator::malloc_aligned(allocator, Type.sizeof, Type.alignof)!!;
*ptr = $vaexpr[0];
$case !$vacount:
Type* ptr = allocator::calloc(allocator, Type.sizeof);
$default:
Type* ptr = allocator::malloc(allocator, Type.sizeof);
*ptr = $vaexpr[0];
$endswitch
return { .refcount = allocator::new(allocator, Atomic{int}),
.ptr = ptr,
.allocator = allocator };
}
struct Ref
{
Atomic{int}* refcount;
Type* ptr;
Allocator allocator;
}
fn Ref* Ref.retain(&self)
{
assert(self.refcount != null, "Reference already released");
assert(self.refcount.load(RELAXED) >= 0, "Retaining zombie");
self.refcount.add(1, RELAXED);
return self;
}
fn void Ref.release(&self)
{
assert(self.refcount != null, "Reference already released");
assert(self.refcount.load(RELAXED) >= 0, "Overrelease of refcount");
if (self.refcount.sub(1, RELAXED) == 0)
{
thread::fence(ACQUIRE);
$if $defined(Type.dealloc):
self.ptr.dealloc();
$endif
$if OVERALIGNED:
allocator::free_aligned(self.allocator, self.ptr);
$else
allocator::free(self.allocator, self.ptr);
$endif
allocator::free(self.allocator, self.refcount);
*self = {};
}
}
module std::core::mem::rc;
import std::thread, std::atomic;
<*
A RefCounted struct should be an inline base of a struct.
If a `dealloc` is defined, then it will be called rather than `free`
For convenience, a ref count of 0 is still valid, and the struct is
only freed when when ref count drops to -1.
The macros rc::retain and rc::release must be used on the full pointer,
not on the RefCounted substruct.
So `Foo* f = ...; RefCounted* rc = f; rc::release(rc);` will not do the right thing.
*>
struct RefCounted
{
Atomic{int} refcount;
}
<*
@require $defined(RefCounted* c = refcounted) : "Expected a ref counted value"
*>
macro retain(refcounted)
{
if (refcounted)
{
assert(refcounted.refcount.load(RELAXED) >= 0, "Retaining zombie");
refcounted.refcount.add(1, RELAXED);
}
return refcounted;
}
<*
@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)
{
if (!refcounted) return;
assert(refcounted.refcount.load(RELAXED) >= 0, "Overrelease of refcount");
if (refcounted.refcount.sub(1, RELAXED) == 0)
{
thread::fence(ACQUIRE);
$if $defined(refcounted.dealloc):
refcounted.dealloc();
$else
free(refcounted);
$endif
}
}

View File

@@ -39,10 +39,10 @@ macro @enum_lookup_new($Type, $name, value)
}
module std::core::runtime @if(env::FREESTANDING_WASM);
module std::core::runtime @if(WASM_NOLIBC);
extern fn void __wasm_call_ctors();
fn void wasm_initialize() @cname("_initialize") @wasm
fn void wasm_initialize() @extern("_initialize") @wasm
{
// The linker synthesizes this to call constructors.
__wasm_call_ctors();

View File

@@ -1,9 +1,7 @@
module std::core::runtime;
import libc, std::time, std::io, std::sort, std::math, std::collections::map;
import libc, std::time, std::io, std::sort;
alias BenchmarkFn = fn void ();
HashMap { String, uint } bench_fn_iters @local;
alias BenchmarkFn = fn void();
struct BenchmarkUnit
{
@@ -19,7 +17,6 @@ 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;
}
@@ -39,49 +36,6 @@ 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)
@@ -104,69 +58,36 @@ fn bool run_benchmarks(BenchmarkUnit[] benchmarks)
name.clear();
foreach (unit : benchmarks)
long sys_clock_started;
long sys_clock_finished;
long sys_clocks;
Clock clock;
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;
NanoDuration running_timer;
long total_clocks;
clock = std::time::clock::now();
sys_clock_started = $$sysclock();
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 = {})
for (uint i = 0; i < benchmark_max_iterations; i++)
{
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;
}
float clock_cycles = (float)total_clocks / current_benchmark_iterations;
float measurement = (float)running_timer / current_benchmark_iterations;
String[] units = { "nanoseconds", "microseconds", "milliseconds", "seconds" };
sys_clock_finished = $$sysclock();
NanoDuration nano_seconds = clock.mark();
sys_clocks = sys_clock_finished - sys_clock_started;
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("[COMPLETE] %.2f ns, %.2f CPU's clocks", (float)nano_seconds / benchmark_max_iterations, (float)sys_clocks / benchmark_max_iterations);
}
io::printfn("\n%d benchmark%s run.\n", benchmarks.len, benchmarks.len > 1 ? "s" : "");
@@ -175,12 +96,5 @@ 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));
}

View File

@@ -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,6 +140,7 @@ 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]");
}
@@ -170,11 +171,6 @@ 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;
@@ -309,7 +305,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: ",

View File

@@ -29,11 +29,11 @@ alias ErrorCallback = fn void (ZString);
@param addr : "Start of memory region."
@param size : "Size of memory region."
*>
macro void poison_memory_region(void* addr, usz size)
macro 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 void poison_memory_region(void* addr, usz size)
@param addr : "Start of memory region."
@param size : "Size of memory region."
*>
macro void unpoison_memory_region(void* addr, usz size)
macro unpoison_memory_region(void* addr, usz size)
{
$if env::ADDRESS_SANITIZER:
$if env::ADDRESS_SANITIZER:
__asan_unpoison_memory_region(addr, size);
$endif
$endif
}
<*

View File

@@ -1,6 +1,5 @@
module std::core::string;
import std::io, std::ascii;
import std::io;
typedef String @if(!$defined(String)) = inline char[];
<*
@@ -108,28 +107,6 @@ fn String format(Allocator allocator, String fmt, args...) @format(1) => @pool()
return str.copy_str(allocator);
}
<*
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)
{
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];
}
<*
Return a temporary String created using the formatting function.
@@ -146,7 +123,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 : `String containing the characters`
@param [in] set : `The formatting string`
@pure
@return `True if a character is in the set`
*>
@@ -195,7 +172,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(allocator);
return dstring::join(tmem, split, new_str).copy_str(mem);
};
}
@@ -222,28 +199,11 @@ 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 = " \n\t\r\f\v")
fn String String.trim(self, String to_trim = "\t\n\r ")
{
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.
@@ -252,7 +212,7 @@ fn String String.trim_charset(self, AsciiCharset to_trim = ascii::WHITESPACE_SET
@pure
@return `a substring of the string passed in`
*>
fn String String.trim_left(self, String to_trim = " \n\t\r\f\v")
fn String String.trim_left(self, String to_trim = "\t\n\r ")
{
usz start = 0;
usz len = self.len;
@@ -269,7 +229,7 @@ fn String String.trim_left(self, String to_trim = " \n\t\r\f\v")
@pure
@return `a substring of the string passed in`
*>
fn String String.trim_right(self, String to_trim = " \n\t\r\f\v")
fn String String.trim_right(self, String to_trim = "\t\n\r ")
{
usz len = self.len;
while (len > 0 && char_in_set(self[len - 1], to_trim)) len--;
@@ -452,19 +412,6 @@ 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.
@@ -635,7 +582,6 @@ fn bool ZString.eq(self, ZString other) @operator(==)
char* a = self;
char* b = other;
if (a == b) return true;
if (!a || !b) return false;
for (;; a++, b++)
{
char c = *a;
@@ -662,17 +608,12 @@ fn usz ZString.char_len(str)
fn usz ZString.len(self)
{
usz len;
for (char* ptr = (char*)self; *ptr; ptr++) len++;
usz len = 0;
char* ptr = (char*)self;
while (char c = ptr++[0]) len++;
return len;
}
fn usz WString.len(self)
{
usz len;
for (Char16* ptr = (Char16*)self; *ptr; ptr++) len++;
return len;
}
fn ZString String.zstr_copy(self, Allocator allocator)
{
@@ -815,92 +756,6 @@ fn String String.to_upper_copy(self, Allocator allocator)
return copy;
}
fn String String.capitalize_copy(self, Allocator allocator)
{
String s = self.copy(allocator);
if (s.len > 0 && s[0].is_lower())
{
s[0] &= (char)~0x20;
}
return s;
}
<*
Convert a string from `snake_case` to PascalCase.
@param [in] self
@return `"FooBar" from "foo_bar" the resulting pointer may safely be cast to ZString.`
*>
fn String String.snake_to_pascal_copy(self, Allocator allocator)
{
Splitter splitter = self.tokenize("_");
char[] new_string = allocator::alloc_array(allocator, char, self.len + 1);
usz index = 0;
while (try s = splitter.next())
{
assert(s.len > 0);
char c = s[0];
if (c.is_lower()) c = c.to_upper();
new_string[index++] = c;
s = s[1..];
new_string[index:s.len] = s[..];
index += s.len;
}
new_string[index] = 0;
return (String)new_string[:index];
}
<*
Movifies the current string from `snake_case` to PascalCase.
@param [inout] self
*>
fn void String.convert_snake_to_pascal(&self)
{
Splitter splitter = self.tokenize("_");
String new_string = *self;
usz index = 0;
while (try s = splitter.next())
{
assert(s.len > 0);
char c = s[0];
if (c.is_lower()) c = c.to_upper();
new_string[index++] = c;
s = s[1..];
new_string[index:s.len] = s[..];
index += s.len;
}
*self = new_string[:index];
}
<*
Convert a string from `PascalCase` to `snake_case`.
@param [in] self
@return `"foo_bar" from "FooBar" the resulting pointer may safely be cast to ZString.`
*>
fn String String.pascal_to_snake_copy(self, Allocator allocator) => @pool()
{
DString d;
d.init(tmem, (usz)(self.len * 1.5));
usz index = 0;
foreach (i, c : self)
{
if (c.is_upper())
{
if (i > 0 && ((self[i - 1].is_lower() || self[i - 1].is_digit()) || (i < self.len - 1 && self[i + 1].is_lower())))
{
d.append_char('_');
}
d.append_char(c.to_lower());
continue;
}
d.append_char(c);
}
return d.copy_str(allocator);
}
fn StringIterator String.iterator(self)
{
return { self, 0 };

View File

@@ -1,233 +0,0 @@
// Copyright (c) 2024 Christoffer Lerno. All rights reserved.
// Use of this source code is governed by the MIT license
// a copy of which can be found in the LICENSE_STDLIB file.
<*
This module provides functionality for escaping and unescaping strings
with standard C-style escape sequences, similar to what's used in JSON
and other string literals.
*>
module std::core::string;
import std::io;
faultdef INVALID_ESCAPE_SEQUENCE, UNTERMINATED_STRING, INVALID_HEX_ESCAPE, INVALID_UNICODE_ESCAPE;
<*
Escape a string by adding quotes and converting special characters to escape sequences.
@param allocator : "The allocator to use for the result"
@param s : "The string to escape"
@param strip_quotes : "Do not include beginning and end quotes, defaults to false"
@return "The escaped string with surrounding quotes, can safely be cast to ZString"
*>
fn String String.escape(String s, Allocator allocator, bool strip_quotes = true)
{
// Conservative allocation: most strings need minimal escaping
usz initial_capacity = s.len + s.len / 5 + 2; // ~1.2x + quotes
DString result = dstring::new_with_capacity(allocator, initial_capacity);
if (!strip_quotes) result.append_char('"');
foreach (char c : s)
{
switch (c)
{
case '"': result.append(`\"`);
case '\\': result.append(`\\`);
case '\b': result.append(`\b`);
case '\f': result.append(`\f`);
case '\n': result.append(`\n`);
case '\r': result.append(`\r`);
case '\t': result.append(`\t`);
case '\v': result.append(`\v`);
case '\0': result.append(`\0`);
default:
if (c >= 32 && c <= 126)
{
// Printable ASCII
result.append_char(c);
}
else
{
// Non-printable, use hex escape
result.appendf("\\x%02x", (uint)c);
}
}
}
if (!strip_quotes) result.append_char('"');
return result.copy_str(allocator);
}
<*
Escape a string using the temp allocator.
@param s : "The string to escape"
@param strip_quotes : "Do not include beginning and end quotes, defaults to false"
@return "The escaped string with surrounding quotes"
*>
fn String String.tescape(String s, bool strip_quotes = false) => s.escape(tmem, strip_quotes);
<*
Calculate the length needed for an escaped string (including quotes).
@param s : "The string to check"
@return "The length needed for the escaped version"
*>
fn usz escape_len(String s)
{
usz len = 2; // For quotes
foreach (char c : s)
{
switch (c)
{
case '"':
case '\\':
case '\b':
case '\f':
case '\n':
case '\r':
case '\t':
case '\v':
case '\0':
len += 2; // \X
default:
if (c >= 32 && c <= 126)
{
len += 1;
}
else
{
len += 4; // \xHH
}
}
}
return len;
}
<*
Unescape a quoted string by parsing escape sequences.
@param allocator : "The allocator to use for the result"
@param s : "The quoted string to unescape"
@param allow_unquoted : "Set to true to unescape strings not surrounded by quotes, defaults to false"
@return "The unescaped string without quotes, safe to convert to ZString"
@return? UNTERMINATED_STRING, INVALID_ESCAPE_SEQUENCE, INVALID_HEX_ESCAPE, INVALID_UNICODE_ESCAPE
*>
fn String? String.unescape(String s, Allocator allocator, bool allow_unquoted = false)
{
if (s.len >= 2 && s[0] == '"' && s[^1] == '"')
{
// Remove quotes.
s = s[1:^2];
}
else if (!allow_unquoted) return UNTERMINATED_STRING?;
// Handle empty string case
if (!s.len)
{
return "".copy(allocator);
}
DString result = dstring::new_with_capacity(allocator, s.len);
usz len = s.len;
for (usz i = 0; i < len; i++)
{
char c = s[i];
if (c != '\\')
{
result.append_char(c);
continue;
}
// Handle escape sequence
if (i + 1 >= len) return INVALID_ESCAPE_SEQUENCE?;
char escape_char = s[++i];
switch (escape_char)
{
case '"': result.append_char('"');
case '\\': result.append_char('\\');
case '/': result.append_char('/');
case 'b': result.append_char('\b');
case 'f': result.append_char('\f');
case 'n': result.append_char('\n');
case 'r': result.append_char('\r');
case 't': result.append_char('\t');
case 'v': result.append_char('\v');
case '0': result.append_char('\0');
case 'x':
// Hex escape \xHH
if (i + 2 >= len) return INVALID_HEX_ESCAPE?;
char h1 = s[++i];
char h2 = s[++i];
if (!h1.is_xdigit() || !h2.is_xdigit()) return INVALID_HEX_ESCAPE?;
uint val = h1 > '9' ? (h1 | 32) - 'a' + 10 : h1 - '0';
val = val << 4;
val += h2 > '9' ? (h2 | 32) - 'a' + 10 : h2 - '0';
result.append_char((char)val);
case 'u':
// Unicode escape \uHHHH
if (i + 4 >= len) return INVALID_UNICODE_ESCAPE?;
uint val;
for (int j = 0; j < 4; j++)
{
char hex_char = s[++i];
if (!hex_char.is_xdigit()) return INVALID_UNICODE_ESCAPE?;
val = val << 4 + (hex_char > '9' ? (hex_char | 32) - 'a' + 10 : hex_char - '0');
}
result.append_char32(val);
case 'U':
// Unicode escape \UHHHHHHHH
if (i + 8 >= len) return INVALID_UNICODE_ESCAPE?;
uint val;
for (int j = 0; j < 8; j++)
{
char hex_char = s[++i];
if (!hex_char.is_xdigit()) return INVALID_UNICODE_ESCAPE?;
val = val << 4 + (hex_char > '9' ? (hex_char | 32) - 'a' + 10 : hex_char - '0');
}
result.append_char32(val);
default:
return INVALID_ESCAPE_SEQUENCE?;
}
}
return result.copy_str(allocator);
}
<*
Unescape a quoted string using the temp allocator.
@param s : "The quoted string to unescape"
@param allow_unquoted : "Set to true to unescape strings not surrounded by quotes, defaults to false"
@return "The unescaped string without quotes"
@return? UNTERMINATED_STRING, INVALID_ESCAPE_SEQUENCE, INVALID_HEX_ESCAPE, INVALID_UNICODE_ESCAPE
*>
fn String? String.tunescape(String s, bool allow_unquoted = false) => s.unescape(tmem, allow_unquoted);
<*
Check if a character needs to be escaped in a string literal.
@param c : "The character to check"
@return "True if the character needs escaping"
*>
fn bool needs_escape(char c)
{
switch (c)
{
case '"':
case '\\':
case '\b':
case '\f':
case '\n':
case '\r':
case '\t':
case '\v':
case '\0':
return true;
default:
return c < 32 || c > 126;
}
}

View File

@@ -462,7 +462,7 @@ macro String.to_real(chars, $Type) @private
$error "Unexpected type";
$endswitch
chars = chars.trim();
while (chars.len && chars[0] == ' ') chars = chars[1..];
if (!chars.len) return MALFORMED_FLOAT?;
if (chars.len != 1)
@@ -476,9 +476,6 @@ macro String.to_real(chars, $Type) @private
chars = chars[1..];
}
}
chars = chars.trim();
if (!chars.len) return MALFORMED_FLOAT?;
if (chars == "infinity" || chars == "INFINITY") return sign * $Type.inf;
if (chars == "NAN" || chars == "nan") return $Type.nan;

View File

@@ -115,9 +115,7 @@ fn bool TypeKind.is_int(kind) @inline
return kind == TypeKind.SIGNED_INT || kind == TypeKind.UNSIGNED_INT;
}
macro bool is_slice_convertable($Type) @deprecated("Use is_slice_convertible") => is_slice_convertible($Type);
macro bool is_slice_convertible($Type)
macro bool is_slice_convertable($Type)
{
$switch $Type.kindof:
$case SLICE:
@@ -341,8 +339,6 @@ 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):

View File

@@ -2,11 +2,13 @@ module std::core::values;
import std::core::types;
macro bool @typematch(#value1, #value2) @builtin @const => $typeof(#value1) == $typeof(#value2);
macro typeid @typeid(#value) @const @builtin => $typeof(#value).typeid;
macro TypeKind @typekind(#value) @const @builtin => $typeof(#value).kindof;
macro bool @typeis(#value, $Type) @const @builtin => $typeof(#value).typeid == $Type.typeid;
<*
Return true if two values have the same type before any conversions.
*>
macro bool @is_same_type(#value1, #value2) @const @deprecated("Use @typematch") => $typeof(#value1).typeid == $typeof(#value2).typeid;
macro bool @is_same_type(#value1, #value2) @const => $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));
@@ -16,12 +18,8 @@ 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 @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)'")
{
return $defined(var $v = #foo);
}
macro bool @assign_to(#value1, #value2) @const => $assignable(#value1, $typeof(#value2));
macro bool @is_lvalue(#value) => $defined(#value = #value);
macro promote_int(x)
{
@@ -43,7 +41,7 @@ macro promote_int(x)
@param #value_2
@returns `The selected value.`
*>
macro @select(bool $bool, #value_1, #value_2) @builtin @deprecated("Use '$bool ? #value_1 : #value_2' instead.")
macro @select(bool $bool, #value_1, #value_2) @builtin
{
$if $bool:
return #value_1;
@@ -51,7 +49,6 @@ macro @select(bool $bool, #value_1, #value_2) @builtin @deprecated("Use '$bool ?
return #value_2;
$endif
}
macro promote_int_same(x, y)
{
$if @is_int(x):

View File

@@ -1,650 +0,0 @@
<*
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];
}
}

View File

@@ -1,87 +0,0 @@
// 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);
}

View File

@@ -9,3 +9,4 @@ fn bool safe_compare(void* data1, void* data2, usz len)
}
return match == 0;
}

View File

@@ -1,737 +0,0 @@
/*
Ed25519 Digital Signature Algorithm
*/
module std::crypto::ed25519;
import std::hash::sha512;
alias Ed25519PrivateKey = char[32];
alias Ed25519PublicKey = char[Ed25519PrivateKey.len];
alias Ed25519Signature = char[2 * Ed25519PublicKey.len];
<*
Generate a public key from a private key.
@param [in] private_key : "32 bytes of cryptographically secure random data"
@require private_key.len == Ed25519PrivateKey.len
*>
fn Ed25519PublicKey public_keygen(char[] private_key)
{
return pack(&&unproject(&&(BASE * expand_private_key(private_key)[:FBaseInt.len])));
}
<*
Sign a message.
@param [in] message
@param [in] private_key
@param [in] public_key
@require private_key.len == Ed25519PrivateKey.len
@require public_key.len == Ed25519PublicKey.len
*>
fn Ed25519Signature sign(char[] message, char[] private_key, char[] public_key)
{
Ed25519Signature r @noinit;
char[*] exp = expand_private_key(private_key);
Sha512 sha @noinit;
sha.init();
sha.update(exp[FBaseInt.len..]);
sha.update(message);
FBaseInt k = from_bytes(&&sha.final());
r[:F25519Int.len] = pack(&&unproject(&&(BASE * k[..])))[..];
sha.init();
sha.update(r[:F25519Int.len]);
sha.update(public_key);
sha.update(message);
FBaseInt z = from_bytes(&&sha.final());
FBaseInt e = from_bytes(exp[:FBaseInt.len]);
r[F25519Int.len..] = (z * e + k)[..];
return r;
}
<*
Verify the signature of a message.
@param [in] message
@param [in] signature
@param [in] public_key
@require signature.len == Ed25519Signature.len
@require public_key.len == Ed25519PublicKey.len
*>
fn bool verify(char[] message, char[] signature, char[] public_key)
{
char ok = 1;
F25519Int lhs = pack(&&unproject(&&(BASE * signature[F25519Int.len..])));
Unpacking unp_p = unpack_on_curve((F25519Int*)public_key);
Projection p = project(&unp_p.point);
ok &= unp_p.on_curve;
Sha512 sha @noinit;
sha.init();
sha.update(signature[:F25519Int.len]);
sha.update(public_key);
sha.update(message);
FBaseInt z = from_bytes(&&sha.final());
p = p * z[..];
Unpacking unp_q = unpack_on_curve((F25519Int*)signature[:F25519Int.len]);
Projection q = project(&unp_q.point);
ok &= unp_q.on_curve;
p = p + q;
F25519Int rhs = pack(&&unproject(&p));
return (bool)(ok & eq(&lhs, &rhs));
}
// Base point for Ed25519. Generate a subgroup of order 2^252+0x14def9dea2f79cd65812631a5cf5d3ed
const Projection BASE @private =
{
x"1ad5258f602d56c9 b2a7259560c72c69 5cdcd6fd31e2a4c0 fe536ecdd3366921",
x"5866666666666666 6666666666666666 6666666666666666 6666666666666666",
x"a3ddb7a5b38ade6d f5525177809ff020 7de3ab648e4eea66 65768bd70f5f8767",
ONE
};
<*
Compute the pruned SHA-512 hash of a private key.
@param [in] private_key
@require private_key.len == Ed25519PrivateKey.len
*>
fn char[sha512::HASH_SIZE] expand_private_key(char[] private_key) @local
{
char[*] r = sha512::hash(private_key);
r[0] &= 0b11111000;
r[FBaseInt.len - 1] &= 0b01111111;
r[FBaseInt.len - 1] |= 0b01000000;
return r;
}
/*
Operations on the twisted Edwards curve -x^2+y^2=1-121665/121666*x^2*y^2 over the prime field F_(2^255-19) (edwards25519)
The set of F_(2^255-19)-rational curve points is a group of order 2^3*(2^252+0x14def9dea2f79cd65812631a5cf5d3ed)
*/
module std::crypto::ed25519 @private;
// Affine coordinates.
struct Point
{
F25519Int x;
F25519Int y;
}
// Projective coordinates.
struct Projection
{
F25519Int x;
F25519Int y;
F25519Int t;
F25519Int z;
}
// Neutral.
const Projection NEUTRAL =
{
ZERO,
ONE,
ZERO,
ONE
};
<*
Convert affine to projective coordinates.
@param [&in] p
*>
fn Projection project(Point* p) => { p.x, p.y, p.x * p.y, ONE };
<*
Convert projective to affine coordinates.
@param [&in] p
*>
fn Point unproject(Projection* p)
{
Point r @noinit;
F25519Int inv = p.z.inv();
r.x = p.x * inv;
r.y = p.y * inv;
r.x.normalize();
r.y.normalize();
return r;
}
// d parameter for edwards25519 : -121665/121666
const F25519Int D = x"a3785913ca4deb75 abd841414d0a7000 98e879777940c78c 73fe6f2bee6c0352";
// 2*d
const F25519Int DD = x"59f1b226949bd6eb 56b183829a14e000 30d1f3eef2808e19 e7fcdf56dcd90624";
<*
Compress a point.
@param [&in] p
*>
fn F25519Int pack(Point* p)
{
Point r = *p;
r.x.normalize();
r.y.normalize();
r.y[^1] |= (r.x[0] & 1) << 7;
return r.y;
}
struct Unpacking
{
Point point;
<* Non-zero if true. *>
char on_curve;
}
<*
Uncompress a point. Check if it is on the curve.
@param [&in] encoding
*>
fn Unpacking unpack_on_curve(F25519Int* encoding)
{
Point p @noinit;
char parity = (*encoding)[^1] >> 7;
p.y = *encoding;
p.y[^1] &= 0b01111111;
F25519Int y2 = p.y * p.y;
F25519Int x2 = (D * y2 + ONE).inv() * (y2 - ONE);
F25519Int x = x2.sqrt();
p.x = f25519_select(&x, &&-x, (x[0] ^ parity) & 1);
F25519Int _x2 = p.x * p.x;
x2.normalize();
_x2.normalize();
return {p, eq(&x2, &_x2)};
}
macro Projection Projection.@add(&s, Projection #p) @operator(+) => s.add(@addr(#p));
<*
Addition.
@param [&in] s
*>
fn Projection Projection.add(&s, Projection* p) @operator(+)
{
Projection r @noinit;
F25519Int a = (s.y - s.x) * (p.y - p.x);
F25519Int b = (s.y + s.x) * (p.y + p.x);
F25519Int c = s.t * DD * p.t;
F25519Int d = (s.z * p.z).mul_s(2);
F25519Int e = b - a;
F25519Int f = d - c;
F25519Int g = d + c;
F25519Int h = b + a;
r.x = e * f;
r.y = g * h;
r.t = e * h;
r.z = f * g;
return r;
}
<*
Double a point.
@param [&in] s
*>
fn Projection Projection.twice(&s)
{
Projection r @noinit;
F25519Int a = s.x * s.x;
F25519Int b = s.y * s.y;
F25519Int c = (s.z * s.z).mul_s(2);
F25519Int d = s.x + s.y;
F25519Int e = d * d - a - b;
F25519Int g = b - a;
F25519Int f = g - c;
F25519Int h = -b - a;
r.x = e * f;
r.y = g * h;
r.t = e * h;
r.z = f * g;
return r;
}
<*
Variable base scalar multiplication.
@param [&in] s
@param [in] n
*>
fn Projection Projection.mul(&s, char[] n) @operator(*)
{
Projection r = NEUTRAL;
for (isz i = n.len << 3 - 1; i >= 0; i--)
{
r = r.twice();
Projection t = r + s;
char bit = n[i >> 3] >> (i & 7) & 1;
r.x = f25519_select(&r.x, &t.x, bit);
r.y = f25519_select(&r.y, &t.y, bit);
r.z = f25519_select(&r.z, &t.z, bit);
r.t = f25519_select(&r.t, &t.t, bit);
}
return r;
}
/*
Modular arithmetic over the prime field F_(2^255-19)
*/
module std::crypto::ed25519 @private;
typedef F25519Int = inline char[32];
const F25519Int ZERO = {};
const F25519Int ONE = {[0] = 1};
<*
Reduce an element with carry to at most 2^255+18 (32 bytes)
@param [&inout] s
*>
fn void F25519Int.reduce_carry(&s, uint carry)
{
// Reduce using 2^255 = 19 mod p
(*s)[^1] &= 0b01111111;
carry *= 19;
foreach (i, &v : s)
{
carry += *v;
*v = (char)carry;
carry >>= 8;
}
}
<*
Reduce an element to at most 2^255-19
@param [&inout] s
*>
fn void F25519Int.normalize(&s)
{
s.reduce_carry((*s)[^1] >> 7);
// Subtract p
F25519Int sub @noinit;
ushort c = 19;
foreach (i, v : (*s)[:^1])
{
c += v;
sub[i] = (char)c;
c >>= 8;
}
c += (*s)[^1] - 0b10000000;
sub[^1] = (char)c;
*s = f25519_select(&sub, s, (char)(c >> 15));
}
<*
Constant-time equality comparison. Return is non-zero if true.
@param [&in] a
@param [&in] b
*>
fn char eq(F25519Int* a, F25519Int* b)
{
char e;
foreach (i, v : a) e |= v ^ (*b)[i];
e |= (e >> 4);
e |= (e >> 2);
e |= (e >> 1);
return e ^ 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"
*>
fn F25519Int f25519_select(F25519Int* zero, F25519Int* one, char condition)
{
F25519Int r @noinit;
foreach (i, z : zero) r[i] = z ^ (-condition & ((*one)[i] ^ z));
return r;
}
macro F25519Int F25519Int.@add(&s, F25519Int #n) @operator(+) => s.add(@addr(#n));
<*
Addition.
@param [&in] s
@param [&in] n
*>
fn F25519Int F25519Int.add(&s, F25519Int* n) @operator(+)
{
F25519Int r @noinit;
ushort c;
foreach (i, v : s)
{
c >>= 8;
c += v + (*n)[i];
r[i] = (char)c;
}
r.reduce_carry(c >> 7);
return r;
}
macro F25519Int F25519Int.@sub(&s, F25519Int #n) @operator(-) => s.sub(@addr(#n));
<*
Subtraction.
@param [&in] s
@param [&in] n
*>
fn F25519Int F25519Int.sub(&s, F25519Int* n) @operator(-)
{
// Compute s+2*p-n instead of s-n to avoid underflow.
F25519Int r @noinit;
uint c = (char)~(2 * 19 - 1);
foreach (i, v : (*s)[:^1])
{
c += 0b11111111_00000000 + v - (*n)[i];
r[i] = (char)c;
c >>= 8;
}
c += (*s)[^1] - (*n)[^1];
r[^1] = (char)c;
r.reduce_carry(c >> 7);
return r;
}
<*
Negation.
@param [&in] s
*>
fn F25519Int F25519Int.neg(&s) @operator(-)
{
// Compute 2*p-s instead of -s to avoid underflow.
F25519Int r @noinit;
uint c = (char)~(2 * 19 - 1);
foreach (i, v : (*s)[:^1])
{
c += 0b11111111_00000000 - v;
r[i] = (char)c;
c >>= 8;
}
c -= (*s)[^1];
r[^1] = (char)c;
r.reduce_carry(c >> 7);
return r;
}
macro F25519Int F25519Int.@mul(&s, F25519Int #n) @operator(*) => s.mul(@addr(#n));
<*
Multiplication.
@param [&in] s
@param [&in] n
*>
fn F25519Int F25519Int.mul(&s, F25519Int* n) @operator(*)
{
F25519Int r @noinit;
uint c;
for (usz i = 0; i < F25519Int.len; i++)
{
c >>= 8;
for (usz j; j <= i; j++) c += (*s)[j] * (*n)[i - j];
// Reduce using 2^256 = 2*19 mod p
for (usz j = i + 1; j < F25519Int.len; j++) c += (*s)[j] * (*n)[^j - i] * 2 * 19;
r[i] = (char)c;
}
r.reduce_carry(c >> 7);
return r;
}
<*
Multiplication by a small element.
@param [&in] s
*>
fn F25519Int F25519Int.mul_s(&s, uint n)
{
F25519Int r @noinit;
uint c;
foreach (i, v : s)
{
c >>= 8;
c += v * n;
r[i] = (char)c;
}
r.reduce_carry(c >> 7);
return r;
}
<*
Inverse an element.
@param [&in] s
*>
fn F25519Int F25519Int.inv(&s)
{
//Compute s^(p-2)
F25519Int r = *s;
for (usz i; i < 255 - 1 - 5; i++) r = r * r * s;
r *= r;
r = r * r * s;
r *= r;
r = r * r * s;
r = r * r * s;
return r;
}
<*
Raise an element to the power of 2^252-3
@param [&in] s
*>
fn F25519Int F25519Int.pow_2523(&s) @local
{
F25519Int r = *s;
for (usz i; i < 252 - 1 - 2; i++) r = r * r * s;
r *= r;
r = r * r * s;
return r;
}
<*
Compute the square root of an element.
@param [&in] s
*>
fn F25519Int F25519Int.sqrt(&s)
{
F25519Int twice = s.mul_s(2);
F25519Int pow = twice.pow_2523();
return (twice * pow * pow - ONE) * s * pow;
}
/*
Modular arithmetic over the prime field F_(2^252+0x14def9dea2f79cd65812631a5cf5d3ed)
*/
module std::crypto::ed25519 @private;
import std::math;
typedef FBaseInt = inline char[32];
// Order of the field : 2^252+0x14def9dea2f79cd65812631a5cf5d3ed
const FBaseInt ORDER = x"edd3f55c1a631258 d69cf7a2def9de14 0000000000000000 0000000000000010";
<*
Interpret bytes as a normalized element.
@param [in] bytes
*>
fn FBaseInt from_bytes(char[] bytes)
{
FBaseInt r;
usz bitc = min(252 - 1, bytes.len << 3);
usz bytec = bitc >> 3;
usz mod = bitc & 7;
usz rem = bytes.len << 3 - bitc;
r[:bytec] = bytes[^bytec..];
if (mod)
{
r <<= mod;
r[0] |= bytes[^bytec + 1] >> (8 - mod);
}
for (isz i = rem - 1; i >= 0; i--)
{
r <<= 1;
r[0] |= bytes[i >> 3] >> (i & 7) & 1;
r = r.sub_l(&ORDER);
}
return r;
}
<*
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"
*>
fn FBaseInt fbase_select(FBaseInt* zero, FBaseInt* one, char condition)
{
FBaseInt r @noinit;
foreach (i, z : zero) r[i] = z ^ (-condition & ((*one)[i] ^ z));
return r;
}
macro FBaseInt FBaseInt.@add(&s, FBaseInt #n) @operator(+) => s.add(@addr(#n));
<*
Addition.
@param [&in] s
@param [&in] n
*>
fn FBaseInt FBaseInt.add(&s, FBaseInt* n) @operator(+)
{
FBaseInt r @noinit;
ushort c;
foreach (i, v : s)
{
c += v + (*n)[i];
r[i] = (char)c;
c >>= 8;
}
return r.sub_l(&ORDER);
}
<*
Subtraction if RHS is less than LHS else identity.
@param [&in] s
@param [&in] n
*>
fn FBaseInt FBaseInt.sub_l(&s, FBaseInt* n)
{
FBaseInt sub @noinit;
ushort c;
foreach (i, v : s)
{
c = v - (*n)[i] - c;
sub[i] = (char)c;
c = (c >> 8) & 1;
}
return fbase_select(&sub, s, (char)c);
}
<*
Left shift.
@param [&in] s
*>
fn FBaseInt FBaseInt.shl(&s, usz n) @operator(<<)
{
FBaseInt r @noinit;
ushort c;
foreach (i, v : s)
{
c |= v << n;
r[i] = (char)c;
c >>= 8;
}
return r;
}
macro FBaseInt FBaseInt.@mul(&s, FBaseInt #n) @operator(*) => s.mul(@addr(#n));
<*
Multiplication.
@param [&in] s
@param [&in] n
*>
fn FBaseInt FBaseInt.mul(&s, FBaseInt* n) @operator(*)
{
FBaseInt r;
for (isz i = 252; i >= 0; i--)
{
r = (r << 1).sub_l(&ORDER);
r = fbase_select(&r, &&(r + s), (*n)[i >> 3] >> (i & 7) & 1);
}
return r;
}

View File

@@ -5,9 +5,7 @@ module std::encoding::json;
import std::io;
import std::collections::object;
faultdef UNEXPECTED_CHARACTER, INVALID_ESCAPE_SEQUENCE, INVALID_NUMBER, MAX_DEPTH_REACHED;
int max_depth = 128;
faultdef UNEXPECTED_CHARACTER, INVALID_ESCAPE_SEQUENCE, DUPLICATE_MEMBERS, INVALID_NUMBER;
fn Object*? parse_string(Allocator allocator, String s)
{
@@ -26,7 +24,7 @@ fn Object*? parse(Allocator allocator, InStream s)
JsonContext context = { .last_string = dstring::new_with_capacity(smem, 64), .stream = s, .allocator = allocator };
@pool()
{
return parse_any(&context)!;
return parse_any(&context);
};
};
}
@@ -64,14 +62,11 @@ struct JsonContext @local
DString last_string;
double last_number;
char current;
int depth;
bitstruct : char
{
bitstruct : char {
bool skip_comments;
bool reached_end;
bool pushed_back;
}
}
@@ -110,16 +105,10 @@ fn JsonTokenType? lex_number(JsonContext *context, char c) @local
t.append(c);
c = read_next(context)!;
}
bool leading_zero = c == '0';
while (c.is_digit())
{
t.append(c);
c = read_next(context)!;
if (leading_zero)
{
if (c.is_digit()) return INVALID_NUMBER?;
leading_zero = false;
}
}
if (c == '.')
{
@@ -159,8 +148,6 @@ fn Object*? parse_map(JsonContext* context) @local
Object* map = object::new_obj(context.allocator);
defer catch map.free();
JsonTokenType token = advance(context)!;
defer context.depth--;
if (++context.depth >= max_depth) return json::MAX_DEPTH_REACHED?;
@stack_mem(256; Allocator mem)
{
@@ -169,6 +156,7 @@ fn Object*? parse_map(JsonContext* context) @local
{
if (token != JsonTokenType.STRING) return UNEXPECTED_CHARACTER?;
DString string = context.last_string;
if (map.has_key(string.str_view())) return DUPLICATE_MEMBERS?;
// Copy the key to our temp holder, since our
// last_string may be used in parse_any
temp_key.clear();
@@ -192,8 +180,6 @@ fn Object*? parse_array(JsonContext* context) @local
{
Object* list = object::new_obj(context.allocator);
defer catch list.free();
defer context.depth--;
if (++context.depth >= max_depth) return json::MAX_DEPTH_REACHED?;
JsonTokenType token = advance(context)!;
while (token != JsonTokenType.RBRACKET)
{
@@ -262,7 +248,7 @@ fn JsonTokenType? advance(JsonContext* context) @local
case '\v':
continue;
case '/':
if (!context.skip_comments) break WS;
if (!context.skip_comments) break;
c = read_next(context)!;
if (c != '*')
{

View File

@@ -1,96 +0,0 @@
// 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.
//
// An implementation of Aleksey Vaneev's a5hash, version 5.16, in C3:
// https://github.com/avaneev/komihash
//
// The license for komihash from the above repository at the time of writing is as follows:
//
// >> MIT License
// >>
// >> Copyright (c) 2025 Aleksey Vaneev
// >>
// >> 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.
//
//
module std::hash::a5hash;
macro void @a5mul(#u, #v, #lo, #hi) @local
{
uint128 imd = (uint128)#u * (uint128)#v;
#lo = (ulong)imd;
#hi = (ulong)(imd >> 64);
}
fn ulong hash(char[] data, ulong seed = 0)
{
ulong seed1 = 0x243F_6A88_85A3_08D3 ^ data.len;
ulong seed2 = 0x4528_21E6_38D0_1377 ^ data.len;
ulong val10 = 0xAAAA_AAAA_AAAA_AAAA;
ulong val01 = 0x5555_5555_5555_5555;
ulong a, b;
@a5mul(seed2 ^ (seed & val10), seed1 ^ (seed & val01), seed1, seed2);
val10 ^= seed2;
if (@likely(data.len > 3))
{
if (data.len > 16)
{
val01 ^= seed1;
for (; data.len > 16; data = data[16..])
{
@a5mul(
@unaligned_load(((ulong*)data.ptr)[0], 1) ^ seed1,
@unaligned_load(((ulong*)data.ptr)[1], 1) ^ seed2,
seed1, seed2
);
seed1 += val01;
seed2 += val10;
}
a = @unaligned_load(*(ulong*)(data.ptr + (uptr)data.len - 16), 1);
b = @unaligned_load(*(ulong*)(data.ptr + (uptr)data.len - 8), 1);
}
else
{
a = ((ulong)@unaligned_load(*(uint*)&data[0], 1) << 32)
| @unaligned_load(*(uint*)&data[^4], 1);
b = ((ulong)@unaligned_load(*(uint*)&data[(data.len >> 3) * 4], 1) << 32)
| @unaligned_load(*(uint*)(data.ptr + data.len - 4 - (data.len >> 3) * 4), 1);
}
}
else
{
a = data.len ? (data[0] | (data.len > 1 ? ((ulong)data[1] << 8) : 0) | (data.len > 2 ? ((ulong)data[2] << 16) : 0)) : 0;
b = 0;
}
@a5mul(a ^ seed1, b ^ seed2, seed1, seed2);
@a5mul(val01 ^ seed1, seed2, a, b);
return a ^ b;
}

View File

@@ -83,7 +83,7 @@ fn char[HASH_BYTES] Hmac.final(&self)
const IPAD @private = 0x36;
const OPAD @private = 0x5C;
macro void @derive(Hmac *hmac_start, char[] salt, uint iterations, usz index, char[] out)
macro @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 void @derive(Hmac *hmac_start, char[] salt, uint iterations, usz index, ch
out[i] ^= v;
}
}
}
}

View File

@@ -1,156 +0,0 @@
// 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.
//
// An implementation of Aleksey Vaneev's komihash, version 5.27, in C3:
// https://github.com/avaneev/komihash
//
// The license for komihash from the above repository at the time of writing is as follows:
//
// >> MIT License
// >>
// >> Copyright (c) 2021-2025 Aleksey Vaneev
// >>
// >> 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.
//
//
module std::hash::komi;
macro void @komimul(#u, #v, #lo, #hi) @local
{
uint128 imd = (uint128)#u * (uint128)#v;
#lo = (ulong)imd;
#hi += (ulong)(imd >> 64);
}
fn ulong hash(char[] data, ulong seed = 0)
{
ulong seed1 = 0x243F_6A88_85A3_08D3 ^ (seed & 0x5555_5555_5555_5555);
ulong seed5 = 0x4528_21E6_38D0_1377 ^ (seed & 0xAAAA_AAAA_AAAA_AAAA);
ulong r1h, r2h;
// HASHROUND
@komimul(seed1, seed5, seed1, seed5);
seed1 ^= seed5;
if (@likely(data.len < 16))
{
r1h = seed1;
r2h = seed5;
if (@likely(data.len >= 8))
{
r1h ^= @unaligned_load(*(ulong*)data.ptr, 1);
r2h ^= (data.len < 12)
? ((data[data.len - 3] | ((ulong)data[data.len - 2] << 8) | ((ulong)data[data.len - 1] << 16) | ((ulong)1 << 24)) >> ((data.len * 8) ^ 88))
: (((@unaligned_load(*(uint*)&data[^4], 1) | ((ulong)1 << 32)) >> (128 - data.len * 8)) << 32 | @unaligned_load(*(uint*)&data[8], 1));
}
else if (data.len != 0)
{
r1h ^= (data.len < 4)
? (((ulong)1 << (data.len * 8)) ^ data[0] ^ (data.len > 1 ? (ulong)data[1] << 8 : 0) ^ (data.len > 2 ? (ulong)data[2] << 16 : 0))
: (((@unaligned_load(*(uint*)&data[^4], 1) | ((ulong)1 << 32)) >> (64 - data.len * 8)) << 32 | @unaligned_load(*(uint*)&data[0], 1));
}
}
else if (data.len < 32)
{
// HASH16
@komimul(
@unaligned_load(*(ulong*)&data[0], 1) ^ seed1,
@unaligned_load(*(ulong*)&data[8], 1) ^ seed5,
seed1, seed5
);
seed1 ^= seed5;
if (data.len < 24)
{
r1h = (((@unaligned_load(*(ulong*)&data[^8], 1) >> 8) | ((ulong)1 << 56)) >> (((int)(data.len * 8) ^ 184))) ^ seed1;
r2h = seed5;
}
else
{
r1h = @unaligned_load(*(ulong*)&data[16], 1) ^ seed1;
r2h = (((@unaligned_load(*(ulong*)&data[^8], 1) >> 8) | ((ulong)1 << 56)) >> (((int)(data.len * 8) ^ 248))) ^ seed5;
}
}
else
{
if (data.len >= 64)
{
ulong[8] seeds = {
seed1, 0x1319_8A2E_0370_7344 ^ seed1, 0xA409_3822_299F_31D0 ^ seed1, 0x082E_FA98_EC4E_6C89 ^ seed1,
seed5, 0xBE54_66CF_34E9_0C6C ^ seed5, 0xC0AC_29B7_C97C_50DD ^ seed5, 0x3F84_D5B5_B547_0917 ^ seed5,
};
// HASHLOOP64
for (; data.len >= 64; data = data[64:^64])
{
$for var $x = 0; $x < 4; ++$x :
@komimul(
@unaligned_load(*(ulong*)&data[0 + ($x * 8)], 1) ^ seeds[$x],
@unaligned_load(*(ulong*)&data[32 + ($x * 8)], 1) ^ seeds[4 + $x],
seeds[$x], seeds[4 + $x]
);
$endfor
seeds[3] ^= seeds[6];
seeds[0] ^= seeds[7];
seeds[2] ^= seeds[5];
seeds[1] ^= seeds[4];
}
seed1 = seeds[0] ^ seeds[1] ^ seeds[2] ^ seeds[3];
seed5 = seeds[4] ^ seeds[5] ^ seeds[6] ^ seeds[7];
}
for (; data.len >= 16; data = data[16:^16])
{
@komimul(
@unaligned_load(*(ulong*)&data[0], 1) ^ seed1,
@unaligned_load(*(ulong*)&data[8], 1) ^ seed5,
seed1, seed5
);
seed1 ^= seed5;
}
if (data.len < 8)
{
// NOTE: This is translated from the original code. It grabs the last ulong off the buffer even though the
// data slice is less than 8 bytes. This is possible because this branch only occurs in a loop where
// the original data slice length is >= 32.
r1h = (((@unaligned_load(*(ulong*)(data.ptr + data.len - 8), 1) >> 8) | ((ulong)1 << 56)) >> ((data.len * 8) ^ 0x38)) ^ seed1;
r2h = seed5;
}
else
{
r1h = @unaligned_load(*(ulong*)data.ptr, 1) ^ seed1;
r2h = (((@unaligned_load(*(ulong*)&data[^8], 1) >> 8) | ((ulong)1 << 56)) >> ((data.len * 8) ^ 0x78)) ^ seed5;
}
}
// HASHFIN
@komimul(r1h, r2h, seed1, seed5);
seed1 ^= seed5;
@komimul(seed1, seed5, seed1, seed5);
seed1 ^= seed5;
return seed1;
}

View File

@@ -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 void @step(#f, a, b, c, d, ptr, n, t, s)
macro @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));

View File

@@ -1,149 +0,0 @@
// 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.
//
// MetroHash64 and MetroHash128 are different enough to warrant their own
// modules, and there would be no reason to create a generic module just
// for the two. If you inspect the differences, the only shared portion
// of the entire process is the `update` method.
//
module std::hash::metro128;
const ulong[4] K @local = {
0xc83a91e1,
0x8648dbdb,
0x7bdec03b,
0x2f5870a5,
};
struct MetroHash128
{
union
{
ulong[4] state;
uint128 result;
}
union
{
ulong[4] stomach_64;
char[32] stomach;
}
ulong bytes;
}
fn uint128 hash(char[] data, ulong seed = 0)
{
MetroHash128 m;
m.init(seed);
m.update(data);
return m.final();
}
fn void MetroHash128.init(&self, ulong seed = 0)
{
self.state = {
(seed - K[0]) * K[3],
(seed + K[1]) * K[2],
(seed + K[0]) * K[2],
(seed - K[1]) * K[3],
};
}
fn void MetroHash128.update(&self, char[] data)
{
if (self.bytes % 32) // partial buffer
{
ulong to_fill = min(data.len, (32 - (self.bytes % 32)));
self.stomach[(self.bytes % 32):to_fill] = data[:to_fill];
data = data[to_fill..];
self.bytes += to_fill;
if (self.bytes % 32) return; // still awaiting more input, or final
self.state[0] += self.stomach_64[0] * K[0]; self.state[0] = self.state[0].rotr(29) + self.state[2];
self.state[1] += self.stomach_64[1] * K[1]; self.state[1] = self.state[1].rotr(29) + self.state[3];
self.state[2] += self.stomach_64[2] * K[2]; self.state[2] = self.state[2].rotr(29) + self.state[0];
self.state[3] += self.stomach_64[3] * K[3]; self.state[3] = self.state[3].rotr(29) + self.state[1];
}
self.bytes += data.len;
for (; data.len >= 32; data = data[32:^32])
{
self.state[0] += @unaligned_load(((ulong*)data.ptr)[0], 1) * K[0]; self.state[0] = self.state[0].rotr(29) + self.state[2];
self.state[1] += @unaligned_load(((ulong*)data.ptr)[1], 1) * K[1]; self.state[1] = self.state[1].rotr(29) + self.state[3];
self.state[2] += @unaligned_load(((ulong*)data.ptr)[2], 1) * K[2]; self.state[2] = self.state[2].rotr(29) + self.state[0];
self.state[3] += @unaligned_load(((ulong*)data.ptr)[3], 1) * K[3]; self.state[3] = self.state[3].rotr(29) + self.state[1];
}
// Gobble up the leftover bytes. Nom nom.
if (data.len > 0) self.stomach[:data.len] = data[..];
}
fn uint128 MetroHash128.final(&self)
{
if (self.bytes >= 32)
{
self.state[2] ^= (((self.state[0] + self.state[3]) * K[0]) + self.state[1]).rotr(21) * K[1];
self.state[3] ^= (((self.state[1] + self.state[2]) * K[1]) + self.state[0]).rotr(21) * K[0];
self.state[0] ^= (((self.state[0] + self.state[2]) * K[0]) + self.state[3]).rotr(21) * K[1];
self.state[1] ^= (((self.state[1] + self.state[3]) * K[1]) + self.state[2]).rotr(21) * K[0];
}
char[] final_data = self.stomach[:(self.bytes % 32)];
if (final_data.len >= 16)
{
self.state[0] += ((ulong*)final_data.ptr)[0] * K[2]; self.state[0] = self.state[0].rotr(33) * K[3];
self.state[1] += ((ulong*)final_data.ptr)[1] * K[2]; self.state[1] = self.state[1].rotr(33) * K[3];
self.state[0] ^= ((self.state[0] * K[2]) + self.state[1]).rotr(45) * K[1];
self.state[1] ^= ((self.state[1] * K[3]) + self.state[0]).rotr(45) * K[0];
final_data = final_data[16:^16];
}
if (final_data.len >= 8)
{
self.state[0] += @unaligned_load(((ulong*)final_data.ptr)[0], 1) * K[2]; self.state[0] = self.state[0].rotr(33) * K[3];
self.state[0] ^= ((self.state[0] * K[2]) + self.state[1]).rotr(27) * K[1];
final_data = final_data[8:^8];
}
if (final_data.len >= 4)
{
self.state[1] += @unaligned_load(((uint*)final_data.ptr)[0], 1) * K[2]; self.state[1] = self.state[1].rotr(33) * K[3];
self.state[1] ^= ((self.state[1] * K[3]) + self.state[0]).rotr(46) * K[0];
final_data = final_data[4:^4];
}
if (final_data.len >= 2)
{
self.state[0] += @unaligned_load(((ushort*)final_data.ptr)[0], 1) * K[2]; self.state[0] = self.state[0].rotr(33) * K[3];
self.state[0] ^= ((self.state[0] * K[2]) + self.state[1]).rotr(22) * K[1];
final_data = final_data[2:^2];
}
if (final_data.len >= 1)
{
self.state[1] += ((char*)final_data.ptr)[0] * K[2]; self.state[1] = self.state[1].rotr(33) * K[3];
self.state[1] ^= ((self.state[1] * K[3]) + self.state[0]).rotr(58) * K[0];
}
self.state[0] += ((self.state[0] * K[0]) + self.state[1]).rotr(13);
self.state[1] += ((self.state[1] * K[1]) + self.state[0]).rotr(37);
self.state[0] += ((self.state[0] * K[2]) + self.state[1]).rotr(13);
self.state[1] += ((self.state[1] * K[3]) + self.state[0]).rotr(37);
return self.result;
}

View File

@@ -1,152 +0,0 @@
// 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.
//
// MetroHash64 and MetroHash128 are different enough to warrant their own
// modules, and there would be no reason to create a generic module just
// for the two. If you inspect the differences, the only shared portion
// of the entire process is the `update` method.
//
module std::hash::metro64;
const ulong[4] K @local = {
0xd6d018f5,
0xa2aa033b,
0x62992fc1,
0x30bc5b29,
};
struct MetroHash64
{
union
{
ulong[4] state;
ulong result;
}
union
{
ulong[4] stomach_64;
char[32] stomach;
}
ulong bytes;
ulong vseed;
}
fn ulong hash(char[] data, ulong seed = 0)
{
MetroHash64 m;
m.init(seed);
m.update(data);
return m.final();
}
fn void MetroHash64.init(&self, ulong seed = 0)
{
self.vseed = (seed + K[2]) * K[0];
self.state[0] = self.vseed;
self.state[1] = self.vseed;
self.state[2] = self.vseed;
self.state[3] = self.vseed;
}
fn void MetroHash64.update(&self, char[] data)
{
if (self.bytes % 32) // partial buffer
{
ulong to_fill = min(data.len, (32 - (self.bytes % 32)));
self.stomach[(self.bytes % 32):to_fill] = data[:to_fill];
data = data[to_fill..];
self.bytes += to_fill;
if (self.bytes % 32) return; // still awaiting more input, or final
self.state[0] += self.stomach_64[0] * K[0]; self.state[0] = self.state[0].rotr(29) + self.state[2];
self.state[1] += self.stomach_64[1] * K[1]; self.state[1] = self.state[1].rotr(29) + self.state[3];
self.state[2] += self.stomach_64[2] * K[2]; self.state[2] = self.state[2].rotr(29) + self.state[0];
self.state[3] += self.stomach_64[3] * K[3]; self.state[3] = self.state[3].rotr(29) + self.state[1];
}
self.bytes += data.len;
for (; data.len >= 32; data = data[32:^32])
{
self.state[0] += @unaligned_load(((ulong*)data.ptr)[0], 1) * K[0]; self.state[0] = self.state[0].rotr(29) + self.state[2];
self.state[1] += @unaligned_load(((ulong*)data.ptr)[1], 1) * K[1]; self.state[1] = self.state[1].rotr(29) + self.state[3];
self.state[2] += @unaligned_load(((ulong*)data.ptr)[2], 1) * K[2]; self.state[2] = self.state[2].rotr(29) + self.state[0];
self.state[3] += @unaligned_load(((ulong*)data.ptr)[3], 1) * K[3]; self.state[3] = self.state[3].rotr(29) + self.state[1];
}
// Gobble up the leftover bytes. Nom nom.
if (data.len > 0) self.stomach[:data.len] = data[..];
}
fn ulong MetroHash64.final(&self)
{
if (self.bytes >= 32)
{
self.state[2] ^= (((self.state[0] + self.state[3]) * K[0]) + self.state[1]).rotr(37) * K[1];
self.state[3] ^= (((self.state[1] + self.state[2]) * K[1]) + self.state[0]).rotr(37) * K[0];
self.state[0] ^= (((self.state[0] + self.state[2]) * K[0]) + self.state[3]).rotr(37) * K[1];
self.state[1] ^= (((self.state[1] + self.state[3]) * K[1]) + self.state[2]).rotr(37) * K[0];
self.state[0] = self.vseed + (self.state[0] ^ self.state[1]);
}
char[] final_data = self.stomach[:(self.bytes % 32)];
if (final_data.len >= 16)
{
self.state[1] = self.state[0] + @unaligned_load(((ulong*)final_data.ptr)[0], 1) * K[2]; self.state[1] = self.state[1].rotr(29) * K[3];
self.state[2] = self.state[0] + @unaligned_load(((ulong*)final_data.ptr)[1], 1) * K[2]; self.state[2] = self.state[2].rotr(29) * K[3];
self.state[1] ^= (self.state[1] * K[0]).rotr(21) + self.state[2];
self.state[2] ^= (self.state[2] * K[3]).rotr(21) + self.state[1];
self.state[0] += self.state[2];
final_data = final_data[16:^16];
}
if (final_data.len >= 8)
{
self.state[0] += @unaligned_load(((ulong*)final_data.ptr)[0], 1) * K[3];
self.state[0] ^= self.state[0].rotr(55) * K[1];
final_data = final_data[8:^8];
}
if (final_data.len >= 4)
{
self.state[0] += @unaligned_load(((uint*)final_data.ptr)[0], 1) * K[3];
self.state[0] ^= self.state[0].rotr(26) * K[1];
final_data = final_data[4:^4];
}
if (final_data.len >= 2)
{
self.state[0] += @unaligned_load(((ushort*)final_data.ptr)[0], 1) * K[3];
self.state[0] ^= self.state[0].rotr(48) * K[1];
final_data = final_data[2:^2];
}
if (final_data.len >= 1)
{
self.state[0] += ((char*)final_data.ptr)[0] * K[3];
self.state[0] ^= self.state[0].rotr(37) * K[1];
}
self.state[0] ^= self.state[0].rotr(28);
self.state[0] *= K[0];
self.state[0] ^= self.state[0].rotr(29);
return self.result;
}

View File

@@ -103,13 +103,13 @@ union Long16 @local
uint[16] l;
}
macro uint blk(Long16* block, i) @local
macro 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 uint blk0(Long16* block, i) @local
macro blk0(Long16* block, i) @local
{
$if env::BIG_ENDIAN:
return block.l[i];
@@ -119,35 +119,35 @@ macro uint blk0(Long16* block, i) @local
$endif
}
macro void r0(Long16* block, uint v, uint* wref, uint x, uint y, uint* z, uint i) @local
macro 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 void r1(Long16* block, uint v, uint* wref, uint x, uint y, uint* z, uint i) @local
macro 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 void r2(Long16* block, uint v, uint* wref, uint x, uint y, uint* z, uint i) @local
macro 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 void r3(Long16* block, uint v, uint* wref, uint x, uint y, uint* z, uint i) @local
macro 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 void r4(Long16* block, uint v, uint* wref, uint x, uint y, uint* z, uint i) @local
macro 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 = {};
}
}

View File

@@ -1,164 +0,0 @@
// 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.
<*
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 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 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 };
<*
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 };
struct SipHash
{
ulong[4] v;
long m;
int m_idx;
usz len;
}
fn OutType hash(char[] data, uint128 key)
{
SipHash s;
s.init(key);
s.update(data);
return s.final();
}
fn void SipHash.init(&self, uint128 key)
{
ulong[2] key_64 = bitcast(key, ulong[2]);
self.v = {
0x736f_6d65_7073_6575 ^ key_64[0],
0x646f_7261_6e64_6f6d ^ key_64[1],
0x6c79_6765_6e65_7261 ^ key_64[0],
0x7465_6462_7974_6573 ^ key_64[1],
};
$if OutType.typeid == uint128.typeid :
self.v[1] ^= 0xEE;
$endif
}
<*
@param [in] data : "Bytes to hash"
*>
fn void SipHash.update(&self, char[] data)
{
self.len += data.len;
foreach (byte : data)
{
self.m |= (long)byte << (self.m_idx++ << 3);
if (self.m_idx < 8) continue;
// This runs every time the m_idx is 8, then it resets to 0.
self.v[3] ^= self.m;
$for var $i = 0; $i < BLOCK_ROUNDS; ++$i : // unrolled loop
self.round();
$endfor
self.v[0] ^= self.m;
self.m_idx = 0;
self.m = 0;
}
}
fn OutType SipHash.final(&self)
{
char[8] last = { [7] = (char)self.len };
self.update(last[(self.m_idx < 7 ? self.m_idx : 7)..]);
$if OutType.typeid == uint128.typeid :
self.v[2] ^= 0xEE;
$else
self.v[2] ^= 0xFF;
$endif
$for var $i = 0; $i < FINALIZE_ROUNDS; ++$i : // unrolled loop
self.round();
$endfor
$if OutType.typeid == ulong.typeid :
return self.v[0] ^ self.v[1] ^ self.v[2] ^ self.v[3];
$else
ulong lo = self.v[0] ^ self.v[1] ^ self.v[2] ^ self.v[3];
self.v[1] ^= 0xDD;
$for var $i = 0; $i < FINALIZE_ROUNDS; ++$i : // unrolled loop
self.round();
$endfor
return lo | ((uint128)(self.v[0] ^ self.v[1] ^ self.v[2] ^ self.v[3]) << 64);
$endif
}
fn void SipHash.round(&self) @local
{
self.v[0] += self.v[1];
self.v[1] = self.v[1].rotl(13);
self.v[1] ^= self.v[0];
self.v[0] = self.v[0].rotl(32);
self.v[2] += self.v[3];
self.v[3] = self.v[3].rotl(16);
self.v[3] ^= self.v[2];
self.v[0] += self.v[3];
self.v[3] = self.v[3].rotl(21);
self.v[3] ^= self.v[0];
self.v[2] += self.v[1];
self.v[1] = self.v[1].rotl(17);
self.v[1] ^= self.v[2];
self.v[2] = self.v[2].rotl(32);
}

View File

@@ -1,185 +0,0 @@
// 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.
//
// Dedicated from repo: https://github.com/NotsoanoNimus/whirlpool.c3l
module std::hash::whirlpool;
import std::hash::hmac;
const BLOCK_SIZE = 64;
const HASH_SIZE = 64;
const BLOCK_128 = 64 / int128.sizeof;
struct Whirlpool
{
ulong[8] hash;
union
{
char[BLOCK_SIZE] block;
int128[BLOCK_128] block_128;
}
// Using these two integers with a funnel shift permits the original WHIRLPOOL 2^256 length limit.
uint128 counter_high;
uint128 counter_low;
}
alias HmacWhirlpool = Hmac { Whirlpool, HASH_SIZE, BLOCK_SIZE };
alias hmac = hmac::hash { Whirlpool, HASH_SIZE, BLOCK_SIZE };
alias pbkdf2 = hmac::pbkdf2 { Whirlpool, HASH_SIZE, BLOCK_SIZE };
fn char[HASH_SIZE] hash(char[] data)
{
Whirlpool w;
w.update(data);
return w.final();
}
// Added to satisfy HMAC
macro void Whirlpool.init(&self) => *self = { };
<*
@require data.len <= isz.max : "Update with smaller slices"
*>
fn void Whirlpool.update(&self, char[] data)
{
char remainder = (char)self.counter_low & 0x3F; // if not at an even BLOCK_SIZE, how many bytes are over it
uint to_pad = BLOCK_SIZE - remainder; // how many bytes must be filled to get the BLOCK_SIZE
uint128 prev_ctr = self.counter_low;
self.counter_low += data.len;
// Handle counter rollover by just adding 1 onto 'high'. The overflow amount in 'low' remains valid.
// Since 'data' is limited to INT64_MAX, there are no edge cases where 'high' must be incremented by more than 1.
// This is so much data (> 2^128 bytes) that this will likely NEVER happen in practice...
if (@unlikely(self.counter_low < prev_ctr)) ++self.counter_high;
// Pad partial blocks of input data.
if (remainder > 0)
{
usz span = to_pad < data.len ? to_pad : data.len;
self.block[remainder:span] = data[:span];
// When there wasn't enough data ingested to fill a block, just return to allow more incoming data to fill in.
// If the algorithm is finalized during a 'partial' block, it has its own padding to fill the remaining gap.
if (data.len < to_pad) return;
self.process_block(&self.block);
data = data[to_pad..];
}
// Digest blocks wholesale.
while (data.len >= BLOCK_SIZE)
{
self.process_block(data);
data = (data.len > BLOCK_SIZE) ? data[BLOCK_SIZE..] : {};
}
// Any leftovers should be swept into the 'block' buffer in the context for the next update or for 'final'.
if (data.len)
{
usz leftover_span = (BLOCK_SIZE < data.len ? BLOCK_SIZE : data.len);
self.block[:leftover_span] = data[:leftover_span];
}
}
fn char[HASH_SIZE] Whirlpool.final(&self)
{
char remainder = (char)self.counter_low & 0x3F; // if not at an even BLOCK_SIZE, how many bytes are over it
// Terminating byte gets added to the block.
self.block[remainder++] = 0x80;
// When fewer than 256 bits are available to store the counter into, pad the block with zeroes and process it.
if (remainder > 32)
{
if (remainder < 64) self.block[remainder..63] = 0x00;
self.process_block(&self.block);
remainder = 0;
}
// Zero out the rest of the chunk (or a new chunk if the above 'if' was true), up to byte 32.
if (remainder < 32) self.block[remainder..31] = 0x00;
// Insert the big-endian values of the counter as a suffix of the block.
// This 'counter' value is actually the number of BITS processed, hence the << 3 (or x8).
self.block_128[2] = $$bswap(self.counter_high << 3 | (self.counter_low >> 125 & 0x07));
self.block_128[3] = $$bswap(self.counter_low << 3);
// Process the final block.
self.process_block(&self.block);
// Each ulong in the resultant hash should be bit-swapped before the final return.
char[HASH_SIZE] hash @align(ulong.alignof);
ulong* hash_ref = (ulong*)&hash;
$for var $i = 0; $i < 8; ++$i :
hash_ref[$i] = $$bswap(self.hash[$i]);
$endfor
// All done!
return hash;
}
macro ulong @w_op(#src, $shift) @private
=> S_BOX[(0 * 256) + (int)(#src[($shift + 0) & 7] >> 56) ]
^ S_BOX[(1 * 256) + (int)(#src[($shift + 7) & 7] >> 48) & 0xFF]
^ S_BOX[(2 * 256) + (int)(#src[($shift + 6) & 7] >> 40) & 0xFF]
^ S_BOX[(3 * 256) + (int)(#src[($shift + 5) & 7] >> 32) & 0xFF]
^ S_BOX[(4 * 256) + (int)(#src[($shift + 4) & 7] >> 24) & 0xFF]
^ S_BOX[(5 * 256) + (int)(#src[($shift + 3) & 7] >> 16) & 0xFF]
^ S_BOX[(6 * 256) + (int)(#src[($shift + 2) & 7] >> 8) & 0xFF]
^ S_BOX[(7 * 256) + (int)(#src[($shift + 1) & 7] >> 0) & 0xFF];
const ulong[10] RC @private = {
0x1823c6e887b8014f,
0x36a6d2f5796f9152,
0x60bc9b8ea30c7b35,
0x1de0d7c22e4bfe57,
0x157737e59ff04ada,
0x58c9290ab1a06b85,
0xbd5d10f4cb3e0567,
0xe427418ba77d95d8,
0xfbee7c66dd17479e,
0xca2dbf07ad5a8333
};
const ROUNDS = 10;
fn void Whirlpool.process_block(&self, char* block) @local
{
ulong[2 * 8] k; // key
ulong[2 * 8] state; // state
// NOTE: These loops are unrolled with C3's Chad-tier compile-time evaluation.
$for var $round = 0; $round < 8; $round++:
k[$round] = self.hash[$round];
state[$round] = $$bswap(@unaligned_load(((ulong*)block)[$round], 1)) ^ self.hash[$round];
self.hash[$round] = state[$round];
$endfor
$for var $round = 0; $round < ROUNDS; ++$round :
var $m = $round % 2;
k[(($m ^ 1) * 8) + 0] = @w_op((&k[$m * 8]), 0) ^ RC[$round];
$for var $i = 1; $i < 8; $i++ :
k[(($m ^ 1) * 8) + $i] = @w_op((&k[$m * 8]), $i);
$endfor
$for var $i = 0; $i < 8; $i++ :
state[(($m ^ 1) * 8) + $i] = @w_op(&(state[$m * 8]), $i) ^ k[(($m ^ 1) * 8) + $i];
$endfor
$endfor
$for var $x = 0; $x < 8; $x++:
self.hash[$x] ^= state[$x];
$endfor
}

View File

@@ -1,539 +0,0 @@
// 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.
//
// Dedicated from repo: https://github.com/NotsoanoNimus/whirlpool.c3l
module std::hash::whirlpool @private;
// =========================================================================
// WHIRLPOOL s-box data.
const ulong[8 * 256] S_BOX = {
// C0
0x18186018c07830d8, 0x23238c2305af4626, 0xc6c63fc67ef991b8, 0xe8e887e8136fcdfb,
0x878726874ca113cb, 0xb8b8dab8a9626d11, 0x0101040108050209, 0x4f4f214f426e9e0d,
0x3636d836adee6c9b, 0xa6a6a2a6590451ff, 0xd2d26fd2debdb90c, 0xf5f5f3f5fb06f70e,
0x7979f979ef80f296, 0x6f6fa16f5fcede30, 0x91917e91fcef3f6d, 0x52525552aa07a4f8,
0x60609d6027fdc047, 0xbcbccabc89766535, 0x9b9b569baccd2b37, 0x8e8e028e048c018a,
0xa3a3b6a371155bd2, 0x0c0c300c603c186c, 0x7b7bf17bff8af684, 0x3535d435b5e16a80,
0x1d1d741de8693af5, 0xe0e0a7e05347ddb3, 0xd7d77bd7f6acb321, 0xc2c22fc25eed999c,
0x2e2eb82e6d965c43, 0x4b4b314b627a9629, 0xfefedffea321e15d, 0x575741578216aed5,
0x15155415a8412abd, 0x7777c1779fb6eee8, 0x3737dc37a5eb6e92, 0xe5e5b3e57b56d79e,
0x9f9f469f8cd92313, 0xf0f0e7f0d317fd23, 0x4a4a354a6a7f9420, 0xdada4fda9e95a944,
0x58587d58fa25b0a2, 0xc9c903c906ca8fcf, 0x2929a429558d527c, 0x0a0a280a5022145a,
0xb1b1feb1e14f7f50, 0xa0a0baa0691a5dc9, 0x6b6bb16b7fdad614, 0x85852e855cab17d9,
0xbdbdcebd8173673c, 0x5d5d695dd234ba8f, 0x1010401080502090, 0xf4f4f7f4f303f507,
0xcbcb0bcb16c08bdd, 0x3e3ef83eedc67cd3, 0x0505140528110a2d, 0x676781671fe6ce78,
0xe4e4b7e47353d597, 0x27279c2725bb4e02, 0x4141194132588273, 0x8b8b168b2c9d0ba7,
0xa7a7a6a7510153f6, 0x7d7de97dcf94fab2, 0x95956e95dcfb3749, 0xd8d847d88e9fad56,
0xfbfbcbfb8b30eb70, 0xeeee9fee2371c1cd, 0x7c7ced7cc791f8bb, 0x6666856617e3cc71,
0xdddd53dda68ea77b, 0x17175c17b84b2eaf, 0x4747014702468e45, 0x9e9e429e84dc211a,
0xcaca0fca1ec589d4, 0x2d2db42d75995a58, 0xbfbfc6bf9179632e, 0x07071c07381b0e3f,
0xadad8ead012347ac, 0x5a5a755aea2fb4b0, 0x838336836cb51bef, 0x3333cc3385ff66b6,
0x636391633ff2c65c, 0x02020802100a0412, 0xaaaa92aa39384993, 0x7171d971afa8e2de,
0xc8c807c80ecf8dc6, 0x19196419c87d32d1, 0x494939497270923b, 0xd9d943d9869aaf5f,
0xf2f2eff2c31df931, 0xe3e3abe34b48dba8, 0x5b5b715be22ab6b9, 0x88881a8834920dbc,
0x9a9a529aa4c8293e, 0x262698262dbe4c0b, 0x3232c8328dfa64bf, 0xb0b0fab0e94a7d59,
0xe9e983e91b6acff2, 0x0f0f3c0f78331e77, 0xd5d573d5e6a6b733, 0x80803a8074ba1df4,
0xbebec2be997c6127, 0xcdcd13cd26de87eb, 0x3434d034bde46889, 0x48483d487a759032,
0xffffdbffab24e354, 0x7a7af57af78ff48d, 0x90907a90f4ea3d64, 0x5f5f615fc23ebe9d,
0x202080201da0403d, 0x6868bd6867d5d00f, 0x1a1a681ad07234ca, 0xaeae82ae192c41b7,
0xb4b4eab4c95e757d, 0x54544d549a19a8ce, 0x93937693ece53b7f, 0x222288220daa442f,
0x64648d6407e9c863, 0xf1f1e3f1db12ff2a, 0x7373d173bfa2e6cc, 0x12124812905a2482,
0x40401d403a5d807a, 0x0808200840281048, 0xc3c32bc356e89b95, 0xecec97ec337bc5df,
0xdbdb4bdb9690ab4d, 0xa1a1bea1611f5fc0, 0x8d8d0e8d1c830791, 0x3d3df43df5c97ac8,
0x97976697ccf1335b, 0x0000000000000000, 0xcfcf1bcf36d483f9, 0x2b2bac2b4587566e,
0x7676c57697b3ece1, 0x8282328264b019e6, 0xd6d67fd6fea9b128, 0x1b1b6c1bd87736c3,
0xb5b5eeb5c15b7774, 0xafaf86af112943be, 0x6a6ab56a77dfd41d, 0x50505d50ba0da0ea,
0x45450945124c8a57, 0xf3f3ebf3cb18fb38, 0x3030c0309df060ad, 0xefef9bef2b74c3c4,
0x3f3ffc3fe5c37eda, 0x55554955921caac7, 0xa2a2b2a2791059db, 0xeaea8fea0365c9e9,
0x656589650fecca6a, 0xbabad2bab9686903, 0x2f2fbc2f65935e4a, 0xc0c027c04ee79d8e,
0xdede5fdebe81a160, 0x1c1c701ce06c38fc, 0xfdfdd3fdbb2ee746, 0x4d4d294d52649a1f,
0x92927292e4e03976, 0x7575c9758fbceafa, 0x06061806301e0c36, 0x8a8a128a249809ae,
0xb2b2f2b2f940794b, 0xe6e6bfe66359d185, 0x0e0e380e70361c7e, 0x1f1f7c1ff8633ee7,
0x6262956237f7c455, 0xd4d477d4eea3b53a, 0xa8a89aa829324d81, 0x96966296c4f43152,
0xf9f9c3f99b3aef62, 0xc5c533c566f697a3, 0x2525942535b14a10, 0x59597959f220b2ab,
0x84842a8454ae15d0, 0x7272d572b7a7e4c5, 0x3939e439d5dd72ec, 0x4c4c2d4c5a619816,
0x5e5e655eca3bbc94, 0x7878fd78e785f09f, 0x3838e038ddd870e5, 0x8c8c0a8c14860598,
0xd1d163d1c6b2bf17, 0xa5a5aea5410b57e4, 0xe2e2afe2434dd9a1, 0x616199612ff8c24e,
0xb3b3f6b3f1457b42, 0x2121842115a54234, 0x9c9c4a9c94d62508, 0x1e1e781ef0663cee,
0x4343114322528661, 0xc7c73bc776fc93b1, 0xfcfcd7fcb32be54f, 0x0404100420140824,
0x51515951b208a2e3, 0x99995e99bcc72f25, 0x6d6da96d4fc4da22, 0x0d0d340d68391a65,
0xfafacffa8335e979, 0xdfdf5bdfb684a369, 0x7e7ee57ed79bfca9, 0x242490243db44819,
0x3b3bec3bc5d776fe, 0xabab96ab313d4b9a, 0xcece1fce3ed181f0, 0x1111441188552299,
0x8f8f068f0c890383, 0x4e4e254e4a6b9c04, 0xb7b7e6b7d1517366, 0xebeb8beb0b60cbe0,
0x3c3cf03cfdcc78c1, 0x81813e817cbf1ffd, 0x94946a94d4fe3540, 0xf7f7fbf7eb0cf31c,
0xb9b9deb9a1676f18, 0x13134c13985f268b, 0x2c2cb02c7d9c5851, 0xd3d36bd3d6b8bb05,
0xe7e7bbe76b5cd38c, 0x6e6ea56e57cbdc39, 0xc4c437c46ef395aa, 0x03030c03180f061b,
0x565645568a13acdc, 0x44440d441a49885e, 0x7f7fe17fdf9efea0, 0xa9a99ea921374f88,
0x2a2aa82a4d825467, 0xbbbbd6bbb16d6b0a, 0xc1c123c146e29f87, 0x53535153a202a6f1,
0xdcdc57dcae8ba572, 0x0b0b2c0b58271653, 0x9d9d4e9d9cd32701, 0x6c6cad6c47c1d82b,
0x3131c43195f562a4, 0x7474cd7487b9e8f3, 0xf6f6fff6e309f115, 0x464605460a438c4c,
0xacac8aac092645a5, 0x89891e893c970fb5, 0x14145014a04428b4, 0xe1e1a3e15b42dfba,
0x16165816b04e2ca6, 0x3a3ae83acdd274f7, 0x6969b9696fd0d206, 0x09092409482d1241,
0x7070dd70a7ade0d7, 0xb6b6e2b6d954716f, 0xd0d067d0ceb7bd1e, 0xeded93ed3b7ec7d6,
0xcccc17cc2edb85e2, 0x424215422a578468, 0x98985a98b4c22d2c, 0xa4a4aaa4490e55ed,
0x2828a0285d885075, 0x5c5c6d5cda31b886, 0xf8f8c7f8933fed6b, 0x8686228644a411c2,
// C1
0xd818186018c07830, 0x2623238c2305af46, 0xb8c6c63fc67ef991, 0xfbe8e887e8136fcd,
0xcb878726874ca113, 0x11b8b8dab8a9626d, 0x0901010401080502, 0x0d4f4f214f426e9e,
0x9b3636d836adee6c, 0xffa6a6a2a6590451, 0x0cd2d26fd2debdb9, 0x0ef5f5f3f5fb06f7,
0x967979f979ef80f2, 0x306f6fa16f5fcede, 0x6d91917e91fcef3f, 0xf852525552aa07a4,
0x4760609d6027fdc0, 0x35bcbccabc897665, 0x379b9b569baccd2b, 0x8a8e8e028e048c01,
0xd2a3a3b6a371155b, 0x6c0c0c300c603c18, 0x847b7bf17bff8af6, 0x803535d435b5e16a,
0xf51d1d741de8693a, 0xb3e0e0a7e05347dd, 0x21d7d77bd7f6acb3, 0x9cc2c22fc25eed99,
0x432e2eb82e6d965c, 0x294b4b314b627a96, 0x5dfefedffea321e1, 0xd5575741578216ae,
0xbd15155415a8412a, 0xe87777c1779fb6ee, 0x923737dc37a5eb6e, 0x9ee5e5b3e57b56d7,
0x139f9f469f8cd923, 0x23f0f0e7f0d317fd, 0x204a4a354a6a7f94, 0x44dada4fda9e95a9,
0xa258587d58fa25b0, 0xcfc9c903c906ca8f, 0x7c2929a429558d52, 0x5a0a0a280a502214,
0x50b1b1feb1e14f7f, 0xc9a0a0baa0691a5d, 0x146b6bb16b7fdad6, 0xd985852e855cab17,
0x3cbdbdcebd817367, 0x8f5d5d695dd234ba, 0x9010104010805020, 0x07f4f4f7f4f303f5,
0xddcbcb0bcb16c08b, 0xd33e3ef83eedc67c, 0x2d0505140528110a, 0x78676781671fe6ce,
0x97e4e4b7e47353d5, 0x0227279c2725bb4e, 0x7341411941325882, 0xa78b8b168b2c9d0b,
0xf6a7a7a6a7510153, 0xb27d7de97dcf94fa, 0x4995956e95dcfb37, 0x56d8d847d88e9fad,
0x70fbfbcbfb8b30eb, 0xcdeeee9fee2371c1, 0xbb7c7ced7cc791f8, 0x716666856617e3cc,
0x7bdddd53dda68ea7, 0xaf17175c17b84b2e, 0x454747014702468e, 0x1a9e9e429e84dc21,
0xd4caca0fca1ec589, 0x582d2db42d75995a, 0x2ebfbfc6bf917963, 0x3f07071c07381b0e,
0xacadad8ead012347, 0xb05a5a755aea2fb4, 0xef838336836cb51b, 0xb63333cc3385ff66,
0x5c636391633ff2c6, 0x1202020802100a04, 0x93aaaa92aa393849, 0xde7171d971afa8e2,
0xc6c8c807c80ecf8d, 0xd119196419c87d32, 0x3b49493949727092, 0x5fd9d943d9869aaf,
0x31f2f2eff2c31df9, 0xa8e3e3abe34b48db, 0xb95b5b715be22ab6, 0xbc88881a8834920d,
0x3e9a9a529aa4c829, 0x0b262698262dbe4c, 0xbf3232c8328dfa64, 0x59b0b0fab0e94a7d,
0xf2e9e983e91b6acf, 0x770f0f3c0f78331e, 0x33d5d573d5e6a6b7, 0xf480803a8074ba1d,
0x27bebec2be997c61, 0xebcdcd13cd26de87, 0x893434d034bde468, 0x3248483d487a7590,
0x54ffffdbffab24e3, 0x8d7a7af57af78ff4, 0x6490907a90f4ea3d, 0x9d5f5f615fc23ebe,
0x3d202080201da040, 0x0f6868bd6867d5d0, 0xca1a1a681ad07234, 0xb7aeae82ae192c41,
0x7db4b4eab4c95e75, 0xce54544d549a19a8, 0x7f93937693ece53b, 0x2f222288220daa44,
0x6364648d6407e9c8, 0x2af1f1e3f1db12ff, 0xcc7373d173bfa2e6, 0x8212124812905a24,
0x7a40401d403a5d80, 0x4808082008402810, 0x95c3c32bc356e89b, 0xdfecec97ec337bc5,
0x4ddbdb4bdb9690ab, 0xc0a1a1bea1611f5f, 0x918d8d0e8d1c8307, 0xc83d3df43df5c97a,
0x5b97976697ccf133, 0x0000000000000000, 0xf9cfcf1bcf36d483, 0x6e2b2bac2b458756,
0xe17676c57697b3ec, 0xe68282328264b019, 0x28d6d67fd6fea9b1, 0xc31b1b6c1bd87736,
0x74b5b5eeb5c15b77, 0xbeafaf86af112943, 0x1d6a6ab56a77dfd4, 0xea50505d50ba0da0,
0x5745450945124c8a, 0x38f3f3ebf3cb18fb, 0xad3030c0309df060, 0xc4efef9bef2b74c3,
0xda3f3ffc3fe5c37e, 0xc755554955921caa, 0xdba2a2b2a2791059, 0xe9eaea8fea0365c9,
0x6a656589650fecca, 0x03babad2bab96869, 0x4a2f2fbc2f65935e, 0x8ec0c027c04ee79d,
0x60dede5fdebe81a1, 0xfc1c1c701ce06c38, 0x46fdfdd3fdbb2ee7, 0x1f4d4d294d52649a,
0x7692927292e4e039, 0xfa7575c9758fbcea, 0x3606061806301e0c, 0xae8a8a128a249809,
0x4bb2b2f2b2f94079, 0x85e6e6bfe66359d1, 0x7e0e0e380e70361c, 0xe71f1f7c1ff8633e,
0x556262956237f7c4, 0x3ad4d477d4eea3b5, 0x81a8a89aa829324d, 0x5296966296c4f431,
0x62f9f9c3f99b3aef, 0xa3c5c533c566f697, 0x102525942535b14a, 0xab59597959f220b2,
0xd084842a8454ae15, 0xc57272d572b7a7e4, 0xec3939e439d5dd72, 0x164c4c2d4c5a6198,
0x945e5e655eca3bbc, 0x9f7878fd78e785f0, 0xe53838e038ddd870, 0x988c8c0a8c148605,
0x17d1d163d1c6b2bf, 0xe4a5a5aea5410b57, 0xa1e2e2afe2434dd9, 0x4e616199612ff8c2,
0x42b3b3f6b3f1457b, 0x342121842115a542, 0x089c9c4a9c94d625, 0xee1e1e781ef0663c,
0x6143431143225286, 0xb1c7c73bc776fc93, 0x4ffcfcd7fcb32be5, 0x2404041004201408,
0xe351515951b208a2, 0x2599995e99bcc72f, 0x226d6da96d4fc4da, 0x650d0d340d68391a,
0x79fafacffa8335e9, 0x69dfdf5bdfb684a3, 0xa97e7ee57ed79bfc, 0x19242490243db448,
0xfe3b3bec3bc5d776, 0x9aabab96ab313d4b, 0xf0cece1fce3ed181, 0x9911114411885522,
0x838f8f068f0c8903, 0x044e4e254e4a6b9c, 0x66b7b7e6b7d15173, 0xe0ebeb8beb0b60cb,
0xc13c3cf03cfdcc78, 0xfd81813e817cbf1f, 0x4094946a94d4fe35, 0x1cf7f7fbf7eb0cf3,
0x18b9b9deb9a1676f, 0x8b13134c13985f26, 0x512c2cb02c7d9c58, 0x05d3d36bd3d6b8bb,
0x8ce7e7bbe76b5cd3, 0x396e6ea56e57cbdc, 0xaac4c437c46ef395, 0x1b03030c03180f06,
0xdc565645568a13ac, 0x5e44440d441a4988, 0xa07f7fe17fdf9efe, 0x88a9a99ea921374f,
0x672a2aa82a4d8254, 0x0abbbbd6bbb16d6b, 0x87c1c123c146e29f, 0xf153535153a202a6,
0x72dcdc57dcae8ba5, 0x530b0b2c0b582716, 0x019d9d4e9d9cd327, 0x2b6c6cad6c47c1d8,
0xa43131c43195f562, 0xf37474cd7487b9e8, 0x15f6f6fff6e309f1, 0x4c464605460a438c,
0xa5acac8aac092645, 0xb589891e893c970f, 0xb414145014a04428, 0xbae1e1a3e15b42df,
0xa616165816b04e2c, 0xf73a3ae83acdd274, 0x066969b9696fd0d2, 0x4109092409482d12,
0xd77070dd70a7ade0, 0x6fb6b6e2b6d95471, 0x1ed0d067d0ceb7bd, 0xd6eded93ed3b7ec7,
0xe2cccc17cc2edb85, 0x68424215422a5784, 0x2c98985a98b4c22d, 0xeda4a4aaa4490e55,
0x752828a0285d8850, 0x865c5c6d5cda31b8, 0x6bf8f8c7f8933fed, 0xc28686228644a411,
// C2
0x30d818186018c078, 0x462623238c2305af, 0x91b8c6c63fc67ef9, 0xcdfbe8e887e8136f,
0x13cb878726874ca1, 0x6d11b8b8dab8a962, 0x0209010104010805, 0x9e0d4f4f214f426e,
0x6c9b3636d836adee, 0x51ffa6a6a2a65904, 0xb90cd2d26fd2debd, 0xf70ef5f5f3f5fb06,
0xf2967979f979ef80, 0xde306f6fa16f5fce, 0x3f6d91917e91fcef, 0xa4f852525552aa07,
0xc04760609d6027fd, 0x6535bcbccabc8976, 0x2b379b9b569baccd, 0x018a8e8e028e048c,
0x5bd2a3a3b6a37115, 0x186c0c0c300c603c, 0xf6847b7bf17bff8a, 0x6a803535d435b5e1,
0x3af51d1d741de869, 0xddb3e0e0a7e05347, 0xb321d7d77bd7f6ac, 0x999cc2c22fc25eed,
0x5c432e2eb82e6d96, 0x96294b4b314b627a, 0xe15dfefedffea321, 0xaed5575741578216,
0x2abd15155415a841, 0xeee87777c1779fb6, 0x6e923737dc37a5eb, 0xd79ee5e5b3e57b56,
0x23139f9f469f8cd9, 0xfd23f0f0e7f0d317, 0x94204a4a354a6a7f, 0xa944dada4fda9e95,
0xb0a258587d58fa25, 0x8fcfc9c903c906ca, 0x527c2929a429558d, 0x145a0a0a280a5022,
0x7f50b1b1feb1e14f, 0x5dc9a0a0baa0691a, 0xd6146b6bb16b7fda, 0x17d985852e855cab,
0x673cbdbdcebd8173, 0xba8f5d5d695dd234, 0x2090101040108050, 0xf507f4f4f7f4f303,
0x8bddcbcb0bcb16c0, 0x7cd33e3ef83eedc6, 0x0a2d050514052811, 0xce78676781671fe6,
0xd597e4e4b7e47353, 0x4e0227279c2725bb, 0x8273414119413258, 0x0ba78b8b168b2c9d,
0x53f6a7a7a6a75101, 0xfab27d7de97dcf94, 0x374995956e95dcfb, 0xad56d8d847d88e9f,
0xeb70fbfbcbfb8b30, 0xc1cdeeee9fee2371, 0xf8bb7c7ced7cc791, 0xcc716666856617e3,
0xa77bdddd53dda68e, 0x2eaf17175c17b84b, 0x8e45474701470246, 0x211a9e9e429e84dc,
0x89d4caca0fca1ec5, 0x5a582d2db42d7599, 0x632ebfbfc6bf9179, 0x0e3f07071c07381b,
0x47acadad8ead0123, 0xb4b05a5a755aea2f, 0x1bef838336836cb5, 0x66b63333cc3385ff,
0xc65c636391633ff2, 0x041202020802100a, 0x4993aaaa92aa3938, 0xe2de7171d971afa8,
0x8dc6c8c807c80ecf, 0x32d119196419c87d, 0x923b494939497270, 0xaf5fd9d943d9869a,
0xf931f2f2eff2c31d, 0xdba8e3e3abe34b48, 0xb6b95b5b715be22a, 0x0dbc88881a883492,
0x293e9a9a529aa4c8, 0x4c0b262698262dbe, 0x64bf3232c8328dfa, 0x7d59b0b0fab0e94a,
0xcff2e9e983e91b6a, 0x1e770f0f3c0f7833, 0xb733d5d573d5e6a6, 0x1df480803a8074ba,
0x6127bebec2be997c, 0x87ebcdcd13cd26de, 0x68893434d034bde4, 0x903248483d487a75,
0xe354ffffdbffab24, 0xf48d7a7af57af78f, 0x3d6490907a90f4ea, 0xbe9d5f5f615fc23e,
0x403d202080201da0, 0xd00f6868bd6867d5, 0x34ca1a1a681ad072, 0x41b7aeae82ae192c,
0x757db4b4eab4c95e, 0xa8ce54544d549a19, 0x3b7f93937693ece5, 0x442f222288220daa,
0xc86364648d6407e9, 0xff2af1f1e3f1db12, 0xe6cc7373d173bfa2, 0x248212124812905a,
0x807a40401d403a5d, 0x1048080820084028, 0x9b95c3c32bc356e8, 0xc5dfecec97ec337b,
0xab4ddbdb4bdb9690, 0x5fc0a1a1bea1611f, 0x07918d8d0e8d1c83, 0x7ac83d3df43df5c9,
0x335b97976697ccf1, 0x0000000000000000, 0x83f9cfcf1bcf36d4, 0x566e2b2bac2b4587,
0xece17676c57697b3, 0x19e68282328264b0, 0xb128d6d67fd6fea9, 0x36c31b1b6c1bd877,
0x7774b5b5eeb5c15b, 0x43beafaf86af1129, 0xd41d6a6ab56a77df, 0xa0ea50505d50ba0d,
0x8a5745450945124c, 0xfb38f3f3ebf3cb18, 0x60ad3030c0309df0, 0xc3c4efef9bef2b74,
0x7eda3f3ffc3fe5c3, 0xaac755554955921c, 0x59dba2a2b2a27910, 0xc9e9eaea8fea0365,
0xca6a656589650fec, 0x6903babad2bab968, 0x5e4a2f2fbc2f6593, 0x9d8ec0c027c04ee7,
0xa160dede5fdebe81, 0x38fc1c1c701ce06c, 0xe746fdfdd3fdbb2e, 0x9a1f4d4d294d5264,
0x397692927292e4e0, 0xeafa7575c9758fbc, 0x0c3606061806301e, 0x09ae8a8a128a2498,
0x794bb2b2f2b2f940, 0xd185e6e6bfe66359, 0x1c7e0e0e380e7036, 0x3ee71f1f7c1ff863,
0xc4556262956237f7, 0xb53ad4d477d4eea3, 0x4d81a8a89aa82932, 0x315296966296c4f4,
0xef62f9f9c3f99b3a, 0x97a3c5c533c566f6, 0x4a102525942535b1, 0xb2ab59597959f220,
0x15d084842a8454ae, 0xe4c57272d572b7a7, 0x72ec3939e439d5dd, 0x98164c4c2d4c5a61,
0xbc945e5e655eca3b, 0xf09f7878fd78e785, 0x70e53838e038ddd8, 0x05988c8c0a8c1486,
0xbf17d1d163d1c6b2, 0x57e4a5a5aea5410b, 0xd9a1e2e2afe2434d, 0xc24e616199612ff8,
0x7b42b3b3f6b3f145, 0x42342121842115a5, 0x25089c9c4a9c94d6, 0x3cee1e1e781ef066,
0x8661434311432252, 0x93b1c7c73bc776fc, 0xe54ffcfcd7fcb32b, 0x0824040410042014,
0xa2e351515951b208, 0x2f2599995e99bcc7, 0xda226d6da96d4fc4, 0x1a650d0d340d6839,
0xe979fafacffa8335, 0xa369dfdf5bdfb684, 0xfca97e7ee57ed79b, 0x4819242490243db4,
0x76fe3b3bec3bc5d7, 0x4b9aabab96ab313d, 0x81f0cece1fce3ed1, 0x2299111144118855,
0x03838f8f068f0c89, 0x9c044e4e254e4a6b, 0x7366b7b7e6b7d151, 0xcbe0ebeb8beb0b60,
0x78c13c3cf03cfdcc, 0x1ffd81813e817cbf, 0x354094946a94d4fe, 0xf31cf7f7fbf7eb0c,
0x6f18b9b9deb9a167, 0x268b13134c13985f, 0x58512c2cb02c7d9c, 0xbb05d3d36bd3d6b8,
0xd38ce7e7bbe76b5c, 0xdc396e6ea56e57cb, 0x95aac4c437c46ef3, 0x061b03030c03180f,
0xacdc565645568a13, 0x885e44440d441a49, 0xfea07f7fe17fdf9e, 0x4f88a9a99ea92137,
0x54672a2aa82a4d82, 0x6b0abbbbd6bbb16d, 0x9f87c1c123c146e2, 0xa6f153535153a202,
0xa572dcdc57dcae8b, 0x16530b0b2c0b5827, 0x27019d9d4e9d9cd3, 0xd82b6c6cad6c47c1,
0x62a43131c43195f5, 0xe8f37474cd7487b9, 0xf115f6f6fff6e309, 0x8c4c464605460a43,
0x45a5acac8aac0926, 0x0fb589891e893c97, 0x28b414145014a044, 0xdfbae1e1a3e15b42,
0x2ca616165816b04e, 0x74f73a3ae83acdd2, 0xd2066969b9696fd0, 0x124109092409482d,
0xe0d77070dd70a7ad, 0x716fb6b6e2b6d954, 0xbd1ed0d067d0ceb7, 0xc7d6eded93ed3b7e,
0x85e2cccc17cc2edb, 0x8468424215422a57, 0x2d2c98985a98b4c2, 0x55eda4a4aaa4490e,
0x50752828a0285d88, 0xb8865c5c6d5cda31, 0xed6bf8f8c7f8933f, 0x11c28686228644a4,
// C3
0x7830d818186018c0, 0xaf462623238c2305, 0xf991b8c6c63fc67e, 0x6fcdfbe8e887e813,
0xa113cb878726874c, 0x626d11b8b8dab8a9, 0x0502090101040108, 0x6e9e0d4f4f214f42,
0xee6c9b3636d836ad, 0x0451ffa6a6a2a659, 0xbdb90cd2d26fd2de, 0x06f70ef5f5f3f5fb,
0x80f2967979f979ef, 0xcede306f6fa16f5f, 0xef3f6d91917e91fc, 0x07a4f852525552aa,
0xfdc04760609d6027, 0x766535bcbccabc89, 0xcd2b379b9b569bac, 0x8c018a8e8e028e04,
0x155bd2a3a3b6a371, 0x3c186c0c0c300c60, 0x8af6847b7bf17bff, 0xe16a803535d435b5,
0x693af51d1d741de8, 0x47ddb3e0e0a7e053, 0xacb321d7d77bd7f6, 0xed999cc2c22fc25e,
0x965c432e2eb82e6d, 0x7a96294b4b314b62, 0x21e15dfefedffea3, 0x16aed55757415782,
0x412abd15155415a8, 0xb6eee87777c1779f, 0xeb6e923737dc37a5, 0x56d79ee5e5b3e57b,
0xd923139f9f469f8c, 0x17fd23f0f0e7f0d3, 0x7f94204a4a354a6a, 0x95a944dada4fda9e,
0x25b0a258587d58fa, 0xca8fcfc9c903c906, 0x8d527c2929a42955, 0x22145a0a0a280a50,
0x4f7f50b1b1feb1e1, 0x1a5dc9a0a0baa069, 0xdad6146b6bb16b7f, 0xab17d985852e855c,
0x73673cbdbdcebd81, 0x34ba8f5d5d695dd2, 0x5020901010401080, 0x03f507f4f4f7f4f3,
0xc08bddcbcb0bcb16, 0xc67cd33e3ef83eed, 0x110a2d0505140528, 0xe6ce78676781671f,
0x53d597e4e4b7e473, 0xbb4e0227279c2725, 0x5882734141194132, 0x9d0ba78b8b168b2c,
0x0153f6a7a7a6a751, 0x94fab27d7de97dcf, 0xfb374995956e95dc, 0x9fad56d8d847d88e,
0x30eb70fbfbcbfb8b, 0x71c1cdeeee9fee23, 0x91f8bb7c7ced7cc7, 0xe3cc716666856617,
0x8ea77bdddd53dda6, 0x4b2eaf17175c17b8, 0x468e454747014702, 0xdc211a9e9e429e84,
0xc589d4caca0fca1e, 0x995a582d2db42d75, 0x79632ebfbfc6bf91, 0x1b0e3f07071c0738,
0x2347acadad8ead01, 0x2fb4b05a5a755aea, 0xb51bef838336836c, 0xff66b63333cc3385,
0xf2c65c636391633f, 0x0a04120202080210, 0x384993aaaa92aa39, 0xa8e2de7171d971af,
0xcf8dc6c8c807c80e, 0x7d32d119196419c8, 0x70923b4949394972, 0x9aaf5fd9d943d986,
0x1df931f2f2eff2c3, 0x48dba8e3e3abe34b, 0x2ab6b95b5b715be2, 0x920dbc88881a8834,
0xc8293e9a9a529aa4, 0xbe4c0b262698262d, 0xfa64bf3232c8328d, 0x4a7d59b0b0fab0e9,
0x6acff2e9e983e91b, 0x331e770f0f3c0f78, 0xa6b733d5d573d5e6, 0xba1df480803a8074,
0x7c6127bebec2be99, 0xde87ebcdcd13cd26, 0xe468893434d034bd, 0x75903248483d487a,
0x24e354ffffdbffab, 0x8ff48d7a7af57af7, 0xea3d6490907a90f4, 0x3ebe9d5f5f615fc2,
0xa0403d202080201d, 0xd5d00f6868bd6867, 0x7234ca1a1a681ad0, 0x2c41b7aeae82ae19,
0x5e757db4b4eab4c9, 0x19a8ce54544d549a, 0xe53b7f93937693ec, 0xaa442f222288220d,
0xe9c86364648d6407, 0x12ff2af1f1e3f1db, 0xa2e6cc7373d173bf, 0x5a24821212481290,
0x5d807a40401d403a, 0x2810480808200840, 0xe89b95c3c32bc356, 0x7bc5dfecec97ec33,
0x90ab4ddbdb4bdb96, 0x1f5fc0a1a1bea161, 0x8307918d8d0e8d1c, 0xc97ac83d3df43df5,
0xf1335b97976697cc, 0x0000000000000000, 0xd483f9cfcf1bcf36, 0x87566e2b2bac2b45,
0xb3ece17676c57697, 0xb019e68282328264, 0xa9b128d6d67fd6fe, 0x7736c31b1b6c1bd8,
0x5b7774b5b5eeb5c1, 0x2943beafaf86af11, 0xdfd41d6a6ab56a77, 0x0da0ea50505d50ba,
0x4c8a574545094512, 0x18fb38f3f3ebf3cb, 0xf060ad3030c0309d, 0x74c3c4efef9bef2b,
0xc37eda3f3ffc3fe5, 0x1caac75555495592, 0x1059dba2a2b2a279, 0x65c9e9eaea8fea03,
0xecca6a656589650f, 0x686903babad2bab9, 0x935e4a2f2fbc2f65, 0xe79d8ec0c027c04e,
0x81a160dede5fdebe, 0x6c38fc1c1c701ce0, 0x2ee746fdfdd3fdbb, 0x649a1f4d4d294d52,
0xe0397692927292e4, 0xbceafa7575c9758f, 0x1e0c360606180630, 0x9809ae8a8a128a24,
0x40794bb2b2f2b2f9, 0x59d185e6e6bfe663, 0x361c7e0e0e380e70, 0x633ee71f1f7c1ff8,
0xf7c4556262956237, 0xa3b53ad4d477d4ee, 0x324d81a8a89aa829, 0xf4315296966296c4,
0x3aef62f9f9c3f99b, 0xf697a3c5c533c566, 0xb14a102525942535, 0x20b2ab59597959f2,
0xae15d084842a8454, 0xa7e4c57272d572b7, 0xdd72ec3939e439d5, 0x6198164c4c2d4c5a,
0x3bbc945e5e655eca, 0x85f09f7878fd78e7, 0xd870e53838e038dd, 0x8605988c8c0a8c14,
0xb2bf17d1d163d1c6, 0x0b57e4a5a5aea541, 0x4dd9a1e2e2afe243, 0xf8c24e616199612f,
0x457b42b3b3f6b3f1, 0xa542342121842115, 0xd625089c9c4a9c94, 0x663cee1e1e781ef0,
0x5286614343114322, 0xfc93b1c7c73bc776, 0x2be54ffcfcd7fcb3, 0x1408240404100420,
0x08a2e351515951b2, 0xc72f2599995e99bc, 0xc4da226d6da96d4f, 0x391a650d0d340d68,
0x35e979fafacffa83, 0x84a369dfdf5bdfb6, 0x9bfca97e7ee57ed7, 0xb44819242490243d,
0xd776fe3b3bec3bc5, 0x3d4b9aabab96ab31, 0xd181f0cece1fce3e, 0x5522991111441188,
0x8903838f8f068f0c, 0x6b9c044e4e254e4a, 0x517366b7b7e6b7d1, 0x60cbe0ebeb8beb0b,
0xcc78c13c3cf03cfd, 0xbf1ffd81813e817c, 0xfe354094946a94d4, 0x0cf31cf7f7fbf7eb,
0x676f18b9b9deb9a1, 0x5f268b13134c1398, 0x9c58512c2cb02c7d, 0xb8bb05d3d36bd3d6,
0x5cd38ce7e7bbe76b, 0xcbdc396e6ea56e57, 0xf395aac4c437c46e, 0x0f061b03030c0318,
0x13acdc565645568a, 0x49885e44440d441a, 0x9efea07f7fe17fdf, 0x374f88a9a99ea921,
0x8254672a2aa82a4d, 0x6d6b0abbbbd6bbb1, 0xe29f87c1c123c146, 0x02a6f153535153a2,
0x8ba572dcdc57dcae, 0x2716530b0b2c0b58, 0xd327019d9d4e9d9c, 0xc1d82b6c6cad6c47,
0xf562a43131c43195, 0xb9e8f37474cd7487, 0x09f115f6f6fff6e3, 0x438c4c464605460a,
0x2645a5acac8aac09, 0x970fb589891e893c, 0x4428b414145014a0, 0x42dfbae1e1a3e15b,
0x4e2ca616165816b0, 0xd274f73a3ae83acd, 0xd0d2066969b9696f, 0x2d12410909240948,
0xade0d77070dd70a7, 0x54716fb6b6e2b6d9, 0xb7bd1ed0d067d0ce, 0x7ec7d6eded93ed3b,
0xdb85e2cccc17cc2e, 0x578468424215422a, 0xc22d2c98985a98b4, 0x0e55eda4a4aaa449,
0x8850752828a0285d, 0x31b8865c5c6d5cda, 0x3fed6bf8f8c7f893, 0xa411c28686228644,
// C4
0xc07830d818186018, 0x05af462623238c23, 0x7ef991b8c6c63fc6, 0x136fcdfbe8e887e8,
0x4ca113cb87872687, 0xa9626d11b8b8dab8, 0x0805020901010401, 0x426e9e0d4f4f214f,
0xadee6c9b3636d836, 0x590451ffa6a6a2a6, 0xdebdb90cd2d26fd2, 0xfb06f70ef5f5f3f5,
0xef80f2967979f979, 0x5fcede306f6fa16f, 0xfcef3f6d91917e91, 0xaa07a4f852525552,
0x27fdc04760609d60, 0x89766535bcbccabc, 0xaccd2b379b9b569b, 0x048c018a8e8e028e,
0x71155bd2a3a3b6a3, 0x603c186c0c0c300c, 0xff8af6847b7bf17b, 0xb5e16a803535d435,
0xe8693af51d1d741d, 0x5347ddb3e0e0a7e0, 0xf6acb321d7d77bd7, 0x5eed999cc2c22fc2,
0x6d965c432e2eb82e, 0x627a96294b4b314b, 0xa321e15dfefedffe, 0x8216aed557574157,
0xa8412abd15155415, 0x9fb6eee87777c177, 0xa5eb6e923737dc37, 0x7b56d79ee5e5b3e5,
0x8cd923139f9f469f, 0xd317fd23f0f0e7f0, 0x6a7f94204a4a354a, 0x9e95a944dada4fda,
0xfa25b0a258587d58, 0x06ca8fcfc9c903c9, 0x558d527c2929a429, 0x5022145a0a0a280a,
0xe14f7f50b1b1feb1, 0x691a5dc9a0a0baa0, 0x7fdad6146b6bb16b, 0x5cab17d985852e85,
0x8173673cbdbdcebd, 0xd234ba8f5d5d695d, 0x8050209010104010, 0xf303f507f4f4f7f4,
0x16c08bddcbcb0bcb, 0xedc67cd33e3ef83e, 0x28110a2d05051405, 0x1fe6ce7867678167,
0x7353d597e4e4b7e4, 0x25bb4e0227279c27, 0x3258827341411941, 0x2c9d0ba78b8b168b,
0x510153f6a7a7a6a7, 0xcf94fab27d7de97d, 0xdcfb374995956e95, 0x8e9fad56d8d847d8,
0x8b30eb70fbfbcbfb, 0x2371c1cdeeee9fee, 0xc791f8bb7c7ced7c, 0x17e3cc7166668566,
0xa68ea77bdddd53dd, 0xb84b2eaf17175c17, 0x02468e4547470147, 0x84dc211a9e9e429e,
0x1ec589d4caca0fca, 0x75995a582d2db42d, 0x9179632ebfbfc6bf, 0x381b0e3f07071c07,
0x012347acadad8ead, 0xea2fb4b05a5a755a, 0x6cb51bef83833683, 0x85ff66b63333cc33,
0x3ff2c65c63639163, 0x100a041202020802, 0x39384993aaaa92aa, 0xafa8e2de7171d971,
0x0ecf8dc6c8c807c8, 0xc87d32d119196419, 0x7270923b49493949, 0x869aaf5fd9d943d9,
0xc31df931f2f2eff2, 0x4b48dba8e3e3abe3, 0xe22ab6b95b5b715b, 0x34920dbc88881a88,
0xa4c8293e9a9a529a, 0x2dbe4c0b26269826, 0x8dfa64bf3232c832, 0xe94a7d59b0b0fab0,
0x1b6acff2e9e983e9, 0x78331e770f0f3c0f, 0xe6a6b733d5d573d5, 0x74ba1df480803a80,
0x997c6127bebec2be, 0x26de87ebcdcd13cd, 0xbde468893434d034, 0x7a75903248483d48,
0xab24e354ffffdbff, 0xf78ff48d7a7af57a, 0xf4ea3d6490907a90, 0xc23ebe9d5f5f615f,
0x1da0403d20208020, 0x67d5d00f6868bd68, 0xd07234ca1a1a681a, 0x192c41b7aeae82ae,
0xc95e757db4b4eab4, 0x9a19a8ce54544d54, 0xece53b7f93937693, 0x0daa442f22228822,
0x07e9c86364648d64, 0xdb12ff2af1f1e3f1, 0xbfa2e6cc7373d173, 0x905a248212124812,
0x3a5d807a40401d40, 0x4028104808082008, 0x56e89b95c3c32bc3, 0x337bc5dfecec97ec,
0x9690ab4ddbdb4bdb, 0x611f5fc0a1a1bea1, 0x1c8307918d8d0e8d, 0xf5c97ac83d3df43d,
0xccf1335b97976697, 0x0000000000000000, 0x36d483f9cfcf1bcf, 0x4587566e2b2bac2b,
0x97b3ece17676c576, 0x64b019e682823282, 0xfea9b128d6d67fd6, 0xd87736c31b1b6c1b,
0xc15b7774b5b5eeb5, 0x112943beafaf86af, 0x77dfd41d6a6ab56a, 0xba0da0ea50505d50,
0x124c8a5745450945, 0xcb18fb38f3f3ebf3, 0x9df060ad3030c030, 0x2b74c3c4efef9bef,
0xe5c37eda3f3ffc3f, 0x921caac755554955, 0x791059dba2a2b2a2, 0x0365c9e9eaea8fea,
0x0fecca6a65658965, 0xb9686903babad2ba, 0x65935e4a2f2fbc2f, 0x4ee79d8ec0c027c0,
0xbe81a160dede5fde, 0xe06c38fc1c1c701c, 0xbb2ee746fdfdd3fd, 0x52649a1f4d4d294d,
0xe4e0397692927292, 0x8fbceafa7575c975, 0x301e0c3606061806, 0x249809ae8a8a128a,
0xf940794bb2b2f2b2, 0x6359d185e6e6bfe6, 0x70361c7e0e0e380e, 0xf8633ee71f1f7c1f,
0x37f7c45562629562, 0xeea3b53ad4d477d4, 0x29324d81a8a89aa8, 0xc4f4315296966296,
0x9b3aef62f9f9c3f9, 0x66f697a3c5c533c5, 0x35b14a1025259425, 0xf220b2ab59597959,
0x54ae15d084842a84, 0xb7a7e4c57272d572, 0xd5dd72ec3939e439, 0x5a6198164c4c2d4c,
0xca3bbc945e5e655e, 0xe785f09f7878fd78, 0xddd870e53838e038, 0x148605988c8c0a8c,
0xc6b2bf17d1d163d1, 0x410b57e4a5a5aea5, 0x434dd9a1e2e2afe2, 0x2ff8c24e61619961,
0xf1457b42b3b3f6b3, 0x15a5423421218421, 0x94d625089c9c4a9c, 0xf0663cee1e1e781e,
0x2252866143431143, 0x76fc93b1c7c73bc7, 0xb32be54ffcfcd7fc, 0x2014082404041004,
0xb208a2e351515951, 0xbcc72f2599995e99, 0x4fc4da226d6da96d, 0x68391a650d0d340d,
0x8335e979fafacffa, 0xb684a369dfdf5bdf, 0xd79bfca97e7ee57e, 0x3db4481924249024,
0xc5d776fe3b3bec3b, 0x313d4b9aabab96ab, 0x3ed181f0cece1fce, 0x8855229911114411,
0x0c8903838f8f068f, 0x4a6b9c044e4e254e, 0xd1517366b7b7e6b7, 0x0b60cbe0ebeb8beb,
0xfdcc78c13c3cf03c, 0x7cbf1ffd81813e81, 0xd4fe354094946a94, 0xeb0cf31cf7f7fbf7,
0xa1676f18b9b9deb9, 0x985f268b13134c13, 0x7d9c58512c2cb02c, 0xd6b8bb05d3d36bd3,
0x6b5cd38ce7e7bbe7, 0x57cbdc396e6ea56e, 0x6ef395aac4c437c4, 0x180f061b03030c03,
0x8a13acdc56564556, 0x1a49885e44440d44, 0xdf9efea07f7fe17f, 0x21374f88a9a99ea9,
0x4d8254672a2aa82a, 0xb16d6b0abbbbd6bb, 0x46e29f87c1c123c1, 0xa202a6f153535153,
0xae8ba572dcdc57dc, 0x582716530b0b2c0b, 0x9cd327019d9d4e9d, 0x47c1d82b6c6cad6c,
0x95f562a43131c431, 0x87b9e8f37474cd74, 0xe309f115f6f6fff6, 0x0a438c4c46460546,
0x092645a5acac8aac, 0x3c970fb589891e89, 0xa04428b414145014, 0x5b42dfbae1e1a3e1,
0xb04e2ca616165816, 0xcdd274f73a3ae83a, 0x6fd0d2066969b969, 0x482d124109092409,
0xa7ade0d77070dd70, 0xd954716fb6b6e2b6, 0xceb7bd1ed0d067d0, 0x3b7ec7d6eded93ed,
0x2edb85e2cccc17cc, 0x2a57846842421542, 0xb4c22d2c98985a98, 0x490e55eda4a4aaa4,
0x5d8850752828a028, 0xda31b8865c5c6d5c, 0x933fed6bf8f8c7f8, 0x44a411c286862286,
// C5
0x18c07830d8181860, 0x2305af462623238c, 0xc67ef991b8c6c63f, 0xe8136fcdfbe8e887,
0x874ca113cb878726, 0xb8a9626d11b8b8da, 0x0108050209010104, 0x4f426e9e0d4f4f21,
0x36adee6c9b3636d8, 0xa6590451ffa6a6a2, 0xd2debdb90cd2d26f, 0xf5fb06f70ef5f5f3,
0x79ef80f2967979f9, 0x6f5fcede306f6fa1, 0x91fcef3f6d91917e, 0x52aa07a4f8525255,
0x6027fdc04760609d, 0xbc89766535bcbcca, 0x9baccd2b379b9b56, 0x8e048c018a8e8e02,
0xa371155bd2a3a3b6, 0x0c603c186c0c0c30, 0x7bff8af6847b7bf1, 0x35b5e16a803535d4,
0x1de8693af51d1d74, 0xe05347ddb3e0e0a7, 0xd7f6acb321d7d77b, 0xc25eed999cc2c22f,
0x2e6d965c432e2eb8, 0x4b627a96294b4b31, 0xfea321e15dfefedf, 0x578216aed5575741,
0x15a8412abd151554, 0x779fb6eee87777c1, 0x37a5eb6e923737dc, 0xe57b56d79ee5e5b3,
0x9f8cd923139f9f46, 0xf0d317fd23f0f0e7, 0x4a6a7f94204a4a35, 0xda9e95a944dada4f,
0x58fa25b0a258587d, 0xc906ca8fcfc9c903, 0x29558d527c2929a4, 0x0a5022145a0a0a28,
0xb1e14f7f50b1b1fe, 0xa0691a5dc9a0a0ba, 0x6b7fdad6146b6bb1, 0x855cab17d985852e,
0xbd8173673cbdbdce, 0x5dd234ba8f5d5d69, 0x1080502090101040, 0xf4f303f507f4f4f7,
0xcb16c08bddcbcb0b, 0x3eedc67cd33e3ef8, 0x0528110a2d050514, 0x671fe6ce78676781,
0xe47353d597e4e4b7, 0x2725bb4e0227279c, 0x4132588273414119, 0x8b2c9d0ba78b8b16,
0xa7510153f6a7a7a6, 0x7dcf94fab27d7de9, 0x95dcfb374995956e, 0xd88e9fad56d8d847,
0xfb8b30eb70fbfbcb, 0xee2371c1cdeeee9f, 0x7cc791f8bb7c7ced, 0x6617e3cc71666685,
0xdda68ea77bdddd53, 0x17b84b2eaf17175c, 0x4702468e45474701, 0x9e84dc211a9e9e42,
0xca1ec589d4caca0f, 0x2d75995a582d2db4, 0xbf9179632ebfbfc6, 0x07381b0e3f07071c,
0xad012347acadad8e, 0x5aea2fb4b05a5a75, 0x836cb51bef838336, 0x3385ff66b63333cc,
0x633ff2c65c636391, 0x02100a0412020208, 0xaa39384993aaaa92, 0x71afa8e2de7171d9,
0xc80ecf8dc6c8c807, 0x19c87d32d1191964, 0x497270923b494939, 0xd9869aaf5fd9d943,
0xf2c31df931f2f2ef, 0xe34b48dba8e3e3ab, 0x5be22ab6b95b5b71, 0x8834920dbc88881a,
0x9aa4c8293e9a9a52, 0x262dbe4c0b262698, 0x328dfa64bf3232c8, 0xb0e94a7d59b0b0fa,
0xe91b6acff2e9e983, 0x0f78331e770f0f3c, 0xd5e6a6b733d5d573, 0x8074ba1df480803a,
0xbe997c6127bebec2, 0xcd26de87ebcdcd13, 0x34bde468893434d0, 0x487a75903248483d,
0xffab24e354ffffdb, 0x7af78ff48d7a7af5, 0x90f4ea3d6490907a, 0x5fc23ebe9d5f5f61,
0x201da0403d202080, 0x6867d5d00f6868bd, 0x1ad07234ca1a1a68, 0xae192c41b7aeae82,
0xb4c95e757db4b4ea, 0x549a19a8ce54544d, 0x93ece53b7f939376, 0x220daa442f222288,
0x6407e9c86364648d, 0xf1db12ff2af1f1e3, 0x73bfa2e6cc7373d1, 0x12905a2482121248,
0x403a5d807a40401d, 0x0840281048080820, 0xc356e89b95c3c32b, 0xec337bc5dfecec97,
0xdb9690ab4ddbdb4b, 0xa1611f5fc0a1a1be, 0x8d1c8307918d8d0e, 0x3df5c97ac83d3df4,
0x97ccf1335b979766, 0x0000000000000000, 0xcf36d483f9cfcf1b, 0x2b4587566e2b2bac,
0x7697b3ece17676c5, 0x8264b019e6828232, 0xd6fea9b128d6d67f, 0x1bd87736c31b1b6c,
0xb5c15b7774b5b5ee, 0xaf112943beafaf86, 0x6a77dfd41d6a6ab5, 0x50ba0da0ea50505d,
0x45124c8a57454509, 0xf3cb18fb38f3f3eb, 0x309df060ad3030c0, 0xef2b74c3c4efef9b,
0x3fe5c37eda3f3ffc, 0x55921caac7555549, 0xa2791059dba2a2b2, 0xea0365c9e9eaea8f,
0x650fecca6a656589, 0xbab9686903babad2, 0x2f65935e4a2f2fbc, 0xc04ee79d8ec0c027,
0xdebe81a160dede5f, 0x1ce06c38fc1c1c70, 0xfdbb2ee746fdfdd3, 0x4d52649a1f4d4d29,
0x92e4e03976929272, 0x758fbceafa7575c9, 0x06301e0c36060618, 0x8a249809ae8a8a12,
0xb2f940794bb2b2f2, 0xe66359d185e6e6bf, 0x0e70361c7e0e0e38, 0x1ff8633ee71f1f7c,
0x6237f7c455626295, 0xd4eea3b53ad4d477, 0xa829324d81a8a89a, 0x96c4f43152969662,
0xf99b3aef62f9f9c3, 0xc566f697a3c5c533, 0x2535b14a10252594, 0x59f220b2ab595979,
0x8454ae15d084842a, 0x72b7a7e4c57272d5, 0x39d5dd72ec3939e4, 0x4c5a6198164c4c2d,
0x5eca3bbc945e5e65, 0x78e785f09f7878fd, 0x38ddd870e53838e0, 0x8c148605988c8c0a,
0xd1c6b2bf17d1d163, 0xa5410b57e4a5a5ae, 0xe2434dd9a1e2e2af, 0x612ff8c24e616199,
0xb3f1457b42b3b3f6, 0x2115a54234212184, 0x9c94d625089c9c4a, 0x1ef0663cee1e1e78,
0x4322528661434311, 0xc776fc93b1c7c73b, 0xfcb32be54ffcfcd7, 0x0420140824040410,
0x51b208a2e3515159, 0x99bcc72f2599995e, 0x6d4fc4da226d6da9, 0x0d68391a650d0d34,
0xfa8335e979fafacf, 0xdfb684a369dfdf5b, 0x7ed79bfca97e7ee5, 0x243db44819242490,
0x3bc5d776fe3b3bec, 0xab313d4b9aabab96, 0xce3ed181f0cece1f, 0x1188552299111144,
0x8f0c8903838f8f06, 0x4e4a6b9c044e4e25, 0xb7d1517366b7b7e6, 0xeb0b60cbe0ebeb8b,
0x3cfdcc78c13c3cf0, 0x817cbf1ffd81813e, 0x94d4fe354094946a, 0xf7eb0cf31cf7f7fb,
0xb9a1676f18b9b9de, 0x13985f268b13134c, 0x2c7d9c58512c2cb0, 0xd3d6b8bb05d3d36b,
0xe76b5cd38ce7e7bb, 0x6e57cbdc396e6ea5, 0xc46ef395aac4c437, 0x03180f061b03030c,
0x568a13acdc565645, 0x441a49885e44440d, 0x7fdf9efea07f7fe1, 0xa921374f88a9a99e,
0x2a4d8254672a2aa8, 0xbbb16d6b0abbbbd6, 0xc146e29f87c1c123, 0x53a202a6f1535351,
0xdcae8ba572dcdc57, 0x0b582716530b0b2c, 0x9d9cd327019d9d4e, 0x6c47c1d82b6c6cad,
0x3195f562a43131c4, 0x7487b9e8f37474cd, 0xf6e309f115f6f6ff, 0x460a438c4c464605,
0xac092645a5acac8a, 0x893c970fb589891e, 0x14a04428b4141450, 0xe15b42dfbae1e1a3,
0x16b04e2ca6161658, 0x3acdd274f73a3ae8, 0x696fd0d2066969b9, 0x09482d1241090924,
0x70a7ade0d77070dd, 0xb6d954716fb6b6e2, 0xd0ceb7bd1ed0d067, 0xed3b7ec7d6eded93,
0xcc2edb85e2cccc17, 0x422a578468424215, 0x98b4c22d2c98985a, 0xa4490e55eda4a4aa,
0x285d8850752828a0, 0x5cda31b8865c5c6d, 0xf8933fed6bf8f8c7, 0x8644a411c2868622,
// C6
0x6018c07830d81818, 0x8c2305af46262323, 0x3fc67ef991b8c6c6, 0x87e8136fcdfbe8e8,
0x26874ca113cb8787, 0xdab8a9626d11b8b8, 0x0401080502090101, 0x214f426e9e0d4f4f,
0xd836adee6c9b3636, 0xa2a6590451ffa6a6, 0x6fd2debdb90cd2d2, 0xf3f5fb06f70ef5f5,
0xf979ef80f2967979, 0xa16f5fcede306f6f, 0x7e91fcef3f6d9191, 0x5552aa07a4f85252,
0x9d6027fdc0476060, 0xcabc89766535bcbc, 0x569baccd2b379b9b, 0x028e048c018a8e8e,
0xb6a371155bd2a3a3, 0x300c603c186c0c0c, 0xf17bff8af6847b7b, 0xd435b5e16a803535,
0x741de8693af51d1d, 0xa7e05347ddb3e0e0, 0x7bd7f6acb321d7d7, 0x2fc25eed999cc2c2,
0xb82e6d965c432e2e, 0x314b627a96294b4b, 0xdffea321e15dfefe, 0x41578216aed55757,
0x5415a8412abd1515, 0xc1779fb6eee87777, 0xdc37a5eb6e923737, 0xb3e57b56d79ee5e5,
0x469f8cd923139f9f, 0xe7f0d317fd23f0f0, 0x354a6a7f94204a4a, 0x4fda9e95a944dada,
0x7d58fa25b0a25858, 0x03c906ca8fcfc9c9, 0xa429558d527c2929, 0x280a5022145a0a0a,
0xfeb1e14f7f50b1b1, 0xbaa0691a5dc9a0a0, 0xb16b7fdad6146b6b, 0x2e855cab17d98585,
0xcebd8173673cbdbd, 0x695dd234ba8f5d5d, 0x4010805020901010, 0xf7f4f303f507f4f4,
0x0bcb16c08bddcbcb, 0xf83eedc67cd33e3e, 0x140528110a2d0505, 0x81671fe6ce786767,
0xb7e47353d597e4e4, 0x9c2725bb4e022727, 0x1941325882734141, 0x168b2c9d0ba78b8b,
0xa6a7510153f6a7a7, 0xe97dcf94fab27d7d, 0x6e95dcfb37499595, 0x47d88e9fad56d8d8,
0xcbfb8b30eb70fbfb, 0x9fee2371c1cdeeee, 0xed7cc791f8bb7c7c, 0x856617e3cc716666,
0x53dda68ea77bdddd, 0x5c17b84b2eaf1717, 0x014702468e454747, 0x429e84dc211a9e9e,
0x0fca1ec589d4caca, 0xb42d75995a582d2d, 0xc6bf9179632ebfbf, 0x1c07381b0e3f0707,
0x8ead012347acadad, 0x755aea2fb4b05a5a, 0x36836cb51bef8383, 0xcc3385ff66b63333,
0x91633ff2c65c6363, 0x0802100a04120202, 0x92aa39384993aaaa, 0xd971afa8e2de7171,
0x07c80ecf8dc6c8c8, 0x6419c87d32d11919, 0x39497270923b4949, 0x43d9869aaf5fd9d9,
0xeff2c31df931f2f2, 0xabe34b48dba8e3e3, 0x715be22ab6b95b5b, 0x1a8834920dbc8888,
0x529aa4c8293e9a9a, 0x98262dbe4c0b2626, 0xc8328dfa64bf3232, 0xfab0e94a7d59b0b0,
0x83e91b6acff2e9e9, 0x3c0f78331e770f0f, 0x73d5e6a6b733d5d5, 0x3a8074ba1df48080,
0xc2be997c6127bebe, 0x13cd26de87ebcdcd, 0xd034bde468893434, 0x3d487a7590324848,
0xdbffab24e354ffff, 0xf57af78ff48d7a7a, 0x7a90f4ea3d649090, 0x615fc23ebe9d5f5f,
0x80201da0403d2020, 0xbd6867d5d00f6868, 0x681ad07234ca1a1a, 0x82ae192c41b7aeae,
0xeab4c95e757db4b4, 0x4d549a19a8ce5454, 0x7693ece53b7f9393, 0x88220daa442f2222,
0x8d6407e9c8636464, 0xe3f1db12ff2af1f1, 0xd173bfa2e6cc7373, 0x4812905a24821212,
0x1d403a5d807a4040, 0x2008402810480808, 0x2bc356e89b95c3c3, 0x97ec337bc5dfecec,
0x4bdb9690ab4ddbdb, 0xbea1611f5fc0a1a1, 0x0e8d1c8307918d8d, 0xf43df5c97ac83d3d,
0x6697ccf1335b9797, 0x0000000000000000, 0x1bcf36d483f9cfcf, 0xac2b4587566e2b2b,
0xc57697b3ece17676, 0x328264b019e68282, 0x7fd6fea9b128d6d6, 0x6c1bd87736c31b1b,
0xeeb5c15b7774b5b5, 0x86af112943beafaf, 0xb56a77dfd41d6a6a, 0x5d50ba0da0ea5050,
0x0945124c8a574545, 0xebf3cb18fb38f3f3, 0xc0309df060ad3030, 0x9bef2b74c3c4efef,
0xfc3fe5c37eda3f3f, 0x4955921caac75555, 0xb2a2791059dba2a2, 0x8fea0365c9e9eaea,
0x89650fecca6a6565, 0xd2bab9686903baba, 0xbc2f65935e4a2f2f, 0x27c04ee79d8ec0c0,
0x5fdebe81a160dede, 0x701ce06c38fc1c1c, 0xd3fdbb2ee746fdfd, 0x294d52649a1f4d4d,
0x7292e4e039769292, 0xc9758fbceafa7575, 0x1806301e0c360606, 0x128a249809ae8a8a,
0xf2b2f940794bb2b2, 0xbfe66359d185e6e6, 0x380e70361c7e0e0e, 0x7c1ff8633ee71f1f,
0x956237f7c4556262, 0x77d4eea3b53ad4d4, 0x9aa829324d81a8a8, 0x6296c4f431529696,
0xc3f99b3aef62f9f9, 0x33c566f697a3c5c5, 0x942535b14a102525, 0x7959f220b2ab5959,
0x2a8454ae15d08484, 0xd572b7a7e4c57272, 0xe439d5dd72ec3939, 0x2d4c5a6198164c4c,
0x655eca3bbc945e5e, 0xfd78e785f09f7878, 0xe038ddd870e53838, 0x0a8c148605988c8c,
0x63d1c6b2bf17d1d1, 0xaea5410b57e4a5a5, 0xafe2434dd9a1e2e2, 0x99612ff8c24e6161,
0xf6b3f1457b42b3b3, 0x842115a542342121, 0x4a9c94d625089c9c, 0x781ef0663cee1e1e,
0x1143225286614343, 0x3bc776fc93b1c7c7, 0xd7fcb32be54ffcfc, 0x1004201408240404,
0x5951b208a2e35151, 0x5e99bcc72f259999, 0xa96d4fc4da226d6d, 0x340d68391a650d0d,
0xcffa8335e979fafa, 0x5bdfb684a369dfdf, 0xe57ed79bfca97e7e, 0x90243db448192424,
0xec3bc5d776fe3b3b, 0x96ab313d4b9aabab, 0x1fce3ed181f0cece, 0x4411885522991111,
0x068f0c8903838f8f, 0x254e4a6b9c044e4e, 0xe6b7d1517366b7b7, 0x8beb0b60cbe0ebeb,
0xf03cfdcc78c13c3c, 0x3e817cbf1ffd8181, 0x6a94d4fe35409494, 0xfbf7eb0cf31cf7f7,
0xdeb9a1676f18b9b9, 0x4c13985f268b1313, 0xb02c7d9c58512c2c, 0x6bd3d6b8bb05d3d3,
0xbbe76b5cd38ce7e7, 0xa56e57cbdc396e6e, 0x37c46ef395aac4c4, 0x0c03180f061b0303,
0x45568a13acdc5656, 0x0d441a49885e4444, 0xe17fdf9efea07f7f, 0x9ea921374f88a9a9,
0xa82a4d8254672a2a, 0xd6bbb16d6b0abbbb, 0x23c146e29f87c1c1, 0x5153a202a6f15353,
0x57dcae8ba572dcdc, 0x2c0b582716530b0b, 0x4e9d9cd327019d9d, 0xad6c47c1d82b6c6c,
0xc43195f562a43131, 0xcd7487b9e8f37474, 0xfff6e309f115f6f6, 0x05460a438c4c4646,
0x8aac092645a5acac, 0x1e893c970fb58989, 0x5014a04428b41414, 0xa3e15b42dfbae1e1,
0x5816b04e2ca61616, 0xe83acdd274f73a3a, 0xb9696fd0d2066969, 0x2409482d12410909,
0xdd70a7ade0d77070, 0xe2b6d954716fb6b6, 0x67d0ceb7bd1ed0d0, 0x93ed3b7ec7d6eded,
0x17cc2edb85e2cccc, 0x15422a5784684242, 0x5a98b4c22d2c9898, 0xaaa4490e55eda4a4,
0xa0285d8850752828, 0x6d5cda31b8865c5c, 0xc7f8933fed6bf8f8, 0x228644a411c28686,
// C7
0x186018c07830d818, 0x238c2305af462623, 0xc63fc67ef991b8c6, 0xe887e8136fcdfbe8,
0x8726874ca113cb87, 0xb8dab8a9626d11b8, 0x0104010805020901, 0x4f214f426e9e0d4f,
0x36d836adee6c9b36, 0xa6a2a6590451ffa6, 0xd26fd2debdb90cd2, 0xf5f3f5fb06f70ef5,
0x79f979ef80f29679, 0x6fa16f5fcede306f, 0x917e91fcef3f6d91, 0x525552aa07a4f852,
0x609d6027fdc04760, 0xbccabc89766535bc, 0x9b569baccd2b379b, 0x8e028e048c018a8e,
0xa3b6a371155bd2a3, 0x0c300c603c186c0c, 0x7bf17bff8af6847b, 0x35d435b5e16a8035,
0x1d741de8693af51d, 0xe0a7e05347ddb3e0, 0xd77bd7f6acb321d7, 0xc22fc25eed999cc2,
0x2eb82e6d965c432e, 0x4b314b627a96294b, 0xfedffea321e15dfe, 0x5741578216aed557,
0x155415a8412abd15, 0x77c1779fb6eee877, 0x37dc37a5eb6e9237, 0xe5b3e57b56d79ee5,
0x9f469f8cd923139f, 0xf0e7f0d317fd23f0, 0x4a354a6a7f94204a, 0xda4fda9e95a944da,
0x587d58fa25b0a258, 0xc903c906ca8fcfc9, 0x29a429558d527c29, 0x0a280a5022145a0a,
0xb1feb1e14f7f50b1, 0xa0baa0691a5dc9a0, 0x6bb16b7fdad6146b, 0x852e855cab17d985,
0xbdcebd8173673cbd, 0x5d695dd234ba8f5d, 0x1040108050209010, 0xf4f7f4f303f507f4,
0xcb0bcb16c08bddcb, 0x3ef83eedc67cd33e, 0x05140528110a2d05, 0x6781671fe6ce7867,
0xe4b7e47353d597e4, 0x279c2725bb4e0227, 0x4119413258827341, 0x8b168b2c9d0ba78b,
0xa7a6a7510153f6a7, 0x7de97dcf94fab27d, 0x956e95dcfb374995, 0xd847d88e9fad56d8,
0xfbcbfb8b30eb70fb, 0xee9fee2371c1cdee, 0x7ced7cc791f8bb7c, 0x66856617e3cc7166,
0xdd53dda68ea77bdd, 0x175c17b84b2eaf17, 0x47014702468e4547, 0x9e429e84dc211a9e,
0xca0fca1ec589d4ca, 0x2db42d75995a582d, 0xbfc6bf9179632ebf, 0x071c07381b0e3f07,
0xad8ead012347acad, 0x5a755aea2fb4b05a, 0x8336836cb51bef83, 0x33cc3385ff66b633,
0x6391633ff2c65c63, 0x020802100a041202, 0xaa92aa39384993aa, 0x71d971afa8e2de71,
0xc807c80ecf8dc6c8, 0x196419c87d32d119, 0x4939497270923b49, 0xd943d9869aaf5fd9,
0xf2eff2c31df931f2, 0xe3abe34b48dba8e3, 0x5b715be22ab6b95b, 0x881a8834920dbc88,
0x9a529aa4c8293e9a, 0x2698262dbe4c0b26, 0x32c8328dfa64bf32, 0xb0fab0e94a7d59b0,
0xe983e91b6acff2e9, 0x0f3c0f78331e770f, 0xd573d5e6a6b733d5, 0x803a8074ba1df480,
0xbec2be997c6127be, 0xcd13cd26de87ebcd, 0x34d034bde4688934, 0x483d487a75903248,
0xffdbffab24e354ff, 0x7af57af78ff48d7a, 0x907a90f4ea3d6490, 0x5f615fc23ebe9d5f,
0x2080201da0403d20, 0x68bd6867d5d00f68, 0x1a681ad07234ca1a, 0xae82ae192c41b7ae,
0xb4eab4c95e757db4, 0x544d549a19a8ce54, 0x937693ece53b7f93, 0x2288220daa442f22,
0x648d6407e9c86364, 0xf1e3f1db12ff2af1, 0x73d173bfa2e6cc73, 0x124812905a248212,
0x401d403a5d807a40, 0x0820084028104808, 0xc32bc356e89b95c3, 0xec97ec337bc5dfec,
0xdb4bdb9690ab4ddb, 0xa1bea1611f5fc0a1, 0x8d0e8d1c8307918d, 0x3df43df5c97ac83d,
0x976697ccf1335b97, 0x0000000000000000, 0xcf1bcf36d483f9cf, 0x2bac2b4587566e2b,
0x76c57697b3ece176, 0x82328264b019e682, 0xd67fd6fea9b128d6, 0x1b6c1bd87736c31b,
0xb5eeb5c15b7774b5, 0xaf86af112943beaf, 0x6ab56a77dfd41d6a, 0x505d50ba0da0ea50,
0x450945124c8a5745, 0xf3ebf3cb18fb38f3, 0x30c0309df060ad30, 0xef9bef2b74c3c4ef,
0x3ffc3fe5c37eda3f, 0x554955921caac755, 0xa2b2a2791059dba2, 0xea8fea0365c9e9ea,
0x6589650fecca6a65, 0xbad2bab9686903ba, 0x2fbc2f65935e4a2f, 0xc027c04ee79d8ec0,
0xde5fdebe81a160de, 0x1c701ce06c38fc1c, 0xfdd3fdbb2ee746fd, 0x4d294d52649a1f4d,
0x927292e4e0397692, 0x75c9758fbceafa75, 0x061806301e0c3606, 0x8a128a249809ae8a,
0xb2f2b2f940794bb2, 0xe6bfe66359d185e6, 0x0e380e70361c7e0e, 0x1f7c1ff8633ee71f,
0x62956237f7c45562, 0xd477d4eea3b53ad4, 0xa89aa829324d81a8, 0x966296c4f4315296,
0xf9c3f99b3aef62f9, 0xc533c566f697a3c5, 0x25942535b14a1025, 0x597959f220b2ab59,
0x842a8454ae15d084, 0x72d572b7a7e4c572, 0x39e439d5dd72ec39, 0x4c2d4c5a6198164c,
0x5e655eca3bbc945e, 0x78fd78e785f09f78, 0x38e038ddd870e538, 0x8c0a8c148605988c,
0xd163d1c6b2bf17d1, 0xa5aea5410b57e4a5, 0xe2afe2434dd9a1e2, 0x6199612ff8c24e61,
0xb3f6b3f1457b42b3, 0x21842115a5423421, 0x9c4a9c94d625089c, 0x1e781ef0663cee1e,
0x4311432252866143, 0xc73bc776fc93b1c7, 0xfcd7fcb32be54ffc, 0x0410042014082404,
0x515951b208a2e351, 0x995e99bcc72f2599, 0x6da96d4fc4da226d, 0x0d340d68391a650d,
0xfacffa8335e979fa, 0xdf5bdfb684a369df, 0x7ee57ed79bfca97e, 0x2490243db4481924,
0x3bec3bc5d776fe3b, 0xab96ab313d4b9aab, 0xce1fce3ed181f0ce, 0x1144118855229911,
0x8f068f0c8903838f, 0x4e254e4a6b9c044e, 0xb7e6b7d1517366b7, 0xeb8beb0b60cbe0eb,
0x3cf03cfdcc78c13c, 0x813e817cbf1ffd81, 0x946a94d4fe354094, 0xf7fbf7eb0cf31cf7,
0xb9deb9a1676f18b9, 0x134c13985f268b13, 0x2cb02c7d9c58512c, 0xd36bd3d6b8bb05d3,
0xe7bbe76b5cd38ce7, 0x6ea56e57cbdc396e, 0xc437c46ef395aac4, 0x030c03180f061b03,
0x5645568a13acdc56, 0x440d441a49885e44, 0x7fe17fdf9efea07f, 0xa99ea921374f88a9,
0x2aa82a4d8254672a, 0xbbd6bbb16d6b0abb, 0xc123c146e29f87c1, 0x535153a202a6f153,
0xdc57dcae8ba572dc, 0x0b2c0b582716530b, 0x9d4e9d9cd327019d, 0x6cad6c47c1d82b6c,
0x31c43195f562a431, 0x74cd7487b9e8f374, 0xf6fff6e309f115f6, 0x4605460a438c4c46,
0xac8aac092645a5ac, 0x891e893c970fb589, 0x145014a04428b414, 0xe1a3e15b42dfbae1,
0x165816b04e2ca616, 0x3ae83acdd274f73a, 0x69b9696fd0d20669, 0x092409482d124109,
0x70dd70a7ade0d770, 0xb6e2b6d954716fb6, 0xd067d0ceb7bd1ed0, 0xed93ed3b7ec7d6ed,
0xcc17cc2edb85e2cc, 0x4215422a57846842, 0x985a98b4c22d2c98, 0xa4aaa4490e55eda4,
0x28a0285d88507528, 0x5c6d5cda31b8865c, 0xf8c7f8933fed6bf8, 0x86228644a411c286,
};

View File

@@ -1,56 +0,0 @@
// 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.
//
// An implementation of Wang Yi's wyhash(2) algorithm in C3:
// https://github.com/wangyi-fudan/wyhash
//
module std::hash::wyhash2;
fn ulong wyr3(char* in, usz len) @inline
=> ((ulong)in[0] << 16) | ((ulong)in[len >> 1] << 8) | (ulong)in[len - 1];
// See: https://docs.google.com/spreadsheets/d/1HmqDj-suH4wBFNg7etwE8WVBlfCufvD5-gAnIENs94k/edit?gid=1915335726#gid=1915335726
// Credit to article:
// https://medium.com/@tprodanov/benchmarking-non-cryptographic-hash-functions-in-rust-2e6091077d11
//
// wyhash2 has a >90% chance of collisions when its input data is above 16 bytes in length.
// However, it is the fastest performing and most evenly randomized hash for very low-length inputs,
// making it an ideal candidate for hashing primitive data types quickly and making things like hash
// tables even faster. Therefore, a 16-byte input limit is imposed on all calls to the hash function.
//
<*
@require input.len <= 16 : `wyhash2 is not useable for inputs over 16 bytes in length.`
*>
fn ulong hash(char[] input, ulong seed = 0)
{
seed ^= 0xa076_1d64_78bd_642f;
ulong a, b;
if (@likely(input.len <= 8)) // more likely to encounter 8-byte or lower type here
{
if (@likely(input.len >= 4))
{
a = (ulong)@unaligned_load(*(uint*)input.ptr, 1); // first 4 bytes widened to a u64
b = (ulong)@unaligned_load(*(uint*)&input[^4], 1); // a walking 4-byte window based on input.len
}
else if (input.len > 0)
{
a = wyr3(input, input.len);
}
}
else
{
a = @unaligned_load(*(ulong*)input.ptr, 1); // first 8 bytes
b = @unaligned_load(*(ulong*)&input[^8], 1); // a walking 8-byte window based on input.len
}
uint128 r = ((uint128)a ^ 0xe703_7ed1_a0b4_28db) * ((uint128)b ^ seed);
ulong pre_res = (ulong)r ^ (ulong)(r >> 64);
r = ((uint128)0xe703_7ed1_a0b4_28db ^ input.len) * (uint128)pre_res;
return (ulong)r ^ (ulong)(r >> 64);
}

View File

@@ -1,92 +0,0 @@
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;
}

View File

@@ -27,7 +27,7 @@ macro bool is_struct_with_default_print($Type)
<*
Introspect a struct and print it to a formatter
@require $kindof(value) == STRUCT || $kindof(value) == BITSTRUCT : `This macro is only valid on macros`
@require @typekind(value) == STRUCT || @typekind(value) == BITSTRUCT : `This macro is only valid on macros`
*>
macro usz? struct_to_format(value, Formatter* f, bool $force_dump)
{
@@ -69,6 +69,7 @@ struct Formatter
PrintFlags flags;
uint width;
uint prec;
usz idx;
fault first_fault;
}
}
@@ -146,14 +147,10 @@ fn usz? Formatter.out_str(&self, any arg) @private
case VOID:
return self.out_substr("void");
case FAULT:
fault f = *(fault*)arg.ptr;
return self.out_substr(f ? f.nameof : "(empty-fault)");
return self.out_substr((*(fault*)arg.ptr).nameof);
case INTERFACE:
any a = *(any*)arg;
return a ? self.out_str(a) : self.out_substr("(empty-interface)");
case ANY:
any a = *(any*)arg;
return a ? self.out_str(a) : self.out_substr("(empty-any)");
return self.out_str(*(any*)arg);
case OPTIONAL:
unreachable();
case SIGNED_INT:
@@ -498,11 +495,6 @@ fn usz? Formatter.vprintf(&self, String format, any[] anys)
out = ((char*)current.ptr)[:current.type.sizeof];
break;
}
if (current.type.kindof == POINTER)
{
out = ((*(char**)current.ptr))[:current.type.inner.sizeof];
break;
}
total_len += self.out_substr("<INVALID>")!;
continue;
}
@@ -574,5 +566,5 @@ fn usz? Formatter.print(&self, String str)
self.out_fn = &out_null_fn;
}
foreach (c : str) self.out(c)!;
return str.len;
return self.idx;
}

View File

@@ -22,7 +22,7 @@ fn usz? print_hex_chars(Formatter* f, char[] out, bool uppercase) @inline
return len;
}
macro fault Formatter.first_err(&self, fault f)
macro Formatter.first_err(&self, fault f)
{
if (self.first_fault) return self.first_fault;
self.first_fault = f;
@@ -660,6 +660,7 @@ fn usz? Formatter.out_char(&self, any arg) @private
fn usz? Formatter.out_reverse(&self, char[] buf) @private
{
usz n;
usz buffer_start_idx = self.idx;
usz len = buf.len;
// pad spaces up to given width
if (!self.flags.zeropad && !self.flags.left)

View File

@@ -1,4 +1,4 @@
// Copyright (c) 2021-2025 Christoffer Lerno. All rights reserved.
// Copyright (c) 2021-2022 Christoffer Lerno. All rights reserved.
// Use of this source code is governed by the MIT license
// a copy of which can be found in the LICENSE_STDLIB file.
module std::io;
@@ -16,7 +16,6 @@ faultdef
BUSY,
CANNOT_READ_DIR,
DIR_NOT_EMPTY,
PARENT_DIR_MISSING,
EOF,
FILE_CANNOT_DELETE,
FILE_IS_DIR,
@@ -35,7 +34,6 @@ faultdef
NO_PERMISSION,
OUT_OF_SPACE,
OVERFLOW,
PATH_COULD_NOT_BE_FOUND,
READ_ONLY,
SYMLINK_FAILED,
TOO_MANY_DESCRIPTORS,
@@ -51,23 +49,41 @@ faultdef
"\r" will be filtered from the String.
@param stream : `The stream to read from.`
@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.`
@require @is_instream(stream) : `The stream must implement InStream.`
@param [inout] allocator : `the allocator to use.`
@return `The string containing the data read.`
*>
macro String? readline(Allocator allocator, stream = io::stdin())
{
if (allocator == tmem)
{
DString str = dstring::temp_with_capacity(256);
readline_to_stream(&str, stream)!;
return str.str_view();
}
bool $is_stream = @typeis(stream, InStream);
$if $is_stream:
$typeof(&stream.read_byte) func = &stream.read_byte;
char val = func((void*)stream)!;
$else
char val = stream.read_byte()!;
$endif
if (val == '\n') return "";
@pool()
{
DString str = dstring::temp_with_capacity(256);
readline_to_stream(&str, stream)!;
if (val != '\r') str.append(val);
while (1)
{
$if $is_stream:
char? c = func((void*)stream);
$else
char? c = stream.read_byte();
$endif
if (catch err = c)
{
if (err == io::EOF) break;
return err?;
}
if (c == '\r') continue;
if (c == '\n') break;
str.append_char(c);
}
return str.copy_str(allocator);
};
}
@@ -77,7 +93,6 @@ 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 @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.`
*>
@@ -86,65 +101,6 @@ macro String? treadline(stream = io::stdin())
return readline(tmem, stream) @inline;
}
<*
Reads a string, see `readline`, the data is passed to an outstream
@param out_stream : `The stream to write to`
@param in_stream : `The stream to read from.`
@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 = $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 = $typeof(out_stream) == OutStream;
$if $is_out_stream:
var out_func = &out_stream.write_byte;
$endif
if (val == '\n') return 0;
usz len;
if (val != '\r')
{
$if $is_out_stream:
out_func((void*)out_stream.ptr, val)!;
$else
out_stream.write_byte(val)!;
$endif
len++;
}
while (1)
{
$if $is_stream:
char? c = func((void*)in_stream);
$else
char? c = in_stream.read_byte();
$endif
if (catch err = c)
{
if (err == io::EOF) break;
return err?;
}
if (c == '\r') continue;
if (c == '\n') break;
$if $is_out_stream:
out_func((void*)out_stream.ptr, c)!;
$else
out_stream.write_byte(c)!;
$endif
len++;
}
return len;
}
<*
Print a value to a stream.
@@ -161,7 +117,7 @@ macro usz? fprint(out, x)
$case ZString: return out.write(x.str_view());
$case DString: return out.write(x.str_view());
$default:
$if $defined(String a = x):
$if $assignable(x, String):
return out.write((String)x);
$else
$if is_struct_with_default_print($Type):
@@ -216,7 +172,7 @@ macro usz? fprintn(out, x = "")
usz len = fprint(out, x)!;
out.write_byte('\n')!;
$switch:
$case $typeof(out) == OutStream:
$case @typeid(out) == OutStream.typeid:
if (&out.flush) out.flush()!;
$case $defined(out.flush):
out.flush()!;
@@ -255,7 +211,7 @@ macro void eprint(x)
@param x : "The value to print"
*>
macro void eprintn(x = "")
macro void eprintn(x)
{
(void)fprintn(io::stderr(), x);
}

View File

@@ -3,28 +3,28 @@ import std::io::path, libc, std::os;
macro void? native_chdir(Path path)
{
@pool()
{
$switch:
$case env::POSIX:
if (posix::chdir(path.str_view().zstr_tcopy()))
$switch:
$case env::POSIX:
if (posix::chdir(path.as_zstr()))
{
switch (libc::errno())
{
switch (libc::errno())
{
case errno::EACCES: return io::NO_PERMISSION?;
case errno::ENAMETOOLONG: return io::NAME_TOO_LONG?;
case errno::ENOTDIR: return io::FILE_NOT_DIR?;
case errno::ENOENT: return io::FILE_NOT_FOUND?;
case errno::ELOOP: return io::SYMLINK_FAILED?;
default: return io::GENERAL_ERROR?;
}
case errno::EACCES: return io::NO_PERMISSION?;
case errno::ENAMETOOLONG: return io::NAME_TOO_LONG?;
case errno::ENOTDIR: return io::FILE_NOT_DIR?;
case errno::ENOENT: return io::FILE_NOT_FOUND?;
case errno::ELOOP: return io::SYMLINK_FAILED?;
default: return io::GENERAL_ERROR?;
}
$case env::WIN32:
}
$case env::WIN32:
@pool()
{
// TODO improve with better error handling.
if (win32::setCurrentDirectoryW(path.str_view().to_temp_utf16()!!)) return;
return io::GENERAL_ERROR?;
$default:
return io::UNSUPPORTED_OPERATION?;
$endswitch
};
};
return io::GENERAL_ERROR?;
$default:
return io::UNSUPPORTED_OPERATION?;
$endswitch
}

View File

@@ -5,11 +5,7 @@ fn PathList? native_ls(Path dir, bool no_dirs, bool no_symlinks, String mask, Al
{
PathList list;
list.init(allocator);
DIRPtr directory @noinit;
@pool()
{
directory = posix::opendir(dir.str_view() ? dir.str_view().zstr_tcopy() : (ZString)".");
};
DIRPtr directory = posix::opendir(dir.str_view() ? dir.as_zstr() : (ZString)".");
defer if (directory) posix::closedir(directory);
if (!directory) return (path::is_dir(dir) ? io::CANNOT_READ_DIR : io::FILE_NOT_DIR)?;
Posix_dirent* entry;

View File

@@ -7,11 +7,9 @@ import std::os::posix;
macro bool? native_mkdir(Path path, MkdirPermissions permissions)
{
@pool()
{
$switch:
$case env::POSIX:
if (!posix::mkdir(path.str_view().zstr_tcopy(), permissions == NORMAL ? 0o777 : 0o700)) return true;
if (!posix::mkdir(path.as_zstr(), permissions == NORMAL ? 0o777 : 0o700)) return true;
switch (libc::errno())
{
case errno::EACCES:
@@ -25,11 +23,11 @@ macro bool? native_mkdir(Path path, MkdirPermissions permissions)
case errno::EEXIST: return false;
case errno::ELOOP: return io::SYMLINK_FAILED?;
case errno::ENOTDIR: return io::FILE_NOT_FOUND?;
case errno::ENOENT: return io::PARENT_DIR_MISSING?;
default:
return io::GENERAL_ERROR?;
default: return io::GENERAL_ERROR?;
}
$case env::WIN32:
@pool()
{
// TODO security attributes
if (win32::createDirectoryW(path.str_view().to_temp_utf16()!!, null)) return true;
switch (win32::getLastError())
@@ -45,8 +43,8 @@ macro bool? native_mkdir(Path path, MkdirPermissions permissions)
default:
return io::GENERAL_ERROR?;
}
};
$default:
return io::UNSUPPORTED_OPERATION?;
$endswitch
};
}

View File

@@ -6,11 +6,9 @@ import std::os::posix;
macro bool? native_rmdir(Path path)
{
@pool()
{
$switch:
$case env::POSIX:
if (!posix::rmdir(path.str_view().zstr_tcopy())) return true;
if (!posix::rmdir(path.as_zstr())) return true;
switch (libc::errno())
{
case errno::EBUSY: return io::BUSY?;
@@ -26,23 +24,25 @@ macro bool? native_rmdir(Path path)
default: return io::GENERAL_ERROR?;
}
$case env::WIN32:
if (win32::removeDirectoryW(path.str_view().to_temp_utf16()!!)) return true;
switch (win32::getLastError())
@pool()
{
case win32::ERROR_ACCESS_DENIED:
return io::NO_PERMISSION?;
case win32::ERROR_CURRENT_DIRECTORY:
return io::BUSY?;
case win32::ERROR_DIR_NOT_EMPTY:
return io::DIR_NOT_EMPTY?;
case win32::ERROR_DIRECTORY:
case win32::ERROR_PATH_NOT_FOUND:
return false;
default:
return io::GENERAL_ERROR?;
}
if (win32::removeDirectoryW(path.str_view().to_temp_utf16()!!)) return true;
switch (win32::getLastError())
{
case win32::ERROR_ACCESS_DENIED:
return io::NO_PERMISSION?;
case win32::ERROR_CURRENT_DIRECTORY:
return io::BUSY?;
case win32::ERROR_DIR_NOT_EMPTY:
return io::DIR_NOT_EMPTY?;
case win32::ERROR_DIRECTORY:
case win32::ERROR_PATH_NOT_FOUND:
return false;
default:
return io::GENERAL_ERROR?;
}
};
$default:
return io::UNSUPPORTED_OPERATION?;
$endswitch
};
}

View File

@@ -6,33 +6,30 @@ import std::io, std::os, libc;
*>
fn void? native_rmtree(Path dir)
{
@pool()
DIRPtr directory = posix::opendir(dir.as_zstr());
defer if (directory) posix::closedir(directory);
if (!directory) return path::is_dir(dir) ? io::CANNOT_READ_DIR? : io::FILE_NOT_DIR?;
Posix_dirent* entry;
while ((entry = posix::readdir(directory)))
{
DIRPtr directory = posix::opendir(dir.str_view().zstr_tcopy());
defer if (directory) posix::closedir(directory);
if (!directory) return path::is_dir(dir) ? io::CANNOT_READ_DIR? : io::FILE_NOT_DIR?;
Posix_dirent* entry;
while ((entry = posix::readdir(directory)))
@pool()
{
@pool()
String name = ((ZString)&entry.name).str_view();
if (!name || name == "." || name == "..") continue;
Path new_path = dir.tappend(name)!;
if (entry.d_type == posix::DT_DIR)
{
String name = ((ZString)&entry.name).str_view();
if (!name || name == "." || name == "..") continue;
Path new_path = dir.tappend(name)!;
if (entry.d_type == posix::DT_DIR)
{
native_rmtree(new_path)!;
continue;
}
if (libc::remove(new_path.str_view().zstr_tcopy()))
{
// TODO improve
return io::GENERAL_ERROR?;
}
};
}
os::native_rmdir(dir)!;
};
native_rmtree(new_path)!;
continue;
}
if (libc::remove(new_path.as_zstr()))
{
// TODO improve
return io::GENERAL_ERROR?;
}
};
}
os::native_rmdir(dir)!;
}
module std::io::os @if(env::WIN32);

View File

@@ -1,112 +1,5 @@
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, 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
}
import std::io::path, std::os;
fn Path? native_temp_directory(Allocator allocator) @if(!env::WIN32)
{
@@ -130,6 +23,7 @@ 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_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?;
macro Path? native_temp_directory(Allocator allocator)
{
return io::UNSUPPORTED_OPERATION?;
}

View File

@@ -45,7 +45,7 @@ fn Path? tcwd() => cwd(tmem) @inline;
*>
macro void? chdir(path)
{
$if $typeof(path) == String:
$if @typeis(path, String):
@pool()
{
return os::native_chdir(temp(path));
@@ -57,21 +57,9 @@ 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) @const => $typeof(#path) == String ||| $typeof(#path) == Path;
macro bool @is_pathlike(#path) => @typeis(#path, String) || @typeis(#path, Path);
macro bool is_separator(char c, PathEnv path_env = DEFAULT_ENV)
{
@@ -105,9 +93,9 @@ enum MkdirPermissions
@param recursive : `If directories in between should be created if they're missing, defaults to false`
@param permissions : `The permissions to set on the directory`
*>
macro bool? mkdir(path, bool recursive = false, MkdirPermissions permissions = NORMAL)
macro bool? mkdir(Path path, bool recursive = false, MkdirPermissions permissions = NORMAL)
{
$if $typeof(path) == String:
$if @typeis(path, String):
@pool() { return _mkdir(temp(path), recursive, permissions); };
$else
return _mkdir(path, recursive, permissions);
@@ -125,7 +113,7 @@ macro bool? mkdir(path, bool recursive = false, MkdirPermissions permissions = N
*>
macro bool? rmdir(path)
{
$if $typeof(path) == String:
$if @typeis(path, String):
@pool() { return _rmdir(temp(path)); };
$else
return _mkdir(path);
@@ -160,20 +148,27 @@ fn Path? new(Allocator allocator, String path, PathEnv path_env = DEFAULT_ENV)
@return? INVALID_PATH : `if the path was invalid`
*>
fn Path? temp(String path, PathEnv path_env = DEFAULT_ENV) => new(tmem, path, path_env);
fn Path? temp(String path, PathEnv path_env = DEFAULT_ENV)
{
return new(tmem, path, path_env);
}
fn Path? from_wstring(Allocator allocator, WString path) => @pool()
fn Path? from_win32_wstring(Allocator allocator, WString path) => @pool()
{
return path::new(allocator, string::tfrom_wstring(path)!);
}
fn Path? from_win32_wstring(Allocator allocator, WString path) @deprecated("Use 'from_wstring' instead") => from_wstring(allocator, path);
fn Path? for_windows(Allocator allocator, String path)
{
return new(allocator, path, WIN32);
}
fn Path? for_windows(Allocator allocator, String path) => new(allocator, path, WIN32);
fn Path? for_posix(Allocator allocator, String path)
{
return new(allocator, path, POSIX);
}
fn Path? for_posix(Allocator allocator, String path) => new(allocator, path, POSIX);
fn bool Path.equals(self, Path p2) @operator(==)
fn bool Path.equals(self, Path p2)
{
return self.env == p2.env && self.path_string == p2.path_string;
}
@@ -345,9 +340,15 @@ fn String Path.volume_name(self)
return self.path_string[:len];
}
fn Path? String.to_path(self, Allocator allocator) => new(allocator, self);
fn Path? String.to_path(self, Allocator allocator)
{
return new(allocator, self);
}
fn Path? String.to_tpath(self) => new(tmem, self);
fn Path? String.to_tpath(self)
{
return new(tmem, self);
}
fn usz? volume_name_len(String path, PathEnv path_env) @local
{
@@ -526,7 +527,7 @@ fn String? normalize(String path_str, PathEnv path_env = DEFAULT_ENV)
return path_str[:len];
}
fn ZString Path.as_zstr(self) @deprecated => (ZString)self.path_string.ptr;
fn ZString Path.as_zstr(self) => (ZString)self.path_string.ptr;
fn String Path.root_directory(self)
{
@@ -606,17 +607,29 @@ fn bool? traverse(Path path, TraverseCallback callback, any data)
return false;
}
fn String Path.str_view(self) @inline => self.path_string;
fn String Path.str_view(self) @inline
{
return self.path_string;
}
fn bool Path.has_suffix(self, String str) => self.str_view().ends_with(str);
fn bool Path.has_suffix(self, String str)
{
return self.str_view().ends_with(str);
}
<*
@require self.allocator != null : "This Path should never be freed"
*>
fn void Path.free(self) => allocator::free(self.allocator, self.path_string.ptr);
fn void Path.free(self)
{
allocator::free(self.allocator, self.path_string.ptr);
}
fn usz? Path.to_format(&self, Formatter* formatter) @dynamic => formatter.print(self.str_view());
fn usz? Path.to_format(&self, Formatter* formatter) @dynamic
{
return formatter.print(self.str_view());
}
const bool[256] RESERVED_PATH_CHAR_POSIX = {
@@ -636,7 +649,10 @@ const bool[256] RESERVED_PATH_CHAR_WIN32 = {
['*'] = true,
};
macro bool is_reserved_win32_path_char(char c) => RESERVED_PATH_CHAR_WIN32[c];
macro bool is_reserved_win32_path_char(char c)
{
return RESERVED_PATH_CHAR_WIN32[c];
}
macro bool is_reserved_path_char(char c, PathEnv path_env = DEFAULT_ENV)
{

View File

@@ -1,7 +1,5 @@
module std::io;
import std::math;
import std::core::env;
interface InStream
{
@@ -39,24 +37,14 @@ fn usz? available(InStream s)
return 0;
}
macro bool @is_instream(#expr) @const
macro bool @is_instream(#expr)
{
return $defined(InStream i = #expr);
return $assignable(#expr, InStream);
}
macro bool @is_not_instream_if_ptr(#expr) @const
macro bool @is_outstream(#expr)
{
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);
return $assignable(#expr, OutStream);
}
<*
@@ -116,9 +104,6 @@ macro usz? write_all(stream, char[] buffer)
return n;
}
<*
@require @is_instream(s)
*>
macro usz? read_using_read_byte(s, char[] buffer)
{
usz len = 0;
@@ -136,18 +121,12 @@ macro usz? read_using_read_byte(s, char[] buffer)
return len;
}
<*
@require @is_outstream(s)
*>
macro void? write_byte_using_write(s, char c)
{
char[1] buff = { c };
s.write(&buff)!;
}
<*
@require @is_instream(s)
*>
macro char? read_byte_using_read(s)
{
char[1] buffer;
@@ -158,12 +137,10 @@ macro char? read_byte_using_read(s)
alias ReadByteFn = fn char?();
<*
@require @is_outstream(s)
*>
macro usz? write_using_write_byte(s, char[] bytes)
{
foreach (c : bytes) s.write_byte(c)!;
foreach (c : bytes) s.write_byte(self, c)!;
return bytes.len;
}
@@ -210,7 +187,7 @@ const char[*] MAX_VARS @private = { [2] = 3, [4] = 5, [8] = 10 };
<*
@require @is_instream(stream)
@require $kindof(x_ptr) == POINTER && $typeof(x_ptr).inner.kindof.is_int()
@require @typekind(x_ptr) == POINTER && $typeof(x_ptr).inner.kindof.is_int()
*>
macro usz? read_varint(stream, x_ptr)
{
@@ -245,7 +222,7 @@ macro usz? read_varint(stream, x_ptr)
}
<*
@require @is_outstream(stream)
@require $kindof(x).is_int()
@require @typekind(x).is_int()
*>
macro usz? write_varint(stream, x)
{
@@ -273,16 +250,6 @@ 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)
*>
@@ -291,14 +258,6 @@ 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)
*>
@@ -308,15 +267,6 @@ 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)
*>
@@ -328,17 +278,6 @@ 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)
*>
@@ -347,14 +286,6 @@ 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)
*>
@@ -366,17 +297,6 @@ 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)
*>
@@ -392,21 +312,6 @@ 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)
*>
@@ -415,14 +320,6 @@ 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)
*>
@@ -438,21 +335,6 @@ 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)
*>
@@ -476,29 +358,6 @@ 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)
*>
@@ -507,14 +366,6 @@ 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)
*>
@@ -538,30 +389,6 @@ 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"
@@ -606,34 +433,6 @@ 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.
*>

View File

@@ -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 shrunk.
max_read defines how many bytes might be kept before its internal buffer is shrinked.
@require self.bytes.len == 0 : "Buffer already initialized."
*>
fn ByteBuffer* ByteBuffer.init(&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 void ByteBuffer.shrink(&self)
macro ByteBuffer.shrink(&self)
{
if (self.read_idx >= self.max_read)
{
@@ -145,4 +145,4 @@ macro void ByteBuffer.shrink(&self)
self.write_idx = 1 + readable;
self.read_idx = 1;
}
}
}

View File

@@ -1,4 +1,4 @@
// Copyright (c) 2021-2025 Christoffer Lerno. All rights reserved.
// Copyright (c) 2021 Christoffer Lerno. All rights reserved.
// Use of this source code is governed by the MIT license
// a copy of which can be found in the LICENSE_STDLIB file.
module libc;
@@ -8,9 +8,6 @@ const int EXIT_FAILURE = 1;
const int EXIT_SUCCESS = 0;
const int RAND_MAX = 0x7fffffff;
alias WChar @if(env::WIN32) = Char16;
alias WChar @if(!env::WIN32) = Char32;
struct DivResult
{
CInt quot;
@@ -100,7 +97,7 @@ extern fn CInt close(CInt fd) @if(!env::WIN32);
extern fn double difftime(Time_t time1, Time_t time2) @if(!env::WIN32);
extern fn DivResult div(CInt numer, CInt denom);
extern fn void exit(CInt status);
extern fn void _exit(CInt status) @cname("_Exit");
extern fn void _exit(CInt status) @extern("_Exit");
extern fn CInt fclose(CFile stream);
extern fn CFile fdopen(CInt fd, ZString mode) @if(!env::WIN32);
extern fn CInt feof(CFile stream);
@@ -151,7 +148,6 @@ extern fn void qsort(void* base, usz items, usz size, CompareFunction compare);
extern fn CInt raise(CInt signal);
extern fn CInt rand();
extern fn isz read(Fd fd, void* buf, usz nbyte) @if(!env::WIN32);
extern fn isz readlink(ZString pathname, char* buf, int bufsize) @if(!env::WIN32);
extern fn void* realloc(void* ptr, usz size);
extern fn CInt remove(ZString filename);
extern fn CInt rename(ZString old_name, ZString new_name);
@@ -211,9 +207,9 @@ const CInt STDOUT_FD = 1;
const CInt STDERR_FD = 2;
module libc @if(env::LINUX || env::ANDROID);
extern CFile __stdin @cname("stdin");
extern CFile __stdout @cname("stdout");
extern CFile __stderr @cname("stderr");
extern CFile __stdin @extern("stdin");
extern CFile __stdout @extern("stdout");
extern CFile __stderr @extern("stderr");
extern fn usz malloc_usable_size(void* ptr);
macro usz malloc_size(void* ptr) => malloc_usable_size(ptr);
extern fn void* aligned_alloc(usz align, usz size);
@@ -259,104 +255,115 @@ 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 @cname("longjmp") @nostrip
fn void longjmp(JmpBuf* buffer, CInt value) @weak @extern("longjmp") @nostrip
{
unreachable("longjmp unavailable");
}
fn CInt setjmp(JmpBuf* buffer) @weak @cname("setjmp") @nostrip
fn CInt setjmp(JmpBuf* buffer) @weak @extern("setjmp") @nostrip
{
unreachable("setjmp unavailable");
}
fn void* malloc(usz size) @weak @cname("malloc") @nostrip
fn void* malloc(usz size) @weak @extern("malloc") @nostrip
{
unreachable("malloc unavailable");
}
fn void* calloc(usz count, usz size) @weak @cname("calloc") @nostrip
fn void* calloc(usz count, usz size) @weak @extern("calloc") @nostrip
{
unreachable("calloc unavailable");
}
fn void* free(void*) @weak @cname("free")
fn void* free(void*) @weak @extern("free")
{
unreachable("free unavailable");
}
fn void* realloc(void* ptr, usz size) @weak @cname("realloc") @nostrip
fn void* realloc(void* ptr, usz size) @weak @extern("realloc") @nostrip
{
unreachable("realloc unavailable");
}
alias memcpy = mem::__memcpy;
alias memmove = mem::__memcpy;
alias memset = mem::__memset;
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 int fseek(CFile stream, SeekIndex offset, int whence) @weak @cname("fseek") @nostrip
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;
}
fn int fseek(CFile stream, SeekIndex offset, int whence) @weak @extern("fseek") @nostrip
{
unreachable("'fseek' not available.");
}
fn CFile fopen(ZString filename, ZString mode) @weak @cname("fopen") @nostrip
fn CFile fopen(ZString filename, ZString mode) @weak @extern("fopen") @nostrip
{
unreachable("'fopen' not available.");
}
fn CFile freopen(ZString filename, ZString mode, CFile stream) @weak @cname("fopen") @nostrip
fn CFile freopen(ZString filename, ZString mode, CFile stream) @weak @extern("fopen") @nostrip
{
unreachable("'freopen' not available.");
}
fn usz fwrite(void* ptr, usz size, usz nmemb, CFile stream) @weak @cname("fwrite") @nostrip
fn usz fwrite(void* ptr, usz size, usz nmemb, CFile stream) @weak @extern("fwrite") @nostrip
{
unreachable("'fwrite' not available.");
}
fn usz fread(void* ptr, usz size, usz nmemb, CFile stream) @weak @cname("fread") @nostrip
fn usz fread(void* ptr, usz size, usz nmemb, CFile stream) @weak @extern("fread") @nostrip
{
unreachable("'fread' not available.");
}
fn CFile fclose(CFile) @weak @cname("fclose") @nostrip
fn CFile fclose(CFile) @weak @extern("fclose") @nostrip
{
unreachable("'fclose' not available.");
}
fn int fflush(CFile stream) @weak @cname("fflush") @nostrip
fn int fflush(CFile stream) @weak @extern("fflush") @nostrip
{
unreachable("'fflush' not available.");
}
fn int fputc(int c, CFile stream) @weak @cname("fputc") @nostrip
fn int fputc(int c, CFile stream) @weak @extern("fputc") @nostrip
{
unreachable("'fputc' not available.");
}
fn char* fgets(ZString str, int n, CFile stream) @weak @cname("fgets") @nostrip
fn char* fgets(ZString str, int n, CFile stream) @weak @extern("fgets") @nostrip
{
unreachable("'fgets' not available.");
}
fn int fgetc(CFile stream) @weak @cname("fgetc") @nostrip
fn int fgetc(CFile stream) @weak @extern("fgetc") @nostrip
{
unreachable("'fgetc' not available.");
}
fn int feof(CFile stream) @weak @cname("feof") @nostrip
fn int feof(CFile stream) @weak @extern("feof") @nostrip
{
unreachable("'feof' not available.");
}
fn int putc(int c, CFile stream) @weak @cname("putc") @nostrip
fn int putc(int c, CFile stream) @weak @extern("putc") @nostrip
{
unreachable("'putc' not available.");
}
fn int putchar(int c) @weak @cname("putchar") @nostrip
fn int putchar(int c) @weak @extern("putchar") @nostrip
{
unreachable("'putchar' not available.");
}
fn int puts(ZString str) @weak @cname("puts") @nostrip
fn int puts(ZString str) @weak @extern("puts") @nostrip
{
unreachable("'puts' not available.");
}
@@ -407,28 +414,17 @@ alias SeekIndex = CLong;
struct Tm
{
<* 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_sec; // seconds after the minute [0-60]
CInt tm_min; // minutes after the hour [0-59]
CInt tm_hour; // hours since midnight [0-23]
CInt tm_mday; // day of the month [1-31]
CInt tm_mon; // months since January [0-11]
CInt tm_year; // years since 1900
CInt tm_wday; // days since Sunday [0-6]
CInt tm_yday; // days since January 1 [0-365]
CInt tm_isdst; // Daylight Savings Time flag
TimeOffset tm_gmtoff @if(!env::WIN32); /* offset from UTC in seconds */
char* tm_zone @if(!env::WIN32); /* timezone abbreviation */
CInt tm_nsec @if(env::WASI);
}
@@ -444,12 +440,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 approximation, but the C standard does not enforce
// This is a best-effort aproximation, 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;

View File

@@ -4,7 +4,7 @@ import std::time;
<*
Return a "timespec" from a duration.
@require self >= time::NANO_DURATION_ZERO
@require self >= 0
*>
fn TimeSpec NanoDuration.to_timespec(self) @inline
{
@@ -16,7 +16,7 @@ fn TimeSpec NanoDuration.to_timespec(self) @inline
<*
Convert a duration to a timespec.
@require self >= time::DURATION_ZERO
@require self >= 0
*>
fn TimeSpec Duration.to_timespec(self) @inline
{
@@ -24,11 +24,3 @@ fn TimeSpec Duration.to_timespec(self) @inline
Time_t sec = (Time_t)(self / time::SEC);
return { .s = sec, .ns = ns };
}
<*
Convert a timestamp to a timespec.
*>
fn TimeSpec Time.to_timespec(self) @inline
{
return ((Duration)self).to_timespec();
}

View File

@@ -17,30 +17,19 @@ struct Stat
Gid_t st_gid;
Dev_t st_rdev;
<* 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;
TimeSpec st_atimespec; // time of last access
TimeSpec st_mtimespec; // time of last data modification
TimeSpec st_ctimespec; // time of last status change
TimeSpec st_birthtimespec; // time of file creation(birth)
Off_t st_size; // file size, in bytes
Blkcnt_t st_blocks; // blocks allocated for file
Blksize_t st_blocksize; // optimal blocksize for I/O
uint st_flags; // user defined flags for file
uint st_gen; // file generation number
int st_lspare; // RESERVED
long[2] st_qspare; // RESERVED
}
extern fn int stat(ZString str, Stat* stat) @cname("stat64");
extern fn int stat(ZString str, Stat* stat) @extern("stat64");
extern fn CInt sysctl(CInt *name, CUInt namelen, void *oldp, usz *oldlenp, void *newp, usz newlen);

View File

@@ -11,11 +11,6 @@ extern fn int* __errno() @if(env::ANDROID);
macro int errno() @if(env::ANDROID) => *__errno();
macro void errno_set(int err) @if(env::ANDROID) => *(__errno()) = err;
// OpenBSD
extern fn int* __errno() @if(env::OPENBSD);
macro int errno() @if(env::OPENBSD) => *__errno();
macro void errno_set(int err) @if(env::OPENBSD) => *(__errno()) = err;
// Darwin
extern fn int* __error() @if(env::DARWIN);
macro int errno() @if(env::DARWIN) => *__error();

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