Compare commits

..

1 Commits

Author SHA1 Message Date
Christoffer Lerno
91a457850b Hack import rules 2025-08-06 20:20:19 +02:00
1047 changed files with 19653 additions and 60518 deletions

View File

@@ -1,15 +0,0 @@
IndentWidth: 4
UseCRLF: false
IndentCaseLabels: true
UseTab: ForIndentation
TabWidth: 4
BreakBeforeBraces: Allman
AllowShortBlocksOnASingleLine: Empty
AllowShortIfStatementsOnASingleLine: WithoutElse
SpaceBeforeAssignmentOperators: true
SpaceBeforeCaseColon: false
SpaceBeforeParens: ControlStatementsExceptControlMacros
SpacesInCStyleCastParentheses: false
SpacesInConditionalStatement: false
SpacesInParentheses: false
SpacesInSquareBrackets: false

View File

@@ -1,7 +1,21 @@
--- ---
# Configure clang-tidy for this project. # Configure clang-tidy for this project.
IndentWidth: 4
UseCRLF: false
IndentCaseLabels: true
UseTab: UT_ForIndentation
TabWidth: 4
BreakBeforeBraces: Allman
AllowShortBlocksOnASingleLine: SBS_Empty
AllowShortIfStatementsOnASingleLine: SIS_WithoutElse
SpaceBeforeAssignmentOperators: true
SpaceBeforeCaseColon: false
SpaceBeforeParens: SBPO_ControlStatementsExceptControlMacros
SpacesInCStyleCastParentheses: false
SpacesInConditionalStatement: false
SpacesInParentheses: false
SpacesInSquareBrackets: false
# Disabled: # Disabled:
# -google-readability-namespace-comments the *_CLIENT_NS is a macro, and # -google-readability-namespace-comments the *_CLIENT_NS is a macro, and
@@ -26,19 +40,17 @@ Checks: >
# Turn all the warnings from the checks above into errors. # Turn all the warnings from the checks above into errors.
WarningsAsErrors: "*" WarningsAsErrors: "*"
CheckOptions:
- { key: readability-function-cognitive-complexity.Threshold, value: 100 }
- { key: readability-identifier-naming.StructCase, value: CamelCase }
- { key: readability-identifier-naming.FunctionCase, value: lower_case }
- { key: readability-identifier-naming.VariableCase, value: lower_case }
- { key: readability-identifier-naming.MacroDefinitionCase, value: UPPER_CASE }
- { key: readability-identifier-naming.EnumConstantCase, value: UPPER_CASE }
- { key: readability-identifier-naming.ConstexprVariableCase, value: CamelCase }
- { key: readability-identifier-naming.ConstexprVariablePrefix, value: k }
- { key: readability-identifier-naming.GlobalConstantCase, value: CamelCase }
- { key: readability-identifier-naming.GlobalConstantPrefix, value: k }
- { key: readability-identifier-naming.StaticConstantCase, value: CamelCase }
- { key: readability-identifier-naming.StaticConstantPrefix, value: k }
- { key: readability-identifier-naming.MinimumParameterNameLength, value: 0 }
MinimumParameterNameLength: 0 CheckOptions:
- { key: readability-function-cognitive-complexity.Threshold, value: 100 }
- { key: readability-identifier-naming.StructCase, value: CamelCase }
- { key: readability-identifier-naming.FunctionCase, value: lower_case }
- { key: readability-identifier-naming.VariableCase, value: lower_case }
- { key: readability-identifier-naming.MacroDefinitionCase, value: UPPER_CASE }
- { key: readability-identifier-naming.EnumConstantCase, value: UPPER_CASE }
- { key: readability-identifier-naming.ConstexprVariableCase, value: CamelCase }
- { key: readability-identifier-naming.ConstexprVariablePrefix, value: k }
- { key: readability-identifier-naming.GlobalConstantCase, value: CamelCase }
- { key: readability-identifier-naming.GlobalConstantPrefix, value: k }
- { key: readability-identifier-naming.StaticConstantCase, value: CamelCase }
- { key: readability-identifier-naming.StaticConstantPrefix, value: k }

File diff suppressed because it is too large Load Diff

26
.gitignore vendored
View File

@@ -1,7 +1,5 @@
# Prerequisites # Prerequisites
*.d *.d
testrun
benchmarkrun
# Object files # Object files
*.o *.o
@@ -9,8 +7,6 @@ benchmarkrun
*.obj *.obj
*.elf *.elf
*.ll *.ll
*.wasm
*.s
# Linker output # Linker output
*.ilk *.ilk
@@ -73,34 +69,20 @@ out/
/cmake-build-debug/ /cmake-build-debug/
/cmake-build-release/ /cmake-build-release/
CMakeFiles/cmake.check_cache # Emacs files
CMakeCache.txt
# etags(Emacs), ctags, gtags
TAGS TAGS
GPATH
GRTAGS
GTAGS
tags
# Clangd LSP files # Clangd LSP files
/.cache/ /.cache/
/compile_commands.json /compile_commands.json
# Nix # 'nix build' resulting symlink
result result
/.envrc
/.direnv/
# macOS # macOS
.DS_Store .DS_Store
# tests # tests
/test/tmp/* /test/tmp/*
testrun /test/testrun
test_suite_runner /test/test_suite_runner
# patches, originals and rejects
*.patch
*.rej
*.orig

View File

@@ -1,8 +1,8 @@
cmake_minimum_required(VERSION 3.20) cmake_minimum_required(VERSION 3.20)
set(C3_LLVM_MIN_VERSION 17) set(C3_LLVM_MIN_VERSION 17)
set(C3_LLVM_MAX_VERSION 22) set(C3_LLVM_MAX_VERSION 21)
set(C3_LLVM_DEFAULT_VERSION 21) set(C3_LLVM_DEFAULT_VERSION 19)
if (CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_CURRENT_BINARY_DIR) if (CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_CURRENT_BINARY_DIR)
message(FATAL_ERROR "In-tree build detected, please build in a separate directory") message(FATAL_ERROR "In-tree build detected, please build in a separate directory")
@@ -56,57 +56,33 @@ set(CMAKE_FIND_PACKAGE_SORT_DIRECTION DEC)
set(CMAKE_C_STANDARD 11) set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD 17)
# Use /MT or /MTd
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
if(MSVC) if(MSVC)
message(STATUS "MSVC version ${MSVC_VERSION}") message(STATUS "MSVC version ${MSVC_VERSION}")
add_compile_options(/utf-8) add_compile_options(/utf-8)
if(C3_WITH_LLVM)
FetchContent_GetProperties(LLVM_Windows)
if(NOT LLVM_Windows_URL MATCHES "msvcrt")
set(MSVC_CRT_SUFFIX "")
message(STATUS "Detected STATIC LLVM (libcmt)")
else()
set(MSVC_CRT_SUFFIX "DLL")
message(STATUS "Detected DYNAMIC LLVM (msvcrt)")
endif()
endif()
# Force the Runtime to Release (/MT or /MD) even in Debug
# This is required to match our RelWithDebInfo LLVM builds
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded${MSVC_CRT_SUFFIX}")
set_property(GLOBAL PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded${MSVC_CRT_SUFFIX}")
add_compile_definitions(_ITERATOR_DEBUG_LEVEL=0)
# Suppresses the LNK4098 mismatch warning in Debug builds
set(CMAKE_EXE_LINKER_FLAGS_DEBUG "${CMAKE_EXE_LINKER_FLAGS_DEBUG} /NODEFAULTLIB:libcmtd /NODEFAULTLIB:msvcrtd")
else() else()
add_compile_options(-gdwarf-3 -fno-exceptions) add_compile_options(-gdwarf-3 -fno-exceptions)
#add_compile_options(-fsanitize=address,undefined) # add_compile_options(-fsanitize=address,undefined)
#add_link_options(-fsanitize=address,undefined) # add_link_options(-fsanitize=address,undefined)
endif() endif()
# Options # Options
set(C3_LINK_DYNAMIC OFF CACHE BOOL "Link dynamically with LLVM/LLD libs") 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_WITH_LLVM ON CACHE BOOL "Build with LLVM")
set(C3_LLVM_VERSION "auto" CACHE STRING "Use LLVM version [default: auto]") 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_USE_MIMALLOC OFF CACHE BOOL "Use built-in mimalloc")
set(C3_MIMALLOC_TAG "v1.7.3" CACHE STRING "Used version of 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_USE_TB OFF CACHE BOOL "Use TB")
set(C3_LLD_DIR "" CACHE STRING "Use custom LLD directory") set(C3_LLD_DIR "" CACHE STRING "Use custom LLD directory")
set(C3_LLD_INCLUDE_DIR "" CACHE STRING "Use custom LLD include directory") set(C3_ENABLE_CLANGD_LSP OFF CACHE BOOL "Enable/Disable output of compile commands during generation")
set(C3_ENABLE_CLANGD_LSP OFF CACHE BOOL "Enable/Disable output of compile commands during generation") set(LLVM_CRT_LIBRARY_DIR "" CACHE STRING "Use custom llvm's compiler-rt directory")
set(C3_FETCH_LLVM OFF CACHE BOOL "Automatically download LLVM artifacts")
set(C3_LLVM_TAG "llvm_21.x" CACHE STRING "Tag/Branch to download LLVM from")
set(LLVM_CRT_LIBRARY_DIR "" CACHE STRING "Use custom llvm's compiler-rt directory")
set(TCC_LIB_PATH "/usr/lib/tcc/libtcc1.a" CACHE STRING "Use custom libtcc1.a path")
set(C3_OPTIONS set(C3_OPTIONS
C3_LINK_DYNAMIC C3_LINK_DYNAMIC
C3_WITH_LLVM C3_WITH_LLVM
C3_FETCH_LLVM
C3_LLVM_TAG
C3_LLVM_VERSION C3_LLVM_VERSION
C3_USE_MIMALLOC C3_USE_MIMALLOC
C3_MIMALLOC_TAG C3_MIMALLOC_TAG
@@ -129,6 +105,9 @@ if(C3_USE_MIMALLOC)
) )
FetchContent_MakeAvailable(mimalloc) FetchContent_MakeAvailable(mimalloc)
endif() endif()
if (NOT WIN32)
find_package(CURL)
endif()
find_package(Git QUIET) find_package(Git QUIET)
if(C3_USE_TB AND GIT_FOUND AND EXISTS "${CMAKE_SOURCE_DIR}/.git") if(C3_USE_TB AND GIT_FOUND AND EXISTS "${CMAKE_SOURCE_DIR}/.git")
@@ -156,83 +135,55 @@ if(C3_ENABLE_CLANGD_LSP)
endif(C3_ENABLE_CLANGD_LSP) endif(C3_ENABLE_CLANGD_LSP)
if(C3_WITH_LLVM) if(C3_WITH_LLVM)
if(C3_FETCH_LLVM) if(CMAKE_C_COMPILER_ID STREQUAL "MSVC")
# 1. Determine local platform ID if (C3_LLVM_VERSION STREQUAL "auto")
if (WIN32) set(C3_LLVM_VERSION ${C3_LLVM_DEFAULT_VERSION})
set(C3_LLVM_PLATFORM "windows-amd64")
elseif (APPLE)
if (CMAKE_SYSTEM_PROCESSOR STREQUAL "arm64" OR CMAKE_HOST_SYSTEM_PROCESSOR STREQUAL "arm64")
set(C3_LLVM_PLATFORM "darwin-aarch64")
else()
set(C3_LLVM_PLATFORM "darwin-amd64")
endif()
else() # Linux
if (CMAKE_SYSTEM_PROCESSOR STREQUAL "aarch64" OR CMAKE_HOST_SYSTEM_PROCESSOR STREQUAL "aarch64")
set(C3_LLVM_PLATFORM "linux-aarch64")
elseif (CMAKE_SYSTEM_PROCESSOR STREQUAL "riscv64" OR CMAKE_HOST_SYSTEM_PROCESSOR STREQUAL "riscv64")
set(C3_LLVM_PLATFORM "linux-riscv64")
else()
set(C3_LLVM_PLATFORM "linux-amd64")
endif()
endif() endif()
# 2. Determine if we want Debug or Release
set(C3_LLVM_SUFFIX "")
set(C3_LLVM_TYPE "Release")
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
set(C3_LLVM_SUFFIX "-dbg")
set(C3_LLVM_TYPE "Debug")
endif()
# 3. Construct URL
set(C3_LLVM_ARTIFACT_NAME "llvm-${C3_LLVM_PLATFORM}${C3_LLVM_SUFFIX}")
#set(C3_LLVM_URL "https://github.com/c3lang/c3-llvm/releases/download/${C3_LLVM_TAG}/${C3_LLVM_ARTIFACT_NAME}.tar.gz")
#set(C3_LLVM_URL "https://github.com//ManuLinares/llvm-custom-builds/releases/download/${C3_LLVM_TAG}/${C3_LLVM_ARTIFACT_NAME}.tar.gz")
# We could also just set "latest" here to always fetch the latest release
set(C3_LLVM_URL "https://github.com/c3lang/llvm-for-c3/releases/latest/download/${C3_LLVM_ARTIFACT_NAME}.tar.gz")
message(STATUS "Fetching ${C3_LLVM_TYPE} LLVM artifact for ${C3_LLVM_PLATFORM}...")
message(STATUS "URL: ${C3_LLVM_URL}")
FetchContent_Declare( FetchContent_Declare(
LLVM_Artifact LLVM_Windows
URL ${C3_LLVM_URL} URL https://github.com/c3lang/win-llvm/releases/download/llvm_19_1_5/llvm-19.1.5-windows-amd64-msvc17-libcmt.7z
) )
FetchContent_MakeAvailable(LLVM_Artifact) FetchContent_Declare(
LLVM_Windows_debug
# 4. Point CMake to the fetched location URL https://github.com/c3lang/win-llvm/releases/download/llvm_19_1_5/llvm-19.1.5-windows-amd64-msvc17-libcmt-dbg.7z
set(llvm_dir ${llvm_artifact_SOURCE_DIR}) )
set(CMAKE_PREFIX_PATH ${llvm_dir} ${CMAKE_PREFIX_PATH}) if(CMAKE_BUILD_TYPE STREQUAL "Debug")
set(LLVM_DIR "${llvm_dir}/lib/cmake/llvm") message("Loading Windows LLVM debug libraries, this may take a while...")
set(LLD_DIR "${llvm_dir}/lib/cmake/lld") FetchContent_MakeAvailable(LLVM_Windows_debug)
set(llvm_dir ${llvm_windows_debug_SOURCE_DIR})
# TEST: For Windows, we might need to add the bin dir to prefix path for find_package to work well else()
if (WIN32) message("Loading Windows LLVM libraries, this may take a while...")
set(CMAKE_SYSTEM_PREFIX_PATH ${llvm_dir} ${CMAKE_SYSTEM_PREFIX_PATH}) FetchContent_MakeAvailable(LLVM_Windows)
endif() set(llvm_dir ${llvm_windows_SOURCE_DIR})
endif()
find_package(LLVM REQUIRED CONFIG NO_DEFAULT_PATH) message("Loaded Windows LLVM libraries into ${llvm_dir}")
find_package(LLD REQUIRED CONFIG NO_DEFAULT_PATH) set(CMAKE_SYSTEM_PREFIX_PATH ${llvm_dir} ${CMAKE_SYSTEM_PREFIX_PATH})
else()
if(CMAKE_C_COMPILER_ID STREQUAL "MSVC") find_package(LLVM REQUIRED CONFIG)
# Legacy MSVC path if someone explicitly disabled fetching but is on MSVC find_package(LLD REQUIRED CONFIG)
find_package(LLVM REQUIRED CONFIG) else()
find_package(LLD REQUIRED CONFIG) # Add paths for LLVM CMake files of version 19 and higher as they follow a new installation
# layout and are now in /usr/lib/llvm/*/lib/cmake/llvm/ rather than /usr/lib/cmake/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)
if (DEFINED LLVM_DIR)
message(STATUS "Looking for LLVM CMake files in user-specified directory ${LLVM_DIR}")
else() else()
# Default system search
file (GLOB LLVM_CMAKE_PATHS "/usr/lib/llvm/*/lib/cmake/llvm/") file (GLOB LLVM_CMAKE_PATHS "/usr/lib/llvm/*/lib/cmake/llvm/")
list (APPEND CMAKE_PREFIX_PATH ${LLVM_CMAKE_PATHS} "/usr/lib/") list (APPEND CMAKE_PREFIX_PATH ${LLVM_CMAKE_PATHS} "/usr/lib/")
message(STATUS "No LLVM_DIR specified, searching default directories ${CMAKE_PREFIX_PATH}")
endif()
if (NOT C3_LLVM_VERSION STREQUAL "auto") if (NOT C3_LLVM_VERSION STREQUAL "auto")
find_package(LLVM ${C3_LLVM_VERSION} REQUIRED CONFIG) find_package(LLVM ${C3_LLVM_VERSION} REQUIRED CONFIG)
else() else()
find_package(LLVM REQUIRED CONFIG) find_package(LLVM REQUIRED CONFIG)
endif()
endif() endif()
endif() endif()
if (NOT C3_FETCH_LLVM AND EXISTS /opt/homebrew/lib) if (EXISTS /opt/homebrew/lib)
list(APPEND LLVM_LIBRARY_DIRS /opt/homebrew/lib) list(APPEND LLVM_LIBRARY_DIRS /opt/homebrew/lib)
endif() endif()
@@ -308,25 +259,20 @@ if(C3_WITH_LLVM)
message(STATUS "Looking for static lld libraries in ${LLVM_LIBRARY_DIRS}") message(STATUS "Looking for static lld libraries in ${LLVM_LIBRARY_DIRS}")
# These don't seem to be reliable on windows. # These don't seem to be reliable on windows.
find_library(LLD_COFF NAMES lldCOFF.a liblldCOFF.a liblldCOFF.dylib lldCOFF.lib liblldCOFF.dll.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH REQUIRED) 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 lldCommon.a liblldCommon.a liblldCommon.dylib lldCommon.lib liblldCommon.dll.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 lldELF.a liblldELF.a liblldELF.dylib lldELF.lib liblldELF.dll.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") if (NOT ${CMAKE_SYSTEM_NAME} STREQUAL "OpenBSD")
find_library(LLD_MACHO NAMES lldMachO.a liblldMachO.a liblldMachO.dylib lldMachO.lib liblldMachO.dll.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH REQUIRED) 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() else()
set(LLD_MACHO "") set(LLD_MACHO "")
endif() endif()
find_library(LLD_MINGW NAMES lldMinGW.a liblldMinGW.a liblldMinGW.dylib lldMinGW.lib liblldMinGW.dll.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH REQUIRED) 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 lldWasm.a liblldWasm.a liblldWasm.dylib lldWasm.lib liblldWasm.dll.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)
else() else()
message(STATUS "Looking for shared lld libraries in ${LLVM_LIBRARY_DIRS}") 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 REQUIRED)
if(UNIX AND NOT WIN32)
find_library(LLVM NAMES libLLVM.so PATHS ${LLVM_LIBRARY_DIRS} REQUIRED)
else()
find_library(LLVM NAMES libLLVM.a LLVM.lib PATHS ${LLVM_LIBRARY_DIRS} REQUIRED)
endif()
set(llvm_libs ${LLVM}) set(llvm_libs ${LLVM})
# These don't seem to be reliable on windows. # These don't seem to be reliable on windows.
@@ -354,36 +300,17 @@ if(C3_WITH_LLVM)
if (APPLE) if (APPLE)
set(lld_libs ${lld_libs} xar) set(lld_libs ${lld_libs} xar)
if (llvm_dir) find_file(RT_ASAN_DYNAMIC NAMES libclang_rt.asan_osx_dynamic.dylib PATHS "${LLVM_LIBRARY_DIR}/clang/${LLVM_MAJOR_VERSION}/lib/darwin" ${LLVM_CRT_LIBRARY_DIR})
file(GLOB_RECURSE RT_ASAN_DYNAMIC "${llvm_dir}/*libclang_rt.asan_osx_dynamic.dylib") find_file(RT_TSAN_DYNAMIC NAMES libclang_rt.tsan_osx_dynamic.dylib PATHS "${LLVM_LIBRARY_DIR}/clang/${LLVM_MAJOR_VERSION}/lib/darwin" ${LLVM_CRT_LIBRARY_DIR})
file(GLOB_RECURSE RT_TSAN_DYNAMIC "${llvm_dir}/*libclang_rt.tsan_osx_dynamic.dylib") find_file(RT_UBSAN_DYNAMIC NAMES libclang_rt.ubsan_osx_dynamic.dylib PATHS "${LLVM_LIBRARY_DIR}/clang/${LLVM_MAJOR_VERSION}/lib/darwin" ${LLVM_CRT_LIBRARY_DIR})
endif() find_file(RT_LSAN_DYNAMIC NAMES libclang_rt.lsan_osx_dynamic.dylib PATHS "${LLVM_LIBRARY_DIR}/clang/${LLVM_MAJOR_VERSION}/lib/darwin" ${LLVM_CRT_LIBRARY_DIR})
set(sanitizer_runtime_libraries
if (NOT RT_ASAN_DYNAMIC OR NOT RT_TSAN_DYNAMIC) ${RT_ASAN_DYNAMIC}
# Fallback to searching in LLVM_LIBRARY_DIRS (for non-fetched LLVM) ${RT_TSAN_DYNAMIC}
find_file(RT_ASAN_DYNAMIC_PATH NAMES libclang_rt.asan_osx_dynamic.dylib PATHS ${LLVM_LIBRARY_DIRS} PATH_SUFFIXES "clang/${LLVM_MAJOR_VERSION}/lib/darwin" "clang/${LLVM_PACKAGE_VERSION}/lib/darwin") # Unused
find_file(RT_TSAN_DYNAMIC_PATH NAMES libclang_rt.tsan_osx_dynamic.dylib PATHS ${LLVM_LIBRARY_DIRS} PATH_SUFFIXES "clang/${LLVM_MAJOR_VERSION}/lib/darwin" "clang/${LLVM_PACKAGE_VERSION}/lib/darwin") # ${RT_UBSAN_DYNAMIC}
if (RT_ASAN_DYNAMIC_PATH) # ${RT_LSAN_DYNAMIC}
set(RT_ASAN_DYNAMIC ${RT_ASAN_DYNAMIC_PATH}) )
endif()
if (RT_TSAN_DYNAMIC_PATH)
set(RT_TSAN_DYNAMIC ${RT_TSAN_DYNAMIC_PATH})
endif()
endif()
if (RT_ASAN_DYNAMIC)
list(GET RT_ASAN_DYNAMIC 0 RT_ASAN_DYNAMIC)
endif()
if (RT_TSAN_DYNAMIC)
list(GET RT_TSAN_DYNAMIC 0 RT_TSAN_DYNAMIC)
endif()
if (RT_ASAN_DYNAMIC AND RT_TSAN_DYNAMIC)
set(sanitizer_runtime_libraries
${RT_ASAN_DYNAMIC}
${RT_TSAN_DYNAMIC}
)
endif()
endif() endif()
message(STATUS "Linking to llvm libs ${llvm_libs}") message(STATUS "Linking to llvm libs ${llvm_libs}")
@@ -461,12 +388,9 @@ add_executable(c3c
src/utils/whereami.c src/utils/whereami.c
src/utils/cpus.c src/utils/cpus.c
src/utils/unzipper.c src/utils/unzipper.c
src/utils/msi.c
src/compiler/c_codegen.c src/compiler/c_codegen.c
src/compiler/decltable.c src/compiler/decltable.c
src/compiler/methodtable.c
src/compiler/mac_support.c src/compiler/mac_support.c
src/utils/fetch_msvc.c
src/compiler/windows_support.c src/compiler/windows_support.c
src/compiler/codegen_asm.c src/compiler/codegen_asm.c
src/compiler/asm_target.c src/compiler/asm_target.c
@@ -509,22 +433,13 @@ if(C3_WITH_LLVM)
target_compile_definitions(c3c PUBLIC LLVM_AVAILABLE=1) target_compile_definitions(c3c PUBLIC LLVM_AVAILABLE=1)
add_library(c3c_wrappers STATIC wrapper/src/wrapper.cpp) add_library(c3c_wrappers STATIC wrapper/src/wrapper.cpp)
if (MSVC) if (MSVC)
# Use the same detected CRT for the wrapper
set_target_properties(c3c_wrappers PROPERTIES MSVC_RUNTIME_LIBRARY "MultiThreaded${MSVC_CRT_SUFFIX}")
set_target_properties(miniz PROPERTIES MSVC_RUNTIME_LIBRARY "MultiThreaded${MSVC_CRT_SUFFIX}")
target_compile_options(c3c PRIVATE target_compile_options(c3c PRIVATE
"$<$<CONFIG:Debug>:/EHa>" "$<$<CONFIG:Debug>:/EHa>"
"$<$<CONFIG:Release>:/EHsc>") "$<$<CONFIG:Release>:/EHsc>")
endif() endif()
if(C3_LLD_INCLUDE_DIR)
target_include_directories(c3c_wrappers PRIVATE ${C3_LLD_INCLUDE_DIR})
endif()
else() else()
target_sources(c3c PRIVATE src/utils/hostinfo.c) target_sources(c3c PRIVATE src/utils/hostinfo.c)
target_compile_definitions(c3c PUBLIC LLVM_AVAILABLE=0) target_compile_definitions(c3c PUBLIC LLVM_AVAILABLE=0)
target_link_libraries(c3c m)
endif() endif()
target_include_directories(c3c PRIVATE target_include_directories(c3c PRIVATE
@@ -574,7 +489,7 @@ else()
endif() endif()
if(C3_WITH_LLVM) if(C3_WITH_LLVM)
target_link_libraries(c3c miniz c3c_wrappers) target_link_libraries(c3c ${llvm_libs} miniz c3c_wrappers ${lld_libs})
target_include_directories(c3c PRIVATE target_include_directories(c3c PRIVATE
"${CMAKE_SOURCE_DIR}/wrapper/include/") "${CMAKE_SOURCE_DIR}/wrapper/include/")
@@ -582,11 +497,11 @@ if(C3_WITH_LLVM)
target_include_directories(c3c_wrappers PRIVATE target_include_directories(c3c_wrappers PRIVATE
"${CMAKE_SOURCE_DIR}/wrapper/include/") "${CMAKE_SOURCE_DIR}/wrapper/include/")
target_link_libraries(c3c_wrappers PUBLIC ${lld_libs} ${llvm_libs}) target_link_libraries(c3c_wrappers ${llvm_libs} ${lld_libs})
else() else()
target_link_libraries(c3c miniz ${lld_libs} ${llvm_libs}) target_link_libraries(c3c ${llvm_libs} miniz ${lld_libs})
endif() endif()
@@ -603,11 +518,12 @@ if(MINGW)
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--stack,8388608") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--stack,8388608")
endif () endif ()
if (NOT WIN32) if (CURL_FOUND)
# For dlopen support target_link_libraries(c3c ${CURL_LIBRARIES})
if (CMAKE_DL_LIBS) target_include_directories(c3c PRIVATE ${CURL_INCLUDE_DIRS})
target_link_libraries(c3c ${CMAKE_DL_LIBS}) target_compile_definitions(c3c PUBLIC CURL_FOUND=1)
endif() else()
target_compile_definitions(c3c PUBLIC CURL_FOUND=0)
endif() endif()
@@ -634,21 +550,13 @@ if(MSVC)
endif() endif()
if(C3_WITH_LLVM) if(C3_WITH_LLVM)
# The the sanitizer libs are in the folder "lib/clang/21/lib/windows/" but use the find anyway set(clang_lib_dir ${llvm_dir}/lib/clang/${C3_LLVM_VERSION}/lib/windows)
file(GLOB_RECURSE FOUND_ASAN_LIB "${llvm_dir}/*clang_rt.asan_dynamic-x86_64.lib") set(sanitizer_runtime_libraries
if(FOUND_ASAN_LIB) ${clang_lib_dir}/clang_rt.asan-x86_64.lib
list(GET FOUND_ASAN_LIB 0 _asan_path) ${clang_lib_dir}/clang_rt.asan_dynamic-x86_64.lib
get_filename_component(_asan_dir "${_asan_path}" DIRECTORY) ${clang_lib_dir}/clang_rt.asan_dynamic-x86_64.dll
set(sanitizer_runtime_libraries ${clang_lib_dir}/clang_rt.asan_dynamic_runtime_thunk-x86_64.lib)
${_asan_dir}/clang_rt.asan_dynamic-x86_64.lib
${_asan_dir}/clang_rt.asan_dynamic-x86_64.dll
${_asan_dir}/clang_rt.asan_dynamic_runtime_thunk-x86_64.lib)
message(STATUS "Found Sanitizer binaries at: ${_asan_dir}")
else()
message(WARNING "Could not find sanitizer runtime libraries in ${llvm_dir}")
endif()
endif() endif()
else() else()
if (C3_WITH_LLVM AND NOT LLVM_ENABLE_RTTI) if (C3_WITH_LLVM AND NOT LLVM_ENABLE_RTTI)
target_compile_options(c3c_wrappers PRIVATE -fno-rtti) target_compile_options(c3c_wrappers PRIVATE -fno-rtti)
@@ -662,22 +570,10 @@ else()
-Wno-unused-function -Wno-unused-function
-Wno-unused-variable -Wno-unused-variable
-Wno-unused-parameter -Wno-unused-parameter
-Wno-char-subscripts
) )
target_link_options(c3c PRIVATE -pthread) target_link_options(c3c PRIVATE -pthread)
endif() endif()
if(CMAKE_C_COMPILER_ID STREQUAL "TinyCC")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wl,--no-eh-frame-hdr -z noexecstack")
# Link the static tcc runtime archive if it exists
if(EXISTS "${TCC_LIB_PATH}")
target_link_libraries(c3c "${TCC_LIB_PATH}")
else()
message(FATAL_ERROR "TCC runtime not found at ${TCC_LIB_PATH}; Ensure the path is correct.")
endif()
endif()
install(TARGETS c3c DESTINATION bin) install(TARGETS c3c DESTINATION bin)
install(DIRECTORY lib/ DESTINATION lib/c3) install(DIRECTORY lib/ DESTINATION lib/c3)
@@ -702,10 +598,8 @@ if (C3_WITH_LLVM AND DEFINED sanitizer_runtime_libraries)
if (APPLE) if (APPLE)
# Change LC_ID_DYLIB to be rpath-based instead of having an absolute path # Change LC_ID_DYLIB to be rpath-based instead of having an absolute path
# We set DYLD_LIBRARY_PATH so the tools (which might be dynamic) can find libLLVM.dylib in the artifact
string(REPLACE ";" ":" _dyld_path "${LLVM_LIBRARY_DIRS}")
add_custom_command(TARGET c3c POST_BUILD add_custom_command(TARGET c3c POST_BUILD
COMMAND ${CMAKE_COMMAND} -E env "DYLD_LIBRARY_PATH=${_dyld_path}:$ENV{DYLD_LIBRARY_PATH}" find $<TARGET_FILE_DIR:c3c>/c3c_rt -type f -name "*.dylib" -execdir ${LLVM_TOOLS_BINARY_DIR}/llvm-install-name-tool -id @rpath/{} {} $<SEMICOLON> COMMAND find $<TARGET_FILE_DIR:c3c>/c3c_rt -type f -name "*.dylib" -execdir ${LLVM_TOOLS_BINARY_DIR}/llvm-install-name-tool -id @rpath/{} {} $<SEMICOLON>
VERBATIM) VERBATIM)
endif() endif()

View File

@@ -59,7 +59,7 @@ further defined and clarified by project maintainers.
## Enforcement ## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be 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 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 is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident. 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 Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
a copy of this software and associated documentation files (the Everyone is permitted to copy and distribute verbatim copies
"Software"), to deal in the Software without restriction, including of this license document, but changing it is not allowed.
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, This version of the GNU Lesser General Public License incorporates
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF the terms and conditions of version 3 of the GNU General Public
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND License, supplemented by the additional permissions listed below.
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 0. Additional Definitions.
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 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.

156
README.md
View File

@@ -8,11 +8,11 @@ for programmers who like C.
Precompiled binaries for the following operating systems are available: Precompiled binaries for the following operating systems are available:
- Windows x64 [download](https://github.com/c3lang/c3c/releases/download/latest-prerelease-tag/c3-windows.zip), [install instructions](#installing-on-windows-with-precompiled-binaries). - Windows x64 [download](https://github.com/c3lang/c3c/releases/download/latest-prerelease/c3-windows.zip), [install instructions](#installing-on-windows-with-precompiled-binaries).
- Debian x64 [download](https://github.com/c3lang/c3c/releases/download/latest-prerelease-tag/c3-linux.tar.gz), [install instructions](#installing-on-debian-with-precompiled-binaries). - 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-tag/c3-ubuntu-20.tar.gz), [install instructions](#installing-on-ubuntu-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-tag/c3-macos.zip), [install instructions](#installing-on-macos-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-tag/c3-openbsd.tar.gz), [install instructions](#installing-on-openbsd-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). The manual for C3 can be found at [www.c3-lang.org](http://www.c3-lang.org).
@@ -36,10 +36,10 @@ whole new language.
### Example code ### Example code
The following code shows [generics](https://c3-lang.org/generic-programming/generics/) (more examples can be found at https://c3-lang.org/language-overview/examples/). The following code shows [generic modules](https://c3-lang.org/generic-programming/generics/) (more examples can be found at https://c3-lang.org/language-overview/examples/).
```c3 ```cpp
module stack <Type>; module stack {Type};
// Above: the parameterized type is applied to the entire module. // Above: the parameterized type is applied to the entire module.
struct Stack struct Stack
@@ -78,7 +78,7 @@ fn bool Stack.empty(Stack* this)
Testing it out: Testing it out:
```c3 ```cpp
import stack; import stack;
// Define our new types, the first will implicitly create // Define our new types, the first will implicitly create
@@ -142,7 +142,7 @@ fn void main()
### Current status ### Current status
The current stable version of the compiler is **version 0.7.10**. The current stable version of the compiler is **version 0.7.4**.
The upcoming 0.7.x releases will focus on expanding the standard library, The upcoming 0.7.x releases will focus on expanding the standard library,
fixing bugs and improving compile time analysis. fixing bugs and improving compile time analysis.
@@ -151,7 +151,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) 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). 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** **Support matrix**
@@ -162,8 +162,6 @@ The compiler is currently verified to compile on Linux, OpenBSD, Windows and Mac
| MacOS x64 | Yes | Yes + cross compilation | Yes | Yes | Yes | Yes* | | MacOS x64 | Yes | Yes + cross compilation | Yes | Yes | Yes | Yes* |
| MacOS Aarch64 | Yes | Yes + cross compilation | Yes | Yes | Yes | Yes* | | MacOS Aarch64 | Yes | Yes + cross compilation | Yes | Yes | Yes | Yes* |
| iOS Aarch64 | No | Untested | Untested | Yes | Yes | Yes* | | iOS Aarch64 | No | Untested | Untested | Yes | Yes | Yes* |
| Android Aarch64 | No | Untested | Untested | Untested | Untested | Yes* |
| Android x64 | No | Untested | Untested | Untested | Untested | Yes* |
| Linux x86 | Yes | Yes | Yes | Yes | Yes | Yes* | | Linux x86 | Yes | Yes | Yes | Yes | Yes | Yes* |
| Linux x64 | Yes | Yes | Yes | Yes | Yes | Yes* | | Linux x64 | Yes | Yes | Yes | Yes | Yes | Yes* |
| Linux Aarch64 | Yes | Yes | Yes | Yes | Yes | Yes* | | Linux Aarch64 | Yes | Yes | Yes | Yes | Yes | Yes* |
@@ -174,7 +172,6 @@ The compiler is currently verified to compile on Linux, OpenBSD, Windows and Mac
| ELF freestanding Aarch64 | No | Untested | No | No | No | Yes* | | ELF freestanding Aarch64 | No | Untested | No | No | No | Yes* |
| ELF freestanding Riscv64 | No | Untested | No | No | No | Untested | | ELF freestanding Riscv64 | No | Untested | No | No | No | Untested |
| ELF freestanding Riscv32 | No | Untested | No | No | No | Untested | | ELF freestanding Riscv32 | No | Untested | No | No | No | Untested |
| ELF freestanding Xtensa* | No | Untested | No | No | No | Untested |
| FreeBSD x86 | Untested | Untested | No | Yes | Untested | Yes* | | FreeBSD x86 | Untested | Untested | No | Yes | Untested | Yes* |
| FreeBSD x64 | Untested | Untested | No | Yes | Untested | Yes* | | FreeBSD x64 | Untested | Untested | No | Yes | Untested | Yes* |
| NetBSD x86 | Untested | Untested | No | Yes | Untested | Yes* | | NetBSD x86 | Untested | Untested | No | Yes | Untested | Yes* |
@@ -187,8 +184,7 @@ The compiler is currently verified to compile on Linux, OpenBSD, Windows and Mac
*\* Inline asm is still a work in progress*<br> *\* Inline asm is still a work in progress*<br>
*\* OpenBSD 7.7 is the only tested version*<br> *\* OpenBSD 7.7 is the only tested version*<br>
*\* OpenBSD has limited stacktrace, needs to be tested further*<br> *\* OpenBSD has limited stacktrace, needs to be tested further*
*\* Xtensa support is enabled by compiling with `-DXTENSA_ENABLE`. The [espressif llvm fork](https://github.com/espressif/llvm-project) is recommended for best compatibility*
More platforms will be supported in the future. More platforms will be supported in the future.
@@ -197,7 +193,7 @@ More platforms will be supported in the future.
- If you wish to contribute with ideas, please file issues or discuss on Discord. - If you wish to contribute with ideas, please file issues or discuss on Discord.
- Interested in contributing to the stdlib? Please get in touch on Discord. - Interested in contributing to the stdlib? Please get in touch on Discord.
- Compilation instructions for other Linux and Unix variants are appreciated. - Compilation instructions for other Linux and Unix variants are appreciated.
- Would you like to contribute bindings to some library? It would be nice to have support for SDL3 and more. If you have created some bindings, please submit them to https://github.com/c3lang/vendor. - Would you like to contribute bindings to some library? It would be nice to have support for SDL, Raylib and more.
- Build something with C3 and show it off and give feedback. The language is still open for significant tweaks. - Build something with C3 and show it off and give feedback. The language is still open for significant tweaks.
- Start work on the C -> C3 converter which takes C code and does a "best effort" to translate it to C3. The first version only needs to work on C headers. - Start work on the C -> C3 converter which takes C code and does a "best effort" to translate it to C3. The first version only needs to work on C headers.
- Do you have some specific area you have deep knowledge of and could help make C3 even better at doing? File or comment on issues. - Do you have some specific area you have deep knowledge of and could help make C3 even better at doing? File or comment on issues.
@@ -207,67 +203,36 @@ More platforms will be supported in the future.
This installs the latest prerelease build, as opposed to the latest released version. This installs the latest prerelease build, as opposed to the latest released version.
#### Installing on Windows with precompiled binaries #### Installing on Windows with precompiled binaries
1. Download the zip file: [https://github.com/c3lang/c3c/releases/download/latest-prerelease-tag/c3-windows.zip](https://github.com/c3lang/c3c/releases/download/latest-prerelease-tag/c3-windows.zip) 1. Download the zip file: [https://github.com/c3lang/c3c/releases/download/latest-prerelease/c3-windows.zip](https://github.com/c3lang/c3c/releases/download/latest-prerelease/c3-windows.zip)
(debug version [here](https://github.com/c3lang/c3c/releases/download/latest-prerelease-tag/c3-windows-debug.zip)) (debug version [here](https://github.com/c3lang/c3c/releases/download/latest-prerelease/c3-windows-debug.zip))
2. Unzip exe and standard lib. 2. Unzip exe and standard lib.
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. 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`. 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 #### Installing on Debian with precompiled binaries
1. Download tar file: [https://github.com/c3lang/c3c/releases/download/latest-prerelease-tag/c3-linux.tar.gz](https://github.com/c3lang/c3c/releases/download/latest-prerelease-tag/c3-linux.tar.gz) 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-tag/c3-linux-debug.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. 2. Unpack executable and standard lib.
3. Run `./c3c`. 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 #### Installing on Ubuntu with precompiled binaries
1. Download tar file: [https://github.com/c3lang/c3c/releases/download/latest-prerelease-tag/c3-ubuntu-20.tar.gz](https://github.com/c3lang/c3c/releases/download/latest-prerelease-tag/c3-ubuntu-20.tar.gz) 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-tag/c3-ubuntu-20-debug.tar.gz)) (debug version [here](https://github.com/c3lang/c3c/releases/download/latest-prerelease/c3-ubuntu-20-debug.tar.gz))
2. Unpack executable and standard lib. 2. Unpack executable and standard lib.
3. Run `./c3c`. 3. Run `./c3c`.
#### Installing on MacOS with precompiled binaries #### Installing on MacOS with precompiled binaries
1. Make sure you have XCode with command line tools installed. 1. Make sure you have XCode with command line tools installed.
2. Download the zip file: [https://github.com/c3lang/c3c/releases/download/latest-prerelease-tag/c3-macos.zip](https://github.com/c3lang/c3c/releases/download/latest-prerelease-tag/c3-macos.zip) 2. Download the zip file: [https://github.com/c3lang/c3c/releases/download/latest-prerelease/c3-macos.zip](https://github.com/c3lang/c3c/releases/download/latest-prerelease/c3-macos.zip)
(debug version [here](https://github.com/c3lang/c3c/releases/download/latest-prerelease-tag/c3-macos-debug.zip)) (debug version [here](https://github.com/c3lang/c3c/releases/download/latest-prerelease/c3-macos-debug.zip))
3. Unzip executable and standard lib. 3. Unzip executable and standard lib.
4. Run `./c3c`. 4. Run `./c3c`.
(*Note that there is a known issue with debug symbol generation on MacOS 13, see [issue #1086](https://github.com/c3lang/c3c/issues/1086)) (*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 #### Installing on OpenBSD with precompiled binaries
1. Download tar file: [https://github.com/c3lang/c3c/releases/download/latest-prerelease-tag/c3-openbsd.tar.gz](https://github.com/c3lang/c3c/releases/download/latest-prerelease-tag/c3-openbsd.tar.gz) 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-tag/c3-openbsd-debug.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. 2. Unpack executable and standard lib.
3. Run `./c3c`. 3. Run `./c3c`.
@@ -299,60 +264,6 @@ cd c3c-git
makepkg -si 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 #### 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. 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.
@@ -386,7 +297,7 @@ scoop install c3
#### Getting started with a "hello world" #### Getting started with a "hello world"
Create a `main.c3` file with: Create a `main.c3` file with:
```c3 ```c++
module hello_world; module hello_world;
import std::io; import std::io;
@@ -494,14 +405,6 @@ After compilation, the `c3c` binary will be located in the `build` directory. Yo
6. To install the compiler globally: `sudo cmake --install build` 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 #### Compiling on other Linux / Unix variants
1. Install CMake. 1. Install CMake.
@@ -515,17 +418,12 @@ provide the link path to the LLVM CMake directories, e.g. `cmake -B build -S . -
*A note on compiling for Linux/Unix/MacOS: to be able to fetch vendor libraries *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 libcurl is needed. The CMake script should detect it if it is available. Note that
this functionality is non-essential and it is perfectly fine to use the compiler without it.* this functionality is non-essential and it is perfectly fine to user the compiler without it.*
#### Licensing #### Licensing
Unless specified otherwise, the code in this repository is MIT licensed. The C3 compiler is licensed under LGPL 3.0, the standard library itself is
The exception is the compiler source code (the source code under `src`), MIT licensed.
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 you build your own C3 compiler.
#### Editor plugins #### Editor plugins

View File

@@ -107,8 +107,7 @@ fn void hash_speeds_of_many_random_values() => @pool()
foreach (&v : vwideints) *v = (uint128)random::next(&rand, uint.max); foreach (&v : vwideints) *v = (uint128)random::next(&rand, uint.max);
char[48][] zstrs = allocator::new_array(tmem, char[48], $arrsz)[:$arrsz]; char[48][] zstrs = allocator::new_array(tmem, char[48], $arrsz)[:$arrsz];
String[$arrsz] strs;
String[] strs = mem::temp_array(String, $arrsz);
foreach (x, &v : zstrs) foreach (x, &v : zstrs)
{ {
foreach (&c : (*v)[:random::next(&rand, 48)]) *c = (char)random::next(&rand, char.max); foreach (&c : (*v)[:random::next(&rand, 48)]) *c = (char)random::next(&rand, char.max);
@@ -196,7 +195,7 @@ fn void random_access_string_keys() => @pool()
v.tinit(); v.tinit();
usz pseudo_checksum = 0; usz pseudo_checksum = 0;
String[] saved = mem::temp_array(String, 5_000); String[5_000] saved;
for (usz i = 0; i < saved.len; ++i) for (usz i = 0; i < saved.len; ++i)
{ {

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,51 +0,0 @@
module deflate_benchmarks;
import std::compression::deflate;
const uint SMALL_ITERATIONS = 50000;
const uint LARGE_ITERATIONS = 100;
// Data to compress
const char[] SMALL_DATA = { [0..1023] = 'A' };
const char[] LARGE_DATA = { [0..1048575] = 'B' };
char[] small_compressed;
char[] large_compressed;
fn void initialize_bench() @init
{
small_compressed = deflate::compress(mem, SMALL_DATA)!!;
large_compressed = deflate::compress(mem, LARGE_DATA)!!;
set_benchmark_warmup_iterations(2);
set_benchmark_max_iterations(10);
set_benchmark_func_iterations($qnameof(deflate_compress_small), SMALL_ITERATIONS);
set_benchmark_func_iterations($qnameof(deflate_decompress_small), SMALL_ITERATIONS);
set_benchmark_func_iterations($qnameof(deflate_compress_large), LARGE_ITERATIONS);
set_benchmark_func_iterations($qnameof(deflate_decompress_large), LARGE_ITERATIONS);
}
// =======================================================================================
module deflate_benchmarks @benchmark;
import std::compression::deflate;
import std::core::mem;
fn void deflate_compress_small() => @pool()
{
char[]? compressed = deflate::compress(tmem, SMALL_DATA);
}
fn void deflate_decompress_small() => @pool()
{
char[]? decompressed = deflate::decompress(tmem, small_compressed);
}
fn void deflate_compress_large() => @pool()
{
char[]? compressed = deflate::compress(tmem, LARGE_DATA);
}
fn void deflate_decompress_large() => @pool()
{
char[]? decompressed = deflate::decompress(tmem, large_compressed);
}

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,39 +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 chacha20_benchmarks;
import std::crypto::chacha20;
fn void initialize_bench() @init
{
set_benchmark_warmup_iterations(3);
set_benchmark_max_iterations(1024);
}
const char[] KEY = x'98bef1469be7269837a45bfbc92a5a6ac762507cf96443bf33b96b1bd4c6f8f6';
const char[] NONCE = x'44e792d63335abb1582e9253';
const uint COUNTER = 42;
char[] one_mb @align(ulong.sizeof) = { [0..1024*1024] = 0xA5 };
// This doesn't test both encryption + decryption, because it's a symmetric operation that shares
// a single common data transformation. Testing one limb is enough.
fn void gogo_chacha20() @benchmark
{
chacha20::encrypt_mut(one_mb[..], KEY, NONCE, COUNTER);
}
// Check what the speed of an unligned buffer looks like.
fn void gogo_chacha20_unaligned() @benchmark => @pool()
{
char[] copy = mem::talloc_array(char, one_mb.len + 3);
char[] im_off_slightly = copy[3..];
copy[3..] = one_mb[..];
assert((usz)im_off_slightly.ptr % usz.sizeof > 0);
runtime::@start_benchmark();
chacha20::encrypt_mut(im_off_slightly, KEY, NONCE, COUNTER);
runtime::@end_benchmark();
}

View File

@@ -1,113 +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 crypto_hash_benchmarks;
import std::collections::pair;
const usz COMMON_ITERATIONS = 1 << 17;
char* common_1mib_ptr;
char[] common_16;
char[] common_256;
char[] common_4kib;
char[] common_1mib;
fn void initialize_bench() @init
{
set_benchmark_warmup_iterations(3);
set_benchmark_max_iterations(COMMON_ITERATIONS + 3);
common_1mib_ptr = mem::alloc_array(char, 1024*1024);
common_1mib = common_1mib_ptr[:1024*1024];
common_1mib[..] = 0xA5;
common_16 = common_1mib[:16];
common_256 = common_1mib[:256];
common_4kib = common_1mib[:4096];
static String[] function_prefixes = {
$qnameof(md5_16)[..^4],
$qnameof(sha1_16)[..^4],
$qnameof(sha2_256_16)[..^4],
$qnameof(sha2_512_16)[..^4],
//$qnameof(blake2s_256_16)[..^4],
//$qnameof(blake2b_256_16)[..^4],
$qnameof(blake3_16)[..^4],
$qnameof(ripemd_160_16)[..^4],
$qnameof(whirlpool_16)[..^4],
$qnameof(streebog_256_16)[..^4],
$qnameof(streebog_512_16)[..^4],
};
static Pair{ String, uint }[] to_iters = {
{ "_4kib", 1 << 15 },
{ "_1mib", 1024 },
};
foreach (p : to_iters)
{
foreach (name : function_prefixes) set_benchmark_func_iterations(name.tconcat(p.first), p.second);
}
}
fn void teardown_bench() @finalizer
{
mem::free(common_1mib_ptr);
}
// =======================================================================================
module crypto_hash_benchmarks @benchmark;
import std::hash;
fn void md5_16() => md5::hash(common_16);
fn void sha1_16() => sha1::hash(common_16);
fn void sha2_256_16() => sha256::hash(common_16);
fn void sha2_512_16() => sha512::hash(common_16);
//fn void blake2s_256_16() => blake2::s(256, common_16);
//fn void blake2b_256_16() => blake2::b(256, common_16);
fn void blake3_16() => blake3::hash(common_16);
fn void ripemd_160_16() => ripemd::hash{160}(common_16);
fn void whirlpool_16() => whirlpool::hash(common_16);
fn void streebog_256_16() => streebog::hash_256(common_16);
fn void streebog_512_16() => streebog::hash_512(common_16);
fn void md5_256() => md5::hash(common_256);
fn void sha1_256() => sha1::hash(common_256);
fn void sha2_256_256() => sha256::hash(common_256);
fn void sha2_512_256() => sha512::hash(common_256);
//fn void blake2s_256_256() => blake2::s(256, common_256);
//fn void blake2b_256_256() => blake2::b(256, common_256);
fn void blake3_256() => blake3::hash(common_256);
fn void ripemd_160_256() => ripemd::hash{160}(common_256);
fn void whirlpool_256() => whirlpool::hash(common_256);
fn void streebog_256_256() => streebog::hash_256(common_256);
fn void streebog_512_256() => streebog::hash_512(common_256);
fn void md5_4kib() => md5::hash(common_4kib);
fn void sha1_4kib() => sha1::hash(common_4kib);
fn void sha2_256_4kib() => sha256::hash(common_4kib);
fn void sha2_512_4kib() => sha512::hash(common_4kib);
//fn void blake2s_256_4kib() => blake2::s(256, common_4kib);
//fn void blake2b_256_4kib() => blake2::b(256, common_4kib);
fn void blake3_4kib() => blake3::hash(common_4kib);
fn void ripemd_160_4kib() => ripemd::hash{160}(common_4kib);
fn void whirlpool_4kib() => whirlpool::hash(common_4kib);
fn void streebog_256_4kib() => streebog::hash_256(common_4kib);
fn void streebog_512_4kib() => streebog::hash_512(common_4kib);
fn void md5_1mib() => md5::hash(common_1mib);
fn void sha1_1mib() => sha1::hash(common_1mib);
fn void sha2_256_1mib() => sha256::hash(common_1mib);
fn void sha2_512_1mib() => sha512::hash(common_1mib);
//fn void blake2s_256_1mib() => blake2::s(256, common_1mib);
//fn void blake2b_256_1mib() => blake2::b(256, common_1mib);
fn void blake3_1mib() => blake3::hash(common_1mib);
fn void ripemd_160_1mib() => ripemd::hash{160}(common_1mib);
fn void whirlpool_1mib() => whirlpool::hash(common_1mib);
fn void streebog_256_1mib() => streebog::hash_256(common_1mib);
fn void streebog_512_1mib() => streebog::hash_512(common_1mib);

View File

@@ -1,57 +0,0 @@
module blake3_bench;
fn void initialize_bench() @init
{
set_benchmark_warmup_iterations(3);
set_benchmark_max_iterations(128);
input = mem::alloc_array(char, BUFSZ);
input[:BUFSZ] = (char[]){ [0..BUFSZ-1] = 0xA5 }[..];
input_slice = input[:BUFSZ];
}
fn void teardown_bench() @finalizer
{
mem::free(input);
input = null;
}
char* input;
char[] input_slice;
const usz BUFSZ = 1024 * 1024;
module blake3_bench @benchmark;
import std::hash;
fn void blake3_hash()
{
runtime::@start_benchmark();
char[*] myset = blake3::hash(input_slice);
runtime::@end_benchmark();
mem::zero_volatile(myset[..]);
}
fn void compared_with_sha256()
{
runtime::@start_benchmark();
char[*] myset = sha256::hash(input_slice);
runtime::@end_benchmark();
mem::zero_volatile(myset[..]);
}
fn void compared_with_sha512()
{
runtime::@start_benchmark();
char[*] myset = sha512::hash(input_slice);
runtime::@end_benchmark();
mem::zero_volatile(myset[..]);
}
fn void compared_with_whirlpool()
{
runtime::@start_benchmark();
char[*] myset = whirlpool::hash(input_slice);
runtime::@end_benchmark();
mem::zero_volatile(myset[..]);
}

View File

@@ -1,41 +0,0 @@
module md5_bench;
fn void initialize_bench() @init
{
set_benchmark_warmup_iterations(3);
set_benchmark_max_iterations(128);
input = mem::alloc_array(char, BUFSZ);
input[:BUFSZ] = (char[]){ [0..BUFSZ-1] = 0xA5 }[..];
input_slice = input[:BUFSZ];
}
fn void teardown_bench() @finalizer
{
mem::free(input);
input = null;
}
char* input;
char[] input_slice;
const usz BUFSZ = 1024 * 1024;
module md5_bench @benchmark;
import std::hash;
fn void md5_hash()
{
runtime::@start_benchmark();
char[*] myset = md5::hash(input_slice);
runtime::@end_benchmark();
mem::zero_volatile(myset[..]);
}
fn void compared_with_sha256()
{
runtime::@start_benchmark();
char[*] myset = sha256::hash(input_slice);
runtime::@end_benchmark();
mem::zero_volatile(myset[..]);
}

View File

@@ -1,76 +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 ripemd_bench;
fn void initialize_bench() @init
{
set_benchmark_warmup_iterations(3);
set_benchmark_max_iterations(256);
input = mem::alloc_array(char, BUFSZ);
input[:BUFSZ] = (char[]){ [0..BUFSZ-1] = 0xA5 }[..];
input_slice = input[:BUFSZ];
}
fn void teardown_bench() @finalizer
{
mem::free(input);
input = null;
}
char* input;
char[] input_slice;
const usz BUFSZ = 1024 * 1024;
module ripemd_bench @benchmark;
import std::hash;
fn void ripemd_128()
{
runtime::@start_benchmark();
char[*] myset = ripemd::hash{128}(input_slice);
runtime::@end_benchmark();
mem::zero_volatile(myset[..]);
}
fn void ripemd_160()
{
runtime::@start_benchmark();
char[*] myset = ripemd::hash{160}(input_slice);
runtime::@end_benchmark();
mem::zero_volatile(myset[..]);
}
fn void ripemd_256()
{
runtime::@start_benchmark();
char[*] myset = ripemd::hash{256}(input_slice);
runtime::@end_benchmark();
mem::zero_volatile(myset[..]);
}
fn void ripemd_320()
{
runtime::@start_benchmark();
char[*] myset = ripemd::hash{320}(input_slice);
runtime::@end_benchmark();
mem::zero_volatile(myset[..]);
}
fn void compared_with_sha256()
{
runtime::@start_benchmark();
char[*] myset = sha256::hash(input_slice);
runtime::@end_benchmark();
mem::zero_volatile(myset[..]);
}
fn void compared_with_whirlpool()
{
runtime::@start_benchmark();
char[*] myset = whirlpool::hash(input_slice);
runtime::@end_benchmark();
mem::zero_volatile(myset[..]);
}

View File

@@ -1,51 +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 streebog_bench;
fn void initialize_bench() @init
{
set_benchmark_warmup_iterations(3);
set_benchmark_max_iterations(256);
}
const char[] INPUT = { [0..1024*1024] = 0xA5 };
const char[*] EXPECTED_256 = x'694676905b7cf099755db1cc186f741f0fd1877aaaa4badcbfb305537f986971';
const char[*] EXPECTED_512 = x'fe21d08857ea97e79035d1e5c9ba5130786e8d1875bc74d628349560d94d6bdff0b0dcd2f6347eb8b3f0239b6cca76b5028c0ff45f631fcdf77b1d551dd079f3';
module streebog_bench @benchmark;
import std::hash;
fn void get_in_the_bog_256()
{
runtime::@start_benchmark();
char[*] myset = streebog::hash_256(INPUT);
runtime::@end_benchmark();
mem::zero_volatile(myset[..]);
}
fn void get_in_the_bog_512()
{
runtime::@start_benchmark();
char[*] myset = streebog::hash_512(INPUT);
runtime::@end_benchmark();
mem::zero_volatile(myset[..]);
}
fn void compared_with_sha256()
{
runtime::@start_benchmark();
char[*] myset = sha256::hash(INPUT);
runtime::@end_benchmark();
mem::zero_volatile(myset[..]);
}
fn void compared_with_whirlpool()
{
runtime::@start_benchmark();
char[*] myset = whirlpool::hash(INPUT);
runtime::@end_benchmark();
mem::zero_volatile(myset[..]);
}

View File

@@ -1,57 +0,0 @@
module whirlpool_bench;
fn void initialize_bench() @init
{
set_benchmark_warmup_iterations(3);
set_benchmark_max_iterations(128);
input = mem::alloc_array(char, BUFSZ);
input[:BUFSZ] = (char[]){ [0..BUFSZ-1] = 0xA5 }[..];
input_slice = input[:BUFSZ];
}
fn void teardown_bench() @finalizer
{
mem::free(input);
input = null;
}
char* input;
char[] input_slice;
const usz BUFSZ = 1024 * 1024;
module whirlpool_bench @benchmark;
import std::hash;
fn void whirlpool_hash()
{
runtime::@start_benchmark();
char[*] myset = whirlpool::hash(input_slice);
runtime::@end_benchmark();
mem::zero_volatile(myset[..]);
}
fn void compared_with_sha256()
{
runtime::@start_benchmark();
char[*] myset = sha256::hash(input_slice);
runtime::@end_benchmark();
mem::zero_volatile(myset[..]);
}
fn void compared_with_sha512()
{
runtime::@start_benchmark();
char[*] myset = sha512::hash(input_slice);
runtime::@end_benchmark();
mem::zero_volatile(myset[..]);
}
fn void compared_with_streebog_512()
{
runtime::@start_benchmark();
char[*] myset = streebog::hash_512(input_slice);
runtime::@end_benchmark();
mem::zero_volatile(myset[..]);
}

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env bash #!/bin/bash
: ${DOCKER:=docker} : ${DOCKER:=docker}
: ${IMAGE:="c3c-builder"} : ${IMAGE:="c3c-builder"}
@@ -30,8 +30,7 @@ chmod -R 777 build bin
exec $DOCKER run -i --rm \ exec $DOCKER run -i --rm \
-v "$PWD":/home/c3c/source \ -v "$PWD":/home/c3c/source \
-w /home/c3c/source $IMAGE bash -c \ -w /home/c3c/source $IMAGE bash -c \
"git config --global --add safe.directory /home/c3c/source && \ "cmake -S . -B build \
cmake -S . -B build \
-G Ninja \ -G Ninja \
-DCMAKE_BUILD_TYPE=$CMAKE_BUILD_TYPE \ -DCMAKE_BUILD_TYPE=$CMAKE_BUILD_TYPE \
-DCMAKE_C_COMPILER=clang-$LLVM_VERSION \ -DCMAKE_C_COMPILER=clang-$LLVM_VERSION \
@@ -42,4 +41,4 @@ exec $DOCKER run -i --rm \
-DCMAKE_DLLTOOL=llvm-dlltool-$LLVM_VERSION \ -DCMAKE_DLLTOOL=llvm-dlltool-$LLVM_VERSION \
-DC3_LLVM_VERSION=auto && \ -DC3_LLVM_VERSION=auto && \
cmake --build build && \ cmake --build build && \
cp -r build/c3c build/lib bin" cp -r build/c3c build/lib bin"

View File

@@ -2,44 +2,48 @@ ARG UBUNTU_VERSION=22.04
FROM ubuntu:${UBUNTU_VERSION} FROM ubuntu:${UBUNTU_VERSION}
ARG LLVM_VERSION=18 ARG LLVM_VERSION=18
ARG CMAKE_VERSION=3.20.0 ENV LLVM_DEV_VERSION=20
# Prevent interactive prompts during apt install ARG CMAKE_VERSION=3.20
ENV DEBIAN_FRONTEND=noninteractive
RUN for i in 1 2 3; do apt-get update && break || sleep 2; done && \ RUN apt-get update && apt-get install -y wget gnupg software-properties-common zlib1g zlib1g-dev python3 ninja-build curl g++ && \
apt-get install -y --fix-missing \ wget https://github.com/Kitware/CMake/releases/download/v${CMAKE_VERSION}/cmake-$CMAKE_VERSION-linux-x86_64.sh && \
wget gnupg software-properties-common lsb-release \
zlib1g zlib1g-dev python3 ninja-build curl g++ libcurl4-openssl-dev git && \
CODENAME=$(lsb_release -cs) && \
ARCH=$(uname -m) && \
wget https://github.com/Kitware/CMake/releases/download/v${CMAKE_VERSION}/cmake-${CMAKE_VERSION}-linux-${ARCH}.sh && \
mkdir -p /opt/cmake && \ mkdir -p /opt/cmake && \
sh cmake-${CMAKE_VERSION}-linux-${ARCH}.sh --prefix=/opt/cmake --skip-license && \ sh cmake-${CMAKE_VERSION}-linux-x86_64.sh --prefix=/opt/cmake --skip-license && \
ln -s /opt/cmake/bin/cmake /usr/local/bin/cmake && \ rm cmake-${CMAKE_VERSION}-linux-x86_64.sh && \
rm cmake-${CMAKE_VERSION}-linux-${ARCH}.sh ln -s /opt/cmake/bin/cmake /usr/local/bin/cmake
RUN CODENAME=$(lsb_release -cs) && \ RUN wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | apt-key add - && \
for i in 1 2; do wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key && break || sleep 2; done | apt-key add - && \ if [ "${LLVM_VERSION}" -lt 18 ]; then \
if [ "${LLVM_VERSION}" -ge 16 ]; then \ add-apt-repository "deb http://apt.llvm.org/focal/ llvm-toolchain-focal-${LLVM_VERSION} main" && \
add-apt-repository "deb http://apt.llvm.org/${CODENAME}/ llvm-toolchain-${CODENAME}-${LLVM_VERSION} main"; \ apt-get update && \
apt-get install -y -t llvm-toolchain-focal-${LLVM_VERSION} \
libpolly-${LLVM_VERSION}-dev \
clang-${LLVM_VERSION} llvm-${LLVM_VERSION} llvm-${LLVM_VERSION}-dev \
lld-${LLVM_VERSION} liblld-${LLVM_VERSION}-dev libmlir-${LLVM_VERSION} \
libmlir-${LLVM_VERSION}-dev mlir-${LLVM_VERSION}-tools; \
elif [ "${LLVM_VERSION}" -lt "${LLVM_DEV_VERSION}" ]; then \
add-apt-repository "deb http://apt.llvm.org/focal/ llvm-toolchain-focal-${LLVM_VERSION} main" && \
apt-get update && \
apt-get install -y -t llvm-toolchain-focal-${LLVM_VERSION} \
libpolly-${LLVM_VERSION}-dev \
clang-${LLVM_VERSION} clang++-${LLVM_VERSION} llvm-${LLVM_VERSION} llvm-${LLVM_VERSION}-dev \
lld-${LLVM_VERSION} liblld-${LLVM_VERSION}-dev; \
else \ else \
add-apt-repository "deb http://apt.llvm.org/${CODENAME}/ llvm-toolchain-${CODENAME} main"; \ add-apt-repository "deb http://apt.llvm.org/focal/ llvm-toolchain-focal main" && \
apt-get update && \
apt-get install -y -t llvm-toolchain-focal \
libpolly-${LLVM_VERSION}-dev \
clang-${LLVM_VERSION} llvm-${LLVM_VERSION} llvm-${LLVM_VERSION}-dev \
lld-${LLVM_VERSION} liblld-${LLVM_VERSION}-dev; \
fi && \ fi && \
for i in 1 2 3; do apt-get update && break || sleep 2; done && \
apt-get install -y --fix-missing \
clang-${LLVM_VERSION} \
clang++-${LLVM_VERSION} \
llvm-${LLVM_VERSION} \
llvm-${LLVM_VERSION}-dev \
lld-${LLVM_VERSION} \
liblld-${LLVM_VERSION}-dev \
libpolly-${LLVM_VERSION}-dev && \
rm -rf /var/lib/apt/lists/* rm -rf /var/lib/apt/lists/*
RUN groupadd -g 1337 c3c && \ RUN groupadd -g 1337 c3c && \
useradd -m -u 1337 -g c3c c3c useradd -m -u 1337 -g c3c c3c
# Add cmake to PATH for user c3c
USER c3c USER c3c
ENV PATH="/opt/cmake/bin:${PATH}" 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"; flake-utils.url = "github:numtide/flake-utils";
}; };
outputs = { self, ... }@inputs: inputs.flake-utils.lib.eachDefaultSystem outputs = { self, ... } @ inputs: inputs.flake-utils.lib.eachDefaultSystem
(system: (system:
let pkgs = import inputs.nixpkgs { inherit system; }; let pkgs = import inputs.nixpkgs { inherit system; };
c3cBuild = set: pkgs.callPackage ./nix/default.nix (set // { call = set: pkgs.callPackage ./nix/default.nix (
rev = self.rev or "unknown"; set // {
}); rev = self.rev or "unknown";
}
);
in { in {
packages = { packages = {
default = self.packages.${system}.c3c; default = self.packages.${system}.c3c;
c3c = c3cBuild {}; c3c = call {};
c3c-checks = c3cBuild { c3c-checks = pkgs.callPackage ./nix/default.nix {
checks = true; checks = true;
}; };
c3c-debug = c3cBuild { c3c-debug = pkgs.callPackage ./nix/default.nix {
debug = true; debug = true;
}; };
c3c-debug-checks = c3cBuild { c3c-debug-checks = pkgs.callPackage ./nix/default.nix {
debug = true; debug = true;
checks = 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

@@ -1,9 +1,9 @@
// Copyright (c) 2023-2025 Eduardo José Gómez Hernández. All rights reserved. // Copyright (c) 2023-2025 Eduardo José Gómez Hernández. All rights reserved.
// Use of this source code is governed by the MIT license // Use of this source code is governed by the MIT license
// a copy of which can be found in the LICENSE_STDLIB file. // a copy of which can be found in the LICENSE_STDLIB file.
module std::atomic::types; module std::atomic::types{Type};
struct Atomic <Type> struct Atomic
{ {
Type data; Type data;
} }
@@ -11,87 +11,147 @@ struct Atomic <Type>
<* <*
Loads data atomically, by default this uses SEQ_CONSISTENT ordering. Loads data atomically, by default this uses SEQ_CONSISTENT ordering.
@param $ordering : "The ordering, cannot be release or acquire-release." @param ordering : "The ordering, cannot be release or acquire-release."
@require $ordering != RELEASE && $ordering != ACQUIRE_RELEASE : "Release and acquire-release are not valid for load" @require ordering != RELEASE && ordering != ACQUIRE_RELEASE : "Release and acquire-release are not valid for load"
*> *>
macro Type Atomic.load(&self, AtomicOrdering $ordering = SEQ_CONSISTENT) macro Type Atomic.load(&self, AtomicOrdering ordering = SEQ_CONSISTENT)
{ {
return $$atomic_load(&self.data, false, $ordering.ordinal); Type* data = &self.data;
switch(ordering)
{
case NOT_ATOMIC: return $$atomic_load(data, false, AtomicOrdering.NOT_ATOMIC.ordinal);
case UNORDERED: return $$atomic_load(data, false, AtomicOrdering.UNORDERED.ordinal);
case RELAXED: return $$atomic_load(data, false, AtomicOrdering.RELAXED.ordinal);
case ACQUIRE: return $$atomic_load(data, false, AtomicOrdering.ACQUIRE.ordinal);
case SEQ_CONSISTENT: return $$atomic_load(data, false, AtomicOrdering.SEQ_CONSISTENT.ordinal);
case ACQUIRE_RELEASE:
case RELEASE: unreachable("Invalid ordering.");
}
} }
<* <*
Stores data atomically, by default this uses SEQ_CONSISTENT ordering. Stores data atomically, by default this uses SEQ_CONSISTENT ordering.
@param $ordering : "The ordering, cannot be acquire or acquire-release." @param ordering : "The ordering, cannot be acquire or acquire-release."
@require $ordering != ACQUIRE && $ordering != ACQUIRE_RELEASE : "Acquire and acquire-release are not valid for store" @require ordering != ACQUIRE && ordering != ACQUIRE_RELEASE : "Acquire and acquire-release are not valid for store"
*> *>
macro void Atomic.store(&self, Type value, AtomicOrdering $ordering = SEQ_CONSISTENT) macro void Atomic.store(&self, Type value, AtomicOrdering ordering = SEQ_CONSISTENT)
{ {
$$atomic_store(&self.data, value, false, $ordering.ordinal); Type* data = &self.data;
switch(ordering)
{
case NOT_ATOMIC: $$atomic_store(data, value, false, AtomicOrdering.NOT_ATOMIC.ordinal);
case UNORDERED: $$atomic_store(data, value, false, AtomicOrdering.UNORDERED.ordinal);
case RELAXED: $$atomic_store(data, value, false, AtomicOrdering.RELAXED.ordinal);
case RELEASE: $$atomic_store(data, value, false, AtomicOrdering.RELEASE.ordinal);
case SEQ_CONSISTENT: $$atomic_store(data, value, false, AtomicOrdering.SEQ_CONSISTENT.ordinal);
case ACQUIRE_RELEASE:
case ACQUIRE: unreachable("Invalid ordering.");
}
} }
macro Type Atomic.add(&self, Type value, AtomicOrdering $ordering = SEQ_CONSISTENT) macro Type Atomic.add(&self, Type value, AtomicOrdering ordering = SEQ_CONSISTENT)
{ {
return atomic::fetch_add(&self.data, value, $ordering); Type* data = &self.data;
return @atomic_exec(atomic::fetch_add, data, value, ordering);
} }
macro Type Atomic.sub(&self, Type value, AtomicOrdering $ordering = SEQ_CONSISTENT) macro Type Atomic.sub(&self, Type value, AtomicOrdering ordering = SEQ_CONSISTENT)
{ {
return atomic::fetch_sub(&self.data, value, $ordering); Type* data = &self.data;
return @atomic_exec(atomic::fetch_sub, data, value, ordering);
} }
macro Type Atomic.mul(&self, Type value, AtomicOrdering $ordering = SEQ_CONSISTENT) macro Type Atomic.mul(&self, Type value, AtomicOrdering ordering = SEQ_CONSISTENT)
{ {
return atomic::fetch_mul(&self.data, value, $ordering); Type* data = &self.data;
return @atomic_exec(atomic::fetch_mul, data, value, ordering);
} }
macro Type Atomic.div(&self, Type value, AtomicOrdering $ordering = SEQ_CONSISTENT) macro Type Atomic.div(&self, Type value, AtomicOrdering ordering = SEQ_CONSISTENT)
{ {
return atomic::fetch_div(&self.data, value, $ordering); Type* data = &self.data;
return @atomic_exec(atomic::fetch_div, data, value, ordering);
} }
macro Type Atomic.max(&self, Type value, AtomicOrdering $ordering = SEQ_CONSISTENT) macro Type Atomic.max(&self, Type value, AtomicOrdering ordering = SEQ_CONSISTENT)
{ {
return atomic::fetch_max(&self.data, value, $ordering); Type* data = &self.data;
return @atomic_exec(atomic::fetch_max, data, value, ordering);
} }
macro Type Atomic.min(&self, Type value, AtomicOrdering $ordering = SEQ_CONSISTENT) macro Type Atomic.min(&self, Type value, AtomicOrdering ordering = SEQ_CONSISTENT)
{ {
return atomic::fetch_min(&self.data, value, $ordering); Type* data = &self.data;
return @atomic_exec(atomic::fetch_min, data, value, ordering);
} }
macro Type Atomic.or(&self, Type value, AtomicOrdering $ordering = SEQ_CONSISTENT) @if(types::flat_kind(Type) != FLOAT) macro Type Atomic.or(&self, Type value, AtomicOrdering ordering = SEQ_CONSISTENT) @if(types::flat_kind(Type) != FLOAT)
{ {
return atomic::fetch_or(&self.data, value, $ordering); Type* data = &self.data;
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) macro Type Atomic.xor(&self, Type value, AtomicOrdering ordering = SEQ_CONSISTENT) @if(types::flat_kind(Type) != FLOAT)
{ {
return atomic::fetch_xor(&self.data, value, $ordering); Type* data = &self.data;
return @atomic_exec(atomic::fetch_xor, data, value, ordering);
} }
macro Type Atomic.and(&self, Type value, AtomicOrdering $ordering = SEQ_CONSISTENT) @if(types::flat_kind(Type) != FLOAT) macro Type Atomic.and(&self, Type value, AtomicOrdering ordering = SEQ_CONSISTENT) @if(types::flat_kind(Type) != FLOAT)
{ {
return atomic::fetch_and(&self.data, value, $ordering); Type* data = &self.data;
return @atomic_exec(atomic::fetch_and, data, value, ordering);
} }
macro Type Atomic.shr(&self, Type amount, AtomicOrdering $ordering = SEQ_CONSISTENT) @if(types::flat_kind(Type) != FLOAT) macro Type Atomic.shr(&self, Type amount, AtomicOrdering ordering = SEQ_CONSISTENT) @if(types::flat_kind(Type) != FLOAT)
{ {
return atomic::fetch_shift_right(&self.data, amount, $ordering); Type* data = &self.data;
return @atomic_exec(atomic::fetch_shift_right, data, amount, ordering);
} }
macro Type Atomic.shl(&self, Type amount, AtomicOrdering $ordering = SEQ_CONSISTENT) @if(types::flat_kind(Type) != FLOAT) macro Type Atomic.shl(&self, Type amount, AtomicOrdering ordering = SEQ_CONSISTENT) @if(types::flat_kind(Type) != FLOAT)
{ {
return atomic::fetch_shift_left(&self.data, amount, $ordering); Type* data = &self.data;
return @atomic_exec(atomic::fetch_shift_left, data, amount, ordering);
} }
macro Type Atomic.set(&self, AtomicOrdering $ordering = SEQ_CONSISTENT) @if(types::flat_kind(Type) == BOOL) macro Type Atomic.set(&self, AtomicOrdering ordering = SEQ_CONSISTENT) @if(types::flat_kind(Type) == BOOL)
{ {
return atomic::flag_set(&self.data, $ordering); Type* data = &self.data;
return @atomic_exec_no_arg(atomic::flag_set, data, ordering);
} }
macro Type Atomic.clear(&self, AtomicOrdering $ordering = SEQ_CONSISTENT) @if(types::flat_kind(Type) == BOOL) macro Type Atomic.clear(&self, AtomicOrdering ordering = SEQ_CONSISTENT) @if(types::flat_kind(Type) == BOOL)
{ {
return atomic::flag_clear(&self.data, $ordering); Type* data = &self.data;
return @atomic_exec_no_arg(atomic::flag_clear, data, ordering);
}
macro @atomic_exec(#func, data, value, ordering) @local
{
switch(ordering)
{
case RELAXED: return #func(data, value, RELAXED);
case ACQUIRE: return #func(data, value, ACQUIRE);
case RELEASE: return #func(data, value, RELEASE);
case ACQUIRE_RELEASE: return #func(data, value, ACQUIRE_RELEASE);
case SEQ_CONSISTENT: return #func(data, value, SEQ_CONSISTENT);
default: unreachable("Ordering may not be non-atomic or unordered.");
}
}
macro @atomic_exec_no_arg(#func, data, ordering) @local
{
switch(ordering)
{
case RELAXED: return #func(data, RELAXED);
case ACQUIRE: return #func(data, ACQUIRE);
case RELEASE: return #func(data, RELEASE);
case ACQUIRE_RELEASE: return #func(data, ACQUIRE_RELEASE);
case SEQ_CONSISTENT: return #func(data, SEQ_CONSISTENT);
default: unreachable("Ordering may not be non-atomic or unordered.");
}
} }
module std::atomic; module std::atomic;
@@ -115,8 +175,8 @@ macro bool is_native_atomic_type($Type)
$case FLOAT: $case FLOAT:
$case BOOL: $case BOOL:
return true; return true;
$case TYPEDEF: $case DISTINCT:
$case CONSTDEF: $case CONST_ENUM:
return is_native_atomic_type($Type.inner); return is_native_atomic_type($Type.inner);
$default: $default:
return false; return false;
@@ -212,7 +272,7 @@ macro fetch_mul(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT)
@require $defined(*ptr) : "Expected a pointer" @require $defined(*ptr) : "Expected a pointer"
@require @is_native_atomic_value(*ptr) : "Only types that are native atomic may be used." @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 $defined(*ptr * y) : "/ must be defined between the values."
@require $ordering != NOT_ATOMIC && $ordering != UNORDERED : "Acquire ordering is not valid." @require $ordering != NOT_ATOMIC && $ordering != UNORDERED : "Acquire ordering is not valid."
*> *>
macro fetch_div(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT) macro fetch_div(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT)

View File

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

View File

@@ -2,10 +2,10 @@
// Use of self source code is governed by the MIT license // Use of self source code is governed by the MIT license
// a copy of which can be found in the LICENSE_STDLIB file. // a copy of which can be found in the LICENSE_STDLIB file.
module std::collections::anylist; module std::collections::anylist;
import std::collections::interfacelist; import std::io,std::math;
alias AnyPredicate = InterfacePredicate {any}; alias AnyPredicate = fn bool(any value);
alias AnyTest = InterfaceTest {any}; alias AnyTest = fn bool(any type, any context);
<* <*
The AnyList contains a heterogenous set of types. Anything placed in the 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 If we're not doing pop, then things are easier, since we can just hand over
the existing any. 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. 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 "The first element"
@return? NO_MORE_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. 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 "The last element"
@return? NO_MORE_ELEMENT @return? NO_MORE_ELEMENT
*> *>
fn any? AnyList.last_any(&self) @inline => InterfaceList {any}.last(self); fn any? AnyList.last_any(&self) @inline
<*
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~; return self.size ? self.entries[self.size - 1] : NO_MORE_ELEMENT?;
defer self.free_element(self.entries[self.size]);
return *anycast(self.entries[--self.size], $Type);
} }
<* <*
Pop a value who's type is known. If the type is incorrect, this Return whether the list is empty.
will still pop the element.
@param $Type : "The type we assume the value has" @return "True if the list is empty"
@return "The first value as the type given"
@return? TYPE_MISMATCH, NO_MORE_ELEMENT
*> *>
macro AnyList.pop_first(&self, $Type) fn bool AnyList.is_empty(&self) @inline
{ {
if (!self.size) return NO_MORE_ELEMENT~; return !self.size;
defer self.remove_at(0); }
return *anycast(self.entries[0], $Type);
<*
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 @return? TYPE_MISMATCH, NO_MORE_ELEMENT
@require index < self.size : "Index out of range" @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. Completely free and clear a list.
@return "The number of elements in the 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

@@ -4,7 +4,7 @@
<* <*
@require SIZE > 0 : "The size of the bitset in bits must be at least 1" @require SIZE > 0 : "The size of the bitset in bits must be at least 1"
*> *>
module std::collections::bitset <SIZE>; module std::collections::bitset {SIZE};
const BITS = uint.sizeof * 8; const BITS = uint.sizeof * 8;
const SZ = (SIZE + BITS - 1) / BITS; const SZ = (SIZE + BITS - 1) / BITS;
@@ -136,7 +136,6 @@ fn void BitSet.unset(&self, usz i)
@param i : "The index of the bit" @param i : "The index of the bit"
@require i < SIZE : "Index was out of range" @require i < SIZE : "Index was out of range"
@pure
*> *>
fn bool BitSet.get(&self, usz i) @operator([]) @inline 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 self.data[q] & (1 << r) != 0;
} }
<*
Return the number of bits.
@pure
*>
fn usz BitSet.len(&self) @operator(len) @inline fn usz BitSet.len(&self) @operator(len) @inline
{ {
return SZ * BITS; return SZ * BITS;
@@ -172,7 +166,7 @@ fn void BitSet.set_bool(&self, usz i, bool value) @operator([]=) @inline
<* <*
@require Type.kindof == UNSIGNED_INT @require Type.kindof == UNSIGNED_INT
*> *>
module std::collections::growablebitset <Type>; module std::collections::growablebitset{Type};
import std::collections::list; import std::collections::list;
const BITS = Type.sizeof * 8; const BITS = Type.sizeof * 8;

View File

@@ -4,14 +4,14 @@
<* <*
@require MAX_SIZE >= 1 : `The size must be at least 1 element big.` @require MAX_SIZE >= 1 : `The size must be at least 1 element big.`
*> *>
module std::collections::elastic_array <Type, MAX_SIZE>; module std::collections::elastic_array {Type, MAX_SIZE};
import std::io, std::math, std::collections::list_common; import std::io, std::math, std::collections::list_common;
alias ElementPredicate = fn bool(Type *type); alias ElementPredicate = fn bool(Type *type);
alias ElementTest = fn bool(Type *type, any context); alias ElementTest = fn bool(Type *type, any context);
const ELEMENT_IS_EQUATABLE = types::is_equatable_type(Type); const ELEMENT_IS_EQUATABLE = types::is_equatable_type(Type);
const ELEMENT_IS_POINTER = Type.kindof == POINTER; 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) struct ElasticArray (Printable)
{ {
@@ -46,7 +46,7 @@ fn String ElasticArray.to_tstring(&self)
fn void? ElasticArray.push_try(&self, Type element) @inline fn void? ElasticArray.push_try(&self, Type element) @inline
{ {
if (self.size == MAX_SIZE) return mem::OUT_OF_MEMORY~; if (self.size == MAX_SIZE) return mem::OUT_OF_MEMORY?;
self.entries[self.size++] = element; self.entries[self.size++] = element;
} }
@@ -60,7 +60,7 @@ fn void ElasticArray.push(&self, Type element) @inline
fn Type? ElasticArray.pop(&self) fn Type? ElasticArray.pop(&self)
{ {
if (!self.size) return NO_MORE_ELEMENT~; if (!self.size) return NO_MORE_ELEMENT?;
return self.entries[--self.size]; return self.entries[--self.size];
} }
@@ -74,7 +74,7 @@ fn void ElasticArray.clear(&self)
*> *>
fn Type? ElasticArray.pop_first(&self) fn Type? ElasticArray.pop_first(&self)
{ {
if (!self.size) return NO_MORE_ELEMENT~; if (!self.size) return NO_MORE_ELEMENT?;
defer self.remove_at(0); defer self.remove_at(0);
return self.entries[0]; return self.entries[0];
} }
@@ -121,24 +121,7 @@ fn usz ElasticArray.add_all_to_limit(&self, ElasticArray* other_list)
@param [in] array @param [in] array
*> *>
fn usz ElasticArray.add_array_to_limit(&self, Type[] array) @deprecated("Use push_all_to_limit") fn usz ElasticArray.add_array_to_limit(&self, Type[] array)
{
if (!array.len) return 0;
foreach (i, &value : array)
{
if (self.size == MAX_SIZE) return array.len - i;
self.entries[self.size++] = *value;
}
return 0;
}
<*
Add as many values from this array as possible, returning the
number of elements that didn't fit.
@param [in] array
*>
fn usz ElasticArray.push_all_to_limit(&self, Type[] array)
{ {
if (!array.len) return 0; if (!array.len) return 0;
foreach (i, &value : array) 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.` @require array.len + self.size <= MAX_SIZE : `Size would exceed max.`
@ensure self.size >= array.len @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; if (!array.len) return;
foreach (&value : array) 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;
}
}
<* <*
@@ -241,7 +209,7 @@ fn void? ElasticArray.push_front_try(&self, Type type) @inline
*> *>
fn void? ElasticArray.insert_at_try(&self, usz index, Type value) fn void? ElasticArray.insert_at_try(&self, usz index, Type value)
{ {
if (self.size == MAX_SIZE) return mem::OUT_OF_MEMORY~; if (self.size == MAX_SIZE) return mem::OUT_OF_MEMORY?;
self.insert_at(index, value); self.insert_at(index, value);
} }
@@ -269,25 +237,25 @@ fn void ElasticArray.set_at(&self, usz index, Type type)
fn void? ElasticArray.remove_last(&self) @maydiscard fn void? ElasticArray.remove_last(&self) @maydiscard
{ {
if (!self.size) return NO_MORE_ELEMENT~; if (!self.size) return NO_MORE_ELEMENT?;
self.size--; self.size--;
} }
fn void? ElasticArray.remove_first(&self) @maydiscard fn void? ElasticArray.remove_first(&self) @maydiscard
{ {
if (!self.size) return NO_MORE_ELEMENT~; if (!self.size) return NO_MORE_ELEMENT?;
self.remove_at(0); self.remove_at(0);
} }
fn Type? ElasticArray.first(&self) fn Type? ElasticArray.first(&self)
{ {
if (!self.size) return NO_MORE_ELEMENT~; if (!self.size) return NO_MORE_ELEMENT?;
return self.entries[0]; return self.entries[0];
} }
fn Type? ElasticArray.last(&self) fn Type? ElasticArray.last(&self)
{ {
if (!self.size) return NO_MORE_ELEMENT~; if (!self.size) return NO_MORE_ELEMENT?;
return self.entries[self.size - 1]; return self.entries[self.size - 1];
} }
@@ -368,7 +336,7 @@ fn usz? ElasticArray.index_of(&self, Type type) @if(ELEMENT_IS_EQUATABLE)
{ {
if (equals(v, type)) return i; if (equals(v, type)) return i;
} }
return NOT_FOUND~; return NOT_FOUND?;
} }
fn usz? ElasticArray.rindex_of(&self, Type type) @if(ELEMENT_IS_EQUATABLE) fn usz? ElasticArray.rindex_of(&self, Type type) @if(ELEMENT_IS_EQUATABLE)
@@ -377,7 +345,7 @@ fn usz? ElasticArray.rindex_of(&self, Type type) @if(ELEMENT_IS_EQUATABLE)
{ {
if (equals(v, type)) return i; if (equals(v, type)) return i;
} }
return NOT_FOUND~; return NOT_FOUND?;
} }
fn bool ElasticArray.equals(&self, ElasticArray other_list) @if(ELEMENT_IS_EQUATABLE) fn bool ElasticArray.equals(&self, ElasticArray other_list) @if(ELEMENT_IS_EQUATABLE)
@@ -458,4 +426,4 @@ fn usz ElasticArray.compact_count(&self) @if(ELEMENT_IS_POINTER)
fn usz ElasticArray.compact(&self) @if(ELEMENT_IS_POINTER) fn usz ElasticArray.compact(&self) @if(ELEMENT_IS_POINTER)
{ {
return list_common::list_compact(self); return list_common::list_compact(self);
} }

View File

@@ -1,8 +1,7 @@
<* <*
@require Enum.kindof == TypeKind.ENUM : "Only enums may be used with an enummap" @require Enum.kindof == TypeKind.ENUM : "Only enums may be used with an enummap"
@require Enum.values.len > 0 : "Only non-empty enums may be used with enummap"
*> *>
module std::collections::enummap <Enum, ValueType>; module std::collections::enummap{Enum, ValueType};
import std::io; import std::io;
struct EnumMap (Printable) struct EnumMap (Printable)

View File

@@ -5,7 +5,7 @@
<* <*
@require Enum.kindof == TypeKind.ENUM : "Only enums may be used with an enumset" @require Enum.kindof == TypeKind.ENUM : "Only enums may be used with an enumset"
*> *>
module std::collections::enumset <Enum>; module std::collections::enumset{Enum};
import std::io; import std::io;
const ENUM_COUNT @private = Enum.values.len; const ENUM_COUNT @private = Enum.values.len;

View File

@@ -4,7 +4,7 @@
<* <*
@require $defined((Key){}.hash()) : `No .hash function found on the key` @require $defined((Key){}.hash()) : `No .hash function found on the key`
*> *>
module std::collections::map <Key, Value>; module std::collections::map{Key, Value};
import std::math; import std::math;
import std::io @norecurse; import std::io @norecurse;
@@ -30,10 +30,8 @@ struct HashMap (Printable)
{ {
Entry*[] table; Entry*[] table;
Allocator allocator; Allocator allocator;
<* Last inserted LinkedEntry *> uint count; // Number of elements
uint count; uint threshold; // Resize limit
<* Resize limit *>
uint threshold;
float load_factor; float load_factor;
} }
@@ -150,7 +148,7 @@ fn bool HashMap.is_initialized(&map)
fn HashMap* HashMap.init_from_map(&self, Allocator allocator, HashMap* other_map) fn HashMap* HashMap.init_from_map(&self, Allocator allocator, HashMap* other_map)
{ {
self.init(allocator, other_map.table.len, other_map.load_factor); self.init(allocator, other_map.table.len, other_map.load_factor);
hashmap_put_all_for_create(self, other_map); self.put_all_for_create(other_map);
return self; return self;
} }
@@ -175,48 +173,29 @@ fn usz HashMap.len(&map) @inline
fn Value*? HashMap.get_ref(&map, Key key) fn Value*? HashMap.get_ref(&map, Key key)
{ {
if (!map.count) return NOT_FOUND~; if (!map.count) return NOT_FOUND?;
uint hash = rehash(key.hash()); uint hash = rehash(key.hash());
for (Entry *e = map.table[index_for(hash, map.table.len)]; e != null; e = e.next) 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; if (e.hash == hash && equals(key, e.key)) return &e.value;
} }
return NOT_FOUND~; 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) fn Entry*? HashMap.get_entry(&map, Key key)
{ {
if (!map.count) return NOT_FOUND~; if (!map.count) return NOT_FOUND?;
uint hash = rehash(key.hash()); uint hash = rehash(key.hash());
for (Entry *e = map.table[index_for(hash, map.table.len)]; e != null; e = e.next) 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; if (e.hash == hash && equals(key, e.key)) return e;
} }
return NOT_FOUND~; return NOT_FOUND?;
} }
<* <*
Get the value or set it to the value Get the value or update and
@require @assignable_to(#expr, Value)
@require $defined(Value val = #expr)
*> *>
macro Value HashMap.@get_or_set(&map, Key key, Value #expr) macro Value HashMap.@get_or_set(&map, Key key, Value #expr)
{ {
@@ -233,7 +212,7 @@ macro Value HashMap.@get_or_set(&map, Key key, Value #expr)
if (e.hash == hash && equals(key, e.key)) return e.value; if (e.hash == hash && equals(key, e.key)) return e.value;
} }
Value val = #expr; Value val = #expr;
hashmap_add_entry(map, hash, key, val, index); map.add_entry(hash, key, val, index);
return val; return val;
} }
@@ -269,13 +248,13 @@ fn bool HashMap.set(&map, Key key, Value value) @operator([]=)
return true; return true;
} }
} }
hashmap_add_entry(map, hash, key, value, index); map.add_entry(hash, key, value, index);
return false; return false;
} }
fn void? HashMap.remove(&map, Key key) @maydiscard fn void? HashMap.remove(&map, Key key) @maydiscard
{ {
if (!hashmap_remove_entry_for_key(map, key)) return NOT_FOUND~; if (!map.remove_entry_for_key(key)) return NOT_FOUND?;
} }
fn void HashMap.clear(&map) fn void HashMap.clear(&map)
@@ -290,9 +269,9 @@ fn void HashMap.clear(&map)
{ {
Entry *to_delete = next; Entry *to_delete = next;
next = next.next; next = next.next;
hashmap_free_entry(map, to_delete); map.free_entry(to_delete);
} }
hashmap_free_entry(map, entry); map.free_entry(entry);
*entry_ref = null; *entry_ref = null;
} }
map.count = 0; map.count = 0;
@@ -302,7 +281,7 @@ fn void HashMap.free(&map)
{ {
if (!map.is_initialized()) return; if (!map.is_initialized()) return;
map.clear(); map.clear();
hashmap_free_internal(map, map.table.ptr); map.free_internal(map.table.ptr);
map.table = {}; map.table = {};
} }
@@ -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) fn Value[] HashMap.values(&self, Allocator allocator)
{ {
@@ -402,7 +384,7 @@ fn HashMapKeyIterator HashMap.key_iter(&self)
// --- private methods // --- private methods
fn void hashmap_add_entry(HashMap* map, uint hash, Key key, Value value, uint bucket_index) @private fn void HashMap.add_entry(&map, uint hash, Key key, Value value, uint bucket_index) @private
{ {
$if COPY_KEYS: $if COPY_KEYS:
key = key.copy(map.allocator); key = key.copy(map.allocator);
@@ -411,11 +393,11 @@ fn void hashmap_add_entry(HashMap* map, uint hash, Key key, Value value, uint bu
map.table[bucket_index] = entry; map.table[bucket_index] = entry;
if (map.count++ >= map.threshold) if (map.count++ >= map.threshold)
{ {
hashmap_resize(map, map.table.len * 2); map.resize(map.table.len * 2);
} }
} }
fn void hashmap_resize(HashMap* map, uint new_capacity) @private fn void HashMap.resize(&map, uint new_capacity) @private
{ {
Entry*[] old_table = map.table; Entry*[] old_table = map.table;
uint old_capacity = old_table.len; uint old_capacity = old_table.len;
@@ -425,9 +407,9 @@ fn void hashmap_resize(HashMap* map, uint new_capacity) @private
return; return;
} }
Entry*[] new_table = allocator::new_array(map.allocator, Entry*, new_capacity); Entry*[] new_table = allocator::new_array(map.allocator, Entry*, new_capacity);
hashmap_transfer(map, new_table); map.transfer(new_table);
map.table = new_table; map.table = new_table;
hashmap_free_internal(map, old_table.ptr); map.free_internal(old_table.ptr);
map.threshold = (uint)(new_capacity * map.load_factor); map.threshold = (uint)(new_capacity * map.load_factor);
} }
@@ -443,7 +425,7 @@ fn usz? HashMap.to_format(&self, Formatter* f) @dynamic
return len + f.print(" }"); return len + f.print(" }");
} }
fn void hashmap_transfer(HashMap* map, Entry*[] new_table) @private fn void HashMap.transfer(&map, Entry*[] new_table) @private
{ {
Entry*[] src = map.table; Entry*[] src = map.table;
uint new_capacity = new_table.len; uint new_capacity = new_table.len;
@@ -462,20 +444,20 @@ fn void hashmap_transfer(HashMap* map, Entry*[] new_table) @private
} }
} }
fn void hashmap_put_all_for_create(HashMap* map, HashMap* other_map) @private fn void HashMap.put_all_for_create(&map, HashMap* other_map) @private
{ {
if (!other_map.count) return; if (!other_map.count) return;
foreach (Entry *e : other_map.table) foreach (Entry *e : other_map.table)
{ {
while (e) while (e)
{ {
hashmap_put_for_create(map, e.key, e.value); map.put_for_create(e.key, e.value);
e = e.next; e = e.next;
} }
} }
} }
fn void hashmap_put_for_create(HashMap* map, Key key, Value value) @private fn void HashMap.put_for_create(&map, Key key, Value value) @private
{ {
uint hash = rehash(key.hash()); uint hash = rehash(key.hash());
uint i = index_for(hash, map.table.len); uint i = index_for(hash, map.table.len);
@@ -487,15 +469,15 @@ fn void hashmap_put_for_create(HashMap* map, Key key, Value value) @private
return; return;
} }
} }
hashmap_create_entry(map, hash, key, value, i); map.create_entry(hash, key, value, i);
} }
fn void hashmap_free_internal(HashMap* map, void* ptr) @inline @private fn void HashMap.free_internal(&map, void* ptr) @inline @private
{ {
allocator::free(map.allocator, ptr); allocator::free(map.allocator, ptr);
} }
fn bool hashmap_remove_entry_for_key(HashMap* map, Key key) @private fn bool HashMap.remove_entry_for_key(&map, Key key) @private
{ {
if (!map.count) return false; if (!map.count) return false;
uint hash = rehash(key.hash()); uint hash = rehash(key.hash());
@@ -516,7 +498,7 @@ fn bool hashmap_remove_entry_for_key(HashMap* map, Key key) @private
{ {
prev.next = next; prev.next = next;
} }
hashmap_free_entry(map, e); map.free_entry(e);
return true; return true;
} }
prev = e; prev = e;
@@ -525,7 +507,7 @@ fn bool hashmap_remove_entry_for_key(HashMap* map, Key key) @private
return false; return false;
} }
fn void hashmap_create_entry(HashMap* map, uint hash, Key key, Value value, int bucket_index) @private fn void HashMap.create_entry(&map, uint hash, Key key, Value value, int bucket_index) @private
{ {
Entry *e = map.table[bucket_index]; Entry *e = map.table[bucket_index];
$if COPY_KEYS: $if COPY_KEYS:
@@ -536,12 +518,12 @@ fn void hashmap_create_entry(HashMap* map, uint hash, Key key, Value value, int
map.count++; map.count++;
} }
fn void hashmap_free_entry(HashMap* map, Entry *entry) @local fn void HashMap.free_entry(&self, Entry *entry) @local
{ {
$if COPY_KEYS: $if COPY_KEYS:
allocator::free(map.allocator, entry.key); allocator::free(self.allocator, entry.key);
$endif $endif
hashmap_free_internal(map, entry); self.free_internal(entry);
} }
@@ -607,4 +589,4 @@ macro uint index_for(uint hash, uint capacity) @private
return hash & (capacity - 1); return hash & (capacity - 1);
} }
int dummy @local; int dummy @local;

View File

@@ -1,7 +1,7 @@
<* <*
@require $defined((Value){}.hash()) : `No .hash function found on the value` @require $defined((Value){}.hash()) : `No .hash function found on the value`
*> *>
module std::collections::set <Value>; module std::collections::set {Value};
import std::math; import std::math;
import std::io @norecurse; import std::io @norecurse;
@@ -14,25 +14,23 @@ const Allocator SET_HEAP_ALLOCATOR = (Allocator)&dummy;
<* Copy the ONHEAP allocator to initialize to a set that is heap allocated *> <* Copy the ONHEAP allocator to initialize to a set that is heap allocated *>
const HashSet ONHEAP = { .allocator = SET_HEAP_ALLOCATOR }; const HashSet ONHEAP = { .allocator = SET_HEAP_ALLOCATOR };
struct Entry struct Entry
{ {
uint hash; uint hash;
Value value; Value value;
Entry* next; Entry* next;
} }
struct HashSet (Printable) struct HashSet (Printable)
{ {
Entry*[] table; Entry*[] table;
Allocator allocator; Allocator allocator;
<* Number of elements *> usz count; // Number of elements
usz count; usz threshold; // Resize limit
<* Resize limit *>
usz threshold;
float load_factor; float load_factor;
} }
fn usz HashSet.len(&self) @operator(len) => self.count; fn int HashSet.len(&self) @operator(len) => (int) self.count;
<* <*
@param [&inout] allocator : "The allocator to use" @param [&inout] allocator : "The allocator to use"
@@ -41,7 +39,7 @@ fn usz HashSet.len(&self) @operator(len) => self.count;
@require !self.is_initialized() : "Set was already initialized" @require !self.is_initialized() : "Set was already initialized"
@require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum" @require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum"
*> *>
fn HashSet* HashSet.init(&self, Allocator allocator, usz capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR) fn HashSet* HashSet.init(&self, Allocator allocator, usz capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
{ {
capacity = math::next_power_of_2(capacity); capacity = math::next_power_of_2(capacity);
self.allocator = allocator; self.allocator = allocator;
@@ -135,7 +133,7 @@ fn bool HashSet.is_initialized(&set)
fn HashSet* HashSet.init_from_set(&self, Allocator allocator, HashSet* other_set) fn HashSet* HashSet.init_from_set(&self, Allocator allocator, HashSet* other_set)
{ {
self.init(allocator, other_set.table.len, other_set.load_factor); self.init(allocator, other_set.table.len, other_set.load_factor);
hashset_put_all_for_create(self, other_set); self.put_all_for_create(other_set);
return self; return self;
} }
@@ -213,7 +211,7 @@ fn bool HashSet.add(&set, Value value)
{ {
if (e.hash == hash && equals(value, e.value)) return false; if (e.hash == hash && equals(value, e.value)) return false;
} }
hashset_add_entry(set, hash, value, index); set.add_entry(hash, value, index);
return true; return true;
} }
@@ -258,7 +256,7 @@ fn bool HashSet.contains(&set, Value value)
*> *>
fn void? HashSet.remove(&set, Value value) @maydiscard fn void? HashSet.remove(&set, Value value) @maydiscard
{ {
if (!hashset_remove_entry_for_value(set, value)) return NOT_FOUND~; if (!set.remove_entry_for_value(value)) return NOT_FOUND?;
} }
fn usz HashSet.remove_all(&set, Value[] values) fn usz HashSet.remove_all(&set, Value[] values)
@@ -266,7 +264,7 @@ fn usz HashSet.remove_all(&set, Value[] values)
usz total; usz total;
foreach (v : values) foreach (v : values)
{ {
if (hashset_remove_entry_for_value(set, v)) total++; if (set.remove_entry_for_value(v)) total++;
} }
return total; return total;
} }
@@ -279,7 +277,7 @@ fn usz HashSet.remove_all_from(&set, HashSet* other)
usz total; usz total;
other.@each(;Value val) other.@each(;Value val)
{ {
if (hashset_remove_entry_for_value(set, val)) total++; if (set.remove_entry_for_value(val)) total++;
}; };
return total; return total;
} }
@@ -287,11 +285,11 @@ fn usz HashSet.remove_all_from(&set, HashSet* other)
<* <*
Free all memory allocated by the hash set. Free all memory allocated by the hash set.
*> *>
fn void HashSet.free(&set) fn void HashSet.free(&set)
{ {
if (!set.is_initialized()) return; if (!set.is_initialized()) return;
set.clear(); set.clear();
hashset_free_internal(set, set.table.ptr); set.free_internal(set.table.ptr);
*set = {}; *set = {};
} }
@@ -314,10 +312,10 @@ fn void HashSet.clear(&set)
{ {
Entry *to_delete = next; Entry *to_delete = next;
next = next.next; next = next.next;
hashset_free_entry(set, to_delete); set.free_entry(to_delete);
} }
hashset_free_entry(set, entry); set.free_entry(entry);
*entry_ref = null; *entry_ref = null;
} }
set.count = 0; set.count = 0;
@@ -327,27 +325,11 @@ fn void HashSet.reserve(&set, usz capacity)
{ {
if (capacity > set.threshold) if (capacity > set.threshold)
{ {
hashset_resize(set, math::next_power_of_2(capacity)); 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 --- // --- Set Operations ---
@@ -464,17 +446,17 @@ fn bool HashSet.is_subset(&self, HashSet* other)
// --- private methods // --- private methods
fn void hashset_add_entry(HashSet* set, uint hash, Value value, uint bucket_index) @private 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] }); Entry* entry = allocator::new(set.allocator, Entry, { .hash = hash, .value = value, .next = set.table[bucket_index] });
set.table[bucket_index] = entry; set.table[bucket_index] = entry;
if (set.count++ >= set.threshold) if (set.count++ >= set.threshold)
{ {
hashset_resize(set, set.table.len * 2); set.resize(set.table.len * 2);
} }
} }
fn void hashset_resize(HashSet* self, usz new_capacity) @private fn void HashSet.resize(&self, usz new_capacity) @private
{ {
Entry*[] old_table = self.table; Entry*[] old_table = self.table;
usz old_capacity = old_table.len; usz old_capacity = old_table.len;
@@ -484,9 +466,9 @@ fn void hashset_resize(HashSet* self, usz new_capacity) @private
return; return;
} }
Entry*[] new_table = allocator::new_array(self.allocator, Entry*, new_capacity); Entry*[] new_table = allocator::new_array(self.allocator, Entry*, new_capacity);
hashset_transfer(self, new_table); self.transfer(new_table);
self.table = new_table; self.table = new_table;
hashset_free_internal(self, old_table.ptr); self.free_internal(old_table.ptr);
self.threshold = (uint)(new_capacity * self.load_factor); self.threshold = (uint)(new_capacity * self.load_factor);
} }
@@ -502,7 +484,7 @@ fn usz? HashSet.to_format(&self, Formatter* f) @dynamic
return len + f.print(" }"); return len + f.print(" }");
} }
fn void hashset_transfer(HashSet* self, Entry*[] new_table) @private fn void HashSet.transfer(&self, Entry*[] new_table) @private
{ {
Entry*[] src = self.table; Entry*[] src = self.table;
uint new_capacity = new_table.len; uint new_capacity = new_table.len;
@@ -521,20 +503,20 @@ fn void hashset_transfer(HashSet* self, Entry*[] new_table) @private
} }
} }
fn void hashset_put_all_for_create(HashSet* set, HashSet* other_set) @private fn void HashSet.put_all_for_create(&set, HashSet* other_set) @private
{ {
if (!other_set.count) return; if (!other_set.count) return;
foreach (Entry *e : other_set.table) foreach (Entry *e : other_set.table)
{ {
while (e) while (e)
{ {
hashset_put_for_create(set, e.value); set.put_for_create(e.value);
e = e.next; e = e.next;
} }
} }
} }
fn void hashset_put_for_create(HashSet* set, Value value) @private fn void HashSet.put_for_create(&set, Value value) @private
{ {
uint hash = rehash(value.hash()); uint hash = rehash(value.hash());
uint i = index_for(hash, set.table.len); uint i = index_for(hash, set.table.len);
@@ -546,15 +528,15 @@ fn void hashset_put_for_create(HashSet* set, Value value) @private
return; return;
} }
} }
hashset_create_entry(set, hash, value, i); set.create_entry(hash, value, i);
} }
fn void hashset_free_internal(HashSet* self, void* ptr) @inline @private fn void HashSet.free_internal(&self, void* ptr) @inline @private
{ {
allocator::free(self.allocator, ptr); allocator::free(self.allocator, ptr);
} }
fn void hashset_create_entry(HashSet* set, uint hash, Value value, int bucket_index) @private fn void HashSet.create_entry(&set, uint hash, Value value, int bucket_index) @private
{ {
Entry* entry = allocator::new(set.allocator, Entry, { Entry* entry = allocator::new(set.allocator, Entry, {
.hash = hash, .hash = hash,
@@ -569,7 +551,7 @@ fn void hashset_create_entry(HashSet* set, uint hash, Value value, int bucket_in
Removes the entry for the specified value if present Removes the entry for the specified value if present
@return "true if found and removed, false otherwise" @return "true if found and removed, false otherwise"
*> *>
fn bool hashset_remove_entry_for_value(HashSet* set, Value value) @private fn bool HashSet.remove_entry_for_value(&set, Value value) @private
{ {
if (!set.count) return false; if (!set.count) return false;
uint hash = rehash(value.hash()); uint hash = rehash(value.hash());
@@ -590,7 +572,7 @@ fn bool hashset_remove_entry_for_value(HashSet* set, Value value) @private
{ {
prev.next = next; prev.next = next;
} }
hashset_free_entry(set, e); set.free_entry(e);
return true; return true;
} }
prev = e; prev = e;
@@ -600,7 +582,7 @@ fn bool hashset_remove_entry_for_value(HashSet* set, Value value) @private
return false; return false;
} }
fn void hashset_free_entry(HashSet* set, Entry *entry) @private fn void HashSet.free_entry(&set, Entry *entry) @private
{ {
allocator::free(set.allocator, entry); allocator::free(set.allocator, entry);
} }
@@ -634,7 +616,7 @@ fn Value? HashSetIterator.next(&self)
} }
} }
return NOT_FOUND~; return NOT_FOUND?;
} }
fn usz HashSetIterator.len(&self) @operator(len) fn usz HashSetIterator.len(&self) @operator(len)

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;
interfacelist_append(self, 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 interfacelist_remove_if(self, 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 interfacelist_remove_if(self, 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 interfacelist_remove_using_test(self, 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 interfacelist_remove_using_test(self, 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(InterfaceList* 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(InterfaceList* self, Type element) @local
{
interfacelist_ensure_capacity(self);
self.entries[self.size++] = element;
}
<*
@require index < self.size
*>
fn void interfacelist_insert_at(InterfaceList* self, usz index, Type value) @local
{
interfacelist_ensure_capacity(self);
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(InterfaceList* 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(InterfaceList* 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,4 +1,4 @@
module std::collections::blockingqueue <Value>; module std::collections::blockingqueue { Value };
import std::thread, std::time; import std::thread, std::time;
@@ -7,22 +7,16 @@ const INITIAL_CAPACITY = 16;
struct QueueEntry struct QueueEntry
{ {
Value value; Value value;
<* Next in queue order *> QueueEntry* next; // Next in queue order
QueueEntry* next; QueueEntry* prev; // Previous in queue order
<* Previous in queue order *>
QueueEntry* prev;
} }
struct LinkedBlockingQueue struct LinkedBlockingQueue
{ {
<* First element in queue *> QueueEntry* head; // First element in queue
QueueEntry* head; QueueEntry* tail; // Last element in queue
<* Last element in queue *> usz count; // Current number of elements
QueueEntry* tail; usz capacity; // Maximum capacity (0 for unbounded)
<* Current number of elements *>
usz count;
<* Maximum capacity (0 for unbounded) *>
usz capacity;
Mutex lock; Mutex lock;
ConditionVariable not_empty; ConditionVariable not_empty;
ConditionVariable not_full; ConditionVariable not_full;
@@ -70,12 +64,12 @@ fn void LinkedBlockingQueue.free(&self)
} }
}; };
self.lock.destroy(); (void)self.lock.destroy();
self.not_empty.destroy(); (void)self.not_empty.destroy();
self.not_full.destroy(); (void)self.not_full.destroy();
} }
fn void linkedblockingqueue_link_entry(LinkedBlockingQueue* self, QueueEntry* entry) @private fn void LinkedBlockingQueue.link_entry(&self, QueueEntry* entry) @private
{ {
entry.next = null; entry.next = null;
entry.prev = self.tail; entry.prev = self.tail;
@@ -95,7 +89,7 @@ fn void linkedblockingqueue_link_entry(LinkedBlockingQueue* self, QueueEntry* en
} }
fn QueueEntry* linkedblockingqueue_unlink_head(LinkedBlockingQueue* self) @private fn QueueEntry* LinkedBlockingQueue.unlink_head(&self) @private
{ {
if (self.head == null) return null; if (self.head == null) return null;
@@ -126,7 +120,7 @@ fn void LinkedBlockingQueue.push(&self, Value value)
{ {
while (self.capacity > 0 && self.count >= self.capacity) while (self.capacity > 0 && self.count >= self.capacity)
{ {
self.not_full.wait(&self.lock); self.not_full.wait(&self.lock)!!;
} }
QueueEntry* entry = allocator::new(self.allocator, QueueEntry, { QueueEntry* entry = allocator::new(self.allocator, QueueEntry, {
@@ -134,10 +128,10 @@ fn void LinkedBlockingQueue.push(&self, Value value)
.next = null, .next = null,
.prev = null .prev = null
}); });
linkedblockingqueue_link_entry(self, entry); self.link_entry(entry);
// Signal that queue is no longer empty // Signal that queue is no longer empty
self.not_empty.signal(); self.not_empty.signal()!!;
}; };
} }
@@ -153,15 +147,15 @@ fn Value LinkedBlockingQueue.poll(&self)
{ {
while (self.count == 0) while (self.count == 0)
{ {
self.not_empty.wait(&self.lock); self.not_empty.wait(&self.lock)!!;
} }
QueueEntry* entry = linkedblockingqueue_unlink_head(self); QueueEntry* entry = self.unlink_head();
Value value = entry.value; Value value = entry.value;
allocator::free(self.allocator, entry); allocator::free(self.allocator, entry);
if (self.capacity > 0) if (self.capacity > 0)
{ {
self.not_full.signal(); self.not_full.signal()!!;
} }
return value; return value;
}; };
@@ -178,15 +172,15 @@ fn Value? LinkedBlockingQueue.pop(&self)
{ {
self.lock.@in_lock() self.lock.@in_lock()
{ {
if (self.count == 0) return NO_MORE_ELEMENT~; if (self.count == 0) return NO_MORE_ELEMENT?;
QueueEntry* entry = linkedblockingqueue_unlink_head(self); QueueEntry* entry = self.unlink_head();
Value value = entry.value; Value value = entry.value;
allocator::free(self.allocator, entry); allocator::free(self.allocator, entry);
if (self.capacity > 0) if (self.capacity > 0)
{ {
self.not_full.signal(); self.not_full.signal()!!;
} }
return value; return value;
}; };
@@ -214,17 +208,17 @@ fn Value? LinkedBlockingQueue.poll_timeout(&self, Duration timeout)
if (end <= time::now()) break; if (end <= time::now()) break;
if (catch self.not_empty.wait_until(&self.lock, end)) break; if (catch self.not_empty.wait_until(&self.lock, end)) break;
} }
if (!self.count) return NO_MORE_ELEMENT~; if (!self.count) return NO_MORE_ELEMENT?;
} }
QueueEntry* entry = linkedblockingqueue_unlink_head(self); QueueEntry* entry = self.unlink_head();
Value value = entry.value; Value value = entry.value;
allocator::free(self.allocator, entry); allocator::free(self.allocator, entry);
// Must signal not_full after removing an item // Must signal not_full after removing an item
if (self.capacity > 0) if (self.capacity > 0)
{ {
self.not_full.signal(); self.not_full.signal()!!;
} }
return value; return value;
}; };
@@ -266,15 +260,15 @@ fn void? LinkedBlockingQueue.try_push(&self, Value value)
{ {
self.lock.@in_lock() self.lock.@in_lock()
{ {
if (self.capacity > 0 && self.count >= self.capacity) return CAPACITY_EXCEEDED~; if (self.capacity > 0 && self.count >= self.capacity) return CAPACITY_EXCEEDED?;
QueueEntry* entry = allocator::new(self.allocator, QueueEntry, { QueueEntry* entry = allocator::new(self.allocator, QueueEntry, {
.value = value, .value = value,
.next = null, .next = null,
.prev = null .prev = null
}); });
linkedblockingqueue_link_entry(self, entry); self.link_entry(entry);
self.not_empty.signal(); self.not_empty.signal()!!;
}; };
} }
@@ -299,7 +293,7 @@ fn void? LinkedBlockingQueue.push_timeout(&self, Value value, Duration timeout)
if (end <= time::now()) break; if (end <= time::now()) break;
if (catch self.not_empty.wait_until(&self.lock, end)) break; if (catch self.not_empty.wait_until(&self.lock, end)) break;
} }
if (self.capacity > 0 && self.count >= self.capacity) return CAPACITY_EXCEEDED~; if (self.capacity > 0 && self.count >= self.capacity) return CAPACITY_EXCEEDED?;
} }
QueueEntry* entry = allocator::new(self.allocator, QueueEntry, { QueueEntry* entry = allocator::new(self.allocator, QueueEntry, {
@@ -307,20 +301,20 @@ fn void? LinkedBlockingQueue.push_timeout(&self, Value value, Duration timeout)
.next = null, .next = null,
.prev = null .prev = null
}); });
linkedblockingqueue_link_entry(self, entry); self.link_entry(entry);
self.not_empty.signal(); self.not_empty.signal()!!;
}; };
} }
<* <*
@require self.is_initialized() : "Queue must be initialized" @require self.is_initialized() : "Queue must be initialized"
@return "The head value or NO_MORE_ELEMENT~ if queue is empty" @return "The head value or NO_MORE_ELEMENT? if queue is empty"
*> *>
fn Value? LinkedBlockingQueue.peek(&self) fn Value? LinkedBlockingQueue.peek(&self)
{ {
self.lock.@in_lock() self.lock.@in_lock()
{ {
return (self.head != null) ? self.head.value : NO_MORE_ELEMENT~; return (self.head != null) ? self.head.value : NO_MORE_ELEMENT?;
}; };
} }

View File

@@ -4,7 +4,7 @@
<* <*
@require $defined((Key){}.hash()) : `No .hash function found on the key` @require $defined((Key){}.hash()) : `No .hash function found on the key`
*> *>
module std::collections::map <Key, Value>; module std::collections::map{Key, Value};
import std::math; import std::math;
import std::io @norecurse; import std::io @norecurse;
@@ -15,12 +15,9 @@ struct LinkedEntry
uint hash; uint hash;
Key key; Key key;
Value value; Value value;
<* For bucket chain *> LinkedEntry* next; // For bucket chain
LinkedEntry* next; LinkedEntry* before; // Previous in insertion order
<* Previous in insertion order *> LinkedEntry* after; // Next in insertion order
LinkedEntry* before;
<* Next in insertion order *>
LinkedEntry* after;
} }
struct LinkedHashMap (Printable) struct LinkedHashMap (Printable)
@@ -30,10 +27,8 @@ struct LinkedHashMap (Printable)
usz count; usz count;
usz threshold; usz threshold;
float load_factor; float load_factor;
<* First inserted LinkedEntry *> LinkedEntry* head; // First inserted LinkedEntry
LinkedEntry* head; LinkedEntry* tail; // Last inserted LinkedEntry
<* Last inserted LinkedEntry *>
LinkedEntry* tail;
} }
@@ -150,7 +145,7 @@ fn bool LinkedHashMap.is_initialized(&map)
fn LinkedHashMap* LinkedHashMap.init_from_map(&self, Allocator allocator, LinkedHashMap* other_map) fn LinkedHashMap* LinkedHashMap.init_from_map(&self, Allocator allocator, LinkedHashMap* other_map)
{ {
self.init(allocator, other_map.table.len, other_map.load_factor); self.init(allocator, other_map.table.len, other_map.load_factor);
linkedhashmap_put_all_for_create(self, other_map); self.put_all_for_create(other_map);
return self; return self;
} }
@@ -172,30 +167,29 @@ fn usz LinkedHashMap.len(&map) @inline => map.count;
fn Value*? LinkedHashMap.get_ref(&map, Key key) fn Value*? LinkedHashMap.get_ref(&map, Key key)
{ {
if (!map.count) return NOT_FOUND~; if (!map.count) return NOT_FOUND?;
uint hash = rehash(key.hash()); uint hash = rehash(key.hash());
for (LinkedEntry *e = map.table[index_for(hash, map.table.len)]; e != null; e = e.next) 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; if (e.hash == hash && equals(key, e.key)) return &e.value;
} }
return NOT_FOUND~; return NOT_FOUND?;
} }
fn LinkedEntry*? LinkedHashMap.get_entry(&map, Key key) fn LinkedEntry*? LinkedHashMap.get_entry(&map, Key key)
{ {
if (!map.count) return NOT_FOUND~; if (!map.count) return NOT_FOUND?;
uint hash = rehash(key.hash()); uint hash = rehash(key.hash());
for (LinkedEntry *e = map.table[index_for(hash, map.table.len)]; e != null; e = e.next) 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; if (e.hash == hash && equals(key, e.key)) return e;
} }
return NOT_FOUND~; return NOT_FOUND?;
} }
<* <*
Get the value or set it to the value Get the value or update and
@require @assignable_to(#expr, Value)
@require $defined(Value val = #expr)
*> *>
macro Value LinkedHashMap.@get_or_set(&map, Key key, Value #expr) macro Value LinkedHashMap.@get_or_set(&map, Key key, Value #expr)
{ {
@@ -242,32 +236,32 @@ fn bool LinkedHashMap.set(&map, Key key, Value value) @operator([]=)
return true; return true;
} }
} }
linkedhashmap_add_entry(map, hash, key, value, index); map.add_entry(hash, key, value, index);
return false; return false;
} }
fn void? LinkedHashMap.remove(&map, Key key) @maydiscard fn void? LinkedHashMap.remove(&map, Key key) @maydiscard
{ {
if (!linkedhashmap_remove_entry_for_key(map, key)) return NOT_FOUND~; if (!map.remove_entry_for_key(key)) return NOT_FOUND?;
} }
fn void LinkedHashMap.clear(&map) fn void LinkedHashMap.clear(&map)
{ {
if (!map.count) return; if (!map.count) return;
LinkedEntry* entry = map.head; LinkedEntry* entry = map.head;
while (entry) while (entry)
{ {
LinkedEntry* next = entry.after; LinkedEntry* next = entry.after;
linkedhashmap_free_entry(map, entry); map.free_entry(entry);
entry = next; entry = next;
} }
foreach (LinkedEntry** &bucket : map.table) foreach (LinkedEntry** &bucket : map.table)
{ {
*bucket = null; *bucket = null;
} }
map.count = 0; map.count = 0;
map.head = null; map.head = null;
map.tail = null; map.tail = null;
@@ -277,7 +271,7 @@ fn void LinkedHashMap.free(&map)
{ {
if (!map.is_initialized()) return; if (!map.is_initialized()) return;
map.clear(); map.clear();
linkedhashmap_free_internal(map, map.table.ptr); map.free_internal(map.table.ptr);
map.table = {}; map.table = {};
} }
@@ -289,10 +283,10 @@ fn Key[] LinkedHashMap.tkeys(&self)
fn Key[] LinkedHashMap.keys(&self, Allocator allocator) fn Key[] LinkedHashMap.keys(&self, Allocator allocator)
{ {
if (!self.count) return {}; if (!self.count) return {};
Key[] list = allocator::alloc_array(allocator, Key, self.count); Key[] list = allocator::alloc_array(allocator, Key, self.count);
usz index = 0; usz index = 0;
LinkedEntry* entry = self.head; LinkedEntry* entry = self.head;
while (entry) while (entry)
{ {
@@ -343,7 +337,7 @@ fn Value[] LinkedHashMap.values(&self, Allocator allocator)
fn bool LinkedHashMap.has_value(&map, Value v) @if(VALUE_IS_EQUATABLE) fn bool LinkedHashMap.has_value(&map, Value v) @if(VALUE_IS_EQUATABLE)
{ {
if (!map.count) return false; if (!map.count) return false;
LinkedEntry* entry = map.head; LinkedEntry* entry = map.head;
while (entry) while (entry)
{ {
@@ -375,17 +369,17 @@ fn bool LinkedHashMapIterator.next(&self)
fn LinkedEntry*? LinkedHashMapIterator.get(&self) fn LinkedEntry*? LinkedHashMapIterator.get(&self)
{ {
return self.current ? self.current : NOT_FOUND~; return self.current ? self.current : NOT_FOUND?;
} }
fn Value*? LinkedHashMapValueIterator.get(&self) fn Value*? LinkedHashMapValueIterator.get(&self)
{ {
return self.current ? &self.current.value : NOT_FOUND~; return self.current ? &self.current.value : NOT_FOUND?;
} }
fn Key*? LinkedHashMapKeyIterator.get(&self) fn Key*? LinkedHashMapKeyIterator.get(&self)
{ {
return self.current ? &self.current.key : NOT_FOUND~; return self.current ? &self.current.key : NOT_FOUND?;
} }
fn bool LinkedHashMapIterator.has_next(&self) fn bool LinkedHashMapIterator.has_next(&self)
@@ -396,12 +390,12 @@ fn bool LinkedHashMapIterator.has_next(&self)
// --- private methods // --- private methods
fn void linkedhashmap_add_entry(LinkedHashMap* map, uint hash, Key key, Value value, uint bucket_index) @private fn void LinkedHashMap.add_entry(&map, uint hash, Key key, Value value, uint bucket_index) @private
{ {
$if COPY_KEYS: $if COPY_KEYS:
key = key.copy(map.allocator); key = key.copy(map.allocator);
$endif $endif
LinkedEntry* entry = allocator::new(map.allocator, LinkedEntry, { LinkedEntry* entry = allocator::new(map.allocator, LinkedEntry, {
.hash = hash, .hash = hash,
.key = key, .key = key,
@@ -410,10 +404,10 @@ fn void linkedhashmap_add_entry(LinkedHashMap* map, uint hash, Key key, Value va
.before = map.tail, .before = map.tail,
.after = null .after = null
}); });
// Update bucket chain // Update bucket chain
map.table[bucket_index] = entry; map.table[bucket_index] = entry;
// Update linked list // Update linked list
if (map.tail) if (map.tail)
{ {
@@ -425,39 +419,39 @@ fn void linkedhashmap_add_entry(LinkedHashMap* map, uint hash, Key key, Value va
map.head = entry; map.head = entry;
} }
map.tail = entry; map.tail = entry;
if (map.count++ >= map.threshold) if (map.count++ >= map.threshold)
{ {
linkedhashmap_resize(map, map.table.len * 2); map.resize(map.table.len * 2);
} }
} }
fn void linkedhashmap_resize(LinkedHashMap* map, uint new_capacity) @private fn void LinkedHashMap.resize(&map, uint new_capacity) @private
{ {
LinkedEntry*[] old_table = map.table; LinkedEntry*[] old_table = map.table;
uint old_capacity = old_table.len; uint old_capacity = old_table.len;
if (old_capacity == MAXIMUM_CAPACITY) if (old_capacity == MAXIMUM_CAPACITY)
{ {
map.threshold = uint.max; map.threshold = uint.max;
return; return;
} }
LinkedEntry*[] new_table = allocator::new_array(map.allocator, LinkedEntry*, new_capacity); LinkedEntry*[] new_table = allocator::new_array(map.allocator, LinkedEntry*, new_capacity);
map.table = new_table; map.table = new_table;
map.threshold = (uint)(new_capacity * map.load_factor); map.threshold = (uint)(new_capacity * map.load_factor);
// Rehash all entries - linked list order remains unchanged // Rehash all entries - linked list order remains unchanged
foreach (uint i, LinkedEntry *e : old_table) foreach (uint i, LinkedEntry *e : old_table)
{ {
if (!e) continue; if (!e) continue;
// Split the bucket chain into two chains based on new bit // Split the bucket chain into two chains based on new bit
LinkedEntry* lo_head = null; LinkedEntry* lo_head = null;
LinkedEntry* lo_tail = null; LinkedEntry* lo_tail = null;
LinkedEntry* hi_head = null; LinkedEntry* hi_head = null;
LinkedEntry* hi_tail = null; LinkedEntry* hi_tail = null;
do do
{ {
LinkedEntry* next = e.next; LinkedEntry* next = e.next;
@@ -489,7 +483,7 @@ fn void linkedhashmap_resize(LinkedHashMap* map, uint new_capacity) @private
e = next; e = next;
} }
while (e); while (e);
if (lo_tail) if (lo_tail)
{ {
lo_tail.next = null; lo_tail.next = null;
@@ -501,8 +495,8 @@ fn void linkedhashmap_resize(LinkedHashMap* map, uint new_capacity) @private
new_table[i + old_capacity] = hi_head; new_table[i + old_capacity] = hi_head;
} }
} }
linkedhashmap_free_internal(map, old_table.ptr); map.free_internal(old_table.ptr);
} }
fn usz? LinkedHashMap.to_format(&self, Formatter* f) @dynamic fn usz? LinkedHashMap.to_format(&self, Formatter* f) @dynamic
@@ -517,7 +511,7 @@ fn usz? LinkedHashMap.to_format(&self, Formatter* f) @dynamic
return len + f.print(" }"); return len + f.print(" }");
} }
fn void linkedhashmap_transfer(LinkedHashMap* map, LinkedEntry*[] new_table) @private fn void LinkedHashMap.transfer(&map, LinkedEntry*[] new_table) @private
{ {
LinkedEntry*[] src = map.table; LinkedEntry*[] src = map.table;
uint new_capacity = new_table.len; uint new_capacity = new_table.len;
@@ -536,7 +530,7 @@ fn void linkedhashmap_transfer(LinkedHashMap* map, LinkedEntry*[] new_table) @pr
} }
} }
fn void linkedhashmap_put_all_for_create(LinkedHashMap* map, LinkedHashMap* other_map) @private fn void LinkedHashMap.put_all_for_create(&map, LinkedHashMap* other_map) @private
{ {
if (!other_map.count) return; if (!other_map.count) return;
other_map.@each(; Key key, Value value) { other_map.@each(; Key key, Value value) {
@@ -544,7 +538,7 @@ fn void linkedhashmap_put_all_for_create(LinkedHashMap* map, LinkedHashMap* othe
}; };
} }
fn void linkedhashmap_put_for_create(LinkedHashMap* map, Key key, Value value) @private fn void LinkedHashMap.put_for_create(&map, Key key, Value value) @private
{ {
uint hash = rehash(key.hash()); uint hash = rehash(key.hash());
uint i = index_for(hash, map.table.len); uint i = index_for(hash, map.table.len);
@@ -556,23 +550,23 @@ fn void linkedhashmap_put_for_create(LinkedHashMap* map, Key key, Value value) @
return; return;
} }
} }
linkedhashmap_create_entry(map, hash, key, value, i); map.create_entry(hash, key, value, i);
} }
fn void linkedhashmap_free_internal(LinkedHashMap* map, void* ptr) @inline @private fn void LinkedHashMap.free_internal(&map, void* ptr) @inline @private
{ {
allocator::free(map.allocator, ptr); allocator::free(map.allocator, ptr);
} }
fn bool linkedhashmap_remove_entry_for_key(LinkedHashMap* map, Key key) @private fn bool LinkedHashMap.remove_entry_for_key(&map, Key key) @private
{ {
if (!map.count) return false; if (!map.count) return false;
uint hash = rehash(key.hash()); uint hash = rehash(key.hash());
uint i = index_for(hash, map.table.len); uint i = index_for(hash, map.table.len);
LinkedEntry* prev = null; LinkedEntry* prev = null;
LinkedEntry* e = map.table[i]; LinkedEntry* e = map.table[i];
while (e) while (e)
{ {
if (e.hash == hash && equals(key, e.key)) if (e.hash == hash && equals(key, e.key))
@@ -585,7 +579,7 @@ fn bool linkedhashmap_remove_entry_for_key(LinkedHashMap* map, Key key) @private
{ {
map.table[i] = e.next; map.table[i] = e.next;
} }
if (e.before) if (e.before)
{ {
e.before.after = e.after; e.before.after = e.after;
@@ -594,7 +588,7 @@ fn bool linkedhashmap_remove_entry_for_key(LinkedHashMap* map, Key key) @private
{ {
map.head = e.after; map.head = e.after;
} }
if (e.after) if (e.after)
{ {
e.after.before = e.before; e.after.before = e.before;
@@ -603,9 +597,9 @@ fn bool linkedhashmap_remove_entry_for_key(LinkedHashMap* map, Key key) @private
{ {
map.tail = e.before; map.tail = e.before;
} }
map.count--; map.count--;
linkedhashmap_free_entry(map, e); map.free_entry(e);
return true; return true;
} }
prev = e; prev = e;
@@ -614,23 +608,23 @@ fn bool linkedhashmap_remove_entry_for_key(LinkedHashMap* map, Key key) @private
return false; return false;
} }
fn void linkedhashmap_create_entry(LinkedHashMap* map, uint hash, Key key, Value value, int bucket_index) @private fn void LinkedHashMap.create_entry(&map, uint hash, Key key, Value value, int bucket_index) @private
{ {
LinkedEntry *e = map.table[bucket_index]; LinkedEntry *e = map.table[bucket_index];
$if COPY_KEYS: $if COPY_KEYS:
key = key.copy(map.allocator); key = key.copy(map.allocator);
$endif $endif
LinkedEntry* entry = allocator::new(map.allocator, LinkedEntry, { .hash = hash, .key = key, .value = value, .next = map.table[bucket_index] }); LinkedEntry* entry = allocator::new(map.allocator, LinkedEntry, { .hash = hash, .key = key, .value = value, .next = map.table[bucket_index] });
map.table[bucket_index] = entry; map.table[bucket_index] = entry;
map.count++; map.count++;
} }
fn void linkedhashmap_free_entry(LinkedHashMap* self, LinkedEntry *entry) @local fn void LinkedHashMap.free_entry(&self, LinkedEntry *entry) @local
{ {
$if COPY_KEYS: $if COPY_KEYS:
allocator::free(self.allocator, entry.key); allocator::free(self.allocator, entry.key);
$endif $endif
linkedhashmap_free_internal(self, entry); self.free_internal(entry);
} }

View File

@@ -1,7 +1,7 @@
<* <*
@require $defined((Value){}.hash()) : `No .hash function found on the value` @require $defined((Value){}.hash()) : `No .hash function found on the value`
*> *>
module std::collections::set <Value>; module std::collections::set {Value};
import std::math; import std::math;
import std::io @norecurse; import std::io @norecurse;
@@ -11,30 +11,23 @@ struct LinkedEntry
{ {
uint hash; uint hash;
Value value; Value value;
<* For bucket chain *> LinkedEntry* next; // For bucket chain
LinkedEntry* next; LinkedEntry* before; // Previous in insertion order
<* Previous in insertion order *> LinkedEntry* after; // Next in insertion order
LinkedEntry* before;
<* Next in insertion order *>
LinkedEntry* after;
} }
struct LinkedHashSet (Printable) struct LinkedHashSet (Printable)
{ {
LinkedEntry*[] table; LinkedEntry*[] table;
Allocator allocator; Allocator allocator;
<* Number of elements *> usz count; // Number of elements
usz count; usz threshold; // Resize limit
<* Resize limit *>
usz threshold;
float load_factor; float load_factor;
<* Resize limit *> LinkedEntry* head; // First inserted LinkedEntry
LinkedEntry* head; LinkedEntry* tail; // Last inserted LinkedEntry
<* First inserted LinkedEntry *>
LinkedEntry* tail;
} }
fn usz LinkedHashSet.len(&self) @operator(len) => self.count; fn int LinkedHashSet.len(&self) @operator(len) => (int) self.count;
<* <*
@param [&inout] allocator : "The allocator to use" @param [&inout] allocator : "The allocator to use"
@@ -50,7 +43,7 @@ fn LinkedHashSet* LinkedHashSet.init(&self, Allocator allocator, usz capacity =
self.threshold = (usz)(capacity * load_factor); self.threshold = (usz)(capacity * load_factor);
self.load_factor = load_factor; self.load_factor = load_factor;
self.table = allocator::new_array(allocator, LinkedEntry*, capacity); self.table = allocator::new_array(allocator, LinkedEntry*, capacity);
self.head = null; self.head = null;
self.tail = null; self.tail = null;
return self; return self;
@@ -143,7 +136,7 @@ fn LinkedHashSet* LinkedHashSet.init_from_set(&self, Allocator allocator, Linked
LinkedEntry* entry = other_set.head; LinkedEntry* entry = other_set.head;
while (entry) // Save insertion order while (entry) // Save insertion order
{ {
linkedhashset_put_for_create(self, entry.value); self.put_for_create(entry.value);
entry = entry.after; entry = entry.after;
} }
return self; return self;
@@ -223,7 +216,7 @@ fn bool LinkedHashSet.add(&set, Value value)
{ {
if (e.hash == hash && equals(value, e.value)) return false; if (e.hash == hash && equals(value, e.value)) return false;
} }
linkedhashset_add_entry(set, hash, value, index); set.add_entry(hash, value, index);
return true; return true;
} }
@@ -266,7 +259,7 @@ fn bool LinkedHashSet.contains(&set, Value value)
*> *>
fn void? LinkedHashSet.remove(&set, Value value) @maydiscard fn void? LinkedHashSet.remove(&set, Value value) @maydiscard
{ {
if (!linkedhashset_remove_entry_for_value(set, value)) return NOT_FOUND~; if (!set.remove_entry_for_value(value)) return NOT_FOUND?;
} }
fn usz LinkedHashSet.remove_all(&set, Value[] values) fn usz LinkedHashSet.remove_all(&set, Value[] values)
@@ -274,7 +267,7 @@ fn usz LinkedHashSet.remove_all(&set, Value[] values)
usz total; usz total;
foreach (v : values) foreach (v : values)
{ {
if (linkedhashset_remove_entry_for_value(set, v)) total++; if (set.remove_entry_for_value(v)) total++;
} }
return total; return total;
} }
@@ -287,7 +280,7 @@ fn usz LinkedHashSet.remove_all_from(&set, LinkedHashSet* other)
usz total; usz total;
other.@each(;Value val) other.@each(;Value val)
{ {
if (linkedhashset_remove_entry_for_value(set, val)) total++; if (set.remove_entry_for_value(val)) total++;
}; };
return total; return total;
} }
@@ -299,7 +292,7 @@ fn void LinkedHashSet.free(&set)
{ {
if (!set.is_initialized()) return; if (!set.is_initialized()) return;
set.clear(); set.clear();
linkedhashset_free_internal(set, set.table.ptr); set.free_internal(set.table.ptr);
set.table = {}; set.table = {};
} }
@@ -311,20 +304,20 @@ fn void LinkedHashSet.free(&set)
fn void LinkedHashSet.clear(&set) fn void LinkedHashSet.clear(&set)
{ {
if (!set.count) return; if (!set.count) return;
LinkedEntry* entry = set.head; LinkedEntry* entry = set.head;
while (entry) while (entry)
{ {
LinkedEntry* next = entry.after; LinkedEntry* next = entry.after;
linkedhashset_free_entry(set, entry); set.free_entry(entry);
entry = next; entry = next;
} }
foreach (LinkedEntry** &bucket : set.table) foreach (LinkedEntry** &bucket : set.table)
{ {
*bucket = null; *bucket = null;
} }
set.count = 0; set.count = 0;
set.head = null; set.head = null;
set.tail = null; set.tail = null;
@@ -334,7 +327,7 @@ fn void LinkedHashSet.reserve(&set, usz capacity)
{ {
if (capacity > set.threshold) if (capacity > set.threshold)
{ {
linkedhashset_resize(set, math::next_power_of_2(capacity)); set.resize(math::next_power_of_2(capacity));
} }
} }
@@ -370,16 +363,16 @@ fn LinkedHashSet LinkedHashSet.intersection(&self, Allocator allocator, LinkedHa
{ {
LinkedHashSet result; LinkedHashSet result;
result.init(allocator, math::min(self.table.len, other.table.len), self.load_factor); result.init(allocator, math::min(self.table.len, other.table.len), self.load_factor);
// Iterate through the smaller set for efficiency // Iterate through the smaller set for efficiency
LinkedHashSet* smaller = self.count <= other.count ? self : other; LinkedHashSet* smaller = self.count <= other.count ? self : other;
LinkedHashSet* larger = self.count > other.count ? self : other; LinkedHashSet* larger = self.count > other.count ? self : other;
smaller.@each(;Value value) smaller.@each(;Value value)
{ {
if (larger.contains(value)) result.add(value); if (larger.contains(value)) result.add(value);
}; };
return result; return result;
} }
@@ -442,7 +435,7 @@ fn bool LinkedHashSet.is_subset(&self, LinkedHashSet* other)
{ {
if (self.count == 0) return true; if (self.count == 0) return true;
if (self.count > other.count) return false; if (self.count > other.count) return false;
self.@each(; Value value) { self.@each(; Value value) {
if (!other.contains(value)) return false; if (!other.contains(value)) return false;
}; };
@@ -451,7 +444,7 @@ fn bool LinkedHashSet.is_subset(&self, LinkedHashSet* other)
// --- private methods // --- private methods
fn void linkedhashset_add_entry(LinkedHashSet* set, uint hash, Value value, uint bucket_index) @private fn void LinkedHashSet.add_entry(&set, uint hash, Value value, uint bucket_index) @private
{ {
LinkedEntry* entry = allocator::new(set.allocator, LinkedEntry, { LinkedEntry* entry = allocator::new(set.allocator, LinkedEntry, {
.hash = hash, .hash = hash,
@@ -460,10 +453,10 @@ fn void linkedhashset_add_entry(LinkedHashSet* set, uint hash, Value value, uint
.before = set.tail, .before = set.tail,
.after = null .after = null
}); });
// Update bucket chain // Update bucket chain
set.table[bucket_index] = entry; set.table[bucket_index] = entry;
// Update linked list // Update linked list
if (set.tail) if (set.tail)
{ {
@@ -475,39 +468,39 @@ fn void linkedhashset_add_entry(LinkedHashSet* set, uint hash, Value value, uint
set.head = entry; set.head = entry;
} }
set.tail = entry; set.tail = entry;
if (set.count++ >= set.threshold) if (set.count++ >= set.threshold)
{ {
linkedhashset_resize(set, set.table.len * 2); set.resize(set.table.len * 2);
} }
} }
fn void linkedhashset_resize(LinkedHashSet* set, usz new_capacity) @private fn void LinkedHashSet.resize(&set, usz new_capacity) @private
{ {
LinkedEntry*[] old_table = set.table; LinkedEntry*[] old_table = set.table;
usz old_capacity = old_table.len; usz old_capacity = old_table.len;
if (old_capacity == MAXIMUM_CAPACITY) if (old_capacity == MAXIMUM_CAPACITY)
{ {
set.threshold = uint.max; set.threshold = uint.max;
return; return;
} }
LinkedEntry*[] new_table = allocator::new_array(set.allocator, LinkedEntry*, new_capacity); LinkedEntry*[] new_table = allocator::new_array(set.allocator, LinkedEntry*, new_capacity);
set.table = new_table; set.table = new_table;
set.threshold = (uint)(new_capacity * set.load_factor); set.threshold = (uint)(new_capacity * set.load_factor);
// Rehash all entries - linked list order remains unchanged // Rehash all entries - linked list order remains unchanged
foreach (uint i, LinkedEntry *e : old_table) foreach (uint i, LinkedEntry *e : old_table)
{ {
if (!e) continue; if (!e) continue;
// Split the bucket chain into two chains based on new bit // Split the bucket chain into two chains based on new bit
LinkedEntry* lo_head = null; LinkedEntry* lo_head = null;
LinkedEntry* lo_tail = null; LinkedEntry* lo_tail = null;
LinkedEntry* hi_head = null; LinkedEntry* hi_head = null;
LinkedEntry* hi_tail = null; LinkedEntry* hi_tail = null;
do do
{ {
LinkedEntry* next = e.next; LinkedEntry* next = e.next;
@@ -539,7 +532,7 @@ fn void linkedhashset_resize(LinkedHashSet* set, usz new_capacity) @private
e = next; e = next;
} }
while (e); while (e);
if (lo_tail) if (lo_tail)
{ {
lo_tail.next = null; lo_tail.next = null;
@@ -551,8 +544,8 @@ fn void linkedhashset_resize(LinkedHashSet* set, usz new_capacity) @private
new_table[i + old_capacity] = hi_head; new_table[i + old_capacity] = hi_head;
} }
} }
linkedhashset_free_internal(set, old_table.ptr); set.free_internal(old_table.ptr);
} }
fn usz? LinkedHashSet.to_format(&self, Formatter* f) @dynamic fn usz? LinkedHashSet.to_format(&self, Formatter* f) @dynamic
@@ -567,7 +560,7 @@ fn usz? LinkedHashSet.to_format(&self, Formatter* f) @dynamic
return len + f.print(" }"); return len + f.print(" }");
} }
fn void linked_hashset_transfer(LinkedHashSet* set, LinkedEntry*[] new_table) @private fn void LinkedHashSet.transfer(&set, LinkedEntry*[] new_table) @private
{ {
LinkedEntry*[] src = set.table; LinkedEntry*[] src = set.table;
uint new_capacity = new_table.len; uint new_capacity = new_table.len;
@@ -586,7 +579,7 @@ fn void linked_hashset_transfer(LinkedHashSet* set, LinkedEntry*[] new_table) @p
} }
} }
fn void linkedhashset_put_for_create(LinkedHashSet* set, Value value) @private fn void LinkedHashSet.put_for_create(&set, Value value) @private
{ {
uint hash = rehash(value.hash()); uint hash = rehash(value.hash());
uint i = index_for(hash, set.table.len); uint i = index_for(hash, set.table.len);
@@ -598,26 +591,26 @@ fn void linkedhashset_put_for_create(LinkedHashSet* set, Value value) @private
return; return;
} }
} }
linkedhashset_create_entry(set, hash, value, i); set.create_entry(hash, value, i);
} }
fn void linkedhashset_free_internal(LinkedHashSet* set, void* ptr) @inline @private fn void LinkedHashSet.free_internal(&set, void* ptr) @inline @private
{ {
allocator::free(set.allocator, ptr); allocator::free(set.allocator, ptr);
} }
fn void linkedhashset_create_entry(LinkedHashSet* set, uint hash, Value value, int bucket_index) @private fn void LinkedHashSet.create_entry(&set, uint hash, Value value, int bucket_index) @private
{ {
LinkedEntry* entry = allocator::new(set.allocator, LinkedEntry, { LinkedEntry* entry = allocator::new(set.allocator, LinkedEntry, {
.hash = hash, .hash = hash,
.value = value, .value = value,
.next = set.table[bucket_index], .next = set.table[bucket_index],
.before = set.tail, .before = set.tail,
.after = null .after = null
}); });
set.table[bucket_index] = entry; set.table[bucket_index] = entry;
// Update linked list // Update linked list
if (set.tail) if (set.tail)
{ {
@@ -629,19 +622,19 @@ fn void linkedhashset_create_entry(LinkedHashSet* set, uint hash, Value value, i
set.head = entry; set.head = entry;
} }
set.tail = entry; set.tail = entry;
set.count++; set.count++;
} }
fn bool linkedhashset_remove_entry_for_value(LinkedHashSet* set, Value value) @private fn bool LinkedHashSet.remove_entry_for_value(&set, Value value) @private
{ {
if (!set.count) return false; if (!set.count) return false;
uint hash = rehash(value.hash()); uint hash = rehash(value.hash());
uint i = index_for(hash, set.table.len); uint i = index_for(hash, set.table.len);
LinkedEntry* prev = null; LinkedEntry* prev = null;
LinkedEntry* e = set.table[i]; LinkedEntry* e = set.table[i];
while (e) while (e)
{ {
if (e.hash == hash && equals(value, e.value)) if (e.hash == hash && equals(value, e.value))
@@ -654,7 +647,7 @@ fn bool linkedhashset_remove_entry_for_value(LinkedHashSet* set, Value value) @p
{ {
set.table[i] = e.next; set.table[i] = e.next;
} }
if (e.before) if (e.before)
{ {
e.before.after = e.after; e.before.after = e.after;
@@ -663,7 +656,7 @@ fn bool linkedhashset_remove_entry_for_value(LinkedHashSet* set, Value value) @p
{ {
set.head = e.after; set.head = e.after;
} }
if (e.after) if (e.after)
{ {
e.after.before = e.before; e.after.before = e.before;
@@ -672,9 +665,9 @@ fn bool linkedhashset_remove_entry_for_value(LinkedHashSet* set, Value value) @p
{ {
set.tail = e.before; set.tail = e.before;
} }
set.count--; set.count--;
linkedhashset_free_entry(set, e); set.free_entry(e);
return true; return true;
} }
prev = e; prev = e;
@@ -683,7 +676,7 @@ fn bool linkedhashset_remove_entry_for_value(LinkedHashSet* set, Value value) @p
return false; return false;
} }
fn void linkedhashset_free_entry(LinkedHashSet* set, LinkedEntry *entry) @private fn void LinkedHashSet.free_entry(&set, LinkedEntry *entry) @private
{ {
allocator::free(set.allocator, entry); allocator::free(set.allocator, entry);
} }
@@ -713,7 +706,7 @@ fn bool LinkedHashSetIterator.next(&self)
fn Value*? LinkedHashSetIterator.get(&self) fn Value*? LinkedHashSetIterator.get(&self)
{ {
return self.current ? &self.current.value : NOT_FOUND~; return self.current ? &self.current.value : NOT_FOUND?;
} }
fn bool LinkedHashSetIterator.has_next(&self) fn bool LinkedHashSetIterator.has_next(&self)
@@ -722,9 +715,9 @@ fn bool LinkedHashSetIterator.has_next(&self)
return self.current && self.current.after != null; return self.current && self.current.after != null;
} }
fn usz LinkedHashSetIterator.len(&self) @operator(len) fn usz LinkedHashSetIterator.len(&self) @operator(len)
{ {
return self.set.count; return self.set.count;
} }
int dummy @local; int dummy @local;

View File

@@ -1,15 +1,14 @@
// Copyright (c) 2021-2024 Christoffer Lerno. All rights reserved. // Copyright (c) 2021-2024 Christoffer Lerno. All rights reserved.
// Use of self source code is governed by the MIT license // Use of self source code is governed by the MIT license
// a copy of which can be found in the LICENSE_STDLIB file. // a copy of which can be found in the LICENSE_STDLIB file.
module std::collections::linkedlist <Type>; module std::collections::linkedlist{Type};
import std::io;
const ELEMENT_IS_EQUATABLE = types::is_equatable_type(Type); const ELEMENT_IS_EQUATABLE = types::is_equatable_type(Type);
struct Node struct Node @private
{ {
Node* next; Node *next;
Node* prev; Node *prev;
Type value; Type value;
} }
@@ -17,31 +16,8 @@ struct LinkedList
{ {
Allocator allocator; Allocator allocator;
usz size; usz size;
Node* _first; Node *_first;
Node* _last; Node *_last;
}
fn usz? LinkedList.to_format(&self, Formatter* f) @dynamic
{
usz len = f.print("{ ")!;
for (Node* node = self._first; node != null; node = 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);
} }
<* <*
@@ -92,11 +68,6 @@ fn void LinkedList.push_front(&self, Type value)
self.size++; 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) fn void LinkedList.push(&self, Type value)
{ {
Node *last = self._last; Node *last = self._last;
@@ -114,23 +85,18 @@ fn void LinkedList.push(&self, Type value)
self.size++; 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(&self) => self.first() @inline;
fn Type? LinkedList.peek_last(&self) => self.last() @inline; fn Type? LinkedList.peek_last(&self) => self.last() @inline;
fn Type? LinkedList.first(&self) fn Type? LinkedList.first(&self)
{ {
if (!self._first) return NO_MORE_ELEMENT~; if (!self._first) return NO_MORE_ELEMENT?;
return self._first.value; return self._first.value;
} }
fn Type? LinkedList.last(&self) fn Type? LinkedList.last(&self)
{ {
if (!self._last) return NO_MORE_ELEMENT~; if (!self._last) return NO_MORE_ELEMENT?;
return self._last.value; return self._last.value;
} }
@@ -167,7 +133,6 @@ macro Node* LinkedList.node_at_index(&self, usz index)
while (index--) node = node.next; while (index--) node = node.next;
return node; return node;
} }
<* <*
@require index < self.size @require index < self.size
*> *>
@@ -176,14 +141,6 @@ fn Type LinkedList.get(&self, usz index)
return self.node_at_index(index).value; 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 @require index < self.size
*> *>
@@ -192,32 +149,12 @@ fn void LinkedList.set(&self, usz index, Type element)
self.node_at_index(index).value = 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 @require index < self.size
*> *>
fn void LinkedList.remove_at(&self, usz index) fn void LinkedList.remove_at(&self, usz index)
{ {
linked_list_unlink(self, self.node_at_index(index)); self.unlink(self.node_at_index(index));
} }
<* <*
@@ -232,47 +169,47 @@ fn void LinkedList.insert_at(&self, usz index, Type element)
case self.size: case self.size:
self.push(element); self.push(element);
default: default:
linked_list_link_before(self, self.node_at_index(index), element); self.link_before(self.node_at_index(index), element);
} }
} }
<* <*
@require succ != null @require succ != null
*> *>
fn void linked_list_link_before(LinkedList* l, Node *succ, Type value) @private fn void LinkedList.link_before(&self, Node *succ, Type value) @private
{ {
Node* pred = succ.prev; Node* pred = succ.prev;
Node* new_node = l.alloc_node(); Node* new_node = self.alloc_node();
*new_node = { .prev = pred, .next = succ, .value = value }; *new_node = { .prev = pred, .next = succ, .value = value };
succ.prev = new_node; succ.prev = new_node;
if (!pred) if (!pred)
{ {
l._first = new_node; self._first = new_node;
} }
else else
{ {
pred.next = new_node; pred.next = new_node;
} }
l.size++; self.size++;
} }
<* <*
@require l._first != null @require self._first != null
*> *>
fn void linked_list_unlink_first(LinkedList* l) @private fn void LinkedList.unlink_first(&self) @private
{ {
Node* f = l._first; Node* f = self._first;
Node* next = f.next; Node* next = f.next;
l.free_node(f); self.free_node(f);
l._first = next; self._first = next;
if (!next) if (!next)
{ {
l._last = null; self._last = null;
} }
else else
{ {
next.prev = null; next.prev = null;
} }
l.size--; self.size--;
} }
fn usz LinkedList.remove(&self, Type t) @if(ELEMENT_IS_EQUATABLE) fn usz LinkedList.remove(&self, Type t) @if(ELEMENT_IS_EQUATABLE)
@@ -285,7 +222,7 @@ fn usz LinkedList.remove(&self, Type t) @if(ELEMENT_IS_EQUATABLE)
{ {
case equals(node.value, t): case equals(node.value, t):
Node* next = node.next; Node* next = node.next;
linked_list_unlink(self, node); self.unlink(node);
node = next; node = next;
default: default:
node = node.next; node = node.next;
@@ -296,8 +233,8 @@ fn usz LinkedList.remove(&self, Type t) @if(ELEMENT_IS_EQUATABLE)
fn Type? LinkedList.pop(&self) fn Type? LinkedList.pop(&self)
{ {
if (!self._last) return NO_MORE_ELEMENT~; if (!self._last) return NO_MORE_ELEMENT?;
defer linked_list_unlink_last(self); defer self.unlink_last();
return self._last.value; return self._last.value;
} }
@@ -308,21 +245,21 @@ fn bool LinkedList.is_empty(&self)
fn Type? LinkedList.pop_front(&self) fn Type? LinkedList.pop_front(&self)
{ {
if (!self._first) return NO_MORE_ELEMENT~; if (!self._first) return NO_MORE_ELEMENT?;
defer linked_list_unlink_first(self); defer self.unlink_first();
return self._first.value; return self._first.value;
} }
fn void? LinkedList.remove_last(&self) @maydiscard fn void? LinkedList.remove_last(&self) @maydiscard
{ {
if (!self._first) return NO_MORE_ELEMENT~; if (!self._first) return NO_MORE_ELEMENT?;
linked_list_unlink_last(self); self.unlink_last();
} }
fn void? LinkedList.remove_first(&self) @maydiscard fn void? LinkedList.remove_first(&self) @maydiscard
{ {
if (!self._first) return NO_MORE_ELEMENT~; if (!self._first) return NO_MORE_ELEMENT?;
linked_list_unlink_first(self); self.unlink_first();
} }
@@ -332,7 +269,7 @@ fn bool LinkedList.remove_first_match(&self, Type t) @if(ELEMENT_IS_EQUATABLE)
{ {
if (node.value == t) if (node.value == t)
{ {
linked_list_unlink(self, node); self.unlink(node);
return true; return true;
} }
} }
@@ -345,7 +282,7 @@ fn bool LinkedList.remove_last_match(&self, Type t) @if(ELEMENT_IS_EQUATABLE)
{ {
if (node.value == t) if (node.value == t)
{ {
linked_list_unlink(self, node); self.unlink(node);
return true; return true;
} }
} }
@@ -354,7 +291,7 @@ fn bool LinkedList.remove_last_match(&self, Type t) @if(ELEMENT_IS_EQUATABLE)
<* <*
@require self._last != null @require self._last != null
*> *>
fn void linked_list_unlink_last(LinkedList* self) @inline @private fn void LinkedList.unlink_last(&self) @inline @private
{ {
Node* l = self._last; Node* l = self._last;
Node* prev = l.prev; Node* prev = l.prev;
@@ -374,7 +311,7 @@ fn void linked_list_unlink_last(LinkedList* self) @inline @private
<* <*
@require x != null @require x != null
*> *>
fn void linked_list_unlink(LinkedList* self, Node* x) @private fn void LinkedList.unlink(&self, Node* x) @private
{ {
Node* next = x.next; Node* next = x.next;
Node* prev = x.prev; Node* prev = x.prev;
@@ -397,69 +334,3 @@ fn void linked_list_unlink(LinkedList* self, Node* x) @private
self.free_node(x); self.free_node(x);
self.size--; 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

@@ -1,7 +1,7 @@
// Copyright (c) 2021-2024 Christoffer Lerno. All rights reserved. // Copyright (c) 2021-2024 Christoffer Lerno. All rights reserved.
// Use of self source code is governed by the MIT license // Use of self source code is governed by the MIT license
// a copy of which can be found in the LICENSE_STDLIB file. // a copy of which can be found in the LICENSE_STDLIB file.
module std::collections::list <Type>; module std::collections::list{Type};
import std::io, std::math, std::collections::list_common; import std::io, std::math, std::collections::list_common;
alias ElementPredicate = fn bool(Type *type); alias ElementPredicate = fn bool(Type *type);
@@ -13,7 +13,7 @@ const Allocator LIST_HEAP_ALLOCATOR = (Allocator)&dummy;
const List ONHEAP = { .allocator = LIST_HEAP_ALLOCATOR }; 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) 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) fn List* List.init_with_array(&self, Allocator allocator, Type[] values)
{ {
self.init(allocator, values.len) @inline; self.init(allocator, values.len) @inline;
self.push_all(values) @inline; self.add_array(values) @inline;
return self; 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) fn List* List.tinit_with_array(&self, Type[] values)
{ {
self.tinit(values.len) @inline; self.tinit(values.len) @inline;
self.push_all(values) @inline; self.add_array(values) @inline;
return self; return self;
} }
@@ -82,7 +82,7 @@ fn void List.init_wrapping_array(&self, Allocator allocator, Type[] types)
self.allocator = allocator; self.allocator = allocator;
self.capacity = types.len; self.capacity = types.len;
self.entries = types.ptr; self.entries = types.ptr;
list_set_size(self, types.len); self.set_size(types.len);
} }
fn bool List.is_initialized(&self) @inline => self.allocator != null && self.allocator != (Allocator)&dummy; fn bool List.is_initialized(&self) @inline => self.allocator != null && self.allocator != (Allocator)&dummy;
@@ -110,24 +110,24 @@ fn usz? List.to_format(&self, Formatter* formatter) @dynamic
fn void List.push(&self, Type element) @inline fn void List.push(&self, Type element) @inline
{ {
self.reserve(1); self.reserve(1);
self.entries[list_set_size(self, self.size + 1)] = element; self.entries[self.set_size(self.size + 1)] = element;
} }
fn Type? List.pop(&self) fn Type? List.pop(&self)
{ {
if (!self.size) return NO_MORE_ELEMENT~; if (!self.size) return NO_MORE_ELEMENT?;
defer list_set_size(self, self.size - 1); defer self.set_size(self.size - 1);
return self.entries[self.size - 1]; return self.entries[self.size - 1];
} }
fn void List.clear(&self) fn void List.clear(&self)
{ {
list_set_size(self, 0); self.set_size(0);
} }
fn Type? List.pop_first(&self) fn Type? List.pop_first(&self)
{ {
if (!self.size) return NO_MORE_ELEMENT~; if (!self.size) return NO_MORE_ELEMENT?;
defer self.remove_at(0); defer self.remove_at(0);
return self.entries[0]; return self.entries[0];
} }
@@ -138,7 +138,7 @@ fn Type? List.pop_first(&self)
fn void List.remove_at(&self, usz index) fn void List.remove_at(&self, usz index)
{ {
usz new_size = self.size - 1; usz new_size = self.size - 1;
defer list_set_size(self, new_size); defer self.set_size(new_size);
if (!new_size || index == new_size) return; if (!new_size || index == new_size) return;
self.entries[index .. new_size - 1] = self.entries[index + 1 .. new_size]; self.entries[index .. new_size - 1] = self.entries[index + 1 .. new_size];
} }
@@ -147,7 +147,7 @@ fn void List.add_all(&self, List* other_list)
{ {
if (!other_list.size) return; if (!other_list.size) return;
self.reserve(other_list.size); self.reserve(other_list.size);
usz index = list_set_size(self, self.size + other_list.size); usz index = self.set_size(self.size + other_list.size);
foreach (&value : other_list) foreach (&value : other_list)
{ {
self.entries[index++] = *value; self.entries[index++] = *value;
@@ -199,25 +199,11 @@ fn Type[] List.array_view(&self)
@param [in] array @param [in] array
@ensure self.size >= array.len @ensure self.size >= array.len
*> *>
fn void List.add_array(&self, Type[] array) @deprecated("Use push_all") fn void List.add_array(&self, Type[] array)
{ {
if (!array.len) return; if (!array.len) return;
self.reserve(array.len); self.reserve(array.len);
usz index = list_set_size(self, self.size + array.len); usz index = self.set_size(self.size + array.len);
self.entries[index : array.len] = array[..];
}
<*
Add the values of an array to this list.
@param [in] array
@ensure self.size >= array.len
*>
fn void List.push_all(&self, Type[] array)
{
if (!array.len) return;
self.reserve(array.len);
usz index = list_set_size(self, self.size + array.len);
self.entries[index : array.len] = array[..]; self.entries[index : array.len] = array[..];
} }
@@ -232,7 +218,7 @@ fn void List.push_front(&self, Type type) @inline
fn void List.insert_at(&self, usz index, Type type) fn void List.insert_at(&self, usz index, Type type)
{ {
self.reserve(1); self.reserve(1);
list_set_size(self, self.size + 1); self.set_size(self.size + 1);
for (isz i = self.size - 1; i > index; i--) for (isz i = self.size - 1; i > index; i--)
{ {
self.entries[i] = self.entries[i - 1]; self.entries[i] = self.entries[i - 1];
@@ -250,25 +236,25 @@ fn void List.set_at(&self, usz index, Type type)
fn void? List.remove_last(&self) @maydiscard fn void? List.remove_last(&self) @maydiscard
{ {
if (!self.size) return NO_MORE_ELEMENT~; if (!self.size) return NO_MORE_ELEMENT?;
list_set_size(self, self.size - 1); self.set_size(self.size - 1);
} }
fn void? List.remove_first(&self) @maydiscard fn void? List.remove_first(&self) @maydiscard
{ {
if (!self.size) return NO_MORE_ELEMENT~; if (!self.size) return NO_MORE_ELEMENT?;
self.remove_at(0); self.remove_at(0);
} }
fn Type? List.first(&self) fn Type? List.first(&self)
{ {
if (!self.size) return NO_MORE_ELEMENT~; if (!self.size) return NO_MORE_ELEMENT?;
return self.entries[0]; return self.entries[0];
} }
fn Type? List.last(&self) fn Type? List.last(&self)
{ {
if (!self.size) return NO_MORE_ELEMENT~; if (!self.size) return NO_MORE_ELEMENT?;
return self.entries[self.size - 1]; return self.entries[self.size - 1];
} }
@@ -299,7 +285,7 @@ fn void List.free(&self)
{ {
if (!self.allocator || self.allocator.ptr == &dummy || !self.capacity) return; if (!self.allocator || self.allocator.ptr == &dummy || !self.capacity) return;
list_pre_free(self); // Remove sanitizer annotation self.pre_free(); // Remove sanitizer annotation
$if type_is_overaligned(): $if type_is_overaligned():
allocator::free_aligned(self.allocator, self.entries); allocator::free_aligned(self.allocator, self.entries);
@@ -358,7 +344,7 @@ fn usz List.retain_using_test(&self, ElementTest filter, any context)
return list_common::list_remove_using_test(self, filter, true, context); return list_common::list_remove_using_test(self, filter, true, context);
} }
fn void list_ensure_capacity(List* self, usz min_capacity) @local fn void List.ensure_capacity(&self, usz min_capacity) @local
{ {
if (!min_capacity) return; if (!min_capacity) return;
if (self.capacity >= min_capacity) return; if (self.capacity >= min_capacity) return;
@@ -374,7 +360,7 @@ fn void list_ensure_capacity(List* self, usz min_capacity) @local
break; break;
} }
list_pre_free(self); // Remove sanitizer annotation self.pre_free(); // Remove sanitizer annotation
min_capacity = math::next_power_of_2(min_capacity); min_capacity = math::next_power_of_2(min_capacity);
$if type_is_overaligned(): $if type_is_overaligned():
@@ -384,7 +370,7 @@ fn void list_ensure_capacity(List* self, usz min_capacity) @local
$endif; $endif;
self.capacity = min_capacity; self.capacity = min_capacity;
list_post_alloc(self); // Add sanitizer annotation self.post_alloc(); // Add sanitizer annotation
} }
<* <*
@@ -419,7 +405,7 @@ fn void List.reserve(&self, usz added)
assert(new_size < usz.max / 2U); assert(new_size < usz.max / 2U);
usz new_capacity = self.capacity ? 2U * self.capacity : 16U; usz new_capacity = self.capacity ? 2U * self.capacity : 16U;
while (new_capacity < new_size) new_capacity *= 2U; while (new_capacity < new_size) new_capacity *= 2U;
list_ensure_capacity(self, new_capacity); self.ensure_capacity(new_capacity);
} }
fn void List._update_size_change(&self,usz old_size, usz new_size) fn void List._update_size_change(&self,usz old_size, usz new_size)
@@ -436,7 +422,7 @@ fn void List._update_size_change(&self,usz old_size, usz new_size)
<* <*
@require new_size == 0 || self.capacity != 0 @require new_size == 0 || self.capacity != 0
*> *>
fn usz list_set_size(List* self, usz new_size) @inline @private fn usz List.set_size(&self, usz new_size) @inline @private
{ {
usz old_size = self.size; usz old_size = self.size;
self._update_size_change(old_size, new_size); self._update_size_change(old_size, new_size);
@@ -444,7 +430,7 @@ fn usz list_set_size(List* self, usz new_size) @inline @private
return old_size; return old_size;
} }
macro void list_pre_free(List* self) @private macro void List.pre_free(&self) @private
{ {
if (!self.capacity) return; if (!self.capacity) return;
self._update_size_change(self.size, self.capacity); self._update_size_change(self.size, self.capacity);
@@ -453,7 +439,7 @@ macro void list_pre_free(List* self) @private
<* <*
@require self.capacity > 0 @require self.capacity > 0
*> *>
macro void list_post_alloc(List* self) @private macro void List.post_alloc(&self) @private
{ {
self._update_size_change(self.capacity, self.size); self._update_size_change(self.capacity, self.size);
} }
@@ -461,22 +447,22 @@ macro void list_post_alloc(List* self) @private
// Functions for equatable types // Functions for equatable types
fn usz? List.index_of(&self, Type type) @if (ELEMENT_IS_EQUATABLE) fn usz? List.index_of(&self, Type type) @if(ELEMENT_IS_EQUATABLE)
{ {
foreach (i, v : self) foreach (i, v : self)
{ {
if (equals(v, type)) return i; if (equals(v, type)) return i;
} }
return NOT_FOUND~; return NOT_FOUND?;
} }
fn usz? List.rindex_of(&self, Type type) @if (ELEMENT_IS_EQUATABLE) fn usz? List.rindex_of(&self, Type type) @if(ELEMENT_IS_EQUATABLE)
{ {
foreach_r (i, v : self) foreach_r (i, v : self)
{ {
if (equals(v, type)) return i; if (equals(v, type)) return i;
} }
return NOT_FOUND~; return NOT_FOUND?;
} }
fn bool List.equals(&self, List other_list) @if(ELEMENT_IS_EQUATABLE) fn bool List.equals(&self, List other_list) @if(ELEMENT_IS_EQUATABLE)
@@ -532,8 +518,7 @@ fn bool List.remove_first_item(&self, Type value) @if(ELEMENT_IS_EQUATABLE)
fn usz List.remove_item(&self, Type value) @if(ELEMENT_IS_EQUATABLE) fn usz List.remove_item(&self, Type value) @if(ELEMENT_IS_EQUATABLE)
{ {
usz old_size = self.size; usz old_size = self.size;
defer defer {
{
if (old_size != self.size) self._update_size_change(old_size, self.size); if (old_size != self.size) self._update_size_change(old_size, self.size);
} }
return list_common::list_remove_item(self, value); return list_common::list_remove_item(self, value);

View File

@@ -1,4 +1,4 @@
module std::collections::maybe <Type>; module std::collections::maybe{Type};
import std::io; import std::io;
struct Maybe (Printable) struct Maybe (Printable)
@@ -32,7 +32,7 @@ const Maybe EMPTY = { };
macro Type? Maybe.get(self) macro Type? Maybe.get(self)
{ {
return self.has_value ? self.value : NOT_FOUND~; return self.has_value ? self.value : NOT_FOUND?;
} }
fn bool Maybe.equals(self, Maybe other) @operator(==) @if(types::is_equatable_type(Type)) fn bool Maybe.equals(self, Maybe other) @operator(==) @if(types::is_equatable_type(Type))

View File

@@ -151,7 +151,7 @@ fn bool Object.is_indexable(&self) => self.is_empty() || self.is_array();
<* <*
@require self.is_keyable() @require self.is_keyable()
*> *>
fn void object_init_map_if_needed(Object* self) @private fn void Object.init_map_if_needed(&self) @private
{ {
if (self.is_empty()) if (self.is_empty())
{ {
@@ -163,7 +163,7 @@ fn void object_init_map_if_needed(Object* self) @private
<* <*
@require self.is_indexable() @require self.is_indexable()
*> *>
fn void object_init_array_if_needed(Object* self) @private fn void Object.init_array_if_needed(&self) @private
{ {
if (self.is_empty()) if (self.is_empty())
{ {
@@ -175,9 +175,9 @@ fn void object_init_array_if_needed(Object* self) @private
<* <*
@require self.is_keyable() @require self.is_keyable()
*> *>
fn void object_set_object(Object* self, String key, Object* new_object) @private fn void Object.set_object(&self, String key, Object* new_object) @private
{ {
object_init_map_if_needed(self); self.init_map_if_needed();
Object*? val = self.map.get_entry(key).value; Object*? val = self.map.get_entry(key).value;
defer (void)val.free(); defer (void)val.free();
self.map.set(key, new_object); self.map.set(key, new_object);
@@ -185,7 +185,7 @@ fn void object_set_object(Object* self, String key, Object* new_object) @private
<* <*
@require self.allocator != null : "This object is not properly initialized, was it really created using 'new'" @require 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 macro Object* Object.object_from_value(&self, value) @private
{ {
@@ -203,7 +203,7 @@ macro Object* Object.object_from_value(&self, value) @private
return value; return value;
$case $Type.typeid == void*.typeid: $case $Type.typeid == void*.typeid:
return &NULL_OBJECT; return &NULL_OBJECT;
$case $defined(String x = value): $case @assignable_to(value, String):
return new_string(value, self.allocator); return new_string(value, self.allocator);
$default: $default:
$error "Unsupported object type."; $error "Unsupported object type.";
@@ -214,7 +214,7 @@ macro Object* Object.object_from_value(&self, value) @private
macro Object* Object.set(&self, String key, value) macro Object* Object.set(&self, String key, value)
{ {
Object* val = self.object_from_value(value); Object* val = self.object_from_value(value);
object_set_object(self, key, val); self.set_object(key, val);
return val; return val;
} }
@@ -242,7 +242,8 @@ macro Object* Object.push(&self, value)
<* <*
@require self.is_keyable() @require self.is_keyable()
*> *>
fn Object*? Object.get(&self, String key) => self.is_empty() ? NOT_FOUND~ : self.map.get(key); fn Object*? Object.get(&self, String key) => self.is_empty() ? NOT_FOUND? : self.map.get(key);
fn bool Object.has_key(&self, String key) => self.is_map() && self.map.has_key(key); fn bool Object.has_key(&self, String key) => self.is_map() && self.map.has_key(key);
@@ -267,7 +268,7 @@ fn usz Object.get_len(&self)
*> *>
fn void Object.push_object(&self, Object* to_append) fn void Object.push_object(&self, Object* to_append)
{ {
object_init_array_if_needed(self); self.init_array_if_needed();
self.array.push(to_append); self.array.push(to_append);
} }
@@ -276,7 +277,7 @@ fn void Object.push_object(&self, Object* to_append)
*> *>
fn void Object.set_object_at(&self, usz index, Object* to_set) fn void Object.set_object_at(&self, usz index, Object* to_set)
{ {
object_init_array_if_needed(self); self.init_array_if_needed();
while (self.array.len() < index) while (self.array.len() < index)
{ {
self.array.push(&NULL_OBJECT); self.array.push(&NULL_OBJECT);
@@ -307,7 +308,7 @@ macro get_integer_value(Object* value, $Type)
return ($Type)value.s.to_uint128(); return ($Type)value.s.to_uint128();
$endif $endif
} }
if (!value.is_int()) return string::MALFORMED_INTEGER~; if (!value.is_int()) return string::MALFORMED_INTEGER?;
return ($Type)value.i; return ($Type)value.i;
} }
@@ -360,7 +361,7 @@ fn uint128? Object.get_uint128_at(&self, usz index) => self.get_integer_at(uint1
fn String? Object.get_string(&self, String key) fn String? Object.get_string(&self, String key)
{ {
Object* value = self.get(key)!; Object* value = self.get(key)!;
if (!value.is_string()) return TYPE_MISMATCH~; if (!value.is_string()) return TYPE_MISMATCH?;
return value.s; return value.s;
} }
@@ -370,7 +371,7 @@ fn String? Object.get_string(&self, String key)
fn String? Object.get_string_at(&self, usz index) fn String? Object.get_string_at(&self, usz index)
{ {
Object* value = self.get_at(index); Object* value = self.get_at(index);
if (!value.is_string()) return TYPE_MISMATCH~; if (!value.is_string()) return TYPE_MISMATCH?;
return value.s; return value.s;
} }
@@ -380,7 +381,7 @@ fn String? Object.get_string_at(&self, usz index)
macro String? Object.get_enum(&self, $EnumType, String key) macro String? Object.get_enum(&self, $EnumType, String key)
{ {
Object value = self.get(key)!; Object value = self.get(key)!;
if ($EnumType.typeid != value.type) return TYPE_MISMATCH~; if ($EnumType.typeid != value.type) return TYPE_MISMATCH?;
return ($EnumType)value.i; return ($EnumType)value.i;
} }
@@ -390,7 +391,7 @@ macro String? Object.get_enum(&self, $EnumType, String key)
macro String? Object.get_enum_at(&self, $EnumType, usz index) macro String? Object.get_enum_at(&self, $EnumType, usz index)
{ {
Object value = self.get_at(index); Object value = self.get_at(index);
if ($EnumType.typeid != value.type) return TYPE_MISMATCH~; if ($EnumType.typeid != value.type) return TYPE_MISMATCH?;
return ($EnumType)value.i; return ($EnumType)value.i;
} }
@@ -400,7 +401,7 @@ macro String? Object.get_enum_at(&self, $EnumType, usz index)
fn bool? Object.get_bool(&self, String key) fn bool? Object.get_bool(&self, String key)
{ {
Object* value = self.get(key)!; Object* value = self.get(key)!;
if (!value.is_bool()) return TYPE_MISMATCH~; if (!value.is_bool()) return TYPE_MISMATCH?;
return value.b; return value.b;
} }
@@ -411,7 +412,7 @@ fn bool? Object.get_bool(&self, String key)
fn bool? Object.get_bool_at(&self, usz index) fn bool? Object.get_bool_at(&self, usz index)
{ {
Object* value = self.get_at(index); Object* value = self.get_at(index);
if (!value.is_bool()) return TYPE_MISMATCH~; if (!value.is_bool()) return TYPE_MISMATCH?;
return value.b; return value.b;
} }
@@ -430,7 +431,7 @@ fn double? Object.get_float(&self, String key)
case FLOAT: case FLOAT:
return value.f; return value.f;
default: default:
return TYPE_MISMATCH~; return TYPE_MISMATCH?;
} }
} }
@@ -449,7 +450,7 @@ fn double? Object.get_float_at(&self, usz index)
case FLOAT: case FLOAT:
return value.f; return value.f;
default: default:
return TYPE_MISMATCH~; return TYPE_MISMATCH?;
} }
} }

View File

@@ -20,13 +20,16 @@
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // 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 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE. // SOFTWARE.
module std::collections::priorityqueue; module std::collections::priorityqueue{Type};
import std::collections::priorityqueue::private;
typedef PriorityQueue = inline PrivatePriorityQueue{Type, false};
typedef PriorityQueueMax = inline PrivatePriorityQueue{Type, true};
module std::collections::priorityqueue::private{Type, MAX};
import std::collections::list, std::io; import std::collections::list, std::io;
typedef PriorityQueue <Type> = inline PrivatePriorityQueue{Type, false}; struct PrivatePriorityQueue (Printable)
typedef PriorityQueueMax <Type> = inline PrivatePriorityQueue{Type, true};
struct PrivatePriorityQueue (Printable) <Type, MAX>
{ {
List{Type} heap; List{Type} heap;
} }
@@ -83,7 +86,7 @@ fn Type? PrivatePriorityQueue.pop(&self)
{ {
usz i = 0; usz i = 0;
usz len = self.heap.len(); usz len = self.heap.len();
if (!len) return NO_MORE_ELEMENT~; if (!len) return NO_MORE_ELEMENT?;
usz new_count = len - 1; usz new_count = len - 1;
self.heap.swap(0, new_count); self.heap.swap(0, new_count);
while OUTER: ((2 * i + 1) < new_count) while OUTER: ((2 * i + 1) < new_count)

View File

@@ -1,7 +1,7 @@
<* <*
@require Type.is_ordered : "The type must be ordered" @require Type.is_ordered : "The type must be ordered"
*> *>
module std::collections::range <Type>; module std::collections::range{Type};
import std::io; import std::io;
struct Range (Printable) struct Range (Printable)

View File

@@ -1,7 +1,7 @@
<* <*
@require Type.kindof == ARRAY : "Required an array type" @require Type.kindof == ARRAY : "Required an array type"
*> *>
module std::collections::ringbuffer <Type>; module std::collections::ringbuffer{Type};
import std::io; import std::io;
alias Element = $typeof((Type){}[0]); alias Element = $typeof((Type){}[0]);
@@ -48,7 +48,7 @@ fn Element? RingBuffer.pop(&self)
switch switch
{ {
case self.written == 0: case self.written == 0:
return NO_MORE_ELEMENT~; return NO_MORE_ELEMENT?;
case self.written < self.buf.len: case self.written < self.buf.len:
self.written--; self.written--;
return self.buf[self.written]; return self.buf[self.written];

View File

@@ -1,4 +1,4 @@
module std::collections::pair <Type1, Type2>; module std::collections::pair{Type1, Type2};
import std::io; import std::io;
struct Pair (Printable) struct Pair (Printable)
@@ -15,8 +15,8 @@ fn usz? Pair.to_format(&self, Formatter* f) @dynamic
<* <*
@param [&out] a @param [&out] a
@param [&out] b @param [&out] b
@require $defined(*a = self.first) : "You cannot assign the first value to a" @require @assignable_to(self.first, $typeof(*a)) : "You cannot assign the first value to a"
@require $defined(*b = self.second) : "You cannot assign the second value to b" @require @assignable_to(self.second, $typeof(*b)) : "You cannot assign the second value to b"
*> *>
macro void Pair.unpack(&self, a, b) macro void Pair.unpack(&self, a, b)
{ {
@@ -29,7 +29,9 @@ fn bool Pair.equal(self, Pair other) @operator(==) @if (types::has_equals(Type1)
return self.first == other.first && self.second == other.second; return self.first == other.first && self.second == other.second;
} }
module std::collections::triple <Type1, Type2, Type3>;
module std::collections::triple{Type1, Type2, Type3};
import std::io; import std::io;
struct Triple (Printable) struct Triple (Printable)
@@ -47,9 +49,9 @@ fn usz? Triple.to_format(&self, Formatter* f) @dynamic
@param [&out] a @param [&out] a
@param [&out] b @param [&out] b
@param [&out] c @param [&out] c
@require $defined(*a = self.first) : "You cannot assign the first value to a" @require @assignable_to(self.first, $typeof(*a)) : "You cannot assign the first value to a"
@require $defined(*b = self.second) : "You cannot assign the second value to b" @require @assignable_to(self.second, $typeof(*b)) : "You cannot assign the second value to b"
@require $defined(*c = self.third) : "You cannot assign the second value to c" @require @assignable_to(self.third, $typeof(*c)) : "You cannot assign the second value to c"
*> *>
macro void Triple.unpack(&self, a, b, c) macro void Triple.unpack(&self, a, b, c)
{ {
@@ -63,10 +65,11 @@ fn bool Triple.equal(self, Triple other) @operator(==) @if (types::has_equals(Ty
return self.first == other.first && self.second == other.second && self.third == other.third; return self.first == other.first && self.second == other.second && self.third == other.third;
} }
module std::collections::tuple <Type1, Type2>;
module std::collections::tuple{Type1, Type2};
struct Tuple @deprecated("Use 'Pair' instead") struct Tuple @deprecated("Use 'Pair' instead")
{ {
Type1 first; Type1 first;
Type2 second; Type2 second;
} }

File diff suppressed because it is too large Load Diff

View File

@@ -7,12 +7,10 @@ const uint PIXELS_MAX = 400000000;
Purely informative. It will be saved to the file header, Purely informative. It will be saved to the file header,
but does not affect how chunks are en-/decoded. but does not affect how chunks are en-/decoded.
*> *>
constdef QOIColorspace : char enum QOIColorspace : char (char id)
{ {
<* sRGB with linear alpha *> SRGB = 0, // sRGB with linear alpha
SRGB = 0, LINEAR = 1 // all channels linear
<* all channels linear *>
LINEAR = 1
} }
<* <*
@@ -21,7 +19,7 @@ constdef QOIColorspace : char
AUTO can be used when decoding to automatically determine AUTO can be used when decoding to automatically determine
the channels from the file's header. the channels from the file's header.
*> *>
constdef QOIChannels : inline char enum QOIChannels : char (char id)
{ {
AUTO = 0, AUTO = 0,
RGB = 3, RGB = 3,
@@ -100,7 +98,7 @@ fn usz? write(String filename, char[] input, QOIDesc* desc) => @pool()
fn char[]? read(Allocator allocator, String filename, QOIDesc* desc, QOIChannels channels = AUTO) => @pool() fn char[]? read(Allocator allocator, String filename, QOIDesc* desc, QOIChannels channels = AUTO) => @pool()
{ {
// read file // read file
char[] data = file::load_temp(filename) ?? FILE_OPEN_FAILED~!; char[] data = file::load_temp(filename) ?? FILE_OPEN_FAILED?!;
// pass data to decode function // pass data to decode function
return decode(allocator, data, desc, channels); return decode(allocator, data, desc, channels);
} }
@@ -128,14 +126,14 @@ import std::bits;
fn char[]? encode(Allocator allocator, char[] input, QOIDesc* desc) @nodiscard fn char[]? encode(Allocator allocator, char[] input, QOIDesc* desc) @nodiscard
{ {
// check info in desc // check info in desc
if (desc.width == 0 || desc.height == 0) return INVALID_PARAMETERS~; if (desc.width == 0 || desc.height == 0) return INVALID_PARAMETERS?;
if (desc.channels == AUTO) return INVALID_PARAMETERS~; if (desc.channels == AUTO) return INVALID_PARAMETERS?;
uint pixels = desc.width * desc.height; uint pixels = desc.width * desc.height;
if (pixels > PIXELS_MAX) return TOO_MANY_PIXELS~; if (pixels > PIXELS_MAX) return TOO_MANY_PIXELS?;
// check input data size // 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~; if (image_size != input.len) return INVALID_DATA?;
// allocate memory for encoded data (output) // allocate memory for encoded data (output)
// header + chunk tag and RGB(A) data for each pixel + end of stream // header + chunk tag and RGB(A) data for each pixel + end of stream
@@ -148,13 +146,13 @@ fn char[]? encode(Allocator allocator, char[] input, QOIDesc* desc) @nodiscard
.be_magic = bswap('qoif'), .be_magic = bswap('qoif'),
.be_width = bswap(desc.width), .be_width = bswap(desc.width),
.be_height = bswap(desc.height), .be_height = bswap(desc.height),
.channels = desc.channels, .channels = desc.channels.id,
.colorspace = desc.colorspace .colorspace = desc.colorspace.id
}; };
uint pos = Header.sizeof; // Current position in output uint pos = Header.sizeof; // Current position in output
uint loc; // Current position in image (top-left corner) 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 char run_length = 0; // Length of the current run
Pixel[64] palette; // Zero-initialized by default 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 ichar[<3>] luma; // ...and luma
// write chunks // write chunks
for (loc = 0; loc < image_size; loc += desc.channels) for (loc = 0; loc < image_size; loc += desc.channels.id)
{ {
// set previous pixel // set previous pixel
prev = p; prev = p;
@@ -283,27 +281,27 @@ fn char[]? encode(Allocator allocator, char[] input, QOIDesc* desc) @nodiscard
fn char[]? decode(Allocator allocator, char[] data, QOIDesc* desc, QOIChannels channels = AUTO) @nodiscard fn char[]? decode(Allocator allocator, char[] data, QOIDesc* desc, QOIChannels channels = AUTO) @nodiscard
{ {
// check input data // check input data
if (data.len < Header.sizeof + END_OF_STREAM.len) return INVALID_DATA~; if (data.len < Header.sizeof + END_OF_STREAM.len) return INVALID_DATA?;
// get header // get header
Header* header = (Header*)data.ptr; Header* header = (Header*)data.ptr;
// check magic bytes (FourCC) // check magic bytes (FourCC)
if (bswap(header.be_magic) != 'qoif') return INVALID_DATA~; if (bswap(header.be_magic) != 'qoif') return INVALID_DATA?;
// copy header data to desc // copy header data to desc
uint width = desc.width = bswap(header.be_width); desc.width = bswap(header.be_width);
uint height = desc.height = bswap(header.be_height); desc.height = bswap(header.be_height);
QOIChannels desc_channels = desc.channels = header.channels; desc.channels = @enumcast(QOIChannels, header.channels)!; // Rethrow if invalid
desc.colorspace = header.colorspace; desc.colorspace = @enumcast(QOIColorspace, header.colorspace)!; // Rethrow if invalid
if (desc_channels == AUTO) return INVALID_DATA~; // Channels must be specified in the header if (desc.channels == AUTO) return INVALID_DATA?; // Channels must be specified in the header
// check width and height // check width and height
if (width == 0 || height == 0) return INVALID_DATA~; if (desc.width == 0 || desc.height == 0) return INVALID_DATA?;
// check pixel count // check pixel count
ulong pixels = (ulong)width * (ulong)height; ulong pixels = (ulong)desc.width * (ulong)desc.height;
if (pixels > PIXELS_MAX) return TOO_MANY_PIXELS~; if (pixels > PIXELS_MAX) return TOO_MANY_PIXELS?;
uint pos = Header.sizeof; // Current position in data uint pos = Header.sizeof; // Current position in data
uint loc; // Current position in image (top-left corner) uint loc; // Current position in image (top-left corner)
@@ -313,14 +311,14 @@ fn char[]? decode(Allocator allocator, char[] data, QOIDesc* desc, QOIChannels c
Pixel[64] palette; // Zero-initialized by default Pixel[64] palette; // Zero-initialized by default
Pixel p = { 0, 0, 0, 255 }; Pixel p = { 0, 0, 0, 255 };
if (channels == AUTO) channels = desc_channels; if (channels == AUTO) channels = desc.channels;
// allocate memory for image data // 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); char[] image = allocator::alloc_array(allocator, char, image_size);
defer catch allocator::free(allocator, image); 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 // get chunk tag
tag = data[pos]; tag = data[pos];
@@ -393,22 +391,31 @@ const OP_RUN = 0b11;
struct Header @packed struct Header @packed
{ {
<* magic bytes "qoif" *> uint be_magic; // magic bytes "qoif"
uint be_magic; uint be_width; // image width in pixels (BE)
<* image width in pixels (BE) *> uint be_height; // image height in pixels (BE)
uint be_width;
<* image height in pixels (BE) *>
uint be_height;
// informative fields // informative fields
<* 3 = RGB, 4 = RGB *> char channels; // 3 = RGB, 4 = RGB
QOIChannels channels; char colorspace; // 0 = sRGB with linear alpha, 1 = all channels linear
<* 0 = sRGB with linear alpha, 1 = all channels linear *>
QOIColorspace colorspace;
} }
const char[*] END_OF_STREAM = {0, 0, 0, 0, 0, 0, 0, 1}; 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>]; typedef Pixel = inline char[<4>];
macro char Pixel.hash(Pixel p) macro char Pixel.hash(Pixel p)
{ {

File diff suppressed because it is too large Load Diff

View File

@@ -90,12 +90,12 @@ fn void*? ArenaAllocator.acquire(&self, usz size, AllocInitType init_type, usz a
{ {
alignment = alignment_for_allocation(alignment); alignment = alignment_for_allocation(alignment);
usz total_len = self.data.len; usz total_len = self.data.len;
if (size > total_len) return mem::INVALID_ALLOC_SIZE~; if (size > total_len) return mem::INVALID_ALLOC_SIZE?;
void* start_mem = self.data.ptr; void* start_mem = self.data.ptr;
void* unaligned_pointer_to_offset = start_mem + self.used + ArenaAllocatorHeader.sizeof; void* unaligned_pointer_to_offset = start_mem + self.used + ArenaAllocatorHeader.sizeof;
void* mem = mem::aligned_pointer(unaligned_pointer_to_offset, alignment); void* mem = mem::aligned_pointer(unaligned_pointer_to_offset, alignment);
usz end = (usz)(mem - self.data.ptr) + size; usz end = (usz)(mem - self.data.ptr) + size;
if (end > total_len) return mem::OUT_OF_MEMORY~; if (end > total_len) return mem::OUT_OF_MEMORY?;
self.used = end; self.used = end;
ArenaAllocatorHeader* header = mem - ArenaAllocatorHeader.sizeof; ArenaAllocatorHeader* header = mem - ArenaAllocatorHeader.sizeof;
header.size = size; header.size = size;
@@ -117,7 +117,7 @@ fn void*? ArenaAllocator.resize(&self, void *old_pointer, usz size, usz alignmen
alignment = alignment_for_allocation(alignment); alignment = alignment_for_allocation(alignment);
assert(old_pointer >= self.data.ptr, "Pointer originates from a different allocator."); assert(old_pointer >= self.data.ptr, "Pointer originates from a different allocator.");
usz total_len = self.data.len; usz total_len = self.data.len;
if (size > total_len) return mem::INVALID_ALLOC_SIZE~; if (size > total_len) return mem::INVALID_ALLOC_SIZE?;
ArenaAllocatorHeader* header = old_pointer - ArenaAllocatorHeader.sizeof; ArenaAllocatorHeader* header = old_pointer - ArenaAllocatorHeader.sizeof;
usz old_size = header.size; usz old_size = header.size;
// Do last allocation and alignment match? // Do last allocation and alignment match?
@@ -130,7 +130,7 @@ fn void*? ArenaAllocator.resize(&self, void *old_pointer, usz size, usz alignmen
else else
{ {
usz new_used = self.used + size - old_size; usz new_used = self.used + size - old_size;
if (new_used > total_len) return mem::OUT_OF_MEMORY~; if (new_used > total_len) return mem::OUT_OF_MEMORY?;
self.used = new_used; self.used = new_used;
} }
header.size = size; header.size = size;

View File

@@ -56,7 +56,7 @@ fn BackedArenaAllocator*? new_backed_allocator(usz size, Allocator allocator)
fn void BackedArenaAllocator.destroy(&self) fn void BackedArenaAllocator.destroy(&self)
{ {
self.reset(0); self.reset(0);
if (self.last_page) (void)_free_page(self, self.last_page); if (self.last_page) (void)self._free_page(self.last_page);
allocator::free(self.backing_allocator, self); allocator::free(self.backing_allocator, self);
} }
@@ -79,7 +79,7 @@ fn void BackedArenaAllocator.reset(&self, usz mark)
self.used = last_page.mark; self.used = last_page.mark;
ExtraPage *to_free = last_page; ExtraPage *to_free = last_page;
last_page = last_page.prev_page; last_page = last_page.prev_page;
_free_page(self, to_free)!!; self._free_page(to_free)!!;
} }
self.last_page = last_page; self.last_page = last_page;
$if env::COMPILER_SAFE_MODE || env::ADDRESS_SANITIZER: $if env::COMPILER_SAFE_MODE || env::ADDRESS_SANITIZER:
@@ -98,13 +98,13 @@ fn void BackedArenaAllocator.reset(&self, usz mark)
self.used = mark; self.used = mark;
} }
fn void? _free_page(BackedArenaAllocator* self, ExtraPage* page) @inline @local fn void? BackedArenaAllocator._free_page(&self, ExtraPage* page) @inline @local
{ {
void* mem = page.start; void* mem = page.start;
return self.backing_allocator.release(mem, page.is_aligned()); return self.backing_allocator.release(mem, page.is_aligned());
} }
fn void*? _realloc_page(BackedArenaAllocator* self, ExtraPage* page, usz size, usz alignment) @inline @local fn void*? BackedArenaAllocator._realloc_page(&self, ExtraPage* page, usz size, usz alignment) @inline @local
{ {
// Then the actual start pointer: // Then the actual start pointer:
void* real_pointer = page.start; void* real_pointer = page.start;
@@ -133,7 +133,7 @@ fn void*? BackedArenaAllocator.resize(&self, void* pointer, usz size, usz alignm
assert(self.last_page, "Realloc of unrelated pointer"); assert(self.last_page, "Realloc of unrelated pointer");
// First grab the page // First grab the page
ExtraPage *page = pointer - ExtraPage.sizeof; ExtraPage *page = pointer - ExtraPage.sizeof;
return _realloc_page(self, page, size, alignment); return self._realloc_page(page, size, alignment);
} }
AllocChunk* data = self.acquire(size, NO_ZERO, alignment)!; AllocChunk* data = self.acquire(size, NO_ZERO, alignment)!;

View File

@@ -13,7 +13,7 @@ import std::math;
The advantage over the BackedArenaAllocator, is that when allocating beyond the first "page", it will 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 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) struct DynamicArenaAllocator (Allocator)
{ {
@@ -137,7 +137,36 @@ fn void DynamicArenaAllocator.reset(&self)
self.page = page; self.page = page;
} }
<*
@require math::is_power_of_2(alignment)
@require size > 0
@return? mem::INVALID_ALLOC_SIZE, mem::OUT_OF_MEMORY
*>
fn void*? DynamicArenaAllocator._alloc_new(&self, usz size, usz alignment) @local
{
// First, make sure that we can align it, extending the page size if needed.
usz page_size = max(self.page_size, mem::aligned_offset(size + DynamicArenaChunk.sizeof + alignment, alignment));
assert(page_size > size + DynamicArenaChunk.sizeof);
// Grab the page without alignment (we do it ourselves)
void* mem = allocator::malloc_try(self.backing_allocator, page_size)!;
DynamicArenaPage*? page = allocator::new_try(self.backing_allocator, DynamicArenaPage);
if (catch err = page)
{
allocator::free(self.backing_allocator, mem);
return err?;
}
page.memory = mem;
void* mem_start = mem::aligned_pointer(mem + DynamicArenaChunk.sizeof, alignment);
assert(mem_start + size < mem + page_size);
DynamicArenaChunk* chunk = (DynamicArenaChunk*)mem_start - 1;
chunk.size = size;
page.prev_arena = self.page;
page.total = page_size;
page.used = mem_start + size - page.memory;
self.page = page;
page.current_stack_ptr = mem_start;
return mem_start;
}
<* <*
@require size > 0 : `acquire expects size > 0` @require size > 0 : `acquire expects size > 0`
@@ -160,7 +189,7 @@ fn void*? DynamicArenaAllocator.acquire(&self, usz size, AllocInitType init_type
} }
if (!page) if (!page)
{ {
ptr = _alloc_new(self, size, alignment)!; ptr = self._alloc_new(size, alignment)!;
break SET_DONE; break SET_DONE;
} }
void* start = mem::aligned_pointer(page.memory + page.used + DynamicArenaChunk.sizeof, alignment); void* start = mem::aligned_pointer(page.memory + page.used + DynamicArenaChunk.sizeof, alignment);
@@ -179,7 +208,7 @@ fn void*? DynamicArenaAllocator.acquire(&self, usz size, AllocInitType init_type
break ALLOCATE_NEW; break ALLOCATE_NEW;
} }
} }
ptr = _alloc_new(self, size, alignment)!; ptr = self._alloc_new(size, alignment)!;
break SET_DONE; break SET_DONE;
} }
page.used = new_used; page.used = new_used;
@@ -191,34 +220,3 @@ fn void*? DynamicArenaAllocator.acquire(&self, usz size, AllocInitType init_type
if (init_type == ZERO) mem::clear(ptr, size, mem::DEFAULT_MEM_ALIGNMENT); if (init_type == ZERO) mem::clear(ptr, size, mem::DEFAULT_MEM_ALIGNMENT);
return ptr; return ptr;
} }
<*
@require math::is_power_of_2(alignment)
@require size > 0
@return? mem::INVALID_ALLOC_SIZE, mem::OUT_OF_MEMORY
*>
fn void*? _alloc_new(DynamicArenaAllocator* self, usz size, usz alignment) @local
{
// First, make sure that we can align it, extending the page size if needed.
usz page_size = max(self.page_size, mem::aligned_offset(size + DynamicArenaChunk.sizeof + alignment, alignment));
assert(page_size > size + DynamicArenaChunk.sizeof);
// Grab the page without alignment (we do it ourselves)
void* mem = allocator::malloc_try(self.backing_allocator, page_size)!;
DynamicArenaPage*? page = allocator::new_try(self.backing_allocator, DynamicArenaPage);
if (catch err = page)
{
allocator::free(self.backing_allocator, mem);
return err~;
}
page.memory = mem;
void* mem_start = mem::aligned_pointer(mem + DynamicArenaChunk.sizeof, alignment);
assert(mem_start + size < mem + page_size);
DynamicArenaChunk* chunk = (DynamicArenaChunk*)mem_start - 1;
chunk.size = size;
page.prev_arena = self.page;
page.total = page_size;
page.used = mem_start + size - page.memory;
self.page = page;
page.current_stack_ptr = mem_start;
return mem_start;
}

View File

@@ -32,58 +32,58 @@ fn void*? SimpleHeapAllocator.acquire(&self, usz size, AllocInitType init_type,
{ {
if (init_type == ZERO) if (init_type == ZERO)
{ {
return alignment > 0 ? @aligned_alloc_fn(self, simple_alloc_calloc, size, alignment) : simple_alloc_calloc(self, size); return alignment > 0 ? @aligned_alloc(self._calloc, size, alignment) : self._calloc(size);
} }
return alignment > 0 ? @aligned_alloc_fn(self, simple_alloc_alloc, size, alignment) : simple_alloc_alloc(self, size); return alignment > 0 ? @aligned_alloc(self._alloc, size, alignment) : self._alloc(size);
} }
fn void*? SimpleHeapAllocator.resize(&self, void* old_pointer, usz size, usz alignment) @dynamic fn void*? SimpleHeapAllocator.resize(&self, void* old_pointer, usz size, usz alignment) @dynamic
{ {
return alignment > 0 return alignment > 0
? @aligned_realloc_fn(self, simple_alloc_calloc, simple_alloc_free, old_pointer, size, alignment) ? @aligned_realloc(self._calloc, self._free, old_pointer, size, alignment)
: simple_alloc_realloc(self, old_pointer, size); : self._realloc(old_pointer, size);
} }
fn void SimpleHeapAllocator.release(&self, void* old_pointer, bool aligned) @dynamic fn void SimpleHeapAllocator.release(&self, void* old_pointer, bool aligned) @dynamic
{ {
if (aligned) if (aligned)
{ {
@aligned_free_fn(self, simple_alloc_free, old_pointer)!!; @aligned_free(self._free, old_pointer)!!;
} }
else else
{ {
simple_alloc_free(self, old_pointer); self._free(old_pointer);
} }
} }
<* <*
@require old_pointer && bytes > 0 @require old_pointer && bytes > 0
*> *>
fn void*? simple_alloc_realloc(SimpleHeapAllocator* self, void* old_pointer, usz bytes) @local fn void*? SimpleHeapAllocator._realloc(&self, void* old_pointer, usz bytes) @local
{ {
// Find the block header. // Find the block header.
Header* block = (Header*)old_pointer - 1; Header* block = (Header*)old_pointer - 1;
if (block.size >= bytes) return old_pointer; if (block.size >= bytes) return old_pointer;
void* new = simple_alloc_alloc(self, bytes)!; void* new = self._alloc(bytes)!;
usz max_to_copy = math::min(block.size, bytes); usz max_to_copy = math::min(block.size, bytes);
mem::copy(new, old_pointer, max_to_copy); mem::copy(new, old_pointer, max_to_copy);
simple_alloc_free(self, old_pointer); self._free(old_pointer);
return new; return new;
} }
fn void*? simple_alloc_calloc(SimpleHeapAllocator* self, usz bytes) @local fn void*? SimpleHeapAllocator._calloc(&self, usz bytes) @local
{ {
void* data = simple_alloc_alloc(self, bytes)!; void* data = self._alloc(bytes)!;
mem::clear(data, bytes, mem::DEFAULT_MEM_ALIGNMENT); mem::clear(data, bytes, mem::DEFAULT_MEM_ALIGNMENT);
return data; return data;
} }
fn void*? simple_alloc_alloc(SimpleHeapAllocator* self, usz bytes) @local fn void*? SimpleHeapAllocator._alloc(&self, usz bytes) @local
{ {
usz aligned_bytes = mem::aligned_offset(bytes, mem::DEFAULT_MEM_ALIGNMENT); usz aligned_bytes = mem::aligned_offset(bytes, mem::DEFAULT_MEM_ALIGNMENT);
if (!self.free_list) if (!self.free_list)
{ {
simple_alloc_add_block(self, aligned_bytes)!; self.add_block(aligned_bytes)!;
} }
Header* current = self.free_list; Header* current = self.free_list;
@@ -123,22 +123,22 @@ fn void*? simple_alloc_alloc(SimpleHeapAllocator* self, usz bytes) @local
current = current.next; current = current.next;
} }
} }
simple_alloc_add_block(self, aligned_bytes)!; self.add_block(aligned_bytes)!;
return simple_alloc_alloc(self, aligned_bytes); return self._alloc(aligned_bytes);
} }
fn void? simple_alloc_add_block(SimpleHeapAllocator* self, usz aligned_bytes) @local fn void? SimpleHeapAllocator.add_block(&self, usz aligned_bytes) @local
{ {
assert(mem::aligned_offset(aligned_bytes, mem::DEFAULT_MEM_ALIGNMENT) == aligned_bytes); assert(mem::aligned_offset(aligned_bytes, mem::DEFAULT_MEM_ALIGNMENT) == aligned_bytes);
char[] result = self.alloc_fn(aligned_bytes + Header.sizeof)!; char[] result = self.alloc_fn(aligned_bytes + Header.sizeof)!;
Header* new_block = (Header*)result.ptr; Header* new_block = (Header*)result.ptr;
new_block.size = result.len - Header.sizeof; new_block.size = result.len - Header.sizeof;
new_block.next = null; new_block.next = null;
simple_alloc_free(self, new_block + 1); self._free(new_block + 1);
} }
fn void simple_alloc_free(SimpleHeapAllocator* self, void* ptr) @local fn void SimpleHeapAllocator._free(&self, void* ptr) @local
{ {
// Empty ptr -> do nothing. // Empty ptr -> do nothing.
if (!ptr) return; if (!ptr) return;

View File

@@ -15,6 +15,7 @@ module std::core::mem::allocator @if(env::POSIX);
import std::os; import std::os;
import libc; import libc;
fn void*? LibcAllocator.acquire(&self, usz bytes, AllocInitType init_type, usz alignment) @dynamic fn void*? LibcAllocator.acquire(&self, usz bytes, AllocInitType init_type, usz alignment) @dynamic
{ {
if (init_type == ZERO) if (init_type == ZERO)
@@ -22,22 +23,22 @@ fn void*? LibcAllocator.acquire(&self, usz bytes, AllocInitType init_type, usz a
void* data @noinit; void* data @noinit;
if (alignment > mem::DEFAULT_MEM_ALIGNMENT) if (alignment > mem::DEFAULT_MEM_ALIGNMENT)
{ {
if (posix::posix_memalign(&data, alignment, bytes)) return mem::OUT_OF_MEMORY~; if (posix::posix_memalign(&data, alignment, bytes)) return mem::OUT_OF_MEMORY?;
mem::clear(data, bytes, mem::DEFAULT_MEM_ALIGNMENT); mem::clear(data, bytes, mem::DEFAULT_MEM_ALIGNMENT);
return data; return data;
} }
return libc::calloc(1, bytes) ?: mem::OUT_OF_MEMORY~; return libc::calloc(1, bytes) ?: mem::OUT_OF_MEMORY?;
} }
else else
{ {
void* data @noinit; void* data @noinit;
if (alignment > mem::DEFAULT_MEM_ALIGNMENT) if (alignment > mem::DEFAULT_MEM_ALIGNMENT)
{ {
if (posix::posix_memalign(&data, alignment, bytes)) return mem::OUT_OF_MEMORY~; if (posix::posix_memalign(&data, alignment, bytes)) return mem::OUT_OF_MEMORY?;
} }
else else
{ {
if (!(data = libc::malloc(bytes))) return mem::OUT_OF_MEMORY~; if (!(data = libc::malloc(bytes))) return mem::OUT_OF_MEMORY?;
} }
$if env::TESTING: $if env::TESTING:
for (usz i = 0; i < bytes; i++) ((char*)data)[i] = 0xAA; for (usz i = 0; i < bytes; i++) ((char*)data)[i] = 0xAA;
@@ -48,32 +49,25 @@ fn void*? LibcAllocator.acquire(&self, usz bytes, AllocInitType init_type, usz a
fn void*? LibcAllocator.resize(&self, void* old_ptr, usz new_bytes, usz alignment) @dynamic fn void*? LibcAllocator.resize(&self, void* old_ptr, usz new_bytes, usz alignment) @dynamic
{ {
if (alignment <= mem::DEFAULT_MEM_ALIGNMENT) return libc::realloc(old_ptr, new_bytes) ?: mem::OUT_OF_MEMORY~; if (alignment <= mem::DEFAULT_MEM_ALIGNMENT) return libc::realloc(old_ptr, new_bytes) ?: mem::OUT_OF_MEMORY?;
void* new_ptr;
if (posix::posix_memalign(&new_ptr, alignment, new_bytes)) return mem::OUT_OF_MEMORY?;
// Try realloc, even though it might be unaligned. $switch:
void* new_ptr = libc::realloc(old_ptr, new_bytes) ?: mem::OUT_OF_MEMORY~!; $case env::DARWIN:
usz old_usable_size = darwin::malloc_size(old_ptr);
// If it's aligned then we're done! $case env::LINUX:
uptr ptr_val = (uptr)new_ptr; usz old_usable_size = linux::malloc_usable_size(old_ptr);
if (ptr_val & (alignment - 1) == 0) return new_ptr; $default:
usz old_usable_size = new_bytes;
// We failed, so we need to use memalign $endswitch
// We will free new_ptr before we exit.
defer libc::free(new_ptr);
// Create a pointer which is sure to be aligned.
void* aligned_ptr;
if (posix::posix_memalign(&aligned_ptr, alignment, new_bytes))
{
return mem::OUT_OF_MEMORY~;
}
// Now it is safe to copy the full range of data.
mem::copy(aligned_ptr, new_ptr, new_bytes, mem::DEFAULT_MEM_ALIGNMENT, mem::DEFAULT_MEM_ALIGNMENT);
return aligned_ptr;
usz copy_size = new_bytes < old_usable_size ? new_bytes : old_usable_size;
mem::copy(new_ptr, old_ptr, copy_size, mem::DEFAULT_MEM_ALIGNMENT, mem::DEFAULT_MEM_ALIGNMENT);
libc::free(old_ptr);
return new_ptr;
} }
fn void LibcAllocator.release(&self, void* old_ptr, bool aligned) @dynamic fn void LibcAllocator.release(&self, void* old_ptr, bool aligned) @dynamic
{ {
libc::free(old_ptr); libc::free(old_ptr);
@@ -89,12 +83,12 @@ fn void*? LibcAllocator.acquire(&self, usz bytes, AllocInitType init_type, usz a
{ {
if (alignment > 0) if (alignment > 0)
{ {
return win32::_aligned_recalloc(null, 1, bytes, alignment) ?: mem::OUT_OF_MEMORY~; return win32::_aligned_recalloc(null, 1, bytes, alignment) ?: mem::OUT_OF_MEMORY?;
} }
return libc::calloc(1, bytes) ?: mem::OUT_OF_MEMORY~; return libc::calloc(1, bytes) ?: mem::OUT_OF_MEMORY?;
} }
void* data = alignment > 0 ? win32::_aligned_malloc(bytes, alignment) : libc::malloc(bytes); void* data = alignment > 0 ? win32::_aligned_malloc(bytes, alignment) : libc::malloc(bytes);
if (!data) return mem::OUT_OF_MEMORY~; if (!data) return mem::OUT_OF_MEMORY?;
$if env::TESTING: $if env::TESTING:
for (usz i = 0; i < bytes; i++) ((char*)data)[i] = 0xAA; for (usz i = 0; i < bytes; i++) ((char*)data)[i] = 0xAA;
$endif $endif
@@ -105,9 +99,9 @@ fn void*? LibcAllocator.resize(&self, void* old_ptr, usz new_bytes, usz alignmen
{ {
if (alignment) if (alignment)
{ {
return win32::_aligned_realloc(old_ptr, new_bytes, alignment) ?: mem::OUT_OF_MEMORY~; return win32::_aligned_realloc(old_ptr, new_bytes, alignment) ?: mem::OUT_OF_MEMORY?;
} }
return libc::realloc(old_ptr, new_bytes) ?: mem::OUT_OF_MEMORY~; return libc::realloc(old_ptr, new_bytes) ?: mem::OUT_OF_MEMORY?;
} }
fn void LibcAllocator.release(&self, void* old_ptr, bool aligned) @dynamic fn void LibcAllocator.release(&self, void* old_ptr, bool aligned) @dynamic
@@ -128,12 +122,12 @@ fn void*? LibcAllocator.acquire(&self, usz bytes, AllocInitType init_type, usz a
if (init_type == ZERO) if (init_type == ZERO)
{ {
void* data = alignment ? @aligned_alloc(fn void*(usz bytes) => libc::calloc(bytes, 1), bytes, alignment)!! : libc::calloc(bytes, 1); void* data = alignment ? @aligned_alloc(fn void*(usz bytes) => libc::calloc(bytes, 1), bytes, alignment)!! : libc::calloc(bytes, 1);
return data ?: mem::OUT_OF_MEMORY~; return data ?: mem::OUT_OF_MEMORY?;
} }
else else
{ {
void* data = alignment ? @aligned_alloc(libc::malloc, bytes, alignment)!! : libc::malloc(bytes); void* data = alignment ? @aligned_alloc(libc::malloc, bytes, alignment)!! : libc::malloc(bytes);
if (!data) return mem::OUT_OF_MEMORY~; if (!data) return mem::OUT_OF_MEMORY?;
$if env::TESTING: $if env::TESTING:
for (usz i = 0; i < bytes; i++) ((char*)data)[i] = 0xAA; for (usz i = 0; i < bytes; i++) ((char*)data)[i] = 0xAA;
$endif $endif
@@ -147,9 +141,9 @@ fn void*? LibcAllocator.resize(&self, void* old_ptr, usz new_bytes, usz alignmen
if (alignment) if (alignment)
{ {
void* data = @aligned_realloc(fn void*(usz bytes) => libc::malloc(bytes), libc::free, old_ptr, new_bytes, alignment)!!; void* data = @aligned_realloc(fn void*(usz bytes) => libc::malloc(bytes), libc::free, old_ptr, new_bytes, alignment)!!;
return data ?: mem::OUT_OF_MEMORY~; return data ?: mem::OUT_OF_MEMORY?;
} }
return libc::realloc(old_ptr, new_bytes) ?: mem::OUT_OF_MEMORY~; return libc::realloc(old_ptr, new_bytes) ?: mem::OUT_OF_MEMORY?;
} }

View File

@@ -127,7 +127,7 @@ fn void TempAllocator.reset(&self)
{ {
TempAllocator* old = child; TempAllocator* old = child;
child = old.derived; child = old.derived;
temp_allocator_destroy(old); old.destroy();
} }
self.capacity = self.original_capacity; self.capacity = self.original_capacity;
$if env::ADDRESS_SANITIZER: $if env::ADDRESS_SANITIZER:
@@ -142,17 +142,17 @@ fn void TempAllocator.reset(&self)
fn void TempAllocator.free(&self) fn void TempAllocator.free(&self)
{ {
self.reset(); self.reset();
temp_allocator_destroy(self); self.destroy();
} }
fn void temp_allocator_destroy(TempAllocator* self) fn void TempAllocator.destroy(&self) @local
{ {
TempAllocatorPage *last_page = self.last_page; TempAllocatorPage *last_page = self.last_page;
while (last_page) while (last_page)
{ {
TempAllocatorPage *to_free = last_page; TempAllocatorPage *to_free = last_page;
last_page = last_page.prev_page; last_page = last_page.prev_page;
_free_page(self, to_free)!!; self._free_page(to_free)!!;
} }
if (self.allocated) if (self.allocated)
{ {
@@ -179,6 +179,33 @@ fn void TempAllocator.release(&self, void* old_pointer, bool) @dynamic
} }
fn void? TempAllocator._free_page(&self, TempAllocatorPage* page) @inline @local
{
void* mem = page.start;
return self.backing_allocator.release(mem, page.is_aligned());
}
fn void*? TempAllocator._realloc_page(&self, TempAllocatorPage* page, usz size, usz alignment) @inline @local
{
// Then the actual start pointer:
void* real_pointer = page.start;
// Walk backwards to find the pointer to this page.
TempAllocatorPage **pointer_to_prev = &self.last_page;
// Remove the page from the list
while (*pointer_to_prev != page)
{
pointer_to_prev = &((*pointer_to_prev).prev_page);
}
*pointer_to_prev = page.prev_page;
usz page_size = page.pagesize();
// Clear on size > original size.
void* data = self.acquire(size, NO_ZERO, alignment)!;
if (page_size > size) page_size = size;
mem::copy(data, &page.data[0], page_size, mem::DEFAULT_MEM_ALIGNMENT, mem::DEFAULT_MEM_ALIGNMENT);
self.backing_allocator.release(real_pointer, page.is_aligned());
return data;
}
fn void*? TempAllocator.resize(&self, void* pointer, usz size, usz alignment) @dynamic fn void*? TempAllocator.resize(&self, void* pointer, usz size, usz alignment) @dynamic
{ {
@@ -188,7 +215,7 @@ fn void*? TempAllocator.resize(&self, void* pointer, usz size, usz alignment) @d
assert(self.last_page, "Realloc of non temp pointer"); assert(self.last_page, "Realloc of non temp pointer");
// First grab the page // First grab the page
TempAllocatorPage *page = pointer - TempAllocatorPage.sizeof; TempAllocatorPage *page = pointer - TempAllocatorPage.sizeof;
return _realloc_page(self, page, size, alignment); return self._realloc_page(page, size, alignment);
} }
bool is_realloc_of_last = chunk.size + pointer == &self.data[self.used]; bool is_realloc_of_last = chunk.size + pointer == &self.data[self.used];
if (is_realloc_of_last) if (is_realloc_of_last)
@@ -299,39 +326,9 @@ fn void*? TempAllocator.acquire(&self, usz size, AllocInitType init_type, usz al
return &page.data[0]; return &page.data[0];
} }
fn void? _free_page(TempAllocator* self, TempAllocatorPage* page) @inline @local
{
void* mem = page.start;
return self.backing_allocator.release(mem, page.is_aligned());
}
fn void*? _realloc_page(TempAllocator* self, TempAllocatorPage* page, usz size, usz alignment) @inline @local
{
// Then the actual start pointer:
void* real_pointer = page.start;
// Walk backwards to find the pointer to this page.
TempAllocatorPage **pointer_to_prev = &self.last_page;
// Remove the page from the list
while (*pointer_to_prev != page)
{
pointer_to_prev = &((*pointer_to_prev).prev_page);
}
*pointer_to_prev = page.prev_page;
usz page_size = page.pagesize();
// Clear on size > original size.
void* data = self.acquire(size, NO_ZERO, alignment)!;
if (page_size > size) page_size = size;
mem::copy(data, &page.data[0], page_size, mem::DEFAULT_MEM_ALIGNMENT, mem::DEFAULT_MEM_ALIGNMENT);
self.backing_allocator.release(real_pointer, page.is_aligned());
return data;
}
module std::core::mem::allocator @if((env::POSIX || env::WIN32) && $feature(VMEM_TEMP)); module std::core::mem::allocator @if((env::POSIX || env::WIN32) && $feature(VMEM_TEMP));
import std::math; import std::math;
tlocal VmemOptions temp_allocator_default_options = { tlocal VmemOptions temp_allocator_default_options = {
.shrink_on_reset = env::MEMORY_ENV != NORMAL, .shrink_on_reset = env::MEMORY_ENV != NORMAL,
.protect_unused_pages = env::COMPILER_OPT_LEVEL <= O1 || env::COMPILER_SAFE_MODE, .protect_unused_pages = env::COMPILER_OPT_LEVEL <= O1 || env::COMPILER_SAFE_MODE,
@@ -386,10 +383,10 @@ fn void TempAllocator.reset(&self)
} }
fn void TempAllocator.free(&self) fn void TempAllocator.free(&self)
{ {
_destroy(self); self.destroy();
} }
fn void _destroy(TempAllocator* self) @local fn void TempAllocator.destroy(&self) @local
{ {
TempAllocator* child = self.derived; TempAllocator* child = self.derived;
if (!child) return; if (!child) return;
@@ -406,4 +403,4 @@ fn void*? TempAllocator.resize(&self, void* pointer, usz size, usz alignment) @d
fn void TempAllocator.release(&self, void* old_pointer, bool b) @dynamic fn void TempAllocator.release(&self, void* old_pointer, bool b) @dynamic
{ {
self.vmem.release(old_pointer, b) @inline; 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 // It tracks allocations using a hash map but
// is not compatible with allocators that uses mark() // 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. // do not use it to track allocations that cross threads.
struct TrackingAllocator (Allocator) struct TrackingAllocator (Allocator)
@@ -28,8 +28,6 @@ struct TrackingAllocator (Allocator)
AllocMap map; AllocMap map;
usz mem_total; usz mem_total;
usz allocs_total; usz allocs_total;
usz usage;
usz max_usage;
} }
<* <*
@@ -72,11 +70,6 @@ fn usz TrackingAllocator.total_allocated(&self) => self.mem_total;
*> *>
fn usz TrackingAllocator.total_allocation_count(&self) => self.allocs_total; fn usz TrackingAllocator.total_allocation_count(&self) => self.allocs_total;
<*
@return "the maximum amount of memory allocated"
*>
fn usz TrackingAllocator.max_allocated(&self) => self.max_usage;
fn Allocation[] TrackingAllocator.allocations_tlist(&self, Allocator allocator) fn Allocation[] TrackingAllocator.allocations_tlist(&self, Allocator allocator)
{ {
return self.map.tvalues(); return self.map.tvalues();
@@ -95,34 +88,27 @@ fn void*? TrackingAllocator.acquire(&self, usz size, AllocInitType init_type, us
backtrace::capture_current(&bt); backtrace::capture_current(&bt);
self.map.set((uptr)data, { data, size, bt }); self.map.set((uptr)data, { data, size, bt });
self.mem_total += size; self.mem_total += size;
self.usage += size;
if (self.usage > self.max_usage) self.max_usage = self.usage;
return data; return data;
} }
fn void*? TrackingAllocator.resize(&self, void* old_pointer, usz size, usz alignment) @dynamic fn void*? TrackingAllocator.resize(&self, void* old_pointer, usz size, usz alignment) @dynamic
{ {
void* data = self.inner_allocator.resize(old_pointer, size, alignment)!; void* data = self.inner_allocator.resize(old_pointer, size, alignment)!;
self.usage -= self.map[(uptr)old_pointer]!!.size;
self.map.remove((uptr)old_pointer); self.map.remove((uptr)old_pointer);
void*[MAX_BACKTRACE] bt; void*[MAX_BACKTRACE] bt;
backtrace::capture_current(&bt); backtrace::capture_current(&bt);
self.map.set((uptr)data, { data, size, bt }); self.map.set((uptr)data, { data, size, bt });
self.mem_total += size; self.mem_total += size;
self.usage += size;
if (self.usage > self.max_usage) self.max_usage = self.usage;
self.allocs_total++; self.allocs_total++;
return data; return data;
} }
fn void TrackingAllocator.release(&self, void* old_pointer, bool is_aligned) @dynamic fn void TrackingAllocator.release(&self, void* old_pointer, bool is_aligned) @dynamic
{ {
usz? old_size = self.map[(uptr)old_pointer].size;
if (catch self.map.remove((uptr)old_pointer)) if (catch self.map.remove((uptr)old_pointer))
{ {
unreachable("Attempt to release untracked pointer %p, this is likely a bug.", old_pointer); unreachable("Attempt to release untracked pointer %p, this is likely a bug.", old_pointer);
} }
self.usage -= old_size!!;
self.inner_allocator.release(old_pointer, is_aligned); self.inner_allocator.release(old_pointer, is_aligned);
} }
@@ -191,7 +177,6 @@ fn void? TrackingAllocator.fprint_report(&self, OutStream out) => @pool()
io::fprintfn(out, "- Total current allocations: %d", entries)!; io::fprintfn(out, "- Total current allocations: %d", entries)!;
io::fprintfn(out, "- Total allocations (freed and retained): %d", self.allocs_total)!; io::fprintfn(out, "- Total allocations (freed and retained): %d", self.allocs_total)!;
io::fprintfn(out, "- Total allocated memory (freed and retained): %d", self.mem_total)!; io::fprintfn(out, "- Total allocated memory (freed and retained): %d", self.mem_total)!;
io::fprintfn(out, "- Maximum memory usage: %d", self.max_usage)!;
if (leaks) if (leaks)
{ {
io::fprintn(out)!; io::fprintn(out)!;
@@ -231,4 +216,4 @@ fn void? TrackingAllocator.fprint_report(&self, OutStream out) => @pool()
} }
} }
} }
} }

View File

@@ -22,12 +22,9 @@ struct Vmem (Allocator)
bitstruct VmemOptions : int bitstruct VmemOptions : int
{ {
<* Release memory on reset *> bool shrink_on_reset; // Release memory on reset
bool shrink_on_reset; bool protect_unused_pages; // Protect unused pages on reset
<* Protect unused pages on reset *> bool scratch_released_data; // Overwrite released data with 0xAA
bool protect_unused_pages;
<* Overwrite released data with 0xAA *>
bool scratch_released_data;
} }
<* <*
@@ -41,11 +38,11 @@ bitstruct VmemOptions : int
fn void? Vmem.init(&self, usz preferred_size, usz reserve_page_size = 0, VmemOptions options = { true, true, env::COMPILER_SAFE_MODE }, usz min_size = 0) 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; static usz page_size = 0;
if (!page_size) page_size = mem::os_pagesize(); if (!page_size) page_size = mem::os_pagesize();
if (page_size < reserve_page_size) page_size = reserve_page_size; if (page_size < reserve_page_size) page_size = reserve_page_size;
preferred_size = mem::aligned_offset(preferred_size, page_size); preferred_size = mem::aligned_offset(preferred_size, page_size);
if (!min_size) min_size = max(preferred_size / 1024, 1); if (!min_size) min_size = max(preferred_size / 1024, 1);
VirtualMemory? memory = mem::OUT_OF_MEMORY~; VirtualMemory? memory = mem::OUT_OF_MEMORY?;
while (preferred_size >= min_size) while (preferred_size >= min_size)
{ {
memory = vm::virtual_alloc(preferred_size, PROTECTED); memory = vm::virtual_alloc(preferred_size, PROTECTED);
@@ -62,7 +59,7 @@ fn void? Vmem.init(&self, usz preferred_size, usz reserve_page_size = 0, VmemOpt
break; break;
} }
} }
if (catch memory) return VMEM_RESERVE_FAILED~; if (catch memory) return VMEM_RESERVE_FAILED?;
if (page_size > preferred_size) page_size = preferred_size; if (page_size > preferred_size) page_size = preferred_size;
$if env::ADDRESS_SANITIZER: $if env::ADDRESS_SANITIZER:
asan::poison_memory_region(memory.ptr, memory.size); asan::poison_memory_region(memory.ptr, memory.size);
@@ -87,12 +84,12 @@ fn void*? Vmem.acquire(&self, usz size, AllocInitType init_type, usz alignment)
{ {
alignment = alignment_for_allocation(alignment); alignment = alignment_for_allocation(alignment);
usz total_len = self.memory.size; usz total_len = self.memory.size;
if (size > total_len) return mem::INVALID_ALLOC_SIZE~; if (size > total_len) return mem::INVALID_ALLOC_SIZE?;
void* start_mem = self.memory.ptr; void* start_mem = self.memory.ptr;
void* unaligned_pointer_to_offset = start_mem + self.allocated + VmemHeader.sizeof; void* unaligned_pointer_to_offset = start_mem + self.allocated + VmemHeader.sizeof;
void* mem = mem::aligned_pointer(unaligned_pointer_to_offset, alignment); void* mem = mem::aligned_pointer(unaligned_pointer_to_offset, alignment);
usz after = (usz)(mem - start_mem) + size; usz after = (usz)(mem - start_mem) + size;
if (after > total_len) return mem::OUT_OF_MEMORY~; if (after > total_len) return mem::OUT_OF_MEMORY?;
if (init_type == ZERO && self.high_water <= self.allocated) if (init_type == ZERO && self.high_water <= self.allocated)
{ {
init_type = NO_ZERO; init_type = NO_ZERO;
@@ -119,7 +116,7 @@ fn bool Vmem.owns_pointer(&self, void* ptr) @inline
*> *>
fn void*? Vmem.resize(&self, void *old_pointer, usz size, usz alignment) @dynamic fn void*? Vmem.resize(&self, void *old_pointer, usz size, usz alignment) @dynamic
{ {
if (size > self.memory.size) return mem::INVALID_ALLOC_SIZE~; if (size > self.memory.size) return mem::INVALID_ALLOC_SIZE?;
alignment = alignment_for_allocation(alignment); 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); 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; VmemHeader* header = old_pointer - VmemHeader.sizeof;
@@ -135,7 +132,7 @@ fn void*? Vmem.resize(&self, void *old_pointer, usz size, usz alignment) @dynami
else else
{ {
usz allocated = self.allocated + size - old_size; usz allocated = self.allocated + size - old_size;
if (allocated > self.memory.size) return mem::OUT_OF_MEMORY~; if (allocated > self.memory.size) return mem::OUT_OF_MEMORY?;
protect(self, allocated)!; protect(self, allocated)!;
} }
header.size = size; header.size = size;

View File

@@ -1,7 +1,6 @@
module std::core::string::ansi; module std::core::string::ansi;
import std::io;
constdef Ansi : inline String enum Ansi : const inline String
{ {
RESET = "\e[0m", RESET = "\e[0m",
BOLD = "\e[1m", BOLD = "\e[1m",
@@ -57,37 +56,6 @@ constdef Ansi : inline String
BG_BRIGHT_WHITE = "\e[107m", BG_BRIGHT_WHITE = "\e[107m",
} }
struct AnsiColor (Printable)
{
char r, g, b;
bool bg;
}
fn usz? AnsiColor.to_format(&self, Formatter* fmt) @dynamic
{
return fmt.printf("\e[%s8;2;%s;%s;%sm", self.bg ? 4 : 3, self.r, self.g, self.b);
}
<*
24-bit color code
@return `A struct that, when formatted with '%s', sets the foreground or background colour to the specified rgb value`
*>
fn AnsiColor get_color_rgb(char r, char g, char b, bool bg = false)
{
return {r, g, b, bg};
}
<*
24-bit color code
@return `A struct that, when formatted with '%s', sets the foreground or background colour of printed text to the specified rgb value`
*>
fn AnsiColor get_color(uint rgb, bool bg = false)
{
return {(char)(rgb >> 16), (char)((rgb & 0x00FF00) >> 8), (char)rgb, bg};
}
<* <*
8-bit color code 8-bit color code
@@ -128,7 +96,7 @@ macro String color(uint $rgb, bool $bg = false) @const
@require rgb <= 0xFF_FF_FF : `Expected a 24 bit RGB value` @require rgb <= 0xFF_FF_FF : `Expected a 24 bit RGB value`
@return `the string char for the given foreground color` @return `the string char for the given foreground color`
*> *>
fn String make_color(Allocator mem, uint rgb, bool bg = false) @deprecated("use get_color instead") 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); return make_color_rgb(mem, (char)(rgb >> 16), (char)((rgb & 0xFF00) >> 8), (char)rgb, bg);
} }
@@ -139,7 +107,7 @@ fn String make_color(Allocator mem, uint rgb, bool bg = false) @deprecated("use
@require rgb <= 0xFF_FF_FF : `Expected a 24 bit RGB value` @require rgb <= 0xFF_FF_FF : `Expected a 24 bit RGB value`
@return `the string char for the given foreground color` @return `the string char for the given foreground color`
*> *>
fn String make_tcolor(uint rgb, bool bg = false) @deprecated("use get_color instead") fn String make_tcolor(uint rgb, bool bg = false)
{ {
return make_color_rgb(tmem, (char)(rgb >> 16), (char)((rgb & 0xFF00) >> 8), (char)rgb, bg); return make_color_rgb(tmem, (char)(rgb >> 16), (char)((rgb & 0xFF00) >> 8), (char)rgb, bg);
} }
@@ -149,7 +117,7 @@ fn String make_tcolor(uint rgb, bool bg = false) @deprecated("use get_color inst
@return `the string char for the given foreground color` @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) @deprecated("use get_color_rgb instead") 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); return string::format(mem, "\e[%s8;2;%s;%s;%sm", bg ? 4 : 3, r, g, b);
} }
@@ -159,7 +127,7 @@ fn String make_color_rgb(Allocator mem, char r, char g, char b, bool bg = false)
@return `the string char for the given foreground color` @return `the string char for the given foreground color`
*> *>
fn String make_tcolor_rgb(char r, char g, char b, bool bg = false) @deprecated("use get_color_rgb instead") 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); 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; 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 Returns true if the array contains at least one element, else false
@param [in] array @param [in] array
@param [in] element @param [in] element
@require $kindof(array) == SLICE || $kindof(array) == ARRAY @require @typekind(array) == SLICE || @typekind(array) == ARRAY
@require @typematch(array[0], element) : "array and element must have the same type" @require @typeis(array[0], $typeof(element)) : "array and element must have the same type"
*> *>
macro bool contains(array, element) macro bool contains(array, element)
{ {
@@ -15,30 +15,26 @@ macro bool contains(array, element)
{ {
if (*item == element) return true; if (*item == element) return true;
} }
return false; return false;
} }
<* <*
Return the first index of element found in the array, searching from the start. Return the first index of element found in the array, searching from the start.
@param [in] array @param [in] array
@param [in] element @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 "the first index of the element"
@return? NOT_FOUND @return? NOT_FOUND
*> *>
macro usz? index_of(array, element) macro index_of(array, element)
{ {
foreach (i, &e : array) foreach (i, &e : array)
{ {
if (*e == element) return i; if (*e == element) return i;
} }
return NOT_FOUND~; return NOT_FOUND?;
} }
<* <*
Slice a 2d array and create a Slice2d from it. 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 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" @param ylen : "The length of the slice in y, defaults to the length of the array"
@return "A Slice2d from the array" @return "A Slice2d from the array"
@require $kindof(array_ptr) == POINTER @require @typekind(array_ptr) == POINTER
@require $kindof(*array_ptr) == VECTOR || $kindof(*array_ptr) == ARRAY @require @typekind(*array_ptr) == VECTOR || @typekind(*array_ptr) == ARRAY
@require $kindof((*array_ptr)[0]) == VECTOR || $kindof((*array_ptr)[0]) == ARRAY @require @typekind((*array_ptr)[0]) == VECTOR || @typekind((*array_ptr)[0]) == ARRAY
*> *>
macro slice2d(array_ptr, x = 0, xlen = 0, y = 0, ylen = 0) macro slice2d(array_ptr, x = 0, xlen = 0, y = 0, ylen = 0)
{ {
@@ -63,31 +59,30 @@ 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. Return the first index of element found in the array, searching in reverse from the end.
@param [in] array @param [in] array
@param [in] element @param [in] element
@return "the last index of the element" @return "the last index of the element"
@return? NOT_FOUND @return? NOT_FOUND
*> *>
macro usz? rindex_of(array, element) macro rindex_of(array, element)
{ {
foreach_r (i, &e : array) foreach_r (i, &e : array)
{ {
if (*e == element) return i; if (*e == element) return i;
} }
return NOT_FOUND~; return NOT_FOUND?;
} }
<* <*
Concatenate two arrays or slices, returning a slice containing the concatenation of them. Concatenate two arrays or slices, returning a slice containing the concatenation of them.
@param [in] arr1 @param [in] arr1
@param [in] arr2 @param [in] arr2
@param [&inout] allocator : "The allocator to use, default is the heap allocator" @param [&inout] allocator : "The allocator to use, default is the heap allocator"
@require $kindof(arr1) == SLICE || $kindof(arr1) == ARRAY @require @typekind(arr1) == SLICE || @typekind(arr1) == ARRAY
@require $kindof(arr2) == SLICE || $kindof(arr2) == ARRAY @require @typekind(arr2) == SLICE || @typekind(arr2) == ARRAY
@require @typematch(arr1[0], arr2[0]) : "Arrays must have the same type" @require @typeis(arr1[0], $typeof(arr2[0])) : "Arrays must have the same type"
@ensure result.len == arr1.len + arr2.len @ensure result.len == arr1.len + arr2.len
*> *>
macro concat(Allocator allocator, arr1, arr2) @nodiscard macro concat(Allocator allocator, arr1, arr2) @nodiscard
@@ -104,516 +99,15 @@ macro concat(Allocator allocator, arr1, arr2) @nodiscard
} }
return result; return result;
} }
<* <*
Concatenate two arrays or slices, returning a slice containing the concatenation of them, Concatenate two arrays or slices, returning a slice containing the concatenation of them,
allocated using the temp allocator. allocated using the temp allocator.
@param [in] arr1 @param [in] arr1
@param [in] arr2 @param [in] arr2
@require $kindof(arr1) == SLICE || $kindof(arr1) == ARRAY @require @typekind(arr1) == SLICE || @typekind(arr1) == ARRAY
@require $kindof(arr2) == SLICE || $kindof(arr2) == ARRAY @require @typekind(arr2) == SLICE || @typekind(arr2) == ARRAY
@require @typematch(arr1[0], arr2[0]) : "Arrays must have the same type" @require @typeis(arr1[0], $typeof(arr2[0])) : "Arrays must have the same type"
@ensure return.len == arr1.len + arr2.len @ensure return.len == arr1.len + arr2.len
*> *>
macro tconcat(arr1, arr2) @nodiscard => concat(tmem, arr1, arr2); macro tconcat(arr1, arr2) @nodiscard => concat(tmem, arr1, arr2);
<*
Apply a reduction/folding operation to an iterable type. This walks along the input array
and applies an `#operation` to each value, returning it to the `identity` (or "accumulator")
base value.
For example:
```c3
int[] my_slice = { 1, 8, 12 };
int folded = array::@reduce(my_slice, 2, fn (i, e) => i * e);
assert(folded == (2 * 1 * 8 * 12));
```
Notice how the given `identity` value started the multiplication chain at 2. When enumerating
`my_slice`, each element is accumulated onto the `identity` value with each sequential iteration.
```
i = 2; // identity value
i *= 1; // my_slice[0]
i *= 8; // my_slice[1]
i *= 12; // my_slice[2]
```
@param [in] array
@param identity
@param #operation : "The reduction/folding lambda function or function pointer to apply."
@require @is_valid_list(array) : "Expected a valid list"
@require $defined($typefrom(@reduce_fn(array, identity)) $func = #operation) : "Invalid lambda or function pointer type"
*>
macro @reduce(array, identity, #operation)
{
$typefrom(@reduce_fn(array, identity)) $func = #operation;
foreach (index, element : array) identity = $func(identity, element, index);
return identity;
}
<*
Apply a summation operator (+) to an identity value across a span of array elements
and return the final accumulated result.
@pure
@param [in] array
@param identity_value : "The base accumulator value to use for the sum"
@require @is_valid_list(array) : "Expected a valid list"
@require $defined(array[0] + array[0]) : "Array element type must implement the '+' operator"
@require $defined($typeof(array[0]) t = identity_value) : "The identity type must be assignable to the array element type"
*>
macro @sum(array, identity_value = 0)
{
return @reduce(array, ($typeof(array[0]))identity_value, fn (acc, e, u) => acc + e);
}
<*
Apply a product operator (*) to an identity value across a span of array elements
and return the final accumulated result.
@pure
@param [in] array
@param identity_value : "The base accumulator value to use for the product"
@require @is_valid_list(array) : "Expected a valid list"
@require $defined(array[0] * array[0]) : "Array element type must implement the '*' operator"
@require $defined($typeof(array[0]) t = identity_value) : "The identity type must be assignable to the array element type"
*>
macro @product(array, identity_value = 1)
{
return @reduce(array, ($typeof(array[0]))identity_value, fn (acc, e, u) => acc * e);
}
<*
Applies a given predicate function to each element of an array and returns a new
array of `usz` values, each element representing an index within the original array
where the predicate returned `true`.
The `.len` value of the returned array can also be used to quickly identify how many
input array elements matched the predicate.
For example:
```c3
int[] arr = { 0, 20, 4, 30 };
int[] matched_indices = array::@indices_of(mem, arr, fn (u, a) => a > 10);
```
The `matched_indices` variable should contain a dynamically-allocated array of `[1, 3]`,
and thus its count indicates that 2 of the 4 elements matched the predicate condition.
@param [&inout] allocator
@param [in] array
@param #predicate
@require @is_valid_list(array) : "Expected a valid list"
@require $defined($typefrom(@predicate_fn(array)) p = #predicate)
*>
macro usz[] @indices_of(Allocator allocator, array, #predicate)
{
usz[] results = allocator::new_array(allocator, usz, lengthof(array));
usz matches;
$typefrom(@predicate_fn(array)) $predicate = #predicate;
foreach (index, element : array)
{
if ($predicate(element, index)) results[matches++] = index;
}
return results[:matches];
}
<*
Array `@indices_of` using the temp allocator.
@param [in] array
@param #predicate
@require @is_valid_list(array) : "Expected a valid list"
@require $defined($typefrom(@predicate_fn(array)) p = #predicate)
*>
macro usz[] @tindices_of(array, #predicate)
{
return @indices_of(tmem, array, #predicate);
}
<*
Applies a predicate function to each element of an input array and returns a new array
containing shallow copies of _only_ the elements for which the predicate function returned
a `true` value.
For example:
```c3
int[] my_arr = { 1, 2, 4, 10, 11, 45 };
int[] evens = array::@filter(mem, my_arr, fn (e, u) => !(e % 2));
assert(evens == (int[]){2, 4, 10 });
```
@param [&inout] allocator
@param [in] array
@param #predicate
@require @is_valid_list(array) : "Expected a valid list"
@require $defined($typefrom(@predicate_fn(array)) p = #predicate)
*>
macro @filter(Allocator allocator, array, #predicate) @nodiscard
{
var $InnerType = $typeof(array[0]);
usz[] matched_indices = @indices_of(allocator, array, #predicate);
defer allocator::free(allocator, matched_indices.ptr); // can free this upon leaving this call
if (!matched_indices.len) return ($InnerType[]){};
$InnerType[] result = allocator::new_array(allocator, $InnerType, matched_indices.len);
foreach (i, index : matched_indices) result[i] = array[index];
return result;
}
<*
Array `@filter` using the temp allocator.
@param [in] array
@param #predicate
@require @is_valid_list(array) : "Expected a valid list"
@require $defined($typefrom(@predicate_fn(array)) p = #predicate)
*>
macro @tfilter(array, #predicate) @nodiscard
{
return @filter(tmem, array, #predicate);
}
<*
Returns `true` if _any_ element of the input array returns `true` when
the `#predicate` function is applied.
@param [in] array
@param #predicate
@require @is_valid_list(array) : "Expected a valid list"
@require $defined($typefrom(@predicate_fn(array)) p = #predicate)
*>
macro bool @any(array, #predicate)
{
$typefrom(@predicate_fn(array)) $predicate = #predicate;
foreach (index, element : array) if ($predicate(element, index)) return true;
return false;
}
<*
Returns `true` if _all_ elements of the input array return `true` when
the `#predicate` function is applied.
@param [in] array
@param #predicate
@require @is_valid_list(array) : "Expected a valid list"
@require $defined($typefrom(@predicate_fn(array)) p = #predicate)
*>
macro bool @all(array, #predicate)
{
$typefrom(@predicate_fn(array)) $predicate = #predicate;
foreach (index, element : array) if (!$predicate(element, index)) return false;
return true;
}
<*
Extract a copy of all even-index elements from an input array.
@param [&inout] allocator : "The allocator used to create the return array."
@param [in] array : "The array from which to extract all even elements."
@require @is_valid_list(array) : "Expected a valid list"
@require $defined(array[:lengthof(array)]) : "Expected a sliceable list"
*>
macro even(Allocator allocator, array)
{
return unlace_impl{$typeof(array[0])}(allocator, array[:lengthof(array)]);
}
<*
Extract a copy of all odd-index elements from an input array.
@param [&inout] allocator : "The allocator used to create the return array."
@param [in] array : "The array from which to extract all odd elements."
@require @is_valid_list(array) : "Expected a valid list"
@require $defined(array[:lengthof(array)]) : "Expected a sliceable list"
*>
macro odd(Allocator allocator, array)
{
return unlace_impl{$typeof(array[0])}(allocator, lengthof(array) > 1 ? array[1..] : ($typeof(array[0])[]){});
}
<*
Private implementation of `even` and `odd` macros, expecting a slice and returning one as well.
This function always extracts the even elements of the input slice.
@param [&inout] allocator : "The allocator used to create the return array."
@param [in] array : "The array from which to extract all odd elements."
*>
fn Type[] unlace_impl(Allocator allocator, Type[] array) <Type> @private
{
usz new_len = array.len / 2 + (array.len % 2 == 0 ? 0 : 1);
if (new_len == 0) return (Type[]){};
Type[] new_array = allocator::new_array(allocator, Type, new_len);
foreach (x, &new : new_array) *new = types::implements_copy(Type) ??? array[x * 2].copy(allocator) : array[x * 2];
return new_array[:new_len];
}
<*
Unlace or partition an input list into its component parts such that `[a, b, c, d, e]` becomes
`[a, c, e]` and `[b, d]`. Returned arrays are allocated by the given allocator and are returned
via two `out` parameters, `left` and `right`.
@param [&inout] allocator : "The allocator used to create the returned arrays."
@param [in] array : "The input array to unlace."
@param [out] left : "Stores a copy of all even-index array elements."
@param [out] right : "Stores a copy of all odd-index array elements."
@require @is_valid_list(array) : "Expected a valid list"
@require $typeof(left) == $typeof(array[0])[]*
@require $typeof(right) == $typeof(array[0])[]*
*>
macro unlace(Allocator allocator, array, left, right)
{
if (left) *left = even(allocator, array);
if (right) *right = odd(allocator, array);
}
<*
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
}

View File

@@ -112,45 +112,3 @@ const char[256] HEX_VALUE = {
const char[256] TO_UPPER @private = { ['a'..'z'] = 'a' - 'A' }; const char[256] TO_UPPER @private = { ['a'..'z'] = 'a' - 'A' };
const char[256] TO_LOWER @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) @const => !!($c < 128) & !!($set & (AsciiCharset)(1ULL << $c));
macro AsciiCharset @combine_sets(AsciiCharset $first, AsciiCharset... $sets) @const
{
var $res = $first;
$foreach $c : $sets:
$res |= $c;
$endforeach
return $res;
}
fn AsciiCharset combine_sets(AsciiCharset first, AsciiCharset... sets)
{
foreach (c : sets) first |= c;
return first;
}
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");
const AsciiCharset ALPHA_UPPER_SET = @create_set("ABCDEFGHIJKLMNOPQRSTUVWXYZ");
const AsciiCharset ALPHA_LOWER_SET = @create_set("abcdefghijklmnopqrstuvwxyz");
const AsciiCharset ALPHA_SET = @combine_sets(ALPHA_UPPER_SET, ALPHA_LOWER_SET);
const AsciiCharset ALPHANUMERIC_SET = @combine_sets(ALPHA_SET, NUMBER_SET);

View File

@@ -2,7 +2,7 @@
// Use of this source code is governed by the MIT license // Use of this source code is governed by the MIT license
// a copy of which can be found in the LICENSE_STDLIB file. // a copy of which can be found in the LICENSE_STDLIB file.
module std::core::bitorder; module std::core::bitorder;
import std::bits;
// This module contains types of different endianness. // This module contains types of different endianness.
// *BE types represent big-endian types // *BE types represent big-endian types
// *LE types represent little-endian types. // *LE types represent little-endian types.
@@ -87,91 +87,39 @@ bitstruct UInt128LE : uint128 @littleendian
uint128 val : 0..127; uint128 val : 0..127;
} }
<*
@require $defined(*bytes) : "Pointer must be possible to dereference"
@require types::is_intlike($typeof(*bytes)) : "Type must be an integer or int vector"
*>
macro load_be(bytes)
{
$if env::BIG_ENDIAN:
return mem::load(bytes, $align: 1);
$else
return bswap(mem::load(bytes, $align: 1));
$endif
}
<*
@require $defined(*bytes) : "Pointer must be possible to dereference"
@require types::is_intlike($typeof(*bytes)) : "Type must be an integer or int vector"
*>
macro load_le(bytes)
{
$if env::BIG_ENDIAN:
return bswap(mem::load(bytes, $align: 1));
$else
return mem::load(bytes, $align: 1);
$endif
}
<*
@require types::is_intlike($typeof(value)) : "Type must be an integer or int vector"
*>
macro void store_be(void* dst, value)
{
$if env::BIG_ENDIAN:
mem::store(($typeof(value)*)dst, value, $align: 1);
$else
mem::store(($typeof(value)*)dst, bswap(value), $align: 1);
$endif
}
<*
@require types::is_intlike($typeof(value)) : "Type must be an integer or int vector"
*>
macro void store_le(void* dst, value)
{
$if env::BIG_ENDIAN:
mem::store(($typeof(value)*)dst, bswap(value), $align: 1);
$else
mem::store(($typeof(value)*)dst, value, $align: 1);
$endif
}
<* <*
@require @is_array_or_slice_of_char(bytes) : "argument must be an array, a pointer to an array or a slice of char" @require @is_array_or_slice_of_char(bytes) : "argument must be an array, a pointer to an array or a slice of char"
@require is_bitorder($Type) : "type must be a bitorder integer" @require is_bitorder($Type) : "type must be a bitorder integer"
@require $defined(*bytes) ||| $defined(bytes[:$Type.sizeof]) : "Data is too short to contain value"
*> *>
macro read(bytes, $Type) macro read(bytes, $Type)
{ {
char *ptr; char[] s;
$switch $kindof(bytes): $switch @typekind(bytes):
$case POINTER: $case POINTER:
ptr = bytes; s = (*bytes)[:$Type.sizeof];
$default: $default:
ptr = bytes[..].ptr; s = bytes[:$Type.sizeof];
$endswitch $endswitch
return bitcast(mem::load((char[$Type.sizeof]*)ptr, $align: 1), $Type).val; return bitcast(*(char[$Type.sizeof]*)s.ptr, $Type).val;
} }
<* <*
@require @is_arrayptr_or_slice_of_char(bytes) : "argument must be a pointer to an array or a slice of char" @require @is_arrayptr_or_slice_of_char(bytes) : "argument must be a pointer to an array or a slice of char"
@require is_bitorder($Type) : "type must be a bitorder integer" @require is_bitorder($Type) : "type must be a bitorder integer"
@require $defined(*bytes) ||| $defined(bytes[:$Type.sizeof]) : "Data is not sufficent to hold value"
*> *>
macro write(x, bytes, $Type) macro write(x, bytes, $Type)
{ {
char *ptr; char[] s;
$switch $kindof(bytes): $switch @typekind(bytes):
$case POINTER: $case POINTER:
ptr = bytes; s = (*bytes)[:$Type.sizeof];
$default: $default:
ptr = bytes[..].ptr; s = bytes[:$Type.sizeof];
$endswitch $endswitch
mem::store(($typeof(x)*)ptr, bitcast(x, $Type).val, 1); *($typeof(x)*)s.ptr = bitcast(x, $Type).val;
} }
macro bool is_bitorder($Type) macro is_bitorder($Type)
{ {
$switch $Type: $switch $Type:
$case UShortLE: $case UShortLE:
@@ -233,4 +181,4 @@ macro bool @is_arrayptr_or_slice_of_char(#bytes) @const
$default: $default:
return false; return false;
$endswitch $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 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 You can use `@is_empty_macro_slot()` and `@is_valid_macro_slot()` to figure out whether
the argument has been used or not. the argument has been used or not.
@@ -24,15 +24,11 @@ macro foo(a, #b = EMPTY_MACRO_SLOT)
$endif $endif
} }
*> *>
const EmptySlot EMPTY_MACRO_SLOT @builtin @deprecated("Use `#arg = ...` instead.") = null; const EmptySlot EMPTY_MACRO_SLOT @builtin = null;
typedef EmptySlot @constinit = void*; typedef EmptySlot = void*;
macro bool @is_empty_macro_slot(#arg) @const @builtin macro @is_empty_macro_slot(#arg) @const @builtin => @typeis(#arg, EmptySlot);
@deprecated("Use `#arg = ...` to define an optional macro slot, and `$defined(#arg)` to detect whether the argument is set.") macro @is_valid_macro_slot(#arg) @const @builtin => !@typeis(#arg, EmptySlot);
=> $typeof(#arg) == EmptySlot;
macro bool @is_valid_macro_slot(#arg) @const @builtin
@deprecated("Use `#arg = ...` to define an optional macro slot, and `$defined(#arg)` to detect whether the argument is set.")
=> $typeof(#arg) != EmptySlot;
<* <*
Returns a random value at compile time. Returns a random value at compile time.
@@ -73,7 +69,7 @@ alias VoidFn = fn void();
macro scope. macro scope.
@param #variable : `the variable to store and restore` @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 macro void @scope(#variable; @body) @builtin
{ {
@@ -97,56 +93,22 @@ macro usz bitsizeof($Type) @builtin @const => $Type.sizeof * 8u;
macro usz @bitsizeof(#expr) @builtin @const => $sizeof(#expr) * 8u; macro usz @bitsizeof(#expr) @builtin @const => $sizeof(#expr) * 8u;
<*
Compile-time check for whether a set of constants contains a certain expression.
@param #needle : "The expression whose value should be located."
*>
macro bool @in(#needle, ...) @builtin @const
{
$for var $x = 0; $x < $vacount; $x++:
$assert $defined(#needle == $vaconst[$x])
: "Index %s: types '%s' (needle) and '%s' are not equatable", $x, $typeof(#needle), $typeof($vaconst[$x]);
$if #needle == $vaconst[$x]: return true; $endif
$endfor
return false;
}
<* <*
Convert an `any` type to a type, returning an failure if there is a type mismatch. Convert an `any` type to a type, returning an failure if there is a type mismatch.
@param v : `the any to convert to the given type.` @param v : `the any to convert to the given type.`
@param $Type : `the type to convert to` @param $Type : `the type to convert to`
@return `The any.ptr converted to its type.` @return `The any.ptr converted to its type.`
@ensure $typeof(return) == $Type* @ensure @typeis(return, $Type*)
@return? TYPE_MISMATCH @return? TYPE_MISMATCH
*> *>
macro anycast(any v, $Type) @builtin macro anycast(any v, $Type) @builtin
{ {
if (v.type != $Type.typeid) return TYPE_MISMATCH~; if (v.type != $Type.typeid) return TYPE_MISMATCH?;
return ($Type*)v.ptr; return ($Type*)v.ptr;
} }
<* macro bool @assignable_to(#foo, $Type) @const @builtin => $defined(*&&($Type){} = #foo);
@return "The value in the pointer"
@return? TYPE_MISMATCH
*>
macro any.to(self, $Type)
{
if (self.type != $Type.typeid) return TYPE_MISMATCH~;
return *($Type*)self.ptr;
}
<*
@require self.type == $Type : "The 'any' contained an unexpected type."
@return "The value in the pointer"
*>
macro any.as(self, $Type)
{
return *($Type*)self.ptr;
}
macro bool @assignable_to(#foo, $Type) @const @builtin @deprecated("use '$defined($Type x = #foo)'") => $defined(*&&($Type){} = #foo);
macro @addr(#val) @builtin macro @addr(#val) @builtin
{ {
@@ -162,26 +124,23 @@ macro typeid @typeid(#value) @const @builtin
return $typeof(#value).typeid; return $typeof(#value).typeid;
} }
macro TypeKind @typekind(#value) @const @builtin @deprecated("Use `$kindof(#value)`.") macro TypeKind @typekind(#value) @const @builtin
{ {
return $kindof(#value); return $typeof(#value).kindof;
} }
macro bool @typeis(#value, $Type) @const @builtin @deprecated("Use `$typeof(#value) == $Type` instead.") macro bool @typeis(#value, $Type) @const @builtin
{ {
return $typeof(#value).typeid == $Type.typeid; return $typeof(#value).typeid == $Type.typeid;
} }
fn bool print_backtrace(String message, int backtraces_to_ignore, void *added_backtrace = null) @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)
{ {
void*[256] buffer; void*[256] buffer;
void*[] backtraces = backtrace::capture_current(&buffer); void*[] backtraces = backtrace::capture_current(&buffer);
if (added_backtrace) backtraces_to_ignore++;
{ @stack_mem(2048; Allocator mem)
backtraces[++backtraces_to_ignore] = added_backtrace;
}
@stack_mem(4096; Allocator mem)
{ {
BacktraceList? backtrace = backtrace::symbolize_backtrace(mem, backtraces); BacktraceList? backtrace = backtrace::symbolize_backtrace(mem, backtraces);
if (catch backtrace) return false; if (catch backtrace) return false;
@@ -212,7 +171,6 @@ fn bool print_backtrace(String message, int backtraces_to_ignore, void *added_ba
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)
{ {
in_panic = true;
$if $defined(io::stderr) && env::PANIC_MSG: $if $defined(io::stderr) && env::PANIC_MSG:
if (!print_backtrace(message, 2)) if (!print_backtrace(message, 2))
{ {
@@ -229,7 +187,7 @@ macro void abort(String string = "Unrecoverable error reached", ...) @format(0)
$$trap(); $$trap();
} }
bool in_panic @private = false; 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)
{ {
@@ -327,7 +285,7 @@ macro any.as_inner(&self)
@param $Type : "the type to cast to" @param $Type : "the type to cast to"
@require $sizeof(expr) == $Type.sizeof : "Cannot bitcast between types of different size." @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 macro bitcast(expr, $Type) @builtin
{ {
@@ -344,7 +302,7 @@ macro bitcast(expr, $Type) @builtin
@param $Type : `The type of the enum` @param $Type : `The type of the enum`
@param [in] enum_name : `The name of the enum to search for` @param [in] enum_name : `The name of the enum to search for`
@require $Type.kindof == ENUM : `Only enums may be used` @require $Type.kindof == ENUM : `Only enums may be used`
@ensure $typeof(return) == $Type* @ensure @typeis(return, $Type)
@return? NOT_FOUND @return? NOT_FOUND
*> *>
macro enum_by_name($Type, String enum_name) @builtin macro enum_by_name($Type, String enum_name) @builtin
@@ -354,15 +312,15 @@ macro enum_by_name($Type, String enum_name) @builtin
{ {
if (name == enum_name) return $Type.from_ordinal(i); if (name == enum_name) return $Type.from_ordinal(i);
} }
return NOT_FOUND~; return NOT_FOUND?;
} }
<* <*
@param $Type : `The type of the enum` @param $Type : `The type of the enum`
@require $Type.kindof == ENUM : `Only enums may be used` @require $Type.kindof == ENUM : `Only enums may be used`
@require $defined($Type.#value) : `Expected '#value' to match an enum associated value` @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` @require @assignable_to(value, $typeof(($Type){}.#value)) : `Expected the value to match the type of the associated value`
@ensure $typeof(return) == $Type* @ensure @typeis(return, $Type)
@return? NOT_FOUND @return? NOT_FOUND
*> *>
macro @enum_from_value($Type, #value, value) @builtin @deprecated("Use Enum.lookup_field and Enum.lookup") macro @enum_from_value($Type, #value, value) @builtin @deprecated("Use Enum.lookup_field and Enum.lookup")
@@ -371,7 +329,7 @@ macro @enum_from_value($Type, #value, value) @builtin @deprecated("Use Enum.look
{ {
if (e.#value == value) return e; if (e.#value == value) return e;
} }
return NOT_FOUND~; return NOT_FOUND?;
} }
<* <*
@@ -414,7 +372,7 @@ macro bool @unlikely(bool #value, $probability = 1.0) @builtin
<* <*
@require values::@is_int(#value) || values::@is_bool(#value) @require values::@is_int(#value) || values::@is_bool(#value)
@require $defined($typeof(#value) v = expected) @require @assignable_to(expected, $typeof(#value))
@require $probability >= 0 && $probability <= 1.0 @require $probability >= 0 && $probability <= 1.0
*> *>
macro @expect(#value, expected, $probability = 1.0) @builtin macro @expect(#value, expected, $probability = 1.0) @builtin
@@ -455,25 +413,11 @@ macro @prefetch(void* ptr, PrefetchLocality $locality = VERY_NEAR, bool $write =
$endif $endif
} }
<*
Shuffle a vector by its index
int[<4>] a = { 1, 2, 3, 4 };
assert(swizzle(a, 0, 1, 1, 3) == (int[<4>]) { 1, 2, 2, 4 });
*>
macro swizzle(v, ...) @builtin macro swizzle(v, ...) @builtin
{ {
return $$swizzle(v, $vasplat); return $$swizzle(v, $vasplat);
} }
<*
Shuffle two vectors by a common index from arranging the vectors sequentially in memory
int[<4>] a = { 1, 2, 3, 4 };
int[<4>] b = { 100, 1000, 10000, 100000 };
assert(swizzle2(a, b, 0, 1, 4, 6, 2) == (int[<5>]) { 1, 2, 100, 10000, 3 });
*>
macro swizzle2(v, v2, ...) @builtin macro swizzle2(v, v2, ...) @builtin
{ {
return $$swizzle2(v, v2, $vasplat); return $$swizzle2(v, v2, $vasplat);
@@ -486,7 +430,7 @@ macro swizzle2(v, v2, ...) @builtin
@require types::is_int($typeof($value)) : "Input value must be an integer" @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" @require $sizeof($value) * 8 <= 128 : "Input value must be 128 bits wide or lower"
*> *>
macro uint @clz($value) @builtin @const macro @clz($value) @builtin @const
{ {
$if $value == 0: $if $value == 0:
return $sizeof($value) * 8; // it's all leading zeroes return $sizeof($value) * 8; // it's all leading zeroes
@@ -510,7 +454,7 @@ macro uint @clz($value) @builtin @const
Return the excuse in the Optional if it is Empty, otherwise Return the excuse in the Optional if it is Empty, otherwise
return a null fault. 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 macro fault @catch(#expr) @builtin
{ {
@@ -522,7 +466,7 @@ macro fault @catch(#expr) @builtin
Check if an Optional expression holds a value or is empty, returning true Check if an Optional expression holds a value or is empty, returning true
if it has a value. 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 macro bool @ok(#expr) @builtin
{ {
@@ -536,12 +480,12 @@ macro bool @ok(#expr) @builtin
@require $defined(#v = #v) : "#v must be a variable" @require $defined(#v = #v) : "#v must be a variable"
@require $defined(#expr!) : "Expected an optional expression" @require $defined(#expr!) : "Expected an optional expression"
@require $defined(#v = #expr!!) : `Type of #expr must be an optional of #v's type` @require @assignable_to(#expr!!, $typeof(#v)) : `Type of #expr must be an optional of #v's type`
*> *>
macro void? @try(#v, #expr) @builtin @maydiscard macro void? @try(#v, #expr) @builtin
{ {
var res = #expr; var res = #expr;
if (catch err = res) return err~; if (catch err = res) return err?;
#v = res; #v = res;
} }
@@ -556,7 +500,7 @@ macro void? @try(#v, #expr) @builtin @maydiscard
{ {
char[] data; char[] data;
// Read until end of file // Read until end of file
if (@try_catch(data, load_line(), io::EOF)!) break; if (@try_catch(data, load_line(), io::EOF)) break;
.. use data .. .. use data ..
} }
@@ -577,7 +521,7 @@ macro void? @try(#v, #expr) @builtin @maydiscard
@require $defined(#v = #v) : "#v must be a variable" @require $defined(#v = #v) : "#v must be a variable"
@require $defined(#expr!) : "Expected an optional expression" @require $defined(#expr!) : "Expected an optional expression"
@require $defined(#v = #expr!!) : `Type of #expr must be an optional of #v's type` @require @assignable_to(#expr!!, $typeof(#v)) : `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." @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 macro bool? @try_catch(#v, #expr, fault expected_fault) @builtin
@@ -585,7 +529,7 @@ macro bool? @try_catch(#v, #expr, fault expected_fault) @builtin
var res = #expr; var res = #expr;
if (catch err = res) if (catch err = res)
{ {
return err == expected_fault ? true : err~; return err == expected_fault ? true : err?;
} }
#v = res; #v = res;
return false; return false;
@@ -603,27 +547,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_upper(String $str) @builtin => $$str_upper($str);
macro String @str_lower(String $str) @builtin => $$str_lower($str); macro String @str_lower(String $str) @builtin => $$str_lower($str);
macro uint @str_hash(String $str) @builtin => $$str_hash($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) macro @generic_hash_core(h, value)
{ {
@@ -633,7 +556,7 @@ macro @generic_hash_core(h, value)
return h; return h;
} }
macro uint @generic_hash(value) macro @generic_hash(value)
{ {
uint h = @generic_hash_core((uint)0x3efd4391, value); uint h = @generic_hash_core((uint)0x3efd4391, value);
$for var $cnt = 4; $cnt < $sizeof(value); $cnt += 4: $for var $cnt = 4; $cnt < $sizeof(value); $cnt += 4:
@@ -686,7 +609,7 @@ macro uint char[].hash(char[] c) => (uint)a5hash::hash(c);
macro uint void*.hash(void* ptr) => @generic_hash(((ulong)(uptr)ptr)); 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 macro uint hash_array(array_ptr) @local
{ {
@@ -700,11 +623,11 @@ macro uint hash_array(array_ptr) @local
} }
<* <*
@require $kindof(vec) == VECTOR @require @typekind(vec) == VECTOR
*> *>
macro uint hash_vec(vec) @local macro uint hash_vec(vec) @local
{ {
var $len = $sizeof(vec); var $len = $sizeof(vec.len * $typeof(vec).inner.sizeof);
$if $len > 16: $if $len > 16:
return (uint)komi::hash(((char*)&&vec)[:$len]); return (uint)komi::hash(((char*)&&vec)[:$len]);
@@ -997,73 +920,57 @@ macro void* get_returnaddress(int n)
} }
module std::core::builtin @if((env::LINUX || env::ANDROID || env::DARWIN) && env::COMPILER_SAFE_MODE && env::DEBUG_SYMBOLS); module std::core::builtin @if((env::LINUX || env::ANDROID || env::DARWIN) && env::COMPILER_SAFE_MODE && env::DEBUG_SYMBOLS);
import libc, std::io, std::os::posix; import libc, std::io;
fn void sig_panic(String message) fn void sig_panic(String message)
{ {
default_panic(message, "???", "???", 0); default_panic(message, "???", "???", 0);
} }
fn void sig_bus_error(CInt i, void* info, void* context) SignalFunction old_bus_error;
SignalFunction old_segmentation_fault;
fn void sig_bus_error(CInt i)
{ {
$if !env::NATIVE_STACKTRACE: $if !env::NATIVE_STACKTRACE:
sig_panic("Illegal memory access."); sig_panic("Illegal memory access.");
$else $else
$if $defined(io::stderr): $if $defined(io::stderr):
if (!print_backtrace("Illegal memory access.", 2, posix::stack_instruction(context))) if (!print_backtrace("Illegal memory access.", 1))
{ {
io::eprintn("\nERROR: 'Illegal memory access'."); io::eprintn("\nERROR: 'Illegal memory access'.");
} }
$endif $endif
$endif $endif
os::fastexit(128 + i); $$trap();
} }
fn void sig_segmentation_fault(CInt i, void* p1, void* context) fn void sig_segmentation_fault(CInt i)
{ {
$if !env::NATIVE_STACKTRACE: $if !env::NATIVE_STACKTRACE:
sig_panic("Out of bounds memory access."); sig_panic("Out of bounds memory access.");
$else $else
$if $defined(io::stderr): $if $defined(io::stderr):
if (!print_backtrace("Out of bounds memory access.", 2, posix::stack_instruction(context))) if (!print_backtrace("Out of bounds memory access.", 1))
{ {
io::eprintn("\nERROR: Memory error without backtrace, possible stack overflow."); io::eprintn("\nERROR: Memory error without backtrace, possible stack overflow.");
} }
$endif $endif
$endif $endif
os::fastexit(128 + i); $$trap();
} }
fn void sig_illegal_instruction(CInt i, void* p1, void* context) fn void install_signal_handler(CInt signal, SignalFunction func) @local
{ {
if (in_panic) os::fastexit(128 + i); SignalFunction old = libc::signal(signal, func);
$if !env::NATIVE_STACKTRACE: // Restore
sig_panic("Illegal instruction."); if ((iptr)old > 1024) libc::signal(signal, old);
$else
$if $defined(io::stderr):
if (!print_backtrace("Illegal instruction.", 2, posix::stack_instruction(context)))
{
io::eprintn("\nERROR: Illegal instruction.");
}
$endif
$endif
os::fastexit(128 + i);
} }
char[64 * 1024] sig_stack @local @if(env::BACKTRACE && env::LINUX);
// Clean this up // Clean this up
fn void install_signal_handlers() @init(101) @local @if(env::BACKTRACE) fn void install_signal_handlers() @init(101) @local @if(env::BACKTRACE)
{ {
$if env::LINUX: install_signal_handler(libc::SIGBUS, &sig_bus_error);
Stack_t ss = { install_signal_handler(libc::SIGSEGV, &sig_segmentation_fault);
.ss_sp = &sig_stack,
.ss_size = sig_stack.len
};
libc::sigaltstack(&ss, null);
$endif
posix::install_signal_handler(libc::SIGBUS, &sig_bus_error);
posix::install_signal_handler(libc::SIGSEGV, &sig_segmentation_fault);
posix::install_signal_handler(libc::SIGILL, &sig_illegal_instruction);
} }

View File

@@ -6,7 +6,7 @@ module std::core::builtin;
<* <*
@require types::@comparable_value(a) && types::@comparable_value(b) @require types::@comparable_value(a) && types::@comparable_value(b)
*> *>
macro bool less(a, b) @builtin macro less(a, b) @builtin
{ {
$switch: $switch:
$case $defined(a.less): $case $defined(a.less):
@@ -21,7 +21,7 @@ macro bool less(a, b) @builtin
<* <*
@require types::@comparable_value(a) && types::@comparable_value(b) @require types::@comparable_value(a) && types::@comparable_value(b)
*> *>
macro bool less_eq(a, b) @builtin macro less_eq(a, b) @builtin
{ {
$switch: $switch:
$case $defined(a.less): $case $defined(a.less):
@@ -36,7 +36,7 @@ macro bool less_eq(a, b) @builtin
<* <*
@require types::@comparable_value(a) && types::@comparable_value(b) @require types::@comparable_value(a) && types::@comparable_value(b)
*> *>
macro bool greater(a, b) @builtin macro greater(a, b) @builtin
{ {
$switch: $switch:
$case $defined(a.less): $case $defined(a.less):
@@ -65,7 +65,7 @@ macro int compare_to(a, b) @builtin
<* <*
@require types::@comparable_value(a) && types::@comparable_value(b) @require types::@comparable_value(a) && types::@comparable_value(b)
*> *>
macro bool greater_eq(a, b) @builtin macro greater_eq(a, b) @builtin
{ {
$switch: $switch:
$case $defined(a.less): $case $defined(a.less):
@@ -126,36 +126,3 @@ macro max(x, ...) @builtin
$endif $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 // Use of this source code is governed by the MIT license
// a copy of which can be found in the LICENSE_STDLIB file. // a copy of which can be found in the LICENSE_STDLIB file.
module std::core::cinterop; module std::core::cinterop;
import std::core::env;
const C_INT_SIZE = $$C_INT_SIZE; const C_INT_SIZE = $$C_INT_SIZE;
const C_LONG_SIZE = $$C_LONG_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"); $default: $error("Invalid bitsize");
$endswitch $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

@@ -16,25 +16,25 @@ const uint UTF16_SURROGATE_HIGH_VALUE @private = 0xD800;
*> *>
fn usz? char32_to_utf8(Char32 c, char[] output) fn usz? char32_to_utf8(Char32 c, char[] output)
{ {
if (!output.len) return string::CONVERSION_FAILED~; if (!output.len) return string::CONVERSION_FAILED?;
switch (true) switch (true)
{ {
case c <= 0x7f: case c <= 0x7f:
output[0] = (char)c; output[0] = (char)c;
return 1; return 1;
case c <= 0x7ff: case c <= 0x7ff:
if (output.len < 2) return string::CONVERSION_FAILED~; if (output.len < 2) return string::CONVERSION_FAILED?;
output[0] = (char)(0xC0 | c >> 6); output[0] = (char)(0xC0 | c >> 6);
output[1] = (char)(0x80 | (c & 0x3F)); output[1] = (char)(0x80 | (c & 0x3F));
return 2; return 2;
case c <= 0xffff: case c <= 0xffff:
if (output.len < 3) return string::CONVERSION_FAILED~; if (output.len < 3) return string::CONVERSION_FAILED?;
output[0] = (char)(0xE0 | c >> 12); output[0] = (char)(0xE0 | c >> 12);
output[1] = (char)(0x80 | (c >> 6 & 0x3F)); output[1] = (char)(0x80 | (c >> 6 & 0x3F));
output[2] = (char)(0x80 | (c & 0x3F)); output[2] = (char)(0x80 | (c & 0x3F));
return 3; return 3;
case c <= 0x10ffff: case c <= 0x10ffff:
if (output.len < 4) return string::CONVERSION_FAILED~; if (output.len < 4) return string::CONVERSION_FAILED?;
output[0] = (char)(0xF0 | c >> 18); output[0] = (char)(0xF0 | c >> 18);
output[1] = (char)(0x80 | (c >> 12 & 0x3F)); output[1] = (char)(0x80 | (c >> 12 & 0x3F));
output[2] = (char)(0x80 | (c >> 6 & 0x3F)); output[2] = (char)(0x80 | (c >> 6 & 0x3F));
@@ -42,7 +42,7 @@ fn usz? char32_to_utf8(Char32 c, char[] output)
return 4; return 4;
default: default:
// 0x10FFFF and above is not defined. // 0x10FFFF and above is not defined.
return string::CONVERSION_FAILED~; return string::CONVERSION_FAILED?;
} }
} }
@@ -84,15 +84,15 @@ fn void? char16_to_utf8_unsafe(Char16 *ptr, usz *available, char** output)
return; return;
} }
// Low surrogate first is an error // Low surrogate first is an error
if (high & UTF16_SURROGATE_MASK != UTF16_SURROGATE_HIGH_VALUE) return string::INVALID_UTF16~; if (high & UTF16_SURROGATE_MASK != UTF16_SURROGATE_HIGH_VALUE) return string::INVALID_UTF16?;
// Unmatched high surrogate is an error // Unmatched high surrogate is an error
if (*available == 1) return string::INVALID_UTF16~; if (*available == 1) return string::INVALID_UTF16?;
Char16 low = ptr[1]; Char16 low = ptr[1];
// Unmatched high surrogate, invalid // Unmatched high surrogate, invalid
if (low & UTF16_SURROGATE_MASK != UTF16_SURROGATE_LOW_VALUE) return string::INVALID_UTF16~; if (low & UTF16_SURROGATE_MASK != UTF16_SURROGATE_LOW_VALUE) return string::INVALID_UTF16?;
// The high bits of the codepoint are the value bits of the high surrogate // The high bits of the codepoint are the value bits of the high surrogate
// The low bits of the codepoint are the value bits of the low surrogate // The low bits of the codepoint are the value bits of the low surrogate
@@ -138,7 +138,7 @@ fn usz char32_to_utf8_unsafe(Char32 c, char** output)
fn Char32? utf8_to_char32(char* ptr, usz* size) fn Char32? utf8_to_char32(char* ptr, usz* size)
{ {
usz max_size = *size; usz max_size = *size;
if (max_size < 1) return string::INVALID_UTF8~; if (max_size < 1) return string::INVALID_UTF8?;
char c = (ptr++)[0]; char c = (ptr++)[0];
if ((c & 0x80) == 0) if ((c & 0x80) == 0)
@@ -148,40 +148,40 @@ fn Char32? utf8_to_char32(char* ptr, usz* size)
} }
if ((c & 0xE0) == 0xC0) if ((c & 0xE0) == 0xC0)
{ {
if (max_size < 2) return string::INVALID_UTF8~; if (max_size < 2) return string::INVALID_UTF8?;
*size = 2; *size = 2;
Char32 uc = (c & 0x1F) << 6; Char32 uc = (c & 0x1F) << 6;
c = *ptr; c = *ptr;
// Overlong sequence or invalid second. // Overlong sequence or invalid second.
if (!uc || c & 0xC0 != 0x80) return string::INVALID_UTF8~; if (!uc || c & 0xC0 != 0x80) return string::INVALID_UTF8?;
return uc + c & 0x3F; return uc + c & 0x3F;
} }
if ((c & 0xF0) == 0xE0) if ((c & 0xF0) == 0xE0)
{ {
if (max_size < 3) return string::INVALID_UTF8~; if (max_size < 3) return string::INVALID_UTF8?;
*size = 3; *size = 3;
Char32 uc = (c & 0x0F) << 12; Char32 uc = (c & 0x0F) << 12;
c = ptr++[0]; c = ptr++[0];
if (c & 0xC0 != 0x80) return string::INVALID_UTF8~; if (c & 0xC0 != 0x80) return string::INVALID_UTF8?;
uc += (c & 0x3F) << 6; uc += (c & 0x3F) << 6;
c = ptr++[0]; c = ptr++[0];
// Overlong sequence or invalid last // Overlong sequence or invalid last
if (!uc || c & 0xC0 != 0x80) return string::INVALID_UTF8~; if (!uc || c & 0xC0 != 0x80) return string::INVALID_UTF8?;
return uc + c & 0x3F; return uc + c & 0x3F;
} }
if (max_size < 4) return string::INVALID_UTF8~; if (max_size < 4) return string::INVALID_UTF8?;
if ((c & 0xF8) != 0xF0) return string::INVALID_UTF8~; if ((c & 0xF8) != 0xF0) return string::INVALID_UTF8?;
*size = 4; *size = 4;
Char32 uc = (c & 0x07) << 18; Char32 uc = (c & 0x07) << 18;
c = ptr++[0]; c = ptr++[0];
if (c & 0xC0 != 0x80) return string::INVALID_UTF8~; if (c & 0xC0 != 0x80) return string::INVALID_UTF8?;
uc += (c & 0x3F) << 12; uc += (c & 0x3F) << 12;
c = ptr++[0]; c = ptr++[0];
if (c & 0xC0 != 0x80) return string::INVALID_UTF8~; if (c & 0xC0 != 0x80) return string::INVALID_UTF8?;
uc += (c & 0x3F) << 6; uc += (c & 0x3F) << 6;
c = ptr++[0]; c = ptr++[0];
// Overlong sequence or invalid last // Overlong sequence or invalid last
if (!uc || c & 0xC0 != 0x80) return string::INVALID_UTF8~; if (!uc || c & 0xC0 != 0x80) return string::INVALID_UTF8?;
return uc + c & 0x3F; return uc + c & 0x3F;
} }
@@ -329,7 +329,7 @@ fn usz? utf8to32(String utf8, Char32[] utf32_buffer)
usz buf_len = utf32_buffer.len; usz buf_len = utf32_buffer.len;
for (usz i = 0; i < len;) for (usz i = 0; i < len;)
{ {
if (len32 == buf_len) return string::CONVERSION_FAILED~; if (len32 == buf_len) return string::CONVERSION_FAILED?;
usz width = len - i; usz width = len - i;
Char32 uc = utf8_to_char32(&utf8[i], &width) @inline!; Char32 uc = utf8_to_char32(&utf8[i], &width) @inline!;
i += width; i += width;

View File

@@ -1,5 +1,5 @@
module std::core::dstring; module std::core::dstring;
import std::io, std::math; import std::io;
<* <*
The DString offers a dynamic string builder. The DString offers a dynamic string builder.
@@ -95,7 +95,7 @@ fn void DString.replace(&self, String needle, String replacement)
match++; match++;
if (match == needle_len) if (match == needle_len)
{ {
self.append_string(replacement); self.append_chars(replacement);
match = 0; match = 0;
continue; continue;
} }
@@ -103,12 +103,12 @@ fn void DString.replace(&self, String needle, String replacement)
} }
if (match > 0) if (match > 0)
{ {
self.append_string(str[i - match:match]); self.append_chars(str[i - match:match]);
match = 0; match = 0;
} }
self.append_char(c); self.append_char(c);
} }
if (match > 0) self.append_string(str[^match:match]); if (match > 0) self.append_chars(str[^match:match]);
}; };
} }
@@ -305,23 +305,18 @@ fn bool DString.less(self, DString other_string)
return true; return true;
} }
fn void DString.append_chars(&self, String str) @deprecated("Use append_string") fn void DString.append_chars(&self, String str)
{ {
self.append_bytes(str); usz other_len = str.len;
}
fn void DString.append_bytes(&self, char[] bytes)
{
usz other_len = bytes.len;
if (!other_len) return; if (!other_len) return;
if (!*self) if (!*self)
{ {
*self = temp((String)bytes); *self = temp(str);
return; return;
} }
self.reserve(other_len); self.reserve(other_len);
StringData* data = self.data(); StringData* data = self.data();
mem::copy(&data.chars[data.len], bytes.ptr, other_len); mem::copy(&data.chars[data.len], str.ptr, other_len);
data.len += other_len; data.len += other_len;
} }
@@ -330,24 +325,7 @@ fn Char32[] DString.copy_utf32(&self, Allocator allocator)
return self.str_view().to_utf32(allocator) @inline!!; return self.str_view().to_utf32(allocator) @inline!!;
} }
<* fn void DString.append_string(&self, DString str)
@require $defined(String s = str) ||| $typeof(str) == DString : "Expected string or DString"
*>
macro void DString.append_string(&self, str)
{
$if $typeof(str) == DString:
self.append_string_deprecated(str);
$else
self.append_bytes((String)str);
$endif
}
macro void DString.append_string_deprecated(&self, DString str) @deprecated("Use .append_dstring()")
{
self.append_dstring(str);
}
fn void DString.append_dstring(&self, DString str)
{ {
StringData* other = str.data(); StringData* other = str.data();
if (!other) return; if (!other) return;
@@ -362,7 +340,7 @@ fn void DString.clear(self)
fn usz? DString.write(&self, char[] buffer) @dynamic fn usz? DString.write(&self, char[] buffer) @dynamic
{ {
self.append_bytes(buffer); self.append_chars((String)buffer);
return buffer.len; return buffer.len;
} }
@@ -422,9 +400,9 @@ macro void DString.append(&self, value)
$case ichar: $case ichar:
self.append_char(value); self.append_char(value);
$case DString: $case DString:
self.append_dstring(value);
$case String:
self.append_string(value); self.append_string(value);
$case String:
self.append_chars(value);
$case Char32: $case Char32:
self.append_char32(value); self.append_char32(value);
$default: $default:
@@ -432,7 +410,7 @@ macro void DString.append(&self, value)
$case $defined((Char32)value): $case $defined((Char32)value):
self.append_char32((Char32)value); self.append_char32((Char32)value);
$case $defined((String)value): $case $defined((String)value):
self.append_string((String)value); self.append_chars((String)value);
$default: $default:
$error "Unsupported type for append use appendf instead."; $error "Unsupported type for append use appendf instead.";
$endswitch $endswitch
@@ -631,7 +609,7 @@ fn void DString.reverse(self)
} }
} }
fn StringData* DString.data(self) @inline fn StringData* DString.data(self) @inline @private
{ {
return (StringData*)self; return (StringData*)self;
} }
@@ -648,7 +626,7 @@ fn void DString.reserve(&self, usz addition)
if (data.capacity >= len) return; if (data.capacity >= len) return;
usz new_capacity = data.capacity * 2; usz new_capacity = data.capacity * 2;
if (new_capacity < MIN_CAPACITY) new_capacity = MIN_CAPACITY; if (new_capacity < MIN_CAPACITY) new_capacity = MIN_CAPACITY;
if (new_capacity < len) new_capacity = math::next_power_of_2(len); while (new_capacity < len) new_capacity *= 2;
data.capacity = new_capacity; data.capacity = new_capacity;
*self = (DString)allocator::realloc(data.allocator, data, StringData.sizeof + new_capacity); *self = (DString)allocator::realloc(data.allocator, data, StringData.sizeof + new_capacity);
} }
@@ -658,10 +636,9 @@ fn usz? DString.read_from_stream(&self, InStream reader)
if (&reader.available) if (&reader.available)
{ {
usz total_read = 0; usz total_read = 0;
while (ulong available = reader.available()!) while (usz available = reader.available()!)
{ {
if (available > isz.max) available = (ulong)isz.max; self.reserve(available);
self.reserve((usz)available);
StringData* data = self.data(); StringData* data = self.data();
usz len = reader.read(data.chars[data.len..(data.capacity - 1)])!; usz len = reader.read(data.chars[data.len..(data.capacity - 1)])!;
total_read += len; total_read += len;

View File

@@ -124,9 +124,7 @@ const usz MAX_VECTOR_SIZE = $$MAX_VECTOR_SIZE;
const bool ARCH_32_BIT = $$REGISTER_SIZE == 32; const bool ARCH_32_BIT = $$REGISTER_SIZE == 32;
const bool ARCH_64_BIT = $$REGISTER_SIZE == 64; const bool ARCH_64_BIT = $$REGISTER_SIZE == 64;
const bool LIBC = $$COMPILER_LIBC_AVAILABLE; const bool LIBC = $$COMPILER_LIBC_AVAILABLE;
const bool NO_LIBC = !LIBC && !CUSTOM_LIBC; const bool NO_LIBC = !$$COMPILER_LIBC_AVAILABLE;
const bool CUSTOM_LIBC = $$CUSTOM_LIBC;
const bool OLD_IO = $feature(OLD_IO);
const CompilerOptLevel COMPILER_OPT_LEVEL = CompilerOptLevel.from_ordinal($$COMPILER_OPT_LEVEL); const CompilerOptLevel COMPILER_OPT_LEVEL = CompilerOptLevel.from_ordinal($$COMPILER_OPT_LEVEL);
const bool BIG_ENDIAN = $$PLATFORM_BIG_ENDIAN; const bool BIG_ENDIAN = $$PLATFORM_BIG_ENDIAN;
const bool I128_NATIVE_SUPPORT = $$PLATFORM_I128_SUPPORTED; const bool I128_NATIVE_SUPPORT = $$PLATFORM_I128_SUPPORTED;
@@ -155,20 +153,14 @@ const bool FREEBSD = LIBC && OS_TYPE == FREEBSD;
const bool NETBSD = LIBC && OS_TYPE == NETBSD; const bool NETBSD = LIBC && OS_TYPE == NETBSD;
const bool BSD_FAMILY = env::FREEBSD || env::OPENBSD || env::NETBSD; const bool BSD_FAMILY = env::FREEBSD || env::OPENBSD || env::NETBSD;
const bool WASI = LIBC && OS_TYPE == WASI; const bool WASI = LIBC && OS_TYPE == WASI;
const bool WASM = ARCH_TYPE == ArchType.WASM32 || ARCH_TYPE == ArchType.WASM64;
const bool ANDROID = LIBC && OS_TYPE == ANDROID; 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 WASM_NOLIBC @builtin = !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 && WASM;
const bool FREESTANDING = env::FREESTANDING_PE32 || env::FREESTANDING_MACHO || env::FREESTANDING_ELF || env::FREESTANDING_WASM;
const bool ADDRESS_SANITIZER = $$ADDRESS_SANITIZER; const bool ADDRESS_SANITIZER = $$ADDRESS_SANITIZER;
const bool MEMORY_SANITIZER = $$MEMORY_SANITIZER; const bool MEMORY_SANITIZER = $$MEMORY_SANITIZER;
const bool THREAD_SANITIZER = $$THREAD_SANITIZER; const bool THREAD_SANITIZER = $$THREAD_SANITIZER;
const bool ANY_SANITIZER = ADDRESS_SANITIZER || MEMORY_SANITIZER || THREAD_SANITIZER; const bool ANY_SANITIZER = ADDRESS_SANITIZER || MEMORY_SANITIZER || THREAD_SANITIZER;
const int LANGUAGE_DEV_VERSION = $$LANGUAGE_DEV_VERSION; const int LANGUAGE_DEV_VERSION = $$LANGUAGE_DEV_VERSION;
const bool HAS_NATIVE_ERRNO = env::LINUX || env::ANDROID || env::OPENBSD || env::DARWIN || env::WIN32 || env::NETBSD; const bool HAS_NATIVE_ERRNO = env::LINUX || env::ANDROID || env::OPENBSD || env::DARWIN || env::WIN32;
macro bool os_is_darwin() @const macro bool os_is_darwin() @const
{ {
@@ -209,6 +201,5 @@ macro bool os_is_posix() @const
} }
const String[] AUTHORS = $$AUTHORS; const String[] AUTHORS = $$AUTHORS;
const String[] AUTHOR_EMAILS = $$AUTHOR_EMAILS; const String[] AUTHOR_EMAILS = $$AUTHOR_EMAILS;
const String PROJECT_VERSION = $$PROJECT_VERSION;
const BUILTIN_EXPECT_IS_DISABLED = $feature(DISABLE_BUILTIN_EXPECT); const BUILTIN_EXPECT_IS_DISABLED = $feature(DISABLE_BUILTIN_EXPECT);
const BUILTIN_PREFETCH_IS_DISABLED = $feature(DISABLE_BUILTIN_PREFETCH); const BUILTIN_PREFETCH_IS_DISABLED = $feature(DISABLE_BUILTIN_PREFETCH);

View File

@@ -6,22 +6,22 @@ const FULL_LOG = env::COMPILER_SAFE_MODE || $feature(FULL_LOG);
typedef LogCategory = inline char; typedef LogCategory = inline char;
typedef LogTag = char[12]; typedef LogTag = char[12];
const LogCategory CATEGORY_APPLICATION = (LogCategory)0; const LogCategory CATEGORY_APPLICATION = 0;
const LogCategory CATEGORY_SYSTEM = (LogCategory)1; const LogCategory CATEGORY_SYSTEM = 1;
const LogCategory CATEGORY_KERNEL = (LogCategory)2; const LogCategory CATEGORY_KERNEL = 2;
const LogCategory CATEGORY_AUDIO = (LogCategory)3; const LogCategory CATEGORY_AUDIO = 3;
const LogCategory CATEGORY_VIDEO = (LogCategory)4; const LogCategory CATEGORY_VIDEO = 4;
const LogCategory CATEGORY_RENDER = (LogCategory)5; const LogCategory CATEGORY_RENDER = 5;
const LogCategory CATEGORY_INPUT = (LogCategory)6; const LogCategory CATEGORY_INPUT = 6;
const LogCategory CATEGORY_NETWORK = (LogCategory)7; const LogCategory CATEGORY_NETWORK = 7;
const LogCategory CATEGORY_SOCKET = (LogCategory)8; const LogCategory CATEGORY_SOCKET = 8;
const LogCategory CATEGORY_SECURITY = (LogCategory)9; const LogCategory CATEGORY_SECURITY = 9;
const LogCategory CATEGORY_TEST = (LogCategory)10; const LogCategory CATEGORY_TEST = 10;
const LogCategory CATEGORY_ERROR = (LogCategory)11; const LogCategory CATEGORY_ERROR = 11;
const LogCategory CATEGORY_ASSERT = (LogCategory)12; const LogCategory CATEGORY_ASSERT = 12;
const LogCategory CATEGORY_CRASH = (LogCategory)13; const LogCategory CATEGORY_CRASH = 13;
const LogCategory CATEGORY_STATS = (LogCategory)14; const LogCategory CATEGORY_STATS = 14;
const LogCategory CATEGORY_CUSTOM_START = (LogCategory)100; const LogCategory CATEGORY_CUSTOM_START = 100;
tlocal LogCategory default_category = CATEGORY_APPLICATION; tlocal LogCategory default_category = CATEGORY_APPLICATION;
tlocal LogTag current_tag; tlocal LogTag current_tag;
@@ -134,32 +134,26 @@ macro void init()
log_init.call(fn () => (void)logger_mutex.init()); log_init.call(fn () => (void)logger_mutex.init());
} }
macro void call_log(LogPriority prio, LogCategory category, String fmt, args...) fn 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); LogPriority priority = mem::@atomic_load(config_priorities[category], UNORDERED);
if (priority > prio) return; if (priority < prio) return;
init(); init();
bool locked = logger_mutex.is_initialized(); bool locked = logger_mutex.is_initialized() && @ok(logger_mutex.lock());
if (locked) logger_mutex.lock();
Logger logger = current_logger; Logger logger = current_logger;
LogFn logfn = current_logfn; LogFn logfn = current_logfn;
defer if (locked) logger_mutex.unlock(); defer if (locked) (void)logger_mutex.unlock();
logfn(logger.ptr, prio, category, current_tag, file, func, line, fmt, args); $if FULL_LOG:
logfn(logger.ptr, prio, category, current_tag, $$FILE, $$FUNC, $$LINE, fmt, args);
$else
logfn(logger.ptr, prio, category, current_tag, "", "", 0, fmt, args);
$endif
} }
fn String? get_category_name(LogCategory category) fn String? get_category_name(LogCategory category)
{ {
String val = category_names[category]; String val = category_names[category];
return val ?: NOT_FOUND~; return val ?: NOT_FOUND?;
} }
fn void set_category_name(LogCategory category, String name) fn void set_category_name(LogCategory category, String name)
@@ -205,19 +199,15 @@ fn void StderrLogger.log(&self, LogPriority priority, LogCategory category, LogT
str.init(mem, 256); str.init(mem, 256);
str.appendf(fmt, ...args); str.appendf(fmt, ...args);
TzDateTime time = datetime::now().to_local(); 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); 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); 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; LogFn current_logfn = @select(env::LIBC, (LogFn)&StderrLogger.log, (LogFn)&NullLogger.log);
OnceFlag log_init; OnceFlag log_init;
Mutex logger_mutex; Mutex logger_mutex;
Logger current_logger = env::LIBC ??? &stderr_logger : &null_logger; Logger current_logger = @select(env::LIBC, &stderr_logger, &null_logger);
StderrLogger stderr_logger @if (env::LIBC); StderrLogger stderr_logger @if (env::LIBC);
NullLogger null_logger; NullLogger null_logger;
LogPriority[256] config_priorities = { [0..255] = ERROR, [CATEGORY_APPLICATION] = INFO, [CATEGORY_TEST] = VERBOSE, [CATEGORY_ASSERT] = WARN}; LogPriority[256] config_priorities = { [0..255] = ERROR, [CATEGORY_APPLICATION] = INFO, [CATEGORY_TEST] = VERBOSE, [CATEGORY_ASSERT] = WARN};

View File

@@ -7,7 +7,7 @@ import std::os::posix, std::os::win32;
import std::math; import std::math;
const MAX_MEMORY_ALIGNMENT = 0x1000_0000; const MAX_MEMORY_ALIGNMENT = 0x1000_0000;
const DEFAULT_MEM_ALIGNMENT = env::WASM ? 16 : (void*.alignof) * 2; const DEFAULT_MEM_ALIGNMENT = (void*.alignof) * 2;
const ulong KB = 1024; const ulong KB = 1024;
const ulong MB = KB * 1024; const ulong MB = KB * 1024;
const ulong GB = MB * 1024; const ulong GB = MB * 1024;
@@ -20,9 +20,6 @@ macro bool @constant_is_power_of_2($x) @const @private
return $x != 0 && ($x & ($x - 1)) == 0; return $x != 0 && ($x & ($x - 1)) == 0;
} }
<*
@return "The os page size."
*>
fn usz os_pagesize() fn usz os_pagesize()
{ {
$switch: $switch:
@@ -47,8 +44,8 @@ fn usz os_pagesize()
@param ptr : "The pointer address to load from." @param ptr : "The pointer address to load from."
@param mask : "The mask for the load" @param mask : "The mask for the load"
@param passthru : "The value to use for non masked values" @param passthru : "The value to use for non masked values"
@require $defined(*ptr = passthru) : "Pointer and passthru must match" @require @assignable_to(&&passthru, $typeof(ptr)) : "Pointer and passthru must match"
@require $kindof(passthru) == VECTOR : "Expected passthru to be a vector" @require @typekind(passthru) == VECTOR : "Expected passthru to be a vector"
@require passthru.len == mask.len : "Mask and passthru must have the same length" @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" @return "A vector with the loaded values where the mask is true, passthru where the mask is false"
@@ -66,13 +63,12 @@ macro masked_load(ptr, bool[<*>] mask, passthru)
@param passthru : "The value to use for non masked values" @param passthru : "The value to use for non masked values"
@param $alignment : "The alignment to assume for the pointer" @param $alignment : "The alignment to assume for the pointer"
@require $defined(*ptr = passthru) : "Pointer and passthru must match" @require @assignable_to(&&passthru, $typeof(ptr)) : "Pointer and passthru must match"
@require $kindof(passthru) == VECTOR : "Expected passthru to be a vector" @require @typekind(passthru) == VECTOR : "Expected passthru to be a vector"
@require passthru.len == mask.len : "Mask and passthru must have the same length" @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" @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" @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) macro @masked_load_aligned(ptr, bool[<*>] mask, passthru, usz $alignment)
{ {
@@ -86,9 +82,9 @@ macro @masked_load_aligned(ptr, bool[<*>] mask, passthru, usz $alignment)
@param mask : "The mask for the load" @param mask : "The mask for the load"
@param passthru : "The value to use for non masked values" @param passthru : "The value to use for non masked values"
@require $kindof(ptrvec) == VECTOR : "Expected ptrvec to be a vector" @require @typekind(ptrvec) == VECTOR : "Expected ptrvec to be a vector"
@require $kindof(passthru) == VECTOR : "Expected passthru to be a vector" @require @typekind(passthru) == VECTOR : "Expected passthru to be a vector"
@require $defined(*ptrvec[0] = passthru[0]) : "Pointer and passthru must match" @require @assignable_to(&&passthru[0], $typeof(ptrvec[0])) : "Pointer and passthru must match"
@require passthru.len == mask.len : "Mask and passthru must have the same length" @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 mask.len == ptrvec.len : "Mask and ptrvec must have the same length"
@@ -108,9 +104,9 @@ macro gather(ptrvec, bool[<*>] mask, passthru)
@param passthru : "The value to use for non masked values" @param passthru : "The value to use for non masked values"
@param $alignment : "The alignment to assume for the pointers" @param $alignment : "The alignment to assume for the pointers"
@require $kindof(ptrvec) == VECTOR : "Expected ptrvec to be a vector" @require @typekind(ptrvec) == VECTOR : "Expected ptrvec to be a vector"
@require $kindof(passthru) == VECTOR : "Expected passthru to be a vector" @require @typekind(passthru) == VECTOR : "Expected passthru to be a vector"
@require $defined(*ptrvec[0] = passthru[0]) : "Pointer and passthru must match" @require @assignable_to(&&passthru[0], $typeof(ptrvec[0])) : "Pointer and passthru must match"
@require passthru.len == mask.len : "Mask and passthru must have the same length" @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 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" @require @constant_is_power_of_2($alignment) : "The alignment must be a power of two"
@@ -130,8 +126,8 @@ macro @gather_aligned(ptrvec, bool[<*>] mask, passthru, usz $alignment)
@param value : "The value to store masked" @param value : "The value to store masked"
@param mask : "The mask for the store" @param mask : "The mask for the store"
@require $defined(*ptr = value) : "Pointer and value must match" @require @assignable_to(&&value, $typeof(ptr)) : "Pointer and value must match"
@require $kindof(value) == VECTOR : "Expected value to be a vector" @require @typekind(value) == VECTOR : "Expected value to be a vector"
@require value.len == mask.len : "Mask and value must have the same length" @require value.len == mask.len : "Mask and value must have the same length"
*> *>
macro masked_store(ptr, value, bool[<*>] mask) macro masked_store(ptr, value, bool[<*>] mask)
@@ -145,8 +141,8 @@ macro masked_store(ptr, value, bool[<*>] mask)
@param mask : "The mask for the store" @param mask : "The mask for the store"
@param $alignment : "The alignment of the pointer" @param $alignment : "The alignment of the pointer"
@require $defined(*ptr = value) : "Pointer and value must match" @require @assignable_to(&&value, $typeof(ptr)) : "Pointer and value must match"
@require $kindof(value) == VECTOR : "Expected value to be a vector" @require @typekind(value) == VECTOR : "Expected value to be a vector"
@require value.len == mask.len : "Mask and value must have the same length" @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" @require @constant_is_power_of_2($alignment) : "The alignment must be a power of two"
@@ -160,9 +156,9 @@ macro @masked_store_aligned(ptr, value, bool[<*>] mask, usz $alignment)
@param ptrvec : "The vector pointer containing the addresses to store to." @param ptrvec : "The vector pointer containing the addresses to store to."
@param value : "The value to store masked" @param value : "The value to store masked"
@param mask : "The mask for the store" @param mask : "The mask for the store"
@require $kindof(ptrvec) == VECTOR : "Expected ptrvec to be a vector" @require @typekind(ptrvec) == VECTOR : "Expected ptrvec to be a vector"
@require $kindof(value) == VECTOR : "Expected value to be a vector" @require @typekind(value) == VECTOR : "Expected value to be a vector"
@require $defined(*ptrvec[0] = value[0]) : "Pointer and value must match" @require @assignable_to(&&value[0], $typeof(ptrvec[0])) : "Pointer and value must match"
@require value.len == mask.len : "Mask and value must have the same length" @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 mask.len == ptrvec.len : "Mask and ptrvec must have the same length"
@@ -178,9 +174,9 @@ macro scatter(ptrvec, value, bool[<*>] mask)
@param mask : "The mask for the store" @param mask : "The mask for the store"
@param $alignment : "The alignment of the load" @param $alignment : "The alignment of the load"
@require $kindof(ptrvec) == VECTOR : "Expected ptrvec to be a vector" @require @typekind(ptrvec) == VECTOR : "Expected ptrvec to be a vector"
@require $kindof(value) == VECTOR : "Expected value to be a vector" @require @typekind(value) == VECTOR : "Expected value to be a vector"
@require $defined(*ptrvec[0] = value[0]) : "Pointer and value must match" @require @assignable_to(&&value[0], $typeof(ptrvec[0])) : "Pointer and value must match"
@require value.len == mask.len : "Mask and value must have the same length" @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 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" @require @constant_is_power_of_2($alignment) : "The alignment must be a power of two"
@@ -200,7 +196,7 @@ macro @scatter_aligned(ptrvec, value, bool[<*>] mask, usz $alignment)
*> *>
macro @unaligned_load(#x, usz $alignment) @builtin macro @unaligned_load(#x, usz $alignment) @builtin
{ {
return $$unaligned_load(&#x, $alignment, false); return $$unaligned_load(&#x, $alignment);
} }
<* <*
@@ -215,10 +211,9 @@ macro @unaligned_load(#x, usz $alignment) @builtin
*> *>
macro @unaligned_store(#x, value, usz $alignment) @builtin macro @unaligned_store(#x, value, usz $alignment) @builtin
{ {
return $$unaligned_store(&#x, ($typeof(#x))value, $alignment, false); return $$unaligned_store(&#x, ($typeof(#x))value, $alignment);
} }
<* <*
@param #x : "The variable or dereferenced pointer to load." @param #x : "The variable or dereferenced pointer to load."
@return "The value of the variable" @return "The value of the variable"
@@ -243,39 +238,6 @@ macro @volatile_store(#x, value) @builtin
return $$volatile_store(&#x, ($typeof(#x))value); return $$volatile_store(&#x, ($typeof(#x))value);
} }
<*
@param ptr : "The pointer to load from"
@param $align : "The alignment to assume for the load"
@param $volatile : "Whether the load is volatile or not, defaults to false"
@return "The value of the variable"
@require $defined(*ptr) : "This must be a typed pointer"
@require @constant_is_power_of_2($align) : "The alignment must be a power of two"
*>
macro load(ptr, usz $align, bool $volatile = false)
{
return $$unaligned_load(ptr, $align, $volatile);
}
<*
@param ptr : "The pointer to store to."
@param value : "The value to store."
@param $align : "The alignment to assume for the store"
@param $volatile : "Whether the store is volatile, defaults to false"
@return "The value stored"
@require $defined(*ptr) : "This must be a typed pointer"
@require $defined(*ptr = value) : "The value doesn't match the variable"
@require @constant_is_power_of_2($align) : "The alignment must be a power of two"
*>
macro store(ptr, value, usz $align, bool $volatile = false)
{
return $$unaligned_store(ptr, ($typeof(*ptr))value, $align, $volatile);
}
<*
All possible atomic orderings
*>
enum AtomicOrdering : int enum AtomicOrdering : int
{ {
NOT_ATOMIC, // Not atomic NOT_ATOMIC, // Not atomic
@@ -471,7 +433,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(b) == TypeKind.SLICE || values::@inner_kind(b) == TypeKind.POINTER
@require values::@inner_kind(a) != TypeKind.SLICE || len == -1 @require values::@inner_kind(a) != TypeKind.SLICE || len == -1
@require values::@inner_kind(a) != TypeKind.POINTER || 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) macro bool equals(a, b, isz len = -1, usz $align = 0)
{ {
@@ -684,7 +646,7 @@ macro void @pool(usz reserve = 0; @body) @builtin
@body(); @body();
} }
module std::core::mem @if(env::FREESTANDING_WASM); module std::core::mem @if(WASM_NOLIBC);
import std::core::mem::allocator @public; import std::core::mem::allocator @public;
SimpleHeapAllocator wasm_allocator @private; SimpleHeapAllocator wasm_allocator @private;
extern int __heap_base; extern int __heap_base;
@@ -722,12 +684,6 @@ macro @clone(value) @builtin @nodiscard
return allocator::clone(mem, value); 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" @param value : "The value to clone"
@return "A pointer to the cloned value, which must be released using free_aligned" @return "A pointer to the cloned value, which must be released using free_aligned"
@@ -750,12 +706,6 @@ macro @tclone(value) @builtin @nodiscard
$endif $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 fn void* malloc(usz size) @builtin @inline @nodiscard
{ {
return allocator::malloc(mem, size); return allocator::malloc(mem, size);
@@ -777,70 +727,56 @@ fn void* tmalloc(usz size, usz alignment = 0) @builtin @inline @nodiscard
} }
<* <*
@param $Type : "The type to allocate" @require $vacount < 2 : "Too many arguments."
@param #init : "The optional initializer" @require $vacount == 0 ||| @assignable_to($vaexpr[0], $Type) : "The second argument must be an initializer for the type"
@require !$defined(#init) ||| $defined($Type a = #init) : "#init must be an initializer for the type"
@require $Type.alignof <= DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'alloc_aligned' instead" @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): $if $vacount == 0:
$Type* val = malloc($Type.sizeof);
*val = #init;
return val;
$else
return ($Type*)calloc($Type.sizeof); return ($Type*)calloc($Type.sizeof);
$else
$Type* val = malloc($Type.sizeof);
*val = $vaexpr[0];
return val;
$endif $endif
} }
<* <*
@param $Type : "The type to allocate" @require $vacount < 2 : "Too many arguments."
@param padding : "The padding to add after the allocation" @require $vacount == 0 ||| @assignable_to($vaexpr[0], $Type) : "The second argument must be an initializer for the type"
@param #init : "The optional initializer" @require $Type.alignof <= DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'alloc_aligned' instead"
@require !$defined(#init) ||| $defined($Type a = #init) : "#init must be an initializer for the type"
@return "A pointer to data of type $Type."
*> *>
macro new_with_padding($Type, usz padding, #init = ...) @nodiscard @safemacro macro new_with_padding($Type, usz padding, ...) @nodiscard
{ {
$if $defined(#init): $if $vacount == 0:
$Type* val = malloc($Type.sizeof + padding);
*val = #init;
return val;
$else
return ($Type*)calloc($Type.sizeof + padding); return ($Type*)calloc($Type.sizeof + padding);
$else
$Type* val = malloc($Type.sizeof + padding);
*val = $vaexpr[0];
return val;
$endif $endif
} }
<* <*
Allocate using an aligned allocation. This is necessary for types with a default memory alignment 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. exceeding DEFAULT_MEM_ALIGNMENT. IMPORTANT! It must be freed using free_aligned.
@require $vacount < 2 : "Too many arguments."
@param $Type : "The type to allocate" @require $vacount == 0 ||| @assignable_to($vaexpr[0], $Type) : "The second argument must be an initializer for the type"
@param #init : "The optional initializer"
@require !$defined(#init) ||| $defined($Type a = #init) : "#init must be an initializer for the type"
@return "A pointer to data of type $Type with the proper alignment"
*> *>
macro new_aligned($Type, #init = ...) @nodiscard @safemacro macro new_aligned($Type, ...) @nodiscard
{ {
$if $defined(#init): $if $vacount == 0:
$Type* val = malloc_aligned($Type.sizeof, $Type.alignof);
*val = #init;
return val;
$else
return ($Type*)calloc_aligned($Type.sizeof, $Type.alignof); return ($Type*)calloc_aligned($Type.sizeof, $Type.alignof);
$else
$Type* val = malloc_aligned($Type.sizeof, $Type.alignof);
*val = $vaexpr[0];
return val;
$endif $endif
} }
<* <*
@param $Type : "The type to allocate"
@require $Type.kindof != OPTIONAL : "Expected a non-optional type"
@require $Type.alignof <= DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'alloc_aligned' instead" @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 macro alloc($Type) @nodiscard
{ {
@@ -848,12 +784,7 @@ macro alloc($Type) @nodiscard
} }
<* <*
@param $Type : "The type to allocate" @require $Type.alignof <= DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'alloc_aligned' instead"
@param padding : "The padding to add after the allocation"
@require $Type.kindof != OPTIONAL : "Expected a non-optional type"
@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 macro alloc_with_padding($Type, usz padding) @nodiscard
{ {
@@ -861,13 +792,8 @@ macro alloc_with_padding($Type, usz padding) @nodiscard
} }
<* <*
Allocate using an aligned allocation. This is necessary for types with a default memory alignment 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. exceeding DEFAULT_MEM_ALIGNMENT. IMPORTANT! It must be freed using free_aligned.
@param $Type : "The type to allocate"
@require $Type.kindof != OPTIONAL : "Expected a non-optional type"
@return "A pointer to uninitialized data for the type $Type with the proper alignment"
*> *>
macro alloc_aligned($Type) @nodiscard macro alloc_aligned($Type) @nodiscard
{ {
@@ -875,62 +801,46 @@ macro alloc_aligned($Type) @nodiscard
} }
<* <*
@param $Type : "The type to allocate" @require $vacount < 2 : "Too many arguments."
@param #init : "The optional initializer" @require $vacount == 0 ||| @assignable_to($vaexpr[0], $Type) : "The second argument must be an initializer for the type"
@require $Type.kindof != OPTIONAL : "Expected a non-optional type"
@require !$defined(#init) ||| $defined($Type a = #init) : "#init must be an initializer for the type"
@return "A pointer to temporary data of type $Type."
*> *>
macro tnew($Type, #init = ...) @nodiscard @safemacro macro tnew($Type, ...) @nodiscard
{ {
$if $defined(#init): $if $vacount == 0:
$Type* val = tmalloc($Type.sizeof, $Type.alignof) @inline;
*val = #init;
return val;
$else
return ($Type*)tcalloc($Type.sizeof, $Type.alignof) @inline; return ($Type*)tcalloc($Type.sizeof, $Type.alignof) @inline;
$endif
}
<*
@param $Type : "The type to allocate"
@param padding : "The padding to add after the allocation"
@param #init : "The optional initializer"
@require $Type.kindof != OPTIONAL : "Expected a non-optional type"
@require !$defined(#init) ||| $defined($Type a = #init) : "#init must be an initializer for the type"
@return "A pointer to temporary data of type $Type with added padding at the end."
*>
macro temp_with_padding($Type, usz padding, #init = ...) @nodiscard @safemacro
{
$if $defined(#init):
$Type* val = tmalloc($Type.sizeof + padding, $Type.alignof) @inline;
*val = #init;
return val;
$else $else
return ($Type*)tcalloc($Type.sizeof + padding, $Type.alignof) @inline; $Type* val = tmalloc($Type.sizeof, $Type.alignof) @inline;
*val = $vaexpr[0];
return val;
$endif $endif
} }
<* <*
@require $Type.kindof != OPTIONAL : "Expected a non-optional type" @require $vacount < 2 : "Too many arguments."
@require $vacount == 0 ||| @assignable_to($vaexpr[0], $Type) : "The second argument must be an initializer for the type"
*> *>
macro temp_with_padding($Type, usz padding, ...) @nodiscard
{
$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
}
macro talloc($Type) @nodiscard macro talloc($Type) @nodiscard
{ {
return tmalloc($Type.sizeof, $Type.alignof); return tmalloc($Type.sizeof, $Type.alignof);
} }
<*
@require $Type.kindof != OPTIONAL : "Expected a non-optional type"
*>
macro talloc_with_padding($Type, usz padding) @nodiscard macro talloc_with_padding($Type, usz padding) @nodiscard
{ {
return tmalloc($Type.sizeof + padding, $Type.alignof); return tmalloc($Type.sizeof + padding, $Type.alignof);
} }
<* <*
@require $Type.kindof != OPTIONAL : "Expected a non-optional type"
@require $Type.alignof <= DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'new_array_aligned' instead" @require $Type.alignof <= DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'new_array_aligned' instead"
*> *>
macro new_array($Type, usz elements) @nodiscard macro new_array($Type, usz elements) @nodiscard
@@ -941,8 +851,6 @@ macro new_array($Type, usz elements) @nodiscard
<* <*
Allocate using an aligned allocation. This is necessary for types with a default memory alignment 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. exceeding DEFAULT_MEM_ALIGNMENT. IMPORTANT! It must be freed using free_aligned.
@require $Type.kindof != OPTIONAL : "Expected a non-optional type"
*> *>
macro new_array_aligned($Type, usz elements) @nodiscard macro new_array_aligned($Type, usz elements) @nodiscard
{ {
@@ -950,7 +858,6 @@ macro new_array_aligned($Type, usz elements) @nodiscard
} }
<* <*
@require $Type.kindof != OPTIONAL : "Expected a non-optional type"
@require $Type.alignof <= DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'alloc_array_aligned' instead" @require $Type.alignof <= DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'alloc_array_aligned' instead"
*> *>
macro alloc_array($Type, usz elements) @nodiscard macro alloc_array($Type, usz elements) @nodiscard
@@ -961,8 +868,6 @@ macro alloc_array($Type, usz elements) @nodiscard
<* <*
Allocate using an aligned allocation. This is necessary for types with a default memory alignment 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. exceeding DEFAULT_MEM_ALIGNMENT. IMPORTANT! It must be freed using free_aligned.
@require $Type.kindof != OPTIONAL : "Expected a non-optional type"
*> *>
macro alloc_array_aligned($Type, usz elements) @nodiscard macro alloc_array_aligned($Type, usz elements) @nodiscard
{ {
@@ -1077,7 +982,7 @@ fn void* __memcpy(void* dst, void* src, usz n) @weak @export("memcpy")
} }
module std::core::mem::volatile <Type>; module std::core::mem::volatile { Type };
typedef Volatile @structlike = Type; typedef Volatile @structlike = Type;
@@ -1094,7 +999,7 @@ macro Type Volatile.set(&self, Type val)
<* <*
@require mem::@constant_is_power_of_2(ALIGNMENT) : "The alignment must be a power of 2" @require mem::@constant_is_power_of_2(ALIGNMENT) : "The alignment must be a power of 2"
*> *>
module std::core::mem::alignment <Type, ALIGNMENT>; module std::core::mem::alignment { Type, ALIGNMENT };
import std::core::mem @public; import std::core::mem @public;
<* <*
@@ -1104,10 +1009,10 @@ typedef UnalignedRef = Type*;
macro Type UnalignedRef.get(self) macro Type UnalignedRef.get(self)
{ {
return @unaligned_load(*(Type*)self, ALIGNMENT, false); return @unaligned_load(*(Type*)self, ALIGNMENT);
} }
macro Type UnalignedRef.set(&self, Type val) macro Type UnalignedRef.set(&self, Type val)
{ {
return @unaligned_store(*(Type*)self, val, ALIGNMENT, false); return @unaligned_store(*(Type*)self, val, ALIGNMENT);
} }

View File

@@ -167,7 +167,7 @@ macro void free_aligned(Allocator allocator, void* ptr)
<* <*
@require $Type.alignof <= mem::DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'new_aligned' instead" @require $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 < 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_to($vaexpr[0], $Type) : "The second argument must be an initializer for the type"
*> *>
macro new(Allocator allocator, $Type, ...) @nodiscard macro new(Allocator allocator, $Type, ...) @nodiscard
{ {
@@ -183,7 +183,7 @@ macro new(Allocator allocator, $Type, ...) @nodiscard
<* <*
@require $Type.alignof <= mem::DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'new_aligned' instead" @require $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 < 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_to($vaexpr[0], $Type) : "The second argument must be an initializer for the type"
*> *>
macro new_try(Allocator allocator, $Type, ...) @nodiscard macro new_try(Allocator allocator, $Type, ...) @nodiscard
{ {
@@ -200,7 +200,7 @@ macro new_try(Allocator allocator, $Type, ...) @nodiscard
Allocate using an aligned allocation. This is necessary for types with a default memory alignment 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. exceeding DEFAULT_MEM_ALIGNMENT. IMPORTANT! It must be freed using free_aligned.
@require $vacount < 2 : "Too many arguments." @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_to($vaexpr[0], $Type) : "The second argument must be an initializer for the type"
*> *>
macro new_aligned(Allocator allocator, $Type, ...) @nodiscard macro new_aligned(Allocator allocator, $Type, ...) @nodiscard
{ {
@@ -304,31 +304,6 @@ macro alloc_array_try(Allocator allocator, $Type, usz elements) @nodiscard
return (($Type*)malloc_try(allocator, $Type.sizeof * elements))[:elements]; return (($Type*)malloc_try(allocator, $Type.sizeof * elements))[:elements];
} }
<*
@require $Type.alignof <= mem::DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'alloc_array_aligned' instead"
*>
macro realloc_array(Allocator allocator, void* ptr, $Type, usz elements) @nodiscard
{
return realloc_array_try(allocator, ptr, $Type, elements)!!;
}
<*
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.
*>
macro realloc_array_aligned(Allocator allocator, void* ptr, $Type, usz elements) @nodiscard
{
return (($Type*)realloc_aligned(allocator, ptr, $Type.sizeof * elements, $Type.alignof))[:elements]!!;
}
<*
@require $Type.alignof <= mem::DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'alloc_array_aligned' instead"
*>
macro realloc_array_try(Allocator allocator, void* ptr, $Type, usz elements) @nodiscard
{
return (($Type*)realloc_try(allocator, ptr, $Type.sizeof * elements))[:elements];
}
<* <*
Clone a value. Clone a value.
@@ -342,25 +317,6 @@ macro clone(Allocator allocator, value) @nodiscard
return new(allocator, $typeof(value), value); 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
{
if (!lengthof(slice)) return {};
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. Clone overaligned values. Must be released using free_aligned.
@@ -392,7 +348,7 @@ macro void*? @aligned_alloc(#alloc_fn, usz bytes, usz alignment)
if (alignment < void*.alignof) alignment = void*.alignof; if (alignment < void*.alignof) alignment = void*.alignof;
usz header = AlignedBlock.sizeof + alignment; usz header = AlignedBlock.sizeof + alignment;
usz alignsize = bytes + header; usz alignsize = bytes + header;
$if $kindof(#alloc_fn(bytes)) == OPTIONAL: $if @typekind(#alloc_fn(bytes)) == OPTIONAL:
void* data = #alloc_fn(alignsize)!; void* data = #alloc_fn(alignsize)!;
$else $else
void* data = #alloc_fn(alignsize); void* data = #alloc_fn(alignsize);
@@ -404,28 +360,6 @@ macro void*? @aligned_alloc(#alloc_fn, usz bytes, usz alignment)
return mem; return mem;
} }
<*
@require bytes > 0
@require alignment > 0
@require bytes <= isz.max
*>
macro void*? @aligned_alloc_fn(context, #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(context, bytes)) == OPTIONAL:
void* data = #alloc_fn(context, alignsize)!;
$else
void* data = #alloc_fn(context, alignsize);
$endif
void* mem = mem::aligned_pointer(data + AlignedBlock.sizeof, alignment);
AlignedBlock* desc = (AlignedBlock*)mem - 1;
assert(mem > data);
*desc = { bytes, data };
return mem;
}
struct AlignedBlock struct AlignedBlock
{ {
usz len; usz len;
@@ -435,23 +369,13 @@ struct AlignedBlock
macro void? @aligned_free(#free_fn, void* old_pointer) macro void? @aligned_free(#free_fn, void* old_pointer)
{ {
AlignedBlock* desc = (AlignedBlock*)old_pointer - 1; AlignedBlock* desc = (AlignedBlock*)old_pointer - 1;
$if $kindof(#free_fn(desc.start)) == OPTIONAL: $if @typekind(#free_fn(desc.start)) == OPTIONAL:
#free_fn(desc.start)!; #free_fn(desc.start)!;
$else $else
#free_fn(desc.start); #free_fn(desc.start);
$endif $endif
} }
macro void? @aligned_free_fn(context, #free_fn, void* old_pointer)
{
AlignedBlock* desc = (AlignedBlock*)old_pointer - 1;
$if $kindof(#free_fn(context, desc.start)) == OPTIONAL:
#free_fn(context, desc.start)!;
$else
#free_fn(context, desc.start);
$endif
}
<* <*
@require bytes > 0 @require bytes > 0
@require alignment > 0 @require alignment > 0
@@ -462,7 +386,7 @@ macro void*? @aligned_realloc(#calloc_fn, #free_fn, void* old_pointer, usz bytes
void* data_start = desc.start; void* data_start = desc.start;
void* new_data = @aligned_alloc(#calloc_fn, bytes, alignment)!; void* new_data = @aligned_alloc(#calloc_fn, bytes, alignment)!;
mem::copy(new_data, old_pointer, desc.len < bytes ? desc.len : bytes, 1, 1); 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)!; #free_fn(data_start)!;
$else $else
#free_fn(data_start); #free_fn(data_start);
@@ -470,23 +394,6 @@ macro void*? @aligned_realloc(#calloc_fn, #free_fn, void* old_pointer, usz bytes
return new_data; return new_data;
} }
<*
@require bytes > 0
@require alignment > 0
*>
macro void*? @aligned_realloc_fn(context, #calloc_fn, #free_fn, void* old_pointer, usz bytes, usz alignment)
{
AlignedBlock* desc = (AlignedBlock*)old_pointer - 1;
void* data_start = desc.start;
void* new_data = @aligned_alloc_fn(context, #calloc_fn, bytes, alignment)!;
mem::copy(new_data, old_pointer, desc.len < bytes ? desc.len : bytes, 1, 1);
$if $kindof(#free_fn(context, data_start)) == OPTIONAL:
#free_fn(context, data_start)!;
$else
#free_fn(context, data_start);
$endif
return new_data;
}
// All allocators // All allocators
alias mem @builtin = thread_allocator ; alias mem @builtin = thread_allocator ;
@@ -587,7 +494,7 @@ macro Allocator temp() @deprecated("Use 'tmem' instead")
alias tmem @builtin = 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; auto_create_temp = true;
} }
@@ -632,12 +539,12 @@ typedef NullAllocator (Allocator) = uptr;
fn void*? NullAllocator.acquire(&self, usz bytes, AllocInitType init_type, usz alignment) @dynamic fn void*? NullAllocator.acquire(&self, usz bytes, AllocInitType init_type, usz alignment) @dynamic
{ {
return mem::OUT_OF_MEMORY~; return mem::OUT_OF_MEMORY?;
} }
fn void*? NullAllocator.resize(&self, void* old_ptr, usz new_bytes, usz alignment) @dynamic fn void*? NullAllocator.resize(&self, void* old_ptr, usz new_bytes, usz alignment) @dynamic
{ {
return mem::OUT_OF_MEMORY~; return mem::OUT_OF_MEMORY?;
} }
fn void NullAllocator.release(&self, void* old_ptr, bool aligned) @dynamic fn void NullAllocator.release(&self, void* old_ptr, bool aligned) @dynamic

View File

@@ -1,6 +1,5 @@
module std::core::mem::mempool; module std::core::mem::mempool;
import std::core::mem, std::core::mem::allocator, std::math; import std::core::mem, std::core::mem::allocator, std::math;
import std::core::sanitizer::asan;
const INITIAL_CAPACITY = 0; const INITIAL_CAPACITY = 0;
@@ -54,7 +53,7 @@ struct FixedBlockPool
@require calculate_actual_capacity(capacity, block_size) * block_size >= block_size @require calculate_actual_capacity(capacity, block_size) * block_size >= block_size
: "Total memory would overflow" : "Total memory would overflow"
*> *>
fn FixedBlockPool* FixedBlockPool.init(&self, Allocator allocator, usz block_size, usz capacity = INITIAL_CAPACITY, usz alignment = 0) macro FixedBlockPool* FixedBlockPool.init(&self, Allocator allocator, usz block_size, usz capacity = INITIAL_CAPACITY, usz alignment = 0)
{ {
self.allocator = allocator; self.allocator = allocator;
self.tail = &self.head; self.tail = &self.head;
@@ -64,10 +63,7 @@ fn FixedBlockPool* FixedBlockPool.init(&self, Allocator allocator, usz block_siz
self.alignment = allocator::alignment_for_allocation(alignment); self.alignment = allocator::alignment_for_allocation(alignment);
self.page_size = capacity * self.block_size; self.page_size = capacity * self.block_size;
assert(self.page_size >= self.block_size, "Total memory would overflow %d %d", block_size, capacity); assert(self.page_size >= self.block_size, "Total memory would overflow %d %d", block_size, capacity);
self.head.buffer = fixedblockpool_allocate_page(self); 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.head.capacity = capacity;
self.next_free = self.head.buffer; self.next_free = self.head.buffer;
self.freelist = null; self.freelist = null;
@@ -116,18 +112,12 @@ macro FixedBlockPool* FixedBlockPool.tinit(&self, usz block_size, usz capacity =
*> *>
fn void FixedBlockPool.free(&self) fn void FixedBlockPool.free(&self)
{ {
$if env::COMPILER_SAFE_MODE && env::ADDRESS_SANITIZER: self.free_page(self.head.buffer);
asan::unpoison_memory_region(self.head.buffer, self.page_size);
$endif
fixedblockpool_free_page(self, self.head.buffer);
FixedBlockPoolNode* iter = self.head.next; FixedBlockPoolNode* iter = self.head.next;
while (iter) while (iter)
{ {
$if env::COMPILER_SAFE_MODE && env::ADDRESS_SANITIZER: self.free_page(iter.buffer);
asan::unpoison_memory_region(iter.buffer, self.page_size);
$endif
fixedblockpool_free_page(self, iter.buffer);
FixedBlockPoolNode* current = iter; FixedBlockPoolNode* current = iter;
iter = iter.next; iter = iter.next;
allocator::free(self.allocator, current); allocator::free(self.allocator, current);
@@ -149,21 +139,15 @@ fn void* FixedBlockPool.alloc(&self)
if (self.freelist) if (self.freelist)
{ {
FixedBlockPoolEntry* entry = 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; self.freelist = entry.previous;
mem::clear(entry, self.block_size); mem::clear(entry, self.block_size);
return entry; return entry;
} }
void* end = self.tail.buffer + (self.tail.capacity * self.block_size); void* end = self.tail.buffer + (self.tail.capacity * self.block_size);
if (self.next_free >= end) fixedblockpool_new_node(self); if (self.next_free >= end) self.new_node();
void* ptr = self.next_free; void* ptr = self.next_free;
self.next_free += self.block_size; 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; return ptr;
} }
@@ -172,28 +156,30 @@ fn void* FixedBlockPool.alloc(&self)
Deallocate a block from the block pool Deallocate a block from the block pool
@require self.initialized : "The block pool must be initialized" @require self.initialized : "The block pool must be initialized"
@require fixedblockpool_check_ptr(self, ptr) : "The pointer should be part of the pool" @require self.check_ptr(ptr) : "The pointer should be part of the pool"
*> *>
fn void FixedBlockPool.dealloc(&self, void* ptr) fn void FixedBlockPool.dealloc(&self, void* ptr)
{ {
$if env::COMPILER_SAFE_MODE && !env::ADDRESS_SANITIZER: $if env::COMPILER_SAFE_MODE && !env::ADDRESS_SANITIZER:
mem::set(ptr, 0xAA, self.block_size); if (self.block_size > FixedBlockPoolEntry.sizeof)
{
mem::set(ptr + FixedBlockPoolEntry.sizeof, 0xAA, self.block_size);
}
$else
// POINT FOR IMPROVEMENT, something like:
// asan::poison_memory_region(&ptr, self.block_size);
$endif $endif
FixedBlockPoolEntry* entry = ptr; FixedBlockPoolEntry* entry = ptr;
entry.previous = self.freelist; entry.previous = self.freelist;
self.freelist = entry; self.freelist = entry;
self.used--; 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" @require self.initialized : "The block pool must be initialized"
*> *>
fn bool fixedblockpool_check_ptr(FixedBlockPool* self, void *ptr) @local fn bool FixedBlockPool.check_ptr(&self, void *ptr) @local
{ {
FixedBlockPoolNode* iter = &self.head; FixedBlockPoolNode* iter = &self.head;
@@ -210,13 +196,10 @@ fn bool fixedblockpool_check_ptr(FixedBlockPool* self, void *ptr) @local
<* <*
@require self.grow_capacity > 0 : "How many blocks will it store" @require self.grow_capacity > 0 : "How many blocks will it store"
*> *>
fn void fixedblockpool_new_node(FixedBlockPool* self) @local fn void FixedBlockPool.new_node(&self) @local
{ {
FixedBlockPoolNode* node = allocator::new(self.allocator, FixedBlockPoolNode); FixedBlockPoolNode* node = allocator::new(self.allocator, FixedBlockPoolNode);
node.buffer = fixedblockpool_allocate_page(self); 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; node.capacity = self.grow_capacity;
self.tail.next = node; self.tail.next = node;
self.tail = node; self.tail = node;
@@ -224,14 +207,13 @@ fn void fixedblockpool_new_node(FixedBlockPool* self) @local
self.allocated += node.capacity; self.allocated += node.capacity;
} }
macro void* fixedblockpool_allocate_page(FixedBlockPool* self) @private macro void* FixedBlockPool.allocate_page(&self) @private
{ {
return self.alignment > mem::DEFAULT_MEM_ALIGNMENT return self.alignment > mem::DEFAULT_MEM_ALIGNMENT
? allocator::calloc_aligned(self.allocator, self.page_size, self.alignment)!! ? allocator::calloc_aligned(self.allocator, self.page_size, self.alignment)!!
: allocator::calloc(self.allocator, self.page_size); : allocator::calloc(self.allocator, self.page_size);
} }
macro void FixedBlockPool.free_page(&self, void* page) @private
macro void fixedblockpool_free_page(FixedBlockPool* self, void* page) @private
{ {
if (self.alignment > mem::DEFAULT_MEM_ALIGNMENT) if (self.alignment > mem::DEFAULT_MEM_ALIGNMENT)
{ {

View File

@@ -54,11 +54,11 @@ fn void*? alloc(usz size, VirtualMemoryAccess access)
if (ptr != posix::MAP_FAILED) return ptr; if (ptr != posix::MAP_FAILED) return ptr;
switch (libc::errno()) switch (libc::errno())
{ {
case errno::ENOMEM: return mem::OUT_OF_MEMORY~; case errno::ENOMEM: return mem::OUT_OF_MEMORY?;
case errno::EOVERFLOW: return RANGE_OVERFLOW~; case errno::EOVERFLOW: return RANGE_OVERFLOW?;
case errno::EPERM: return ACCESS_DENIED~; case errno::EPERM: return ACCESS_DENIED?;
case errno::EINVAL: return INVALID_ARGS~; case errno::EINVAL: return INVALID_ARGS?;
default: return UNKNOWN_ERROR~; default: return UNKNOWN_ERROR?;
} }
$case env::WIN32: $case env::WIN32:
void* ptr = win32::virtualAlloc(null, aligned_alloc_size(size), MEM_RESERVE, access.to_win32()); void* ptr = win32::virtualAlloc(null, aligned_alloc_size(size), MEM_RESERVE, access.to_win32());
@@ -66,8 +66,8 @@ fn void*? alloc(usz size, VirtualMemoryAccess access)
switch (win32::getLastError()) switch (win32::getLastError())
{ {
case win32::ERROR_NOT_ENOUGH_MEMORY: case win32::ERROR_NOT_ENOUGH_MEMORY:
case win32::ERROR_COMMITMENT_LIMIT: return mem::OUT_OF_MEMORY~; case win32::ERROR_COMMITMENT_LIMIT: return mem::OUT_OF_MEMORY?;
default: return UNKNOWN_ERROR~; default: return UNKNOWN_ERROR?;
} }
$default: $default:
unsupported("Virtual alloc only available on Win32 and Posix"); unsupported("Virtual alloc only available on Win32 and Posix");
@@ -89,18 +89,18 @@ fn void? release(void* ptr, usz size)
{ {
switch (libc::errno()) switch (libc::errno())
{ {
case errno::EINVAL: return INVALID_ARGS~; // Not a valid mapping or size case errno::EINVAL: return INVALID_ARGS?; // Not a valid mapping or size
case errno::ENOMEM: return UNMAPPED_ACCESS~; // Address not mapped case errno::ENOMEM: return UNMAPPED_ACCESS?; // Address not mapped
default: return RELEASE_FAILED~; default: return RELEASE_FAILED?;
} }
} }
$case env::WIN32: $case env::WIN32:
if (win32::virtualFree(ptr, 0, MEM_RELEASE)) return; if (win32::virtualFree(ptr, 0, MEM_RELEASE)) return;
switch (win32::getLastError()) switch (win32::getLastError())
{ {
case win32::ERROR_INVALID_ADDRESS: return INVALID_ARGS~; case win32::ERROR_INVALID_ADDRESS: return INVALID_ARGS?;
case win32::ERROR_NOT_ENOUGH_MEMORY: return mem::OUT_OF_MEMORY~; case win32::ERROR_NOT_ENOUGH_MEMORY: return mem::OUT_OF_MEMORY?;
default: return RELEASE_FAILED~; default: return RELEASE_FAILED?;
} }
$default: $default:
unsupported("Virtual free only available on Win32 and Posix"); unsupported("Virtual free only available on Win32 and Posix");
@@ -124,21 +124,21 @@ fn void? protect(void* ptr, usz len, VirtualMemoryAccess access)
if (!posix::mprotect(ptr, len, access.to_posix())) return; if (!posix::mprotect(ptr, len, access.to_posix())) return;
switch (libc::errno()) switch (libc::errno())
{ {
case errno::EACCES: return ACCESS_DENIED~; case errno::EACCES: return ACCESS_DENIED?;
case errno::EINVAL: return UNALIGNED_ADDRESS~; case errno::EINVAL: return UNALIGNED_ADDRESS?;
case errno::EOVERFLOW: return RANGE_OVERFLOW~; case errno::EOVERFLOW: return RANGE_OVERFLOW?;
case errno::ENOMEM: return UNMAPPED_ACCESS~; case errno::ENOMEM: return UNMAPPED_ACCESS?;
default: return UPDATE_FAILED~; default: return UPDATE_FAILED?;
} }
$case env::WIN32: $case env::WIN32:
Win32_Protect old; Win32_Protect old;
if (win32::virtualProtect(ptr, len, access.to_win32(), &old)) return; if (win32::virtualProtect(ptr, len, access.to_win32(), &old)) return;
switch (win32::getLastError()) switch (win32::getLastError())
{ {
case win32::ERROR_INVALID_ADDRESS: return UNALIGNED_ADDRESS~; case win32::ERROR_INVALID_ADDRESS: return UNALIGNED_ADDRESS?;
case win32::ERROR_ACCESS_DENIED: return ACCESS_DENIED~; case win32::ERROR_ACCESS_DENIED: return ACCESS_DENIED?;
case win32::ERROR_INVALID_PARAMETER: return INVALID_ARGS~; case win32::ERROR_INVALID_PARAMETER: return INVALID_ARGS?;
default: return UPDATE_FAILED~; default: return UPDATE_FAILED?;
} }
$default: $default:
unsupported("'virtual_protect' is only available on Win32 and Posix."); unsupported("'virtual_protect' is only available on Win32 and Posix.");
@@ -165,12 +165,12 @@ fn void? commit(void* ptr, usz len, VirtualMemoryAccess access = READWRITE)
if (result) return; if (result) return;
switch (win32::getLastError()) switch (win32::getLastError())
{ {
case win32::ERROR_INVALID_ADDRESS: return UNALIGNED_ADDRESS~; case win32::ERROR_INVALID_ADDRESS: return UNALIGNED_ADDRESS?;
case win32::ERROR_ACCESS_DENIED: return ACCESS_DENIED~; case win32::ERROR_ACCESS_DENIED: return ACCESS_DENIED?;
case win32::ERROR_COMMITMENT_LIMIT: case win32::ERROR_COMMITMENT_LIMIT:
case win32::ERROR_NOT_ENOUGH_MEMORY: return mem::OUT_OF_MEMORY~; case win32::ERROR_NOT_ENOUGH_MEMORY: return mem::OUT_OF_MEMORY?;
case win32::ERROR_INVALID_PARAMETER: return INVALID_ARGS~; case win32::ERROR_INVALID_PARAMETER: return INVALID_ARGS?;
default: return UNKNOWN_ERROR~; default: return UNKNOWN_ERROR?;
} }
$default: $default:
unsupported("'virtual_commit' is only available on Win32 and Posix."); unsupported("'virtual_commit' is only available on Win32 and Posix.");
@@ -197,9 +197,9 @@ fn void? decommit(void* ptr, usz len, bool block = true)
{ {
switch (libc::errno()) switch (libc::errno())
{ {
case errno::EINVAL: return UNALIGNED_ADDRESS~; case errno::EINVAL: return UNALIGNED_ADDRESS?;
case errno::ENOMEM: return UNMAPPED_ACCESS~; case errno::ENOMEM: return UNMAPPED_ACCESS?;
default: return UPDATE_FAILED~; default: return UPDATE_FAILED?;
} }
} }
if (block) (void)protect(ptr, len, PROTECTED) @inline; if (block) (void)protect(ptr, len, PROTECTED) @inline;
@@ -208,10 +208,10 @@ fn void? decommit(void* ptr, usz len, bool block = true)
{ {
switch (win32::getLastError()) switch (win32::getLastError())
{ {
case win32::ERROR_INVALID_ADDRESS: return UNALIGNED_ADDRESS~; case win32::ERROR_INVALID_ADDRESS: return UNALIGNED_ADDRESS?;
case win32::ERROR_INVALID_PARAMETER: return INVALID_ARGS~; case win32::ERROR_INVALID_PARAMETER: return INVALID_ARGS?;
case win32::ERROR_ACCESS_DENIED: return ACCESS_DENIED~; case win32::ERROR_ACCESS_DENIED: return ACCESS_DENIED?;
default: return UPDATE_FAILED~; default: return UPDATE_FAILED?;
} }
} }
$default: $default:
@@ -237,15 +237,15 @@ fn void*? mmap_file(Fd fd, usz size, usz offset = 0, VirtualMemoryAccess access
if (ptr != posix::MAP_FAILED) return ptr; if (ptr != posix::MAP_FAILED) return ptr;
switch (libc::errno()) switch (libc::errno())
{ {
case errno::ENOMEM: return mem::OUT_OF_MEMORY~; case errno::ENOMEM: return mem::OUT_OF_MEMORY?;
case errno::EOVERFLOW: return RANGE_OVERFLOW~; case errno::EOVERFLOW: return RANGE_OVERFLOW?;
case errno::EPERM: return ACCESS_DENIED~; case errno::EPERM: return ACCESS_DENIED?;
case errno::EINVAL: return INVALID_ARGS~; case errno::EINVAL: return INVALID_ARGS?;
case errno::EACCES: return io::NO_PERMISSION~; case errno::EACCES: return io::NO_PERMISSION?;
case errno::EBADF: return io::FILE_NOT_VALID~; case errno::EBADF: return io::FILE_NOT_VALID?;
case errno::EAGAIN: return io::WOULD_BLOCK~; case errno::EAGAIN: return io::WOULD_BLOCK?;
case errno::ENXIO: return io::FILE_NOT_FOUND~; case errno::ENXIO: return io::FILE_NOT_FOUND?;
default: return UNKNOWN_ERROR~; default: return UNKNOWN_ERROR?;
} }
} }
@@ -321,7 +321,7 @@ fn void? VirtualMemory.destroy(&self)
return release(self.ptr, self.size); return release(self.ptr, self.size);
} }
fn CInt VirtualMemoryAccess.to_posix(self) @if(env::POSIX) fn CInt VirtualMemoryAccess.to_posix(self) @if(env::POSIX) @private
{ {
switch (self) switch (self)
{ {
@@ -336,7 +336,7 @@ fn CInt VirtualMemoryAccess.to_posix(self) @if(env::POSIX)
} }
} }
fn Win32_Protect VirtualMemoryAccess.to_win32(self) @if(env::WIN32) fn Win32_Protect VirtualMemoryAccess.to_win32(self) @if(env::WIN32) @private
{ {
switch (self) switch (self)
{ {

View File

@@ -25,7 +25,7 @@ fn char[]? WasmMemory.allocate_block(&self, usz bytes)
} }
usz blocks_required = (bytes_required + WASM_BLOCK_SIZE + 1) / WASM_BLOCK_SIZE; usz blocks_required = (bytes_required + WASM_BLOCK_SIZE + 1) / WASM_BLOCK_SIZE;
if ($$wasm_memory_grow(0, blocks_required) == -1) return mem::OUT_OF_MEMORY~; if ($$wasm_memory_grow(0, blocks_required) == -1) return mem::OUT_OF_MEMORY?;
self.allocation = $$wasm_memory_size(0) * WASM_BLOCK_SIZE; self.allocation = $$wasm_memory_size(0) * WASM_BLOCK_SIZE;
defer self.use += bytes; defer self.use += bytes;
return ((char*)self.use)[:bytes]; return ((char*)self.use)[:bytes];

View File

@@ -79,7 +79,7 @@ fn SegmentCommand64*? find_segment(MachHeader* header, char* segname)
} }
command = (void*)command + command.cmdsize; command = (void*)command + command.cmdsize;
} }
return NOT_FOUND~; return NOT_FOUND?;
} }
fn Section64*? find_section(SegmentCommand64* command, char* sectname) fn Section64*? find_section(SegmentCommand64* command, char* sectname)
{ {
@@ -89,7 +89,7 @@ fn Section64*? find_section(SegmentCommand64* command, char* sectname)
if (name_cmp(sectname, &section.sectname)) return section; if (name_cmp(sectname, &section.sectname)) return section;
section++; section++;
} }
return NOT_FOUND~; return NOT_FOUND?;
} }
macro find_segment_section_body(MachHeader* header, char* segname, char* sectname, $Type) macro find_segment_section_body(MachHeader* header, char* segname, char* sectname, $Type)

View File

@@ -7,26 +7,16 @@ macro usz _strlen(ptr) @private
return len; return len;
} }
macro int @main_no_args(#m, int, char**) macro int @main_to_err_main(#m, int, char**)
{ {
$if $typeof(#m()) == void: if (catch #m()) return 1;
#m(); return 0;
return 0;
$else
return #m();
$endif
} }
macro int @main_to_int_main(#m, int, char**) => #m();
macro int @main_args(#m, int argc, char** argv) macro int @main_to_void_main(#m, int, char**)
{ {
String[] list = args_to_strings(argc, argv); #m();
defer free(list.ptr); return 0;
$if $typeof(#m(list)) == void:
#m(list);
return 0;
$else
return #m(list);
$endif
} }
macro String[] args_to_strings(int argc, char** argv) @private macro String[] args_to_strings(int argc, char** argv) @private
@@ -42,6 +32,21 @@ macro String[] args_to_strings(int argc, char** argv) @private
} }
macro int @main_to_err_main_args(#m, int argc, char** argv)
{
String[] list = args_to_strings(argc, argv);
defer free(list.ptr);
if (catch #m(list)) return 1;
return 0;
}
macro int @main_to_int_main_args(#m, int argc, char** argv)
{
String[] list = args_to_strings(argc, argv);
defer free(list.ptr);
return #m(list);
}
macro int @_main_runner(#m, int argc, char** argv) macro int @_main_runner(#m, int argc, char** argv)
{ {
String[] list = args_to_strings(argc, argv); String[] list = args_to_strings(argc, argv);
@@ -49,20 +54,17 @@ macro int @_main_runner(#m, int argc, char** argv)
return #m(list) ? 0 : 1; return #m(list) ? 0 : 1;
} }
module std::core::main_stub @if(env::WIN32); macro int @main_to_void_main_args(#m, int argc, char** argv)
import std::os::win32;
macro win32_set_utf8_codepage() @local
{ {
// By default windows uses an OEM codepage that differs based on locale String[] list = args_to_strings(argc, argv);
// and does not support printing utf-8 characters. This allows both defer free(list.ptr);
// printing utf-8 characters from strings and reading them from stdin. #m(list);
win32::setConsoleCP(UTF8); return 0;
win32::setConsoleOutputCP(UTF8);
} }
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 macro String[] win_command_line_to_strings(ushort* cmd_line) @private
{ {
@@ -89,72 +91,90 @@ macro void release_wargs(String[] list) @private
free(list.ptr); free(list.ptr);
} }
macro int @win_main_no_args(#m, void* handle, void* prev_handle, Char16* cmd_line, int show_cmd) macro int @win_to_err_main_noargs(#m, void* handle, void* prev_handle, Char16* cmd_line, int show_cmd)
{ {
win32_set_utf8_codepage(); if (catch #m()) return 1;
$if $typeof(#m()) == void: return 0;
#m(); }
return 0; macro int @win_to_int_main_noargs(#m, void* handle, void* prev_handle, Char16* cmd_line, int show_cmd) => #m();
$else macro int @win_to_void_main_noargs(#m, void* handle, void* prev_handle, Char16* cmd_line, int show_cmd)
return #m(); {
$endif #m();
return 0;
} }
macro int @win_main_args(#m, void* handle, void* prev_handle, Char16* cmd_line, int show_cmd) macro int @win_to_err_main_args(#m, void* handle, void* prev_handle, Char16* cmd_line, int show_cmd)
{ {
win32_set_utf8_codepage();
String[] args = win_command_line_to_strings(cmd_line); String[] args = win_command_line_to_strings(cmd_line);
defer release_wargs(args); defer release_wargs(args);
$if $typeof(#m(args)) == void: if (catch #m(args)) return 1;
#m(args); return 0;
return 0;
$else
return #m(args);
$endif
} }
macro int @win_main(#m, void* handle, void* prev_handle, Char16* cmd_line, int show_cmd) macro int @win_to_int_main_args(#m, void* handle, void* prev_handle, Char16* cmd_line, int show_cmd)
{ {
win32_set_utf8_codepage();
String[] args = win_command_line_to_strings(cmd_line); String[] args = win_command_line_to_strings(cmd_line);
defer release_wargs(args); defer release_wargs(args);
$if $typeof(#m(handle, prev_handle, args, show_cmd)) == void: return #m(args);
#m(handle, prev_handle, args, show_cmd);
return 0;
$else
return #m(handle, prev_handle, args, show_cmd);
$endif
} }
macro int @win_to_void_main_args(#m, void* handle, void* prev_handle, Char16* cmd_line, int show_cmd)
macro int @wmain_main(#m, int argc, Char16** argv) {
String[] args = win_command_line_to_strings(cmd_line);
defer release_wargs(args);
#m(args);
return 0;
}
macro int @win_to_err_main(#m, void* handle, void* prev_handle, Char16* cmd_line, int show_cmd)
{
String[] args = win_command_line_to_strings(cmd_line);
defer release_wargs(args);
if (catch #m(handle, prev_handle, args, show_cmd)) return 1;
return 0;
}
macro int @win_to_int_main(#m, void* handle, void* prev_handle, Char16* cmd_line, int show_cmd)
{
String[] args = win_command_line_to_strings(cmd_line);
defer release_wargs(args);
return #m(handle, prev_handle, args, show_cmd);
}
macro int @win_to_void_main(#m, void* handle, void* prev_handle, Char16* cmd_line, int show_cmd)
{
String[] args = win_command_line_to_strings(cmd_line);
defer release_wargs(args);
#m(handle, prev_handle, args, show_cmd);
return 0;
}
macro int @wmain_to_err_main_args(#m, int argc, Char16** argv)
{ {
win32_set_utf8_codepage();
String[] args = wargs_strings(argc, argv); String[] args = wargs_strings(argc, argv);
defer release_wargs(args); defer release_wargs(args);
$if $typeof(#m(args)) == void: if (catch #m(args)) return 1;
#m(args); return 1;
return 0;
$else
return #m(args);
$endif
} }
macro int @wmain_main_no_args(#m, int argc, Char16** argv) macro int @wmain_to_int_main_args(#m, int argc, Char16** argv)
{ {
win32_set_utf8_codepage(); String[] args = wargs_strings(argc, argv);
$if $typeof(#m()) == void: defer release_wargs(args);
#m(); return #m(args);
return 0;
$else
return #m();
$endif
} }
macro int @_wmain_runner(#m, int argc, Char16** argv) macro int @_wmain_runner(#m, int argc, Char16** argv)
{ {
win32_set_utf8_codepage();
String[] args = wargs_strings(argc, argv); String[] args = wargs_strings(argc, argv);
defer release_wargs(args); defer release_wargs(args);
return #m(args) ? 0 : 1; return #m(args) ? 0 : 1;
} }
macro int @wmain_to_void_main_args(#m, int argc, Char16** argv)
{
String[] args = wargs_strings(argc, argv);
defer release_wargs(args);
#m(args);
return 0;
}

View File

@@ -6,9 +6,9 @@
then free the pointer and the atomic variable assuming that they are allocated using the Allocator in the Ref. 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) ||| $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'" @require !$defined(Type.dealloc) ||| @typeis((Type){}.dealloc(), void) : "'dealloc' must return 'void'"
*> *>
module std::core::mem::ref <Type>; module std::core::mem::ref { Type };
import std::thread, std::atomic; import std::thread, std::atomic;
const OVERALIGNED @private = Type.alignof > mem::DEFAULT_MEM_ALIGNMENT; const OVERALIGNED @private = Type.alignof > mem::DEFAULT_MEM_ALIGNMENT;
@@ -21,7 +21,7 @@ fn Ref wrap(Type* ptr, Allocator allocator = mem)
} }
<* <*
@require $vacount < 2 : "Too many arguments." @require $vacount < 2 : "Too many arguments."
@require $vacount == 0 ||| $defined(Type a = $vaexpr[0]) : "The first argument must be an initializer for the type" @require $vacount == 0 ||| @assignable_to($vaexpr[0], Type) : "The first argument must be an initializer for the type"
*> *>
macro Ref new(..., Allocator allocator = mem) macro Ref new(..., Allocator allocator = mem)
{ {
@@ -99,7 +99,7 @@ struct RefCounted
} }
<* <*
@require $defined(RefCounted* c = refcounted) : "Expected a ref counted value" @require @assignable_to(refcounted, RefCounted*) : "Expected a ref counted value"
*> *>
macro retain(refcounted) macro retain(refcounted)
{ {
@@ -112,8 +112,8 @@ macro retain(refcounted)
} }
<* <*
@require $defined(RefCounted* c = refcounted) : "Expected a ref counted value" @require @assignable_to(refcounted, RefCounted*) : "Expected a ref counted value"
@require !$defined(refcounted.dealloc()) ||| $typeof(refcounted.dealloc()) == void @require !$defined(refcounted.dealloc()) ||| @typeis(refcounted.dealloc(), void)
: "Expected refcounted type to have a valid dealloc" : "Expected refcounted type to have a valid dealloc"
*> *>
macro void release(refcounted) macro void release(refcounted)

View File

@@ -27,7 +27,7 @@ macro @enum_lookup($Type, #value, value)
$foreach $val : $Type.values: $foreach $val : $Type.values:
if ($val.#value == value) return $val; if ($val.#value == value) return $val;
$endforeach $endforeach
return NOT_FOUND~; return NOT_FOUND?;
} }
macro @enum_lookup_new($Type, $name, value) macro @enum_lookup_new($Type, $name, value)
@@ -35,14 +35,14 @@ macro @enum_lookup_new($Type, $name, value)
$foreach $val : $Type.values: $foreach $val : $Type.values:
if ($val.$eval($name) == value) return $val; if ($val.$eval($name) == value) return $val;
$endforeach $endforeach
return NOT_FOUND~; return NOT_FOUND?;
} }
module std::core::runtime @if(env::FREESTANDING_WASM); module std::core::runtime @if(WASM_NOLIBC);
extern fn void __wasm_call_ctors(); 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. // The linker synthesizes this to call constructors.
__wasm_call_ctors(); __wasm_call_ctors();

View File

@@ -51,32 +51,13 @@ fn void set_benchmark_func_iterations(String func, uint value) @builtin
Clock benchmark_clock @local; Clock benchmark_clock @local;
NanoDuration benchmark_nano_seconds @local; NanoDuration benchmark_nano_seconds @local;
long cycle_start @local;
long cycle_stop @local;
DString benchmark_log @local; DString benchmark_log @local;
bool benchmark_warming @local; bool benchmark_warming @local;
uint this_iteration @local; uint this_iteration @local;
bool benchmark_stop @local;
macro void @start_benchmark() macro @start_benchmark() => benchmark_clock = std::time::clock::now();
{ macro @end_benchmark() => benchmark_nano_seconds = benchmark_clock.mark();
benchmark_clock = clock::now(); macro @log_benchmark(msg, args...) => @pool()
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; if (benchmark_warming) return;
@@ -104,6 +85,10 @@ fn bool run_benchmarks(BenchmarkUnit[] benchmarks)
name.clear(); name.clear();
long sys_clock_started;
long sys_clock_finished;
long sys_clocks;
foreach (unit : benchmarks) foreach (unit : benchmarks)
{ {
defer name.clear(); defer name.clear();
@@ -119,55 +104,42 @@ fn bool run_benchmarks(BenchmarkUnit[] benchmarks)
benchmark_warming = false; benchmark_warming = false;
NanoDuration running_timer; NanoDuration running_timer;
long total_clocks; sys_clock_started = $$sysclock();
benchmark_nano_seconds = {};
uint current_benchmark_iterations = bench_fn_iters[unit.name] ?? benchmark_max_iterations; uint current_benchmark_iterations = bench_fn_iters[unit.name] ?? benchmark_max_iterations;
char[] perc_str = { [0..19] = ' ', [20] = 0 }; char[] perc_str = { [0..19] = ' ', [20] = 0 };
int perc = 0; int perc = 0;
uint print_step = current_benchmark_iterations / 100;
if (print_step == 0) print_step = 1;
for (this_iteration = 0; this_iteration < current_benchmark_iterations; ++this_iteration, benchmark_nano_seconds = {}) for (this_iteration = 0; this_iteration < current_benchmark_iterations; ++this_iteration)
{ {
if (this_iteration % print_step == 0) // 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));
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::printf("\r%s [%s] %d / %d (%d%%)", name.str_view(), (ZString)perc_str, this_iteration, current_benchmark_iterations, perc);
io::stdout().flush()!!; io::stdout().flush()!!;
}
@start_benchmark(); // can be overridden by calls inside the unit's func @start_benchmark(); // can be overridden by calls inside the unit's func
unit.func() @inline; 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 if (!benchmark_nano_seconds) @end_benchmark();
total_clocks += cycle_stop - cycle_start;
running_timer += benchmark_nano_seconds; running_timer += benchmark_nano_seconds;
} }
float clock_cycles = (float)total_clocks / current_benchmark_iterations; sys_clock_finished = $$sysclock();
sys_clocks = sys_clock_finished - sys_clock_started;
float clock_cycles = (float)sys_clocks / current_benchmark_iterations;
float measurement = (float)running_timer / current_benchmark_iterations; float measurement = (float)running_timer / current_benchmark_iterations;
String[] units = { "nanoseconds", "microseconds", "milliseconds", "seconds" }; String[] units = { "nanoseconds", "microseconds", "milliseconds", "seconds" };
float adjusted_measurement = measurement; float adjusted_measurement = measurement;
while (adjusted_measurement > 1_000) adjusted_measurement /= 1_000; 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::printf("\r%s ", name.str_view());
io::printfn( io::printfn("[COMPLETE] %.2f %s, %.2f CPU clocks, %d iterations",
"[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_measurement,
units[math::min(3, (int)math::floor(math::log(measurement, 1_000)))],
clock_cycles,
current_benchmark_iterations,
adjusted_runtime_total,
units[math::min(3, (int)math::floor(math::log((float)running_timer, 1_000)))],
);
} }
io::printfn("\n%d benchmark%s run.\n", benchmarks.len, benchmarks.len > 1 ? "s" : ""); io::printfn("\n%d benchmark%s run.\n", benchmarks.len, benchmarks.len > 1 ? "s" : "");

View File

@@ -4,8 +4,8 @@
module std::core::runtime; module std::core::runtime;
import std::core::test @public; import std::core::test @public;
import std::core::mem::allocator @public; import std::core::mem::allocator @public;
import libc, std::time, std::io, std::sort, std::os; import libc, std::time, std::io, std::sort;
import std::os::env;
alias TestFn = fn void(); alias TestFn = fn void();
@@ -14,12 +14,10 @@ TestContext* test_context @private;
struct TestContext struct TestContext
{ {
JmpBuf buf; 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; 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; bool breakpoint_on_assert;
<* Controls level of printed logs *>
LogPriority log_level;
// internal state // internal state
bool assert_print_backtrace; bool assert_print_backtrace;
@@ -27,8 +25,6 @@ struct TestContext
bool is_in_panic; bool is_in_panic;
bool is_quiet_mode; bool is_quiet_mode;
bool is_no_capture; bool is_no_capture;
bool sort;
bool check_leaks;
String current_test_name; String current_test_name;
TestFn setup_fn; TestFn setup_fn;
TestFn teardown_fn; TestFn teardown_fn;
@@ -90,21 +86,7 @@ fn bool terminal_has_ansi_codes() @local => @pool()
$endif $endif
} }
fn void sig_bus_error(CInt i, void*, void* context) @local @if(env::POSIX)
{
panic_test("Bus error", "Unknown", "Unknown", 1, posix::stack_instruction(context));
}
fn void sig_segmentation_fault(CInt i, void*, void* context) @local @if(env::POSIX)
{
panic_test("Segmentation fault", "Unknown", "Unknown", 1, posix::stack_instruction(context));
}
fn void test_panic(String message, String file, String function, uint line) @local fn void test_panic(String message, String file, String function, uint line) @local
{
panic_test(message, file, function, line);
}
fn void panic_test(String message, String file, String function, uint line, void* extra_trace = null) @local
{ {
if (test_context.is_in_panic) return; if (test_context.is_in_panic) return;
test_context.is_in_panic = true; test_context.is_in_panic = true;
@@ -114,7 +96,7 @@ fn void panic_test(String message, String file, String function, uint line, void
if (test_context.assert_print_backtrace) if (test_context.assert_print_backtrace)
{ {
$if env::NATIVE_STACKTRACE: $if env::NATIVE_STACKTRACE:
builtin::print_backtrace(message, extra_trace ? 3 : 0, extra_trace); builtin::print_backtrace(message, 0);
$endif $endif
} }
io::printf("\nTest failed ^^^ ( %s:%s ) %s\n", file, line, message); io::printf("\nTest failed ^^^ ( %s:%s ) %s\n", file, line, message);
@@ -142,7 +124,7 @@ fn void mute_output() @local
File* stderr = io::stderr(); File* stderr = io::stderr();
*stderr = test_context.fake_stdout; *stderr = test_context.fake_stdout;
*stdout = test_context.fake_stdout; *stdout = test_context.fake_stdout;
(void)test_context.fake_stdout.set_cursor(0)!!; (void)test_context.fake_stdout.seek(0, Seek.SET)!!;
} }
fn void unmute_output(bool has_error) @local fn void unmute_output(bool has_error) @local
@@ -155,9 +137,10 @@ fn void unmute_output(bool has_error) @local
*stderr = test_context.stored.stderr; *stderr = test_context.stored.stderr;
*stdout = test_context.stored.stdout; *stdout = test_context.stored.stdout;
ulong log_size = test_context.fake_stdout.cursor()!!; usz log_size = test_context.fake_stdout.seek(0, Seek.CURSOR)!!;
if (has_error) 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]"); io::printn(test_context.has_ansi_codes ? "[\e[0;31mFAIL\e[0m]" : "[FAIL]");
} }
@@ -165,7 +148,7 @@ fn void unmute_output(bool has_error) @local
{ {
test_context.fake_stdout.write_byte('\n')!!; test_context.fake_stdout.write_byte('\n')!!;
test_context.fake_stdout.write_byte('\0')!!; test_context.fake_stdout.write_byte('\0')!!;
test_context.fake_stdout.set_cursor(0)!!; (void)test_context.fake_stdout.seek(0, Seek.SET)!!;
io::printfn("\n========== TEST LOG ============"); io::printfn("\n========== TEST LOG ============");
io::printfn("%s\n", test_context.current_test_name); io::printfn("%s\n", test_context.current_test_name);
@@ -183,19 +166,16 @@ fn void unmute_output(bool has_error) @local
(void)stdout.flush(); (void)stdout.flush();
} }
fn bool run_tests(String[] args, TestUnit[] tests) @private fn bool run_tests(String[] args, TestUnit[] tests) @private
{ {
usz max_name; usz max_name;
bool sort_tests = true;
bool check_leaks = true;
if (!tests.len) if (!tests.len)
{ {
io::printn("There are no test units to run."); io::printn("There are no test units to run.");
return true; // no tests == technically a pass return true; // no tests == technically a pass
} }
$if !env::NO_LIBC && env::POSIX:
posix::install_signal_handler(libc::SIGBUS, &sig_bus_error);
posix::install_signal_handler(libc::SIGSEGV, &sig_segmentation_fault);
$endif
foreach (&unit : tests) foreach (&unit : tests)
{ {
if (max_name < unit.name.len) max_name = unit.name.len; if (max_name < unit.name.len) max_name = unit.name.len;
@@ -204,9 +184,6 @@ $endif
{ {
.assert_print_backtrace = true, .assert_print_backtrace = true,
.breakpoint_on_assert = false, .breakpoint_on_assert = false,
.sort = true,
.check_leaks = true,
.log_level = LogPriority.ERROR,
.test_filter = "", .test_filter = "",
.has_ansi_codes = terminal_has_ansi_codes(), .has_ansi_codes = terminal_has_ansi_codes(),
.stored.allocator = mem, .stored.allocator = mem,
@@ -220,11 +197,10 @@ $endif
case "--test-breakpoint": case "--test-breakpoint":
context.breakpoint_on_assert = true; context.breakpoint_on_assert = true;
case "--test-nosort": case "--test-nosort":
context.sort = false; sort_tests = false;
case "--test-noleak": case "--test-noleak":
context.check_leaks = false; check_leaks = false;
case "--test-nocapture": case "--test-nocapture":
case "--test-show-output":
context.is_no_capture = true; context.is_no_capture = true;
case "--noansi": case "--noansi":
context.has_ansi_codes = false; context.has_ansi_codes = false;
@@ -240,30 +216,13 @@ $endif
} }
context.test_filter = args[i + 1]; context.test_filter = args[i + 1];
i++; i++;
case "--test-log-level":
if (i == args.len - 1)
{
io::printn("Missing log level for argument `--test-log-level`.");
return false;
}
@pool()
{
String upper = args[i + 1].to_upper_copy(tmem);
if (catch @try(context.log_level, enum_by_name(LogPriority, upper)))
{
io::printn("Log level given to `--test-log-level` is not one of verbose, debug, info, warn, error or critical.");
return false;
}
};
i++;
default: default:
io::printfn("Unknown argument: %s", args[i]); io::printfn("Unknown argument: %s", args[i]);
} }
} }
test_context = &context; test_context = &context;
log::set_priority_all(test_context.log_level);
if (context.sort) if (sort_tests)
{ {
quicksort(tests, &cmp_test_unit); quicksort(tests, &cmp_test_unit);
} }
@@ -323,17 +282,14 @@ $endif
{ {
mute_output(); mute_output();
mem.clear(); mem.clear();
if (context.check_leaks) allocator::thread_allocator = &mem; if (check_leaks) allocator::thread_allocator = &mem;
@pool() unit.func();
{
unit.func();
};
// track cleanup that may take place in teardown_fn // track cleanup that may take place in teardown_fn
if (context.teardown_fn) if (context.teardown_fn)
{ {
context.teardown_fn(); context.teardown_fn();
} }
if (context.check_leaks) allocator::thread_allocator = context.stored.allocator; if (check_leaks) allocator::thread_allocator = context.stored.allocator;
unmute_output(false); // all good, discard output unmute_output(false); // all good, discard output
if (mem.has_leaks()) if (mem.has_leaks())

View File

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

View File

@@ -2,16 +2,16 @@ module std::core::sanitizer::tsan;
typedef MutexFlags = inline CUInt; typedef MutexFlags = inline CUInt;
const MutexFlags MUTEX_LINKER_INIT = (MutexFlags)1 << 0; const MutexFlags MUTEX_LINKER_INIT = 1 << 0;
const MutexFlags MUTEX_WRITE_REENTRANT = (MutexFlags)1 << 1; const MutexFlags MUTEX_WRITE_REENTRANT = 1 << 1;
const MutexFlags MUTEX_READ_REENTRANT = (MutexFlags)1 << 2; const MutexFlags MUTEX_READ_REENTRANT = 1 << 2;
const MutexFlags MUTEX_NOT_STATIC = (MutexFlags)1 << 8; const MutexFlags MUTEX_NOT_STATIC = 1 << 8;
const MutexFlags MUTEX_READ_LOCK = (MutexFlags)1 << 3; const MutexFlags MUTEX_READ_LOCK = 1 << 3;
const MutexFlags MUTEX_TRY_LOCK = (MutexFlags)1 << 4; const MutexFlags MUTEX_TRY_LOCK = 1 << 4;
const MutexFlags MUTEX_TRY_LOCK_FAILED = (MutexFlags)1 << 5; const MutexFlags MUTEX_TRY_LOCK_FAILED = 1 << 5;
const MutexFlags MUTEX_RECURSIVE_LOCK = (MutexFlags)1 << 6; const MutexFlags MUTEX_RECURSIVE_LOCK = 1 << 6;
const MutexFlags MUTEX_RECURSIVE_UNLOCK = (MutexFlags)1 << 7; const MutexFlags MUTEX_RECURSIVE_UNLOCK = 1 << 7;
const MutexFlags MUTEX_TRY_READ_LOCK = MUTEX_READ_LOCK | MUTEX_TRY_LOCK; const MutexFlags MUTEX_TRY_READ_LOCK = MUTEX_READ_LOCK | MUTEX_TRY_LOCK;
const MutexFlags MUTEX_TRY_READ_LOCK_FAILED = MUTEX_TRY_READ_LOCK | MUTEX_TRY_LOCK_FAILED; const MutexFlags MUTEX_TRY_READ_LOCK_FAILED = MUTEX_TRY_READ_LOCK | MUTEX_TRY_LOCK_FAILED;
macro void mutex_create(void* addr, MutexFlags flags) { $if env::THREAD_SANITIZER: __tsan_mutex_create(addr, flags); $endif } macro void mutex_create(void* addr, MutexFlags flags) { $if env::THREAD_SANITIZER: __tsan_mutex_create(addr, flags); $endif }

View File

@@ -1,10 +1,10 @@
module std::core::array; module std::core::array::slice {Type};
<* <*
A slice2d allows slicing an array like int[10][10] into an arbitrary "int[][]"-like counterpart A slice2d allows slicing an array like int[10][10] into an arbitrary "int[][]"-like counterpart
Typically you'd use array::slice2d(...) to create one. Typically you'd use array::slice2d(...) to create one.
*> *>
struct Slice2d <Type> struct Slice2d
{ {
Type* ptr; Type* ptr;
usz inner_len; usz inner_len;

View File

@@ -1,16 +1,15 @@
module std::core::string; module std::core::string;
import std::io, std::ascii; import std::io;
import std::core::mem::allocator; import std::core::mem::allocator;
typedef String @if(!$defined(String)) = inline char[];
typedef String @constinit @if(!$defined(String)) = inline char[];
<* <*
ZString is a pointer to a zero terminated array of chars. ZString is a pointer to a zero terminated array of chars.
Use ZString when you need to interop with C zero terminated strings. Use ZString when you need to interop with C zero terminated strings.
*> *>
typedef ZString @constinit = inline char*; typedef ZString = inline char*;
<* <*
WString is a pointer to a zero terminated array of Char16. WString is a pointer to a zero terminated array of Char16.
@@ -72,7 +71,7 @@ macro WString @wstring(String $string) @builtin
} }
<* <*
Create a slice of an UTF16 encoded string at compile time. Create a slice of an UTF32 encoded string at compile time.
@param $string : "The string to encode" @param $string : "The string to encode"
*> *>
@@ -98,8 +97,7 @@ fn ZString tformat_zstr(String fmt, args...) @format(0)
} }
<* <*
Return a new String created using the formatting function, this function will implicitly Return a new String created using the formatting function.
use the temp allocator.
@param [inout] allocator : `The allocator to use` @param [inout] allocator : `The allocator to use`
@param [in] fmt : `The formatting string` @param [in] fmt : `The formatting string`
@@ -112,25 +110,16 @@ fn String format(Allocator allocator, String fmt, args...) @format(1) => @pool()
} }
<* <*
Return a new String created using the formatting function, the resulting string must fit the buffer. Return a new String created using the formatting function.
@param [inout] buffer : `The buffer to use` @param [inout] buffer : `The buffer to use`
@param [in] fmt : `The formatting string` @param [in] fmt : `The formatting string`
*> *>
fn String bformat(char[] buffer, String fmt, args...) @format(1) fn String bformat(char[] buffer, String fmt, args...) @format(1)
{ {
Formatter f; DString str = dstring::new_with_capacity(allocator::wrap(buffer), fmt.len + args.len * 8);
OutputFn format_fn = fn void?(void* buf, char c) { str.appendf(fmt, ...args);
char[]* buffer_ref = buf; return str.str_view();
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];
} }
<* <*
@@ -149,7 +138,7 @@ fn String tformat(String fmt, args...) @format(0)
Check if a character is in a set. Check if a character is in a set.
@param c : `the character to check` @param c : `the character to check`
@param [in] set : `String containing the characters` @param [in] set : `The formatting string`
@pure @pure
@return `True if a character is in the set` @return `True if a character is in the set`
*> *>
@@ -159,44 +148,31 @@ macro bool char_in_set(char c, String set)
return false; return false;
} }
<*
Join together an array of strings via a "joiner" sequence, which is inserted between each element.
@param [&inout] allocator : "The allocator to use."
@param [in] s : "An array of strings to join in sequence."
@param [in] joiner : "The string used to join each element of `s`."
@return "A single string containing the result, allocated via `allocator`, safe to convert to a ZString."
*>
fn String join(Allocator allocator, String[] s, String joiner) fn String join(Allocator allocator, String[] s, String joiner)
{ {
if (!s) if (!s)
{ {
return (String)allocator::new_array(allocator, char, 2)[:0]; return (String)allocator::new_array(allocator, char, 2)[:0];
} }
usz joiner_len = joiner.len;
usz total_size = joiner_len * (s.len - 1) + 1; usz total_size = joiner.len * s.len;
foreach (String* &str : s) foreach (String* &str : s)
{ {
total_size += str.len; total_size += str.len;
} }
char[] data = allocator::alloc_array(allocator, char, total_size); @pool()
usz offset = s[0].len;
data[:offset] = s[0][:offset];
foreach (String* &str : s[1..])
{ {
data[offset:joiner_len] = joiner[:joiner_len]; DString res = dstring::temp_with_capacity(total_size);
offset += joiner_len; res.append(s[0]);
usz len = str.len; foreach (String* &str : s[1..])
data[offset:len] = str.[:len]; {
offset += len; res.append(joiner);
res.append(*str);
}
return res.copy_str(allocator);
}; };
data[offset] = 0;
return (String)data[:offset];
} }
<* Alias for `string::join` using the temp allocator. *>
macro String tjoin(String[] s, String joiner) => join(tmem, s, joiner);
<* <*
Replace all instances of one substring with a different string. Replace all instances of one substring with a different string.
@@ -206,18 +182,13 @@ macro String tjoin(String[] s, String joiner) => join(tmem, s, joiner);
@param [&inout] allocator : `The allocator to use for the String` @param [&inout] allocator : `The allocator to use for the String`
@return "The new string with the elements replaced" @return "The new string with the elements replaced"
*> *>
fn String String.replace(self, Allocator allocator, String needle, String new_str) @nodiscard => @pool() fn String String.replace(self, Allocator allocator, String needle, String new_str) @nodiscard
{ {
Splitter s = self.tokenize_all(needle); @pool()
DString d;
d.init(tmem, new_str.len * 2 + self.len + 16);
(void)d.append(s.next());
while (try element = s.next())
{ {
d.append(new_str); String[] split = self.tsplit(needle);
d.append(element); return dstring::join(tmem, split, new_str).copy_str(mem);
} };
return d.copy_str(allocator);
} }
<* <*
@@ -230,16 +201,8 @@ fn String String.replace(self, Allocator allocator, String needle, String new_st
*> *>
fn String String.treplace(self, String needle, String new_str) fn String String.treplace(self, String needle, String new_str)
{ {
Splitter s = self.tokenize_all(needle); String[] split = self.tsplit(needle);
DString d; return dstring::join(tmem, split, new_str).str_view();
d.init(tmem, new_str.len * 2 + self.len + 16);
(void)d.append(s.next());
while (try element = s.next())
{
d.append(new_str);
d.append(element);
}
return d.str_view();
} }
@@ -251,28 +214,11 @@ fn String String.treplace(self, String needle, String new_str)
@pure @pure
@return `a substring of the string passed in` @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); 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. Remove characters from the front of a string.
@@ -281,7 +227,7 @@ fn String String.trim_charset(self, AsciiCharset to_trim = ascii::WHITESPACE_SET
@pure @pure
@return `a substring of the string passed in` @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 start = 0;
usz len = self.len; usz len = self.len;
@@ -298,7 +244,7 @@ fn String String.trim_left(self, String to_trim = " \n\t\r\f\v")
@pure @pure
@return `a substring of the string passed in` @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; usz len = self.len;
while (len > 0 && char_in_set(self[len - 1], to_trim)) len--; while (len > 0 && char_in_set(self[len - 1], to_trim)) len--;
@@ -384,7 +330,7 @@ fn String[] String.split(self, Allocator allocator, String delimiter, usz max =
bool no_more = false; bool no_more = false;
while (!no_more) while (!no_more)
{ {
usz? index = i == max - 1 ? NOT_FOUND~ : self.index_of(delimiter); usz? index = i == max - 1 ? NOT_FOUND? : self.index_of(delimiter);
String res @noinit; String res @noinit;
if (try index) if (try index)
{ {
@@ -443,7 +389,7 @@ fn String[]? String.split_to_buffer(s, String delimiter, String[] buffer, usz ma
bool no_more = false; bool no_more = false;
while (!no_more) while (!no_more)
{ {
usz? index = i == max - 1 ? NOT_FOUND~ : s.index_of(delimiter); usz? index = i == max - 1 ? NOT_FOUND? : s.index_of(delimiter);
String res @noinit; String res @noinit;
if (try index) if (try index)
{ {
@@ -461,7 +407,7 @@ fn String[]? String.split_to_buffer(s, String delimiter, String[] buffer, usz ma
} }
if (i == max_capacity) if (i == max_capacity)
{ {
return BUFFER_EXCEEDED~; return BUFFER_EXCEEDED?;
} }
buffer[i++] = res; buffer[i++] = res;
} }
@@ -481,19 +427,6 @@ fn bool String.contains(s, String substr)
return @ok(s.index_of(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. Check how many non-overlapping instances of a substring there is.
@@ -542,7 +475,7 @@ fn usz? String.index_of_char(self, char character)
{ {
if (c == character) return i; if (c == character) return i;
} }
return NOT_FOUND~; return NOT_FOUND?;
} }
<* <*
@@ -565,7 +498,7 @@ fn usz? String.index_of_chars(String self, char[] characters)
} }
} }
return NOT_FOUND~; return NOT_FOUND?;
} }
<* <*
@@ -582,12 +515,12 @@ fn usz? String.index_of_chars(String self, char[] characters)
fn usz? String.index_of_char_from(self, char character, usz start_index) fn usz? String.index_of_char_from(self, char character, usz start_index)
{ {
usz len = self.len; usz len = self.len;
if (len <= start_index) return NOT_FOUND~; if (len <= start_index) return NOT_FOUND?;
for (usz i = start_index; i < len; i++) for (usz i = start_index; i < len; i++)
{ {
if (self[i] == character) return i; if (self[i] == character) return i;
} }
return NOT_FOUND~; return NOT_FOUND?;
} }
<* <*
@@ -606,7 +539,7 @@ fn usz? String.rindex_of_char(self, char character)
{ {
if (c == character) return i; if (c == character) return i;
} }
return NOT_FOUND~; return NOT_FOUND?;
} }
<* <*
@@ -631,7 +564,7 @@ fn usz? String.index_of(self, String substr)
if (c == first && self[i : needed] == substr) return i; if (c == first && self[i : needed] == substr) return i;
} }
} }
return NOT_FOUND~; return NOT_FOUND?;
} }
<* <*
@@ -656,7 +589,7 @@ fn usz? String.rindex_of(self, String substr)
if (c == first && self[i : needed] == substr) return i; if (c == first && self[i : needed] == substr) return i;
} }
} }
return NOT_FOUND~; return NOT_FOUND?;
} }
fn bool ZString.eq(self, ZString other) @operator(==) fn bool ZString.eq(self, ZString other) @operator(==)
@@ -1042,12 +975,12 @@ macro String.to_integer(self, $Type, int base = 10)
usz index = 0; usz index = 0;
char* ptr = self.ptr; char* ptr = self.ptr;
while (index < len && ptr[index].is_blank()) index++; while (index < len && ptr[index].is_blank()) index++;
if (len == index) return EMPTY_STRING~; if (len == index) return EMPTY_STRING?;
bool is_negative; bool is_negative;
switch (self[index]) switch (self[index])
{ {
case '-': case '-':
if ($Type.min == 0) return NEGATIVE_VALUE~; if ($Type.min == 0) return NEGATIVE_VALUE?;
is_negative = true; is_negative = true;
index++; index++;
case '+': case '+':
@@ -1055,7 +988,7 @@ macro String.to_integer(self, $Type, int base = 10)
default: default:
break; break;
} }
if (len == index) return MALFORMED_INTEGER~; if (len == index) return MALFORMED_INTEGER?;
$Type base_used = ($Type)base; $Type base_used = ($Type)base;
if (self[index] == '0' && base == 10) if (self[index] == '0' && base == 10)
{ {
@@ -1078,7 +1011,7 @@ macro String.to_integer(self, $Type, int base = 10)
default: default:
break; break;
} }
if (len == index) return MALFORMED_INTEGER~; if (len == index) return MALFORMED_INTEGER?;
} }
$Type value = 0; $Type value = 0;
while (index != len) while (index != len)
@@ -1088,18 +1021,22 @@ macro String.to_integer(self, $Type, int base = 10)
{ {
case base_used < 10 || c < 'A': c -= '0'; case base_used < 10 || c < 'A': c -= '0';
case c <= 'F': c -= 'A' - 10; case c <= 'F': c -= 'A' - 10;
case c < 'a' || c > 'f': return MALFORMED_INTEGER~; case c < 'a' || c > 'f': return MALFORMED_INTEGER?;
default: c -= 'a' - 10; default: c -= 'a' - 10;
} }
if (c >= base_used) return MALFORMED_INTEGER~; if (c >= base_used) return MALFORMED_INTEGER?;
do do
{ {
if (is_negative) if (is_negative)
{ {
value = value.overflow_mul(base_used).overflow_sub(c) ?? INTEGER_OVERFLOW~!; $Type new_value = value * base_used - c;
if (new_value > value) return INTEGER_OVERFLOW?;
value = new_value;
break; break;
} }
value = value.overflow_mul(base_used).overflow_add(c) ?? INTEGER_OVERFLOW~!; $Type new_value = value * base_used + c;
if (new_value < value) return INTEGER_OVERFLOW?;
value = new_value;
}; };
} }
return value; return value;
@@ -1213,21 +1150,16 @@ fn void Splitter.reset(&self)
self.current = 0; self.current = 0;
} }
fn bool Splitter.at_end(&self)
{
return self.current > self.string.len;
}
fn String? Splitter.next(&self) fn String? Splitter.next(&self)
{ {
while (true) while (true)
{ {
usz len = self.string.len; usz len = self.string.len;
usz current = self.current; usz current = self.current;
if (current > len) return NO_MORE_ELEMENT~; if (current > len) return NO_MORE_ELEMENT?;
if (current == len) if (current == len)
{ {
if (self.type != TOKENIZE_ALL) return NO_MORE_ELEMENT~; if (self.type != TOKENIZE_ALL) return NO_MORE_ELEMENT?;
self.current++; self.current++;
return self.string[current - 1:0]; return self.string[current - 1:0];
} }
@@ -1239,7 +1171,7 @@ fn String? Splitter.next(&self)
if (!next && self.type == TOKENIZE) continue; if (!next && self.type == TOKENIZE) continue;
return remaining[:next]; return remaining[:next];
} }
self.current = len + 1; self.current = len;
return remaining; return remaining;
} }
} }

View File

@@ -1,4 +1,4 @@
// Copyright (c) 2024-2025 Christoffer Lerno. All rights reserved. // Copyright (c) 2024 Christoffer Lerno. All rights reserved.
// Use of this source code is governed by the MIT license // Use of this source code is governed by the MIT license
// a copy of which can be found in the LICENSE_STDLIB file. // a copy of which can be found in the LICENSE_STDLIB file.
@@ -22,57 +22,43 @@ faultdef INVALID_ESCAPE_SEQUENCE, UNTERMINATED_STRING, INVALID_HEX_ESCAPE, INVAL
*> *>
fn String String.escape(String s, Allocator allocator, bool strip_quotes = true) fn String String.escape(String s, Allocator allocator, bool strip_quotes = true)
{ {
// Conservative allocation: most strings need minimal escaping // Conservative allocation: most strings need minimal escaping
usz initial_capacity = s.len + s.len / 5 + 2; // ~1.2x + quotes usz initial_capacity = s.len + s.len / 5 + 2; // ~1.2x + quotes
DString result = dstring::new_with_capacity(allocator, initial_capacity);
if (allocator == tmem) if (!strip_quotes) result.append_char('"');
{
DString result = dstring::new_with_capacity(tmem, initial_capacity); foreach (char c : s)
escape_dstring(s, result, strip_quotes); {
return result.str_view(); switch (c)
} {
@pool() case '"': result.append(`\"`);
{ case '\\': result.append(`\\`);
DString result = dstring::temp_with_capacity(initial_capacity); case '\b': result.append(`\b`);
escape_dstring(s, result, strip_quotes); case '\f': result.append(`\f`);
return result.copy_str(allocator); 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);
} }
fn void escape_dstring(String s, DString result, bool strip_quotes) @private
{
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('"');
}
<* <*
Escape a string using the temp allocator. Escape a string using the temp allocator.
@@ -90,33 +76,33 @@ fn String String.tescape(String s, bool strip_quotes = false) => s.escape(tmem,
*> *>
fn usz escape_len(String s) fn usz escape_len(String s)
{ {
usz len = 2; // For quotes usz len = 2; // For quotes
foreach (char c : s) foreach (char c : s)
{ {
switch (c) switch (c)
{ {
case '"': case '"':
case '\\': case '\\':
case '\b': case '\b':
case '\f': case '\f':
case '\n': case '\n':
case '\r': case '\r':
case '\t': case '\t':
case '\v': case '\v':
case '\0': case '\0':
len += 2; // \X len += 2; // \X
default: default:
if (c >= 32 && c <= 126) if (c >= 32 && c <= 126)
{ {
len += 1; len += 1;
} }
else else
{ {
len += 4; // \xHH len += 4; // \xHH
} }
} }
} }
return len; return len;
} }
<* <*
@@ -125,103 +111,90 @@ fn usz escape_len(String s)
@param allocator : "The allocator to use for the result" @param allocator : "The allocator to use for the result"
@param s : "The quoted string to unescape" @param s : "The quoted string to unescape"
@param allow_unquoted : "Set to true to unescape strings not surrounded by quotes, defaults to false" @param allow_unquoted : "Set to true to unescape strings not surrounded by quotes, defaults to false"
@param lenient : "Be lenient with escapes, resolving unknown sequences to the escape character, defaults to false"
@return "The unescaped string without quotes, safe to convert to ZString" @return "The unescaped string without quotes, safe to convert to ZString"
@return? UNTERMINATED_STRING, INVALID_ESCAPE_SEQUENCE, INVALID_HEX_ESCAPE, INVALID_UNICODE_ESCAPE @return? UNTERMINATED_STRING, INVALID_ESCAPE_SEQUENCE, INVALID_HEX_ESCAPE, INVALID_UNICODE_ESCAPE
*> *>
fn String? String.unescape(String s, Allocator allocator, bool allow_unquoted = false, bool lenient = false) fn String? String.unescape(String s, Allocator allocator, bool allow_unquoted = false)
{ {
if (s.len >= 2 && s[0] == '"' && s[^1] == '"') if (s.len >= 2 && s[0] == '"' && s[^1] == '"')
{ {
// Remove quotes. // Remove quotes.
s = s[1:^2]; s = s[1:^2];
} }
else if (!allow_unquoted) return UNTERMINATED_STRING~; else if (!allow_unquoted) return UNTERMINATED_STRING?;
// Handle empty string case // Handle empty string case
if (!s.len) if (!s.len)
{ {
return "".copy(allocator); return "".copy(allocator);
} }
if (allocator == tmem)
{ DString result = dstring::new_with_capacity(allocator, s.len);
DString result = dstring::new_with_capacity(tmem, s.len);
unescape_dstring(s, result, allow_unquoted, lenient)!;
return result.str_view();
}
@pool()
{
DString result = dstring::temp_with_capacity(s.len);
unescape_dstring(s, result, allow_unquoted, lenient)!;
return result.copy_str(allocator);
};
}
fn void? unescape_dstring(String s, DString result, bool allow_unquoted = false, bool lenient = false) @private
{
usz len = s.len; usz len = s.len;
for (usz i = 0; i < len; i++) for (usz i = 0; i < len; i++)
{ {
char c = s[i]; char c = s[i];
if (c != '\\') if (c != '\\')
{ {
result.append_char(c); result.append_char(c);
continue; continue;
} }
// Handle escape sequence // Handle escape sequence
if (i + 1 >= len) return INVALID_ESCAPE_SEQUENCE~; if (i + 1 >= len) return INVALID_ESCAPE_SEQUENCE?;
char escape_char = s[++i]; char escape_char = s[++i];
switch (escape_char) switch (escape_char)
{ {
case '"': result.append_char('"'); case '"': result.append_char('"');
case '\\': result.append_char('\\'); case '\\': result.append_char('\\');
case '/': result.append_char('/'); case '/': result.append_char('/');
case 'b': result.append_char('\b'); case 'b': result.append_char('\b');
case 'f': result.append_char('\f'); case 'f': result.append_char('\f');
case 'n': result.append_char('\n'); case 'n': result.append_char('\n');
case 'r': result.append_char('\r'); case 'r': result.append_char('\r');
case 't': result.append_char('\t'); case 't': result.append_char('\t');
case 'v': result.append_char('\v'); case 'v': result.append_char('\v');
case '0': result.append_char('\0'); case '0': result.append_char('\0');
case 'x': case 'x':
// Hex escape \xHH // Hex escape \xHH
if (i + 2 >= len) return INVALID_HEX_ESCAPE~; if (i + 2 >= len) return INVALID_HEX_ESCAPE?;
char h1 = s[++i]; char h1 = s[++i];
char h2 = s[++i]; char h2 = s[++i];
if (!h1.is_xdigit() || !h2.is_xdigit()) return INVALID_HEX_ESCAPE~; if (!h1.is_xdigit() || !h2.is_xdigit()) return INVALID_HEX_ESCAPE?;
uint val = h1 > '9' ? (h1 | 32) - 'a' + 10 : h1 - '0'; uint val = h1 > '9' ? (h1 | 32) - 'a' + 10 : h1 - '0';
val = val << 4; val = val << 4;
val += h2 > '9' ? (h2 | 32) - 'a' + 10 : h2 - '0'; val += h2 > '9' ? (h2 | 32) - 'a' + 10 : h2 - '0';
result.append_char((char)val); result.append_char((char)val);
case 'u': case 'u':
// Unicode escape \uHHHH // Unicode escape \uHHHH
if (i + 4 >= len) return INVALID_UNICODE_ESCAPE~; if (i + 4 >= len) return INVALID_UNICODE_ESCAPE?;
uint val; uint val;
for (int j = 0; j < 4; j++) for (int j = 0; j < 4; j++)
{ {
char hex_char = s[++i]; char hex_char = s[++i];
if (!hex_char.is_xdigit()) return INVALID_UNICODE_ESCAPE~; if (!hex_char.is_xdigit()) return INVALID_UNICODE_ESCAPE?;
val = val << 4 + (hex_char > '9' ? (hex_char | 32) - 'a' + 10 : hex_char - '0'); val = val << 4 + (hex_char > '9' ? (hex_char | 32) - 'a' + 10 : hex_char - '0');
} }
result.append_char32(val); result.append_char32(val);
case 'U': case 'U':
// Unicode escape \UHHHHHHHH // Unicode escape \UHHHHHHHH
if (i + 8 >= len) return INVALID_UNICODE_ESCAPE~; if (i + 8 >= len) return INVALID_UNICODE_ESCAPE?;
uint val; uint val;
for (int j = 0; j < 8; j++) for (int j = 0; j < 8; j++)
{ {
char hex_char = s[++i]; char hex_char = s[++i];
if (!hex_char.is_xdigit()) return INVALID_UNICODE_ESCAPE~; if (!hex_char.is_xdigit()) return INVALID_UNICODE_ESCAPE?;
val = val << 4 + (hex_char > '9' ? (hex_char | 32) - 'a' + 10 : hex_char - '0'); val = val << 4 + (hex_char > '9' ? (hex_char | 32) - 'a' + 10 : hex_char - '0');
} }
result.append_char32(val); result.append_char32(val);
default: default:
if (!lenient) return INVALID_ESCAPE_SEQUENCE~; return INVALID_ESCAPE_SEQUENCE?;
result.append_char(escape_char); }
} }
}
return result.copy_str(allocator);
} }
<* <*
@@ -229,11 +202,10 @@ fn void? unescape_dstring(String s, DString result, bool allow_unquoted = false,
@param s : "The quoted string to unescape" @param s : "The quoted string to unescape"
@param allow_unquoted : "Set to true to unescape strings not surrounded by quotes, defaults to false" @param allow_unquoted : "Set to true to unescape strings not surrounded by quotes, defaults to false"
@param lenient : "Be lenient with escapes, resolving unknown sequences to the escape character, defaults to false"
@return "The unescaped string without quotes" @return "The unescaped string without quotes"
@return? UNTERMINATED_STRING, INVALID_ESCAPE_SEQUENCE, INVALID_HEX_ESCAPE, INVALID_UNICODE_ESCAPE @return? UNTERMINATED_STRING, INVALID_ESCAPE_SEQUENCE, INVALID_HEX_ESCAPE, INVALID_UNICODE_ESCAPE
*> *>
fn String? String.tunescape(String s, bool allow_unquoted = false, bool lenient = false) => s.unescape(tmem, allow_unquoted, lenient); 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. Check if a character needs to be escaped in a string literal.
@@ -243,19 +215,19 @@ fn String? String.tunescape(String s, bool allow_unquoted = false, bool lenient
*> *>
fn bool needs_escape(char c) fn bool needs_escape(char c)
{ {
switch (c) switch (c)
{ {
case '"': case '"':
case '\\': case '\\':
case '\b': case '\b':
case '\f': case '\f':
case '\n': case '\n':
case '\r': case '\r':
case '\t': case '\t':
case '\v': case '\v':
case '\0': case '\0':
return true; return true;
default: default:
return c < 32 || c > 126; return c < 32 || c > 126;
} }
} }

View File

@@ -15,7 +15,7 @@ fn Char32? StringIterator.next(&self)
{ {
usz len = self.utf8.len; usz len = self.utf8.len;
usz current = self.current; usz current = self.current;
if (current >= len) return NO_MORE_ELEMENT~; if (current >= len) return NO_MORE_ELEMENT?;
usz read = (len - current < 4 ? len - current : 4); usz read = (len - current < 4 ? len - current : 4);
Char32 res = conv::utf8_to_char32(&self.utf8[current], &read)!; Char32 res = conv::utf8_to_char32(&self.utf8[current], &read)!;
self.current += read; self.current += read;
@@ -26,7 +26,7 @@ fn Char32? StringIterator.peek(&self)
{ {
usz len = self.utf8.len; usz len = self.utf8.len;
usz current = self.current; usz current = self.current;
if (current >= len) return NO_MORE_ELEMENT~; if (current >= len) return NO_MORE_ELEMENT?;
usz read = (len - current < 4 ? len - current : 4); usz read = (len - current < 4 ? len - current : 4);
Char32 res = conv::utf8_to_char32(&self.utf8[current], &read)!; Char32 res = conv::utf8_to_char32(&self.utf8[current], &read)!;
return res; return res;
@@ -43,7 +43,7 @@ fn Char32? StringIterator.get(&self)
usz current = self.current; usz current = self.current;
usz read = (len - current < 4 ? len - current : 4); usz read = (len - current < 4 ? len - current : 4);
usz index = current > read ? current - read : 0; usz index = current > read ? current - read : 0;
if (index >= len) return NO_MORE_ELEMENT~; if (index >= len) return NO_MORE_ELEMENT?;
Char32 res = conv::utf8_to_char32(&self.utf8[index], &read)!; Char32 res = conv::utf8_to_char32(&self.utf8[index], &read)!;
return res; return res;
} }

View File

@@ -64,7 +64,7 @@ macro double? decfloat(char[] chars, int $bits, int $emin, int sign)
got_rad = true; got_rad = true;
if (index == last_char) if (index == last_char)
{ {
if (!got_digit) return MALFORMED_FLOAT~; if (!got_digit) return MALFORMED_FLOAT?;
return sign * 0.0; return sign * 0.0;
} }
if (index != last_char && (c = chars[++index]) == '0') if (index != last_char && (c = chars[++index]) == '0')
@@ -83,7 +83,7 @@ macro double? decfloat(char[] chars, int $bits, int $emin, int sign)
switch switch
{ {
case c == '.': case c == '.':
if (got_rad) return MALFORMED_FLOAT~; if (got_rad) return MALFORMED_FLOAT?;
got_rad = true; got_rad = true;
lrp = dc; lrp = dc;
case k < KMAX - 3: case k < KMAX - 3:
@@ -113,24 +113,24 @@ macro double? decfloat(char[] chars, int $bits, int $emin, int sign)
c = chars[++index]; c = chars[++index];
} }
if (!got_rad) lrp = dc; if (!got_rad) lrp = dc;
if (!got_digit) return MALFORMED_FLOAT~; if (!got_digit) return MALFORMED_FLOAT?;
if ((c | 32) == 'e') if ((c | 32) == 'e')
{ {
if (last_char == index) return MALFORMED_FLOAT~; if (last_char == index) return MALFORMED_FLOAT?;
long e10 = String.to_long((String)chars[index + 1..]) ?? MALFORMED_FLOAT~!; long e10 = String.to_long((String)chars[index + 1..]) ?? MALFORMED_FLOAT?!;
lrp += e10; lrp += e10;
} }
else if (index != last_char) else if (index != last_char)
{ {
return MALFORMED_FLOAT~; return MALFORMED_FLOAT?;
} }
// Handle zero specially to avoid nasty special cases later // Handle zero specially to avoid nasty special cases later
if (!x[0]) return sign * 0.0; if (!x[0]) return sign * 0.0;
// Optimize small integers (w/no exponent) and over/under-flow // Optimize small integers (w/no exponent) and over/under-flow
if (lrp == dc && dc < 10 && ($bits > 30 || (ulong)x[0] >> $bits == 0)) return sign * (double)x[0]; if (lrp == dc && dc < 10 && ($bits > 30 || (ulong)x[0] >> $bits == 0)) return sign * (double)x[0];
if (lrp > - $emin / 2) return FLOAT_OUT_OF_RANGE~; if (lrp > - $emin / 2) return FLOAT_OUT_OF_RANGE?;
if (lrp < $emin - 2 * math::DOUBLE_MANT_DIG) return FLOAT_OUT_OF_RANGE~; if (lrp < $emin - 2 * math::DOUBLE_MANT_DIG) return FLOAT_OUT_OF_RANGE?;
// Align incomplete final B1B digit // Align incomplete final B1B digit
if (j) if (j)
@@ -158,7 +158,7 @@ macro double? decfloat(char[] chars, int $bits, int $emin, int sign)
if (rp % 9) if (rp % 9)
{ {
long rpm9 = rp >= 0 ? rp % 9 : rp % 9 + 9; long rpm9 = rp >= 0 ? rp % 9 : rp % 9 + 9;
uint p10 = P10S[8 - rpm9]; int p10 = P10S[8 - rpm9];
uint carry = 0; uint carry = 0;
for (k = a; k != z; k++) for (k = a; k != z; k++)
{ {
@@ -320,7 +320,7 @@ macro double? decfloat(char[] chars, int $bits, int $emin, int sign)
y *= 0.5; y *= 0.5;
e2++; e2++;
} }
if (e2 + math::DOUBLE_MANT_DIG > emax || (denormal && frac)) return MALFORMED_FLOAT~; if (e2 + math::DOUBLE_MANT_DIG > emax || (denormal && frac)) return MALFORMED_FLOAT?;
} }
return math::scalbn(y, e2); return math::scalbn(y, e2);
} }
@@ -351,7 +351,7 @@ macro double? hexfloat(char[] chars, int $bits, int $emin, int sign)
got_rad = true; got_rad = true;
if (index == last_char) if (index == last_char)
{ {
if (!got_digit) return MALFORMED_FLOAT~; if (!got_digit) return MALFORMED_FLOAT?;
return sign * 0.0; return sign * 0.0;
} }
if (index != last_char && (c = chars[++index]) == '0') if (index != last_char && (c = chars[++index]) == '0')
@@ -369,7 +369,7 @@ macro double? hexfloat(char[] chars, int $bits, int $emin, int sign)
{ {
if (c == '.') if (c == '.')
{ {
if (got_rad) return MALFORMED_FLOAT~; if (got_rad) return MALFORMED_FLOAT?;
got_rad = true; got_rad = true;
rp = dc; rp = dc;
} }
@@ -393,20 +393,20 @@ macro double? hexfloat(char[] chars, int $bits, int $emin, int sign)
if (index == last_char) break; if (index == last_char) break;
c = chars[++index]; c = chars[++index];
} }
if (!got_digit) return MALFORMED_FLOAT~; if (!got_digit) return MALFORMED_FLOAT?;
if (!got_rad) rp = dc; if (!got_rad) rp = dc;
for (; dc < 8; dc++) x *= 16; for (; dc < 8; dc++) x *= 16;
long e2; long e2;
if ((c | 32) == 'p') if ((c | 32) == 'p')
{ {
long e2val = String.to_long((String)chars[index + 1..]) ?? MALFORMED_FLOAT~!; long e2val = String.to_long((String)chars[index + 1..]) ?? (MALFORMED_FLOAT?)!;
e2 = e2val; e2 = e2val;
} }
e2 += 4 * rp - 32; e2 += 4 * rp - 32;
if (!x) return sign * 0.0; if (!x) return sign * 0.0;
if (e2 > -$emin) return FLOAT_OUT_OF_RANGE~; if (e2 > -$emin) return FLOAT_OUT_OF_RANGE?;
if (e2 < $emin - 2 * math::DOUBLE_MANT_DIG) return FLOAT_OUT_OF_RANGE~; if (e2 < $emin - 2 * math::DOUBLE_MANT_DIG) return FLOAT_OUT_OF_RANGE?;
while (x < 0x80000000) while (x < 0x80000000)
{ {
@@ -441,7 +441,7 @@ macro double? hexfloat(char[] chars, int $bits, int $emin, int sign)
} }
y = bias + sign * (double)x + sign * y; y = bias + sign * (double)x + sign * y;
y -= bias; y -= bias;
if (!y) return FLOAT_OUT_OF_RANGE~; if (!y) return FLOAT_OUT_OF_RANGE?;
return math::scalbn(y, (int)e2); return math::scalbn(y, (int)e2);
} }
@@ -463,7 +463,7 @@ macro String.to_real(chars, $Type) @private
$endswitch $endswitch
chars = chars.trim(); chars = chars.trim();
if (!chars.len) return MALFORMED_FLOAT~; if (!chars.len) return MALFORMED_FLOAT?;
if (chars.len != 1) if (chars.len != 1)
{ {
@@ -477,7 +477,7 @@ macro String.to_real(chars, $Type) @private
} }
} }
chars = chars.trim(); chars = chars.trim();
if (!chars.len) return MALFORMED_FLOAT~; if (!chars.len) return MALFORMED_FLOAT?;
if (chars == "infinity" || chars == "INFINITY") return sign * $Type.inf; if (chars == "infinity" || chars == "INFINITY") return sign * $Type.inf;
if (chars == "NAN" || chars == "nan") return $Type.nan; if (chars == "NAN" || chars == "nan") return $Type.nan;

View File

@@ -12,7 +12,7 @@ faultdef DIVISION_BY_ZERO;
fn double? divide(int a, int b) fn double? divide(int a, int b)
{ {
if (b == 0) return MathError.DIVISION_BY_ZERO~; if (b == 0) return MathError.DIVISION_BY_ZERO?;
return (double)(a) / (double)(b); return (double)(a) / (double)(b);
} }
@@ -26,7 +26,6 @@ fn void? test_div() @test
test::le(2, 3); test::le(2, 3);
test::eq_approx(m::divide(1, 3)!, 0.333, places: 3); test::eq_approx(m::divide(1, 3)!, 0.333, places: 3);
test::@check(2 == 2, "divide: %d", divide(6, 3)!); test::@check(2 == 2, "divide: %d", divide(6, 3)!);
test::@error(m::divide(3, 0));
test::@error(m::divide(3, 0), MathError.DIVISION_BY_ZERO); test::@error(m::divide(3, 0), MathError.DIVISION_BY_ZERO);
} }
@@ -79,15 +78,14 @@ macro @check(#condition, String format = "", args...)
} }
<* <*
Check if function returns (specific) error Check if function returns specific error
@param #funcresult : `result of function execution` @param #funcresult : `result of function execution`
@param error_expected : `expected error of function execution` @param error_expected : `expected error of function execution`
@require runtime::test_context != null : "Only allowed in @test functions" @require runtime::test_context != null : "Only allowed in @test functions"
*> *>
macro @error(#funcresult, fault error_expected = ...) macro @error(#funcresult, fault error_expected)
{ {
$if $defined(error_expected):
if (catch err = #funcresult) if (catch err = #funcresult)
{ {
if (err != error_expected) if (err != error_expected)
@@ -98,10 +96,6 @@ macro @error(#funcresult, fault error_expected = ...)
return; return;
} }
print_panicf("`%s` error [%s] was not returned.", $stringify(#funcresult), error_expected); print_panicf("`%s` error [%s] was not returned.", $stringify(#funcresult), error_expected);
$else
if (catch err = #funcresult) return;
print_panicf("`%s` unexpectedly did not return error.", $stringify(#funcresult));
$endif
} }
<* <*

View File

@@ -29,47 +29,47 @@ macro any_to_int(any v, $Type)
{ {
case ichar: case ichar:
ichar c = *(char*)v.ptr; ichar c = *(char*)v.ptr;
if (is_mixed_signed && c < 0) return VALUE_OUT_OF_UNSIGNED_RANGE~; if (is_mixed_signed && c < 0) return VALUE_OUT_OF_UNSIGNED_RANGE?;
return ($Type)c; return ($Type)c;
case short: case short:
short s = *(short*)v.ptr; short s = *(short*)v.ptr;
if (is_mixed_signed && s < 0) return VALUE_OUT_OF_UNSIGNED_RANGE~; if (is_mixed_signed && s < 0) return VALUE_OUT_OF_UNSIGNED_RANGE?;
if (s > max || s < min) return VALUE_OUT_OF_RANGE~; if (s > max || s < min) return VALUE_OUT_OF_RANGE?;
return ($Type)s; return ($Type)s;
case int: case int:
int i = *(int*)v.ptr; int i = *(int*)v.ptr;
if (is_mixed_signed && i < 0) return VALUE_OUT_OF_UNSIGNED_RANGE~; if (is_mixed_signed && i < 0) return VALUE_OUT_OF_UNSIGNED_RANGE?;
if (i > max || i < min) return VALUE_OUT_OF_RANGE~; if (i > max || i < min) return VALUE_OUT_OF_RANGE?;
return ($Type)i; return ($Type)i;
case long: case long:
long l = *(long*)v.ptr; long l = *(long*)v.ptr;
if (is_mixed_signed && l < 0) return VALUE_OUT_OF_UNSIGNED_RANGE~; if (is_mixed_signed && l < 0) return VALUE_OUT_OF_UNSIGNED_RANGE?;
if (l > max || l < min) return VALUE_OUT_OF_RANGE~; if (l > max || l < min) return VALUE_OUT_OF_RANGE?;
return ($Type)l; return ($Type)l;
case int128: case int128:
int128 i = *(int128*)v.ptr; int128 i = *(int128*)v.ptr;
if (is_mixed_signed && i < 0) return VALUE_OUT_OF_UNSIGNED_RANGE~; if (is_mixed_signed && i < 0) return VALUE_OUT_OF_UNSIGNED_RANGE?;
if (i > max || i < min) return VALUE_OUT_OF_RANGE~; if (i > max || i < min) return VALUE_OUT_OF_RANGE?;
return ($Type)i; return ($Type)i;
case char: case char:
char c = *(char*)v.ptr; char c = *(char*)v.ptr;
if (c > max) return VALUE_OUT_OF_RANGE~; if (c > max) return VALUE_OUT_OF_RANGE?;
return ($Type)c; return ($Type)c;
case ushort: case ushort:
ushort s = *(ushort*)v.ptr; ushort s = *(ushort*)v.ptr;
if (s > max || s < min) return VALUE_OUT_OF_RANGE~; if (s > max || s < min) return VALUE_OUT_OF_RANGE?;
return ($Type)s; return ($Type)s;
case uint: case uint:
uint i = *(uint*)v.ptr; uint i = *(uint*)v.ptr;
if (i > max || i < min) return VALUE_OUT_OF_RANGE~; if (i > max || i < min) return VALUE_OUT_OF_RANGE?;
return ($Type)i; return ($Type)i;
case ulong: case ulong:
ulong l = *(ulong*)v.ptr; ulong l = *(ulong*)v.ptr;
if (l > max || l < min) return VALUE_OUT_OF_RANGE~; if (l > max || l < min) return VALUE_OUT_OF_RANGE?;
return ($Type)l; return ($Type)l;
case uint128: case uint128:
uint128 i = *(uint128*)v.ptr; uint128 i = *(uint128*)v.ptr;
if (i > max || i < min) return VALUE_OUT_OF_RANGE~; if (i > max || i < min) return VALUE_OUT_OF_RANGE?;
return ($Type)i; return ($Type)i;
default: default:
unreachable(); unreachable();
@@ -97,8 +97,8 @@ macro bool is_subtype_of($Type, $OtherType)
macro bool is_numerical($Type) macro bool is_numerical($Type)
{ {
$switch $Type.kindof: $switch $Type.kindof:
$case TYPEDEF: $case DISTINCT:
$case CONSTDEF: $case CONST_ENUM:
return is_numerical($Type.inner); return is_numerical($Type.inner);
$case SIGNED_INT: $case SIGNED_INT:
$case UNSIGNED_INT: $case UNSIGNED_INT:
@@ -112,17 +112,10 @@ macro bool is_numerical($Type)
fn bool TypeKind.is_int(kind) @inline fn bool TypeKind.is_int(kind) @inline
{ {
return kind == SIGNED_INT || kind == UNSIGNED_INT; return kind == TypeKind.SIGNED_INT || kind == TypeKind.UNSIGNED_INT;
} }
macro bool TypeKind.@is_int($kind) @const macro bool is_slice_convertable($Type)
{
return $kind == SIGNED_INT ||| $kind == UNSIGNED_INT;
}
macro bool is_slice_convertable($Type) @deprecated("Use is_slice_convertible") => is_slice_convertible($Type);
macro bool is_slice_convertible($Type)
{ {
$switch $Type.kindof: $switch $Type.kindof:
$case SLICE: $case SLICE:
@@ -170,7 +163,7 @@ macro bool is_unsigned($Type) @const
macro typeid flat_type($Type) @const macro typeid flat_type($Type) @const
{ {
$if $Type.kindof == TYPEDEF || $Type.kindof == CONSTDEF: $if $Type.kindof == DISTINCT || $Type.kindof == CONST_ENUM:
return flat_type($Type.inner); return flat_type($Type.inner);
$else $else
return $Type.typeid; return $Type.typeid;
@@ -179,7 +172,7 @@ macro typeid flat_type($Type) @const
macro TypeKind flat_kind($Type) @const macro TypeKind flat_kind($Type) @const
{ {
$if $Type.kindof == TYPEDEF || $Type.kindof == CONSTDEF: $if $Type.kindof == DISTINCT || $Type.kindof == CONST_ENUM:
return flat_type($Type.inner); return flat_type($Type.inner);
$else $else
return $Type.kindof; return $Type.kindof;
@@ -203,8 +196,8 @@ macro bool is_flat_intlike($Type) @const
$case UNSIGNED_INT: $case UNSIGNED_INT:
return true; return true;
$case VECTOR: $case VECTOR:
$case TYPEDEF: $case DISTINCT:
$case CONSTDEF: $case CONST_ENUM:
return is_flat_intlike($Type.inner); return is_flat_intlike($Type.inner);
$default: $default:
return false; return false;
@@ -230,7 +223,7 @@ macro bool is_underlying_int($Type) @const
$case SIGNED_INT: $case SIGNED_INT:
$case UNSIGNED_INT: $case UNSIGNED_INT:
return true; return true;
$case TYPEDEF: $case DISTINCT:
return is_underlying_int($Type.inner); return is_underlying_int($Type.inner);
$default: $default:
return false; return false;
@@ -258,7 +251,7 @@ macro bool is_vector($Type) @const
macro typeid inner_type($Type) @const macro typeid inner_type($Type) @const
{ {
$if $Type.kindof == TYPEDEF || $Type.kindof == CONSTDEF: $if $Type.kindof == DISTINCT || $Type.kindof == CONST_ENUM:
return inner_type($Type.inner); return inner_type($Type.inner);
$else $else
return $Type.typeid; return $Type.typeid;
@@ -298,7 +291,7 @@ macro bool may_load_atomic($Type) @const
$case POINTER: $case POINTER:
$case FLOAT: $case FLOAT:
return true; return true;
$case TYPEDEF: $case DISTINCT:
$case ENUM: $case ENUM:
return may_load_atomic($Type.inner); return may_load_atomic($Type.inner);
$default: $default:
@@ -313,8 +306,8 @@ macro lower_to_atomic_compatible_type($Type) @const
$case SIGNED_INT: $case SIGNED_INT:
$case UNSIGNED_INT: $case UNSIGNED_INT:
return $Type.typeid; return $Type.typeid;
$case TYPEDEF: $case DISTINCT:
$case CONSTDEF: $case CONST_ENUM:
return lower_to_atomic_compatible_type($Type.inner); return lower_to_atomic_compatible_type($Type.inner);
$case FLOAT: $case FLOAT:
$switch $Type: $switch $Type:
@@ -334,8 +327,8 @@ macro lower_to_atomic_compatible_type($Type) @const
$endswitch $endswitch
} }
macro bool is_promotable_to_floatlike($Type) @const => types::is_floatlike($Type) ||| types::is_int($Type); macro bool is_promotable_to_floatlike($Type) @const => types::is_floatlike($Type) || types::is_int($Type);
macro bool is_promotable_to_float($Type) @const => types::is_float($Type) ||| types::is_int($Type); macro bool is_promotable_to_float($Type) @const => types::is_float($Type) || types::is_int($Type);
macro bool is_same_vector_type($Type1, $Type2) @const macro bool is_same_vector_type($Type1, $Type2) @const
{ {
@@ -350,7 +343,7 @@ macro bool has_equals($Type) @const => $defined(($Type){} == ($Type){});
macro bool is_equatable_type($Type) @const macro bool is_equatable_type($Type) @const
{ {
$if $defined($Type.less) ||| $defined($Type.compare_to) ||| $defined($Type.equals): $if $defined($Type.less) || $defined($Type.compare_to) || $defined($Type.equals):
return true; return true;
$else $else
return $Type.is_eq; return $Type.is_eq;
@@ -362,7 +355,7 @@ macro bool is_equatable_type($Type) @const
*> *>
macro bool implements_copy($Type) @const macro bool implements_copy($Type) @const
{ {
return $defined($Type.copy) &&& $defined($Type.free); return $defined($Type.copy) && $defined($Type.free);
} }
macro bool @equatable_value(#value) @const macro bool @equatable_value(#value) @const
@@ -372,16 +365,13 @@ macro bool @equatable_value(#value) @const
macro bool @comparable_value(#value) @const macro bool @comparable_value(#value) @const
{ {
$if $defined(#value.less) ||| $defined(#value.compare_to): $if $defined(#value.less) || $defined(#value.compare_to):
return true; return true;
$else $else
return $typeof(#value).is_ordered; return $typeof(#value).is_ordered;
$endif $endif
} }
const CONST_ENUM @builtin @deprecated("Use TypeKind.CONSTDEF instead") = TypeKind.CONSTDEF;
const DISTINCT @builtin @deprecated("Use TypeKind.TYPEDEF instead") = TypeKind.TYPEDEF;
enum TypeKind : char enum TypeKind : char
{ {
VOID, VOID,
@@ -393,7 +383,7 @@ enum TypeKind : char
FAULT, FAULT,
ANY, ANY,
ENUM, ENUM,
CONSTDEF, CONST_ENUM,
STRUCT, STRUCT,
UNION, UNION,
BITSTRUCT, BITSTRUCT,
@@ -402,7 +392,7 @@ enum TypeKind : char
ARRAY, ARRAY,
SLICE, SLICE,
VECTOR, VECTOR,
TYPEDEF, DISTINCT,
POINTER, POINTER,
INTERFACE, INTERFACE,
} }

View File

@@ -2,11 +2,10 @@ module std::core::values;
import std::core::types; import std::core::types;
macro bool @typematch(#value1, #value2) @builtin @const => $typeof(#value1) == $typeof(#value2);
<* <*
Return true if two values have the same type before any conversions. 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_bool(#value) @const => types::is_bool($typeof(#value));
macro bool @is_int(#value) @const => types::is_int($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)); macro bool @is_flat_intlike(#value) @const => types::is_flat_intlike($typeof(#value));
@@ -16,11 +15,12 @@ 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_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_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 @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 @assign_to(#value1, #value2) @const => @assignable_to(#value1, $typeof(#value2));
macro bool @is_lvalue(#value) @deprecated("use '$defined(#value = #value)'")=> $defined(#value = #value); macro bool @is_lvalue(#value) => $defined(#value = #value);
macro bool @is_const(#foo) @const @builtin @deprecated("use '$defined(var $v = expr)'") macro bool @is_const(#foo) @const @builtin
{ {
return $defined(var $v = #foo); var $v;
return $defined($v = #foo);
} }
macro promote_int(x) macro promote_int(x)
@@ -43,7 +43,7 @@ macro promote_int(x)
@param #value_2 @param #value_2
@returns `The selected value.` @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: $if $bool:
return #value_1; return #value_1;
@@ -51,7 +51,6 @@ macro @select(bool $bool, #value_1, #value_2) @builtin @deprecated("Use '$bool ?
return #value_2; return #value_2;
$endif $endif
} }
macro promote_int_same(x, y) macro promote_int_same(x, y)
{ {
$if @is_int(x): $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

@@ -1,233 +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.
//
// ChaCha20 code dedicated from repo: https://github.com/NotsoanoNimus/chacha20_aead.c3l (but massively cleaned)
module std::crypto::chacha20;
<* The typical cipher block size in bytes. *>
const BLOCK_SIZE = 64;
<* Required key size in bytes. *>
const KEY_SIZE = 32;
<* ChaCha20 "nonce" (initialization vector) size. *>
const NONCE_SIZE = 12;
<* A required ChaCha20 "magic" value used for state initialization. *>
const char[] MAGIC = "expand 32-byte k";
<*
Once a single ChaCha20 context has processed this many bytes, a new nonce MUST be used,
unless the static `permit_overflow` runtime module variable is set to true.
*>
const CHACHA20_NONCE_REUSE_LIMIT = 64 * (1ull << 32);
<*
SECURITY WARNING:
This boolean should always remain 'false'. If set to 'true', you accept the security
implications of nonce re-use caused by an overflow in the cipher's 'counter' field.
This security warning is only applicable when a single ChaCha20 context is being used
to process more than about 256 GiB of data.
*>
bool permit_overflow = false;
<* A context structure used to track an ongoing ChaCha20 transformation. *>
struct ChaCha20
{
<* The position within a block before permuting the rounds. *>
usz position;
<* Count of bytes processed. Useful to track an approach to the 256GiB limit of a single context. *>
ulong bytes_processed;
<* The key stream or state used during cipher block operations. *>
uint[16] key_stream @align(ulong.sizeof);
<* The secret key for the context. *>
char[32] key;
<* The one-time nonce (or IV - initialization vector) used for the context. *>
char[12] nonce;
<* Internal state of the cipher. *>
uint[16] state;
}
<* The meat and potatoes of the ChaCha20 stream cipher. *>
macro quarter_round(uint* x, int a, int b, int c, int d) @local
{
x[a] += x[b]; x[d] = (x[d] ^ x[a]).rotl(16);
x[c] += x[d]; x[b] = (x[b] ^ x[c]).rotl(12);
x[a] += x[b]; x[d] = (x[d] ^ x[a]).rotl(8);
x[c] += x[d]; x[b] = (x[b] ^ x[c]).rotl(7);
}
<* Process the next (or final) chunk of ingested data. *>
fn void chacha20_mutate_keystream(ChaCha20* self) @local @inline
{
self.key_stream[..] = self.state[..];
for (usz i = 0; i < 10; i++) // unrolling this does not improve performance measurably
{
quarter_round(&self.key_stream[0], 0, 4, 8, 12);
quarter_round(&self.key_stream[0], 1, 5, 9, 13);
quarter_round(&self.key_stream[0], 2, 6, 10, 14);
quarter_round(&self.key_stream[0], 3, 7, 11, 15);
quarter_round(&self.key_stream[0], 0, 5, 10, 15);
quarter_round(&self.key_stream[0], 1, 6, 11, 12);
quarter_round(&self.key_stream[0], 2, 7, 8, 13);
quarter_round(&self.key_stream[0], 3, 4, 9, 14);
}
// NOTE: This would 'feel' like a performance hit, but testing the benchmark doesn't show any noticeable
// difference on -O5 between this and a for-loop, or even an unrolled loop with compile-time '$for'.
array::@zip_into(self.key_stream[..], self.state[..], fn (a, b) => a + b);
self.state[12]++; // increment the block counter (rollovers are ok)
}
<*
Initialize a ChaCha20 transformation context.
@param key : `The secret key used for the transformation operation.`
@param nonce : `The one-time nonce to use for the transformation operation.`
@param counter : `An optional counter value to adjust the stream's position.`
@require key.len == KEY_SIZE : `Input key slice is not the correct length (32 bytes).`
@require nonce.len == NONCE_SIZE : `Input nonce slice is not the correct length (12 bytes).`
*>
fn void ChaCha20.init(&self, char[KEY_SIZE] key, char[NONCE_SIZE] nonce, uint counter = 0)
{
// Init block.
self.position = BLOCK_SIZE; // start at the "end" of a block on init
self.bytes_processed = 0;
self.key[..] = key[..];
self.nonce[..] = nonce[..];
((char*)&self.state[0])[:MAGIC.len] = MAGIC[..];
((char*)&self.state[4])[:KEY_SIZE] = key[..];
self.state[12] = counter;
((char*)&self.state[13])[:NONCE_SIZE] = nonce[..];
}
<*
Transform some input data using the current context structure.
@param[inout] data : `The data to transform (encrypt or decrypt).`
*>
fn void ChaCha20.transform(&self, char[] data)
{
if (!data.len) return;
usz original_length = data.len;
char[] key_stream = @as_char_view(self.key_stream);
// 1. Process remaining bytes in the current keystream block.
if (self.position < BLOCK_SIZE)
{
usz len = data.len < (BLOCK_SIZE - self.position) ? data.len : (BLOCK_SIZE - self.position);
for (usz i = 0; i < len; i++) data[i] ^= key_stream[self.position + i];
self.position += len;
data = data[len..];
}
// 2. Get the amount of bytes offset from the nearest alignment boundary.
// Process full blocks at a time, word by word according to the system's architecture.
// Any extra bytes on each side are dynamically processed byte-by-byte.
usz offset = usz.sizeof - (((usz)data.ptr % usz.sizeof) ?: usz.sizeof);
for (usz x = offset; data.len >= BLOCK_SIZE; data = data[BLOCK_SIZE..], x = offset)
{
chacha20_mutate_keystream(self);
if (offset) foreach (i, &b : data[:offset]) *b ^= key_stream[i];
char[] aligned_data = data[offset..];
for (; x <= (BLOCK_SIZE - usz.sizeof); x += usz.sizeof)
{
((usz*)aligned_data.ptr)[x / usz.sizeof] ^= mem::load((usz*)(&key_stream[x]), $align: 1);
}
for (; x < BLOCK_SIZE; x++) data[x] ^= key_stream[x];
}
// 3. Process any remaining bytes.
if (data.len > 0)
{
chacha20_mutate_keystream(self);
for (usz i = 0; i < data.len; i++) data[i] ^= key_stream[i];
self.position = data.len;
}
// All done. Capture the transformed length of data and check limits.
self.bytes_processed += original_length;
if (@unlikely(self.bytes_processed >= CHACHA20_NONCE_REUSE_LIMIT && !permit_overflow))
{
abort(
"ChaCha20 transform limit (~256 GiB) exceeded. You can set 'chacha20::permit_overflow = true;' at"
" runtime to disable this panic, but you accept the terrible SECURITY IMPLICATIONS of doing so."
);
}
}
<* Destroy the current context structure by zeroing all fields. *>
fn void ChaCha20.destroy(&self) => mem::zero_volatile(@as_char_view(*self));
<*
Perform an in-place transformation of some data in a buffer, without cloning the data to a new buffer.
@param[inout] data : `The data to transform (encrypt or decrypt).`
@param key : `The secret key used for the transformation operation.`
@param nonce : `The one-time nonce to use for the transformation operation.`
@param counter : `An optional counter value to adjust the stream's position.`
@require key.len == KEY_SIZE : `Input key slice is not the correct length (32 bytes).`
@require nonce.len == NONCE_SIZE : `Input nonce slice is not the correct length (12 bytes).`
*>
fn void crypt(char[] data, char[KEY_SIZE] key, char[NONCE_SIZE] nonce, uint counter = 0) @private
{
if (@unlikely(!data.len)) return;
ChaCha20 c @noinit;
defer c.destroy();
c.init(key, nonce, counter);
c.transform(data);
}
alias encrypt_mut = crypt;
alias decrypt_mut = crypt;
<*
Perform a transformation of some data cloned from a source buffer.
@param[&inout] allocator : `The memory allocator which controls allocation of the cloned input data.`
@param[inout] data : `The data to transform (encrypt or decrypt).`
@param key : `The secret key used for the transformation operation.`
@param nonce : `The one-time nonce to use for the transformation operation.`
@param counter : `An optional counter value to adjust the stream's position.`
@require key.len == KEY_SIZE : `Input key slice is not the correct length (32 bytes).`
@require nonce.len == NONCE_SIZE : `Input nonce slice is not the correct length (12 bytes).`
*>
fn char[] crypt_clone(Allocator allocator, char[] data, char[KEY_SIZE] key, char[NONCE_SIZE] nonce, uint counter = 0) @private
{
if (@unlikely(!data.len)) return {};
char[] buff = allocator::clone_slice(allocator, data);
crypt(buff, key, nonce, counter);
return buff;
}
alias encrypt = crypt_clone;
alias decrypt = crypt_clone;
<*
Perform a transformation of some data cloned from a source buffer by the temp allocator.
@param[inout] data : `The data to transform (encrypt or decrypt).`
@param key : `The secret key used for the transformation operation.`
@param nonce : `The one-time nonce to use for the transformation operation.`
@param counter : `An optional counter value to adjust the stream's position.`
@require key.len == KEY_SIZE : `Input key slice is not the correct length (32 bytes).`
@require nonce.len == NONCE_SIZE : `Input nonce slice is not the correct length (12 bytes).`
*>
fn char[] tcrypt_clone(char[] data, char[KEY_SIZE] key, char[NONCE_SIZE] nonce, uint counter = 0) @private
{
return crypt_clone(tmem, data, key, nonce, counter);
}
alias tencrypt = tcrypt_clone;
alias tdecrypt = tcrypt_clone;

View File

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

View File

@@ -212,8 +212,7 @@ fn F25519Int pack(Point* p)
struct Unpacking struct Unpacking
{ {
Point point; Point point;
<* Non-zero if true. *> char on_curve; // Non-zero if true.
char on_curve;
} }
<* <*
@@ -332,7 +331,7 @@ fn Projection Projection.mul(&s, char[] n) @operator(*)
module std::crypto::ed25519 @private; module std::crypto::ed25519 @private;
typedef F25519Int @constinit = inline char[32]; typedef F25519Int = inline char[32];
const F25519Int ZERO = {}; const F25519Int ZERO = {};
const F25519Int ONE = {[0] = 1}; const F25519Int ONE = {[0] = 1};
@@ -366,7 +365,7 @@ fn void F25519Int.normalize(&s)
{ {
s.reduce_carry((*s)[^1] >> 7); s.reduce_carry((*s)[^1] >> 7);
// Subtract p // Substract p
F25519Int sub @noinit; F25519Int sub @noinit;
ushort c = 19; ushort c = 19;
foreach (i, v : (*s)[:^1]) foreach (i, v : (*s)[:^1])
@@ -400,7 +399,7 @@ fn char eq(F25519Int* a, F25519Int* b)
} }
<* <*
Constant-time conditional selection. Result is undefined if condition is neither 0 nor 1. Constant-time conditonal selection. Result is undefined if condition is neither 0 nor 1.
@param [&in] zero : "selected if condition is 0" @param [&in] zero : "selected if condition is 0"
@param [&in] one : "selected if condition is 1" @param [&in] one : "selected if condition is 1"
@@ -442,7 +441,7 @@ fn F25519Int F25519Int.add(&s, F25519Int* n) @operator(+)
macro F25519Int F25519Int.@sub(&s, F25519Int #n) @operator(-) => s.sub(@addr(#n)); macro F25519Int F25519Int.@sub(&s, F25519Int #n) @operator(-) => s.sub(@addr(#n));
<* <*
Subtraction. Substraction.
@param [&in] s @param [&in] s
@param [&in] n @param [&in] n
@@ -567,7 +566,7 @@ fn F25519Int F25519Int.inv(&s)
@param [&in] s @param [&in] s
*> *>
fn F25519Int pow_2523(F25519Int* s) @local fn F25519Int F25519Int.pow_2523(&s) @local
{ {
F25519Int r = *s; F25519Int r = *s;
@@ -587,7 +586,7 @@ fn F25519Int pow_2523(F25519Int* s) @local
fn F25519Int F25519Int.sqrt(&s) fn F25519Int F25519Int.sqrt(&s)
{ {
F25519Int twice = s.mul_s(2); F25519Int twice = s.mul_s(2);
F25519Int pow = pow_2523(&twice); F25519Int pow = twice.pow_2523();
return (twice * pow * pow - ONE) * s * pow; return (twice * pow * pow - ONE) * s * pow;
} }
@@ -601,7 +600,7 @@ fn F25519Int F25519Int.sqrt(&s)
module std::crypto::ed25519 @private; module std::crypto::ed25519 @private;
import std::math; import std::math;
typedef FBaseInt @constinit = inline char[32]; typedef FBaseInt = inline char[32];
// Order of the field : 2^252+0x14def9dea2f79cd65812631a5cf5d3ed // Order of the field : 2^252+0x14def9dea2f79cd65812631a5cf5d3ed
const FBaseInt ORDER = x"edd3f55c1a631258 d69cf7a2def9de14 0000000000000000 0000000000000010"; const FBaseInt ORDER = x"edd3f55c1a631258 d69cf7a2def9de14 0000000000000000 0000000000000010";
@@ -639,7 +638,7 @@ fn FBaseInt from_bytes(char[] bytes)
} }
<* <*
Constant-time conditional selection. Result is undefined if condition is neither 0 nor 1. Constant-time conditonal selection. Result is undefined if condition is neither 0 nor 1.
@param [&in] zero : "selected if condition is 0" @param [&in] zero : "selected if condition is 0"
@param [&in] one : "selected if condition is 1" @param [&in] one : "selected if condition is 1"
@@ -677,7 +676,7 @@ fn FBaseInt FBaseInt.add(&s, FBaseInt* n) @operator(+)
} }
<* <*
Subtraction if RHS is less than LHS else identity. Substraction if RHS is less than LHS else identity.
@param [&in] s @param [&in] s
@param [&in] n @param [&in] n

View File

@@ -101,12 +101,12 @@ fn char[]? decode_buffer(char[] src, char[] dst, char padding = DEFAULT_PAD, Bas
{ {
if (src.len == 0) if (src.len == 0)
{ {
if (padding > 0) return encoding::INVALID_PADDING~; if (padding > 0) return encoding::INVALID_PADDING?;
break; break;
} }
if (src[0] == padding) break; if (src[0] == padding) break;
buf[i] = alphabet.reverse[src[0]]; buf[i] = alphabet.reverse[src[0]];
if (buf[i] == INVALID) return encoding::INVALID_CHARACTER~; if (buf[i] == INVALID) return encoding::INVALID_CHARACTER?;
src = src[1..]; src = src[1..];
} }
@@ -150,7 +150,7 @@ fn char[]? decode_buffer(char[] src, char[] dst, char padding = DEFAULT_PAD, Bas
dst[0] = buf[1] >> 2 | buf[0] << 3; dst[0] = buf[1] >> 2 | buf[0] << 3;
n++; n++;
default: default:
return encoding::INVALID_CHARACTER~; return encoding::INVALID_CHARACTER?;
} }
if (dst.len < 5) break; if (dst.len < 5) break;
dst = dst[5..]; dst = dst[5..];
@@ -242,7 +242,7 @@ const char INVALID @private = 0xff;
const int STD_PADDING = '='; const int STD_PADDING = '=';
const int NO_PADDING = -1; const int NO_PADDING = -1;
typedef Alphabet @constinit = char[32]; typedef Alphabet = char[32];
// Standard base32 Alphabet // Standard base32 Alphabet
const Alphabet STD_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; const Alphabet STD_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
// Extended Hex Alphabet // Extended Hex Alphabet

View File

@@ -87,11 +87,11 @@ fn usz? decode_len(usz n, char padding)
usz trailing = n % 4; usz trailing = n % 4;
if (padding) if (padding)
{ {
if (trailing != 0) return encoding::INVALID_PADDING~; if (trailing != 0) return encoding::INVALID_PADDING?;
// source size is multiple of 4 // source size is multiple of 4
return dn; return dn;
} }
if (trailing == 1) return encoding::INVALID_PADDING~; if (trailing == 1) return encoding::INVALID_PADDING?;
return dn + trailing * 3 / 4; return dn + trailing * 3 / 4;
} }
@@ -196,7 +196,7 @@ fn char[]? decode_buffer(char[] src, char[] dst, char padding = DEFAULT_PAD, Bas
case c1: case c1:
case c2: case c2:
case c3: case c3:
return encoding::INVALID_CHARACTER~; return encoding::INVALID_CHARACTER?;
} }
uint group = (uint)c0 << 18 | (uint)c1 << 12 | (uint)c2 << 6 | (uint)c3; uint group = (uint)c0 << 18 | (uint)c1 << 12 | (uint)c2 << 6 | (uint)c3;
dst[0] = (char)(group >> 16); dst[0] = (char)(group >> 16);
@@ -211,7 +211,7 @@ fn char[]? decode_buffer(char[] src, char[] dst, char padding = DEFAULT_PAD, Bas
src = src[^trailing..]; src = src[^trailing..];
char c0 = alphabet.reverse[src[0]]; char c0 = alphabet.reverse[src[0]];
char c1 = alphabet.reverse[src[1]]; char c1 = alphabet.reverse[src[1]];
if (c0 == 0xFF || c1 == 0xFF) return encoding::INVALID_PADDING~; if (c0 == 0xFF || c1 == 0xFF) return encoding::INVALID_PADDING?;
if (!padding) if (!padding)
{ {
switch (src.len) switch (src.len)
@@ -221,7 +221,7 @@ fn char[]? decode_buffer(char[] src, char[] dst, char padding = DEFAULT_PAD, Bas
dst[0] = (char)(group >> 16); dst[0] = (char)(group >> 16);
case 3: case 3:
char c2 = alphabet.reverse[src[2]]; char c2 = alphabet.reverse[src[2]];
if (c2 == 0xFF) return encoding::INVALID_CHARACTER~; if (c2 == 0xFF) return encoding::INVALID_CHARACTER?;
uint group = (uint)c0 << 18 | (uint)c1 << 12 | (uint)c2 << 6; uint group = (uint)c0 << 18 | (uint)c1 << 12 | (uint)c2 << 6;
dst[0] = (char)(group >> 16); dst[0] = (char)(group >> 16);
dst[1] = (char)(group >> 8); dst[1] = (char)(group >> 8);
@@ -235,13 +235,13 @@ fn char[]? decode_buffer(char[] src, char[] dst, char padding = DEFAULT_PAD, Bas
switch (padding) switch (padding)
{ {
case src[2]: case src[2]:
if (src[3] != padding) return encoding::INVALID_PADDING~; if (src[3] != padding) return encoding::INVALID_PADDING?;
uint group = (uint)c0 << 18 | (uint)c1 << 12; uint group = (uint)c0 << 18 | (uint)c1 << 12;
dst[0] = (char)(group >> 16); dst[0] = (char)(group >> 16);
dn -= 2; dn -= 2;
case src[3]: case src[3]:
char c2 = alphabet.reverse[src[2]]; char c2 = alphabet.reverse[src[2]];
if (c2 == 0xFF) return encoding::INVALID_CHARACTER~; if (c2 == 0xFF) return encoding::INVALID_CHARACTER?;
uint group = (uint)c0 << 18 | (uint)c1 << 12 | (uint)c2 << 6; uint group = (uint)c0 << 18 | (uint)c1 << 12 | (uint)c2 << 6;
dst[0] = (char)(group >> 16); dst[0] = (char)(group >> 16);
dst[1] = (char)(group >> 8); dst[1] = (char)(group >> 8);

View File

@@ -1,243 +0,0 @@
// Copyright (c) 2026 Koni Marti. All rights reserved.
// Use of this source code is governed by the MIT license.
<*
Module providing generic singlebyte code page to UTF8 conversion.
This module implements a compact, tabledriven approach for single-byte
(8bit) encodings (e.g. CP437, CP850, CP866, CP125x). It is designed so
that each concrete code page only needs to supply a small, static
mapping table; the conversion logic is shared.
The design has two main goals:
- Fast decode from code page to UTF8 with a single table lookup per byte.
- Memoryefficient encode from UTF8 to code page without a large
Unicodetobyte array (no 64k reverse map per code page).
The design of CodePageTable and the packed reverse mapping is conceptually
similar to golang.org/x/text/encoding/charmap.
*>
module std::encoding::codepage;
import std::sort;
<*
Default replacement byte used when encoding from UTF8 to a singlebyte
code page and a Unicode scalar cannot be represented.
By convention, 0x1A is the ASCII/IBM SUB (substitute) control character.
*>
const char REPLACEMENT_CHAR = 0x1a;
<*
CodePageTable contains the bidirectional mapping tables for a singlebyte code
page in a compact packed form.
to_codepoint is the forward map from codepage byte (0x000xFF) to its UTF8
sequence. The array index is the raw byte value, each entry occupying 4 bytes:
- Byte 0 is the length of the UTF8 sequence (04)
- Bytes 1:len are the UTF8 bytes for the mapped Unicode scalar
The table therefore uses 256 * 4 bytes and is stored as a flat
char[1024] array, where entry i starts at offset i*4.
from_codepoint is the reverse map from Unicode scalar value to codepage byte,
also stored as a packed char[1024] array. It contains 256 entries of 4 bytes
each, where each 4byte chunk is interpreted as a littleendian uint
with the following packing scheme:
high 8 bits = codepage byte value (0x000xFF)
low 24 bits = Unicode scalar value (code point)
In other words:
entry = (byte_value << 24) | codepoint;
Ordering:
The 256 packed uint entries in from_codepoint are sorted by the low 24 bits
(code points). This allows binary search over Unicode scalar values without
a 64k reverselookup table. For any given code page, there are at most
256 mappings, so a log2(256) or 8 step search is sufficient.
*>
struct CodePageTable
{
char[1024] to_codepoint;
char[1024] from_codepoint;
}
enum CodePage : (String name, CodePageTable* table)
{
CP1250 { "cp1250", &codepage::CP1250 },
CP1251 { "cp1251", &codepage::CP1251 },
CP1252 { "cp1252", &codepage::CP1252 },
CP1253 { "cp1253", &codepage::CP1253 },
CP1254 { "cp1254", &codepage::CP1254 },
CP1255 { "cp1255", &codepage::CP1255 },
CP1256 { "cp1256", &codepage::CP1256 },
CP1257 { "cp1257", &codepage::CP1257 },
CP1258 { "cp1258", &codepage::CP1258 },
CP437 { "cp437", &codepage::CP437 },
CP737 { "cp737", &codepage::CP737 },
CP775 { "cp775", &codepage::CP775 },
CP850 { "cp850", &codepage::CP850 },
CP852 { "cp852", &codepage::CP852 },
CP855 { "cp855", &codepage::CP855 },
CP857 { "cp857", &codepage::CP857 },
CP860 { "cp860", &codepage::CP860 },
CP861 { "cp861", &codepage::CP861 },
CP862 { "cp862", &codepage::CP862 },
CP863 { "cp863", &codepage::CP863 },
CP864 { "cp864", &codepage::CP864 },
CP865 { "cp865", &codepage::CP865 },
CP866 { "cp866", &codepage::CP866 },
CP869 { "cp869", &codepage::CP869 },
CP874 { "cp874", &codepage::CP874 },
ISO_8859_1 { "iso-8859-1", &codepage::ISO_8859_1 },
ISO_8859_10 { "iso-8859-10", &codepage::ISO_8859_10 },
ISO_8859_11 { "iso-8859-11", &codepage::ISO_8859_11 },
ISO_8859_13 { "iso-8859-13", &codepage::ISO_8859_13 },
ISO_8859_14 { "iso-8859-14", &codepage::ISO_8859_14 },
ISO_8859_15 { "iso-8859-15", &codepage::ISO_8859_15 },
ISO_8859_16 { "iso-8859-16", &codepage::ISO_8859_16 },
ISO_8859_2 { "iso-8859-2", &codepage::ISO_8859_2 },
ISO_8859_3 { "iso-8859-3", &codepage::ISO_8859_3 },
ISO_8859_4 { "iso-8859-4", &codepage::ISO_8859_4 },
ISO_8859_5 { "iso-8859-5", &codepage::ISO_8859_5 },
ISO_8859_6 { "iso-8859-6", &codepage::ISO_8859_6 },
ISO_8859_7 { "iso-8859-7", &codepage::ISO_8859_7 },
ISO_8859_8 { "iso-8859-8", &codepage::ISO_8859_8 },
ISO_8859_9 { "iso-8859-9", &codepage::ISO_8859_9 },
US_ASCII { "us-ascii", &codepage::US_ASCII },
}
<*
Returns a CodePage for the given charset name.
@param [in] charset_name : "A name, case insensitive, using _ or - for separator"
@return "The CodePage for the name"
@return? NOT_FOUND : "If the charset is unknown or unsupported"
*>
fn CodePage? by_name(String charset_name) => @pool()
{
String name = charset_name.treplace("_","-");
name.convert_to_lower();
foreach (page : CodePage.values)
{
if (page.name == charset_name) return page;
}
return NOT_FOUND~;
}
fn String? decode(Allocator allocator, char[] src, CodePage code_page)
{
char[] dst = allocator::alloc_array(allocator, char, decode_len(src, code_page));
return decode_buffer(src, dst, code_page);
}
<*
Decode a code-page byte buffer into a UTF8 string.
@param src : "Input byte array in the given code page."
@param dst : "Destination output string in UTF-8."
@param code_page : "Code page for this encoding."
@return "String in UTF-8."
*>
fn String? decode_buffer(char[] src, char[] dst, CodePage code_page)
{
usz n = 0;
CodePageTable *table = code_page.table;
foreach (c: src)
{
usz pos = (usz)c * 4;
char len = table.to_codepoint[pos];
dst[n:len] = table.to_codepoint[pos+1:len];
n += len;
}
return (String)dst[:n];
}
fn char[]? encode(Allocator allocator, char[] src, CodePage code_page, char replacement = REPLACEMENT_CHAR)
{
char[] dst = allocator::alloc_array(allocator, char, encode_len(src));
return encode_buffer(src, dst, code_page, replacement);
}
const uint MASK @private = (1u << 24) - 1;
<*
Encode a UTF8 string into a singlebyte code page.
@param src : "Input byte array in UTF-8"
@param dst : "Destination output byte array in the target code page"
@param code_page : "Code page for this encoding."
@param replacement : "Byte to emit when Unicode scalar cannot be represented in the target code page."
@return "Byte array in the given code page."
*>
fn char[]? encode_buffer(char[] src, char[] dst, CodePage code_page, char replacement = REPLACEMENT_CHAR)
{
// Unpack the packed reverse table once into a local uint[256] view.
uint[256] from_map;
CodePageTable *table = code_page.table;
for (usz i = 0; i < 256; i++)
{
UIntLE *val = (UIntLE*)&table.from_codepoint[i * 4];
from_map[i] = mem::load(val, 1).val;
}
usz out = 0;
usz n = src.len;
for (usz i = 0; i < n; )
{
usz rem = n - i;
if (rem > 4) rem = 4;
Char32 codepoint = conv::utf8_to_char32(&src[i], &rem)!;
i += rem;
// Binary search for codepoint in low 24 bits of each entry.
// Returned index is between [0..from_map.len).
usz index = sort::binarysearch(from_map[..], (uint)codepoint, fn int(uint lhs, uint rhs) => (int)(lhs & MASK) - (int)(rhs & MASK));
uint entry = from_map[index];
if ((entry & MASK) == (uint)codepoint)
{
char b = (char)(entry >> 24);
dst[out++] = b;
}
else
{
dst[out++] = replacement;
}
}
return dst[:out];
}
<*
Compute the number of UTF8 bytes produced when decoding src with the given
code page table.
@param src : "Input byte array in the given code page."
@param code_page : "Code page for this encoding."
*>
fn usz decode_len(char[] src, CodePage code_page) @inline
{
usz n;
CodePageTable *table = code_page.table;
foreach (usz c: src) n += table.to_codepoint[c *4 ];
return n;
}
<*
Compute the number of output bytes produced when
encoding src from UTF8 to a singlebyte code page.
@param src : "Input byte array in UTF-8"
*>
fn usz encode_len(char[] src) @inline
{
return conv::utf8_codepoints((String)src);
}

File diff suppressed because it is too large Load Diff

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