Compare commits

..

2 Commits

Author SHA1 Message Date
Christoffer Lerno
058d637407 Additional codegen. 2024-12-28 16:46:12 +01:00
Christoffer Lerno
01335f6862 Additional codegen. 2024-12-28 16:23:25 +01:00
984 changed files with 21232 additions and 36271 deletions

View File

@@ -11,7 +11,7 @@ env:
LLVM_RELEASE_VERSION_MAC: 17
LLVM_RELEASE_VERSION_LINUX: 17
LLVM_RELEASE_VERSION_UBUNTU20: 17
LLVM_DEV_VERSION: 21
LLVM_DEV_VERSION: 20
jobs:
build-msvc:
@@ -51,17 +51,17 @@ jobs:
run: |
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" x64
cd resources/testproject
..\..\build\${{ matrix.build_type }}\c3c.exe -vvv --emit-llvm run hello_world_win32 --trust=full
dir build\llvm\windows-x64
..\..\build\${{ matrix.build_type }}\c3c.exe -vvv --emit-llvm run hello_world_win32
dir build\llvm_ir
..\..\build\${{ matrix.build_type }}\c3c.exe clean
dir build\llvm\windows-x64
dir build\llvm_ir
- name: Build testproject lib
run: |
cd resources/testproject
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" x64
..\..\build\${{ matrix.build_type }}\c3c.exe -vvv build hello_world_win32_lib --trust=full
..\..\build\${{ matrix.build_type }}\c3c.exe -vvv build hello_world_win32_lib
- name: Compile and run dynlib-test
run: |
@@ -77,26 +77,26 @@ jobs:
- name: Vendor-fetch
run: |
build\${{ matrix.build_type }}\c3c.exe vendor-fetch raylib55_v7
build\${{ matrix.build_type }}\c3c.exe vendor-fetch raylib5
- name: Try raylib5
run: |
cd resources
..\build\${{ matrix.build_type }}\c3c.exe vendor-fetch raylib55_v7
..\build\${{ matrix.build_type }}\c3c.exe compile --lib raylib55_v7 --print-linking examples\raylib\raylib_arkanoid.c3
..\build\${{ matrix.build_type }}\c3c.exe compile --lib raylib55_v7 --print-linking examples\raylib\raylib_snake.c3
..\build\${{ matrix.build_type }}\c3c.exe compile --lib raylib55_v7 --print-linking examples\raylib\raylib_tetris.c3
..\build\${{ matrix.build_type }}\c3c.exe vendor-fetch raylib5
..\build\${{ matrix.build_type }}\c3c.exe compile --lib raylib5 --print-linking examples\raylib\raylib_arkanoid.c3
..\build\${{ matrix.build_type }}\c3c.exe compile --lib raylib5 --print-linking examples\raylib\raylib_snake.c3
..\build\${{ matrix.build_type }}\c3c.exe compile --lib raylib5 --print-linking examples\raylib\raylib_tetris.c3
- name: run compiler tests
run: |
cd test
python3.exe src/tester.py ..\build\${{ matrix.build_type }}\c3c.exe test_suite/
- name: Compile run unit tests
run: |
cd test
..\build\${{ matrix.build_type }}\c3c.exe compile-test unit -O1
- name: run compiler tests
run: |
cd test
..\build\${{ matrix.build_type }}\c3c.exe compile-run -O1 src/test_suite_runner.c3 -- ..\build\${{ matrix.build_type }}\c3c.exe test_suite/ --no-terminal
- name: Test python script
run: |
py msvc_build_libraries.py --accept-license
@@ -112,7 +112,7 @@ jobs:
build-msys2-mingw:
runs-on: windows-latest
if: ${{ false }}
# if: ${{ false }}
strategy:
# Don't abort runners if a single one fails
fail-fast: false
@@ -132,18 +132,17 @@ jobs:
install: git binutils mingw-w64-x86_64-clang mingw-w64-x86_64-ninja mingw-w64-x86_64-cmake mingw-w64-x86_64-toolchain mingw-w64-x86_64-python
- shell: msys2 {0}
run: |
pacman --noconfirm -U https://mirror.msys2.org/mingw/mingw64/mingw-w64-x86_64-llvm-20.1.0-1-any.pkg.tar.zst
pacman --noconfirm -U https://mirror.msys2.org/mingw/mingw64/mingw-w64-x86_64-lld-20.1.0-1-any.pkg.tar.zst
pacman --noconfirm -U https://mirror.msys2.org/mingw/mingw64/mingw-w64-x86_64-llvm-19.1.6-1-any.pkg.tar.zst
pacman --noconfirm -U https://mirror.msys2.org/mingw/mingw64/mingw-w64-x86_64-lld-19.1.6-1-any.pkg.tar.zst
- name: CMake
run: |
cmake -B build -G Ninja -DCMAKE_C_COMPILER=clang -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} -DCMAKE_LINKER=lld
cmake -B build -G Ninja -DCMAKE_C_COMPILER=clang -DCMAKE_BUILD_TYPE=${{ matrix.build_type }}
cmake --build build
- name: Compile and run some examples
run: |
cd resources
../build/c3c compile-run --print-linking examples/hello_world_many.c3
../build/c3c compile-run --print-linking examples/process.c3
../build/c3c compile-run --print-linking examples/time.c3
../build/c3c compile-run --print-linking examples/fannkuch-redux.c3
../build/c3c compile-run --print-linking examples/contextfree/boolerr.c3
@@ -154,22 +153,22 @@ jobs:
- name: Build testproject
run: |
cd resources/testproject
../../build/c3c run -vvv --trust=full
../../build/c3c run -vvv
- name: Vendor-fetch
run: |
./build/c3c vendor-fetch raylib55_v7
./build/c3c vendor-fetch raylib5
- name: Build testproject lib
run: |
cd resources/testproject
../../build/c3c build hello_world_lib --cc cc -vvv --trust=full
../../build/c3c build hello_world_lib --cc cc -vvv
- name: run compiler tests
run: |
cd test
../build/c3c.exe compile --target windows-x64 -O1 src/test_suite_runner.c3
./test_suite_runner.exe ../build/c3c.exe test_suite/ --no-terminal
python3 src/tester.py ../build/c3c.exe test_suite/
build-msys2-clang:
runs-on: windows-latest
@@ -210,26 +209,26 @@ jobs:
- name: Build testproject
run: |
cd resources/testproject
../../build/c3c run -vvv --trust=full
../../build/c3c run -vvv
- name: Build testproject lib
run: |
cd resources/testproject
../../build/c3c build hello_world_lib -vvv --trust=full
../../build/c3c build hello_world_lib -vvv
- name: run compiler tests
run: |
cd test
../build/c3c.exe compile-run -O1 src/test_suite_runner.c3 -- ../build/c3c.exe test_suite/ --no-terminal
python3 src/tester.py ../build/c3c.exe test_suite/
build-linux:
runs-on: ubuntu-22.04
runs-on: ubuntu-latest
strategy:
# Don't abort runners if a single one fails
fail-fast: false
matrix:
build_type: [Release, Debug]
llvm_version: [17, 18, 19, 20]
llvm_version: [17, 18, 19]
steps:
- uses: actions/checkout@v4
@@ -350,7 +349,7 @@ jobs:
- name: Build testproject
run: |
cd resources/testproject
../../build/c3c run -vvv --trust=full
../../build/c3c run -vvv
- name: Test WASM
run: |
@@ -369,7 +368,7 @@ jobs:
- name: Build testproject direct linker
run: |
cd resources/testproject
../../build/c3c run -vvv --linker=builtin --trust=full
../../build/c3c run -vvv --linker=builtin
- name: Init a library & a project
run: |
@@ -381,7 +380,7 @@ jobs:
- name: run compiler tests
run: |
cd test
../build/c3c compile-run -O1 src/test_suite_runner.c3 -- ../build/c3c test_suite/
python3 src/tester.py ../build/c3c test_suite/
- name: bundle_output
if: matrix.llvm_version == env.LLVM_RELEASE_VERSION_LINUX
@@ -390,8 +389,6 @@ jobs:
cp -r lib c3
cp msvc_build_libraries.py c3
cp build/c3c c3
cp README.md c3
cp releasenotes.md c3
tar czf c3-linux-${{matrix.build_type}}.tar.gz c3
- name: upload artifacts
@@ -408,7 +405,7 @@ jobs:
fail-fast: false
matrix:
build_type: [Release, Debug]
llvm_version: [17, 18, 19, 20]
llvm_version: [17, 18, 19]
steps:
- uses: actions/checkout@v4
- name: Install common deps
@@ -489,30 +486,28 @@ jobs:
- name: Compile run unit tests
run: |
cd test
../build/c3c compile-test unit --sanitize=address
../build/c3c compile-test unit
- name: Build testproject
run: |
cd resources/testproject
../../build/c3c run -vvv --trust=full
../../build/c3c run -vvv
- name: Build testproject direct linker
run: |
cd resources/testproject
../../build/c3c run -vvv --linker=builtin --trust=full
../../build/c3c run -vvv --linker=builtin
- name: run compiler tests
run: |
cd test
../build/c3c compile-run -O1 src/test_suite_runner.c3 -- ../build/c3c test_suite/
python3 src/tester.py ../build/c3c test_suite/
- name: bundle_output
if: matrix.llvm_version == env.LLVM_RELEASE_VERSION_UBUNTU20
run: |
mkdir c3
cp -r lib c3
cp README.md c3
cp releasenotes.md c3
cp msvc_build_libraries.py c3
cp build/c3c c3
tar czf c3-ubuntu-20-${{matrix.build_type}}.tar.gz c3
@@ -525,7 +520,7 @@ jobs:
path: c3-ubuntu-20-${{matrix.build_type}}.tar.gz
build-with-docker:
runs-on: ubuntu-22.04
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
@@ -588,7 +583,7 @@ jobs:
- name: Build testproject
run: |
cd resources/testproject
../../build/c3c run -vvv --trust=full
../../build/c3c run -vvv
- name: Test WASM
run: |
@@ -598,7 +593,7 @@ jobs:
- name: Build testproject direct linker
run: |
cd resources/testproject
../../build/c3c run -vvv --linker=builtin --trust=full
../../build/c3c run -vvv --linker=builtin
- name: Init a library & a project
run: |
@@ -610,7 +605,7 @@ jobs:
- name: run compiler tests
run: |
cd test
../build/c3c compile-run -O1 src/test_suite_runner.c3 -- ../build/c3c test_suite/
python3 src/tester.py ../build/c3c test_suite/
build-mac:
runs-on: macos-latest
@@ -642,7 +637,7 @@ jobs:
- name: Vendor-fetch
run: |
./build/c3c vendor-fetch raylib55_v7
./build/c3c vendor-fetch raylib5
- name: Compile and run some examples
run: |
@@ -665,7 +660,7 @@ jobs:
- name: Compile run unit tests
run: |
cd test
../build/c3c compile-test unit -O1
../build/c3c compile-test unit
- name: Test WASM
run: |
@@ -675,28 +670,22 @@ jobs:
- name: Build testproject
run: |
cd resources/testproject
../../build/c3c run -vvv --trust=full
../../build/c3c run -vvv
- name: Build testproject direct linker
run: |
cd resources/testproject
../../build/c3c run -vvv --linker=builtin --trust=full
../../build/c3c run -vvv --linker=builtin
- name: Build testproject lib
run: |
cd resources/testproject
../../build/c3c build hello_world_lib -vvv --trust=full
../../build/c3c build hello_world_lib -vvv
- name: run compiler tests
run: |
cd test
../build/c3c compile -O1 src/test_suite_runner.c3
./test_suite_runner ../build/c3c test_suite/
- name: run build test suite runner
run: |
cd test
../build/c3c compile -O1 src/test_suite_runner.c3
python3 src/tester.py ../build/c3c test_suite/
- name: bundle_output
if: matrix.llvm_version == env.LLVM_RELEASE_VERSION_MAC
@@ -704,8 +693,6 @@ jobs:
mkdir macos
cp -r lib macos
cp msvc_build_libraries.py macos
cp README.md macos
cp releasenotes.md macos
cp build/c3c macos
zip -r c3-macos-${{matrix.build_type}}.zip macos
@@ -716,55 +703,41 @@ jobs:
name: c3-macos-${{matrix.build_type}}
path: c3-macos-${{matrix.build_type}}.zip
build-nix:
runs-on: ubuntu-22.04
strategy:
# Don't abort runners if a single one fails
fail-fast: false
matrix:
build_type: [ Release, Debug ]
nixpkgs: [ Lock, Latest ]
steps:
- uses: actions/checkout@v4
- name: Install Nix
uses: cachix/install-nix-action@v30
with:
github_access_token: ${{ secrets.GITHUB_TOKEN }}
- name: Update flake (if necessary)
run: |
if [[ matrix.nixpkgs == "Latest" ]]; then
nix flake update
fi
nix flake info
- name: Build and check
run: |
if [[ ${{ matrix.build_type }} = "Debug" ]]; then
nix build -L ".#c3c-debug-checks"
else
nix build -L ".#c3c-checks"
fi
release:
runs-on: ubuntu-22.04
runs-on: ubuntu-latest
needs: [build-msvc, build-linux, build-mac, build-linux-ubuntu20]
if: github.ref == 'refs/heads/master'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- uses: actions/checkout@v4
- name: delete tag
continue-on-error: true
uses: actions/github-script@v6
with:
script: |
github.rest.git.deleteRef({
owner: context.repo.owner,
repo: context.repo.repo,
ref: 'tags/latest',
sha: context.sha
})
- name: create tag
uses: actions/github-script@v6
with:
script: |
github.rest.git.createRef({
owner: context.repo.owner,
repo: context.repo.repo,
ref: 'refs/tags/latest',
sha: context.sha
})
- uses: actions/download-artifact@v4
- run: cp -r lib c3-windows-Release
- run: cp -r lib c3-windows-Debug
- run: cp msvc_build_libraries.py c3-windows-Release
- run: cp msvc_build_libraries.py c3-windows-Debug
- run: cp README.md c3-windows-Release
- run: cp README.md c3-windows-Debug
- run: cp releasenotes.md c3-windows-Release
- run: cp releasenotes.md c3-windows-Debug
- run: zip -r c3-windows.zip c3-windows-Release
- run: zip -r c3-windows-debug.zip c3-windows-Debug
- run: mv c3-linux-Release/c3-linux-Release.tar.gz c3-linux-Release/c3-linux.tar.gz
@@ -773,16 +746,14 @@ jobs:
- run: mv c3-ubuntu-20-Debug/c3-ubuntu-20-Debug.tar.gz c3-ubuntu-20-Debug/c3-ubuntu-20-debug.tar.gz
- run: mv c3-macos-Release/c3-macos-Release.zip c3-macos-Release/c3-macos.zip
- run: mv c3-macos-Debug/c3-macos-Debug.zip c3-macos-Debug/c3-macos-debug.zip
- run: gh release delete latest-0.7.0-prerelease --cleanup-tag -y || true
- run: echo "RELEASE_NAME=latest-0.7.0-rc-$(date +'%Y%m%d-%H%M')" >> $GITHUB_ENV
- id: create_release
uses: softprops/action-gh-release@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: latest-0.7.0-prerelease
name: ${{ env.RELEASE_NAME }}
tag_name: latest
release_name: latest
draft: false
prerelease: true
files: |

7
.gitignore vendored
View File

@@ -79,10 +79,3 @@ TAGS
# 'nix build' resulting symlink
result
# macOS
.DS_Store
# tests
/test/tmp/*
/test/testrun
/test/test_suite_runner

View File

@@ -38,14 +38,14 @@ set(CMAKE_CXX_STANDARD 17)
if(MSVC)
message(STATUS "MSVC version ${MSVC_VERSION}")
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /O2 /EHsc /utf-8")
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} /O2 /EHsc /utf-8")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /Od /Zi /EHa /utf-8")
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} /Od /Zi /EHa /utf-8")
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /O2 /EHsc")
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} /O2 /EHsc")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /Od /Zi /EHa")
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} /Od /Zi /EHa")
else()
if (true)
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O0 -fno-exceptions")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -gdwarf-3 -O0 -fno-exceptions")
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3 -fno-exceptions")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -gdwarf-3 -fno-exceptions")
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -gdwarf-3 -O3 -fno-exceptions")
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -gdwarf-3 -fno-exceptions")
else()
@@ -65,7 +65,6 @@ option(C3_USE_TB "Use TB" OFF)
set(C3_MIMALLOC_TAG "v1.7.3" CACHE STRING "Used version of mimalloc")
option(C3_WITH_LLVM "Build with LLVM" ON)
option(C3_LLD_DIR "Use custom LLD directory" "")
option(LLVM_CRT_LIBRARY_DIR "Use custom llvm's compiler-rt directory" "")
set(C3_USE_MIMALLOC OFF)
if(C3_USE_MIMALLOC)
@@ -85,7 +84,7 @@ if (NOT WIN32)
endif()
if(C3_WITH_LLVM)
if (NOT C3_LLVM_VERSION STREQUAL "auto")
if (${C3_LLVM_VERSION} VERSION_LESS 17 OR ${C3_LLVM_VERSION} VERSION_GREATER 21)
if (${C3_LLVM_VERSION} VERSION_LESS 17 OR ${C3_LLVM_VERSION} VERSION_GREATER 20)
message(FATAL_ERROR "LLVM ${C3_LLVM_VERSION} is not supported!")
endif()
endif()
@@ -151,13 +150,6 @@ if(C3_WITH_LLVM)
endif()
endif()
if (EXISTS /usr/lib)
# Some systems (such as Alpine Linux) seem to put some of the relevant
# LLVM files in /usr/lib, but this doesn't seem to be included in the
# value of LLVM_LIBRARY_DIRS.
list(APPEND LLVM_LIBRARY_DIRS /usr/lib)
endif()
message(STATUS "Found LLVM ${LLVM_PACKAGE_VERSION}")
message(STATUS "Using LLVMConfig.cmake in: ${LLVM_DIR}")
message(STATUS "Libraries located in: ${LLVM_LIBRARY_DIRS}")
@@ -253,19 +245,19 @@ if(C3_WITH_LLVM)
find_library(LLD_LOONG NAMES libLLVMLoongArchCodeGen.lib libLLVMLoongArchAsmParser.lib libLLVMLoongArchCodeGen.a libLLVMLoongArchAsmParser.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
set(lld_libs
${LLD_COFF}
${LLD_COMMON}
${LLD_WASM}
${LLD_MINGW}
${LLD_ELF}
${LLD_MACHO}
${LLD_COMMON}
)
if (APPLE)
set(lld_libs ${lld_libs} xar)
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})
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})
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})
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})
find_file(RT_ASAN_DYNAMIC NAMES libclang_rt.asan_osx_dynamic.dylib PATHS "${LLVM_LIBRARY_DIR}/clang/${LLVM_MAJOR_VERSION}/lib/darwin")
find_file(RT_TSAN_DYNAMIC NAMES libclang_rt.tsan_osx_dynamic.dylib PATHS "${LLVM_LIBRARY_DIR}/clang/${LLVM_MAJOR_VERSION}/lib/darwin")
find_file(RT_UBSAN_DYNAMIC NAMES libclang_rt.ubsan_osx_dynamic.dylib PATHS "${LLVM_LIBRARY_DIR}/clang/${LLVM_MAJOR_VERSION}/lib/darwin")
find_file(RT_LSAN_DYNAMIC NAMES libclang_rt.lsan_osx_dynamic.dylib PATHS "${LLVM_LIBRARY_DIR}/clang/${LLVM_MAJOR_VERSION}/lib/darwin")
set(sanitizer_runtime_libraries
${RT_ASAN_DYNAMIC}
${RT_TSAN_DYNAMIC}
@@ -350,7 +342,6 @@ add_executable(c3c
src/utils/whereami.c
src/utils/cpus.c
src/utils/unzipper.c
src/compiler/c_codegen.c
src/compiler/decltable.c
src/compiler/mac_support.c
src/compiler/windows_support.c
@@ -380,6 +371,7 @@ endif()
if(C3_WITH_LLVM)
target_sources(c3c PRIVATE
src/compiler/c_codegen.c
src/compiler/llvm_codegen.c
src/compiler/llvm_codegen_debug_info.c
src/compiler/llvm_codegen_expr.c

View File

@@ -74,7 +74,7 @@ No space inside parenthesis:
### Tab vs spaces
Use tabs for indentation, no CRLF in the source.
Recommendation: tabs, 4 spaces wide. No CRLF in the source.
### If, braces and new lines
@@ -147,113 +147,4 @@ Iterating over the elements are done using `VECEACH`.
### Scratch buffer for strings.
There is a scratch buffer for strings in the `global_context` prefer using that
one with related functions when working on temporary strings.
# C3 Standard library style guide.
When contributing to the standard librairy please to your best to follow the following style requirements
as to ensure a consistent style in the stdlib and also make accepting PRs more quickly.
### Braces are placed on the next line
**NO:**
```c
fn void foo(String bar) {
@pool() {
...
};
}
```
**YES:**
```c
fn void foo(String bar)
{
@pool()
{
...
};
}
```
### Indentation with tabs
Use tab for indentation, not spaces, no CRLF in the sources
### Type names
Use `PascalCase` not `Ada_Case` for type names.
**YES:**
```c
enum MyEnum
{
ABC,
DEF
}
```
**NO:**
```c
enum My_Enum
{
ABC,
DEF
}
```
### Type names when binding to OS libraries
When doing bindings (for instance, adding declarations referring to Win32 APIs),
try to retain the original name when possible. If it isn't possible use (consistently)
one of two options:
1. Prefix: `HANDLE` -> `Win32_HANDLE`
2. Change the first letter to upper case: `mode_t` -> `Mode_t`
### Variables, function, methods and globals
Use `snake_case`, not `camelCase`.
**YES:**
```c
int some_global = 1;
fn void open_file(String special_file)
{
...
}
```
**NO:**
```c
int someGlobal = 1;
fn void openFile(String specialFile)
{
...
}
```
### Variables, function, methods and globals when binding to OS libraries
When doing bindings (for instance, adding declarations referring to Win32 APIs),
try to retain the original name when possible. If it isn't possible use (consistently)
one of two options:
1. Prefix: `win32_GetWindowLongPtrW`. However, this is usually only recommended if it is builtin.
2. Change first character to lower case: `GetWindowLongPtrW` -> `getWindowLongPtrW`
### Use `self` as the first method argument
Unless there is a strong reason not to, use `self` for the first parameter in a method.
### The allocator argument
Prefer always calling the allocator parameter `allocator`, and make it the first regular
argument.
## Add tests to your changes
If you add or fix things, then there should always be tests in `test/unit/stdlib` to verify
the functionality.
one with related functions when working on temporary strings.

View File

@@ -11,7 +11,7 @@ Precompiled binaries for the following operating systems are available:
- Windows x64 [download](https://github.com/c3lang/c3c/releases/download/latest/c3-windows.zip), [install instructions](#installing-on-windows-with-precompiled-binaries).
- Debian x64 [download](https://github.com/c3lang/c3c/releases/download/latest/c3-linux.tar.gz), [install instructions](#installing-on-debian-with-precompiled-binaries).
- Ubuntu x86 [download](https://github.com/c3lang/c3c/releases/download/latest/c3-ubuntu-20.tar.gz), [install instructions](#installing-on-ubuntu-with-precompiled-binaries).
- MacOS Arm64 [download](https://github.com/c3lang/c3c/releases/download/latest/c3-macos.zip), [install instructions](#installing-on-macos-with-precompiled-binaries).
- MacOS Arm64 [download](https://github.com/c3lang/c3c/releases/download/latest/c3-macos.zip), [install instructions](#installing-on-mac-with-precompiled-binaries).
The manual for C3 can be found at [www.c3-lang.org](http://www.c3-lang.org).
@@ -36,7 +36,7 @@ whole new language.
The following code shows [generic modules](https://c3-lang.org/references/docs/generics/) (more examples can be found at https://c3-lang.org/references/docs/examples/).
```cpp
module stack {Type};
module stack (<Type>);
// Above: the parameterized type is applied to the entire module.
struct Stack
@@ -80,13 +80,13 @@ import stack;
// Define our new types, the first will implicitly create
// a complete copy of the entire Stack module with "Type" set to "int"
alias IntStack = Stack {int};
def IntStack = Stack(<int>);
// The second creates another copy with "Type" set to "double"
alias DoubleStack = Stack {double};
def DoubleStack = Stack(<double>);
// If we had added "alias IntStack2 = Stack {int}"
// If we had added "define IntStack2 = Stack(<int>)"
// no additional copy would have been made (since we already
// have an parameterization of Stack {int} so it would
// have an parameterization of Stack(<int>)) so it would
// be same as declaring IntStack2 an alias of IntStack
// Importing an external C function is straightforward
@@ -138,9 +138,9 @@ fn void main()
### Current status
The current stable version of the compiler is **version 0.6.8**.
The current stable version of the compiler is **version 0.6.5**.
The the next version is 0.7.0 which will be a breaking release.
The upcoming 0.6.x releases will focus on expanding the standard library.
Follow the issues [here](https://github.com/c3lang/c3c/issues).
If you have suggestions on how to improve the language, either [file an issue](https://github.com/c3lang/c3c/issues)
@@ -249,13 +249,20 @@ makepkg -si
#### 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 either an Ubuntu 18.04 or 20.04 container:
```
UBUNTU_VERSION=20.04 ./build-with-docker.sh
./build-with-docker.sh 18
```
See the `build-with-docker.sh` script for more information on other configurable environment variables.
Replace `18` with `20` to build through Ubuntu 20.04.
For a release build specify:
```
./build-with-docker.sh 20 Release
```
A `c3c` executable will be found under `bin/`.
#### Installing on OS X using Homebrew
@@ -268,14 +275,6 @@ See the `build-with-docker.sh` script for more information on other configurable
8. Set up CMake build for debug: `cmake ..`
9. Build: `cmake --build .`
#### Installing on Windows using Scoop
c3c is included in 'Main' bucket.
```sh
scoop install c3
```
#### Getting started with a "hello world"
Create a `main.c3` file with:
@@ -350,20 +349,6 @@ Your c3c executable should have compiled properly. You may want to test it: `./c
For a sytem-wide installation, run the following as root: `cmake --install .`
#### Compiling on Fedora
1. Install required project dependencies: `dnf install cmake clang git llvm llvm-devel lld lld-devel ncurses-devel`
2. Optionally, install additional dependencies: `dnf install libcurl-devel zlib-devel libzstd-devel libxml2-devel libffi-devel`
3. Clone the C3C repository: `git clone https://github.com/c3lang/c3c.git`
- If you only need the latest commit, you may want to make a shallow clone: `git clone https://github.com/c3lang/c3c.git --depth=1`
4. Enter the C3C directory: `cd c3c`
5. Create a build directory and navigate into it: `mkdir build && cd build`
6. Create the CMake build cache. The Fedora repositories provide `.so` libraries for lld, so you need to set the C3_LINK_DYNAMIC flag: `cmake .. -DC3_LINK_DYNAMIC=1`
7. Build the project: `cmake --build .`
The c3c binary should be created in the build directory. You can try it out by running some sample code: `./c3c compile ../resources/examples/hash.c3`
#### Compiling on other Linux / Unix variants
1. Install CMake.
@@ -403,7 +388,3 @@ Editor plugins can be found at https://github.com/c3lang/editor-plugins.
A huge **THANK YOU** goes out to all contributors and sponsors.
A special thank you to sponsors [Caleb-o](https://github.com/Caleb-o) and [devdad](https://github.com/devdad) for going the extra mile.
## Star History
[![Star History Chart](https://api.star-history.com/svg?repos=c3lang/c3c&type=Date)](https://www.star-history.com/#c3lang/c3c&Date)

View File

@@ -8,7 +8,7 @@ fn void init() @init
set_benchmark_max_iterations(10_000);
}
fn void quicksort_bench() @benchmark
fn void! quicksort_bench() @benchmark
{
// test set: 500 numbers between 0 and 99;
int[] data = {

12
flake.lock generated
View File

@@ -5,11 +5,11 @@
"systems": "systems"
},
"locked": {
"lastModified": 1731533236,
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
"lastModified": 1726560853,
"narHash": "sha256-X6rJYSESBVr3hBoH0WbKE5KvhPU5bloyZ2L4K60/fPQ=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
"rev": "c1dfcf08411b08f6b8615f7d8971a2bfa81d5e8a",
"type": "github"
},
"original": {
@@ -20,11 +20,11 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1738297584,
"narHash": "sha256-AYvaFBzt8dU0fcSK2jKD0Vg23K2eIRxfsVXIPCW9a0E=",
"lastModified": 1730958623,
"narHash": "sha256-JwQZIGSYnRNOgDDoIgqKITrPVil+RMWHsZH1eE1VGN0=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "9189ac18287c599860e878e905da550aa6dec1cd",
"rev": "85f7e662eda4fa3a995556527c87b2524b691933",
"type": "github"
},
"original": {

View File

@@ -8,17 +8,12 @@
outputs = { self, ... } @ inputs: inputs.flake-utils.lib.eachDefaultSystem
(system:
let pkgs = import inputs.nixpkgs { inherit system; };
call = set: pkgs.callPackage ./nix/default.nix (
set // {
rev = self.rev or "unknown";
}
);
in {
let pkgs = import inputs.nixpkgs { inherit system; }; in
{
packages = {
default = self.packages.${system}.c3c;
c3c = call {};
c3c = pkgs.callPackage ./nix/default.nix {};
c3c-checks = pkgs.callPackage ./nix/default.nix {
checks = true;

View File

@@ -4,7 +4,6 @@ set(GIT_HASH "unknown")
if(GIT_FOUND AND EXISTS "${CMAKE_CURRENT_LIST_DIR}/.git")
execute_process(COMMAND ${GIT_EXECUTABLE} rev-parse HEAD
WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}
OUTPUT_VARIABLE GIT_HASH
OUTPUT_STRIP_TRAILING_WHITESPACE
COMMAND_ERROR_IS_FATAL ANY)

View File

@@ -1,7 +1,7 @@
// Copyright (c) 2023 Eduardo José Gómez Hernández. All rights reserved.
// Use of this source code is governed by the MIT license
// a copy of which can be found in the LICENSE_STDLIB file.
module std::atomic::types{Type};
module std::atomic::types(<Type>);
struct Atomic
{
@@ -11,7 +11,7 @@ struct Atomic
<*
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"
*>
macro Type Atomic.load(&self, AtomicOrdering ordering = SEQ_CONSISTENT)
@@ -31,7 +31,7 @@ macro Type Atomic.load(&self, AtomicOrdering ordering = SEQ_CONSISTENT)
<*
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"
*>
macro void Atomic.store(&self, Type value, AtomicOrdering ordering = SEQ_CONSISTENT)
@@ -76,7 +76,7 @@ macro Type Atomic.div(&self, Type value, AtomicOrdering ordering = SEQ_CONSISTEN
macro Type Atomic.max(&self, Type value, AtomicOrdering ordering = SEQ_CONSISTENT)
{
Type* data = &self.data;
return @atomic_exec(atomic::fetch_max, data, value, ordering);
return @atomic_exec(atomic::fetch_div, data, value, ordering);
}
macro Type Atomic.min(&self, Type value, AtomicOrdering ordering = SEQ_CONSISTENT)
@@ -85,49 +85,36 @@ macro Type Atomic.min(&self, Type value, AtomicOrdering ordering = SEQ_CONSISTEN
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, uint value, AtomicOrdering ordering = SEQ_CONSISTENT) @if(!types::is_float(Type))
{
Type* data = &self.data;
return @atomic_exec(atomic::fetch_or, data, value, ordering);
}
fn Type Atomic.xor(&self, Type value, AtomicOrdering ordering = SEQ_CONSISTENT) @if(types::flat_kind(Type) != FLOAT)
fn Type Atomic.xor(&self, uint value, AtomicOrdering ordering = SEQ_CONSISTENT) @if(!types::is_float(Type))
{
Type* data = &self.data;
return @atomic_exec(atomic::fetch_xor, data, value, ordering);
}
macro Type Atomic.and(&self, Type value, AtomicOrdering ordering = SEQ_CONSISTENT) @if(types::flat_kind(Type) != FLOAT)
macro Type Atomic.and(&self, uint value, AtomicOrdering ordering = SEQ_CONSISTENT) @if(!types::is_float(Type))
{
Type* data = &self.data;
return @atomic_exec(atomic::fetch_and, data, value, ordering);
}
macro Type Atomic.shr(&self, Type amount, AtomicOrdering ordering = SEQ_CONSISTENT) @if(types::flat_kind(Type) != FLOAT)
macro Type Atomic.shift_right(&self, uint amount, AtomicOrdering ordering = SEQ_CONSISTENT) @if(!types::is_float(Type))
{
Type* data = &self.data;
return @atomic_exec(atomic::fetch_shift_right, data, amount, ordering);
}
macro Type Atomic.shl(&self, Type amount, AtomicOrdering ordering = SEQ_CONSISTENT) @if(types::flat_kind(Type) != FLOAT)
macro Type Atomic.shift_left(&self, uint amount, AtomicOrdering ordering = SEQ_CONSISTENT) @if(!types::is_float(Type))
{
Type* data = &self.data;
return @atomic_exec(atomic::fetch_shift_left, data, amount, ordering);
}
macro Type Atomic.set(&self, AtomicOrdering ordering = SEQ_CONSISTENT) @if(types::flat_kind(Type) == BOOL)
{
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)
{
Type* data = &self.data;
return @atomic_exec_no_arg(atomic::flag_clear, data, ordering);
}
macro @atomic_exec(#func, data, value, ordering) @local
{
switch(ordering)
@@ -141,58 +128,18 @@ macro @atomic_exec(#func, data, value, ordering) @local
}
}
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;
import std::math;
macro bool @is_native_atomic_value(#value) @private
{
return is_native_atomic_type($typeof(#value));
}
macro bool is_native_atomic_type($Type)
{
$if $Type.sizeof > void*.sizeof:
return false;
$else
$switch $Type.kindof:
$case SIGNED_INT:
$case UNSIGNED_INT:
$case POINTER:
$case FLOAT:
$case BOOL:
return true;
$case DISTINCT:
return is_native_atomic_type($typefrom($Type.inner));
$default:
return false;
$endswitch
$endif
}
<*
@param [&in] ptr : "the variable or dereferenced pointer to the data."
@param [in] y : "the value to be added to ptr."
@param $ordering : "atomic ordering of the load, defaults to SEQ_CONSISTENT"
@param [&in] ptr "the variable or dereferenced pointer to the data."
@param [in] y "the value to be added to ptr."
@param $ordering "atomic ordering of the load, defaults to SEQ_CONSISTENT"
@return "returns the old value of ptr"
@require !$alignment || math::is_power_of_2($alignment) : "Alignment must be a power of two."
@require $defined(*ptr) : "Expected a pointer"
@require @is_native_atomic_value(*ptr) : "Only types that are native atomic may be used."
@require $defined(*ptr + y) : "+ must be defined between the values."
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED : "Acquire ordering is not valid."
@require !$alignment || math::is_power_of_2($alignment) "Alignment must be a power of two."
@require types::is_int($typeof(*ptr)) || types::is_float($typeof(*ptr)) "Only integer/float pointers may be used."
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED "Acquire ordering is not valid."
*>
macro fetch_add(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile = false, usz $alignment = 0)
{
@@ -203,16 +150,14 @@ macro fetch_add(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatil
}
<*
@param [&in] ptr : "the variable or dereferenced pointer to the data."
@param [in] y : "the value to be subtracted from ptr."
@param $ordering : "atomic ordering of the load, defaults to SEQ_CONSISTENT"
@param [&in] ptr "the variable or dereferenced pointer to the data."
@param [in] y "the value to be added to ptr."
@param $ordering "atomic ordering of the load, defaults to SEQ_CONSISTENT"
@return "returns the old value of ptr"
@require !$alignment || math::is_power_of_2($alignment) : "Alignment must be a power of two."
@require $defined(*ptr) : "Expected a pointer"
@require @is_native_atomic_value(*ptr) : "Only types that are native atomic may be used."
@require $defined(*ptr - y) : "- must be defined between the values."
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED : "Acquire ordering is not valid."
@require !$alignment || math::is_power_of_2($alignment) "Alignment must be a power of two."
@require types::is_int($typeof(*ptr)) || types::is_float($typeof(*ptr)) "Only integer/float pointers may be used."
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED "Acquire ordering is not valid."
*>
macro fetch_sub(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile = false, usz $alignment = 0)
{
@@ -223,15 +168,13 @@ macro fetch_sub(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatil
}
<*
@param [&in] ptr : "the variable or dereferenced pointer to the data."
@param [in] y : "the value to be multiplied with ptr."
@param $ordering : "atomic ordering of the load, defaults to SEQ_CONSISTENT"
@param [&in] ptr "the variable or dereferenced pointer to the data."
@param [in] y "the value to be added to ptr."
@param $ordering "atomic ordering of the load, defaults to SEQ_CONSISTENT"
@return "returns the old value of ptr"
@require $defined(*ptr) : "Expected a pointer"
@require @is_native_atomic_value(*ptr) : "Only types that are native atomic may be used."
@require $defined(*ptr * y) : "* must be defined between the values."
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED : "Acquire ordering is not valid."
@require types::is_int($typeof(*ptr)) || types::is_float($typeof(*ptr)) "Only integer/float pointers may be used."
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED "Acquire ordering is not valid."
*>
macro fetch_mul(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT)
{
@@ -250,28 +193,25 @@ macro fetch_mul(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT)
$StorageType storage_old_value;
$StorageType storage_new_value;
do
{
do {
storage_old_value = $$atomic_load(storage_ptr, false, $load_ordering.ordinal);
old_value = bitcast(storage_old_value, $typeof(*ptr));
new_value = old_value * y;
storage_new_value = bitcast(new_value, $StorageType);
}
while (mem::compare_exchange(storage_ptr, storage_old_value, storage_new_value, $ordering, $load_ordering) != storage_old_value);
} while (mem::compare_exchange(storage_ptr, storage_old_value, storage_new_value, $ordering, $load_ordering) != storage_old_value);
return old_value;
}
<*
@param [&in] ptr : "the variable or dereferenced pointer to the data."
@param [in] y : "the value to divide ptr by."
@param $ordering : "atomic ordering of the load, defaults to SEQ_CONSISTENT"
@param [&in] ptr "the variable or dereferenced pointer to the data."
@param [in] y "the value to be added to ptr."
@param $ordering "atomic ordering of the load, defaults to SEQ_CONSISTENT"
@return "returns the old value of ptr"
@require $defined(*ptr) : "Expected a pointer"
@require @is_native_atomic_value(*ptr) : "Only types that are native atomic may be used."
@require $defined(*ptr * y) : "/ must be defined between the values."
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED : "Acquire ordering is not valid."
@require types::is_int($typeof(*ptr)) || types::is_float($typeof(*ptr)) "Only integer/float pointers may be used."
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED "Acquire ordering is not valid."
*>
macro fetch_div(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT)
{
@@ -290,80 +230,154 @@ macro fetch_div(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT)
$StorageType storage_old_value;
$StorageType storage_new_value;
do
{
do {
storage_old_value = $$atomic_load(storage_ptr, false, $load_ordering.ordinal);
old_value = bitcast(storage_old_value, $typeof(*ptr));
new_value = old_value / y;
storage_new_value = bitcast(new_value, $StorageType);
}
while (mem::compare_exchange(storage_ptr, storage_old_value, storage_new_value, $ordering, $load_ordering) != storage_old_value);
} while (mem::compare_exchange(storage_ptr, storage_old_value, storage_new_value, $ordering, $load_ordering) != storage_old_value);
return old_value;
}
<*
@param [&in] ptr : "the variable or dereferenced pointer to the data."
@param [in] y : "the value to perform a bitwise or with."
@param $ordering : "atomic ordering of the load, defaults to SEQ_CONSISTENT"
@param [&in] ptr "the variable or dereferenced pointer to the data."
@param [in] y "the value to be added to ptr."
@param $ordering "atomic ordering of the load, defaults to SEQ_CONSISTENT"
@return "returns the old value of ptr"
@require !$alignment || math::is_power_of_2($alignment) : "Alignment must be a power of two."
@require $defined(*ptr) : "Expected a pointer"
@require @is_native_atomic_value(*ptr) : "Only types that are native atomic may be used."
@require $defined(*ptr | y) : "| must be defined between the values."
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED : "Acquire ordering is not valid."
@require !$alignment || math::is_power_of_2($alignment) "Alignment must be a power of two."
@require types::is_int($typeof(*ptr)) "Only integer pointers may be used."
@require types::is_int($typeof(y)) "The value for or must be an int"
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED "Acquire ordering is not valid."
*>
macro fetch_or(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile = false, usz $alignment = 0)
{
return $$atomic_fetch_or(ptr, y, $volatile, $ordering.ordinal, $alignment);
$if types::is_int($typeof(*ptr)):
return $$atomic_fetch_or(ptr, y, $volatile, $ordering.ordinal, $alignment);
$endif
var $load_ordering = $ordering;
$if $ordering == RELEASE || $ordering == ACQUIRE_RELEASE:
$load_ordering = AtomicOrdering.SEQ_CONSISTENT;
$endif
var $StorageType = $typefrom(types::lower_to_atomic_compatible_type($typeof(*ptr)));
$StorageType* storage_ptr = ($StorageType*)ptr;
$typeof(*ptr) old_value;
$typeof(*ptr) new_value;
$StorageType storage_old_value;
$StorageType storage_new_value;
$StorageType storage_y = ($StorageType)y;
do {
storage_old_value = $$atomic_load(storage_ptr, false, $load_ordering.ordinal);
old_value = bitcast(storage_old_value, $typeof(*ptr));
new_value = storage_old_value | storage_y;
storage_new_value = bitcast(new_value, $StorageType);
} while (mem::compare_exchange(storage_ptr, storage_old_value, storage_new_value, $ordering, $load_ordering) != storage_old_value);
return old_value;
}
<*
@param [&in] ptr : "the variable or dereferenced pointer to the data."
@param [in] y : "the value to perform a bitwise xor with."
@param $ordering : "atomic ordering of the load, defaults to SEQ_CONSISTENT"
@param [&in] ptr "the variable or dereferenced pointer to the data."
@param [in] y "the value to be added to ptr."
@param $ordering "atomic ordering of the load, defaults to SEQ_CONSISTENT"
@return "returns the old value of ptr"
@require !$alignment || math::is_power_of_2($alignment) : "Alignment must be a power of two."
@require $defined(*ptr) : "Expected a pointer"
@require @is_native_atomic_value(*ptr) : "Only types that are native atomic may be used."
@require $defined(*ptr ^ y) : "^ must be defined between the values."
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED : "Acquire ordering is not valid."
@require !$alignment || math::is_power_of_2($alignment) "Alignment must be a power of two."
@require types::is_int($typeof(*ptr)) "Only integer pointers may be used."
@require types::is_int($typeof(y)) "The value for or must be an int"
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED "Acquire ordering is not valid."
*>
macro fetch_xor(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile = false, usz $alignment = 0)
{
return $$atomic_fetch_xor(ptr, y, $volatile, $ordering.ordinal, $alignment);
$if types::is_int($typeof(*ptr)):
return $$atomic_fetch_xor(ptr, y, $volatile, $ordering.ordinal, $alignment);
$endif
var $load_ordering = $ordering;
$if $ordering == RELEASE || $ordering == ACQUIRE_RELEASE:
$load_ordering = AtomicOrdering.SEQ_CONSISTENT;
$endif
var $StorageType = $typefrom(types::lower_to_atomic_compatible_type($typeof(*ptr)));
$StorageType* storage_ptr = ($StorageType*)ptr;
$typeof(*ptr) old_value;
$typeof(*ptr) new_value;
$StorageType storage_old_value;
$StorageType storage_new_value;
$StorageType storage_y = ($StorageType)y;
do {
storage_old_value = $$atomic_load(storage_ptr, false, $load_ordering.ordinal);
old_value = bitcast(storage_old_value, $typeof(*ptr));
new_value = storage_old_value ^ storage_y;
storage_new_value = bitcast(new_value, $StorageType);
} while (mem::compare_exchange(storage_ptr, storage_old_value, storage_new_value, $ordering, $load_ordering) != storage_old_value);
return old_value;
}
<*
@param [&in] ptr : "the variable or dereferenced pointer to the data."
@param [in] y : "the value to perform a bitwise and with."
@param $ordering : "atomic ordering of the load, defaults to SEQ_CONSISTENT"
@param [&in] ptr "the variable or dereferenced pointer to the data."
@param [in] y "the value to be added to ptr."
@param $ordering "atomic ordering of the load, defaults to SEQ_CONSISTENT"
@return "returns the old value of ptr"
@require !$alignment || math::is_power_of_2($alignment) : "Alignment must be a power of two."
@require $defined(*ptr) : "Expected a pointer"
@require @is_native_atomic_value(*ptr) : "Only types that are native atomic may be used."
@require $defined(*ptr ^ y) : "& must be defined between the values."
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED : "Acquire ordering is not valid."
@require !$alignment || math::is_power_of_2($alignment) "Alignment must be a power of two."
@require types::is_int($typeof(*ptr)) "Only integer pointers may be used."
@require types::is_int($typeof(y)) "The value for or must be an int"
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED "Acquire ordering is not valid."
*>
macro fetch_and(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile = false, usz $alignment = 0)
{
return $$atomic_fetch_and(ptr, y, $volatile, $ordering.ordinal, $alignment);
$if types::is_int($typeof(*ptr)):
return $$atomic_fetch_and(ptr, y, $volatile, $ordering.ordinal, $alignment);
$endif
var $load_ordering = $ordering;
$if $ordering == RELEASE || $ordering == ACQUIRE_RELEASE:
$load_ordering = AtomicOrdering.SEQ_CONSISTENT;
$endif
var $StorageType = $typefrom(types::lower_to_atomic_compatible_type($typeof(*ptr)));
$StorageType* storage_ptr = ($StorageType*)ptr;
$typeof(*ptr) old_value;
$typeof(*ptr) new_value;
$StorageType storage_old_value;
$StorageType storage_new_value;
$StorageType storage_y = ($StorageType)y;
do {
storage_old_value = $$atomic_load(storage_ptr, false, $load_ordering.ordinal);
old_value = bitcast(storage_old_value, $typeof(*ptr));
new_value = storage_old_value & storage_y;
storage_new_value = bitcast(new_value, $StorageType);
} while (mem::compare_exchange(storage_ptr, storage_old_value, storage_new_value, $ordering, $load_ordering) != storage_old_value);
return old_value;
}
<*
@param [&in] ptr : "the variable or dereferenced pointer to the data."
@param [in] y : "the value to shift ptr by."
@param $ordering : "atomic ordering of the load, defaults to SEQ_CONSISTENT"
@param [&in] ptr "the variable or dereferenced pointer to the data."
@param [in] y "the value to be added to ptr."
@param $ordering "atomic ordering of the load, defaults to SEQ_CONSISTENT"
@return "returns the old value of ptr"
@require $defined(*ptr) : "Expected a pointer"
@require @is_native_atomic_value(*ptr) : "Only types that are native atomic may be used."
@require types::is_int($typeof(*ptr)) : "Only integer pointers may be used."
@require types::is_int($typeof(y)) : "The value for shift right must be an integer"
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED : "Acquire ordering is not valid."
@require types::is_int($typeof(*ptr)) "Only integer pointers may be used."
@require types::is_int($typeof(y)) "The value for or must be an int"
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED "Acquire ordering is not valid."
*>
macro fetch_shift_right(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT)
{
@@ -383,29 +397,25 @@ macro fetch_shift_right(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT)
$StorageType storage_new_value;
$StorageType storage_y = ($StorageType)y;
do
{
do {
storage_old_value = $$atomic_load(storage_ptr, false, $load_ordering.ordinal);
old_value = bitcast(storage_old_value, $typeof(*ptr));
new_value = storage_old_value >> storage_y;
storage_new_value = bitcast(new_value, $StorageType);
}
while (mem::compare_exchange(storage_ptr, storage_old_value, storage_new_value, $ordering, $load_ordering) != storage_old_value);
} while (mem::compare_exchange(storage_ptr, storage_old_value, storage_new_value, $ordering, $load_ordering) != storage_old_value);
return old_value;
}
<*
@param [&in] ptr : "the variable or dereferenced pointer to the data."
@param [in] y : "the value to shift ptr by."
@param $ordering : "atomic ordering of the load, defaults to SEQ_CONSISTENT"
@param [&in] ptr "the variable or dereferenced pointer to the data."
@param [in] y "the value to be added to ptr."
@param $ordering "atomic ordering of the load, defaults to SEQ_CONSISTENT"
@return "returns the old value of ptr"
@require $defined(*ptr) : "Expected a pointer"
@require @is_native_atomic_value(*ptr) : "Only types that are native atomic may be used."
@require types::is_int($typeof(*ptr)) : "Only integer pointers may be used."
@require types::is_int($typeof(y)) : "The value for shift left must be an integer"
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED : "Acquire ordering is not valid."
@require types::is_int($typeof(*ptr)) "Only integer pointers may be used."
@require types::is_int($typeof(y)) "The value for or must be an int"
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED "Acquire ordering is not valid."
*>
macro fetch_shift_left(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT)
{
@@ -425,83 +435,64 @@ macro fetch_shift_left(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT)
$StorageType storage_new_value;
$StorageType storage_y = ($StorageType)y;
do
{
do {
storage_old_value = $$atomic_load(storage_ptr, false, $load_ordering.ordinal);
old_value = bitcast(storage_old_value, $typeof(*ptr));
new_value = storage_old_value << storage_y;
storage_new_value = bitcast(new_value, $StorageType);
}
while (mem::compare_exchange(storage_ptr, storage_old_value, storage_new_value, $ordering, $load_ordering) != storage_old_value);
} while (mem::compare_exchange(storage_ptr, storage_old_value, storage_new_value, $ordering, $load_ordering) != storage_old_value);
return old_value;
}
<*
@param [&in] ptr : "the variable or dereferenced pointer to the data."
@param $ordering : "atomic ordering of the load, defaults to SEQ_CONSISTENT"
@param [&in] ptr "the variable or dereferenced pointer to the data."
@param $ordering "atomic ordering of the load, defaults to SEQ_CONSISTENT"
@return "returns the old value of ptr"
@require $defined(*ptr) : "Expected a pointer"
@require @is_native_atomic_value(*ptr) : "Only types that are native atomic may be used."
@require types::flat_kind($typeof(*ptr)) == BOOL : "Only bool pointers may be used."
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED : "Acquire ordering is not valid."
@require types::is_int($typeof(*ptr)) "Only integer pointers may be used."
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED "Acquire ordering is not valid."
*>
macro flag_set(ptr, AtomicOrdering $ordering = SEQ_CONSISTENT)
{
$typeof(*ptr) old_value;
$typeof(*ptr) new_value = true;
var $load_ordering = $ordering;
$if $ordering == RELEASE || $ordering == ACQUIRE_RELEASE:
$load_ordering = AtomicOrdering.SEQ_CONSISTENT;
$endif
do
{
old_value = $$atomic_load(ptr, false, $load_ordering.ordinal);
}
while (mem::compare_exchange(ptr, old_value, new_value, $ordering, $load_ordering) != old_value);
do {
old_value = $$atomic_load(ptr, false, $ordering.ordinal);
} while (mem::compare_exchange(ptr, old_value, new_value, $ordering, $load_ordering) != old_value);
return old_value;
}
<*
@param [&in] ptr : "the variable or dereferenced pointer to the data."
@param $ordering : "atomic ordering of the load, defaults to SEQ_CONSISTENT"
@param [&in] ptr "the variable or dereferenced pointer to the data."
@param $ordering "atomic ordering of the load, defaults to SEQ_CONSISTENT"
@return "returns the old value of ptr"
@require $defined(*ptr) : "Expected a pointer"
@require @is_native_atomic_value(*ptr) : "Only types that are native atomic may be used."
@require types::flat_kind($typeof(*ptr)) == BOOL : "Only bool pointers may be used."
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED : "Acquire ordering is not valid."
@require types::is_int($typeof(*ptr)) "Only integer pointers may be used."
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED "Acquire ordering is not valid."
*>
macro flag_clear(ptr, AtomicOrdering $ordering = SEQ_CONSISTENT)
{
$typeof(*ptr) old_value;
$typeof(*ptr) new_value = false;
var $load_ordering = $ordering;
$if $ordering == RELEASE || $ordering == ACQUIRE_RELEASE:
$load_ordering = AtomicOrdering.SEQ_CONSISTENT;
$endif
do
{
old_value = $$atomic_load(ptr, false, $load_ordering.ordinal);
}
while (mem::compare_exchange(ptr, old_value, new_value, $ordering, $load_ordering) != old_value);
do {
old_value = $$atomic_load(ptr, false, $ordering.ordinal);
} while (mem::compare_exchange(ptr, old_value, new_value, $ordering, $load_ordering) != old_value);
return old_value;
}
<*
@param [&in] ptr : "the variable or dereferenced pointer to the data."
@param [in] y : "the value to be compared to ptr."
@param $ordering : "atomic ordering of the load, defaults to SEQ_CONSISTENT"
@param [&in] ptr "the variable or dereferenced pointer to the data."
@param [in] y "the value to be added to ptr."
@param $ordering "atomic ordering of the load, defaults to SEQ_CONSISTENT"
@return "returns the old value of ptr"
@require $defined(*ptr) : "Expected a pointer"
@require @is_native_atomic_value(*ptr) : "Only types that are native atomic may be used."
@require $defined(*ptr > y) : "Only values that are comparable with > may be used"
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED : "Acquire ordering is not valid."
@require types::is_int($typeof(*ptr)) || types::is_float($typeof(*ptr)) "Only integer/float pointers may be used."
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED "Acquire ordering is not valid."
*>
macro fetch_max(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile = false, usz $alignment = 0)
{
@@ -512,15 +503,13 @@ macro fetch_max(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatil
}
<*
@param [&in] ptr : "the variable or dereferenced pointer to the data."
@param [in] y : "the value to be compared to ptr."
@param $ordering : "atomic ordering of the load, defaults to SEQ_CONSISTENT"
@param [&in] ptr "the variable or dereferenced pointer to the data."
@param [in] y "the value to be added to ptr."
@param $ordering "atomic ordering of the load, defaults to SEQ_CONSISTENT"
@return "returns the old value of ptr"
@require $defined(*ptr) : "Expected a pointer"
@require @is_native_atomic_value(*ptr) : "Only types that are native atomic may be used."
@require $defined(*ptr > y) : "Only values that are comparable with > may be used"
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED : "Acquire ordering is not valid."
@require types::is_int($typeof(*ptr)) || types::is_float($typeof(*ptr)) "Only integer/float pointers may be used."
@require $ordering != AtomicOrdering.NOT_ATOMIC && $ordering != AtomicOrdering.UNORDERED "Acquire ordering is not valid."
*>
macro fetch_min(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile = false, usz $alignment = 0)
{

View File

@@ -3,9 +3,8 @@
// a copy of which can be found in the LICENSE_STDLIB file.
module std::atomic;
macro @__atomic_compare_exchange_ordering_failure(ptr, expected, desired, $success, failure, $alignment)
{
switch (failure)
macro @__atomic_compare_exchange_ordering_failure(ptr, expected, desired, $success, failure, $alignment) {
switch(failure)
{
case AtomicOrdering.RELAXED.ordinal: return $$compare_exchange(ptr, expected, desired, false, false, $success, AtomicOrdering.RELAXED.ordinal, $alignment);
case AtomicOrdering.ACQUIRE.ordinal: return $$compare_exchange(ptr, expected, desired, false, false, $success, AtomicOrdering.ACQUIRE.ordinal, $alignment);
@@ -17,7 +16,7 @@ macro @__atomic_compare_exchange_ordering_failure(ptr, expected, desired, $succe
macro @__atomic_compare_exchange_ordering_success(ptr, expected, desired, success, failure, $alignment)
{
switch (success)
switch(success)
{
case AtomicOrdering.RELAXED.ordinal: return @__atomic_compare_exchange_ordering_failure(ptr, expected, desired, AtomicOrdering.RELAXED.ordinal, failure, $alignment);
case AtomicOrdering.ACQUIRE.ordinal: return @__atomic_compare_exchange_ordering_failure(ptr, expected, desired, AtomicOrdering.ACQUIRE.ordinal, failure, $alignment);
@@ -29,7 +28,7 @@ macro @__atomic_compare_exchange_ordering_success(ptr, expected, desired, succes
return 0;
}
fn CInt __atomic_compare_exchange(CInt size, any ptr, any expected, any desired, CInt success, CInt failure) @weak @export("__atomic_compare_exchange")
fn CInt __atomic_compare_exchange(CInt size, any ptr, any expected, any desired, CInt success, CInt failure) @extern("__atomic_compare_exchange") @export
{
switch (size)
{

View File

@@ -1,12 +1,12 @@
module std::bits;
<*
@require types::is_intlike($typeof(i)) : `The input must be an integer or integer vector`
@require types::is_intlike($typeof(i)) `The input must be an integer or integer vector`
*>
macro reverse(i) => $$bitreverse(i);
<*
@require types::is_intlike($typeof(i)) : `The input must be an integer or integer vector`
@require types::is_intlike($typeof(i)) `The input must be an integer or integer vector`
*>
macro bswap(i) @builtin => $$bswap(i);

View File

@@ -1,11 +1,11 @@
// Copyright (c) 2024-2025 Christoffer Lerno. All rights reserved.
// Copyright (c) 2024 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.
module std::collections::anylist;
import std::io,std::math;
alias AnyPredicate = fn bool(any value);
alias AnyTest = fn bool(any type, any context);
def AnyPredicate = fn bool(any value);
def AnyTest = fn bool(any type, any context);
struct AnyList (Printable)
{
@@ -17,8 +17,18 @@ struct AnyList (Printable)
<*
@param [&inout] allocator : "The allocator to use"
@param initial_capacity : "The initial capacity to reserve"
Use `init` for to use a custom allocator.
@param initial_capacity "The initial capacity to reserve"
*>
fn AnyList* AnyList.new_init(&self, usz initial_capacity = 16, Allocator allocator = null)
{
return self.init(allocator ?: allocator::heap(), initial_capacity) @inline;
}
<*
@param [&inout] allocator "The allocator to use"
@param initial_capacity "The initial capacity to reserve"
*>
fn AnyList* AnyList.init(&self, Allocator allocator, usz initial_capacity = 16)
{
@@ -40,16 +50,14 @@ fn AnyList* AnyList.init(&self, Allocator allocator, usz initial_capacity = 16)
<*
Initialize the list using the temp allocator.
@param initial_capacity : "The initial capacity to reserve"
@param initial_capacity "The initial capacity to reserve"
*>
fn AnyList* AnyList.tinit(&self, usz initial_capacity = 16)
fn AnyList* AnyList.temp_init(&self, usz initial_capacity = 16)
{
return self.init(tmem, initial_capacity) @inline;
return self.init(allocator::temp(), initial_capacity) @inline;
}
fn bool AnyList.is_initialized(&self) @inline => self.allocator != null;
fn usz? AnyList.to_format(&self, Formatter* formatter) @dynamic
fn usz! AnyList.to_format(&self, Formatter* formatter) @dynamic
{
switch (self.size)
{
@@ -69,12 +77,25 @@ fn usz? AnyList.to_format(&self, Formatter* formatter) @dynamic
}
}
fn String AnyList.to_new_string(&self, Allocator allocator = null) @dynamic
{
return string::format("%s", *self, allocator: allocator ?: allocator::heap());
}
fn String AnyList.to_string(&self, Allocator allocator) @dynamic
{
return string::format("%s", *self, allocator: allocator);
}
fn String AnyList.to_tstring(&self) => string::tformat("%s", *self);
<*
Push an element on the list by cloning it.
*>
macro void AnyList.push(&self, element)
{
if (!self.allocator) self.allocator = tmem;
if (!self.allocator) self.allocator = allocator::heap();
self.append_internal(allocator::clone(self.allocator, element));
}
@@ -96,40 +117,56 @@ fn void AnyList.free_element(&self, any element) @inline
Pop a value who's type is known. If the type is incorrect, this
will still pop the element.
@return? TYPE_MISMATCH, NO_MORE_ELEMENT
@return! CastResult.TYPE_MISMATCH, IteratorResult.NO_MORE_ELEMENT
*>
macro AnyList.pop(&self, $Type)
{
if (!self.size) return NO_MORE_ELEMENT?;
if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
defer self.free_element(self.entries[self.size]);
return *anycast(self.entries[--self.size], $Type);
}
<*
Pop the last value and allocate the copy using the given allocator.
@return? NO_MORE_ELEMENT
@return! IteratorResult.NO_MORE_ELEMENT
*>
fn any? AnyList.copy_pop(&self, Allocator allocator)
fn any! AnyList.copy_pop(&self, Allocator allocator = allocator::heap())
{
if (!self.size) return NO_MORE_ELEMENT?;
if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
defer self.free_element(self.entries[self.size]);
return allocator::clone_any(allocator, self.entries[--self.size]);
}
<*
Pop the last value and allocate the copy using the given allocator.
@return! IteratorResult.NO_MORE_ELEMENT
@deprecated `use copy_pop`
*>
fn any! AnyList.new_pop(&self, Allocator allocator = allocator::heap())
{
return self.copy_pop(allocator);
}
<*
Pop the last value and allocate the copy using the temp allocator
@return? NO_MORE_ELEMENT
@return! IteratorResult.NO_MORE_ELEMENT
@deprecated `use tcopy_pop`
*>
fn any? AnyList.tcopy_pop(&self) => self.copy_pop(tmem);
fn any! AnyList.temp_pop(&self) => self.copy_pop(allocator::temp());
<*
Pop the last value and allocate the copy using the temp allocator
@return! IteratorResult.NO_MORE_ELEMENT
*>
fn any! AnyList.tcopy_pop(&self) => self.copy_pop(allocator::temp());
<*
Pop the last value. It must later be released using list.free_element()
@return? NO_MORE_ELEMENT
@return! IteratorResult.NO_MORE_ELEMENT
*>
fn any? AnyList.pop_retained(&self)
fn any! AnyList.pop_retained(&self)
{
if (!self.size) return NO_MORE_ELEMENT?;
if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
return self.entries[--self.size];
}
@@ -147,7 +184,7 @@ fn void AnyList.clear(&self)
*>
macro AnyList.pop_first(&self, $Type)
{
if (!self.size) return NO_MORE_ELEMENT?;
if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
defer self.remove_at(0);
return *anycast(self.entries[0], $Type);
}
@@ -155,20 +192,28 @@ macro AnyList.pop_first(&self, $Type)
<*
Same as pop_retained() but pops the first value instead.
*>
fn any? AnyList.pop_first_retained(&self)
fn any! AnyList.pop_first_retained(&self)
{
if (!self.size) return NO_MORE_ELEMENT?;
if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
defer self.remove_at(0);
return self.entries[0];
}
<*
Same as new_pop() but pops the first value instead.
@deprecated `use copy_pop_first`
*>
fn any! AnyList.new_pop_first(&self, Allocator allocator = allocator::heap())
{
return self.copy_pop_first(allocator) @inline;
}
<*
Same as copy_pop() but pops the first value instead.
Same as new_pop() but pops the first value instead.
*>
fn any? AnyList.copy_pop_first(&self, Allocator allocator)
fn any! AnyList.copy_pop_first(&self, Allocator allocator = allocator::heap())
{
if (!self.size) return NO_MORE_ELEMENT?;
if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
defer self.free_element(self.entries[self.size]);
defer self.remove_at(0);
return allocator::clone_any(allocator, self.entries[0]);
@@ -177,7 +222,13 @@ fn any? AnyList.copy_pop_first(&self, Allocator allocator)
<*
Same as temp_pop() but pops the first value instead.
*>
fn any? AnyList.tcopy_pop_first(&self) => self.copy_pop_first(tmem);
fn any! AnyList.tcopy_pop_first(&self) => self.copy_pop_first(allocator::temp());
<*
Same as temp_pop() but pops the first value instead.
@deprecated `use tcopy_pop_first`
*>
fn any! AnyList.temp_pop_first(&self) => self.new_pop_first(allocator::temp());
<*
@require index < self.size
@@ -271,9 +322,9 @@ macro AnyList.first(&self, $Type)
return *anycast(self.first_any(), $Type);
}
fn any? AnyList.first_any(&self) @inline
fn any! AnyList.first_any(&self) @inline
{
return self.size ? self.entries[0] : NO_MORE_ELEMENT?;
return self.size ? self.entries[0] : IteratorResult.NO_MORE_ELEMENT?;
}
macro AnyList.last(&self, $Type)
@@ -281,9 +332,9 @@ macro AnyList.last(&self, $Type)
return *anycast(self.last_any(), $Type);
}
fn any? AnyList.last_any(&self) @inline
fn any! AnyList.last_any(&self) @inline
{
return self.size ? self.entries[self.size - 1] : NO_MORE_ELEMENT?;
return self.size ? self.entries[self.size - 1] : IteratorResult.NO_MORE_ELEMENT?;
}
fn bool AnyList.is_empty(&self) @inline
@@ -297,7 +348,7 @@ fn usz AnyList.len(&self) @operator(len) @inline
}
<*
@require index < self.size : "Index out of range"
@require index < self.size "Index out of range"
*>
macro AnyList.get(&self, usz index, $Type)
{
@@ -305,7 +356,7 @@ macro AnyList.get(&self, usz index, $Type)
}
<*
@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
{
@@ -329,7 +380,7 @@ fn void AnyList.swap(&self, usz i, usz j)
}
<*
@param filter : "The function to determine if it should be removed or not"
@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)
@@ -338,7 +389,7 @@ fn usz AnyList.remove_if(&self, AnyPredicate filter)
}
<*
@param selection : "The function to determine if it should be kept or not"
@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)
@@ -415,7 +466,7 @@ fn void AnyList.reserve(&self, usz min_capacity)
{
if (!min_capacity) return;
if (self.capacity >= min_capacity) return;
if (!self.allocator) self.allocator = tmem;
if (!self.allocator) self.allocator = allocator::heap();
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;
@@ -427,7 +478,7 @@ macro any AnyList.@item_at(&self, usz index) @operator([])
}
<*
@require index <= self.size : "Index out of range"
@require index <= self.size "Index out of range"
*>
macro void AnyList.set(&self, usz index, value)
{

View File

@@ -1,9 +1,9 @@
<*
@require SIZE > 0
*>
module std::collections::bitset{SIZE};
module std::collections::bitset(<SIZE>);
alias Type = uint;
def Type = uint;
const BITS = Type.sizeof * 8;
const SZ = (SIZE + BITS - 1) / BITS;
@@ -70,12 +70,12 @@ fn void BitSet.set_bool(&self, usz i, bool value) @operator([]=) @inline
<*
@require Type.kindof == UNSIGNED_INT
*>
module std::collections::growablebitset{Type};
module std::collections::growablebitset(<Type>);
import std::collections::list;
const BITS = Type.sizeof * 8;
alias GrowableBitSetList = List{Type};
def GrowableBitSetList = List(<Type>);
struct GrowableBitSet
{
@@ -84,17 +84,17 @@ struct GrowableBitSet
<*
@param initial_capacity
@param [&inout] allocator : "The allocator to use, defaults to the heap allocator"
@param [&inout] allocator "The allocator to use, defaults to the heap allocator"
*>
fn GrowableBitSet* GrowableBitSet.init(&self, Allocator allocator, usz initial_capacity = 1)
fn GrowableBitSet* GrowableBitSet.new_init(&self, usz initial_capacity = 1, Allocator allocator = allocator::heap())
{
self.data.init(allocator, initial_capacity);
self.data.new_init(initial_capacity, allocator);
return self;
}
fn GrowableBitSet* GrowableBitSet.tinit(&self, usz initial_capacity = 1)
fn GrowableBitSet* GrowableBitSet.temp_init(&self, usz initial_capacity = 1)
{
return self.init(tmem, initial_capacity) @inline;
return self.new_init(initial_capacity, allocator::temp()) @inline;
}
fn void GrowableBitSet.free(&self)
@@ -117,10 +117,15 @@ fn void GrowableBitSet.set(&self, usz i)
usz q = i / BITS;
usz r = i % BITS;
usz current_len = self.data.len();
while (q >= current_len)
if (q >= current_len)
{
self.data.push(0);
current_len++;
usz n = q + 1;
self.data.reserve(n);
if (n - 1 >= current_len)
{
self.data.entries[current_len .. (n - 1)] = 0;
}
self.data.size = n;
}
self.data.set(q, self.data[q] | (1 << r));
}

View File

@@ -2,13 +2,13 @@
// Use of self source code is governed by the MIT license
// a copy of which can be found in the LICENSE_STDLIB file.
<*
@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;
alias ElementPredicate = fn bool(Type *type);
alias ElementTest = fn bool(Type *type, any context);
def ElementPredicate = fn bool(Type *type);
def ElementTest = fn bool(Type *type, any context);
const ELEMENT_IS_EQUATABLE = types::is_equatable_type(Type);
const ELEMENT_IS_POINTER = Type.kindof == POINTER;
macro type_is_overaligned() => Type.alignof > mem::DEFAULT_MEM_ALIGNMENT;
@@ -19,7 +19,7 @@ struct ElasticArray (Printable)
Type[MAX_SIZE] entries;
}
fn usz? ElasticArray.to_format(&self, Formatter* formatter) @dynamic
fn usz! ElasticArray.to_format(&self, Formatter* formatter) @dynamic
{
switch (self.size)
{
@@ -39,28 +39,38 @@ fn usz? ElasticArray.to_format(&self, Formatter* formatter) @dynamic
}
}
fn String ElasticArray.to_string(&self, Allocator allocator) @dynamic
{
return string::format("%s", *self, allocator: allocator);
}
fn String ElasticArray.to_new_string(&self, Allocator allocator = nul) @dynamic
{
return string::format("%s", *self, allocator: allocator ?: allocator::heap());
}
fn String ElasticArray.to_tstring(&self)
{
return string::tformat("%s", *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 AllocationFailure.OUT_OF_MEMORY?;
self.entries[self.size++] = element;
}
<*
@require self.size < MAX_SIZE : `Tried to exceed the max size`
@require self.size < MAX_SIZE `Tried to exceed the max size`
*>
fn void ElasticArray.push(&self, Type element) @inline
{
self.entries[self.size++] = element;
}
fn Type? ElasticArray.pop(&self)
fn Type! ElasticArray.pop(&self)
{
if (!self.size) return NO_MORE_ELEMENT?;
if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
return self.entries[--self.size];
}
@@ -72,9 +82,9 @@ fn void ElasticArray.clear(&self)
<*
@require self.size > 0
*>
fn Type? ElasticArray.pop_first(&self)
fn Type! ElasticArray.pop_first(&self)
{
if (!self.size) return NO_MORE_ELEMENT?;
if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
defer self.remove_at(0);
return self.entries[0];
}
@@ -136,7 +146,7 @@ fn usz ElasticArray.add_array_to_limit(&self, Type[] array)
Add the values of an array to this list.
@param [in] array
@require array.len + self.size <= MAX_SIZE : `Size would exceed max.`
@require array.len + self.size <= MAX_SIZE `Size would exceed max.`
@ensure self.size >= array.len
*>
fn void ElasticArray.add_array(&self, Type[] array)
@@ -150,12 +160,28 @@ fn void ElasticArray.add_array(&self, Type[] array)
<*
IMPORTANT The returned array must be freed using free_aligned.
*>
fn Type[] ElasticArray.to_new_aligned_array(&self)
{
return list_common::list_to_new_aligned_array(Type, self, allocator::heap());
}
<*
IMPORTANT The returned array must be freed using free_aligned.
*>
fn Type[] ElasticArray.to_aligned_array(&self, Allocator allocator)
{
return list_common::list_to_aligned_array(Type, self, allocator);
return list_common::list_to_new_aligned_array(Type, self, allocator);
}
<*
@require !type_is_overaligned() : "This function is not available on overaligned types"
*>
macro Type[] ElasticArray.to_new_array(&self)
{
return list_common::list_to_array(Type, self, allocator::heap());
}
<*
@@ -163,15 +189,15 @@ fn Type[] ElasticArray.to_aligned_array(&self, Allocator allocator)
*>
macro Type[] ElasticArray.to_array(&self, Allocator allocator)
{
return list_common::list_to_array(Type, self, allocator);
return list_common::list_to_new_array(Type, self, allocator);
}
fn Type[] ElasticArray.to_tarray(&self)
{
$if type_is_overaligned():
return self.to_aligned_array(tmem);
return self.to_aligned_array(allocator::temp());
$else
return self.to_array(tmem);
return self.to_array(allocator::temp());
$endif;
}
@@ -189,7 +215,7 @@ fn Type[] ElasticArray.array_view(&self)
}
<*
@require self.size < MAX_SIZE : `List would exceed max size`
@require self.size < MAX_SIZE `List would exceed max size`
*>
fn void ElasticArray.push_front(&self, Type type) @inline
{
@@ -197,9 +223,9 @@ fn void ElasticArray.push_front(&self, Type type) @inline
}
<*
@require self.size < MAX_SIZE : `List would exceed max size`
@require self.size < MAX_SIZE `List would exceed max size`
*>
fn void? ElasticArray.push_front_try(&self, Type type) @inline
fn void! ElasticArray.push_front_try(&self, Type type) @inline
{
return self.insert_at_try(0, type);
}
@@ -207,14 +233,14 @@ fn void? ElasticArray.push_front_try(&self, Type type) @inline
<*
@require index <= self.size
*>
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 AllocationFailure.OUT_OF_MEMORY?;
self.insert_at(index, value);
}
<*
@require self.size < MAX_SIZE : `List would exceed max size`
@require self.size < MAX_SIZE `List would exceed max size`
@require index <= self.size
*>
fn void ElasticArray.insert_at(&self, usz index, Type type)
@@ -235,27 +261,27 @@ fn void ElasticArray.set_at(&self, usz index, Type type)
self.entries[index] = 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 IteratorResult.NO_MORE_ELEMENT?;
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 IteratorResult.NO_MORE_ELEMENT?;
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 IteratorResult.NO_MORE_ELEMENT?;
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 IteratorResult.NO_MORE_ELEMENT?;
return self.entries[self.size - 1];
}
@@ -285,7 +311,7 @@ fn void ElasticArray.swap(&self, usz i, usz j)
}
<*
@param filter : "The function to determine if it should be removed or not"
@param filter "The function to determine if it should be removed or not"
@return "the number of deleted elements"
*>
fn usz ElasticArray.remove_if(&self, ElementPredicate filter)
@@ -294,7 +320,7 @@ fn usz ElasticArray.remove_if(&self, ElementPredicate filter)
}
<*
@param selection : "The function to determine if it should be kept or not"
@param selection "The function to determine if it should be kept or not"
@return "the number of deleted elements"
*>
fn usz ElasticArray.retain_if(&self, ElementPredicate selection)
@@ -330,22 +356,22 @@ fn void ElasticArray.set(&self, usz index, Type value) @operator([]=)
// Functions for equatable types
fn usz? ElasticArray.index_of(&self, Type type) @if(ELEMENT_IS_EQUATABLE)
fn usz! ElasticArray.index_of(&self, Type type) @if(ELEMENT_IS_EQUATABLE)
{
foreach (i, v : self)
{
if (equals(v, type)) return i;
}
return NOT_FOUND?;
return SearchResult.MISSING?;
}
fn usz? ElasticArray.rindex_of(&self, Type type) @if(ELEMENT_IS_EQUATABLE)
fn usz! ElasticArray.rindex_of(&self, Type type) @if(ELEMENT_IS_EQUATABLE)
{
foreach_r (i, v : self)
{
if (equals(v, type)) return i;
}
return NOT_FOUND?;
return SearchResult.MISSING?;
}
fn bool ElasticArray.equals(&self, ElasticArray other_list) @if(ELEMENT_IS_EQUATABLE)
@@ -361,8 +387,8 @@ fn bool ElasticArray.equals(&self, ElasticArray other_list) @if(ELEMENT_IS_EQUAT
<*
Check for presence of a value in a list.
@param [&in] self : "the list to find elements in"
@param value : "The value to search for"
@param [&in] self "the list to find elements in"
@param value "The value to search for"
@return "True if the value is found, false otherwise"
*>
fn bool ElasticArray.contains(&self, Type value) @if(ELEMENT_IS_EQUATABLE)
@@ -375,8 +401,8 @@ fn bool ElasticArray.contains(&self, Type value) @if(ELEMENT_IS_EQUATABLE)
}
<*
@param [&inout] self : "The list to remove elements from"
@param value : "The value to remove"
@param [&inout] self "The list to remove elements from"
@param value "The value to remove"
@return "true if the value was found"
*>
fn bool ElasticArray.remove_last_item(&self, Type value) @if(ELEMENT_IS_EQUATABLE)
@@ -385,8 +411,8 @@ fn bool ElasticArray.remove_last_item(&self, Type value) @if(ELEMENT_IS_EQUATABL
}
<*
@param [&inout] self : "The list to remove elements from"
@param value : "The value to remove"
@param [&inout] self "The list to remove elements from"
@param value "The value to remove"
@return "true if the value was found"
*>
fn bool ElasticArray.remove_first_item(&self, Type value) @if(ELEMENT_IS_EQUATABLE)
@@ -395,8 +421,8 @@ fn bool ElasticArray.remove_first_item(&self, Type value) @if(ELEMENT_IS_EQUATAB
}
<*
@param [&inout] self : "The list to remove elements from"
@param value : "The value to remove"
@param [&inout] self "The list to remove elements from"
@param value "The value to remove"
@return "the number of deleted elements."
*>
fn usz ElasticArray.remove_item(&self, Type value) @if(ELEMENT_IS_EQUATABLE)

View File

@@ -1,7 +1,7 @@
<*
@require Enum.kindof == TypeKind.ENUM : "Only enums may be used with an enummap"
*>
module std::collections::enummap{Enum, ValueType};
module std::collections::enummap(<Enum, ValueType>);
import std::io;
struct EnumMap (Printable)
{
@@ -16,7 +16,7 @@ fn void EnumMap.init(&self, ValueType init_value)
}
}
fn usz? EnumMap.to_format(&self, Formatter* formatter) @dynamic
fn usz! EnumMap.to_format(&self, Formatter* formatter) @dynamic
{
usz n = formatter.print("{ ")!;
foreach (i, &value : self.values)
@@ -28,6 +28,21 @@ fn usz? EnumMap.to_format(&self, Formatter* formatter) @dynamic
return n;
}
fn String EnumMap.to_string(&self, Allocator allocator) @dynamic
{
return string::format("%s", *self, allocator: allocator);
}
fn String EnumMap.to_new_string(&self, Allocator allocator = null) @dynamic
{
return string::format("%s", *self, allocator: allocator ?: allocator::heap());
}
fn String EnumMap.to_tstring(&self) @dynamic
{
return string::tformat("%s", *self);
}
<*
@return "The total size of this map, which is the same as the number of enum values"
@pure

View File

@@ -5,13 +5,13 @@
<*
@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;
alias EnumSetType @private = $typefrom(type_for_enum_elements(Enum.elements));
def EnumSetType = $typefrom(private::type_for_enum_elements(Enum.elements)) @private;
const IS_CHAR_ARRAY = Enum.elements > 128;
typedef EnumSet (Printable) = EnumSetType;
distinct EnumSet (Printable) = EnumSetType;
fn void EnumSet.add(&self, Enum v)
{
@@ -126,7 +126,7 @@ fn EnumSet EnumSet.xor_of(&self, EnumSet s)
$endif
}
fn usz? EnumSet.to_format(&set, Formatter* formatter) @dynamic
fn usz! EnumSet.to_format(&set, Formatter* formatter) @dynamic
{
usz n = formatter.print("[")!;
bool found;
@@ -141,9 +141,26 @@ fn usz? EnumSet.to_format(&set, Formatter* formatter) @dynamic
return n;
}
macro typeid type_for_enum_elements(usz $elements) @local
fn String EnumSet.to_new_string(&set, Allocator allocator = allocator::heap()) @dynamic
{
$switch:
return string::format("%s", *set, allocator: allocator);
}
fn String EnumSet.to_string(&set, Allocator allocator) @dynamic
{
return string::format("%s", *set, allocator: allocator);
}
fn String EnumSet.to_tstring(&set) @dynamic
{
return string::tformat("%s", *set);
}
module std::collections::enumset::private;
macro typeid type_for_enum_elements(usz $elements)
{
$switch
$case ($elements > 128):
return char[($elements + 7) / 8].typeid;
$case ($elements > 64):

View File

@@ -2,31 +2,12 @@
// Use of this source code is governed by the MIT license
// a copy of which can be found in the LICENSE_STDLIB file.
<*
@require $defined((Key){}.hash()) : `No .hash function found on the key`
@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::io @norecurse;
const uint DEFAULT_INITIAL_CAPACITY = 16;
const uint MAXIMUM_CAPACITY = 1u << 31;
const float DEFAULT_LOAD_FACTOR = 0.75;
const VALUE_IS_EQUATABLE = Value.is_eq;
const bool COPY_KEYS = types::implements_copy(Key);
const Allocator MAP_HEAP_ALLOCATOR = (Allocator)&dummy;
const HashMap ONHEAP = { .allocator = MAP_HEAP_ALLOCATOR };
struct Entry
{
uint hash;
Key key;
Value value;
Entry* next;
}
struct HashMap (Printable)
struct HashMap
{
Entry*[] table;
Allocator allocator;
@@ -35,13 +16,24 @@ struct HashMap (Printable)
float load_factor;
}
<*
@param [&inout] allocator "The allocator to use"
@require capacity > 0 "The capacity must be 1 or higher"
@require load_factor > 0.0 "The load factor must be higher than 0"
@require !self.allocator "Map was already initialized"
@require capacity < MAXIMUM_CAPACITY "Capacity cannot exceed maximum"
*>
fn HashMap* HashMap.new_init(&self, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR, Allocator allocator = null)
{
return self.init(allocator ?: allocator::heap(), capacity, load_factor);
}
<*
@param [&inout] allocator : "The allocator to use"
@require capacity > 0 : "The capacity must be 1 or higher"
@require load_factor > 0.0 : "The load factor must be higher than 0"
@require !self.is_initialized() : "Map was already initialized"
@require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum"
@param [&inout] allocator "The allocator to use"
@require capacity > 0 "The capacity must be 1 or higher"
@require load_factor > 0.0 "The load factor must be higher than 0"
@require !self.allocator "Map was already initialized"
@require capacity < MAXIMUM_CAPACITY "Capacity cannot exceed maximum"
*>
fn HashMap* HashMap.init(&self, Allocator allocator, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
{
@@ -54,59 +46,47 @@ fn HashMap* HashMap.init(&self, Allocator allocator, uint capacity = DEFAULT_INI
}
<*
@require capacity > 0 : "The capacity must be 1 or higher"
@require load_factor > 0.0 : "The load factor must be higher than 0"
@require !self.is_initialized() : "Map was already initialized"
@require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum"
@require capacity > 0 "The capacity must be 1 or higher"
@require load_factor > 0.0 "The load factor must be higher than 0"
@require !self.allocator "Map was already initialized"
@require capacity < MAXIMUM_CAPACITY "Capacity cannot exceed maximum"
*>
fn HashMap* HashMap.tinit(&self, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
fn HashMap* HashMap.temp_init(&self, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
{
return self.init(tmem, capacity, load_factor) @inline;
return self.init(allocator::temp(), capacity, load_factor) @inline;
}
<*
@param [&inout] allocator : "The allocator to use"
@require $vacount % 2 == 0 : "There must be an even number of arguments provided for keys and values"
@require capacity > 0 : "The capacity must be 1 or higher"
@require load_factor > 0.0 : "The load factor must be higher than 0"
@require !self.is_initialized() : "Map was already initialized"
@require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum"
@param [&inout] allocator "The allocator to use"
@require $vacount % 2 == 0 "There must be an even number of arguments provided for keys and values"
@require capacity > 0 "The capacity must be 1 or higher"
@require load_factor > 0.0 "The load factor must be higher than 0"
@require !self.allocator "Map was already initialized"
@require capacity < MAXIMUM_CAPACITY "Capacity cannot exceed maximum"
*>
macro HashMap* HashMap.init_with_key_values(&self, Allocator allocator, ..., uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
macro HashMap* HashMap.new_init_with_key_values(&self, ..., uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR, Allocator allocator = allocator::heap())
{
self.init(allocator, capacity, load_factor);
$for var $i = 0; $i < $vacount; $i += 2:
self.set($vaarg[$i], $vaarg[$i + 1]);
self.new_init(capacity, load_factor, allocator);
$for (var $i = 0; $i < $vacount; $i += 2)
self.set($vaarg[$i], $vaarg[$i+1]);
$endfor
return self;
}
<*
@require $vacount % 2 == 0 : "There must be an even number of arguments provided for keys and values"
@require capacity > 0 : "The capacity must be 1 or higher"
@require load_factor > 0.0 : "The load factor must be higher than 0"
@require !self.is_initialized() : "Map was already initialized"
@require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum"
@param [in] keys "The keys for the HashMap entries"
@param [in] values "The values for the HashMap entries"
@param [&inout] allocator "The allocator to use"
@require keys.len == values.len "Both keys and values arrays must be the same length"
@require capacity > 0 "The capacity must be 1 or higher"
@require load_factor > 0.0 "The load factor must be higher than 0"
@require !self.allocator "Map was already initialized"
@require capacity < MAXIMUM_CAPACITY "Capacity cannot exceed maximum"
*>
macro HashMap* HashMap.tinit_with_key_values(&self, ..., uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
{
return self.tinit_with_key_values(tmem, capacity, load_factor);
}
<*
@param [in] keys : "The keys for the HashMap entries"
@param [in] values : "The values for the HashMap entries"
@param [&inout] allocator : "The allocator to use"
@require keys.len == values.len : "Both keys and values arrays must be the same length"
@require capacity > 0 : "The capacity must be 1 or higher"
@require load_factor > 0.0 : "The load factor must be higher than 0"
@require !self.is_initialized() : "Map was already initialized"
@require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum"
*>
fn HashMap* HashMap.init_from_keys_and_values(&self, Allocator allocator, Key[] keys, Value[] values, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
fn HashMap* HashMap.new_init_from_keys_and_values(&self, Key[] keys, Value[] values, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR, Allocator allocator = allocator::heap())
{
assert(keys.len == values.len);
self.init(allocator, capacity, load_factor);
self.new_init(capacity, load_factor, allocator);
for (usz i = 0; i < keys.len; i++)
{
self.set(keys[i], values[i]);
@@ -114,51 +94,79 @@ fn HashMap* HashMap.init_from_keys_and_values(&self, Allocator allocator, Key[]
return self;
}
<*
@require $vacount % 2 == 0 "There must be an even number of arguments provided for keys and values"
@require capacity > 0 "The capacity must be 1 or higher"
@require load_factor > 0.0 "The load factor must be higher than 0"
@require !self.allocator "Map was already initialized"
@require capacity < MAXIMUM_CAPACITY "Capacity cannot exceed maximum"
*>
macro HashMap* HashMap.temp_init_with_key_values(&self, ..., uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
{
self.temp_init(capacity, load_factor);
$for (var $i = 0; $i < $vacount; $i += 2)
self.set($vaarg[$i], $vaarg[$i+1]);
$endfor
return self;
}
<*
@param [in] keys : "The keys for the HashMap entries"
@param [in] values : "The values for the HashMap entries"
@require keys.len == values.len : "Both keys and values arrays must be the same length"
@require capacity > 0 : "The capacity must be 1 or higher"
@require load_factor > 0.0 : "The load factor must be higher than 0"
@require !self.is_initialized() : "Map was already initialized"
@require capacity < MAXIMUM_CAPACITY : "Capacity cannot exceed maximum"
@param [in] keys "The keys for the HashMap entries"
@param [in] values "The values for the HashMap entries"
@param [&inout] allocator "The allocator to use"
@require keys.len == values.len "Both keys and values arrays must be the same length"
@require capacity > 0 "The capacity must be 1 or higher"
@require load_factor > 0.0 "The load factor must be higher than 0"
@require !self.allocator "Map was already initialized"
@require capacity < MAXIMUM_CAPACITY "Capacity cannot exceed maximum"
*>
fn HashMap* HashMap.tinit_from_keys_and_values(&self, Key[] keys, Value[] values, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
fn HashMap* HashMap.temp_init_from_keys_and_values(&self, Key[] keys, Value[] values, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR, Allocator allocator = allocator::heap())
{
return self.init_from_keys_and_values(tmem, keys, values, capacity, load_factor);
assert(keys.len == values.len);
self.temp_init(capacity, load_factor);
for (usz i = 0; i < keys.len; i++)
{
self.set(keys[i], values[i]);
}
return self;
}
<*
Has this hash map been initialized yet?
@param [&in] map : "The hash map we are testing"
@param [&in] map "The hash map we are testing"
@return "Returns true if it has been initialized, false otherwise"
*>
fn bool HashMap.is_initialized(&map)
{
return map.allocator && map.allocator.ptr != &dummy;
return (bool)map.allocator;
}
<*
@param [&inout] allocator : "The allocator to use"
@param [&in] other_map : "The map to copy from."
@require !self.is_initialized() : "Map was already initialized"
@param [&in] other_map "The map to copy from."
*>
fn HashMap* HashMap.init_from_map(&self, Allocator allocator, HashMap* other_map)
fn HashMap* HashMap.new_init_from_map(&self, HashMap* other_map)
{
self.init(allocator, other_map.table.len, other_map.load_factor);
return self.init_from_map(other_map, allocator::heap()) @inline;
}
<*
@param [&inout] allocator "The allocator to use"
@param [&in] other_map "The map to copy from."
*>
fn HashMap* HashMap.init_from_map(&self, HashMap* other_map, Allocator allocator)
{
self.new_init(other_map.table.len, other_map.load_factor, allocator);
self.put_all_for_create(other_map);
return self;
}
<*
@param [&in] other_map : "The map to copy from."
@require !map.is_initialized() : "Map was already initialized"
@param [&in] other_map "The map to copy from."
*>
fn HashMap* HashMap.tinit_from_map(&map, HashMap* other_map)
fn HashMap* HashMap.temp_init_from_map(&map, HashMap* other_map)
{
return map.init_from_map(tmem, other_map) @inline;
return map.init_from_map(other_map, allocator::temp()) @inline;
}
fn bool HashMap.is_empty(&map) @inline
@@ -171,26 +179,26 @@ fn usz HashMap.len(&map) @inline
return map.count;
}
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 SearchResult.MISSING?;
uint hash = rehash(key.hash());
for (Entry *e = map.table[index_for(hash, map.table.len)]; e != null; e = e.next)
{
if (e.hash == hash && equals(key, e.key)) return &e.value;
}
return NOT_FOUND?;
return SearchResult.MISSING?;
}
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 SearchResult.MISSING?;
uint hash = rehash(key.hash());
for (Entry *e = map.table[index_for(hash, map.table.len)]; e != null; e = e.next)
{
if (e.hash == hash && equals(key, e.key)) return e;
}
return NOT_FOUND?;
return SearchResult.MISSING?;
}
<*
@@ -216,7 +224,7 @@ macro Value HashMap.@get_or_set(&map, Key key, Value #expr)
return val;
}
fn Value? HashMap.get(&map, Key key) @operator([])
fn Value! HashMap.get(&map, Key key) @operator([])
{
return *map.get_ref(key) @inline;
}
@@ -229,15 +237,10 @@ fn bool HashMap.has_key(&map, Key key)
fn bool HashMap.set(&map, Key key, Value value) @operator([]=)
{
// If the map isn't initialized, use the defaults to initialize it.
switch (map.allocator.ptr)
{
case &dummy:
map.init(mem);
case null:
map.tinit();
default:
break;
}
if (!map.allocator)
{
map.new_init();
}
uint hash = rehash(key.hash());
uint index = index_for(hash, map.table.len);
for (Entry *e = map.table[index]; e != null; e = e.next)
@@ -252,9 +255,9 @@ fn bool HashMap.set(&map, Key key, Value value) @operator([]=)
return false;
}
fn void? HashMap.remove(&map, Key key) @maydiscard
fn void! HashMap.remove(&map, Key key) @maydiscard
{
if (!map.remove_entry_for_key(key)) return NOT_FOUND?;
if (!map.remove_entry_for_key(key)) return SearchResult.MISSING?;
}
fn void HashMap.clear(&map)
@@ -279,24 +282,37 @@ fn void HashMap.clear(&map)
fn void HashMap.free(&map)
{
if (!map.is_initialized()) return;
if (!map.allocator) return;
map.clear();
map.free_internal(map.table.ptr);
map.table = {};
}
fn Key[] HashMap.tkeys(&self)
fn Key[] HashMap.tcopy_keys(&map)
{
return self.keys(tmem) @inline;
return map.copy_keys(allocator::temp()) @inline;
}
fn Key[] HashMap.keys(&self, Allocator allocator)
fn Key[] HashMap.key_tlist(&map) @deprecated("Use 'tcopy_keys'")
{
if (!self.count) return {};
return map.copy_keys(allocator::temp()) @inline;
}
Key[] list = allocator::alloc_array(allocator, Key, self.count);
<*
@deprecated "use copy_keys"
*>
fn Key[] HashMap.key_new_list(&map, Allocator allocator = allocator::heap())
{
return map.copy_keys(allocator) @inline;
}
fn Key[] HashMap.copy_keys(&map, Allocator allocator = allocator::heap())
{
if (!map.count) return {};
Key[] list = allocator::alloc_array(allocator, Key, map.count);
usz index = 0;
foreach (Entry* entry : self.table)
foreach (Entry* entry : map.table)
{
while (entry)
{
@@ -313,36 +329,53 @@ fn Key[] HashMap.keys(&self, Allocator allocator)
macro HashMap.@each(map; @body(key, value))
{
map.@each_entry(; Entry* entry)
{
map.@each_entry(; Entry* entry) {
@body(entry.key, entry.value);
};
}
macro HashMap.@each_entry(map; @body(entry))
{
if (!map.count) return;
foreach (Entry* entry : map.table)
if (map.count)
{
while (entry)
foreach (Entry* entry : map.table)
{
@body(entry);
entry = entry.next;
while (entry)
{
@body(entry);
entry = entry.next;
}
}
}
}
fn Value[] HashMap.tvalues(&map)
<*
@deprecated `use tcopy_values`
*>
fn Value[] HashMap.value_tlist(&map)
{
return map.values(tmem) @inline;
return map.copy_values(allocator::temp()) @inline;
}
fn Value[] HashMap.values(&self, Allocator allocator)
fn Value[] HashMap.tcopy_values(&map)
{
if (!self.count) return {};
Value[] list = allocator::alloc_array(allocator, Value, self.count);
return map.copy_values(allocator::temp()) @inline;
}
<*
@deprecated `use copy_values`
*>
fn Value[] HashMap.value_new_list(&map, Allocator allocator = allocator::heap())
{
return map.copy_values(allocator);
}
fn Value[] HashMap.copy_values(&map, Allocator allocator = allocator::heap())
{
if (!map.count) return {};
Value[] list = allocator::alloc_array(allocator, Value, map.count);
usz index = 0;
foreach (Entry* entry : self.table)
foreach (Entry* entry : map.table)
{
while (entry)
{
@@ -413,18 +446,6 @@ fn void HashMap.resize(&map, uint new_capacity) @private
map.threshold = (uint)(new_capacity * map.load_factor);
}
fn usz? HashMap.to_format(&self, Formatter* f) @dynamic
{
usz len;
len += f.print("{ ")!;
self.@each_entry(; Entry* entry)
{
if (len > 2) len += f.print(", ")!;
len += f.printf("%s: %s", entry.key, entry.value)!;
};
return len + f.print(" }");
}
fn void HashMap.transfer(&map, Entry*[] new_table) @private
{
Entry*[] src = map.table;
@@ -535,8 +556,8 @@ struct HashMapIterator
Entry* current_entry;
}
typedef HashMapValueIterator = HashMapIterator;
typedef HashMapKeyIterator = HashMapIterator;
distinct HashMapValueIterator = HashMapIterator;
distinct HashMapKeyIterator = HashMapIterator;
<*
@@ -577,16 +598,3 @@ fn Key HashMapKeyIterator.get(&self, usz idx) @operator([])
fn usz HashMapValueIterator.len(self) @operator(len) => self.map.count;
fn usz HashMapKeyIterator.len(self) @operator(len) => self.map.count;
fn usz HashMapIterator.len(self) @operator(len) => self.map.count;
fn uint rehash(uint hash) @inline @private
{
hash ^= (hash >> 20) ^ (hash >> 12);
return hash ^ ((hash >> 7) ^ (hash >> 4));
}
macro uint index_for(uint hash, uint capacity) @private
{
return hash & (capacity - 1);
}
int dummy @local;

View File

@@ -1,7 +1,7 @@
// Copyright (c) 2021-2024 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.
module std::collections::linkedlist{Type};
module std::collections::linkedlist(<Type>);
const ELEMENT_IS_EQUATABLE = types::is_equatable_type(Type);
@@ -21,7 +21,7 @@ struct LinkedList
}
<*
@param [&inout] allocator : "The allocator to use, defaults to the heap allocator"
@param [&inout] allocator "The allocator to use, defaults to the heap allocator"
@return "the initialized list"
*>
fn LinkedList* LinkedList.init(&self, Allocator allocator)
@@ -30,15 +30,22 @@ fn LinkedList* LinkedList.init(&self, Allocator allocator)
return self;
}
fn LinkedList* LinkedList.tinit(&self)
<*
@return "the initialized list"
*>
fn LinkedList* LinkedList.new_init(&self)
{
return self.init(tmem) @inline;
return self.init(allocator::heap()) @inline;
}
fn bool LinkedList.is_initialized(&self) @inline => self.allocator != null;
fn LinkedList* LinkedList.temp_init(&self)
{
return self.init(allocator::temp()) @inline;
}
<*
@require self.is_initialized()
@require self.allocator
*>
macro void LinkedList.free_node(&self, Node* node) @private
{
@@ -47,7 +54,7 @@ macro void LinkedList.free_node(&self, Node* node) @private
macro Node* LinkedList.alloc_node(&self) @private
{
if (!self.allocator) self.allocator = tmem;
if (!self.allocator) self.allocator = allocator::heap();
return allocator::alloc(self.allocator, Node);
}
@@ -85,18 +92,18 @@ fn void LinkedList.push(&self, Type value)
self.size++;
}
fn Type? LinkedList.peek(&self) => self.first() @inline;
fn Type? LinkedList.peek_last(&self) => self.last() @inline;
fn Type! LinkedList.peek(&self) => self.first() @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 IteratorResult.NO_MORE_ELEMENT?;
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 IteratorResult.NO_MORE_ELEMENT?;
return self._last.value;
}
@@ -193,7 +200,7 @@ fn void LinkedList.link_before(&self, Node *succ, Type value) @private
}
<*
@require self._first != null
@require self._first
*>
fn void LinkedList.unlink_first(&self) @private
{
@@ -231,9 +238,9 @@ fn usz LinkedList.remove(&self, Type t) @if(ELEMENT_IS_EQUATABLE)
return start - self.size;
}
fn Type? LinkedList.pop(&self)
fn Type! LinkedList.pop(&self)
{
if (!self._last) return NO_MORE_ELEMENT?;
if (!self._last) return IteratorResult.NO_MORE_ELEMENT?;
defer self.unlink_last();
return self._last.value;
}
@@ -243,22 +250,22 @@ fn bool LinkedList.is_empty(&self)
return !self._first;
}
fn Type? LinkedList.pop_front(&self)
fn Type! LinkedList.pop_front(&self)
{
if (!self._first) return NO_MORE_ELEMENT?;
if (!self._first) return IteratorResult.NO_MORE_ELEMENT?;
defer self.unlink_first();
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 IteratorResult.NO_MORE_ELEMENT?;
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 IteratorResult.NO_MORE_ELEMENT?;
self.unlink_first();
}
@@ -289,7 +296,7 @@ fn bool LinkedList.remove_last_match(&self, Type t) @if(ELEMENT_IS_EQUATABLE)
return false;
}
<*
@require self._last != null
@require self._last
*>
fn void LinkedList.unlink_last(&self) @inline @private
{

View File

@@ -1,18 +1,14 @@
// Copyright (c) 2021-2024 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.
module std::collections::list{Type};
module std::collections::list(<Type>);
import std::io, std::math, std::collections::list_common;
alias ElementPredicate = fn bool(Type *type);
alias ElementTest = fn bool(Type *type, any context);
def ElementPredicate = fn bool(Type *type);
def ElementTest = fn bool(Type *type, any context);
const ELEMENT_IS_EQUATABLE = types::is_equatable_type(Type);
const ELEMENT_IS_POINTER = Type.kindof == POINTER;
const Allocator LIST_HEAP_ALLOCATOR = (Allocator)&dummy;
const List ONHEAP = { .allocator = LIST_HEAP_ALLOCATOR };
macro type_is_overaligned() => Type.alignof > mem::DEFAULT_MEM_ALIGNMENT;
struct List (Printable)
@@ -24,10 +20,10 @@ struct List (Printable)
}
<*
@param initial_capacity : "The initial capacity to reserve"
@param [&inout] allocator : "The allocator to use, defaults to the heap allocator"
@param initial_capacity "The initial capacity to reserve"
@param [&inout] allocator "The allocator to use, defaults to the heap allocator"
*>
fn List* List.init(&self, Allocator allocator, usz initial_capacity = 16)
fn List* List.new_init(&self, usz initial_capacity = 16, Allocator allocator = allocator::heap())
{
self.allocator = allocator;
self.size = 0;
@@ -37,26 +33,25 @@ fn List* List.init(&self, Allocator allocator, usz initial_capacity = 16)
return self;
}
<*
Initialize the list using the temp allocator.
@param initial_capacity : "The initial capacity to reserve"
@param initial_capacity "The initial capacity to reserve"
*>
fn List* List.tinit(&self, usz initial_capacity = 16)
fn List* List.temp_init(&self, usz initial_capacity = 16)
{
return self.init(tmem, initial_capacity) @inline;
return self.new_init(initial_capacity, allocator::temp()) @inline;
}
<*
Initialize a new list with an array.
@param [in] values : `The values to initialize the list with.`
@require self.size == 0 : "The List must be empty"
@param [in] values `The values to initialize the list with.`
@require self.size == 0 "The List must be empty"
*>
fn List* List.init_with_array(&self, Allocator allocator, Type[] values)
fn List* List.new_init_with_array(&self, Type[] values, Allocator allocator = allocator::heap())
{
self.init(allocator, values.len) @inline;
self.new_init(values.len, allocator) @inline;
self.add_array(values) @inline;
return self;
}
@@ -64,20 +59,20 @@ fn List* List.init_with_array(&self, Allocator allocator, Type[] values)
<*
Initialize a temporary list with an array.
@param [in] values : `The values to initialize the list with.`
@require self.size == 0 : "The List must be empty"
@param [in] values `The values to initialize the list with.`
@require self.size == 0 "The List must be empty"
*>
fn List* List.tinit_with_array(&self, Type[] values)
fn List* List.temp_init_with_array(&self, Type[] values)
{
self.tinit(values.len) @inline;
self.temp_init(values.len) @inline;
self.add_array(values) @inline;
return self;
}
<*
@require !self.is_initialized() : "The List must not be allocated"
@require self.capacity == 0 "The List must not be allocated"
*>
fn void List.init_wrapping_array(&self, Allocator allocator, Type[] types)
fn void List.init_wrapping_array(&self, Type[] types, Allocator allocator = allocator::heap())
{
self.allocator = allocator;
self.capacity = types.len;
@@ -85,9 +80,7 @@ fn void List.init_wrapping_array(&self, Allocator allocator, Type[] types)
self.set_size(types.len);
}
fn bool List.is_initialized(&self) @inline => self.allocator != null && self.allocator != (Allocator)&dummy;
fn usz? List.to_format(&self, Formatter* formatter) @dynamic
fn usz! List.to_format(&self, Formatter* formatter) @dynamic
{
switch (self.size)
{
@@ -107,15 +100,25 @@ fn usz? List.to_format(&self, Formatter* formatter) @dynamic
}
}
fn String List.to_new_string(&self, Allocator allocator = allocator::heap()) @dynamic
{
return string::format("%s", *self, allocator: allocator);
}
fn String List.to_tstring(&self)
{
return string::tformat("%s", *self);
}
fn void List.push(&self, Type element) @inline
{
self.reserve(1);
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 IteratorResult.NO_MORE_ELEMENT?;
defer self.set_size(self.size - 1);
return self.entries[self.size - 1];
}
@@ -125,15 +128,15 @@ fn void List.clear(&self)
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 IteratorResult.NO_MORE_ELEMENT?;
defer self.remove_at(0);
return self.entries[0];
}
<*
@require index < self.size : `Removed element out of bounds`
@require index < self.size `Removed element out of bounds`
*>
fn void List.remove_at(&self, usz index)
{
@@ -157,25 +160,25 @@ fn void List.add_all(&self, List* other_list)
<*
IMPORTANT The returned array must be freed using free_aligned.
*>
fn Type[] List.to_aligned_array(&self, Allocator allocator)
fn Type[] List.to_new_aligned_array(&self, Allocator allocator = allocator::heap())
{
return list_common::list_to_aligned_array(Type, self, allocator);
return list_common::list_to_new_aligned_array(Type, self, allocator);
}
<*
@require !type_is_overaligned() : "This function is not available on overaligned types"
*>
macro Type[] List.to_array(&self, Allocator allocator)
macro Type[] List.to_new_array(&self, Allocator allocator = allocator::heap())
{
return list_common::list_to_array(Type, self, allocator);
return list_common::list_to_new_array(Type, self, allocator);
}
fn Type[] List.to_tarray(&self)
{
$if type_is_overaligned():
return self.to_aligned_array(tmem);
return self.to_new_aligned_array(allocator::temp());
$else
return self.to_array(tmem);
return self.to_new_array(allocator::temp());
$endif;
}
@@ -212,16 +215,16 @@ fn void List.push_front(&self, Type type) @inline
}
<*
@require index <= self.size : `Insert was out of bounds`
@require index <= self.size `Insert was out of bounds`
*>
fn void List.insert_at(&self, usz index, Type type)
{
self.reserve(1);
self.set_size(self.size + 1);
for (isz i = self.size - 1; i > index; i--)
for (usz i = self.size; i > index; i--)
{
self.entries[i] = self.entries[i - 1];
}
self.set_size(self.size + 1);
self.entries[index] = type;
}
@@ -233,27 +236,27 @@ fn void List.set_at(&self, usz index, Type type)
self.entries[index] = 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 IteratorResult.NO_MORE_ELEMENT?;
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 IteratorResult.NO_MORE_ELEMENT?;
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 IteratorResult.NO_MORE_ELEMENT?;
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 IteratorResult.NO_MORE_ELEMENT?;
return self.entries[self.size - 1];
}
@@ -273,7 +276,7 @@ fn usz List.len(&self) @operator(len) @inline
}
<*
@require index < self.size : `Access out of bounds`
@require index < self.size `Access out of bounds`
*>
fn Type List.get(&self, usz index) @inline
{
@@ -282,7 +285,7 @@ fn Type List.get(&self, usz index) @inline
fn void List.free(&self)
{
if (!self.allocator || self.allocator.ptr == &dummy || !self.capacity) return;
if (!self.allocator || !self.capacity) return;
self.pre_free(); // Remove sanitizer annotation
@@ -297,7 +300,7 @@ fn void List.free(&self)
}
<*
@require i < self.size && j < self.size : `Access out of bounds`
@require i < self.size && j < self.size `Access out of bounds`
*>
fn void List.swap(&self, usz i, usz j)
{
@@ -305,7 +308,7 @@ fn void List.swap(&self, usz i, usz j)
}
<*
@param filter : "The function to determine if it should be removed or not"
@param filter "The function to determine if it should be removed or not"
@return "the number of deleted elements"
*>
fn usz List.remove_if(&self, ElementPredicate filter)
@@ -314,7 +317,7 @@ fn usz List.remove_if(&self, ElementPredicate filter)
}
<*
@param selection : "The function to determine if it should be kept or not"
@param selection "The function to determine if it should be kept or not"
@return "the number of deleted elements"
*>
fn usz List.retain_if(&self, ElementPredicate selection)
@@ -325,8 +328,7 @@ fn usz List.retain_if(&self, ElementPredicate selection)
fn usz List.remove_using_test(&self, ElementTest filter, any context)
{
usz old_size = self.size;
defer
{
defer {
if (old_size != self.size) self._update_size_change(old_size, self.size);
}
return list_common::list_remove_using_test(self, filter, false, context);
@@ -347,17 +349,7 @@ fn void List.ensure_capacity(&self, usz min_capacity) @local
{
if (!min_capacity) return;
if (self.capacity >= min_capacity) return;
// Get a proper allocator
switch (self.allocator.ptr)
{
case &dummy:
self.allocator = mem;
case null:
self.allocator = tmem;
default:
break;
}
if (!self.allocator) self.allocator = allocator::heap();
self.pre_free(); // Remove sanitizer annotation
@@ -373,7 +365,7 @@ fn void List.ensure_capacity(&self, usz min_capacity) @local
}
<*
@require index < self.size : `Access out of bounds`
@require index < self.size `Access out of bounds`
*>
macro Type List.@item_at(&self, usz index) @operator([])
{
@@ -381,7 +373,7 @@ macro Type List.@item_at(&self, usz index) @operator([])
}
<*
@require index < self.size : `Access out of bounds`
@require index < self.size `Access out of bounds`
*>
fn Type* List.get_ref(&self, usz index) @operator(&[]) @inline
{
@@ -389,7 +381,7 @@ fn Type* List.get_ref(&self, usz index) @operator(&[]) @inline
}
<*
@require index < self.size : `Access out of bounds`
@require index < self.size `Access out of bounds`
*>
fn void List.set(&self, usz index, Type value) @operator([]=)
{
@@ -410,13 +402,10 @@ fn void List.reserve(&self, usz added)
fn void List._update_size_change(&self,usz old_size, usz new_size)
{
if (old_size == new_size) return;
$if env::ADDRESS_SANITIZER:
if (self.allocator.ptr != &allocator::LIBC_ALLOCATOR) return;
sanitizer::annotate_contiguous_container(self.entries,
sanitizer::annotate_contiguous_container(self.entries,
&self.entries[self.capacity],
&self.entries[old_size],
&self.entries[new_size]);
$endif
}
<*
@require new_size == 0 || self.capacity != 0
@@ -436,7 +425,7 @@ macro void List.pre_free(&self) @private
}
<*
@require self.capacity > 0
@require self.capacity
*>
macro void List.post_alloc(&self) @private
{
@@ -446,22 +435,22 @@ macro void List.post_alloc(&self) @private
// 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)
{
if (equals(v, type)) return i;
}
return NOT_FOUND?;
return SearchResult.MISSING?;
}
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)
{
if (equals(v, type)) return i;
}
return NOT_FOUND?;
return SearchResult.MISSING?;
}
fn bool List.equals(&self, List other_list) @if(ELEMENT_IS_EQUATABLE)
@@ -477,8 +466,8 @@ fn bool List.equals(&self, List other_list) @if(ELEMENT_IS_EQUATABLE)
<*
Check for presence of a value in a list.
@param [&in] self : "the list to find elements in"
@param value : "The value to search for"
@param [&in] self "the list to find elements in"
@param value "The value to search for"
@return "True if the value is found, false otherwise"
*>
fn bool List.contains(&self, Type value) @if(ELEMENT_IS_EQUATABLE)
@@ -491,8 +480,8 @@ fn bool List.contains(&self, Type value) @if(ELEMENT_IS_EQUATABLE)
}
<*
@param [&inout] self : "The list to remove elements from"
@param value : "The value to remove"
@param [&inout] self "The list to remove elements from"
@param value "The value to remove"
@return "true if the value was found"
*>
fn bool List.remove_last_item(&self, Type value) @if(ELEMENT_IS_EQUATABLE)
@@ -501,8 +490,8 @@ fn bool List.remove_last_item(&self, Type value) @if(ELEMENT_IS_EQUATABLE)
}
<*
@param [&inout] self : "The list to remove elements from"
@param value : "The value to remove"
@param [&inout] self "The list to remove elements from"
@param value "The value to remove"
@return "true if the value was found"
*>
fn bool List.remove_first_item(&self, Type value) @if(ELEMENT_IS_EQUATABLE)
@@ -510,8 +499,8 @@ fn bool List.remove_first_item(&self, Type value) @if(ELEMENT_IS_EQUATABLE)
return @ok(self.remove_at(self.index_of(value)));
}
<*
@param [&inout] self : "The list to remove elements from"
@param value : "The value to remove"
@param [&inout] self "The list to remove elements from"
@param value "The value to remove"
@return "the number of deleted elements."
*>
fn usz List.remove_item(&self, Type value) @if(ELEMENT_IS_EQUATABLE)
@@ -555,4 +544,35 @@ fn usz List.compact(&self) @if(ELEMENT_IS_POINTER)
return list_common::list_compact(self);
}
int dummy @local;
// --> Deprecated
<*
@param [&inout] self "The list to remove elements from"
@param value "The value to remove"
@return "true if the value was found"
*>
fn bool List.remove_last_match(&self, Type value) @if(ELEMENT_IS_EQUATABLE) @deprecated
{
return self.remove_last_item(value) @inline;
}
<*
@param [&inout] self "The list to remove elements from"
@param value "The value to remove"
@return "true if the value was found"
*>
fn bool List.remove_first_match(&self, Type value) @if(ELEMENT_IS_EQUATABLE) @deprecated
{
return self.remove_first_item(value) @inline;
}
<*
@param [&inout] self "The list to remove elements from"
@param value "The value to remove"
@return "the number of deleted elements."
*>
fn usz List.remove_all_matches(&self, Type value) @if(ELEMENT_IS_EQUATABLE) @deprecated
{
return self.remove_item(value) @inline;
}

View File

@@ -3,17 +3,17 @@ module std::collections::list_common;
<*
IMPORTANT The returned array must be freed using free_aligned.
*>
macro list_to_aligned_array($Type, self, Allocator allocator)
macro list_to_new_aligned_array($Type, self, Allocator allocator)
{
if (!self.size) return ($Type[]){};
if (!self.size) return $Type[] {};
$Type[] result = allocator::alloc_array_aligned(allocator, $Type, self.size);
result[..] = self.entries[:self.size];
return result;
}
macro list_to_array($Type, self, Allocator allocator)
macro list_to_new_array($Type, self, Allocator allocator)
{
if (!self.size) return ($Type[]){};
if (!self.size) return $Type[] {};
$Type[] result = allocator::alloc_array(allocator, $Type, self.size);
result[..] = self.entries[:self.size];
return result;

508
lib/std/collections/map.c3 Normal file
View File

@@ -0,0 +1,508 @@
// Copyright (c) 2023 Christoffer Lerno. All rights reserved.
// Use of this source code is governed by the MIT license
// a copy of which can be found in the LICENSE_STDLIB file.
module std::collections::map(<Key, Value>);
import std::math;
const uint DEFAULT_INITIAL_CAPACITY = 16;
const uint MAXIMUM_CAPACITY = 1u << 31;
const float DEFAULT_LOAD_FACTOR = 0.75;
const VALUE_IS_EQUATABLE = Value.is_eq;
const bool COPY_KEYS = types::implements_copy(Key);
distinct Map = void*;
struct MapImpl
{
Entry*[] table;
Allocator allocator;
uint count; // Number of elements
uint threshold; // Resize limit
float load_factor;
}
<*
@require capacity > 0 "The capacity must be 1 or higher"
@require load_factor > 0.0 "The load factor must be higher than 0"
@require capacity < MAXIMUM_CAPACITY "Capacity cannot exceed maximum"
*>
fn Map new(uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR, Allocator allocator = allocator::heap())
{
MapImpl* map = allocator::alloc(allocator, MapImpl);
_init(map, capacity, load_factor, allocator);
return (Map)map;
}
<*
@require capacity > 0 "The capacity must be 1 or higher"
@require load_factor > 0.0 "The load factor must be higher than 0"
@require capacity < MAXIMUM_CAPACITY "Capacity cannot exceed maximum"
*>
fn Map temp(uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
{
MapImpl* map = mem::temp_alloc(MapImpl);
_init(map, capacity, load_factor, allocator::temp());
return (Map)map;
}
<*
@param [&inout] allocator "The allocator to use"
@require $vacount % 2 == 0 "There must be an even number of arguments provided for keys and values"
@require capacity > 0 "The capacity must be 1 or higher"
@require load_factor > 0.0 "The load factor must be higher than 0"
@require capacity < MAXIMUM_CAPACITY "Capacity cannot exceed maximum"
*>
macro Map new_init_with_key_values(..., uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR, Allocator allocator = allocator::heap())
{
Map map = new(capacity, load_factor, allocator);
$for (var $i = 0; $i < $vacount; $i += 2)
map.set($vaarg[$i], $vaarg[$i+1]);
$endfor
return map;
}
<*
@param [in] keys "Array of keys for the Map entries"
@param [in] values "Array of values for the Map entries"
@param [&inout] allocator "The allocator to use"
@require keys.len == values.len "Both keys and values arrays must be the same length"
@require capacity > 0 "The capacity must be 1 or higher"
@require load_factor > 0.0 "The load factor must be higher than 0"
@require capacity < MAXIMUM_CAPACITY "Capacity cannot exceed maximum"
*>
fn Map new_init_from_keys_and_values(Key[] keys, Value[] values, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR, Allocator allocator = allocator::heap())
{
assert(keys.len == values.len);
Map map = new(capacity, load_factor, allocator);
for (usz i = 0; i < keys.len; i++)
{
map.set(keys[i], values[i]);
}
return map;
}
<*
@require $vacount % 2 == 0 "There must be an even number of arguments provided for keys and values"
@require capacity > 0 "The capacity must be 1 or higher"
@require load_factor > 0.0 "The load factor must be higher than 0"
@require capacity < MAXIMUM_CAPACITY "Capacity cannot exceed maximum"
*>
macro Map temp_new_with_key_values(..., uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
{
Map map = temp(capacity, load_factor);
$for (var $i = 0; $i < $vacount; $i += 2)
map.set($vaarg[$i], $vaarg[$i+1]);
$endfor
return map;
}
<*
@param [in] keys "The keys for the HashMap entries"
@param [in] values "The values for the HashMap entries"
@param [&inout] allocator "The allocator to use"
@require keys.len == values.len "Both keys and values arrays must be the same length"
@require capacity > 0 "The capacity must be 1 or higher"
@require load_factor > 0.0 "The load factor must be higher than 0"
@require capacity < MAXIMUM_CAPACITY "Capacity cannot exceed maximum"
*>
fn Map temp_init_from_keys_and_values(Key[] keys, Value[] values, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR, Allocator allocator = allocator::heap())
{
assert(keys.len == values.len);
Map map = temp(capacity, load_factor);
for (usz i = 0; i < keys.len; i++)
{
map.set(keys[i], values[i]);
}
return map;
}
<*
@param [&in] other_map "The map to copy from."
*>
fn Map new_from_map(Map other_map, Allocator allocator = null)
{
MapImpl* other_map_impl = (MapImpl*)other_map;
if (!other_map_impl)
{
if (allocator) return new(allocator: allocator);
return null;
}
MapImpl* map = (MapImpl*)new(other_map_impl.table.len, other_map_impl.load_factor, allocator ?: allocator::heap());
if (!other_map_impl.count) return (Map)map;
foreach (Entry *e : other_map_impl.table)
{
while (e)
{
map._put_for_create(e.key, e.value);
e = e.next;
}
}
return (Map)map;
}
<*
@param [&in] other_map "The map to copy from."
*>
fn Map temp_from_map(Map other_map)
{
return new_from_map(other_map, allocator::temp());
}
fn bool Map.is_empty(map) @inline
{
return !map || !((MapImpl*)map).count;
}
fn usz Map.len(map) @inline
{
return map ? ((MapImpl*)map).count : 0;
}
fn Value*! Map.get_ref(self, Key key)
{
MapImpl *map = (MapImpl*)self;
if (!map || !map.count) return SearchResult.MISSING?;
uint hash = rehash(key.hash());
for (Entry *e = map.table[index_for(hash, map.table.len)]; e != null; e = e.next)
{
if (e.hash == hash && equals(key, e.key)) return &e.value;
}
return SearchResult.MISSING?;
}
fn Entry*! Map.get_entry(map, Key key)
{
MapImpl *map_impl = (MapImpl*)map;
if (!map_impl || !map_impl.count) return SearchResult.MISSING?;
uint hash = rehash(key.hash());
for (Entry *e = map_impl.table[index_for(hash, map_impl.table.len)]; e != null; e = e.next)
{
if (e.hash == hash && equals(key, e.key)) return e;
}
return SearchResult.MISSING?;
}
<*
Get the value or update and
@require $assignable(#expr, Value)
*>
macro Value Map.@get_or_set(&self, Key key, Value #expr)
{
MapImpl *map = (MapImpl*)*self;
if (!map || !map.count)
{
Value val = #expr;
map.set(key, val);
return val;
}
uint hash = rehash(key.hash());
uint index = index_for(hash, map.table.len);
for (Entry *e = map.table[index]; e != null; e = e.next)
{
if (e.hash == hash && equals(key, e.key)) return e.value;
}
Value val = #expr;
map.add_entry(hash, key, val, index);
return val;
}
fn Value! Map.get(map, Key key) @operator([])
{
return *map.get_ref(key) @inline;
}
fn bool Map.has_key(map, Key key)
{
return @ok(map.get_ref(key));
}
macro Value Map.set_value_return(&map, Key key, Value value) @operator([]=)
{
map.set(key, value);
return value;
}
fn bool Map.set(&self, Key key, Value value)
{
// If the map isn't initialized, use the defaults to initialize it.
if (!*self) *self = new();
MapImpl* map = (MapImpl*)*self;
uint hash = rehash(key.hash());
uint index = index_for(hash, map.table.len);
for (Entry *e = map.table[index]; e != null; e = e.next)
{
if (e.hash == hash && equals(key, e.key))
{
e.value = value;
return true;
}
}
map._add_entry(hash, key, value, index);
return false;
}
fn void! Map.remove(map, Key key) @maydiscard
{
if (!map || !((MapImpl*)map)._remove_entry_for_key(key)) return SearchResult.MISSING?;
}
fn void Map.clear(self)
{
MapImpl* map = (MapImpl*)self;
if (!map || !map.count) return;
foreach (Entry** &entry_ref : map.table)
{
Entry* entry = *entry_ref;
if (!entry) continue;
Entry *next = entry.next;
while (next)
{
Entry *to_delete = next;
next = next.next;
map._free_entry(to_delete);
}
map._free_entry(entry);
*entry_ref = null;
}
map.count = 0;
}
fn void Map.free(self)
{
if (!self) return;
MapImpl* map = (MapImpl*)self;
self.clear();
map._free_internal(map.table.ptr);
map.table = {};
allocator::free(map.allocator, map);
}
fn Key[] Map.temp_keys_list(map)
{
return map.new_keys_list(allocator::temp()) @inline;
}
fn Key[] Map.new_keys_list(self, Allocator allocator = allocator::heap())
{
MapImpl* map = (MapImpl*)self;
if (!map || !map.count) return {};
Key[] list = allocator::alloc_array(allocator, Key, map.count);
usz index = 0;
foreach (Entry* entry : map.table)
{
while (entry)
{
list[index++] = entry.key;
entry = entry.next;
}
}
return list;
}
macro Map.@each(map; @body(key, value))
{
map.@each_entry(; Entry* entry) {
@body(entry.key, entry.value);
};
}
macro Map.@each_entry(self; @body(entry))
{
MapImpl *map = (MapImpl*)self;
if (!map || !map.count) return;
foreach (Entry* entry : map.table)
{
while (entry)
{
@body(entry);
entry = entry.next;
}
}
}
fn Value[] Map.temp_values_list(map)
{
return map.new_values_list(allocator::temp()) @inline;
}
fn Value[] Map.new_values_list(self, Allocator allocator = allocator::heap())
{
MapImpl* map = (MapImpl*)self;
if (!map || !map.count) return {};
Value[] list = allocator::alloc_array(allocator, Value, map.count);
usz index = 0;
foreach (Entry* entry : map.table)
{
while (entry)
{
list[index++] = entry.value;
entry = entry.next;
}
}
return list;
}
fn bool Map.has_value(self, Value v) @if(VALUE_IS_EQUATABLE)
{
MapImpl* map = (MapImpl*)self;
if (!map || !map.count) return false;
foreach (Entry* entry : map.table)
{
while (entry)
{
if (equals(v, entry.value)) return true;
entry = entry.next;
}
}
return false;
}
// --- private methods
fn void MapImpl._add_entry(&map, uint hash, Key key, Value value, uint bucket_index) @private
{
$if COPY_KEYS:
key = key.copy(map.allocator);
$endif
Entry* entry = allocator::new(map.allocator, Entry, { .hash = hash, .key = key, .value = value, .next = map.table[bucket_index] });
map.table[bucket_index] = entry;
if (map.count++ >= map.threshold)
{
map._resize(map.table.len * 2);
}
}
fn void MapImpl._resize(&map, uint new_capacity) @private
{
Entry*[] old_table = map.table;
uint old_capacity = old_table.len;
if (old_capacity == MAXIMUM_CAPACITY)
{
map.threshold = uint.max;
return;
}
Entry*[] new_table = allocator::new_array(map.allocator, Entry*, new_capacity);
map._transfer(new_table);
map.table = new_table;
map._free_internal(old_table.ptr);
map.threshold = (uint)(new_capacity * map.load_factor);
}
fn uint rehash(uint hash) @inline @private
{
hash ^= (hash >> 20) ^ (hash >> 12);
return hash ^ ((hash >> 7) ^ (hash >> 4));
}
macro uint index_for(uint hash, uint capacity) @private
{
return hash & (capacity - 1);
}
fn void MapImpl._transfer(&map, Entry*[] new_table) @private
{
Entry*[] src = map.table;
uint new_capacity = new_table.len;
foreach (uint j, Entry *e : src)
{
if (!e) continue;
do
{
Entry* next = e.next;
uint i = index_for(e.hash, new_capacity);
e.next = new_table[i];
new_table[i] = e;
e = next;
}
while (e);
}
}
fn void _init(MapImpl* impl, uint capacity, float load_factor, Allocator allocator) @private
{
capacity = math::next_power_of_2(capacity);
*impl = {
.allocator = allocator,
.load_factor = load_factor,
.threshold = (uint)(capacity * load_factor),
.table = allocator::new_array(allocator, Entry*, capacity)
};
}
fn void MapImpl._put_for_create(&map, Key key, Value value) @private
{
uint hash = rehash(key.hash());
uint i = index_for(hash, map.table.len);
for (Entry *e = map.table[i]; e != null; e = e.next)
{
if (e.hash == hash && equals(key, e.key))
{
e.value = value;
return;
}
}
map._create_entry(hash, key, value, i);
}
fn void MapImpl._free_internal(&map, void* ptr) @inline @private
{
allocator::free(map.allocator, ptr);
}
fn bool MapImpl._remove_entry_for_key(&map, Key key) @private
{
if (!map.count) return false;
uint hash = rehash(key.hash());
uint i = index_for(hash, map.table.len);
Entry* prev = map.table[i];
Entry* e = prev;
while (e)
{
Entry *next = e.next;
if (e.hash == hash && equals(key, e.key))
{
map.count--;
if (prev == e)
{
map.table[i] = next;
}
else
{
prev.next = next;
}
map._free_entry(e);
return true;
}
prev = e;
e = next;
}
return false;
}
fn void MapImpl._create_entry(&map, uint hash, Key key, Value value, int bucket_index) @private
{
Entry *e = map.table[bucket_index];
$if COPY_KEYS:
key = key.copy(map.allocator);
$endif
Entry* entry = allocator::new(map.allocator, Entry, { .hash = hash, .key = key, .value = value, .next = map.table[bucket_index] });
map.table[bucket_index] = entry;
map.count++;
}
fn void MapImpl._free_entry(&self, Entry *entry) @local
{
$if COPY_KEYS:
allocator::free(self.allocator, entry.key);
$endif
self._free_internal(entry);
}
struct Entry
{
uint hash;
Key key;
Value value;
Entry* next;
}

View File

@@ -1,28 +1,11 @@
module std::collections::maybe{Type};
import std::io;
module std::collections::maybe(<Type>);
struct Maybe (Printable)
struct Maybe
{
Type value;
bool has_value;
}
fn usz? Maybe.to_format(&self, Formatter* f) @dynamic
{
if (self.has_value) return f.printf("[%s]", self.value);
return f.printf("[EMPTY]");
}
fn void Maybe.set(&self, Type val)
{
*self = { .value = val, .has_value = true };
}
fn void Maybe.reset(&self)
{
*self = {};
}
fn Maybe value(Type val)
{
return { .value = val, .has_value = true };
@@ -30,7 +13,7 @@ fn Maybe value(Type val)
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 : SearchResult.MISSING?;
}

View File

@@ -25,7 +25,7 @@ struct Object (Printable)
}
fn usz? Object.to_format(&self, Formatter* formatter) @dynamic
fn usz! Object.to_format(&self, Formatter* formatter) @dynamic
{
switch (self.type)
{
@@ -50,7 +50,7 @@ fn usz? Object.to_format(&self, Formatter* formatter) @dynamic
usz n = formatter.printf("{")!;
@stack_mem(1024; Allocator mem)
{
foreach (i, key : self.map.keys(mem))
foreach (i, key : self.map.copy_keys(mem))
{
if (i > 0) n += formatter.printf(",")!;
n += formatter.printf(`"%s":`, key)!;
@@ -156,7 +156,7 @@ fn void Object.init_map_if_needed(&self) @private
if (self.is_empty())
{
self.type = ObjectInternalMap.typeid;
self.map.init(self.allocator);
self.map.new_init(allocator: self.allocator);
}
}
@@ -168,7 +168,7 @@ fn void Object.init_array_if_needed(&self) @private
if (self.is_empty())
{
self.type = ObjectInternalList.typeid;
self.array.init(self.allocator);
self.array.new_init(allocator: self.allocator);
}
}
@@ -178,7 +178,7 @@ fn void Object.init_array_if_needed(&self) @private
fn void Object.set_object(&self, String key, Object* new_object) @private
{
self.init_map_if_needed();
ObjectInternalMapEntry*? entry = self.map.get_entry(key);
ObjectInternalMapEntry*! entry = self.map.get_entry(key);
defer
{
(void)entry.value.free();
@@ -189,7 +189,7 @@ fn void Object.set_object(&self, String key, Object* new_object) @private
macro Object* Object.object_from_value(&self, value) @private
{
var $Type = $typeof(value);
$switch:
$switch
$case types::is_int($Type):
return new_int(value, self.allocator);
$case types::is_float($Type):
@@ -201,7 +201,7 @@ macro Object* Object.object_from_value(&self, value) @private
$case $Type.typeid == Object*.typeid:
return value;
$case $Type.typeid == void*.typeid:
if (value != null) return TYPE_MISMATCH?;
if (value != null) return CastResult.TYPE_MISMATCH?;
return &NULL_OBJECT;
$case $assignable(value, String):
return new_string(value, self.allocator);
@@ -242,7 +242,7 @@ macro Object* Object.push(&self, value)
<*
@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() ? SearchResult.MISSING? : self.map.get(key);
fn bool Object.has_key(&self, String key) => self.is_map() && self.map.has_key(key);
@@ -292,7 +292,7 @@ fn void Object.set_object_at(&self, usz index, Object* to_set)
}
<*
@require $Type.kindof.is_int() : "Expected an integer type."
@require $Type.kindof.is_int() "Expected an integer type."
*>
macro get_integer_value(Object* value, $Type)
{
@@ -308,7 +308,7 @@ macro get_integer_value(Object* value, $Type)
return ($Type)value.s.to_uint128();
$endif
}
if (!value.is_int()) return string::MALFORMED_INTEGER?;
if (!value.is_int()) return NumberConversion.MALFORMED_INTEGER?;
return ($Type)value.i;
}
@@ -331,77 +331,77 @@ macro Object.get_integer(&self, $Type, String key) @private
return get_integer_value(self.get(key), $Type);
}
fn ichar? Object.get_ichar(&self, String key) => self.get_integer(ichar, key);
fn short? Object.get_short(&self, String key) => self.get_integer(short, key);
fn int? Object.get_int(&self, String key) => self.get_integer(int, key);
fn long? Object.get_long(&self, String key) => self.get_integer(long, key);
fn int128? Object.get_int128(&self, String key) => self.get_integer(int128, key);
fn ichar! Object.get_ichar(&self, String key) => self.get_integer(ichar, key);
fn short! Object.get_short(&self, String key) => self.get_integer(short, key);
fn int! Object.get_int(&self, String key) => self.get_integer(int, key);
fn long! Object.get_long(&self, String key) => self.get_integer(long, key);
fn int128! Object.get_int128(&self, String key) => self.get_integer(int128, key);
fn ichar? Object.get_ichar_at(&self, usz index) => self.get_integer_at(ichar, index);
fn short? Object.get_short_at(&self, usz index) => self.get_integer_at(short, index);
fn int? Object.get_int_at(&self, usz index) => self.get_integer_at(int, index);
fn long? Object.get_long_at(&self, usz index) => self.get_integer_at(long, index);
fn int128? Object.get_int128_at(&self, usz index) => self.get_integer_at(int128, index);
fn ichar! Object.get_ichar_at(&self, usz index) => self.get_integer_at(ichar, index);
fn short! Object.get_short_at(&self, usz index) => self.get_integer_at(short, index);
fn int! Object.get_int_at(&self, usz index) => self.get_integer_at(int, index);
fn long! Object.get_long_at(&self, usz index) => self.get_integer_at(long, index);
fn int128! Object.get_int128_at(&self, usz index) => self.get_integer_at(int128, index);
fn char? Object.get_char(&self, String key) => self.get_integer(ichar, key);
fn short? Object.get_ushort(&self, String key) => self.get_integer(ushort, key);
fn uint? Object.get_uint(&self, String key) => self.get_integer(uint, key);
fn ulong? Object.get_ulong(&self, String key) => self.get_integer(ulong, key);
fn uint128? Object.get_uint128(&self, String key) => self.get_integer(uint128, key);
fn char! Object.get_char(&self, String key) => self.get_integer(ichar, key);
fn short! Object.get_ushort(&self, String key) => self.get_integer(ushort, key);
fn uint! Object.get_uint(&self, String key) => self.get_integer(uint, key);
fn ulong! Object.get_ulong(&self, String key) => self.get_integer(ulong, key);
fn uint128! Object.get_uint128(&self, String key) => self.get_integer(uint128, key);
fn char? Object.get_char_at(&self, usz index) => self.get_integer_at(char, index);
fn ushort? Object.get_ushort_at(&self, usz index) => self.get_integer_at(ushort, index);
fn uint? Object.get_uint_at(&self, usz index) => self.get_integer_at(uint, index);
fn ulong? Object.get_ulong_at(&self, usz index) => self.get_integer_at(ulong, index);
fn uint128? Object.get_uint128_at(&self, usz index) => self.get_integer_at(uint128, index);
fn char! Object.get_char_at(&self, usz index) => self.get_integer_at(char, index);
fn ushort! Object.get_ushort_at(&self, usz index) => self.get_integer_at(ushort, index);
fn uint! Object.get_uint_at(&self, usz index) => self.get_integer_at(uint, index);
fn ulong! Object.get_ulong_at(&self, usz index) => self.get_integer_at(ulong, index);
fn uint128! Object.get_uint128_at(&self, usz index) => self.get_integer_at(uint128, index);
<*
@require self.is_keyable()
*>
fn String? Object.get_string(&self, String key)
fn String! Object.get_string(&self, String key)
{
Object* value = self.get(key)!;
if (!value.is_string()) return TYPE_MISMATCH?;
if (!value.is_string()) return CastResult.TYPE_MISMATCH?;
return value.s;
}
<*
@require self.is_indexable()
*>
fn String? Object.get_string_at(&self, usz index)
fn String! Object.get_string_at(&self, usz index)
{
Object* value = self.get_at(index);
if (!value.is_string()) return TYPE_MISMATCH?;
if (!value.is_string()) return CastResult.TYPE_MISMATCH?;
return value.s;
}
<*
@require self.is_keyable()
*>
macro String? Object.get_enum(&self, $EnumType, String key)
macro String! Object.get_enum(&self, $EnumType, String key)
{
Object value = self.get(key)!;
if ($EnumType.typeid != value.type) return TYPE_MISMATCH?;
if ($EnumType.typeid != value.type) return CastResult.TYPE_MISMATCH?;
return ($EnumType)value.i;
}
<*
@require self.is_indexable()
*>
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);
if ($EnumType.typeid != value.type) return TYPE_MISMATCH?;
if ($EnumType.typeid != value.type) return CastResult.TYPE_MISMATCH?;
return ($EnumType)value.i;
}
<*
@require self.is_keyable()
*>
fn bool? Object.get_bool(&self, String key)
fn bool! Object.get_bool(&self, String key)
{
Object* value = self.get(key)!;
if (!value.is_bool()) return TYPE_MISMATCH?;
if (!value.is_bool()) return CastResult.TYPE_MISMATCH?;
return value.b;
}
@@ -409,17 +409,17 @@ fn bool? Object.get_bool(&self, String key)
<*
@require self.is_indexable()
*>
fn bool? Object.get_bool_at(&self, usz index)
fn bool! Object.get_bool_at(&self, usz index)
{
Object* value = self.get_at(index);
if (!value.is_bool()) return TYPE_MISMATCH?;
if (!value.is_bool()) return CastResult.TYPE_MISMATCH?;
return value.b;
}
<*
@require self.is_keyable()
*>
fn double? Object.get_float(&self, String key)
fn double! Object.get_float(&self, String key)
{
Object* value = self.get(key)!;
switch (value.type.kindof)
@@ -431,14 +431,14 @@ fn double? Object.get_float(&self, String key)
case FLOAT:
return value.f;
default:
return TYPE_MISMATCH?;
return CastResult.TYPE_MISMATCH?;
}
}
<*
@require self.is_indexable()
*>
fn double? Object.get_float_at(&self, usz index)
fn double! Object.get_float_at(&self, usz index)
{
Object* value = self.get_at(index);
switch (value.type.kindof)
@@ -450,7 +450,7 @@ fn double? Object.get_float_at(&self, usz index)
case FLOAT:
return value.f;
default:
return TYPE_MISMATCH?;
return CastResult.TYPE_MISMATCH?;
}
}
@@ -462,7 +462,7 @@ fn Object* Object.get_or_create_obj(&self, String key)
return container;
}
alias ObjectInternalMap @private = HashMap {String, Object*};
alias ObjectInternalList @private = List {Object*};
alias ObjectInternalMapEntry @private = Entry {String, Object*};
def ObjectInternalMap = HashMap(<String, Object*>) @private;
def ObjectInternalList = List(<Object*>) @private;
def ObjectInternalMapEntry = Entry(<String, Object*>) @private;

View File

@@ -1,7 +1,7 @@
// priorityqueue.c3
// A priority queue using a classic binary heap for C3.
//
// Copyright (c) 2022-2025 David Kopec
// Copyright (c) 2022 David Kopec
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
@@ -20,30 +20,30 @@
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
module std::collections::priorityqueue{Type};
module std::collections::priorityqueue(<Type>);
import std::collections::priorityqueue::private;
typedef PriorityQueue = inline PrivatePriorityQueue{Type, false};
typedef PriorityQueueMax = inline PrivatePriorityQueue{Type, true};
distinct PriorityQueue = inline PrivatePriorityQueue(<Type, false>);
distinct PriorityQueueMax = inline PrivatePriorityQueue(<Type, true>);
module std::collections::priorityqueue::private{Type, MAX};
module std::collections::priorityqueue::private(<Type, MAX>);
import std::collections::list, std::io;
def Heap = List(<Type>);
struct PrivatePriorityQueue (Printable)
{
List{Type} heap;
Heap heap;
}
fn PrivatePriorityQueue* PrivatePriorityQueue.init(&self, Allocator allocator, usz initial_capacity = 16, ) @inline
fn void PrivatePriorityQueue.new_init(&self, usz initial_capacity = 16, Allocator allocator = allocator::heap()) @inline
{
self.heap.init(allocator, initial_capacity);
return self;
self.heap.new_init(initial_capacity, allocator);
}
fn PrivatePriorityQueue* PrivatePriorityQueue.tinit(&self, usz initial_capacity = 16) @inline
fn void PrivatePriorityQueue.temp_init(&self, usz initial_capacity = 16) @inline
{
self.init(tmem, initial_capacity);
return self;
self.heap.new_init(initial_capacity, allocator::temp()) @inline;
}
@@ -67,9 +67,6 @@ fn void PrivatePriorityQueue.push(&self, Type element)
}
}
<*
@require index < self.len() : "Index out of range"
*>
fn void PrivatePriorityQueue.remove_at(&self, usz index)
{
if (index == 0)
@@ -82,11 +79,11 @@ fn void PrivatePriorityQueue.remove_at(&self, usz index)
<*
@require self != null
*>
fn Type? PrivatePriorityQueue.pop(&self)
fn Type! PrivatePriorityQueue.pop(&self)
{
usz i = 0;
usz len = self.heap.len();
if (!len) return NO_MORE_ELEMENT?;
if (!len) return IteratorResult.NO_MORE_ELEMENT?;
usz new_count = len - 1;
self.heap.swap(0, new_count);
while OUTER: ((2 * i + 1) < new_count)
@@ -120,9 +117,10 @@ fn Type? PrivatePriorityQueue.pop(&self)
return self.heap.pop();
}
fn Type? PrivatePriorityQueue.first(&self)
fn Type! PrivatePriorityQueue.first(&self)
{
return self.heap.first();
if (!self.len()) return IteratorResult.NO_MORE_ELEMENT?;
return self.heap.get(0);
}
fn void PrivatePriorityQueue.free(&self)
@@ -132,12 +130,12 @@ fn void PrivatePriorityQueue.free(&self)
fn usz PrivatePriorityQueue.len(&self) @operator(len)
{
return self.heap.len() @inline;
return self.heap.len();
}
fn bool PrivatePriorityQueue.is_empty(&self)
{
return self.heap.is_empty() @inline;
return self.heap.is_empty();
}
<*
@@ -148,8 +146,13 @@ fn Type PrivatePriorityQueue.get(&self, usz index) @operator([])
return self.heap[index];
}
fn usz? PrivatePriorityQueue.to_format(&self, Formatter* formatter) @dynamic
fn usz! PrivatePriorityQueue.to_format(&self, Formatter* formatter) @dynamic
{
return self.heap.to_format(formatter);
}
fn String PrivatePriorityQueue.to_new_string(&self, Allocator allocator = allocator::heap()) @dynamic
{
return self.heap.to_new_string(allocator);
}

View File

@@ -1,7 +1,7 @@
<*
@require Type.is_ordered : "The type must be ordered"
*>
module std::collections::range{Type};
module std::collections::range(<Type>);
import std::io;
struct Range (Printable)
@@ -29,7 +29,22 @@ fn Type Range.get(&self, usz index) @operator([])
return (Type)(self.start + (usz)index);
}
fn usz? Range.to_format(&self, Formatter* formatter) @dynamic
fn String Range.to_new_string(&self, Allocator allocator = allocator::heap()) @dynamic @deprecated
{
return string::format("[%s..%s]", self.start, self.end, allocator: allocator);
}
fn String Range.to_string(&self, Allocator allocator) @dynamic
{
return string::format("[%s..%s]", self.start, self.end, allocator: allocator);
}
fn String Range.to_tstring(&self)
{
return self.to_string(allocator::temp());
}
fn usz! Range.to_format(&self, Formatter* formatter) @dynamic
{
return formatter.printf("[%s..%s]", self.start, self.end)!;
}
@@ -51,11 +66,26 @@ fn bool ExclusiveRange.contains(&self, Type value) @inline
return value >= self.start && value < self.end;
}
fn usz? ExclusiveRange.to_format(&self, Formatter* formatter) @dynamic
fn usz! ExclusiveRange.to_format(&self, Formatter* formatter) @dynamic
{
return formatter.printf("[%s..<%s]", self.start, self.end)!;
}
fn String ExclusiveRange.to_new_string(&self, Allocator allocator = null) @dynamic
{
return self.to_string(allocator ?: allocator::heap());
}
fn String ExclusiveRange.to_string(&self, Allocator allocator) @dynamic
{
return string::format("[%s..<%s]", self.start, self.end, allocator: allocator);
}
fn String ExclusiveRange.to_tstring(&self)
{
return self.to_new_string(allocator::temp());
}
<*
@require index < self.len() : "Can't index into an empty range"
*>

View File

@@ -1,14 +1,11 @@
<*
@require Type.kindof == ARRAY : "Required an array type"
@require values::@is_int(SIZE) &&& SIZE > 0 "The size must be positive integer"
*>
module std::collections::ringbuffer{Type};
import std::io;
module std::collections::ringbuffer(<Type, SIZE>);
alias Element = $typeof((Type){}[0]);
struct RingBuffer (Printable)
struct RingBuffer
{
Type buf;
Type[SIZE] buf;
usz written;
usz head;
}
@@ -18,9 +15,9 @@ fn void RingBuffer.init(&self) @inline
*self = {};
}
fn void RingBuffer.push(&self, Element c)
fn void RingBuffer.push(&self, Type c)
{
if (self.written < self.buf.len)
if (self.written < SIZE)
{
self.buf[self.written] = c;
self.written++;
@@ -28,14 +25,14 @@ fn void RingBuffer.push(&self, Element c)
else
{
self.buf[self.head] = c;
self.head = (self.head + 1) % self.buf.len;
self.head = (self.head + 1) % SIZE;
}
}
fn Element RingBuffer.get(&self, usz index) @operator([])
fn Type RingBuffer.get(&self, usz index) @operator([])
{
index %= self.buf.len;
usz avail = self.buf.len - self.head;
index %= SIZE;
usz avail = SIZE - self.head;
if (index < avail)
{
return self.buf[self.head + index];
@@ -43,31 +40,25 @@ fn Element RingBuffer.get(&self, usz index) @operator([])
return self.buf[index - avail];
}
fn Element? RingBuffer.pop(&self)
fn Type! RingBuffer.pop(&self)
{
switch
{
case self.written == 0:
return NO_MORE_ELEMENT?;
case self.written < self.buf.len:
return SearchResult.MISSING?;
case self.written < SIZE:
self.written--;
return self.buf[self.written];
default:
self.head = (self.head - 1) % self.buf.len;
self.head = (self.head - 1) % SIZE;
return self.buf[self.head];
}
}
fn usz? RingBuffer.to_format(&self, Formatter* format) @dynamic
fn usz RingBuffer.read(&self, usz index, Type[] buffer)
{
// Improve this?
return format.printf("%s", self.buf);
}
fn usz RingBuffer.read(&self, usz index, Element[] buffer)
{
index %= self.buf.len;
if (self.written < self.buf.len)
index %= SIZE;
if (self.written < SIZE)
{
if (index >= self.written) return 0;
usz end = self.written - index;
@@ -75,7 +66,7 @@ fn usz RingBuffer.read(&self, usz index, Element[] buffer)
buffer[:n] = self.buf[index:n];
return n;
}
usz end = self.buf.len - self.head;
usz end = SIZE - self.head;
if (index >= end)
{
index -= end;
@@ -84,13 +75,13 @@ fn usz RingBuffer.read(&self, usz index, Element[] buffer)
buffer[:n] = self.buf[index:n];
return n;
}
if (buffer.len <= self.buf.len - index)
if (buffer.len <= SIZE - index)
{
usz n = buffer.len;
buffer[:n] = self.buf[self.head + index:n];
return n;
}
usz n1 = self.buf.len - index;
usz n1 = SIZE - index;
buffer[:n1] = self.buf[self.head + index:n1];
buffer = buffer[n1..];
index -= n1;
@@ -99,10 +90,10 @@ fn usz RingBuffer.read(&self, usz index, Element[] buffer)
return n1 + n2;
}
fn void RingBuffer.write(&self, Element[] buffer)
fn void RingBuffer.write(&self, Type[] buffer)
{
usz i;
while (self.written < self.buf.len && i < buffer.len)
while (self.written < SIZE && i < buffer.len)
{
self.buf[self.written] = buffer[i++];
self.written++;
@@ -110,6 +101,6 @@ fn void RingBuffer.write(&self, Element[] buffer)
foreach (c : buffer[i..])
{
self.buf[self.head] = c;
self.head = (self.head + 1) % self.buf.len;
self.head = (self.head + 1) % SIZE;
}
}

View File

@@ -1,4 +1,4 @@
module std::collections::tuple{Type1, Type2};
module std::collections::tuple(<Type1, Type2>);
struct Tuple
{
@@ -6,7 +6,7 @@ struct Tuple
Type2 second;
}
module std::collections::triple{Type1, Type2, Type3};
module std::collections::triple(<Type1, Type2, Type3>);
struct Triple
{

View File

@@ -37,11 +37,19 @@ struct QOIDesc
QOIChannels channels;
QOIColorspace colorspace;
}
<*
QOI Errors.
These are all the possible bad outcomes.
*>
faultdef INVALID_PARAMETERS, FILE_OPEN_FAILED, FILE_WRITE_FAILED, INVALID_DATA, TOO_MANY_PIXELS;
fault QOIError
{
INVALID_PARAMETERS,
FILE_OPEN_FAILED,
FILE_WRITE_FAILED,
INVALID_DATA,
TOO_MANY_PIXELS
}
// Let the user decide if they want to use std::io
@@ -59,17 +67,27 @@ import std::io;
The function returns an optional, which can either be a QOIError
or the number of bytes written on success.
@param [in] filename : `The file's name to write the image to`
@param [in] input : `The raw RGB or RGBA pixels to encode`
@param [&in] desc : `The descriptor of the image`
@param [in] filename `The file's name to write the image to`
@param [in] input `The raw RGB or RGBA pixels to encode`
@param [&in] desc `The descriptor of the image`
*>
fn usz? write(String filename, char[] input, QOIDesc* desc) => @pool()
fn usz! write(String filename, char[] input, QOIDesc* desc)
{
// encode data
char[] output = encode(tmem, input, desc)!;
@pool() {
// encode data
char[] output = encode(input, desc)!;
file::save(filename, output)!;
return output.len;
// open file
File! f = file::open(filename, "wb");
if (catch f) { return QOIError.FILE_OPEN_FAILED?; }
// write data to file and close it
usz! written = f.write(output);
if (catch written) { return QOIError.FILE_WRITE_FAILED?; }
if (catch f.close()) { return QOIError.FILE_WRITE_FAILED?; }
return written;
};
}
@@ -90,19 +108,20 @@ fn usz? write(String filename, char[] input, QOIDesc* desc) => @pool()
The returned pixel data should be free()d after use, or the decoding
and use of the data should be wrapped in a @pool() { ... }; block.
@param [in] filename : `The file's name to read the image from`
@param [&out] desc : `The descriptor to fill with the image's info`
@param channels : `The channels to be used`
@return? FILE_OPEN_FAILED, INVALID_DATA, TOO_MANY_PIXELS
@param [in] filename `The file's name to read the image from`
@param [&out] desc `The descriptor to fill with the image's info`
@param channels `The channels to be used`
*>
fn char[]? read(Allocator allocator, String filename, QOIDesc* desc, QOIChannels channels = AUTO) => @pool()
fn char[]! read(String filename, QOIDesc* desc, QOIChannels channels = AUTO, Allocator allocator = allocator::heap())
{
// read file
char[] data = file::load_temp(filename) ?? FILE_OPEN_FAILED?!;
// pass data to decode function
return decode(allocator, data, desc, channels);
}
char[]! data = file::load_new(filename);
if (catch data) return QOIError.FILE_OPEN_FAILED?;
defer mem::free(data);
// pass data to decode function
return decode(data, desc, channels, allocator);
}
// Back to basic non-stdio mode
@@ -119,21 +138,20 @@ import std::bits;
and use of the data should be wrapped in a @pool() { ... }; block.
See the write() function for an example.
@param [in] input : `The raw RGB or RGBA pixels to encode`
@param [&in] desc : `The descriptor of the image`
@return? INVALID_PARAMETERS, TOO_MANY_PIXELS, INVALID_DATA
@param [in] input `The raw RGB or RGBA pixels to encode`
@param [&in] desc `The descriptor of the image`
*>
fn char[]? encode(Allocator allocator, char[] input, QOIDesc* desc) @nodiscard
fn char[]! encode(char[] input, QOIDesc* desc, Allocator allocator = allocator::heap())
{
// check info in desc
if (desc.width == 0 || desc.height == 0) return INVALID_PARAMETERS?;
if (desc.channels == AUTO) return INVALID_PARAMETERS?;
if (desc.width == 0 || desc.height == 0) return QOIError.INVALID_PARAMETERS?;
if (desc.channels == AUTO) return QOIError.INVALID_PARAMETERS?;
uint pixels = desc.width * desc.height;
if (pixels > PIXELS_MAX) return TOO_MANY_PIXELS?;
if (pixels > PIXELS_MAX) return QOIError.TOO_MANY_PIXELS?;
// check input data size
uint image_size = pixels * desc.channels.id;
if (image_size != input.len) return INVALID_DATA?;
if (image_size != input.len) return QOIError.INVALID_DATA?;
// allocate memory for encoded data (output)
// header + chunk tag and RGB(A) data for each pixel + end of stream
@@ -173,77 +191,70 @@ fn char[]? encode(Allocator allocator, char[] input, QOIDesc* desc) @nodiscard
if (desc.channels == RGBA) p.a = input[loc + 3];
// check if we can run the previous pixel
if (prev == p)
{
if (prev == p) {
run_length++;
if (run_length == 62 || loc == loc_end)
{
if (run_length == 62 || loc == loc_end) {
*@extract(OpRun, output, &pos) = { OP_RUN, run_length - 1 };
run_length = 0;
}
} else {
// end last run if there was one
if (run_length > 0) {
*@extract(OpRun, output, &pos) = { OP_RUN, run_length - 1 };
run_length = 0;
}
continue;
}
// end last run if there was one
if (run_length > 0)
{
*@extract(OpRun, output, &pos) = { OP_RUN, run_length - 1 };
run_length = 0;
}
switch
{
// check if we can index the palette
case (palette[p.hash()] == p):
*@extract(OpIndex, output, &pos) = {
OP_INDEX,
p.hash()
};
// check if we can use diff or luma
case (prev != p && prev.a == p.a):
// diff the pixels
diff = p.rgb - prev.rgb;
if (diff.r > -3 && diff.r < 2
&& diff.g > -3 && diff.g < 2
&& diff.b > -3 && diff.b < 2)
{
*@extract(OpDiff, output, &pos) = {
OP_DIFF,
(char)diff.r + 2,
(char)diff.g + 2,
(char)diff.b + 2
switch {
// check if we can index the palette
case (palette[p.hash()] == p):
*@extract(OpIndex, output, &pos) = {
OP_INDEX,
p.hash()
};
palette[p.hash()] = p;
break;
}
// check luma eligibility
luma = { diff.r - diff.g, diff.g, diff.b - diff.g };
if (luma.r >= -8 && luma.r <= 7
&& luma.g >= -32 && luma.g <= 31
&& luma.b >= -8 && luma.b <= 7)
{
*@extract(OpLuma, output, &pos) = {
OP_LUMA,
(char)luma.g + 32,
(char)luma.r + 8,
(char)luma.b + 8
};
palette[p.hash()] = p;
break;
}
nextcase;
// worst case scenario: just encode the raw pixel
default:
if (prev.a != p.a)
{
*@extract(OpRGBA, output, &pos) = { OP_RGBA, p.r, p.g, p.b, p.a };
}
else
{
*@extract(OpRGB, output, &pos) = { OP_RGB, p.r, p.g, p.b };
}
palette[p.hash()] = p;
// check if we can use diff or luma
case (prev != p && prev.a == p.a):
// diff the pixels
diff = p.rgb - prev.rgb;
if (
diff.r > -3 && diff.r < 2 &&
diff.g > -3 && diff.g < 2 &&
diff.b > -3 && diff.b < 2
) {
*@extract(OpDiff, output, &pos) = {
OP_DIFF,
(char)diff.r + 2,
(char)diff.g + 2,
(char)diff.b + 2
};
palette[p.hash()] = p;
} else {
// check luma eligibility
luma = { diff.r - diff.g, diff.g, diff.b - diff.g };
if (
luma.r >= -8 && luma.r <= 7 &&
luma.g >= -32 && luma.g <= 31 &&
luma.b >= -8 && luma.b <= 7
) {
*@extract(OpLuma, output, &pos) = {
OP_LUMA,
(char)luma.g + 32,
(char)luma.r + 8,
(char)luma.b + 8
};
palette[p.hash()] = p;
} else { nextcase; }
}
// worst case scenario: just encode the raw pixel
default:
if (prev.a != p.a) {
*@extract(OpRGBA, output, &pos) = { OP_RGBA, p.r, p.g, p.b, p.a };
} else {
*@extract(OpRGB, output, &pos) = { OP_RGB, p.r, p.g, p.b };
}
palette[p.hash()] = p;
}
}
}
@@ -255,7 +266,6 @@ fn char[]? encode(Allocator allocator, char[] input, QOIDesc* desc) @nodiscard
}
<*
Decode a QOI image from memory.
@@ -273,35 +283,34 @@ fn char[]? encode(Allocator allocator, char[] input, QOIDesc* desc) @nodiscard
The returned pixel data should be free()d after use, or the decoding
and use of the data should be wrapped in a @pool() { ... }; block.
@param [in] data : `The QOI image data to decode`
@param [&out] desc : `The descriptor to fill with the image's info`
@param channels : `The channels to be used`
@return? INVALID_DATA, TOO_MANY_PIXELS
@param [in] data `The QOI image data to decode`
@param [&out] desc `The descriptor to fill with the image's info`
@param channels `The channels to be used`
*>
fn char[]? decode(Allocator allocator, char[] data, QOIDesc* desc, QOIChannels channels = AUTO) @nodiscard
fn char[]! decode(char[] data, QOIDesc* desc, QOIChannels channels = AUTO, Allocator allocator = allocator::heap())
{
// 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 QOIError.INVALID_DATA?;
// get header
Header* header = (Header*)data.ptr;
// check magic bytes (FourCC)
if (bswap(header.be_magic) != 'qoif') return INVALID_DATA?;
if (bswap(header.be_magic) != 'qoif') return QOIError.INVALID_DATA?;
// copy header data to desc
desc.width = bswap(header.be_width);
desc.height = bswap(header.be_height);
desc.channels = @enumcast(QOIChannels, header.channels)!; // Rethrow if invalid
desc.colorspace = @enumcast(QOIColorspace, header.colorspace)!; // Rethrow if invalid
if (desc.channels == AUTO) return INVALID_DATA?; // Channels must be specified in the header
if (desc.channels == AUTO) return QOIError.INVALID_DATA?; // Channels must be specified in the header
// check width and height
if (desc.width == 0 || desc.height == 0) return INVALID_DATA?;
if (desc.width == 0 || desc.height == 0) return QOIError.INVALID_DATA?;
// check pixel count
ulong pixels = (ulong)desc.width * (ulong)desc.height;
if (pixels > PIXELS_MAX) return TOO_MANY_PIXELS?;
if (pixels > PIXELS_MAX) return QOIError.TOO_MANY_PIXELS?;
uint pos = Header.sizeof; // Current position in data
uint loc; // Current position in image (top-left corner)
@@ -403,22 +412,16 @@ struct Header @packed
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)
{
foreach (value : $Type.values) {
if (value.id == raw) return value;
}
return INVALID_DATA?;
return QOIError.INVALID_DATA?;
}
typedef Pixel = inline char[<4>];
macro char Pixel.hash(Pixel p)
{
distinct Pixel = inline char[<4>];
macro char Pixel.hash(Pixel p) {
return (p.r * 3 + p.g * 5 + p.b * 7 + p.a * 11) % 64;
}
@@ -449,7 +452,7 @@ bitstruct OpDiff : char
char diff_green : 2..3;
char diff_blue : 0..1;
}
bitstruct OpLuma : ushort @align(1)
bitstruct OpLuma : ushort
{
char tag : 6..7;
char diff_green : 0..5;

View File

@@ -31,11 +31,6 @@ struct ArenaAllocatorHeader @local
char[*] data;
}
macro ArenaAllocator* wrap(char[] bytes)
{
return (ArenaAllocator){}.init(bytes);
}
<*
@require ptr != null
*>
@@ -50,25 +45,24 @@ fn void ArenaAllocator.release(&self, void* ptr, bool) @dynamic
}
}
fn usz ArenaAllocator.mark(&self) => self.used;
fn void ArenaAllocator.reset(&self, usz mark) => self.used = mark;
fn usz ArenaAllocator.mark(&self) @dynamic => self.used;
fn void ArenaAllocator.reset(&self, usz mark) @dynamic => self.used = mark;
<*
@require !alignment || math::is_power_of_2(alignment)
@require alignment <= mem::MAX_MEMORY_ALIGNMENT : `alignment too big`
@require alignment <= mem::MAX_MEMORY_ALIGNMENT `alignment too big`
@require size > 0
@return? mem::INVALID_ALLOC_SIZE, mem::OUT_OF_MEMORY
*>
fn void*? ArenaAllocator.acquire(&self, usz size, AllocInitType init_type, usz alignment) @dynamic
fn void*! ArenaAllocator.acquire(&self, usz size, AllocInitType init_type, usz alignment) @dynamic
{
alignment = alignment_for_allocation(alignment);
usz total_len = self.data.len;
if (size > total_len) return mem::INVALID_ALLOC_SIZE?;
if (size > total_len) return AllocationFailure.CHUNK_TOO_LARGE?;
void* start_mem = self.data.ptr;
void* unaligned_pointer_to_offset = start_mem + self.used + ArenaAllocatorHeader.sizeof;
void* mem = mem::aligned_pointer(unaligned_pointer_to_offset, alignment);
usz end = (usz)(mem - self.data.ptr) + size;
if (end > total_len) return mem::OUT_OF_MEMORY?;
if (end > total_len) return AllocationFailure.OUT_OF_MEMORY?;
self.used = end;
ArenaAllocatorHeader* header = mem - ArenaAllocatorHeader.sizeof;
header.size = size;
@@ -78,17 +72,16 @@ fn void*? ArenaAllocator.acquire(&self, usz size, AllocInitType init_type, usz a
<*
@require !alignment || math::is_power_of_2(alignment)
@require alignment <= mem::MAX_MEMORY_ALIGNMENT : `alignment too big`
@require alignment <= mem::MAX_MEMORY_ALIGNMENT `alignment too big`
@require old_pointer != null
@require size > 0
@return? mem::INVALID_ALLOC_SIZE, mem::OUT_OF_MEMORY
*>
fn void*? ArenaAllocator.resize(&self, void *old_pointer, usz size, usz alignment) @dynamic
fn void*! ArenaAllocator.resize(&self, void *old_pointer, usz size, usz alignment) @dynamic
{
alignment = alignment_for_allocation(alignment);
assert(old_pointer >= self.data.ptr, "Pointer originates from a different allocator.");
usz total_len = self.data.len;
if (size > total_len) return mem::INVALID_ALLOC_SIZE?;
if (size > total_len) return AllocationFailure.CHUNK_TOO_LARGE?;
ArenaAllocatorHeader* header = old_pointer - ArenaAllocatorHeader.sizeof;
usz old_size = header.size;
// Do last allocation and alignment match?
@@ -101,7 +94,7 @@ fn void*? ArenaAllocator.resize(&self, void *old_pointer, usz size, usz alignmen
else
{
usz new_used = self.used + size - old_size;
if (new_used > total_len) return mem::OUT_OF_MEMORY?;
if (new_used > total_len) return AllocationFailure.OUT_OF_MEMORY?;
self.used = new_used;
}
header.size = size;

View File

@@ -1,210 +0,0 @@
module std::core::mem::allocator;
import std::io, std::math;
struct AllocChunk @local
{
usz size;
char[*] data;
}
struct BackedArenaAllocator (Allocator)
{
Allocator backing_allocator;
ExtraPage* last_page;
usz used;
usz capacity;
char[*] data;
}
const usz PAGE_IS_ALIGNED @local = (usz)isz.max + 1u;
struct ExtraPage @local
{
ExtraPage* prev_page;
void* start;
usz mark;
usz size;
usz ident;
char[*] data;
}
macro usz ExtraPage.pagesize(&self) => self.size & ~PAGE_IS_ALIGNED;
macro bool ExtraPage.is_aligned(&self) => self.size & PAGE_IS_ALIGNED == PAGE_IS_ALIGNED;
<*
@require size >= 16
*>
fn BackedArenaAllocator*? new_backed_allocator(usz size, Allocator allocator)
{
BackedArenaAllocator* temp = allocator::alloc_with_padding(allocator, BackedArenaAllocator, size)!;
temp.last_page = null;
temp.backing_allocator = allocator;
temp.used = 0;
temp.capacity = size;
return temp;
}
fn void BackedArenaAllocator.destroy(&self)
{
self.reset(0);
if (self.last_page) (void)self._free_page(self.last_page);
allocator::free(self.backing_allocator, self);
}
fn usz BackedArenaAllocator.mark(&self) => self.used;
fn void BackedArenaAllocator.release(&self, void* old_pointer, bool) @dynamic
{
usz old_size = *(usz*)(old_pointer - DEFAULT_SIZE_PREFIX);
if (old_pointer + old_size == &self.data[self.used])
{
self.used -= old_size;
asan::poison_memory_region(&self.data[self.used], old_size);
}
}
fn void BackedArenaAllocator.reset(&self, usz mark)
{
ExtraPage *last_page = self.last_page;
while (last_page && last_page.mark > mark)
{
self.used = last_page.mark;
ExtraPage *to_free = last_page;
last_page = last_page.prev_page;
self._free_page(to_free)!!;
}
self.last_page = last_page;
$if env::COMPILER_SAFE_MODE || env::ADDRESS_SANITIZER:
if (!last_page)
{
usz cleaned = self.used - mark;
if (cleaned > 0)
{
$if env::COMPILER_SAFE_MODE && !env::ADDRESS_SANITIZER:
self.data[mark : cleaned] = 0xAA;
$endif
asan::poison_memory_region(&self.data[mark], cleaned);
}
}
$endif
self.used = mark;
}
fn void? BackedArenaAllocator._free_page(&self, ExtraPage* page) @inline @local
{
void* mem = page.start;
return self.backing_allocator.release(mem, page.is_aligned());
}
fn void*? BackedArenaAllocator._realloc_page(&self, ExtraPage* 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.
ExtraPage **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)!;
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*? BackedArenaAllocator.resize(&self, void* pointer, usz size, usz alignment) @dynamic
{
AllocChunk *chunk = pointer - AllocChunk.sizeof;
if (chunk.size == (usz)-1)
{
assert(self.last_page, "Realloc of unrelated pointer");
// First grab the page
ExtraPage *page = pointer - ExtraPage.sizeof;
return self._realloc_page(page, size, alignment);
}
AllocChunk* data = self.acquire(size, NO_ZERO, alignment)!;
mem::copy(data, pointer, chunk.size, mem::DEFAULT_MEM_ALIGNMENT, mem::DEFAULT_MEM_ALIGNMENT);
return data;
}
<*
@require size > 0
@require !alignment || math::is_power_of_2(alignment)
@require alignment <= mem::MAX_MEMORY_ALIGNMENT : `alignment too big`
*>
fn void*? BackedArenaAllocator.acquire(&self, usz size, AllocInitType init_type, usz alignment) @dynamic
{
alignment = alignment_for_allocation(alignment);
void* start_mem = &self.data;
void* starting_ptr = start_mem + self.used;
void* aligned_header_start = mem::aligned_pointer(starting_ptr, AllocChunk.alignof);
void* mem = aligned_header_start + AllocChunk.sizeof;
if (alignment > AllocChunk.alignof)
{
mem = mem::aligned_pointer(mem, alignment);
}
usz new_usage = (usz)(mem - start_mem) + size;
// Arena allocation, simple!
if (new_usage <= self.capacity)
{
asan::unpoison_memory_region(starting_ptr, new_usage - self.used);
AllocChunk* chunk_start = mem - AllocChunk.sizeof;
chunk_start.size = size;
self.used = new_usage;
if (init_type == ZERO) mem::clear(mem, size, mem::DEFAULT_MEM_ALIGNMENT);
return mem;
}
// Fallback to backing allocator
ExtraPage* page;
// We have something we need to align.
if (alignment > mem::DEFAULT_MEM_ALIGNMENT)
{
// This is actually simpler, since it will create the offset for us.
usz total_alloc_size = mem::aligned_offset(ExtraPage.sizeof + size, alignment);
if (init_type == ZERO)
{
mem = allocator::calloc_aligned(self.backing_allocator, total_alloc_size, alignment)!;
}
else
{
mem = allocator::malloc_aligned(self.backing_allocator, total_alloc_size, alignment)!;
}
void* start = mem;
mem += mem::aligned_offset(ExtraPage.sizeof, alignment);
page = (ExtraPage*)mem - 1;
page.start = start;
page.size = size | PAGE_IS_ALIGNED;
}
else
{
// Here we might need to pad
usz padded_header_size = mem::aligned_offset(ExtraPage.sizeof, mem::DEFAULT_MEM_ALIGNMENT);
usz total_alloc_size = padded_header_size + size;
void* alloc = self.backing_allocator.acquire(total_alloc_size, init_type, 0)!;
// Find the page.
page = alloc + padded_header_size - ExtraPage.sizeof;
assert(mem::ptr_is_aligned(page, BackedArenaAllocator.alignof));
assert(mem::ptr_is_aligned(&page.data[0], mem::DEFAULT_MEM_ALIGNMENT));
page.start = alloc;
page.size = size;
}
// Mark it as a page
page.ident = ~(usz)0;
// Store when it was created
page.mark = ++self.used;
// Hook up the page.
page.prev_page = self.last_page;
self.last_page = page;
return &page.data[0];
}

View File

@@ -16,7 +16,7 @@ struct DynamicArenaAllocator (Allocator)
@param [&inout] allocator
@require page_size >= 128
*>
fn void DynamicArenaAllocator.init(&self, Allocator allocator, usz page_size)
fn void DynamicArenaAllocator.init(&self, usz page_size, Allocator allocator)
{
self.page = null;
self.unused_page = null;
@@ -61,8 +61,8 @@ struct DynamicArenaChunk @local
}
<*
@require ptr != null
@require self.page != null : `tried to free pointer on invalid allocator`
@require ptr
@require self.page `tried to free pointer on invalid allocator`
*>
fn void DynamicArenaAllocator.release(&self, void* ptr, bool) @dynamic
{
@@ -75,12 +75,11 @@ fn void DynamicArenaAllocator.release(&self, void* ptr, bool) @dynamic
}
<*
@require size > 0 : `Resize doesn't support zeroing`
@require old_pointer != null : `Resize doesn't handle null pointers`
@require self.page != null : `tried to realloc pointer on invalid allocator`
@return? mem::INVALID_ALLOC_SIZE, mem::OUT_OF_MEMORY
@require size > 0 `Resize doesn't support zeroing`
@require old_pointer != null `Resize doesn't handle null pointers`
@require self.page `tried to realloc pointer on invalid allocator`
*>
fn void*? DynamicArenaAllocator.resize(&self, void* old_pointer, usz size, usz alignment) @dynamic
fn void*! DynamicArenaAllocator.resize(&self, void* old_pointer, usz size, usz alignment) @dynamic
{
DynamicArenaPage* current_page = self.page;
alignment = alignment_for_allocation(alignment);
@@ -110,8 +109,9 @@ fn void*? DynamicArenaAllocator.resize(&self, void* old_pointer, usz size, usz a
return new_mem;
}
fn void DynamicArenaAllocator.reset(&self)
fn void DynamicArenaAllocator.reset(&self, usz mark = 0) @dynamic
{
assert(mark == 0, "Unexpectedly reset dynamic arena allocator with mark %d", mark);
DynamicArenaPage* page = self.page;
DynamicArenaPage** unused_page_ptr = &self.unused_page;
while (page)
@@ -129,16 +129,15 @@ fn void DynamicArenaAllocator.reset(&self)
<*
@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
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);
DynamicArenaPage*! page = allocator::new_try(self.backing_allocator, DynamicArenaPage);
if (catch err = page)
{
allocator::free(self.backing_allocator, mem);
@@ -158,29 +157,21 @@ fn void*? DynamicArenaAllocator._alloc_new(&self, usz size, usz alignment) @loca
}
<*
@require size > 0 : `acquire expects size > 0`
@require size > 0 `acquire expects size > 0`
@require !alignment || math::is_power_of_2(alignment)
@return? mem::INVALID_ALLOC_SIZE, mem::OUT_OF_MEMORY
*>
fn void*? DynamicArenaAllocator.acquire(&self, usz size, AllocInitType init_type, usz alignment) @dynamic
fn void*! DynamicArenaAllocator.acquire(&self, usz size, AllocInitType init_type, usz alignment) @dynamic
{
alignment = alignment_for_allocation(alignment);
DynamicArenaPage* page = self.page;
void* ptr @noinit;
do SET_DONE:
{
void* ptr = {|
if (!page && self.unused_page)
{
self.page = page = self.unused_page;
self.unused_page = page.prev_arena;
page.prev_arena = null;
}
if (!page)
{
ptr = self._alloc_new(size, alignment)!;
break SET_DONE;
}
if (!page) return self._alloc_new(size, alignment);
void* start = mem::aligned_pointer(page.memory + page.used + DynamicArenaChunk.sizeof, alignment);
usz new_used = start - page.memory + size;
if ALLOCATE_NEW: (new_used > page.total)
@@ -197,15 +188,15 @@ fn void*? DynamicArenaAllocator.acquire(&self, usz size, AllocInitType init_type
break ALLOCATE_NEW;
}
}
ptr = self._alloc_new(size, alignment)!;
break SET_DONE;
return self._alloc_new(size, alignment);
}
page.used = new_used;
assert(start + size == page.memory + page.used);
ptr = start;
DynamicArenaChunk* chunk = (DynamicArenaChunk*)ptr - 1;
void* mem = start;
DynamicArenaChunk* chunk = (DynamicArenaChunk*)mem - 1;
chunk.size = size;
};
return mem;
|}!;
if (init_type == ZERO) mem::clear(ptr, size, mem::DEFAULT_MEM_ALIGNMENT);
return ptr;
}

View File

@@ -1,4 +1,4 @@
// Copyright (c) 2021-2025 Christoffer Lerno. All rights reserved.
// Copyright (c) 2021-2024 Christoffer Lerno. All rights reserved.
// Use of this source code is governed by the MIT license
// a copy of which can be found in the LICENSE_STDLIB file.
@@ -12,8 +12,8 @@ struct SimpleHeapAllocator (Allocator)
}
<*
@require allocator != null : "An underlying memory provider must be given"
@require !self.free_list : "The allocator may not be already initialized"
@require allocator "An underlying memory provider must be given"
@require !self.free_list "The allocator may not be already initialized"
*>
fn void SimpleHeapAllocator.init(&self, MemoryAllocFn allocator)
{
@@ -21,7 +21,7 @@ fn void SimpleHeapAllocator.init(&self, MemoryAllocFn allocator)
self.free_list = null;
}
fn void*? SimpleHeapAllocator.acquire(&self, usz size, AllocInitType init_type, usz alignment) @dynamic
fn void*! SimpleHeapAllocator.acquire(&self, usz size, AllocInitType init_type, usz alignment) @dynamic
{
if (init_type == ZERO)
{
@@ -30,7 +30,7 @@ fn void*? SimpleHeapAllocator.acquire(&self, usz size, AllocInitType init_type,
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
? @aligned_realloc(self._calloc, self._free, old_pointer, size, alignment)
@@ -52,7 +52,7 @@ fn void SimpleHeapAllocator.release(&self, void* old_pointer, bool aligned) @dyn
<*
@require old_pointer && bytes > 0
*>
fn void*? SimpleHeapAllocator._realloc(&self, void* old_pointer, usz bytes) @local
fn void*! SimpleHeapAllocator._realloc(&self, void* old_pointer, usz bytes) @local
{
// Find the block header.
Header* block = (Header*)old_pointer - 1;
@@ -64,14 +64,14 @@ fn void*? SimpleHeapAllocator._realloc(&self, void* old_pointer, usz bytes) @loc
return new;
}
fn void*? SimpleHeapAllocator._calloc(&self, usz bytes) @local
fn void*! SimpleHeapAllocator._calloc(&self, usz bytes) @local
{
void* data = self._alloc(bytes)!;
mem::clear(data, bytes, mem::DEFAULT_MEM_ALIGNMENT);
return data;
}
fn void*? SimpleHeapAllocator._alloc(&self, usz bytes) @local
fn void*! SimpleHeapAllocator._alloc(&self, usz bytes) @local
{
usz aligned_bytes = mem::aligned_offset(bytes, mem::DEFAULT_MEM_ALIGNMENT);
if (!self.free_list)
@@ -120,7 +120,7 @@ fn void*? SimpleHeapAllocator._alloc(&self, usz bytes) @local
return self._alloc(aligned_bytes);
}
fn void? SimpleHeapAllocator.add_block(&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);
char[] result = self.alloc_fn(aligned_bytes + Header.sizeof)!;

View File

@@ -7,39 +7,39 @@ import std::io;
import libc;
const LibcAllocator LIBC_ALLOCATOR = {};
typedef LibcAllocator (Allocator, Printable) = uptr;
distinct LibcAllocator (Allocator, Printable) = uptr;
fn String LibcAllocator.to_string(&self, Allocator allocator) @dynamic => "Libc allocator".copy(allocator);
fn usz? LibcAllocator.to_format(&self, Formatter *format) @dynamic => format.print("Libc allocator");
fn usz! LibcAllocator.to_format(&self, Formatter *format) @dynamic => format.print("Libc allocator");
module std::core::mem::allocator @if(env::POSIX);
import std::os;
import libc;
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)
{
void* data @noinit;
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 AllocationFailure.OUT_OF_MEMORY?;
mem::clear(data, bytes, mem::DEFAULT_MEM_ALIGNMENT);
return data;
}
return libc::calloc(1, bytes) ?: mem::OUT_OF_MEMORY?;
return libc::calloc(1, bytes) ?: AllocationFailure.OUT_OF_MEMORY?;
}
else
{
void* data @noinit;
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 AllocationFailure.OUT_OF_MEMORY?;
}
else
{
if (!(data = libc::malloc(bytes))) return mem::OUT_OF_MEMORY?;
if (!(data = libc::malloc(bytes))) return AllocationFailure.OUT_OF_MEMORY?;
}
$if env::TESTING:
for (usz i = 0; i < bytes; i++) ((char*)data)[i] = 0xAA;
@@ -48,13 +48,13 @@ 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) ?: AllocationFailure.OUT_OF_MEMORY?;
void* new_ptr;
if (posix::posix_memalign(&new_ptr, alignment, new_bytes)) return mem::OUT_OF_MEMORY?;
if (posix::posix_memalign(&new_ptr, alignment, new_bytes)) return AllocationFailure.OUT_OF_MEMORY?;
$switch:
$switch
$case env::DARWIN:
usz old_usable_size = darwin::malloc_size(old_ptr);
$case env::LINUX:
@@ -78,31 +78,31 @@ module std::core::mem::allocator @if(env::WIN32);
import std::os::win32;
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 (alignment > 0)
{
return win32::_aligned_recalloc(null, 1, bytes, alignment) ?: mem::OUT_OF_MEMORY?;
return win32::_aligned_recalloc(null, 1, bytes, alignment) ?: AllocationFailure.OUT_OF_MEMORY?;
}
return libc::calloc(1, bytes) ?: mem::OUT_OF_MEMORY?;
return libc::calloc(1, bytes) ?: AllocationFailure.OUT_OF_MEMORY?;
}
void* data = alignment > 0 ? win32::_aligned_malloc(bytes, alignment) : libc::malloc(bytes);
if (!data) return mem::OUT_OF_MEMORY?;
if (!data) return AllocationFailure.OUT_OF_MEMORY?;
$if env::TESTING:
for (usz i = 0; i < bytes; i++) ((char*)data)[i] = 0xAA;
$endif
return data;
}
fn void*? LibcAllocator.resize(&self, void* old_ptr, usz new_bytes, usz alignment) @dynamic
fn void*! LibcAllocator.resize(&self, void* old_ptr, usz new_bytes, usz alignment) @dynamic
{
if (alignment)
{
return win32::_aligned_realloc(old_ptr, new_bytes, alignment) ?: mem::OUT_OF_MEMORY?;
return win32::_aligned_realloc(old_ptr, new_bytes, alignment) ?: AllocationFailure.OUT_OF_MEMORY?;
}
return libc::realloc(old_ptr, new_bytes) ?: mem::OUT_OF_MEMORY?;
return libc::realloc(old_ptr, new_bytes) ?: AllocationFailure.OUT_OF_MEMORY?;
}
fn void LibcAllocator.release(&self, void* old_ptr, bool aligned) @dynamic
@@ -118,17 +118,17 @@ fn void LibcAllocator.release(&self, void* old_ptr, bool aligned) @dynamic
module std::core::mem::allocator @if(!env::WIN32 && !env::POSIX && env::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)
{
void* data = alignment ? @aligned_alloc(fn void*(usz bytes) => libc::calloc(bytes, 1), bytes, alignment)!! : libc::calloc(bytes, 1);
return data ?: mem::OUT_OF_MEMORYY?;
return data ?: AllocationFailure.OUT_OF_MEMORY?;
}
else
{
void* data = alignment ? @aligned_alloc(libc::malloc, bytes, alignment)!! : libc::malloc(bytes);
if (!data) return mem::OUT_OF_MEMORY?;
if (!data) return AllocationFailure.OUT_OF_MEMORY?;
$if env::TESTING:
for (usz i = 0; i < bytes; i++) ((char*)data)[i] = 0xAA;
$endif
@@ -137,14 +137,14 @@ 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)
{
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 ?: AllocationFailure.OUT_OF_MEMORY?;
}
return libc::realloc(old_ptr, new_bytes) ?: mem::OUT_OF_MEMORY?;
return libc::realloc(old_ptr, new_bytes) ?: AllocationFailure.OUT_OF_MEMORY?;
}

View File

@@ -56,7 +56,7 @@ struct OnStackAllocatorHeader
}
<*
@require old_pointer != null
@require old_pointer
*>
fn void OnStackAllocator.release(&self, void* old_pointer, bool aligned) @dynamic
{
@@ -102,9 +102,9 @@ fn OnStackAllocatorExtraChunk* on_stack_allocator_find_chunk(OnStackAllocator* a
<*
@require size > 0
@require old_pointer != null
@require alignment <= mem::MAX_MEMORY_ALIGNMENT : `alignment too big`
@require alignment <= mem::MAX_MEMORY_ALIGNMENT `alignment too big`
*>
fn void*? OnStackAllocator.resize(&self, void* old_pointer, usz size, usz alignment) @dynamic
fn void*! OnStackAllocator.resize(&self, void* old_pointer, usz size, usz alignment) @dynamic
{
if (!allocation_in_stack_mem(self, old_pointer))
{
@@ -121,10 +121,10 @@ fn void*? OnStackAllocator.resize(&self, void* old_pointer, usz size, usz alignm
}
<*
@require alignment <= mem::MAX_MEMORY_ALIGNMENT : `alignment too big`
@require alignment <= mem::MAX_MEMORY_ALIGNMENT `alignment too big`
@require size > 0
*>
fn void*? OnStackAllocator.acquire(&self, usz size, AllocInitType init_type, usz alignment) @dynamic
fn void*! OnStackAllocator.acquire(&self, usz size, AllocInitType init_type, usz alignment) @dynamic
{
bool aligned = alignment > 0;
alignment = alignment_for_allocation(alignment);

View File

@@ -1,7 +1,5 @@
module std::core::mem::allocator;
import std::io, std::math;
import std::core::sanitizer::asan;
struct TempAllocatorChunk @local
{
@@ -13,11 +11,8 @@ struct TempAllocator (Allocator)
{
Allocator backing_allocator;
TempAllocatorPage* last_page;
TempAllocator* derived;
bool allocated;
usz used;
usz capacity;
usz original_capacity;
char[*] data;
}
@@ -28,6 +23,7 @@ struct TempAllocatorPage
{
TempAllocatorPage* prev_page;
void* start;
usz mark;
usz size;
usz ident;
char[*] data;
@@ -38,98 +34,25 @@ macro bool TempAllocatorPage.is_aligned(&self) => self.size & PAGE_IS_ALIGNED ==
<*
@require size >= 16
@require allocator.type != TempAllocator.typeid : "You may not create a temp allocator with a TempAllocator as the backing allocator."
*>
fn TempAllocator*? new_temp_allocator(Allocator allocator, usz size)
fn TempAllocator*! new_temp_allocator(usz size, Allocator allocator)
{
TempAllocator* temp = allocator::alloc_with_padding(allocator, TempAllocator, size)!;
temp.last_page = null;
temp.backing_allocator = allocator;
temp.used = 0;
temp.allocated = true;
temp.derived = null;
temp.original_capacity = temp.capacity = size;
temp.capacity = size;
return temp;
}
<*
@require !self.derived
@require min_size > TempAllocator.sizeof + 64 : "Min size must meaningfully hold the data + some bytes"
@require mult > 0 : "The multiple can never be zero"
*>
fn TempAllocator*? TempAllocator.derive_allocator(&self, usz min_size, usz buffer, usz mult)
fn void TempAllocator.destroy(&self)
{
usz remaining = self.capacity - self.used;
void* mem @noinit;
usz size @noinit;
if (min_size + buffer > remaining)
{
return self.derived = new_temp_allocator(self.backing_allocator, min_size * mult)!;
}
usz start = mem::aligned_offset(self.used + buffer, mem::DEFAULT_MEM_ALIGNMENT);
void* ptr = &self.data[start];
TempAllocator* temp = (TempAllocator*)ptr;
$if env::ADDRESS_SANITIZER:
asan::unpoison_memory_region(ptr, TempAllocator.sizeof);
$endif
temp.last_page = null;
temp.backing_allocator = self.backing_allocator;
temp.used = 0;
temp.allocated = false;
temp.derived = null;
temp.original_capacity = temp.capacity = self.capacity - start - TempAllocator.sizeof;
self.capacity = start;
self.derived = temp;
return temp;
self.reset(0);
if (self.last_page) (void)self._free_page(self.last_page);
allocator::free(self.backing_allocator, self);
}
fn void TempAllocator.reset(&self)
{
TempAllocator* child = self.derived;
if (!child) return;
while (child)
{
TempAllocator* old = child;
child = old.derived;
old.destroy();
}
self.capacity = self.original_capacity;
$if env::ADDRESS_SANITIZER:
asan::poison_memory_region(&self.data[self.used], self.capacity - self.used);
$endif
self.derived = null;
}
<*
@require self.allocated : "Only a top level allocator should be freed."
*>
fn void TempAllocator.free(&self)
{
self.reset();
self.destroy();
}
fn void TempAllocator.destroy(&self) @local
{
TempAllocatorPage *last_page = self.last_page;
while (last_page)
{
TempAllocatorPage *to_free = last_page;
last_page = last_page.prev_page;
self._free_page(to_free)!!;
}
if (self.allocated)
{
allocator::free(self.backing_allocator, self);
return;
}
$if env::COMPILER_SAFE_MODE || env::ADDRESS_SANITIZER:
$if env::COMPILER_SAFE_MODE && !env::ADDRESS_SANITIZER:
self.data[0 : self.used] = 0xAA;
$else
asan::poison_memory_region(&self.data[0], self.used);
$endif
$endif
}
fn usz TempAllocator.mark(&self) @dynamic => self.used;
fn void TempAllocator.release(&self, void* old_pointer, bool) @dynamic
{
@@ -140,15 +63,40 @@ fn void TempAllocator.release(&self, void* old_pointer, bool) @dynamic
asan::poison_memory_region(&self.data[self.used], old_size);
}
}
fn void TempAllocator.reset(&self, usz mark) @dynamic
{
TempAllocatorPage *last_page = self.last_page;
while (last_page && last_page.mark > mark)
{
self.used = last_page.mark;
TempAllocatorPage *to_free = last_page;
last_page = last_page.prev_page;
self._free_page(to_free)!!;
}
self.last_page = last_page;
$if env::COMPILER_SAFE_MODE || env::ADDRESS_SANITIZER:
if (!last_page)
{
usz cleaned = self.used - mark;
if (cleaned > 0)
{
$if env::COMPILER_SAFE_MODE:
self.data[mark : cleaned] = 0xAA;
$endif
asan::poison_memory_region(&self.data[mark], cleaned);
}
}
$endif
self.used = mark;
}
fn void? TempAllocator._free_page(&self, TempAllocatorPage* page) @inline @local
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
fn void*! TempAllocator._realloc_page(&self, TempAllocatorPage* page, usz size, usz alignment) @inline @local
{
// Then the actual start pointer:
void* real_pointer = page.start;
@@ -164,13 +112,12 @@ fn void*? TempAllocator._realloc_page(&self, TempAllocatorPage* page, usz size,
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
{
TempAllocatorChunk *chunk = pointer - TempAllocatorChunk.sizeof;
if (chunk.size == (usz)-1)
@@ -180,47 +127,19 @@ fn void*? TempAllocator.resize(&self, void* pointer, usz size, usz alignment) @d
TempAllocatorPage *page = pointer - TempAllocatorPage.sizeof;
return self._realloc_page(page, size, alignment);
}
bool is_realloc_of_last = chunk.size + pointer == &self.data[self.used];
if (is_realloc_of_last)
{
isz diff = size - chunk.size;
if (diff == 0) return pointer;
if (self.capacity - self.used > diff)
{
chunk.size += diff;
self.used += diff;
$if env::ADDRESS_SANITIZER:
if (diff < 0)
{
asan::poison_memory_region(pointer + chunk.size, -diff);
}
else
{
asan::unpoison_memory_region(pointer, chunk.size);
}
$endif
return pointer;
}
}
void* data = self.acquire(size, NO_ZERO, alignment)!;
usz len_to_copy = chunk.size > size ? size : chunk.size;
mem::copy(data, pointer, len_to_copy, mem::DEFAULT_MEM_ALIGNMENT, mem::DEFAULT_MEM_ALIGNMENT);
if (is_realloc_of_last)
{
self.used = (uptr)chunk - (uptr)&self.data;
$if env::ADDRESS_SANITIZER:
asan::poison_memory_region(chunk, TempAllocatorChunk.sizeof + chunk.size);
$endif
}
TempAllocatorChunk* data = self.acquire(size, NO_ZERO, alignment)!;
mem::copy(data, pointer, chunk.size, mem::DEFAULT_MEM_ALIGNMENT, mem::DEFAULT_MEM_ALIGNMENT);
return data;
}
<*
@require size > 0
@require !alignment || math::is_power_of_2(alignment)
@require alignment <= mem::MAX_MEMORY_ALIGNMENT : `alignment too big`
@require alignment <= mem::MAX_MEMORY_ALIGNMENT `alignment too big`
*>
fn void*? TempAllocator.acquire(&self, usz size, AllocInitType init_type, usz alignment) @dynamic
fn void*! TempAllocator.acquire(&self, usz size, AllocInitType init_type, usz alignment) @dynamic
{
alignment = alignment_for_allocation(alignment);
void* start_mem = &self.data;
@@ -283,9 +202,29 @@ fn void*? TempAllocator.acquire(&self, usz size, AllocInitType init_type, usz al
// Mark it as a page
page.ident = ~(usz)0;
// Store when it was created
page.mark = ++self.used;
// Hook up the page.
page.prev_page = self.last_page;
self.last_page = page;
return &page.data[0];
}
fn void! TempAllocator.print_pages(&self, File* f)
{
TempAllocatorPage *last_page = self.last_page;
if (!last_page)
{
io::fprintf(f, "No pages.\n")!;
return;
}
io::fprintf(f, "---Pages----\n")!;
uint index = 0;
while (last_page)
{
bool is_not_aligned = !(last_page.size & (1u64 << 63));
io::fprintf(f, "%d. Alloc: %d %d at %p%s\n", ++index,
last_page.size & ~(1u64 << 63), last_page.mark, &last_page.data[0], is_not_aligned ? "" : " [aligned]")!;
last_page = last_page.prev_page;
}
}

View File

@@ -13,7 +13,7 @@ struct Allocation
void*[MAX_BACKTRACE] backtrace;
}
alias AllocMap = HashMap { uptr, Allocation };
def AllocMap = HashMap(<uptr, Allocation>);
// A simple tracking allocator.
// It tracks allocations using a hash map but
@@ -29,12 +29,12 @@ struct TrackingAllocator (Allocator)
<*
Initialize a tracking allocator to wrap (and track) another allocator.
@param [&inout] allocator : "The allocator to track"
@param [&inout] allocator "The allocator to track"
*>
fn void TrackingAllocator.init(&self, Allocator allocator)
{
*self = { .inner_allocator = allocator };
self.map.init(allocator);
self.map.new_init(allocator: allocator);
}
<*
@@ -49,10 +49,13 @@ fn void TrackingAllocator.free(&self)
<*
@return "the total allocated memory not yet freed."
*>
fn usz TrackingAllocator.allocated(&self) => @pool()
fn usz TrackingAllocator.allocated(&self)
{
usz allocated = 0;
foreach (&allocation : self.map.tvalues()) allocated += allocation.size;
@pool()
{
foreach (&allocation : self.map.value_tlist()) allocated += allocation.size;
};
return allocated;
}
@@ -68,7 +71,7 @@ fn usz TrackingAllocator.total_allocation_count(&self) => self.allocs_total;
fn Allocation[] TrackingAllocator.allocations_tlist(&self, Allocator allocator)
{
return self.map.tvalues();
return self.map.value_tlist();
}
<*
@@ -76,7 +79,7 @@ fn Allocation[] TrackingAllocator.allocations_tlist(&self, Allocator allocator)
*>
fn usz TrackingAllocator.allocation_count(&self) => self.map.count;
fn void*? TrackingAllocator.acquire(&self, usz size, AllocInitType init_type, usz alignment) @dynamic
fn void*! TrackingAllocator.acquire(&self, usz size, AllocInitType init_type, usz alignment) @dynamic
{
void* data = self.inner_allocator.acquire(size, init_type, alignment)!;
self.allocs_total++;
@@ -87,7 +90,7 @@ fn void*? TrackingAllocator.acquire(&self, usz size, AllocInitType init_type, us
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)!;
self.map.remove((uptr)old_pointer);
@@ -113,103 +116,101 @@ fn void TrackingAllocator.clear(&self)
self.map.clear();
}
fn bool TrackingAllocator.has_leaks(&self)
{
return self.map.len() > 0;
}
fn void TrackingAllocator.print_report(&self) => self.fprint_report(io::stdout())!!;
fn void? TrackingAllocator.fprint_report(&self, OutStream out) => @pool()
fn void! TrackingAllocator.fprint_report(&self, OutStream out)
{
usz total = 0;
usz entries = 0;
bool leaks = false;
Allocation[] allocs = self.map.tvalues();
if (allocs.len)
@pool()
{
if (!allocs[0].backtrace[0])
Allocation[] allocs = self.map.value_tlist();
if (allocs.len)
{
io::fprintn(out, "======== Memory Report ========")!;
io::fprintn(out, "Size in bytes Address")!;
foreach (i, &allocation : allocs)
if (!allocs[0].backtrace[0])
{
entries++;
total += allocation.size;
io::fprintfn(out, "%13s %p", allocation.size, allocation.ptr)!;
}
io::fprintn(out, "===============================")!;
io::fprintn(out, "======== Memory Report ========")!;
io::fprintn(out, "Size in bytes Address")!;
foreach (i, &allocation : allocs)
{
entries++;
total += allocation.size;
io::fprintfn(out, "%13s %p", allocation.size, allocation.ptr)!;
}
io::fprintn(out, "===============================")!;
}
else
{
io::fprintn(out, "================================== Memory Report ==================================")!;
io::fprintn(out, "Size in bytes Address Function ")!;
foreach (i, &allocation : allocs)
{
entries++;
total += allocation.size;
BacktraceList backtraces = {};
Backtrace trace = backtrace::BACKTRACE_UNKNOWN;
if (allocation.backtrace[3])
{
trace = backtrace::symbolize_backtrace(allocation.backtrace[3:1], allocator::temp()).get(0) ?? backtrace::BACKTRACE_UNKNOWN;
}
if (trace.function.len) leaks = true;
io::fprintfn(out, "%13s %p %s:%d", allocation.size,
allocation.ptr, trace.function.len ? trace.function : "???",
trace.line ? trace.line : 0)!;
}
io::fprintn(out, "===================================================================================")!;
}
}
else
{
io::fprintn(out, "================================== Memory Report ==================================")!;
io::fprintn(out, "Size in bytes Address Function ")!;
io::fprintn(out, "* NO ALLOCATIONS FOUND *")!;
}
io::fprintfn(out, "- Total currently allocated memory: %d", total)!;
io::fprintfn(out, "- Total current allocations: %d", entries)!;
io::fprintfn(out, "- Total allocations (freed and retained): %d", self.allocs_total)!;
io::fprintfn(out, "- Total allocated memory (freed and retained): %d", self.mem_total)!;
if (leaks)
{
io::fprintn(out)!;
io::fprintn(out, "Full leak report:")!;
foreach (i, &allocation : allocs)
{
entries++;
total += allocation.size;
if (!allocation.backtrace[3])
{
io::fprintfn(out, "Allocation %d (%d bytes) - no backtrace available.", i + 1, allocation.size)!;
continue;
}
BacktraceList backtraces = {};
Backtrace trace = backtrace::BACKTRACE_UNKNOWN;
if (allocation.backtrace[3])
usz end = MAX_BACKTRACE;
foreach (j, val : allocation.backtrace)
{
trace = backtrace::symbolize_backtrace(tmem, allocation.backtrace[3:1]).get(0) ?? backtrace::BACKTRACE_UNKNOWN;
if (!val)
{
end = j;
break;
}
}
if (trace.function.len) leaks = true;
io::fprintfn(out, "%13s %p %s:%d", allocation.size,
allocation.ptr, trace.function.len ? trace.function : "???",
trace.line ? trace.line : 0)!;
}
io::fprintn(out, "===================================================================================")!;
}
}
else
{
io::fprintn(out, "* NO ALLOCATIONS FOUND *")!;
}
io::fprintfn(out, "- Total currently allocated memory: %d", total)!;
io::fprintfn(out, "- Total current allocations: %d", entries)!;
io::fprintfn(out, "- Total allocations (freed and retained): %d", self.allocs_total)!;
io::fprintfn(out, "- Total allocated memory (freed and retained): %d", self.mem_total)!;
if (leaks)
{
io::fprintn(out)!;
io::fprintn(out, "Full leak report:")!;
foreach (i, &allocation : allocs)
{
if (!allocation.backtrace[3])
{
io::fprintfn(out, "Allocation %d (%d bytes) - no backtrace available.", i + 1, allocation.size)!;
continue;
}
BacktraceList backtraces = {};
usz end = MAX_BACKTRACE;
foreach (j, val : allocation.backtrace)
{
if (!val)
BacktraceList list = backtrace::symbolize_backtrace(allocation.backtrace[3..(end - 1)], allocator::temp())!;
io::fprintfn(out, "Allocation %d (%d bytes): ", i + 1, allocation.size)!;
foreach (trace : list)
{
end = j;
break;
if (trace.has_file())
{
io::fprintfn(out, " %s (in %s:%d)", trace.function, trace.file, trace.line);
continue;
}
if (trace.is_unknown())
{
io::fprintfn(out, " ??? (in unknown)");
continue;
}
io::fprintfn(out, " %s (source unavailable)", trace.function);
}
}
BacktraceList list = backtrace::symbolize_backtrace(tmem, allocation.backtrace[3..(end - 1)])!;
io::fprintfn(out, "Allocation %d (%d bytes): ", i + 1, allocation.size)!;
foreach (trace : list)
{
if (trace.has_file())
{
io::fprintfn(out, " %s (in %s:%d)", trace.function, trace.file, trace.line);
continue;
}
if (trace.is_unknown())
{
io::fprintfn(out, " ??? (in unknown)");
continue;
}
io::fprintfn(out, " %s (source unavailable)", trace.function);
}
}
}
};
}

View File

@@ -5,7 +5,7 @@ import std::core::array::slice;
@param [in] array
@param [in] element
@return "the first index of the element"
@return? NOT_FOUND
@return! SearchResult.MISSING
*>
macro index_of(array, element)
{
@@ -13,7 +13,7 @@ macro index_of(array, element)
{
if (*e == element) return i;
}
return NOT_FOUND?;
return SearchResult.MISSING?;
}
<*
@@ -26,7 +26,7 @@ macro slice2d(array_ptr, x = 0, xlen = 0, y = 0, ylen = 0)
if (xlen < 1) xlen = $typeof((*array_ptr)[0]).len + xlen;
if (ylen < 1) ylen = $typeof((*array_ptr)).len + ylen;
var $ElementType = $typeof((*array_ptr)[0][0]);
return Slice2d{$ElementType} { ($ElementType*)array_ptr, $typeof((*array_ptr)[0]).len, y, ylen, x, xlen };
return Slice2d(<$ElementType>) { ($ElementType*)array_ptr, $typeof((*array_ptr)[0]).len, y, ylen, x, xlen };
}
@@ -34,7 +34,7 @@ macro slice2d(array_ptr, x = 0, xlen = 0, y = 0, ylen = 0)
@param [in] array
@param [in] element
@return "the last index of the element"
@return? NOT_FOUND
@return! SearchResult.MISSING
*>
macro rindex_of(array, element)
{
@@ -42,7 +42,7 @@ macro rindex_of(array, element)
{
if (*e == element) return i;
}
return NOT_FOUND?;
return SearchResult.MISSING?;
}
<*
@@ -50,13 +50,13 @@ macro rindex_of(array, element)
@param [in] arr1
@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 @typekind(arr1) == SLICE || @typekind(arr1) == ARRAY
@require @typekind(arr2) == SLICE || @typekind(arr2) == ARRAY
@require @typeis(arr1[0], $typeof(arr2[0])) : "Arrays must have the same type"
@require @typeis(arr1[0], $typeof(arr2[0])) "Arrays must have the same type"
@ensure result.len == arr1.len + arr2.len
*>
macro concat(Allocator allocator, arr1, arr2) @nodiscard
macro concat(arr1, arr2, Allocator allocator) @nodiscard
{
var $Type = $typeof(arr1[0]);
$Type[] result = allocator::alloc_array(allocator, $Type, arr1.len + arr2.len);
@@ -70,6 +70,22 @@ macro concat(Allocator allocator, arr1, arr2) @nodiscard
}
return result;
}
<*
Concatenate two arrays or slices, returning a slice containing the concatenation of them.
@param [in] arr1
@param [in] arr2
@param [&inout] allocator "The allocator to use, default is the heap allocator"
@require @typekind(arr1) == SLICE || @typekind(arr1) == ARRAY
@require @typekind(arr2) == SLICE || @typekind(arr2) == ARRAY
@require @typeis(arr1[0], $typeof(arr2[0])) "Arrays must have the same type"
@ensure result.len == arr1.len + arr2.len
*>
macro concat_new(arr1, arr2, Allocator allocator = allocator::heap()) @nodiscard
{
return concat(arr1, arr2, allocator);
}
<*
Concatenate two arrays or slices, returning a slice containing the concatenation of them,
allocated using the temp allocator.
@@ -78,12 +94,12 @@ macro concat(Allocator allocator, arr1, arr2) @nodiscard
@param [in] arr2
@require @typekind(arr1) == SLICE || @typekind(arr1) == ARRAY
@require @typekind(arr2) == SLICE || @typekind(arr2) == ARRAY
@require @typeis(arr1[0], $typeof(arr2[0])) : "Arrays must have the same type"
@ensure return.len == arr1.len + arr2.len
@require @typeis(arr1[0], $typeof(arr2[0])) "Arrays must have the same type"
@ensure result.len == arr1.len + arr2.len
*>
macro tconcat(arr1, arr2) @nodiscard => concat(tmem, arr1, arr2);
macro tconcat(arr1, arr2) @nodiscard => concat(arr1, arr2, allocator::temp());
module std::core::array::slice{Type};
module std::core::array::slice(<Type>);
struct Slice2d
{

View File

@@ -88,13 +88,13 @@ bitstruct UInt128LE : uint128 @littleendian
}
<*
@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_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"
*>
macro read(bytes, $Type)
{
char[] s;
$switch @typekind(bytes):
$switch (@typekind(bytes))
$case POINTER:
s = (*bytes)[:$Type.sizeof];
$default:
@@ -104,13 +104,13 @@ macro read(bytes, $Type)
}
<*
@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_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"
*>
macro write(x, bytes, $Type)
{
char[] s;
$switch @typekind(bytes):
$switch (@typekind(bytes))
$case POINTER:
s = (*bytes)[:$Type.sizeof];
$default:
@@ -121,7 +121,7 @@ macro write(x, bytes, $Type)
macro is_bitorder($Type)
{
$switch $Type:
$switch ($Type)
$case UShortLE:
$case ShortLE:
$case UIntLE:
@@ -146,7 +146,7 @@ macro is_bitorder($Type)
macro bool is_array_or_slice_of_char(bytes)
{
$switch @typekind(bytes):
$switch (@typekind(bytes))
$case POINTER:
var $Inner = $typefrom($typeof(bytes).inner);
$if $Inner.kindof == ARRAY:
@@ -164,7 +164,7 @@ macro bool is_array_or_slice_of_char(bytes)
macro bool is_arrayptr_or_slice_of_char(bytes)
{
$switch @typekind(bytes):
$switch (@typekind(bytes))
$case POINTER:
var $Inner = $typefrom($typeof(bytes).inner);
$if $Inner.kindof == ARRAY:

View File

@@ -4,135 +4,102 @@
module std::core::builtin;
import libc, std::hash, std::io, std::os::backtrace;
<*
EMPTY_MACRO_SLOT is a value used for implementing optional arguments for macros in an efficient
way. It relies on the fact that distinct types are not implicitly convertable.
You can use `@is_empty_macro_slot()` and `@is_valid_macro_slot()` to figure out whether
the argument has been used or not.
An example:
```c3
macro foo(a, #b = EMPTY_MACRO_SLOT)
{
$if @is_valid_macro_slot(#b):
return invoke_foo2(a, #b);
$else
return invoke_foo1(a);
$endif
}
*>
const EmptySlot EMPTY_MACRO_SLOT @builtin = null;
typedef EmptySlot = void*;
macro @is_empty_macro_slot(#arg) @const @builtin => @typeis(#arg, EmptySlot);
macro @is_valid_macro_slot(#arg) @const @builtin => !@typeis(#arg, EmptySlot);
/*
Use `IteratorResult` when reading the end of an iterator, or accessing a result out of bounds.
*/
faultdef NO_MORE_ELEMENT @builtin;
fault IteratorResult { NO_MORE_ELEMENT }
/*
Use `SearchResult` when trying to return a value from some collection but the element is missing.
*/
faultdef NOT_FOUND @builtin;
fault SearchResult { MISSING }
/*
Use `CastResult` when an attempt at conversion fails.
*/
faultdef TYPE_MISMATCH @builtin;
fault CastResult { TYPE_MISMATCH }
alias VoidFn = fn void();
def VoidFn = fn void();
<*
Stores a variable on the stack, then restores it at the end of the
macro scope.
@param #variable : `the variable to store and restore`
@require values::@is_lvalue(#variable)
@param variable `the variable to store and restore`
*>
macro void @scope(#variable; @body) @builtin
macro void @scope(&variable; @body) @builtin
{
var temp = #variable;
defer #variable = temp;
var temp = *variable;
defer *variable = temp;
@body();
}
<*
Swap two variables
@require $defined(#a = #b, #b = #a) : `The values must be mutually assignable`
@require $assignable(*b, $typeof(*a)) && $assignable(*a, $typeof(*b))
*>
macro void @swap(#a, #b) @builtin
macro void @swap(&a, &b) @builtin
{
var temp = #a;
#a = #b;
#b = temp;
var temp = *a;
*a = *b;
*b = temp;
}
<*
Convert an `any` type to a type, returning an failure if there is a type mismatch.
@param v : `the any to convert to the given type.`
@param $Type : `the type to convert to`
@param v `the any to convert to the given type.`
@param $Type `the type to convert to`
@return `The any.ptr converted to its type.`
@ensure @typeis(return, $Type*)
@return? TYPE_MISMATCH
@return! CastResult.TYPE_MISMATCH
*>
macro anycast(any v, $Type) @builtin
{
if (v.type != $Type.typeid) return TYPE_MISMATCH?;
if (v.type != $Type.typeid) return CastResult.TYPE_MISMATCH?;
return ($Type*)v.ptr;
}
fn bool print_backtrace(String message, int backtraces_to_ignore) @if(env::NATIVE_STACKTRACE) => @stack_mem(0x1100; Allocator smem)
fn bool print_backtrace(String message, int backtraces_to_ignore) @if(env::NATIVE_STACKTRACE)
{
Allocator t = allocator::current_temp;
TempAllocator* new_t = allocator::new_temp_allocator(smem, 0x1000)!!;
allocator::current_temp = new_t;
defer
@pool()
{
allocator::current_temp = t;
new_t.free();
}
void*[256] buffer;
void*[] backtraces = backtrace::capture_current(&buffer);
backtraces_to_ignore++;
BacktraceList? backtrace = backtrace::symbolize_backtrace(tmem, backtraces);
if (catch backtrace) return false;
if (backtrace.len() <= backtraces_to_ignore) return false;
io::eprint("\nERROR: '");
io::eprint(message);
io::eprintn("'");
foreach (i, &trace : backtrace)
{
if (i < backtraces_to_ignore) continue;
String inline_suffix = trace.is_inline ? " [inline]" : "";
if (trace.is_unknown())
void*[256] buffer;
void*[] backtraces = backtrace::capture_current(&buffer);
backtraces_to_ignore++;
BacktraceList! backtrace = backtrace::symbolize_backtrace(backtraces, allocator::temp());
if (catch backtrace) return false;
if (backtrace.len() <= backtraces_to_ignore) return false;
io::eprint("\nERROR: '");
io::eprint(message);
io::eprintn("'");
foreach (i, &trace : backtrace)
{
io::eprintfn(" in ???%s", inline_suffix);
continue;
if (i < backtraces_to_ignore) continue;
String inline_suffix = trace.is_inline ? " [inline]" : "";
if (trace.is_unknown())
{
io::eprintfn(" in ???%s", inline_suffix);
continue;
}
if (trace.has_file())
{
io::eprintfn(" in %s (%s:%d) [%s]%s", trace.function, trace.file, trace.line, trace.object_file, inline_suffix);
continue;
}
io::eprintfn(" in %s (source unavailable) [%s]%s", trace.function, trace.object_file, inline_suffix);
}
if (trace.has_file())
{
io::eprintfn(" in %s (%s:%d) [%s]%s", trace.function, trace.file, trace.line, trace.object_file, inline_suffix);
continue;
}
io::eprintfn(" in %s (source unavailable) [%s]%s", trace.function, trace.object_file, inline_suffix);
}
return true;
return true;
};
}
fn void default_panic(String message, String file, String function, uint line) @if(env::NATIVE_STACKTRACE)
{
$if $defined(io::stderr):
if (!print_backtrace(message, 2))
{
io::eprintfn("\nERROR: '%s', in %s (%s:%d)", message, function, file, line);
return;
}
$endif
$$trap();
@@ -164,7 +131,7 @@ fn void default_panic(String message, String file, String function, uint line) @
$$trap();
}
alias PanicFn = fn void(String message, String file, String function, uint line);
def PanicFn = fn void(String message, String file, String function, uint line);
PanicFn panic = &default_panic;
@@ -180,7 +147,7 @@ fn void panicf(String fmt, String file, String function, uint line, args...)
@stack_mem(512; Allocator allocator)
{
DString s;
s.init(allocator);
s.new_init(allocator: allocator);
s.appendf(fmt, ...args);
in_panic = false;
panic(s.str_view(), file, function, line);
@@ -190,7 +157,7 @@ fn void panicf(String fmt, String file, String function, uint line, args...)
<*
Marks the path as unreachable. This will panic in safe mode, and in fast will simply be assumed
never happens.
@param [in] string : "The panic message or format string"
@param [in] string "The panic message or format string"
*>
macro void unreachable(String string = "Unreachable statement reached.", ...) @builtin @noreturn
{
@@ -202,7 +169,7 @@ macro void unreachable(String string = "Unreachable statement reached.", ...) @b
<*
Marks the path as unsupported, this is similar to unreachable.
@param [in] string : "The error message"
@param [in] string "The error message"
*>
macro void unsupported(String string = "Unsupported function invoked") @builtin @noreturn
{
@@ -234,10 +201,10 @@ macro any.as_inner(&self)
}
<*
@param expr : "the expression to cast"
@param $Type : "the type to cast to"
@param expr "the expression to cast"
@param $Type "the type to cast to"
@require $sizeof(expr) == $Type.sizeof : "Cannot bitcast between types of different size."
@require $sizeof(expr) == $Type.sizeof "Cannot bitcast between types of different size."
@ensure @typeis(return, $Type)
*>
macro bitcast(expr, $Type) @builtin
@@ -252,11 +219,11 @@ macro bitcast(expr, $Type) @builtin
}
<*
@param $Type : `The type of the enum`
@param [in] enum_name : `The name of the enum to search for`
@require $Type.kindof == ENUM : `Only enums may be used`
@param $Type `The type of the enum`
@param [in] enum_name `The name of the enum to search for`
@require $Type.kindof == ENUM `Only enums may be used`
@ensure @typeis(return, $Type)
@return? NOT_FOUND
@return! SearchResult.MISSING
*>
macro enum_by_name($Type, String enum_name) @builtin
{
@@ -265,16 +232,16 @@ macro enum_by_name($Type, String enum_name) @builtin
{
if (name == enum_name) return $Type.from_ordinal(i);
}
return NOT_FOUND?;
return SearchResult.MISSING?;
}
<*
@param $Type : `The type of the enum`
@require $Type.kindof == ENUM : `Only enums may be used`
@require $defined($Type.#value) : `Expected '#value' to match an enum associated value`
@require $assignable(value, $typeof(($Type){}.#value)) : `Expected the value to match the type of the associated value`
@param $Type `The type of the enum`
@require $Type.kindof == ENUM `Only enums may be used`
@require $defined($Type.#value1) `Expected '#value' to match an enum associated value`
@require $assignable(value, $typeof($Type{}.#value)) `Expected the value to match the type of the associated value`
@ensure @typeis(return, $Type)
@return? NOT_FOUND
@return! SearchResult.MISSING
*>
macro @enum_from_value($Type, #value, value) @builtin
{
@@ -283,19 +250,19 @@ macro @enum_from_value($Type, #value, value) @builtin
{
if (e.#value == value) return e;
}
return NOT_FOUND?;
return SearchResult.MISSING?;
}
<*
Mark an expression as likely to be true
@param #value : "expression to be marked likely"
@param $probability : "in the range 0 - 1"
@param #value "expression to be marked likely"
@param $probability "in the range 0 - 1"
@require $probability >= 0 && $probability <= 1.0
*>
macro bool @likely(bool #value, $probability = 1.0) @builtin
{
$switch:
$switch
$case env::BUILTIN_EXPECT_IS_DISABLED:
return #value;
$case $probability == 1.0:
@@ -308,13 +275,13 @@ macro bool @likely(bool #value, $probability = 1.0) @builtin
<*
Mark an expression as unlikely to be true
@param #value : "expression to be marked unlikely"
@param $probability : "in the range 0 - 1"
@param #value "expression to be marked unlikely"
@param $probability "in the range 0 - 1"
@require $probability >= 0 && $probability <= 1.0
*>
macro bool @unlikely(bool #value, $probability = 1.0) @builtin
{
$switch:
$switch
$case env::BUILTIN_EXPECT_IS_DISABLED:
return #value;
$case $probability == 1.0:
@@ -331,7 +298,7 @@ macro bool @unlikely(bool #value, $probability = 1.0) @builtin
*>
macro @expect(#value, expected, $probability = 1.0) @builtin
{
$switch:
$switch
$case env::BUILTIN_EXPECT_IS_DISABLED:
return #value == expected;
$case $probability == 1.0:
@@ -356,9 +323,9 @@ enum PrefetchLocality
<*
Prefetch a pointer.
@param [in] ptr : `Pointer to prefetch`
@param $locality : `Locality ranging from none to extremely local`
@param $write : `Prefetch for write, otherwise prefetch for read.`
@param [in] ptr `Pointer to prefetch`
@param $locality `Locality ranging from none to extremely local`
@param $write `Prefetch for write, otherwise prefetch for read.`
*>
macro @prefetch(void* ptr, PrefetchLocality $locality = VERY_NEAR, bool $write = false) @builtin
{
@@ -382,10 +349,10 @@ macro swizzle2(v, v2, ...) @builtin
@require @typekind(#expr) == OPTIONAL : `@catch expects an Optional value`
*>
macro fault @catch(#expr) @builtin
macro anyfault @catch(#expr) @builtin
{
if (catch f = #expr) return f;
return {};
return anyfault {};
}
<*
@@ -400,12 +367,9 @@ macro bool @ok(#expr) @builtin
return true;
}
<*
@require $defined(&#value, (char*)&#value) : "This must be a value that can be viewed as a char array"
*>
macro char[] @as_char_view(#value) @builtin
macro char[] @as_char_view(&value) @builtin
{
return ((char*)&#value)[:$sizeof(#value)];
return ((char*)value)[:$sizeof(*value)];
}
macro isz @str_find(String $string, String $needle) @builtin => $$str_find($string, $needle);
@@ -413,40 +377,26 @@ macro String @str_upper(String $str) @builtin => $$str_upper($str);
macro String @str_lower(String $str) @builtin => $$str_lower($str);
macro uint @str_hash(String $str) @builtin => $$str_hash($str);
macro @generic_hash_core(h, value)
{
h ^= (uint)value; // insert lowest 32 bits
h *= 0x96f59e5b; // diffuse them up
h ^= h >> 16; // diffuse them down
return h;
}
macro @generic_hash(value)
{
uint h = @generic_hash_core((uint)0x3efd4391, value);
$for var $cnt = 4; $cnt < $sizeof(value); $cnt += 4:
value >>= 32; // reduce value
h = @generic_hash_core(h, value);
$endfor
return h;
}
macro uint int.hash(int i) => @generic_hash(i);
macro uint uint.hash(uint i) => @generic_hash(i);
macro uint short.hash(short s) => @generic_hash(s);
macro uint ushort.hash(ushort s) => @generic_hash(s);
macro uint char.hash(char c) => @generic_hash(c);
macro uint ichar.hash(ichar c) => @generic_hash(c);
macro uint long.hash(long i) => @generic_hash(i);
macro uint ulong.hash(ulong i) => @generic_hash(i);
macro uint int128.hash(int128 i) => @generic_hash(i);
macro uint uint128.hash(uint128 i) => @generic_hash(i);
macro uint bool.hash(bool b) => @generic_hash(b);
macro uint typeid.hash(typeid t) => @generic_hash(((ulong)(uptr)t));
macro uint String.hash(String c) => (uint)fnv32a::hash(c);
macro uint char[].hash(char[] c) => (uint)fnv32a::hash(c);
macro uint void*.hash(void* ptr) => @generic_hash(((ulong)(uptr)ptr));
macro uint int.hash(int i) => i;
macro uint uint.hash(uint i) => i;
macro uint short.hash(short s) => s;
macro uint ushort.hash(ushort s) => s;
macro uint char.hash(char c) => c;
macro uint ichar.hash(ichar c) => c;
macro uint long.hash(long i) => (uint)((i >> 32) ^ i);
macro uint ulong.hash(ulong i) => (uint)((i >> 32) ^ i);
macro uint int128.hash(int128 i) => (uint)((i >> 96) ^ (i >> 64) ^ (i >> 32) ^ i);
macro uint uint128.hash(uint128 i) => (uint)((i >> 96) ^ (i >> 64) ^ (i >> 32) ^ i);
macro uint bool.hash(bool b) => (uint)b;
macro uint typeid.hash(typeid t) => ((ulong)(uptr)t).hash();
macro uint String.hash(String c) => (uint)fnv32a::encode(c);
macro uint char[].hash(char[] c) => (uint)fnv32a::encode(c);
macro uint void*.hash(void* ptr) => ((ulong)(uptr)ptr).hash();
distinct EmptySlot = void*;
const EmptySlot EMPTY_MACRO_SLOT @builtin = null;
macro @is_empty_macro_slot(#arg) @builtin => @typeis(#arg, EmptySlot);
macro @is_valid_macro_slot(#arg) @builtin => !@typeis(#arg, EmptySlot);
const MAX_FRAMEADDRESS = 128;
<*

View File

@@ -8,7 +8,7 @@ module std::core::builtin;
*>
macro less(a, b) @builtin
{
$switch:
$switch
$case $defined(a.less):
return a.less(b);
$case $defined(a.compare_to):
@@ -23,7 +23,7 @@ macro less(a, b) @builtin
*>
macro less_eq(a, b) @builtin
{
$switch:
$switch
$case $defined(a.less):
return !b.less(a);
$case $defined(a.compare_to):
@@ -38,7 +38,7 @@ macro less_eq(a, b) @builtin
*>
macro greater(a, b) @builtin
{
$switch:
$switch
$case $defined(a.less):
return b.less(a);
$case $defined(a.compare_to):
@@ -53,7 +53,7 @@ macro greater(a, b) @builtin
*>
macro int compare_to(a, b) @builtin
{
$switch:
$switch
$case $defined(a.compare_to):
return a.compare_to(b);
$case $defined(a.less):
@@ -67,7 +67,7 @@ macro int compare_to(a, b) @builtin
*>
macro greater_eq(a, b) @builtin
{
$switch:
$switch
$case $defined(a.less):
return !a.less(b);
$case $defined(a.compare_to):
@@ -78,11 +78,11 @@ macro greater_eq(a, b) @builtin
}
<*
@require types::@equatable_value(a) && types::@equatable_value(b) : `values must be equatable`
@require types::@equatable_value(a) && types::@equatable_value(b) `values must be equatable`
*>
macro bool equals(a, b) @builtin
{
$switch:
$switch
$case $defined(a.equals, a.equals(b)):
return a.equals(b);
$case $defined(a.compare_to, a.compare_to(b)):
@@ -100,7 +100,7 @@ macro min(x, ...) @builtin
return less(x, $vaarg[0]) ? x : $vaarg[0];
$else
var result = x;
$for var $i = 0; $i < $vacount; $i++:
$for (var $i = 0; $i < $vacount; $i++)
if (less($vaarg[$i], result))
{
result = $vaarg[$i];
@@ -116,7 +116,7 @@ macro max(x, ...) @builtin
return greater(x, $vaarg[0]) ? x : $vaarg[0];
$else
var result = x;
$for var $i = 0; $i < $vacount; $i++:
$for (var $i = 0; $i < $vacount; $i++)
if (greater($vaarg[$i], result))
{
result = $vaarg[$i];

View File

@@ -16,20 +16,20 @@ $assert C_SHORT_SIZE <= C_INT_SIZE;
$assert C_INT_SIZE <= C_LONG_SIZE;
$assert C_LONG_SIZE <= C_LONG_LONG_SIZE;
alias CShort = $typefrom(signed_int_from_bitsize($$C_SHORT_SIZE));
alias CUShort = $typefrom(unsigned_int_from_bitsize($$C_SHORT_SIZE));
alias CInt = $typefrom(signed_int_from_bitsize($$C_INT_SIZE));
alias CUInt = $typefrom(unsigned_int_from_bitsize($$C_INT_SIZE));
alias CLong = $typefrom(signed_int_from_bitsize($$C_LONG_SIZE));
alias CULong = $typefrom(unsigned_int_from_bitsize($$C_LONG_SIZE));
alias CLongLong = $typefrom(signed_int_from_bitsize($$C_LONG_LONG_SIZE));
alias CULongLong = $typefrom(unsigned_int_from_bitsize($$C_LONG_LONG_SIZE));
alias CSChar = ichar;
alias CUChar = char;
def CShort = $typefrom(signed_int_from_bitsize($$C_SHORT_SIZE));
def CUShort = $typefrom(unsigned_int_from_bitsize($$C_SHORT_SIZE));
def CInt = $typefrom(signed_int_from_bitsize($$C_INT_SIZE));
def CUInt = $typefrom(unsigned_int_from_bitsize($$C_INT_SIZE));
def CLong = $typefrom(signed_int_from_bitsize($$C_LONG_SIZE));
def CULong = $typefrom(unsigned_int_from_bitsize($$C_LONG_SIZE));
def CLongLong = $typefrom(signed_int_from_bitsize($$C_LONG_LONG_SIZE));
def CULongLong = $typefrom(unsigned_int_from_bitsize($$C_LONG_LONG_SIZE));
def CSChar = ichar;
def CUChar = char;
alias CChar = $typefrom($$C_CHAR_IS_SIGNED ? ichar.typeid : char.typeid);
def CChar = $typefrom($$C_CHAR_IS_SIGNED ? ichar.typeid : char.typeid);
enum CBool : char
enum CBool : CInt
{
FALSE,
TRUE
@@ -38,7 +38,7 @@ enum CBool : char
// Helper macros
macro typeid signed_int_from_bitsize(usz $bitsize) @private
{
$switch $bitsize:
$switch ($bitsize)
$case 128: return int128.typeid;
$case 64: return long.typeid;
$case 32: return int.typeid;
@@ -50,7 +50,7 @@ macro typeid signed_int_from_bitsize(usz $bitsize) @private
macro typeid unsigned_int_from_bitsize(usz $bitsize) @private
{
$switch $bitsize:
$switch ($bitsize)
$case 128: return uint128.typeid;
$case 64: return ulong.typeid;
$case 32: return uint.typeid;

View File

@@ -10,31 +10,30 @@ const uint UTF16_SURROGATE_LOW_VALUE @private = 0xDC00;
const uint UTF16_SURROGATE_HIGH_VALUE @private = 0xD800;
<*
@param c : `The utf32 codepoint to convert`
@param [out] output : `the resulting buffer`
@return? string::CONVERSION_FAILED
@param c `The utf32 codepoint to convert`
@param [out] output `the resulting buffer`
*>
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 UnicodeResult.CONVERSION_FAILED?;
switch (true)
{
case c <= 0x7f:
output[0] = (char)c;
return 1;
case c <= 0x7ff:
if (output.len < 2) return string::CONVERSION_FAILED?;
if (output.len < 2) return UnicodeResult.CONVERSION_FAILED?;
output[0] = (char)(0xC0 | c >> 6);
output[1] = (char)(0x80 | (c & 0x3F));
return 2;
case c <= 0xffff:
if (output.len < 3) return string::CONVERSION_FAILED?;
if (output.len < 3) return UnicodeResult.CONVERSION_FAILED?;
output[0] = (char)(0xE0 | c >> 12);
output[1] = (char)(0x80 | (c >> 6 & 0x3F));
output[2] = (char)(0x80 | (c & 0x3F));
return 3;
case c <= 0x10ffff:
if (output.len < 4) return string::CONVERSION_FAILED?;
if (output.len < 4) return UnicodeResult.CONVERSION_FAILED?;
output[0] = (char)(0xF0 | c >> 18);
output[1] = (char)(0x80 | (c >> 12 & 0x3F));
output[2] = (char)(0x80 | (c >> 6 & 0x3F));
@@ -42,15 +41,15 @@ fn usz? char32_to_utf8(Char32 c, char[] output)
return 4;
default:
// 0x10FFFF and above is not defined.
return string::CONVERSION_FAILED?;
return UnicodeResult.CONVERSION_FAILED?;
}
}
<*
Convert a code pointer into 1-2 UTF16 characters.
@param c : `The character to convert.`
@param [inout] output : `the resulting UTF16 buffer to write to.`
@param c `The character to convert.`
@param [inout] output `the resulting UTF16 buffer to write to.`
*>
fn void char32_to_utf16_unsafe(Char32 c, Char16** output)
{
@@ -70,11 +69,11 @@ fn void char32_to_utf16_unsafe(Char32 c, Char16** output)
<*
Convert 1-2 UTF16 data points into UTF8.
@param [in] ptr : `The UTF16 data to convert.`
@param [inout] available : `amount of UTF16 data available.`
@param [inout] output : `the resulting utf8 buffer to write to.`
@param [in] ptr `The UTF16 data to convert.`
@param [inout] available `amount of UTF16 data available.`
@param [inout] output `the resulting utf8 buffer to write to.`
*>
fn void? char16_to_utf8_unsafe(Char16 *ptr, usz *available, char** output)
fn void! char16_to_utf8_unsafe(Char16 *ptr, usz *available, char** output)
{
Char16 high = *ptr;
if (high & UTF16_SURROGATE_GENERIC_MASK != UTF16_SURROGATE_GENERIC_VALUE)
@@ -84,15 +83,15 @@ fn void? char16_to_utf8_unsafe(Char16 *ptr, usz *available, char** output)
return;
}
// 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 UnicodeResult.INVALID_UTF16?;
// Unmatched high surrogate is an error
if (*available == 1) return string::INVALID_UTF16?;
if (*available == 1) return UnicodeResult.INVALID_UTF16?;
Char16 low = ptr[1];
// Unmatched high surrogate, invalid
if (low & UTF16_SURROGATE_MASK != UTF16_SURROGATE_LOW_VALUE) return string::INVALID_UTF16?;
if (low & UTF16_SURROGATE_MASK != UTF16_SURROGATE_LOW_VALUE) return UnicodeResult.INVALID_UTF16?;
// The high bits of the codepoint are the value bits of the high surrogate
// The low bits of the codepoint are the value bits of the low surrogate
@@ -102,8 +101,8 @@ fn void? char16_to_utf8_unsafe(Char16 *ptr, usz *available, char** output)
*available = 2;
}
<*
@param c : `The utf32 codepoint to convert`
@param [inout] output : `the resulting buffer`
@param c `The utf32 codepoint to convert`
@param [inout] output `the resulting buffer`
*>
fn usz char32_to_utf8_unsafe(Char32 c, char** output)
{
@@ -131,14 +130,14 @@ fn usz char32_to_utf8_unsafe(Char32 c, char** output)
}
<*
@param [in] ptr : `pointer to the first character to parse`
@param [inout] size : `Set to max characters to read, set to characters read`
@param [in] ptr `pointer to the first character to parse`
@param [inout] size `Set to max characters to read, set to characters read`
@return `the parsed 32 bit codepoint`
*>
fn Char32? utf8_to_char32(char* ptr, usz* size)
fn Char32! utf8_to_char32(char* ptr, usz* size)
{
usz max_size = *size;
if (max_size < 1) return string::INVALID_UTF8?;
if (max_size < 1) return UnicodeResult.INVALID_UTF8?;
char c = (ptr++)[0];
if ((c & 0x80) == 0)
@@ -148,45 +147,45 @@ fn Char32? utf8_to_char32(char* ptr, usz* size)
}
if ((c & 0xE0) == 0xC0)
{
if (max_size < 2) return string::INVALID_UTF8?;
if (max_size < 2) return UnicodeResult.INVALID_UTF8?;
*size = 2;
Char32 uc = (c & 0x1F) << 6;
c = *ptr;
// Overlong sequence or invalid second.
if (!uc || c & 0xC0 != 0x80) return string::INVALID_UTF8?;
if (!uc || c & 0xC0 != 0x80) return UnicodeResult.INVALID_UTF8?;
return uc + c & 0x3F;
}
if ((c & 0xF0) == 0xE0)
{
if (max_size < 3) return string::INVALID_UTF8?;
if (max_size < 3) return UnicodeResult.INVALID_UTF8?;
*size = 3;
Char32 uc = (c & 0x0F) << 12;
c = ptr++[0];
if (c & 0xC0 != 0x80) return string::INVALID_UTF8?;
if (c & 0xC0 != 0x80) return UnicodeResult.INVALID_UTF8?;
uc += (c & 0x3F) << 6;
c = ptr++[0];
// Overlong sequence or invalid last
if (!uc || c & 0xC0 != 0x80) return string::INVALID_UTF8?;
if (!uc || c & 0xC0 != 0x80) return UnicodeResult.INVALID_UTF8?;
return uc + c & 0x3F;
}
if (max_size < 4) return string::INVALID_UTF8?;
if ((c & 0xF8) != 0xF0) return string::INVALID_UTF8?;
if (max_size < 4) return UnicodeResult.INVALID_UTF8?;
if ((c & 0xF8) != 0xF0) return UnicodeResult.INVALID_UTF8?;
*size = 4;
Char32 uc = (c & 0x07) << 18;
c = ptr++[0];
if (c & 0xC0 != 0x80) return string::INVALID_UTF8?;
if (c & 0xC0 != 0x80) return UnicodeResult.INVALID_UTF8?;
uc += (c & 0x3F) << 12;
c = ptr++[0];
if (c & 0xC0 != 0x80) return string::INVALID_UTF8?;
if (c & 0xC0 != 0x80) return UnicodeResult.INVALID_UTF8?;
uc += (c & 0x3F) << 6;
c = ptr++[0];
// Overlong sequence or invalid last
if (!uc || c & 0xC0 != 0x80) return string::INVALID_UTF8?;
if (!uc || c & 0xC0 != 0x80) return UnicodeResult.INVALID_UTF8?;
return uc + c & 0x3F;
}
<*
@param utf8 : `An UTF-8 encoded slice of bytes`
@param utf8 `An UTF-8 encoded slice of bytes`
@return `the number of encoded code points`
*>
fn usz utf8_codepoints(String utf8)
@@ -201,7 +200,7 @@ fn usz utf8_codepoints(String utf8)
<*
Calculate the UTF8 length required to encode an UTF32 array.
@param [in] utf32 : `the utf32 data to calculate from`
@param [in] utf32 `the utf32 data to calculate from`
@return `the length of the resulting UTF8 array`
*>
fn usz utf8len_for_utf32(Char32[] utf32)
@@ -226,7 +225,7 @@ fn usz utf8len_for_utf32(Char32[] utf32)
<*
Calculate the UTF8 length required to encode an UTF16 array.
@param [in] utf16 : `the utf16 data to calculate from`
@param [in] utf16 `the utf16 data to calculate from`
@return `the length of the resulting UTF8 array`
*>
fn usz utf8len_for_utf16(Char16[] utf16)
@@ -258,7 +257,7 @@ fn usz utf8len_for_utf16(Char16[] utf16)
<*
Calculate the UTF16 length required to encode a UTF8 array.
@param utf8 : `the utf8 data to calculate from`
@param utf8 `the utf8 data to calculate from`
@return `the length of the resulting UTF16 array`
*>
fn usz utf16len_for_utf8(String utf8)
@@ -281,7 +280,7 @@ fn usz utf16len_for_utf8(String utf8)
}
<*
@param [in] utf32 : `the UTF32 array to check the length for`
@param [in] utf32 `the UTF32 array to check the length for`
@return `the required length of an UTF16 array to hold the UTF32 data.`
*>
fn usz utf16len_for_utf32(Char32[] utf32)
@@ -301,7 +300,7 @@ fn usz utf16len_for_utf32(Char32[] utf32)
@param [out] utf8_buffer
@return `the number of bytes written.`
*>
fn usz? utf32to8(Char32[] utf32, char[] utf8_buffer)
fn usz! utf32to8(Char32[] utf32, char[] utf8_buffer)
{
char[] buffer = utf8_buffer;
foreach (uc : utf32)
@@ -321,7 +320,7 @@ fn usz? utf32to8(Char32[] utf32, char[] utf8_buffer)
@param [out] utf32_buffer
@return `the number of Char32s written.`
*>
fn usz? utf8to32(String utf8, Char32[] utf32_buffer)
fn usz! utf8to32(String utf8, Char32[] utf32_buffer)
{
usz len = utf8.len;
Char32* ptr = utf32_buffer.ptr;
@@ -329,7 +328,7 @@ fn usz? utf8to32(String utf8, Char32[] utf32_buffer)
usz buf_len = utf32_buffer.len;
for (usz i = 0; i < len;)
{
if (len32 == buf_len) return string::CONVERSION_FAILED?;
if (len32 == buf_len) return UnicodeResult.CONVERSION_FAILED?;
usz width = len - i;
Char32 uc = utf8_to_char32(&utf8[i], &width) @inline!;
i += width;
@@ -345,10 +344,10 @@ fn usz? utf8to32(String utf8, Char32[] utf32_buffer)
checking. This will assume the buffer is sufficiently large to hold
the converted data.
@param [in] utf16 : `The UTF16 array containing the data to convert.`
@param [out] utf8_buffer : `the (sufficiently large) buffer to hold the UTF16 data.`
@param [in] utf16 `The UTF16 array containing the data to convert.`
@param [out] utf8_buffer `the (sufficiently large) buffer to hold the UTF16 data.`
*>
fn void? utf16to8_unsafe(Char16[] utf16, char* utf8_buffer)
fn void! utf16to8_unsafe(Char16[] utf16, char* utf8_buffer)
{
usz len16 = utf16.len;
for (usz i = 0; i < len16;)
@@ -364,10 +363,10 @@ fn void? utf16to8_unsafe(Char16[] utf16, char* utf8_buffer)
checking. This will assume the buffer is sufficiently large to hold
the converted data.
@param [in] utf8 : `The UTF8 buffer containing the data to convert.`
@param [out] utf32_buffer : `the (sufficiently large) buffer to hold the UTF8 data.`
@param [in] utf8 `The UTF8 buffer containing the data to convert.`
@param [out] utf32_buffer `the (sufficiently large) buffer to hold the UTF8 data.`
*>
fn void? utf8to32_unsafe(String utf8, Char32* utf32_buffer)
fn void! utf8to32_unsafe(String utf8, Char32* utf32_buffer)
{
usz len = utf8.len;
for (usz i = 0; i < len;)
@@ -384,10 +383,10 @@ fn void? utf8to32_unsafe(String utf8, Char32* utf32_buffer)
checking. This will assume the buffer is sufficiently large to hold
the converted data.
@param [in] utf8 : `The UTF8 buffer containing the data to convert.`
@param [out] utf16_buffer : `the (sufficiently large) buffer to hold the UTF8 data.`
@param [in] utf8 `The UTF8 buffer containing the data to convert.`
@param [out] utf16_buffer `the (sufficiently large) buffer to hold the UTF8 data.`
*>
fn void? utf8to16_unsafe(String utf8, Char16* utf16_buffer)
fn void! utf8to16_unsafe(String utf8, Char16* utf16_buffer)
{
usz len = utf8.len;
for (usz i = 0; i < len;)
@@ -404,8 +403,8 @@ fn void? utf8to16_unsafe(String utf8, Char16* utf16_buffer)
checking. This will assume the buffer is sufficiently large to hold
the converted data.
@param [in] utf32 : `The UTF32 buffer containing the data to convert.`
@param [out] utf8_buffer : `the (sufficiently large) buffer to hold the UTF8 data.`
@param [in] utf32 `The UTF32 buffer containing the data to convert.`
@param [out] utf8_buffer `the (sufficiently large) buffer to hold the UTF8 data.`
*>
fn void utf32to8_unsafe(Char32[] utf32, char* utf8_buffer)
{

View File

@@ -1,15 +1,15 @@
module std::core::dstring;
import std::io;
typedef DString (OutStream) = DStringOpaque*;
typedef DStringOpaque = void;
distinct DString (OutStream) = DStringOpaque*;
distinct DStringOpaque = void;
const usz MIN_CAPACITY @private = 16;
<*
@require !self.data() : "String already initialized"
@require !self.data() "String already initialized"
*>
fn DString DString.init(&self, Allocator allocator, usz capacity = MIN_CAPACITY)
fn DString DString.new_init(&self, usz capacity = MIN_CAPACITY, Allocator allocator = allocator::heap())
{
if (capacity < MIN_CAPACITY) capacity = MIN_CAPACITY;
StringData* data = allocator::alloc_with_padding(allocator, StringData, capacity)!!;
@@ -20,24 +20,25 @@ fn DString DString.init(&self, Allocator allocator, usz capacity = MIN_CAPACITY)
}
<*
@require !self.data() : "String already initialized"
@require !self.data() "String already initialized"
*>
fn DString DString.tinit(&self, usz capacity = MIN_CAPACITY)
fn DString DString.temp_init(&self, usz capacity = MIN_CAPACITY)
{
return self.init(tmem, capacity) @inline;
self.new_init(capacity, allocator::temp()) @inline;
return *self;
}
fn DString new_with_capacity(Allocator allocator, usz capacity)
fn DString new_with_capacity(usz capacity, Allocator allocator = allocator::heap())
{
return (DString){}.init(allocator, capacity);
return DString{}.new_init(capacity, allocator);
}
fn DString temp_with_capacity(usz capacity) => new_with_capacity(tmem, capacity) @inline;
fn DString temp_with_capacity(usz capacity) => new_with_capacity(capacity, allocator::temp()) @inline;
fn DString new(Allocator allocator, String c = "")
fn DString new(String c = "", Allocator allocator = allocator::heap())
{
usz len = c.len;
StringData* data = (StringData*)new_with_capacity(allocator, len);
StringData* data = (StringData*)new_with_capacity(len, allocator);
if (len)
{
data.len = len;
@@ -46,7 +47,7 @@ fn DString new(Allocator allocator, String c = "")
return (DString)data;
}
fn DString temp(String s = "") => new(tmem, s) @inline;
fn DString temp_new(String s = "") => new(s, allocator::temp()) @inline;
fn void DString.replace_char(self, char ch, char replacement)
@@ -69,8 +70,7 @@ fn void DString.replace(&self, String needle, String replacement)
self.replace_char(needle[0], replacement[0]);
return;
}
@pool()
{
@pool(data.allocator) {
String str = self.tcopy_str();
self.clear();
usz len = str.len;
@@ -99,16 +99,16 @@ fn void DString.replace(&self, String needle, String replacement)
};
}
fn DString DString.concat(self, Allocator allocator, DString b)
fn DString DString.new_concat(self, DString b, Allocator allocator = allocator::heap())
{
DString string;
string.init(allocator, self.len() + b.len());
string.new_init(self.len() + b.len(), allocator);
string.append(self);
string.append(b);
return string;
}
fn DString DString.tconcat(self, DString b) => self.concat(tmem, b);
fn DString DString.temp_concat(self, DString b) => self.new_concat(b, allocator::temp());
fn ZString DString.zstr_view(&self)
{
@@ -157,7 +157,7 @@ fn String DString.str_view(self)
<*
@require index < self.len()
@require self.data() != null : "Empty string"
@require self.data() "Empty string"
*>
fn char DString.char_at(self, usz index) @operator([])
{
@@ -166,7 +166,7 @@ fn char DString.char_at(self, usz index) @operator([])
<*
@require index < self.len()
@require self.data() != null : "Empty string"
@require self.data() "Empty string"
*>
fn char* DString.char_ref(&self, usz index) @operator(&[])
{
@@ -218,18 +218,23 @@ fn usz DString.append_char32(&self, Char32 c)
return n;
}
fn DString DString.tcopy(&self) => self.copy(tmem);
fn DString DString.tcopy(&self) => self.copy(allocator::temp());
fn DString DString.copy(self, Allocator allocator)
fn DString DString.copy(self, Allocator allocator = null)
{
if (!self) return new(allocator);
if (!self)
{
if (allocator) return new_with_capacity(0, allocator);
return (DString)null;
}
StringData* data = self.data();
DString new_string = new_with_capacity(allocator, data.capacity);
if (!allocator) allocator = allocator::heap();
DString new_string = new_with_capacity(data.capacity, allocator);
mem::copy((char*)new_string.data(), (char*)data, StringData.sizeof + data.len);
return new_string;
}
fn ZString DString.copy_zstr(self, Allocator allocator)
fn ZString DString.copy_zstr(self, Allocator allocator = allocator::heap())
{
usz str_len = self.len();
if (!str_len)
@@ -243,12 +248,12 @@ fn ZString DString.copy_zstr(self, Allocator allocator)
return (ZString)zstr;
}
fn String DString.copy_str(self, Allocator allocator)
fn String DString.copy_str(self, Allocator allocator = allocator::heap())
{
return (String)self.copy_zstr(allocator)[:self.len()];
}
fn String DString.tcopy_str(self) => self.copy_str(tmem) @inline;
fn String DString.tcopy_str(self) => self.copy_str(allocator::temp()) @inline;
fn bool DString.equals(self, DString other_string)
{
@@ -298,7 +303,7 @@ fn void DString.append_chars(&self, String str)
if (!other_len) return;
if (!*self)
{
*self = temp(str);
*self = new(str);
return;
}
self.reserve(other_len);
@@ -307,7 +312,7 @@ fn void DString.append_chars(&self, String str)
data.len += other_len;
}
fn Char32[] DString.copy_utf32(&self, Allocator allocator)
fn Char32[] DString.copy_utf32(&self, Allocator allocator = allocator::heap())
{
return self.str_view().to_utf32(allocator) @inline!!;
}
@@ -325,13 +330,13 @@ fn void DString.clear(self)
self.data().len = 0;
}
fn usz? DString.write(&self, char[] buffer) @dynamic
fn usz! DString.write(&self, char[] buffer) @dynamic
{
self.append_chars((String)buffer);
return buffer.len;
}
fn void? DString.write_byte(&self, char c) @dynamic
fn void! DString.write_byte(&self, char c) @dynamic
{
self.append_char(c);
}
@@ -340,7 +345,7 @@ fn void DString.append_char(&self, char c)
{
if (!*self)
{
*self = temp_with_capacity(MIN_CAPACITY);
*self = new_with_capacity(MIN_CAPACITY);
}
self.reserve(1);
StringData* data = self.data();
@@ -350,7 +355,7 @@ fn void DString.append_char(&self, char c)
<*
@require start < self.len()
@require end < self.len()
@require end >= start : "End must be same or equal to the start"
@require end >= start "End must be same or equal to the start"
*>
fn void DString.delete_range(&self, usz start, usz end)
{
@@ -382,7 +387,7 @@ fn void DString.delete(&self, usz start, usz len = 1)
macro void DString.append(&self, value)
{
var $Type = $typeof(value);
$switch $Type:
$switch ($Type)
$case char:
$case ichar:
self.append_char(value);
@@ -393,7 +398,7 @@ macro void DString.append(&self, value)
$case Char32:
self.append_char32(value);
$default:
$switch:
$switch
$case $defined((Char32)value):
self.append_char32((Char32)value);
$case $defined((String)value):
@@ -514,7 +519,7 @@ fn usz DString.insert_utf32_at(&self, usz index, Char32[] chars)
macro void DString.insert_at(&self, usz index, value)
{
var $Type = $typeof(value);
$switch $Type:
$switch ($Type)
$case char:
$case ichar:
self.insert_char_at(index, value);
@@ -525,7 +530,7 @@ macro void DString.insert_at(&self, usz index, value)
$case Char32:
self.insert_char32_at(index, value);
$default:
$switch:
$switch
$case $defined((Char32)value):
self.insert_char32_at(index, (Char32)value);
$case $defined((String)value):
@@ -536,19 +541,21 @@ macro void DString.insert_at(&self, usz index, value)
$endswitch
}
import libc;
fn usz? DString.appendf(&self, String format, args...) @maydiscard
fn usz! DString.appendf(&self, String format, args...) @maydiscard
{
if (!self.data()) self.tinit(format.len + 20);
Formatter formatter;
formatter.init(&out_string_append_fn, self);
return formatter.vprintf(format, args);
if (!self.data()) self.new_init(format.len + 20);
@pool(self.data().allocator)
{
Formatter formatter;
formatter.init(&out_string_append_fn, self);
return formatter.vprintf(format, args);
};
}
fn usz? DString.appendfn(&self, String format, args...) @maydiscard
fn usz! DString.appendfn(&self, String format, args...) @maydiscard
{
if (!self.data()) self.tinit(format.len + 20);
@pool()
if (!self.data()) self.new_init(format.len + 20);
@pool(self.data().allocator)
{
Formatter formatter;
formatter.init(&out_string_append_fn, self);
@@ -558,15 +565,15 @@ fn usz? DString.appendfn(&self, String format, args...) @maydiscard
};
}
fn DString join(Allocator allocator, String[] s, String joiner)
fn DString new_join(String[] s, String joiner, Allocator allocator = allocator::heap())
{
if (!s.len) return new(allocator);
if (!s.len) return (DString)null;
usz total_size = joiner.len * s.len;
foreach (String* &str : s)
{
total_size += str.len;
}
DString res = new_with_capacity(allocator, total_size);
DString res = new_with_capacity(total_size, allocator);
res.append(s[0]);
foreach (String* &str : s[1..])
{
@@ -576,7 +583,7 @@ fn DString join(Allocator allocator, String[] s, String joiner)
return res;
}
fn void? out_string_append_fn(void* data, char c) @private
fn void! out_string_append_fn(void* data, char c) @private
{
DString* s = data;
s.append_char(c);
@@ -606,7 +613,7 @@ fn void DString.reserve(&self, usz addition)
StringData* data = self.data();
if (!data)
{
*self = dstring::temp_with_capacity(addition);
*self = dstring::new_with_capacity(addition);
return;
}
usz len = data.len + addition;
@@ -618,7 +625,7 @@ fn void DString.reserve(&self, usz addition)
*self = (DString)allocator::realloc(data.allocator, data, StringData.sizeof + new_capacity);
}
fn usz? DString.read_from_stream(&self, InStream reader)
fn usz! DString.read_from_stream(&self, InStream reader)
{
if (&reader.available)
{

View File

@@ -57,7 +57,6 @@ enum OsType
HURD,
WASI,
EMSCRIPTEN,
ANDROID,
}
enum ArchType
@@ -151,17 +150,15 @@ const bool FREEBSD = LIBC && OS_TYPE == FREEBSD;
const bool NETBSD = LIBC && OS_TYPE == NETBSD;
const bool BSD_FAMILY = env::FREEBSD || env::OPENBSD || env::NETBSD;
const bool WASI = LIBC && OS_TYPE == WASI;
const bool ANDROID = LIBC && OS_TYPE == ANDROID;
const bool WASM_NOLIBC @builtin = !LIBC && ARCH_TYPE == ArchType.WASM32 || ARCH_TYPE == ArchType.WASM64;
const bool ADDRESS_SANITIZER = $$ADDRESS_SANITIZER;
const bool MEMORY_SANITIZER = $$MEMORY_SANITIZER;
const bool THREAD_SANITIZER = $$THREAD_SANITIZER;
const bool ANY_SANITIZER = ADDRESS_SANITIZER || MEMORY_SANITIZER || THREAD_SANITIZER;
const int LANGUAGE_DEV_VERSION = $$LANGUAGE_DEV_VERSION;
macro bool os_is_darwin() @const
{
$switch OS_TYPE:
$switch (OS_TYPE)
$case IOS:
$case MACOS:
$case TVOS:
@@ -174,7 +171,7 @@ macro bool os_is_darwin() @const
macro bool os_is_posix() @const
{
$switch OS_TYPE:
$switch (OS_TYPE)
$case IOS:
$case MACOS:
$case NETBSD:
@@ -185,7 +182,6 @@ macro bool os_is_posix() @const
$case SOLARIS:
$case TVOS:
$case WATCHOS:
$case ANDROID:
return true;
$case WIN32:
$case WASI:

View File

@@ -8,8 +8,6 @@ import std::math;
const MAX_MEMORY_ALIGNMENT = 0x1000_0000;
const DEFAULT_MEM_ALIGNMENT = (void*.alignof) * 2;
faultdef OUT_OF_MEMORY, INVALID_ALLOC_SIZE;
macro bool @constant_is_power_of_2($x) @const @private
{
return $x != 0 && ($x & ($x - 1)) == 0;
@@ -18,9 +16,9 @@ macro bool @constant_is_power_of_2($x) @const @private
<*
Load a vector from memory according to a mask assuming default alignment.
@param ptr : "The pointer address to load from."
@param mask : "The mask for the load"
@param passthru : "The value to use for non masked values"
@param ptr "The pointer address to load from."
@param mask "The mask for the load"
@param passthru "The value to use for non masked values"
@require $assignable(&&passthru, $typeof(ptr)) : "Pointer and passthru must match"
@require @typekind(passthru) == VECTOR : "Expected passthru to be a vector"
@require passthru.len == mask.len : "Mask and passthru must have the same length"
@@ -35,10 +33,10 @@ macro masked_load(ptr, bool[<*>] mask, passthru)
<*
Load a vector from memory according to a mask.
@param ptr : "The pointer address to load from."
@param mask : "The mask for the load"
@param passthru : "The value to use for non masked values"
@param $alignment : "The alignment to assume for the pointer"
@param ptr "The pointer address to load from."
@param mask "The mask for the load"
@param passthru "The value to use for non masked values"
@param $alignment "The alignment to assume for the pointer"
@require $assignable(&&passthru, $typeof(ptr)) : "Pointer and passthru must match"
@require @typekind(passthru) == VECTOR : "Expected passthru to be a vector"
@@ -55,9 +53,9 @@ macro @masked_load_aligned(ptr, bool[<*>] mask, passthru, usz $alignment)
<*
Load values from a pointer vector, assuming default alignment.
@param ptrvec : "The vector of pointers to load from."
@param mask : "The mask for the load"
@param passthru : "The value to use for non masked values"
@param ptrvec "The vector of pointers to load from."
@param mask "The mask for the load"
@param passthru "The value to use for non masked values"
@require @typekind(ptrvec) == VECTOR : "Expected ptrvec to be a vector"
@require @typekind(passthru) == VECTOR : "Expected passthru to be a vector"
@@ -76,10 +74,10 @@ macro gather(ptrvec, bool[<*>] mask, passthru)
<*
Load values from a pointer vector.
@param ptrvec : "The vector of pointers to load from."
@param mask : "The mask for the load"
@param passthru : "The value to use for non masked values"
@param $alignment : "The alignment to assume for the pointers"
@param ptrvec "The vector of pointers to load from."
@param mask "The mask for the load"
@param passthru "The value to use for non masked values"
@param $alignment "The alignment to assume for the pointers"
@require @typekind(ptrvec) == VECTOR : "Expected ptrvec to be a vector"
@require @typekind(passthru) == VECTOR : "Expected passthru to be a vector"
@@ -99,9 +97,9 @@ macro @gather_aligned(ptrvec, bool[<*>] mask, passthru, usz $alignment)
<*
Store parts of a vector according to the mask, assuming default alignment.
@param ptr : "The pointer address to store to."
@param value : "The value to store masked"
@param mask : "The mask for the store"
@param ptr "The pointer address to store to."
@param value "The value to store masked"
@param mask "The mask for the store"
@require $assignable(&&value, $typeof(ptr)) : "Pointer and value must match"
@require @typekind(value) == VECTOR : "Expected value to be a vector"
@@ -113,10 +111,10 @@ macro masked_store(ptr, value, bool[<*>] mask)
}
<*
@param ptr : "The pointer address to store to."
@param value : "The value to store masked"
@param mask : "The mask for the store"
@param $alignment : "The alignment of the pointer"
@param ptr "The pointer address to store to."
@param value "The value to store masked"
@param mask "The mask for the store"
@param $alignment "The alignment of the pointer"
@require $assignable(&&value, $typeof(ptr)) : "Pointer and value must match"
@require @typekind(value) == VECTOR : "Expected value to be a vector"
@@ -130,9 +128,9 @@ macro @masked_store_aligned(ptr, value, bool[<*>] mask, usz $alignment)
}
<*
@param ptrvec : "The vector pointer containing the addresses to store to."
@param value : "The value to store masked"
@param mask : "The mask for the store"
@param ptrvec "The vector pointer containing the addresses to store to."
@param value "The value to store masked"
@param mask "The mask for the store"
@require @typekind(ptrvec) == VECTOR : "Expected ptrvec to be a vector"
@require @typekind(value) == VECTOR : "Expected value to be a vector"
@require $assignable(&&value[0], $typeof(ptrvec[0])) : "Pointer and value must match"
@@ -146,10 +144,10 @@ macro scatter(ptrvec, value, bool[<*>] mask)
}
<*
@param ptrvec : "The vector pointer containing the addresses to store to."
@param value : "The value to store masked"
@param mask : "The mask for the store"
@param $alignment : "The alignment of the load"
@param ptrvec "The vector pointer containing the addresses to store to."
@param value "The value to store masked"
@param mask "The mask for the store"
@param $alignment "The alignment of the load"
@require @typekind(ptrvec) == VECTOR : "Expected ptrvec to be a vector"
@require @typekind(value) == VECTOR : "Expected value to be a vector"
@@ -164,55 +162,42 @@ macro @scatter_aligned(ptrvec, value, bool[<*>] mask, usz $alignment)
}
<*
@param #x : "The variable or dereferenced pointer to load."
@param $alignment : "The alignment to assume for the load"
@return "The value of the variable"
@param [in] x "The variable or dereferenced pointer to load."
@param $alignment "The alignment to assume for the load"
@return "The value of x"
@require @constant_is_power_of_2($alignment) : "The alignment must be a power of two"
@require $defined(&#x) : "This must be a variable or dereferenced pointer"
*>
macro @unaligned_load(#x, usz $alignment) @builtin
macro @unaligned_load(&x, usz $alignment) @builtin
{
return $$unaligned_load(&#x, $alignment);
return $$unaligned_load(x, $alignment);
}
<*
@param #x : "The variable or dereferenced pointer to store to."
@param value : "The value to store."
@param $alignment : "The alignment to assume for the store"
@return "The value stored"
@param [out] x "The variable or dereferenced pointer to store to."
@param value "The value to store."
@param $alignment "The alignment to assume for the store"
@return "The value of x"
@require $defined(&#x) : "This must be a variable or dereferenced pointer"
@require $defined(#x = value) : "The value doesn't match the variable"
@require $assignable(value, $typeof(*x)) : "The value doesn't match the variable"
@require @constant_is_power_of_2($alignment) : "The alignment must be a power of two"
*>
macro @unaligned_store(#x, value, usz $alignment) @builtin
macro @unaligned_store(&x, value, usz $alignment) @builtin
{
return $$unaligned_store(&#x, ($typeof(#x))value, $alignment);
return $$unaligned_store(x, ($typeof(*x))value, $alignment);
}
macro @volatile_load(&x) @builtin
{
return $$volatile_load(x);
}
<*
@param #x : "The variable or dereferenced pointer to load."
@return "The value of the variable"
@require $defined(&#x) : "This must be a variable or dereferenced pointer"
@require $assignable(y, $typeof(*x)) : "The value doesn't match the variable"
*>
macro @volatile_load(#x) @builtin
macro @volatile_store(&x, y) @builtin
{
return $$volatile_load(&#x);
}
<*
@param #x : "The variable or dereferenced pointer to store to."
@param value : "The value to store."
@return "The value stored"
@require $defined(&#x) : "This must be a variable or dereferenced pointer"
@require $defined(#x = value) : "The value doesn't match the variable"
*>
macro @volatile_store(#x, value) @builtin
{
return $$volatile_store(&#x, ($typeof(#x))value);
return $$volatile_store(x, ($typeof(*x))y);
}
enum AtomicOrdering : int
@@ -227,41 +212,39 @@ enum AtomicOrdering : int
}
<*
@param #x : "the variable or dereferenced pointer to load."
@param $ordering : "atomic ordering of the load, defaults to SEQ_CONSISTENT"
@param $volatile : "whether the load should be volatile, defaults to 'false'"
@param [in] x "the variable or dereferenced pointer to load."
@param $ordering "atomic ordering of the load, defaults to SEQ_CONSISTENT"
@param $volatile "whether the load should be volatile, defaults to 'false'"
@return "returns the value of x"
@require $defined(&#x) : "This must be a variable or dereferenced pointer"
@require $ordering != AtomicOrdering.RELEASE : "Release ordering is not valid for load."
@require $ordering != AtomicOrdering.ACQUIRE_RELEASE : "Acquire release is not valid for load."
@require types::may_load_atomic($typeof(#x)) : "Only integer, float and pointers may be used."
@require $ordering != AtomicOrdering.RELEASE "Release ordering is not valid for load."
@require $ordering != AtomicOrdering.ACQUIRE_RELEASE "Acquire release is not valid for load."
@require types::may_load_atomic($typeof(x)) "Only integer, float and pointers may be used."
@require @typekind(x) == POINTER "You can only load from a pointer"
*>
macro @atomic_load(#x, AtomicOrdering $ordering = SEQ_CONSISTENT, $volatile = false) @builtin
macro @atomic_load(&x, AtomicOrdering $ordering = SEQ_CONSISTENT, $volatile = false) @builtin
{
return $$atomic_load(&#x, $volatile, $ordering.ordinal);
return $$atomic_load(x, $volatile, $ordering.ordinal);
}
<*
@param #x : "the variable or dereferenced pointer to store to."
@param value : "the value to store."
@param $ordering : "the atomic ordering of the store, defaults to SEQ_CONSISTENT"
@param $volatile : "whether the store should be volatile, defaults to 'false'"
@param [out] x "the variable or dereferenced pointer to store to."
@param value "the value to store."
@param $ordering "the atomic ordering of the store, defaults to SEQ_CONSISTENT"
@param $volatile "whether the store should be volatile, defaults to 'false'"
@require $ordering != AtomicOrdering.ACQUIRE : "Acquire ordering is not valid for store."
@require $ordering != AtomicOrdering.ACQUIRE_RELEASE : "Acquire release is not valid for store."
@require types::may_load_atomic($typeof(#x)) : "Only integer, float and pointers may be used."
@require $defined(&#x) : "This must be a variable or dereferenced pointer"
@require $defined(#x = value) : "The value doesn't match the variable"
@require $ordering != AtomicOrdering.ACQUIRE "Acquire ordering is not valid for store."
@require $ordering != AtomicOrdering.ACQUIRE_RELEASE "Acquire release is not valid for store."
@require types::may_load_atomic($typeof(x)) "Only integer, float and pointers may be used."
*>
macro void @atomic_store(#x, value, AtomicOrdering $ordering = SEQ_CONSISTENT, $volatile = false) @builtin
macro void @atomic_store(&x, value, AtomicOrdering $ordering = SEQ_CONSISTENT, $volatile = false) @builtin
{
$$atomic_store(&#x, value, $volatile, $ordering.ordinal);
$$atomic_store(x, value, $volatile, $ordering.ordinal);
}
<*
@require $success != AtomicOrdering.NOT_ATOMIC && $success != AtomicOrdering.UNORDERED : "Acquire ordering is not valid."
@require $failure != AtomicOrdering.RELEASE && $failure != AtomicOrdering.ACQUIRE_RELEASE : "Acquire release is not valid."
@require $success != AtomicOrdering.NOT_ATOMIC && $success != AtomicOrdering.UNORDERED "Acquire ordering is not valid."
@require $failure != AtomicOrdering.RELEASE && $failure != AtomicOrdering.ACQUIRE_RELEASE "Acquire release is not valid."
*>
macro compare_exchange(ptr, compare, value, AtomicOrdering $success = SEQ_CONSISTENT, AtomicOrdering $failure = SEQ_CONSISTENT, bool $volatile = true, bool $weak = false, usz $alignment = 0)
{
@@ -269,8 +252,8 @@ macro compare_exchange(ptr, compare, value, AtomicOrdering $success = SEQ_CONSIS
}
<*
@require $success != AtomicOrdering.NOT_ATOMIC && $success != AtomicOrdering.UNORDERED : "Acquire ordering is not valid."
@require $failure != AtomicOrdering.RELEASE && $failure != AtomicOrdering.ACQUIRE_RELEASE : "Acquire release is not valid."
@require $success != AtomicOrdering.NOT_ATOMIC && $success != AtomicOrdering.UNORDERED "Acquire ordering is not valid."
@require $failure != AtomicOrdering.RELEASE && $failure != AtomicOrdering.ACQUIRE_RELEASE "Acquire release is not valid."
*>
macro compare_exchange_volatile(ptr, compare, value, AtomicOrdering $success = SEQ_CONSISTENT, AtomicOrdering $failure = SEQ_CONSISTENT)
{
@@ -303,7 +286,7 @@ macro void zero_volatile(char[] data)
$$memset(data.ptr, (char)0, data.len, true, (usz)1);
}
macro void clear(void* dst, usz len, usz $dst_align = 0, bool $is_volatile = false)
macro void clear(void* dst, usz len, usz $dst_align = 0, bool $is_volatile = false, bool $inlined = false)
{
$$memset(dst, (char)0, len, $is_volatile, $dst_align);
}
@@ -316,17 +299,16 @@ macro void clear_inline(void* dst, usz $len, usz $dst_align = 0, bool $is_volati
<*
Copy memory from src to dst efficiently, assuming the memory ranges do not overlap.
@param [&out] dst : "The destination to copy to"
@param [in] src : "The source to copy from"
@param len : "The number of bytes to copy"
@param $dst_align : "the alignment of the destination if different from the default, 0 assumes the default"
@param $src_align : "the alignment of the destination if different from the default, 0 assumes the default"
@param $is_volatile : "True if this copy should be treated as volatile, i.e. it can't be optimized away."
@param [&out] dst "The destination to copy to"
@param [&in] src "The source to copy from"
@param len "The number of bytes to copy"
@param $dst_align "the alignment of the destination if different from the default, 0 assumes the default"
@param $src_align "the alignment of the destination if different from the default, 0 assumes the default"
@param $is_volatile "True if this copy should be treated as volatile, i.e. it can't be optimized away."
@require src != null || len == 0 : "Copying a null with non-zero length is invalid"
@require len == 0 || dst + len <= src || src + len <= dst : "Ranges may not overlap"
*>
macro void copy(void* dst, void* src, usz len, usz $dst_align = 0, usz $src_align = 0, bool $is_volatile = false)
macro void copy(void* dst, void* src, usz len, usz $dst_align = 0, usz $src_align = 0, bool $is_volatile = false, bool $inlined = false)
{
$$memcpy(dst, src, len, $is_volatile, $dst_align, $src_align);
}
@@ -335,14 +317,13 @@ macro void copy(void* dst, void* src, usz len, usz $dst_align = 0, usz $src_alig
Copy memory from src to dst efficiently, assuming the memory ranges do not overlap, it
will always be inlined and never call memcopy
@param [&out] dst : "The destination to copy to"
@param [in] src : "The source to copy from"
@param $len : "The number of bytes to copy"
@param $dst_align : "the alignment of the destination if different from the default, 0 assumes the default"
@param $src_align : "the alignment of the destination if different from the default, 0 assumes the default"
@param $is_volatile : "True if this copy should be treated as volatile, i.e. it can't be optimized away."
@param [&out] dst "The destination to copy to"
@param [&in] src "The source to copy from"
@param $len "The number of bytes to copy"
@param $dst_align "the alignment of the destination if different from the default, 0 assumes the default"
@param $src_align "the alignment of the destination if different from the default, 0 assumes the default"
@param $is_volatile "True if this copy should be treated as volatile, i.e. it can't be optimized away."
@require src != null || len == 0 : "Copying a null with non-zero length is invalid"
@require $len == 0 || dst + $len <= src || src + $len <= dst : "Ranges may not overlap"
*>
macro void copy_inline(void* dst, void* src, usz $len, usz $dst_align = 0, usz $src_align = 0, bool $is_volatile = false)
@@ -353,14 +334,12 @@ macro void copy_inline(void* dst, void* src, usz $len, usz $dst_align = 0, usz $
<*
Copy memory from src to dst but correctly handle the possibility of overlapping ranges.
@param [&out] dst : "The destination to copy to"
@param [in] src : "The source to copy from"
@param len : "The number of bytes to copy"
@param $dst_align : "the alignment of the destination if different from the default, 0 assumes the default"
@param $src_align : "the alignment of the destination if different from the default, 0 assumes the default"
@param $is_volatile : "True if this copy should be treated as volatile, i.e. it can't be optimized away."
@require src != null || len == 0 : "Moving a null with non-zero length is invalid"
@param [&out] dst "The destination to copy to"
@param [&in] src "The source to copy from"
@param len "The number of bytes to copy"
@param $dst_align "the alignment of the destination if different from the default, 0 assumes the default"
@param $src_align "the alignment of the destination if different from the default, 0 assumes the default"
@param $is_volatile "True if this copy should be treated as volatile, i.e. it can't be optimized away."
*>
macro void move(void* dst, void* src, usz len, usz $dst_align = 0, usz $src_align = 0, bool $is_volatile = false)
{
@@ -370,11 +349,11 @@ macro void move(void* dst, void* src, usz len, usz $dst_align = 0, usz $src_alig
<*
Sets all memory in a region to that of the provided byte.
@param [&out] dst : "The destination to copy to"
@param val : "The value to copy into memory"
@param len : "The number of bytes to copy"
@param $dst_align : "the alignment of the destination if different from the default, 0 assumes the default"
@param $is_volatile : "True if this copy should be treated as volatile, i.e. it can't be optimized away."
@param [&out] dst "The destination to copy to"
@param val "The value to copy into memory"
@param len "The number of bytes to copy"
@param $dst_align "the alignment of the destination if different from the default, 0 assumes the default"
@param $is_volatile "True if this copy should be treated as volatile, i.e. it can't be optimized away."
@ensure !len || (dst[0] == val && dst[len - 1] == val)
*>
@@ -386,11 +365,11 @@ macro void set(void* dst, char val, usz len, usz $dst_align = 0, bool $is_volati
<*
Sets all memory in a region to that of the provided byte. Never calls OS memset.
@param [&out] dst : "The destination to copy to"
@param val : "The value to copy into memory"
@param $len : "The number of bytes to copy"
@param $dst_align : "the alignment of the destination if different from the default, 0 assumes the default"
@param $is_volatile : "True if this copy should be treated as volatile, i.e. it can't be optimized away."
@param [&out] dst "The destination to copy to"
@param val "The value to copy into memory"
@param $len "The number of bytes to copy"
@param $dst_align "the alignment of the destination if different from the default, 0 assumes the default"
@param $is_volatile "True if this copy should be treated as volatile, i.e. it can't be optimized away."
@ensure !$len || (dst[0] == val && dst[$len - 1] == val)
*>
@@ -427,7 +406,7 @@ macro bool equals(a, b, isz len = -1, usz $align = 0)
if (!len) return true;
var $Type;
$switch $align:
$switch ($align)
$case 1:
$Type = char;
$case 2:
@@ -477,7 +456,7 @@ macro void @scoped(Allocator allocator; @body())
Run the tracking allocator in the scope, then
print out stats.
@param $enabled : "Set to false to disable tracking"
@param $enabled "Set to false to disable tracking"
*>
macro void @report_heap_allocs_in_scope($enabled = true; @body())
{
@@ -496,54 +475,19 @@ macro void @report_heap_allocs_in_scope($enabled = true; @body())
@body();
}
<*
Assert on memory leak in the scope of the macro body.
@param $report : "Set to false to disable memory report"
*>
macro void @assert_leak($report = true; @body()) @builtin
{
$if env::DEBUG_SYMBOLS || $feature(MEMORY_ASSERTS):
TrackingAllocator tracker;
tracker.init(mem);
Allocator old_allocator = mem;
allocator::thread_allocator = &tracker;
defer
{
allocator::thread_allocator = old_allocator;
defer tracker.free();
usz allocated = tracker.allocated();
if (allocated)
{
DString report;
report.init(old_allocator);
defer report.free();
$if $report:
report.append_char('\n');
(void)tracker.fprint_report(&report);
$endif
assert(allocated == 0, "Memory leak detected"
" (%d bytes allocated).%s",
allocated, report.str_view());
}
}
$endif
@body();
}
<*
Allocate [size] bytes on the stack to use for allocation,
with the heap allocator as the backing allocator.
Release everything on scope exit.
@param $size : `the size of the buffer`
@param $size `the size of the buffer`
*>
macro void @stack_mem(usz $size; @body(Allocator mem)) @builtin
{
char[$size] buffer;
OnStackAllocator allocator;
allocator.init(&buffer, mem);
allocator.init(&buffer, allocator::heap());
defer allocator.free();
@body(&allocator);
}
@@ -552,7 +496,7 @@ macro void @stack_pool(usz $size; @body) @builtin
{
char[$size] buffer;
OnStackAllocator allocator;
allocator.init(&buffer, mem);
allocator.init(&buffer, allocator::heap());
defer allocator.free();
mem::@scoped(&allocator)
{
@@ -560,45 +504,59 @@ macro void @stack_pool(usz $size; @body) @builtin
};
}
struct TempState
{
TempAllocator* old;
TempAllocator* current;
usz mark;
}
<*
Push the current temp allocator. A push must always be balanced with a pop using the current state.
*>
fn PoolState temp_push()
fn TempState temp_push(TempAllocator* other = null)
{
return allocator::push_pool() @inline;
TempAllocator* current = allocator::temp();
TempAllocator* old = current;
if (other == current)
{
current = allocator::temp_allocator_next();
}
return { old, current, current.used };
}
<*
Pop the current temp allocator. A pop must always be balanced with a push.
*>
fn void temp_pop(PoolState old_state)
fn void temp_pop(TempState old_state)
{
allocator::pop_pool(old_state) @inline;
assert(allocator::thread_temp_allocator == old_state.current, "Tried to pop temp allocators out of order.");
assert(old_state.current.used >= old_state.mark, "Tried to pop temp allocators out of order.");
old_state.current.reset(old_state.mark);
allocator::thread_temp_allocator = old_state.old;
}
macro void @pool_init(Allocator allocator, usz pool_size, usz buffer_size; @body) @builtin
macro void @pool(TempAllocator* #other_temp = null; @body) @builtin
{
Allocator current = allocator::current_temp;
TempAllocator* top = allocator::top_temp;
allocator::create_temp_allocator(allocator, pool_size, buffer_size);
TempAllocator* current = allocator::temp();
var $has_arg = !$is_const(#other_temp);
$if $has_arg:
TempAllocator* original = current;
if (current == (void*)#other_temp) current = allocator::temp_allocator_next();
$endif
usz mark = current.used;
defer
{
allocator::destroy_temp_allocators();
allocator::top_temp = top;
allocator::current_temp = current;
}
@body();
}
macro void @pool(;@body) @builtin
{
PoolState state = allocator::push_pool() @inline;
defer
{
allocator::pop_pool(state) @inline;
current.reset(mark);
$if $has_arg:
allocator::thread_temp_allocator = original;
$endif;
}
@body();
}
import libc;
module std::core::mem @if(WASM_NOLIBC);
import std::core::mem::allocator @public;
SimpleHeapAllocator wasm_allocator @private;
@@ -613,6 +571,7 @@ fn void initialize_wasm_mem() @init(1024) @private
wasm_allocator.init(fn (x) => allocator::wasm_memory.allocate_block(x));
allocator::thread_allocator = &wasm_allocator;
allocator::temp_base_allocator = &wasm_allocator;
allocator::init_default_temp_allocators();
}
module std::core::mem;
@@ -621,7 +580,7 @@ module std::core::mem;
macro TrackingEnv* get_tracking_env()
{
$if env::TRACK_MEMORY:
return &&(TrackingEnv){ $$FILE, $$FUNC, $$LINE };
return &&TrackingEnv { $$FILE, $$FUNC, $$LINE };
$else
return null;
$endif
@@ -629,17 +588,17 @@ macro TrackingEnv* get_tracking_env()
macro @clone(value) @builtin @nodiscard
{
return allocator::clone(mem, value);
return allocator::clone(allocator::heap(), value);
}
macro @tclone(value) @builtin @nodiscard
{
return tnew($typeof(value), value);
return temp_new($typeof(value), value);
}
fn void* malloc(usz size) @builtin @inline @nodiscard
{
return allocator::malloc(mem, size);
return allocator::malloc(allocator::heap(), size);
}
<*
@@ -648,13 +607,13 @@ fn void* malloc(usz size) @builtin @inline @nodiscard
*>
fn void* malloc_aligned(usz size, usz alignment) @builtin @inline @nodiscard
{
return allocator::malloc_aligned(mem, size, alignment)!!;
return allocator::malloc_aligned(allocator::heap(), size, alignment)!!;
}
fn void* tmalloc(usz size, usz alignment = 0) @builtin @inline @nodiscard
{
if (!size) return null;
return tmem.acquire(size, NO_ZERO, alignment)!!;
return allocator::temp().acquire(size, NO_ZERO, alignment)!!;
}
<*
@@ -673,22 +632,6 @@ macro new($Type, ...) @nodiscard
$endif
}
<*
@require $vacount < 2 : "Too many arguments."
@require $vacount == 0 ||| $assignable($vaexpr[0], $Type) : "The second argument must be an initializer for the type"
@require $Type.alignof <= DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'alloc_aligned' instead"
*>
macro new_with_padding($Type, usz padding, ...) @nodiscard
{
$if $vacount == 0:
return ($Type*)calloc($Type.sizeof + padding);
$else
$Type* val = malloc($Type.sizeof + padding);
*val = $vaexpr[0];
return val;
$endif
}
<*
Allocate using an aligned allocation. This is necessary for types with a default memory alignment
exceeding DEFAULT_MEM_ALIGNMENT. IMPORTANT! It must be freed using free_aligned.
@@ -714,14 +657,6 @@ macro alloc($Type) @nodiscard
return ($Type*)malloc($Type.sizeof);
}
<*
@require $Type.alignof <= DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'alloc_aligned' instead"
*>
macro alloc_with_padding($Type, usz padding) @nodiscard
{
return ($Type*)malloc($Type.sizeof + padding);
}
<*
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.
@@ -735,7 +670,7 @@ macro alloc_aligned($Type) @nodiscard
@require $vacount < 2 : "Too many arguments."
@require $vacount == 0 ||| $assignable($vaexpr[0], $Type) : "The second argument must be an initializer for the type"
*>
macro tnew($Type, ...) @nodiscard
macro temp_new($Type, ...) @nodiscard
{
$if $vacount == 0:
return ($Type*)tcalloc($Type.sizeof) @inline;
@@ -746,37 +681,18 @@ macro tnew($Type, ...) @nodiscard
$endif
}
<*
@require $vacount < 2 : "Too many arguments."
@require $vacount == 0 ||| $assignable($vaexpr[0], $Type) : "The second argument must be an initializer for the type"
*>
macro temp_with_padding($Type, usz padding, ...) @nodiscard
{
$if $vacount == 0:
return ($Type*)tcalloc($Type.sizeof + padding) @inline;
$else
$Type* val = tmalloc($Type.sizeof + padding) @inline;
*val = $vaexpr[0];
return val;
$endif
}
macro talloc($Type) @nodiscard
macro temp_alloc($Type) @nodiscard
{
return tmalloc($Type.sizeof);
}
macro talloc_with_padding($Type, usz padding) @nodiscard
{
return tmalloc($Type.sizeof + padding);
}
<*
@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
{
return allocator::new_array(mem, $Type, elements);
return allocator::new_array(allocator::heap(), $Type, elements);
}
<*
@@ -785,7 +701,7 @@ macro new_array($Type, usz elements) @nodiscard
*>
macro new_array_aligned($Type, usz elements) @nodiscard
{
return allocator::new_array_aligned(mem, $Type, elements);
return allocator::new_array_aligned(allocator::heap(), $Type, elements);
}
<*
@@ -793,7 +709,7 @@ macro new_array_aligned($Type, usz elements) @nodiscard
*>
macro alloc_array($Type, usz elements) @nodiscard
{
return allocator::alloc_array(mem, $Type, elements);
return allocator::alloc_array(allocator::heap(), $Type, elements);
}
<*
@@ -802,22 +718,22 @@ macro alloc_array($Type, usz elements) @nodiscard
*>
macro alloc_array_aligned($Type, usz elements) @nodiscard
{
return allocator::alloc_array_aligned(mem, $Type, elements);
return allocator::alloc_array_aligned(allocator::heap(), $Type, elements);
}
macro talloc_array($Type, usz elements) @nodiscard
macro temp_alloc_array($Type, usz elements) @nodiscard
{
return (($Type*)tmalloc($Type.sizeof * elements, $Type.alignof))[:elements];
}
macro temp_array($Type, usz elements) @nodiscard
macro temp_new_array($Type, usz elements) @nodiscard
{
return (($Type*)tcalloc($Type.sizeof * elements, $Type.alignof))[:elements];
}
fn void* calloc(usz size) @builtin @inline @nodiscard
{
return allocator::calloc(mem, size);
return allocator::calloc(allocator::heap(), size);
}
<*
@@ -826,40 +742,40 @@ fn void* calloc(usz size) @builtin @inline @nodiscard
*>
fn void* calloc_aligned(usz size, usz alignment) @builtin @inline @nodiscard
{
return allocator::calloc_aligned(mem, size, alignment)!!;
return allocator::calloc_aligned(allocator::heap(), size, alignment)!!;
}
fn void* tcalloc(usz size, usz alignment = 0) @builtin @inline @nodiscard
{
if (!size) return null;
return tmem.acquire(size, ZERO, alignment)!!;
return allocator::temp().acquire(size, ZERO, alignment)!!;
}
fn void* realloc(void *ptr, usz new_size) @builtin @inline @nodiscard
{
return allocator::realloc(mem, ptr, new_size);
return allocator::realloc(allocator::heap(), ptr, new_size);
}
fn void* realloc_aligned(void *ptr, usz new_size, usz alignment) @builtin @inline @nodiscard
{
return allocator::realloc_aligned(mem, ptr, new_size, alignment)!!;
return allocator::realloc_aligned(allocator::heap(), ptr, new_size, alignment)!!;
}
fn void free(void* ptr) @builtin @inline
{
return allocator::free(mem, ptr);
return allocator::free(allocator::heap(), ptr);
}
fn void free_aligned(void* ptr) @builtin @inline
{
return allocator::free_aligned(mem, ptr);
return allocator::free_aligned(allocator::heap(), ptr);
}
fn void* trealloc(void* ptr, usz size, usz alignment = mem::DEFAULT_MEM_ALIGNMENT) @builtin @inline @nodiscard
{
if (!size) return null;
if (!ptr) return tmalloc(size, alignment);
return tmem.resize(ptr, size, alignment)!!;
return allocator::temp().resize(ptr, size, alignment)!!;
}
module std::core::mem @if(env::NO_LIBC);
@@ -899,3 +815,4 @@ fn void* __memcpy(void* dst, void* src, usz n) @weak @export("memcpy")
}
return dst;
}

View File

@@ -1,5 +1,4 @@
module std::core::mem::allocator;
import std::math;
const DEFAULT_SIZE_PREFIX = usz.sizeof;
const DEFAULT_SIZE_PREFIX_ALIGNMENT = usz.alignof;
@@ -19,30 +18,34 @@ enum AllocInitType
interface Allocator
{
fn void reset(usz mark) @optional;
fn usz mark() @optional;
<*
@require !alignment || math::is_power_of_2(alignment)
@require alignment <= mem::MAX_MEMORY_ALIGNMENT : `alignment too big`
@require alignment <= mem::MAX_MEMORY_ALIGNMENT `alignment too big`
@require size > 0
@return? mem::INVALID_ALLOC_SIZE, mem::OUT_OF_MEMORY
*>
fn void*? acquire(usz size, AllocInitType init_type, usz alignment = 0);
fn void*! acquire(usz size, AllocInitType init_type, usz alignment = 0);
<*
@require !alignment || math::is_power_of_2(alignment)
@require alignment <= mem::MAX_MEMORY_ALIGNMENT : `alignment too big`
@require alignment <= mem::MAX_MEMORY_ALIGNMENT `alignment too big`
@require ptr != null
@require new_size > 0
@return? mem::INVALID_ALLOC_SIZE, mem::OUT_OF_MEMORY
*>
fn void*? resize(void* ptr, usz new_size, usz alignment = 0);
fn void*! resize(void* ptr, usz new_size, usz alignment = 0);
<*
@require ptr != null
*>
fn void release(void* ptr, bool aligned);
}
alias MemoryAllocFn = fn char[]?(usz);
def MemoryAllocFn = fn char[]!(usz);
fault AllocationFailure
{
OUT_OF_MEMORY,
CHUNK_TOO_LARGE,
}
fn usz alignment_for_allocation(usz alignment) @inline @private
{
@@ -54,7 +57,7 @@ macro void* malloc(Allocator allocator, usz size) @nodiscard
return malloc_try(allocator, size)!!;
}
macro void*? malloc_try(Allocator allocator, usz size) @nodiscard
macro void*! malloc_try(Allocator allocator, usz size) @nodiscard
{
if (!size) return null;
$if env::TESTING:
@@ -71,7 +74,7 @@ macro void* calloc(Allocator allocator, usz size) @nodiscard
return calloc_try(allocator, size)!!;
}
macro void*? calloc_try(Allocator allocator, usz size) @nodiscard
macro void*! calloc_try(Allocator allocator, usz size) @nodiscard
{
if (!size) return null;
return allocator.acquire(size, ZERO);
@@ -82,7 +85,7 @@ macro void* realloc(Allocator allocator, void* ptr, usz new_size) @nodiscard
return realloc_try(allocator, ptr, new_size)!!;
}
macro void*? realloc_try(Allocator allocator, void* ptr, usz new_size) @nodiscard
macro void*! realloc_try(Allocator allocator, void* ptr, usz new_size) @nodiscard
{
if (!new_size)
{
@@ -102,7 +105,7 @@ macro void free(Allocator allocator, void* ptr)
allocator.release(ptr, false);
}
macro void*? malloc_aligned(Allocator allocator, usz size, usz alignment) @nodiscard
macro void*! malloc_aligned(Allocator allocator, usz size, usz alignment) @nodiscard
{
if (!size) return null;
$if env::TESTING:
@@ -114,13 +117,13 @@ macro void*? malloc_aligned(Allocator allocator, usz size, usz alignment) @nodis
$endif
}
macro void*? calloc_aligned(Allocator allocator, usz size, usz alignment) @nodiscard
macro void*! calloc_aligned(Allocator allocator, usz size, usz alignment) @nodiscard
{
if (!size) return null;
return allocator.acquire(size, ZERO, alignment);
}
macro void*? realloc_aligned(Allocator allocator, void* ptr, usz new_size, usz alignment) @nodiscard
macro void*! realloc_aligned(Allocator allocator, void* ptr, usz new_size, usz alignment) @nodiscard
{
if (!new_size)
{
@@ -181,12 +184,12 @@ macro new_try(Allocator allocator, $Type, ...) @nodiscard
@require $vacount < 2 : "Too many arguments."
@require $vacount == 0 ||| $assignable($vaexpr[0], $Type) : "The second argument must be an initializer for the type"
*>
macro new_aligned(Allocator allocator, $Type, ...) @nodiscard
macro new_aligned($Type, ...) @nodiscard
{
$if $vacount == 0:
return ($Type*)calloc_aligned(allocator, $Type.sizeof, $Type.alignof);
$else
$Type* val = malloc_aligned(allocator, $Type.sizeof, $Type.alignof)!;
$Type* val = malloc_aligned(allocator, $Type.sizeof, $Type.alignof);
*val = $vaexpr[0];
return val;
$endif
@@ -302,7 +305,7 @@ fn any clone_any(Allocator allocator, any value) @nodiscard
@require alignment > 0
@require bytes <= isz.max
*>
macro void*? @aligned_alloc(#alloc_fn, usz bytes, usz alignment)
macro void*! @aligned_alloc(#alloc_fn, usz bytes, usz alignment)
{
if (alignment < void*.alignof) alignment = void*.alignof;
usz header = AlignedBlock.sizeof + alignment;
@@ -325,7 +328,7 @@ struct AlignedBlock
void* start;
}
macro void? @aligned_free(#free_fn, void* old_pointer)
macro void! @aligned_free(#free_fn, void* old_pointer)
{
AlignedBlock* desc = (AlignedBlock*)old_pointer - 1;
$if @typekind(#free_fn(desc.start)) == OPTIONAL:
@@ -339,7 +342,7 @@ macro void? @aligned_free(#free_fn, void* old_pointer)
@require bytes > 0
@require alignment > 0
*>
macro void*? @aligned_realloc(#calloc_fn, #free_fn, void* old_pointer, usz bytes, usz alignment)
macro void*! @aligned_realloc(#calloc_fn, #free_fn, void* old_pointer, usz bytes, usz alignment)
{
AlignedBlock* desc = (AlignedBlock*)old_pointer - 1;
void* data_start = desc.start;
@@ -355,34 +358,13 @@ macro void*? @aligned_realloc(#calloc_fn, #free_fn, void* old_pointer, usz bytes
// All allocators
alias mem @builtin = thread_allocator ;
tlocal Allocator thread_allocator @private = base_allocator();
Allocator temp_base_allocator @private = base_allocator();
typedef PoolState = TempAllocator*;
const LazyTempAllocator LAZY_TEMP @private = {};
tlocal Allocator current_temp = &LAZY_TEMP;
tlocal TempAllocator* top_temp;
tlocal bool auto_create_temp = false;
usz temp_allocator_min_size = temp_allocator_default_min_size();
usz temp_allocator_buffer_size = temp_allocator_default_buffer_size();
usz temp_allocator_new_mult = 4;
fn PoolState push_pool()
{
Allocator old = top_temp ? current_temp : create_temp_allocator_on_demand();
current_temp = ((TempAllocator*)old).derive_allocator(temp_allocator_min_size, temp_allocator_buffer_size, temp_allocator_new_mult)!!;
return (PoolState)old.ptr;
}
fn void pop_pool(PoolState old)
{
TempAllocator* temp = (TempAllocator*)old;
current_temp = temp;
temp.reset();
}
tlocal TempAllocator* thread_temp_allocator @private = null;
tlocal TempAllocator*[2] temp_allocator_pair @private;
macro Allocator base_allocator() @private
{
@@ -393,68 +375,36 @@ macro Allocator base_allocator() @private
$endif
}
macro usz temp_allocator_size() @local
macro TempAllocator* create_default_sized_temp_allocator(Allocator allocator) @local
{
$switch env::MEMORY_ENV:
$case NORMAL: return 256 * 1024;
$case SMALL: return 1024 * 32;
$case TINY: return 1024 * 4;
$case NONE: return 0;
$endswitch
}
macro usz temp_allocator_default_min_size() @local
{
$switch env::MEMORY_ENV:
$case NORMAL: return 16 * 1024;
$case SMALL: return 1024 * 2;
$case TINY: return 256;
$case NONE: return 256;
$endswitch
}
macro usz temp_allocator_default_buffer_size() @local
{
$switch env::MEMORY_ENV:
$case NORMAL: return 1024;
$case SMALL: return 128;
$case TINY: return 64;
$case NONE: return 64;
$switch (env::MEMORY_ENV)
$case NORMAL:
return new_temp_allocator(1024 * 256, allocator)!!;
$case SMALL:
return new_temp_allocator(1024 * 16, allocator)!!;
$case TINY:
return new_temp_allocator(1024 * 2, allocator)!!;
$case NONE:
unreachable("Temp allocator must explicitly created when memory-env is set to 'none'.");
$endswitch
}
macro Allocator heap() => thread_allocator;
<*
@require !top_temp : "This should never be called when temp already exists"
*>
fn Allocator create_temp_allocator_on_demand() @private
macro TempAllocator* temp()
{
if (!auto_create_temp)
if (!thread_temp_allocator)
{
auto_create_temp = true;
abort("Use '@pool_init()' to enable the temp allocator on a new thread. A temp allocator is only implicitly created on the main thread.");
init_default_temp_allocators();
}
return create_temp_allocator(base_allocator(), temp_allocator_size());
}
<*
@require !top_temp : "This should never be called when temp already exists"
*>
fn Allocator create_temp_allocator(Allocator allocator, usz size, usz buffer = temp_allocator_default_buffer_size()) @private
{
return current_temp = top_temp = allocator::new_temp_allocator(allocator, size)!!;
return thread_temp_allocator;
}
macro Allocator temp()
fn void init_default_temp_allocators() @private
{
return current_temp;
}
alias tmem @builtin = current_temp;
fn void allow_implicit_temp_allocator_on_load_thread() @init(1) @local @if(env::LIBC)
{
auto_create_temp = true;
temp_allocator_pair[0] = create_default_sized_temp_allocator(temp_base_allocator);
temp_allocator_pair[1] = create_default_sized_temp_allocator(temp_base_allocator);
thread_temp_allocator = temp_allocator_pair[0];
}
fn void destroy_temp_allocators_after_exit() @finalizer(65535) @local @if(env::LIBC)
@@ -467,42 +417,35 @@ fn void destroy_temp_allocators_after_exit() @finalizer(65535) @local @if(env::L
*>
fn void destroy_temp_allocators()
{
if (!top_temp) return;
top_temp.free();
top_temp = null;
current_temp = &LAZY_TEMP;
if (!thread_temp_allocator) return;
temp_allocator_pair[0].destroy();
temp_allocator_pair[1].destroy();
temp_allocator_pair[..] = null;
thread_temp_allocator = null;
}
import libc;
typedef LazyTempAllocator (Allocator) @private = uptr;
fn void*? LazyTempAllocator.acquire(&self, usz bytes, AllocInitType init_type, usz alignment) @dynamic
{
if (!top_temp) create_temp_allocator_on_demand();
return top_temp.acquire(bytes, init_type, alignment);
}
fn void*? LazyTempAllocator.resize(&self, void* old_ptr, usz new_bytes, usz alignment) @dynamic
{
if (!top_temp) create_temp_allocator_on_demand();
return top_temp.resize(old_ptr, new_bytes, alignment);
}
fn void LazyTempAllocator.release(&self, void* old_ptr, bool aligned) @dynamic
fn TempAllocator* temp_allocator_next() @private
{
if (!thread_temp_allocator)
{
init_default_temp_allocators();
return thread_temp_allocator;
}
usz index = thread_temp_allocator == temp_allocator_pair[0] ? 1 : 0;
return thread_temp_allocator = temp_allocator_pair[index];
}
const NullAllocator NULL_ALLOCATOR = {};
typedef NullAllocator (Allocator) = uptr;
distinct 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 AllocationFailure.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 AllocationFailure.OUT_OF_MEMORY?;
}
fn void NullAllocator.release(&self, void* old_ptr, bool aligned) @dynamic

View File

@@ -11,7 +11,7 @@ struct WasmMemory
uptr use;
}
fn char[]? WasmMemory.allocate_block(&self, usz bytes)
fn char[]! WasmMemory.allocate_block(&self, usz bytes)
{
if (!self.allocation)
{
@@ -25,7 +25,7 @@ fn char[]? WasmMemory.allocate_block(&self, usz bytes)
}
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 AllocationFailure.OUT_OF_MEMORY?;
self.allocation = $$wasm_memory_size(0) * WASM_BLOCK_SIZE;
defer self.use += bytes;
return ((char*)self.use)[:bytes];

View File

@@ -150,14 +150,14 @@ fn void x86_initialize_cpu_features()
{
uint max_level = x86_cpuid(0).eax;
CpuId feat = x86_cpuid(1);
CpuId leaf7 = max_level >= 8 ? x86_cpuid(7) : {};
CpuId leaf7s1 = leaf7.eax >= 1 ? x86_cpuid(7, 1) : {};
CpuId ext1 = x86_cpuid(0x80000000).eax >= 0x80000001 ? x86_cpuid(0x80000001) : {};
CpuId ext8 = x86_cpuid(0x80000000).eax >= 0x80000008 ? x86_cpuid(0x80000008) : {};
CpuId leaf_d = max_level >= 0xd ? x86_cpuid(0xd, 0x1) : {};
CpuId leaf_14 = max_level >= 0x14 ? x86_cpuid(0x14) : {};
CpuId leaf_19 = max_level >= 0x19 ? x86_cpuid(0x19) : {};
CpuId leaf_24 = max_level >= 0x24 ? x86_cpuid(0x24) : {};
CpuId leaf7 = max_level >= 8 ? x86_cpuid(7) : CpuId {};
CpuId leaf7s1 = leaf7.eax >= 1 ? x86_cpuid(7, 1) : CpuId {};
CpuId ext1 = x86_cpuid(0x80000000).eax >= 0x80000001 ? x86_cpuid(0x80000001) : CpuId {};
CpuId ext8 = x86_cpuid(0x80000000).eax >= 0x80000008 ? x86_cpuid(0x80000008) : CpuId {};
CpuId leaf_d = max_level >= 0xd ? x86_cpuid(0xd, 0x1) : CpuId {};
CpuId leaf_14 = max_level >= 0x14 ? x86_cpuid(0x14) : CpuId {};
CpuId leaf_19 = max_level >= 0x19 ? x86_cpuid(0x19) : CpuId {};
CpuId leaf_24 = max_level >= 0x24 ? x86_cpuid(0x24) : CpuId {};
add_feature_if_bit(ADX, leaf7.ebx, 19);
add_feature_if_bit(AES, feat.ecx, 25);
add_feature_if_bit(AMX_BF16, leaf7.edx, 22);

View File

@@ -56,7 +56,10 @@ struct MachHeader64
const LC_SEGMENT_64 = 0x19;
fault MachoSearch
{
NOT_FOUND
}
fn bool name_cmp(char* a, char[16]* b)
{
for (usz i = 0; i < 16; i++)
@@ -67,7 +70,7 @@ fn bool name_cmp(char* a, char[16]* b)
return false;
}
fn SegmentCommand64*? find_segment(MachHeader* header, char* segname)
fn SegmentCommand64*! find_segment(MachHeader* header, char* segname)
{
LoadCommand* command = (void*)header + MachHeader64.sizeof;
for (uint i = 0; i < header.ncmds; i++)
@@ -79,9 +82,9 @@ fn SegmentCommand64*? find_segment(MachHeader* header, char* segname)
}
command = (void*)command + command.cmdsize;
}
return NOT_FOUND?;
return MachoSearch.NOT_FOUND?;
}
fn Section64*? find_section(SegmentCommand64* command, char* sectname)
fn Section64*! find_section(SegmentCommand64* command, char* sectname)
{
Section64* section = (void*)command + SegmentCommand64.sizeof;
for (uint i = 0; i < command.nsects; i++)
@@ -89,22 +92,22 @@ fn Section64*? find_section(SegmentCommand64* command, char* sectname)
if (name_cmp(sectname, &section.sectname)) return section;
section++;
}
return NOT_FOUND?;
return MachoSearch.NOT_FOUND?;
}
macro find_segment_section_body(MachHeader* header, char* segname, char* sectname, $Type)
{
Section64*? section = find_section(find_segment(header, segname), sectname);
Section64*! section = find_section(find_segment(header, segname), sectname);
if (catch section)
{
return ($Type[]){};
return $Type[] {};
}
$Type* ptr = (void*)header + section.offset;
return ptr[:section.size / $Type.sizeof];
}
alias DyldCallback = fn void (MachHeader* mh, isz vmaddr_slide);
def DyldCallback = fn void (MachHeader* mh, isz vmaddr_slide);
extern fn void _dyld_register_func_for_add_image(DyldCallback);
@@ -123,7 +126,7 @@ extern fn void* realloc(void* ptr, usz size);
extern fn void* malloc(usz size);
extern fn void free(void* ptr);
alias CallbackFn = fn void();
def CallbackFn = fn void();
struct Callback
{
uint priority;

View File

@@ -47,13 +47,6 @@ macro int @main_to_int_main_args(#m, int argc, char** argv)
return #m(list);
}
macro int @_main_runner(#m, int argc, char** argv)
{
String[] list = args_to_strings(argc, argv);
defer free(list.ptr);
return #m(list) ? 0 : 1;
}
macro int @main_to_void_main_args(#m, int argc, char** argv)
{
String[] list = args_to_strings(argc, argv);
@@ -80,7 +73,7 @@ macro String[] wargs_strings(int argc, Char16** argv) @private
{
Char16* arg = argv[i];
Char16[] argstring = arg[:_strlen(arg)];
list[i] = string::from_utf16(mem, argstring) ?? "?".copy(mem);
list[i] = string::new_from_utf16(argstring) ?? "?".copy();
}
return list[:argc];
}
@@ -164,13 +157,6 @@ macro int @wmain_to_int_main_args(#m, int argc, Char16** argv)
return #m(args);
}
macro int @_wmain_runner(#m, int argc, Char16** argv)
{
String[] args = wargs_strings(argc, argv);
defer release_wargs(args);
return #m(args) ? 0 : 1;
}
macro int @wmain_to_void_main_args(#m, int argc, Char16** argv)
{
String[] args = wargs_strings(argc, argv);

View File

@@ -22,6 +22,230 @@ struct SliceRaw
usz len;
}
def BenchmarkFn = fn void!();
struct BenchmarkUnit
{
String name;
BenchmarkFn func;
}
fn BenchmarkUnit[] benchmark_collection_create(Allocator allocator = allocator::heap())
{
BenchmarkFn[] fns = $$BENCHMARK_FNS;
String[] names = $$BENCHMARK_NAMES;
BenchmarkUnit[] benchmarks = allocator::alloc_array(allocator, BenchmarkUnit, names.len);
foreach (i, benchmark : fns)
{
benchmarks[i] = { names[i], fns[i] };
}
return benchmarks;
}
const DEFAULT_BENCHMARK_WARMUP_ITERATIONS = 3;
const DEFAULT_BENCHMARK_MAX_ITERATIONS = 10000;
uint benchmark_warmup_iterations @private = DEFAULT_BENCHMARK_WARMUP_ITERATIONS;
uint benchmark_max_iterations @private = DEFAULT_BENCHMARK_MAX_ITERATIONS;
fn void set_benchmark_warmup_iterations(uint value) @builtin
{
benchmark_warmup_iterations = value;
}
fn void set_benchmark_max_iterations(uint value) @builtin
{
assert(value > 0);
benchmark_max_iterations = value;
}
fn bool run_benchmarks(BenchmarkUnit[] benchmarks)
{
int benchmarks_passed = 0;
int benchmark_count = benchmarks.len;
usz max_name;
foreach (&unit : benchmarks)
{
if (max_name < unit.name.len) max_name = unit.name.len;
}
usz len = max_name + 9;
DString name = dstring::temp_with_capacity(64);
name.append_repeat('-', len / 2);
name.append(" BENCHMARKS ");
name.append_repeat('-', len - len / 2);
io::printn(name);
name.clear();
long sys_clock_started;
long sys_clock_finished;
long sys_clocks;
Clock clock;
anyfault err;
foreach(unit : benchmarks)
{
defer name.clear();
name.appendf("Benchmarking %s ", unit.name);
name.append_repeat('.', max_name - unit.name.len + 2);
io::printf("%s ", name.str_view());
for (uint i = 0; i < benchmark_warmup_iterations; i++)
{
err = @catch(unit.func()) @inline;
@volatile_load(err);
}
clock = std::time::clock::now();
sys_clock_started = $$sysclock();
for (uint i = 0; i < benchmark_max_iterations; i++)
{
err = @catch(unit.func()) @inline;
@volatile_load(err);
}
sys_clock_finished = $$sysclock();
NanoDuration nano_seconds = clock.mark();
sys_clocks = sys_clock_finished - sys_clock_started;
if (err)
{
io::printfn("[failed] Failed due to: %s", err);
continue;
}
io::printfn("[ok] %.2f ns, %.2f CPU's clocks", (float)nano_seconds / benchmark_max_iterations, (float)sys_clocks / benchmark_max_iterations);
benchmarks_passed++;
}
io::printfn("\n%d benchmark%s run.\n", benchmark_count, benchmark_count > 1 ? "s" : "");
io::printfn("Benchmarks Result: %s. %d passed, %d failed.",
benchmarks_passed < benchmark_count ? "FAILED" : "ok",
benchmarks_passed,
benchmark_count - benchmarks_passed);
return benchmark_count == benchmarks_passed;
}
fn bool default_benchmark_runner()
{
@pool()
{
return run_benchmarks(benchmark_collection_create(allocator::temp()));
};
}
def TestFn = fn void!();
struct TestUnit
{
String name;
TestFn func;
}
fn TestUnit[] test_collection_create(Allocator allocator = allocator::heap())
{
TestFn[] fns = $$TEST_FNS;
String[] names = $$TEST_NAMES;
TestUnit[] tests = allocator::alloc_array(allocator, TestUnit, names.len);
foreach (i, test : fns)
{
tests[i] = { names[i], fns[i] };
}
return tests;
}
struct TestContext
{
JmpBuf buf;
}
// Sort the tests by their name in ascending order.
fn int cmp_test_unit(TestUnit a, TestUnit b)
{
usz an = a.name.len;
usz bn = b.name.len;
if (an > bn) @swap(a, b);
foreach (i, ac : a.name)
{
char bc = b.name[i];
if (ac != bc) return an > bn ? bc - ac : ac - bc;
}
return (int)(an - bn);
}
TestContext* test_context @private;
fn void test_panic(String message, String file, String function, uint line)
{
io::printn("[error]");
io::print("\n Error: ");
io::print(message);
io::printn();
io::printfn(" - in %s %s:%s.\n", function, file, line);
libc::longjmp(&test_context.buf, 1);
}
fn bool run_tests(TestUnit[] tests)
{
usz max_name;
foreach (&unit : tests)
{
if (max_name < unit.name.len) max_name = unit.name.len;
}
quicksort(tests, &cmp_test_unit);
TestContext context;
test_context = &context;
PanicFn old_panic = builtin::panic;
defer builtin::panic = old_panic;
builtin::panic = &test_panic;
int tests_passed = 0;
int test_count = tests.len;
DString name = dstring::temp_with_capacity(64);
usz len = max_name + 9;
name.append_repeat('-', len / 2);
name.append(" TESTS ");
name.append_repeat('-', len - len / 2);
io::printn(name);
name.clear();
foreach(unit : tests)
{
defer name.clear();
name.appendf("Testing %s ", unit.name);
name.append_repeat('.', max_name - unit.name.len + 2);
io::printf("%s ", name.str_view());
(void)io::stdout().flush();
if (libc::setjmp(&context.buf) == 0)
{
if (catch err = unit.func())
{
io::printfn("[failed] Failed due to: %s", err);
continue;
}
io::printn("[ok]");
tests_passed++;
}
}
io::printfn("\n%d test%s run.\n", test_count, test_count > 1 ? "s" : "");
io::printfn("Test Result: %s. %d passed, %d failed.",
tests_passed < test_count ? "FAILED" : "ok", tests_passed, test_count - tests_passed);
return test_count == tests_passed;
}
fn bool default_test_runner()
{
@pool()
{
return run_tests(test_collection_create(allocator::temp()));
};
}
module std::core::runtime @if(WASM_NOLIBC);

View File

@@ -1,100 +0,0 @@
module std::core::runtime;
import libc, std::time, std::io, std::sort;
alias BenchmarkFn = fn void();
struct BenchmarkUnit
{
String name;
BenchmarkFn func;
}
fn BenchmarkUnit[] benchmark_collection_create(Allocator allocator)
{
BenchmarkFn[] fns = $$BENCHMARK_FNS;
String[] names = $$BENCHMARK_NAMES;
BenchmarkUnit[] benchmarks = allocator::alloc_array(allocator, BenchmarkUnit, names.len);
foreach (i, benchmark : fns)
{
benchmarks[i] = { names[i], fns[i] };
}
return benchmarks;
}
const DEFAULT_BENCHMARK_WARMUP_ITERATIONS = 3;
const DEFAULT_BENCHMARK_MAX_ITERATIONS = 10000;
uint benchmark_warmup_iterations @private = DEFAULT_BENCHMARK_WARMUP_ITERATIONS;
uint benchmark_max_iterations @private = DEFAULT_BENCHMARK_MAX_ITERATIONS;
fn void set_benchmark_warmup_iterations(uint value) @builtin
{
benchmark_warmup_iterations = value;
}
fn void set_benchmark_max_iterations(uint value) @builtin
{
assert(value > 0);
benchmark_max_iterations = value;
}
fn bool run_benchmarks(BenchmarkUnit[] benchmarks)
{
usz max_name;
foreach (&unit : benchmarks)
{
if (max_name < unit.name.len) max_name = unit.name.len;
}
usz len = max_name + 9;
DString name = dstring::temp_with_capacity(64);
name.append_repeat('-', len / 2);
name.append(" BENCHMARKS ");
name.append_repeat('-', len - len / 2);
io::printn(name);
name.clear();
long sys_clock_started;
long sys_clock_finished;
long sys_clocks;
Clock clock;
foreach(unit : benchmarks)
{
defer name.clear();
name.appendf("Benchmarking %s ", unit.name);
name.append_repeat('.', max_name - unit.name.len + 2);
io::printf("%s ", name.str_view());
for (uint i = 0; i < benchmark_warmup_iterations; i++)
{
unit.func() @inline;
}
clock = std::time::clock::now();
sys_clock_started = $$sysclock();
for (uint i = 0; i < benchmark_max_iterations; i++)
{
unit.func() @inline;
}
sys_clock_finished = $$sysclock();
NanoDuration nano_seconds = clock.mark();
sys_clocks = sys_clock_finished - sys_clock_started;
io::printfn("[COMPLETE] %.2f ns, %.2f CPU's clocks", (float)nano_seconds / benchmark_max_iterations, (float)sys_clocks / benchmark_max_iterations);
}
io::printfn("\n%d benchmark%s run.\n", benchmarks.len, benchmarks.len > 1 ? "s" : "");
return true;
}
fn bool default_benchmark_runner(String[] args) => @pool()
{
return run_benchmarks(benchmark_collection_create(tmem));
}

View File

@@ -1,334 +0,0 @@
// Copyright (c) 2025 Christoffer Lerno. All rights reserved.
// Use of this source code is governed by the MIT license
// a copy of which can be found in the LICENSE_STDLIB file.
module std::core::runtime;
import std::core::test @public;
import std::core::mem::allocator @public;
import libc, std::time, std::io, std::sort;
import std::os::env;
alias TestFn = fn void();
TestContext* test_context @private;
struct TestContext
{
JmpBuf buf;
// Allows filtering test cased or modules by substring, e.g. 'foo::', 'foo::test_add'
String test_filter;
// Triggers debugger breakpoint when assert or test:: checks failed
bool breakpoint_on_assert;
// internal state
bool assert_print_backtrace;
bool has_ansi_codes;
bool is_in_panic;
bool is_quiet_mode;
bool is_no_capture;
String current_test_name;
TestFn setup_fn;
TestFn teardown_fn;
char* error_buffer;
usz error_buffer_capacity;
File fake_stdout;
struct stored
{
File stdout;
File stderr;
Allocator allocator;
}
}
struct TestUnit
{
String name;
TestFn func;
}
fn TestUnit[] test_collection_create(Allocator allocator)
{
TestFn[] fns = $$TEST_FNS;
String[] names = $$TEST_NAMES;
TestUnit[] tests = allocator::alloc_array(allocator, TestUnit, names.len);
foreach (i, test : fns)
{
tests[i] = { names[i], fns[i] };
}
return tests;
}
// Sort the tests by their name in ascending order.
fn int cmp_test_unit(TestUnit a, TestUnit b)
{
usz an = a.name.len;
usz bn = b.name.len;
if (an > bn) @swap(a, b);
foreach (i, ac : a.name)
{
char bc = b.name[i];
if (ac != bc) return an > bn ? bc - ac : ac - bc;
}
return (int)(an - bn);
}
fn bool terminal_has_ansi_codes() @local => @pool()
{
if (try v = env::tget_var("TERM"))
{
if (v.contains("xterm") || v.contains("vt100") || v.contains("screen")) return true;
}
$if env::WIN32 || env::NO_LIBC:
return false;
$else
return io::stdout().isatty();
$endif
}
fn void test_panic(String message, String file, String function, uint line) @local
{
if (test_context.is_in_panic) return;
test_context.is_in_panic = true;
unmute_output(true);
(void)io::stdout().flush();
if (test_context.assert_print_backtrace)
{
$if env::NATIVE_STACKTRACE:
builtin::print_backtrace(message, 0);
$endif
}
io::printf("\nTest failed ^^^ ( %s:%s ) %s\n", file, line, message);
test_context.assert_print_backtrace = true;
if (test_context.breakpoint_on_assert)
{
breakpoint();
}
if (test_context.teardown_fn)
{
test_context.teardown_fn();
}
test_context.is_in_panic = false;
allocator::thread_allocator = test_context.stored.allocator;
libc::longjmp(&test_context.buf, 1);
}
fn void mute_output() @local
{
if (test_context.is_no_capture || !test_context.fake_stdout.file) return;
File* stdout = io::stdout();
File* stderr = io::stderr();
*stderr = test_context.fake_stdout;
*stdout = test_context.fake_stdout;
(void)test_context.fake_stdout.seek(0, Seek.SET)!!;
}
fn void unmute_output(bool has_error) @local
{
if (test_context.is_no_capture || !test_context.fake_stdout.file) return;
File* stdout = io::stdout();
File* stderr = io::stderr();
*stderr = test_context.stored.stderr;
*stdout = test_context.stored.stdout;
usz log_size = test_context.fake_stdout.seek(0, Seek.CURSOR)!!;
if (has_error)
{
io::printf("\nTesting %s ", test_context.current_test_name);
io::printn(test_context.has_ansi_codes ? "[\e[0;31mFAIL\e[0m]" : "[FAIL]");
}
if (has_error && log_size > 0)
{
test_context.fake_stdout.write_byte('\n')!!;
test_context.fake_stdout.write_byte('\0')!!;
(void)test_context.fake_stdout.seek(0, Seek.SET)!!;
io::printfn("\n========== TEST LOG ============");
io::printfn("%s\n", test_context.current_test_name);
while (try c = test_context.fake_stdout.read_byte())
{
if (@unlikely(c == '\0'))
{
// ignore junk from previous tests
break;
}
libc::putchar(c);
}
io::printf("========== TEST END ============");
}
(void)stdout.flush();
}
fn bool run_tests(String[] args, TestUnit[] tests) @private
{
usz max_name;
bool sort_tests = true;
bool check_leaks = true;
foreach (&unit : tests)
{
if (max_name < unit.name.len) max_name = unit.name.len;
}
TestContext context =
{
.assert_print_backtrace = true,
.breakpoint_on_assert = false,
.test_filter = "",
.has_ansi_codes = terminal_has_ansi_codes(),
.stored.allocator = mem,
.stored.stderr = *io::stderr(),
.stored.stdout = *io::stdout(),
};
for (int i = 1; i < args.len; i++)
{
switch (args[i])
{
case "--test-breakpoint":
context.breakpoint_on_assert = true;
case "--test-nosort":
sort_tests = false;
case "--test-noleak":
check_leaks = false;
case "--test-nocapture":
context.is_no_capture = true;
case "--noansi":
context.has_ansi_codes = false;
case "--useansi":
context.has_ansi_codes = true;
case "--test-quiet":
context.is_quiet_mode = true;
case "--test-filter":
if (i == args.len - 1)
{
io::printn("Invalid arguments to test runner.");
return false;
}
context.test_filter = args[i + 1];
i++;
default:
io::printfn("Unknown argument: %s", args[i]);
}
}
test_context = &context;
if (sort_tests)
{
quicksort(tests, &cmp_test_unit);
}
// Buffer for hijacking the output
$if (!env::NO_LIBC):
context.fake_stdout.file = libc::tmpfile();
$endif
if (context.fake_stdout.file == null)
{
io::print("Failed to hijack stdout, tests will print everything");
}
PanicFn old_panic = builtin::panic;
defer builtin::panic = old_panic;
builtin::panic = &test_panic;
int tests_passed = 0;
int tests_skipped = 0;
int test_count = tests.len;
DString name = dstring::temp_with_capacity(64);
usz len = max_name + 9;
name.append_repeat('-', len / 2);
name.append(" TESTS ");
name.append_repeat('-', len - len / 2);
if (!context.is_quiet_mode) io::printn(name);
name.clear();
PoolState temp_state = mem::temp_push();
defer mem::temp_pop(temp_state);
foreach(unit : tests)
{
mem::temp_pop(temp_state);
if (context.test_filter && !unit.name.contains(context.test_filter))
{
tests_skipped++;
continue;
}
context.setup_fn = null;
context.teardown_fn = null;
context.current_test_name = unit.name;
defer name.clear();
name.appendf("Testing %s ", unit.name);
name.append_repeat('.', max_name - unit.name.len + 2);
if (context.is_quiet_mode)
{
io::print(".");
}
else
{
io::printf("%s ", name.str_view());
}
(void)io::stdout().flush();
TrackingAllocator mem;
mem.init(context.stored.allocator);
if (libc::setjmp(&context.buf) == 0)
{
mute_output();
mem.clear();
if (check_leaks) allocator::thread_allocator = &mem;
unit.func();
// track cleanup that may take place in teardown_fn
if (context.teardown_fn)
{
context.teardown_fn();
}
if (check_leaks) allocator::thread_allocator = context.stored.allocator;
unmute_output(false); // all good, discard output
if (mem.has_leaks())
{
if (context.is_quiet_mode) io::printf("\n%s ", context.current_test_name);
io::print(context.has_ansi_codes ? "[\e[0;31mFAIL\e[0m]" : "[FAIL]");
io::printn(" LEAKS DETECTED!");
mem.print_report();
}
else
{
if (!context.is_quiet_mode)
{
io::printfn(context.has_ansi_codes ? "[\e[0;32mPASS\e[0m]" : "[PASS]");
}
tests_passed++;
}
}
mem.free();
}
io::printfn("\n%d test%s run.\n", test_count-tests_skipped, test_count > 1 ? "s" : "");
int n_failed = test_count - tests_passed - tests_skipped;
io::printf("Test Result: %s%s%s: ",
context.has_ansi_codes ? (n_failed ? "\e[0;31m" : "\e[0;32m") : "",
n_failed ? "FAILED" : "PASSED",
context.has_ansi_codes ? "\e[0m" : "",
);
io::printfn("%d passed, %d failed, %d skipped.",
tests_passed,
n_failed,
tests_skipped);
// cleanup fake_stdout file
if (context.fake_stdout.file) libc::fclose(context.fake_stdout.file);
context.fake_stdout.file = null;
return n_failed == 0;
}
fn bool default_test_runner(String[] args) => @pool()
{
assert(test_context == null, "test suite is already running");
return run_tests(args, test_collection_create(tmem));
}

View File

@@ -12,7 +12,7 @@
module std::core::sanitizer::asan;
alias ErrorCallback = fn void (ZString);
def ErrorCallback = fn void (ZString);
<*
Marks a memory region ([addr, addr+size)) as unaddressable.
@@ -26,8 +26,8 @@ alias ErrorCallback = fn void (ZString);
NOTE This function is not thread-safe because no two threads can poison or
unpoison memory in the same memory region simultaneously.
@param addr : "Start of memory region."
@param size : "Size of memory region."
@param addr "Start of memory region."
@param size "Size of memory region."
*>
macro poison_memory_region(void* addr, usz size)
{
@@ -47,8 +47,8 @@ macro poison_memory_region(void* addr, usz size)
NOTE This function is not thread-safe because no two threads can
poison or unpoison memory in the same memory region simultaneously.
@param addr : "Start of memory region."
@param size : "Size of memory region."
@param addr "Start of memory region."
@param size "Size of memory region."
*>
macro unpoison_memory_region(void* addr, usz size)
{
@@ -60,7 +60,7 @@ macro unpoison_memory_region(void* addr, usz size)
<*
Checks if an address is poisoned.
@return "True if 'addr' is poisoned (that is, 1-byte read/write access to this address would result in an error report from ASan). Otherwise returns false."
@param addr : "Address to check."
@param addr "Address to check."
*>
macro bool address_is_poisoned(void* addr)
{
@@ -77,8 +77,8 @@ macro bool address_is_poisoned(void* addr)
If at least one byte in [beg, beg+size) is poisoned, returns the
address of the first such byte. Otherwise returns 0.
@param beg : "Start of memory region."
@param size : "Start of memory region."
@param beg "Start of memory region."
@param size "Start of memory region."
@return "Address of first poisoned byte."
*>
macro void* region_is_poisoned(void* beg, usz size)

View File

@@ -1,6 +1,6 @@
module std::core::sanitizer::tsan;
typedef MutexFlags = inline CUInt;
distinct MutexFlags = inline CUInt;
const MutexFlags MUTEX_LINKER_INIT = 1 << 0;
const MutexFlags MUTEX_WRITE_REENTRANT = 1 << 1;

File diff suppressed because it is too large Load Diff

View File

@@ -11,22 +11,22 @@ fn void StringIterator.reset(&self)
self.current = 0;
}
fn Char32? StringIterator.next(&self)
fn Char32! StringIterator.next(&self)
{
usz len = self.utf8.len;
usz current = self.current;
if (current >= len) return NO_MORE_ELEMENT?;
if (current >= len) return IteratorResult.NO_MORE_ELEMENT?;
usz read = (len - current < 4 ? len - current : 4);
Char32 res = conv::utf8_to_char32(&self.utf8[current], &read)!;
self.current += read;
return res;
}
fn Char32? StringIterator.peek(&self)
fn Char32! StringIterator.peek(&self)
{
usz len = self.utf8.len;
usz current = self.current;
if (current >= len) return NO_MORE_ELEMENT?;
if (current >= len) return IteratorResult.NO_MORE_ELEMENT?;
usz read = (len - current < 4 ? len - current : 4);
Char32 res = conv::utf8_to_char32(&self.utf8[current], &read)!;
return res;
@@ -37,13 +37,13 @@ fn bool StringIterator.has_next(&self)
return self.current < self.utf8.len;
}
fn Char32? StringIterator.get(&self)
fn Char32! StringIterator.get(&self)
{
usz len = self.utf8.len;
usz current = self.current;
usz read = (len - current < 4 ? len - current : 4);
usz index = current > read ? current - read : 0;
if (index >= len) return NO_MORE_ELEMENT?;
if (index >= len) return IteratorResult.NO_MORE_ELEMENT?;
Char32 res = conv::utf8_to_char32(&self.utf8[index], &read)!;
return res;
}

View File

@@ -34,7 +34,7 @@ const uint[2] B1B_MAX = { 9007199, 254740991 };
<*
@require chars.len > 0
*>
macro double? decfloat(char[] chars, int $bits, int $emin, int sign)
macro double! decfloat(char[] chars, int $bits, int $emin, int sign)
{
uint[KMAX] x;
const uint[2] TH = B1B_MAX;
@@ -64,7 +64,7 @@ macro double? decfloat(char[] chars, int $bits, int $emin, int sign)
got_rad = true;
if (index == last_char)
{
if (!got_digit) return MALFORMED_FLOAT?;
if (!got_digit) return NumberConversion.MALFORMED_FLOAT?;
return sign * 0.0;
}
if (index != last_char && (c = chars[++index]) == '0')
@@ -83,7 +83,7 @@ macro double? decfloat(char[] chars, int $bits, int $emin, int sign)
switch
{
case c == '.':
if (got_rad) return MALFORMED_FLOAT?;
if (got_rad) return NumberConversion.MALFORMED_FLOAT?;
got_rad = true;
lrp = dc;
case k < KMAX - 3:
@@ -113,24 +113,24 @@ macro double? decfloat(char[] chars, int $bits, int $emin, int sign)
c = chars[++index];
}
if (!got_rad) lrp = dc;
if (!got_digit) return MALFORMED_FLOAT?;
if (!got_digit) return NumberConversion.MALFORMED_FLOAT?;
if ((c | 32) == 'e')
{
if (last_char == index) return MALFORMED_FLOAT?;
long e10 = String.to_long((String)chars[index + 1..]) ?? MALFORMED_FLOAT?!;
if (last_char == index) return NumberConversion.MALFORMED_FLOAT?;
long e10 = String.to_long((String)chars[index + 1..]) ?? NumberConversion.MALFORMED_FLOAT?!;
lrp += e10;
}
else if (index != last_char)
{
return MALFORMED_FLOAT?;
return NumberConversion.MALFORMED_FLOAT?;
}
// Handle zero specially to avoid nasty special cases later
if (!x[0]) return sign * 0.0;
// Optimize small integers (w/no exponent) and over/under-flow
if (lrp == dc && dc < 10 && ($bits > 30 || (ulong)x[0] >> $bits == 0)) return sign * (double)x[0];
if (lrp > - $emin / 2) return FLOAT_OUT_OF_RANGE?;
if (lrp < $emin - 2 * math::DOUBLE_MANT_DIG) return FLOAT_OUT_OF_RANGE?;
if (lrp > - $emin / 2) return NumberConversion.FLOAT_OUT_OF_RANGE?;
if (lrp < $emin - 2 * math::DOUBLE_MANT_DIG) return NumberConversion.FLOAT_OUT_OF_RANGE?;
// Align incomplete final B1B digit
if (j)
@@ -320,12 +320,12 @@ macro double? decfloat(char[] chars, int $bits, int $emin, int sign)
y *= 0.5;
e2++;
}
if (e2 + math::DOUBLE_MANT_DIG > emax || (denormal && frac)) return MALFORMED_FLOAT?;
if (e2 + math::DOUBLE_MANT_DIG > emax || (denormal && frac)) return NumberConversion.MALFORMED_FLOAT?;
}
return math::scalbn(y, e2);
}
macro double? hexfloat(char[] chars, int $bits, int $emin, int sign)
macro double! hexfloat(char[] chars, int $bits, int $emin, int sign)
{
double scale = 1;
uint x;
@@ -351,7 +351,7 @@ macro double? hexfloat(char[] chars, int $bits, int $emin, int sign)
got_rad = true;
if (index == last_char)
{
if (!got_digit) return MALFORMED_FLOAT?;
if (!got_digit) return NumberConversion.MALFORMED_FLOAT?;
return sign * 0.0;
}
if (index != last_char && (c = chars[++index]) == '0')
@@ -369,14 +369,17 @@ macro double? hexfloat(char[] chars, int $bits, int $emin, int sign)
{
if (c == '.')
{
if (got_rad) return MALFORMED_FLOAT?;
if (got_rad) return NumberConversion.MALFORMED_FLOAT?;
got_rad = true;
rp = dc;
}
else
{
got_digit = true;
int d = c > '9' ? ((c | 32) + 10 - 'a') : (c - '0');
int d = {|
if (c > '9') return (c | 32) + 10 - 'a';
return c - '0';
|};
switch
{
case dc < 8:
@@ -393,20 +396,20 @@ macro double? hexfloat(char[] chars, int $bits, int $emin, int sign)
if (index == last_char) break;
c = chars[++index];
}
if (!got_digit) return MALFORMED_FLOAT?;
if (!got_digit) return NumberConversion.MALFORMED_FLOAT?;
if (!got_rad) rp = dc;
for (; dc < 8; dc++) x *= 16;
long e2;
if ((c | 32) == 'p')
{
long e2val = String.to_long((String)chars[index + 1..]) ?? (MALFORMED_FLOAT?)!;
long e2val = String.to_long((String)chars[index + 1..]) ?? (NumberConversion.MALFORMED_FLOAT?)!;
e2 = e2val;
}
e2 += 4 * rp - 32;
if (!x) return sign * 0.0;
if (e2 > -$emin) return FLOAT_OUT_OF_RANGE?;
if (e2 < $emin - 2 * math::DOUBLE_MANT_DIG) return FLOAT_OUT_OF_RANGE?;
if (e2 > -$emin) return NumberConversion.FLOAT_OUT_OF_RANGE?;
if (e2 < $emin - 2 * math::DOUBLE_MANT_DIG) return NumberConversion.FLOAT_OUT_OF_RANGE?;
while (x < 0x80000000)
{
@@ -441,7 +444,7 @@ macro double? hexfloat(char[] chars, int $bits, int $emin, int sign)
}
y = bias + sign * (double)x + sign * y;
y -= bias;
if (!y) return FLOAT_OUT_OF_RANGE?;
if (!y) return NumberConversion.FLOAT_OUT_OF_RANGE?;
return math::scalbn(y, (int)e2);
}
@@ -449,7 +452,7 @@ macro double? hexfloat(char[] chars, int $bits, int $emin, int sign)
macro String.to_real(chars, $Type) @private
{
int sign = 1;
$switch $Type:
$switch ($Type)
$case float:
const int BITS = math::FLOAT_MANT_DIG;
const int EMIN = math::FLOAT_MIN_EXP - BITS;
@@ -463,18 +466,14 @@ macro String.to_real(chars, $Type) @private
$endswitch
while (chars.len && chars[0] == ' ') chars = chars[1..];
if (!chars.len) return MALFORMED_FLOAT?;
if (chars.len != 1)
if (!chars.len) return NumberConversion.MALFORMED_FLOAT?;
switch (chars[0])
{
switch (chars[0])
{
case '-':
sign = -1;
nextcase;
case '+':
chars = chars[1..];
}
case '-':
sign = -1;
nextcase;
case '+':
chars = chars[1..];
}
if (chars == "infinity" || chars == "INFINITY") return sign * $Type.inf;
if (chars == "NAN" || chars == "nan") return $Type.nan;

View File

@@ -1,220 +0,0 @@
<*
Unit test module
This module provides a toolset of macros for running unit test checks
Example:
```c3
module sample::m;
import std::io;
faultdef DIVISION_BY_ZERO;
fn double? divide(int a, int b)
{
if (b == 0) return MathError.DIVISION_BY_ZERO?;
return (double)(a) / (double)(b);
}
fn void? test_div() @test
{
test::eq(2, divide(6, 3)!);
test::ne(1, 2);
test::ge(3, 3);
test::gt(2, divide(3, 3)!);
test::lt(2, 3);
test::le(2, 3);
test::eq_approx(m::divide(1, 3)!, 0.333, places: 3);
test::@check(2 == 2, "divide: %d", divide(6, 3)!);
test::@error(m::divide(3, 0), MathError.DIVISION_BY_ZERO);
}
```
*>
// Copyright (c) 2025 Alex Veden <i@alexveden.com>. All rights reserved.
// Use of this source code is governed by the MIT license
// a copy of which can be found in the LICENSE_STDLIB file.
module std::core::test;
import std::core::runtime @public;
import std::math, std::io, libc;
<*
Initializes test case context.
@param setup_fn : `initializer function for test case`
@param teardown_fn : `cleanup function for test context (may be null)`
@require runtime::test_context != null : "Only allowed in @test functions"
@require setup_fn != null : "setup_fn must always be set"
*>
macro @setup(TestFn setup_fn, TestFn teardown_fn = null)
{
runtime::test_context.setup_fn = setup_fn;
runtime::test_context.teardown_fn = teardown_fn;
runtime::test_context.setup_fn();
}
<*
Checks condition and fails assertion if not true
@param #condition : `any boolean condition, will be expanded by text`
@param format : `printf compatible format`
@param args : `vargs for format`
@require runtime::test_context != null : "Only allowed in @test functions"
*>
macro @check(#condition, String format = "", args...)
{
if (!#condition)
{
@stack_mem(512; Allocator allocator)
{
DString s;
s.init(allocator);
s.appendf("check `%s` failed. ", $stringify(#condition));
s.appendf(format, ...args);
print_panicf(s.str_view());
};
}
}
<*
Check if function returns specific error
@param #funcresult : `result of function execution`
@param error_expected : `expected error of function execution`
@require runtime::test_context != null : "Only allowed in @test functions"
*>
macro @error(#funcresult, fault error_expected)
{
if (catch err = #funcresult)
{
if (err != error_expected)
{
print_panicf("`%s` expected to return error [%s], got [%s]",
$stringify(#funcresult), error_expected, err);
}
return;
}
print_panicf("`%s` error [%s] was not returned.", $stringify(#funcresult), error_expected);
}
<*
Check if left == right
@param left : `left argument of any comparable type`
@param right : `right argument of any comparable type`
@require runtime::test_context != null : "Only allowed in @test functions"
*>
macro eq(left, right)
{
if (!equals(left, right))
{
print_panicf("`%s` != `%s`", left, right);
}
}
<*
Check left floating point value is approximately equals to right value
@param places : `number of decimal places to compare (default: 7)`
@param delta : `minimal allowed difference (overrides places parameter)`
@param equal_nan : `allows comparing nan values, if left and right both nans result is ok`
@require places > 0, places <= 20 : "too many decimal places"
@require delta >= 0, delta <= 1 : "delta must be a small number"
@require runtime::test_context != null : "Only allowed in @test functions"
*>
macro void eq_approx(double left, double right, uint places = 7, double delta = 0, bool equal_nan = true)
{
double diff = left - right;
double eps = delta;
if (eps == 0) eps = 1.0 / math::pow(10.0, places);
if (!math::is_approx(left, right, eps))
{
if (equal_nan && math::is_nan(left) && math::is_nan(right)) return;
print_panicf("Not almost equal: `%s` !~~ `%s` delta=%e diff: %e", left, right, eps, diff);
}
}
<*
Check if left != right
@param left : `left argument of any comparable type`
@param right : `right argument of any comparable type`
@require runtime::test_context != null : "Only allowed in @test functions"
*>
macro void ne(left, right)
{
if (equals(left, right))
{
print_panicf("`%s` == `%s`", left, right);
}
}
<*
Check if left > right
@param left : `left argument of any comparable type`
@param right : `right argument of any comparable type`
@require runtime::test_context != null : "Only allowed in @test functions"
*>
macro gt(left, right)
{
if (!builtin::greater(left, right))
{
print_panicf("`%s` <= `%s`", left, right);
}
}
<*
Check if left >= right
@param left : `left argument of any comparable type`
@param right : `right argument of any comparable type`
@require runtime::test_context != null : "Only allowed in @test functions"
*>
macro ge(left, right)
{
if (!builtin::greater_eq(left, right))
{
print_panicf("`%s` < `%s`", left, right);
}
}
<*
Check if left < right
@param left : `left argument of any comparable type`
@param right : `right argument of any comparable type`
@require runtime::test_context != null : "Only allowed in @test functions"
*>
macro lt(left, right)
{
if (!builtin::less(left, right))
{
print_panicf("`%s` >= `%s`", left, right);
}
}
<*
Check if left <= right
@param left : `left argument of any comparable type`
@param right : `right argument of any comparable type`
@require runtime::test_context != null : "Only allowed in @test functions"
*>
macro le(left, right)
{
if (!builtin::less_eq(left, right))
{
print_panicf("`%s` > `%s`", left, right);
}
}
macro void print_panicf(format, ...) @local
{
runtime::test_context.assert_print_backtrace = false;
builtin::panicf(format, $$FILE, $$FUNC, $$LINE, $vasplat);
}

View File

@@ -3,25 +3,23 @@ module std::core::types;
import libc;
faultdef VALUE_OUT_OF_RANGE, VALUE_OUT_OF_UNSIGNED_RANGE;
<*
@require $Type.kindof.is_int() : "Type was not an integer"
@require v.type.kindof == ENUM : "Value was not an enum"
*>
macro any_to_enum_ordinal(any v, $Type)
fault ConversionResult
{
return any_to_int(v.as_inner(), $Type);
VALUE_OUT_OF_RANGE,
VALUE_OUT_OF_UNSIGNED_RANGE,
}
<*
@require $Type.kindof.is_int() : "Type was not an integer"
@require v.type.kindof.is_int() : "Value was not an integer"
@require $Type.kindof.is_int() || $Type.kindof == TypeKind.ENUM "Argument was not an integer"
*>
macro any_to_int(any v, $Type)
{
typeid any_type = v.type;
TypeKind kind = any_type.kindof;
if (kind == TypeKind.ENUM)
{
any_type = any_type.inner;
kind = any_type.kindof;
}
bool is_mixed_signed = $Type.kindof != any_type.kindof;
$Type max = $Type.max;
$Type min = $Type.min;
@@ -29,47 +27,47 @@ macro any_to_int(any v, $Type)
{
case ichar:
ichar c = *(char*)v.ptr;
if (is_mixed_signed && c < 0) return VALUE_OUT_OF_UNSIGNED_RANGE?;
if (is_mixed_signed && c < 0) return ConversionResult.VALUE_OUT_OF_UNSIGNED_RANGE?;
return ($Type)c;
case short:
short s = *(short*)v.ptr;
if (is_mixed_signed && s < 0) return VALUE_OUT_OF_UNSIGNED_RANGE?;
if (s > max || s < min) return VALUE_OUT_OF_RANGE?;
if (is_mixed_signed && s < 0) return ConversionResult.VALUE_OUT_OF_UNSIGNED_RANGE?;
if (s > max || s < min) return ConversionResult.VALUE_OUT_OF_RANGE?;
return ($Type)s;
case int:
int i = *(int*)v.ptr;
if (is_mixed_signed && i < 0) return VALUE_OUT_OF_UNSIGNED_RANGE?;
if (i > max || i < min) return VALUE_OUT_OF_RANGE?;
if (is_mixed_signed && i < 0) return ConversionResult.VALUE_OUT_OF_UNSIGNED_RANGE?;
if (i > max || i < min) return ConversionResult.VALUE_OUT_OF_RANGE?;
return ($Type)i;
case long:
long l = *(long*)v.ptr;
if (is_mixed_signed && l < 0) return VALUE_OUT_OF_UNSIGNED_RANGE?;
if (l > max || l < min) return VALUE_OUT_OF_RANGE?;
if (is_mixed_signed && l < 0) return ConversionResult.VALUE_OUT_OF_UNSIGNED_RANGE?;
if (l > max || l < min) return ConversionResult.VALUE_OUT_OF_RANGE?;
return ($Type)l;
case int128:
int128 i = *(int128*)v.ptr;
if (is_mixed_signed && i < 0) return VALUE_OUT_OF_UNSIGNED_RANGE?;
if (i > max || i < min) return VALUE_OUT_OF_RANGE?;
if (is_mixed_signed && i < 0) return ConversionResult.VALUE_OUT_OF_UNSIGNED_RANGE?;
if (i > max || i < min) return ConversionResult.VALUE_OUT_OF_RANGE?;
return ($Type)i;
case char:
char c = *(char*)v.ptr;
if (c > max) return VALUE_OUT_OF_RANGE?;
if (c > max) return ConversionResult.VALUE_OUT_OF_RANGE?;
return ($Type)c;
case ushort:
ushort s = *(ushort*)v.ptr;
if (s > max || s < min) return VALUE_OUT_OF_RANGE?;
if (s > max || s < min) return ConversionResult.VALUE_OUT_OF_RANGE?;
return ($Type)s;
case uint:
uint i = *(uint*)v.ptr;
if (i > max || i < min) return VALUE_OUT_OF_RANGE?;
if (i > max || i < min) return ConversionResult.VALUE_OUT_OF_RANGE?;
return ($Type)i;
case ulong:
ulong l = *(ulong*)v.ptr;
if (l > max || l < min) return VALUE_OUT_OF_RANGE?;
if (l > max || l < min) return ConversionResult.VALUE_OUT_OF_RANGE?;
return ($Type)l;
case uint128:
uint128 i = *(uint128*)v.ptr;
if (i > max || i < min) return VALUE_OUT_OF_RANGE?;
if (i > max || i < min) return ConversionResult.VALUE_OUT_OF_RANGE?;
return ($Type)i;
default:
unreachable();
@@ -89,7 +87,7 @@ fn bool typeid.is_subtype_of(self, typeid other)
macro bool is_subtype_of($Type, $OtherType)
{
var $typeid = $Type.typeid;
$switch $Type:
$switch ($Type)
$case $OtherType: return true;
$default: return false;
$endswitch
@@ -112,7 +110,7 @@ fn bool TypeKind.is_int(kind) @inline
macro bool is_slice_convertable($Type)
{
$switch $Type.kindof:
$switch ($Type.kindof)
$case SLICE:
return true;
$case POINTER:
@@ -126,11 +124,11 @@ macro bool is_bool($Type) @const => $Type.kindof == TypeKind.BOOL;
macro bool is_int($Type) @const => $Type.kindof == TypeKind.SIGNED_INT || $Type.kindof == TypeKind.UNSIGNED_INT;
<*
@require is_numerical($Type) : "Expected a numerical type"
@require is_numerical($Type) "Expected a numerical type"
*>
macro bool is_signed($Type) @const
{
$switch inner_kind($Type):
$switch (inner_kind($Type))
$case SIGNED_INT:
$case FLOAT:
return true;
@@ -142,11 +140,11 @@ macro bool is_signed($Type) @const
}
<*
@require is_numerical($Type) : "Expected a numerical type"
@require is_numerical($Type) "Expected a numerical type"
*>
macro bool is_unsigned($Type) @const
{
$switch inner_kind($Type):
$switch (inner_kind($Type))
$case UNSIGNED_INT:
return true;
$case VECTOR:
@@ -156,37 +154,19 @@ macro bool is_unsigned($Type) @const
$endswitch
}
macro typeid flat_type($Type) @const
{
$if $Type.kindof == DISTINCT:
return flat_type($typefrom($Type.inner));
$else
return $Type.typeid;
$endif
}
macro TypeKind flat_kind($Type) @const
{
$if $Type.kindof == DISTINCT:
return flat_type($typefrom($Type.inner));
$else
return $Type.kindof;
$endif
}
macro bool is_indexable($Type) @const
{
return $defined(($Type){}[0]);
return $defined($Type{}[0]);
}
macro bool is_ref_indexable($Type) @const
{
return $defined(&($Type){}[0]);
return $defined(&$Type{}[0]);
}
macro bool is_intlike($Type) @const
{
$switch $Type.kindof:
$switch ($Type.kindof)
$case SIGNED_INT:
$case UNSIGNED_INT:
return true;
@@ -199,7 +179,7 @@ macro bool is_intlike($Type) @const
macro bool is_underlying_int($Type) @const
{
$switch $Type.kindof:
$switch ($Type.kindof)
$case SIGNED_INT:
$case UNSIGNED_INT:
return true;
@@ -214,7 +194,7 @@ macro bool is_float($Type) @const => $Type.kindof == TypeKind.FLOAT;
macro bool is_floatlike($Type) @const
{
$switch $Type.kindof:
$switch ($Type.kindof)
$case FLOAT:
return true;
$case VECTOR:
@@ -254,7 +234,7 @@ macro bool @has_same(#a, #b, ...) @const
$if $type_a != @typeid(#b):
return false;
$endif
$for var $i = 0; $i < $vacount; $i++:
$for (var $i = 0; $i < $vacount; $i++)
$if @typeid($vaexpr[$i]) != $type_a:
return false;
$endif
@@ -264,8 +244,7 @@ macro bool @has_same(#a, #b, ...) @const
macro bool may_load_atomic($Type) @const
{
$switch $Type.kindof:
$case BOOL:
$switch ($Type.kindof)
$case SIGNED_INT:
$case UNSIGNED_INT:
$case POINTER:
@@ -280,16 +259,15 @@ macro bool may_load_atomic($Type) @const
macro lower_to_atomic_compatible_type($Type) @const
{
$switch $Type.kindof:
$case BOOL:
$case SIGNED_INT:
$case UNSIGNED_INT:
return $Type.typeid;
$switch ($Type.kindof)
$case SIGNED_INT:
$case UNSIGNED_INT:
return $Type.typeid;
$case DISTINCT:
return lower_to_atomic_compatible_type($Type.inner);
$case FLOAT:
$switch $Type:
$case float16:
$switch ($Type)
$case float16:
return ushort.typeid;
$case float:
return uint.typeid;
@@ -334,6 +312,11 @@ macro bool implements_copy($Type) @const
return $defined($Type.copy) && $defined($Type.free);
}
macro bool is_equatable_value(value) @deprecated
{
return is_equatable_type($typeof(value));
}
macro bool @equatable_value(#value) @const
{
return is_equatable_type($typeof(#value));
@@ -348,6 +331,15 @@ macro bool @comparable_value(#value) @const
$endif
}
macro bool is_comparable_value(value) @deprecated
{
$if $defined(value.less) || $defined(value.compare_to):
return true;
$else
return $typeof(value).is_ordered;
$endif
}
enum TypeKind : char
{
VOID,
@@ -356,9 +348,10 @@ enum TypeKind : char
UNSIGNED_INT,
FLOAT,
TYPEID,
FAULT,
ANYFAULT,
ANY,
ENUM,
FAULT,
STRUCT,
UNION,
BITSTRUCT,

View File

@@ -16,7 +16,6 @@ macro bool @is_promotable_to_float(#value) @const => types::is_promotable_to_flo
macro bool @is_vector(#value) @const => types::is_vector($typeof(#value));
macro bool @is_same_vector_type(#value1, #value2) @const => types::is_same_vector_type($typeof(#value1), $typeof(#value2));
macro bool @assign_to(#value1, #value2) @const => $assignable(#value1, $typeof(#value2));
macro bool @is_lvalue(#value) => $defined(#value = #value);
macro promote_int(x)
{
@@ -27,29 +26,10 @@ macro promote_int(x)
$endif
}
<*
Select between two values at compile time,
the values do not have to be of the same type.
This acts like `$bool ? #value_1 : #value_2` but at compile time.
@param $bool : `true for picking the first value, false for the other`
@param #value_1
@param #value_2
@returns `The selected value.`
*>
macro @select(bool $bool, #value_1, #value_2) @builtin
{
$if $bool:
return #value_1;
$else
return #value_2;
$endif
}
macro promote_int_same(x, y)
{
$if @is_int(x):
$switch:
$switch
$case @is_vector(y) &&& $typeof(y).inner == float.typeid:
return (float)x;
$case $typeof(y).typeid == float.typeid:

View File

@@ -12,8 +12,8 @@ struct Rc4
<*
Initialize the RC4 state.
@param [in] key : "The key to use"
@require key.len > 0 : "The key must be at least 1 byte long"
@param [in] key "The key to use"
@require key.len > 0 "The key must be at least 1 byte long"
*>
fn void Rc4.init(&self, char[] key)
{
@@ -43,9 +43,9 @@ fn void crypt(char[] key, char[] data)
<*
Encrypt or decrypt a sequence of bytes.
@param [in] in : "The input"
@param [out] out : "The output"
@require in.len <= out.len : "Output would overflow"
@param [in] in "The input"
@param [out] out "The output"
@require in.len <= out.len "Output would overflow"
*>
fn void Rc4.crypt(&self, char[] in, char[] out)
{
@@ -67,7 +67,7 @@ fn void Rc4.crypt(&self, char[] in, char[] out)
<*
Clear the rc4 state.
@param [&out] self : "The RC4 State"
@param [&out] self "The RC4 State"
*>
fn void Rc4.destroy(&self)
{

View File

@@ -14,13 +14,13 @@ const char DEFAULT_PAD = '=';
<*
Encode the content of src into a newly allocated string
@param [in] src : "The input to be encoded."
@param padding : "The padding character or 0 if none"
@param alphabet : "The alphabet to use"
@require padding < 0xFF : "Invalid padding character"
@param [in] src "The input to be encoded."
@param padding "The padding character or 0 if none"
@param alphabet "The alphabet to use"
@require padding < 0xFF "Invalid padding character"
@return "The encoded string."
*>
fn String? encode(Allocator allocator, char[] src, char padding = DEFAULT_PAD, Base32Alphabet* alphabet = &STANDARD)
fn String! encode(char[] src, Allocator allocator, char padding = DEFAULT_PAD, Base32Alphabet* alphabet = &STANDARD)
{
char[] dst = allocator::alloc_array(allocator, char, encode_len(src.len, padding));
return encode_buffer(src, dst, padding, alphabet);
@@ -28,26 +28,28 @@ fn String? encode(Allocator allocator, char[] src, char padding = DEFAULT_PAD, B
<*
Decode the content of src into a newly allocated char array.
@param [in] src : "The input to be encoded."
@param padding : "The padding character or 0 if none"
@param alphabet : "The alphabet to use"
@require padding < 0xFF : "Invalid padding character"
@param [in] src "The input to be encoded."
@param padding "The padding character or 0 if none"
@param alphabet "The alphabet to use"
@require padding < 0xFF "Invalid padding character"
@return "The decoded data."
*>
fn char[]? decode(Allocator allocator, char[] src, char padding = DEFAULT_PAD, Base32Alphabet* alphabet = &STANDARD)
fn char[]! decode(char[] src, Allocator allocator, char padding = DEFAULT_PAD, Base32Alphabet* alphabet = &STANDARD)
{
char[] dst = allocator::alloc_array(allocator, char, decode_len(src.len, padding));
return decode_buffer(src, dst, padding, alphabet);
}
fn String? tencode(char[] code, char padding = DEFAULT_PAD, Base32Alphabet* alphabet = &STANDARD) @inline => encode(tmem, code, padding, alphabet);
fn char[]? tdecode(char[] code, char padding = DEFAULT_PAD, Base32Alphabet* alphabet = &STANDARD) @inline => decode(tmem, code, padding, alphabet);
fn String! encode_new(char[] code, char padding = DEFAULT_PAD, Base32Alphabet* alphabet = &STANDARD) @inline => encode(code, allocator::heap(), padding, alphabet);
fn String! encode_temp(char[] code, char padding = DEFAULT_PAD, Base32Alphabet* alphabet = &STANDARD) @inline => encode(code, allocator::temp(), padding, alphabet);
fn char[]! decode_new(char[] code, char padding = DEFAULT_PAD, Base32Alphabet* alphabet = &STANDARD) @inline => decode(code, allocator::heap(), padding, alphabet);
fn char[]! decode_temp(char[] code, char padding = DEFAULT_PAD, Base32Alphabet* alphabet = &STANDARD) @inline => decode(code, allocator::temp(), padding, alphabet);
<*
Calculate the length in bytes of the decoded data.
@param n : "Length in bytes of input."
@param padding : "The padding character or 0 if none"
@require padding < 0xFF : "Invalid padding character"
@param n "Length in bytes of input."
@param padding "The padding character or 0 if none"
@require padding < 0xFF "Invalid padding character"
@return "Length in bytes of the decoded data."
*>
fn usz decode_len(usz n, char padding)
@@ -60,9 +62,9 @@ fn usz decode_len(usz n, char padding)
<*
Calculate the length in bytes of the encoded data.
@param n : "Length in bytes on input."
@param padding : "The padding character or 0 if none"
@require padding < 0xFF : "Invalid padding character"
@param n "Length in bytes on input."
@param padding "The padding character or 0 if none"
@require padding < 0xFF "Invalid padding character"
@return "Length in bytes of the encoded data."
*>
fn usz encode_len(usz n, char padding)
@@ -77,16 +79,16 @@ fn usz encode_len(usz n, char padding)
<*
Decode the content of src into dst, which must be properly sized.
@param src : "The input to be decoded."
@param dst : "The decoded input."
@param padding : "The padding character or 0 if none"
@param alphabet : "The alphabet to use"
@require padding < 0xFF : "Invalid padding character"
@require dst.len >= decode_len(src.len, padding) : "Destination buffer too small"
@param src "The input to be decoded."
@param dst "The decoded input."
@param padding "The padding character or 0 if none"
@param alphabet "The alphabet to use"
@require padding < 0xFF "Invalid padding character"
@require dst.len >= decode_len(src.len, padding) "Destination buffer too small"
@return "The resulting dst buffer"
@return? encoding::INVALID_PADDING, encoding::INVALID_CHARACTER
@return! DecodingFailure
*>
fn char[]? decode_buffer(char[] src, char[] dst, char padding = DEFAULT_PAD, Base32Alphabet* alphabet = &STANDARD)
fn char[]! decode_buffer(char[] src, char[] dst, char padding = DEFAULT_PAD, Base32Alphabet* alphabet = &STANDARD)
{
if (src.len == 0) return dst[:0];
char* dst_ptr = dst;
@@ -101,12 +103,12 @@ fn char[]? decode_buffer(char[] src, char[] dst, char padding = DEFAULT_PAD, Bas
{
if (src.len == 0)
{
if (padding > 0) return encoding::INVALID_PADDING?;
if (padding > 0) return DecodingFailure.INVALID_PADDING?;
break;
}
if (src[0] == padding) break;
buf[i] = alphabet.reverse[src[0]];
if (buf[i] == INVALID) return encoding::INVALID_CHARACTER?;
if (buf[i] == INVALID) return DecodingFailure.INVALID_CHARACTER?;
src = src[1..];
}
@@ -150,7 +152,7 @@ fn char[]? decode_buffer(char[] src, char[] dst, char padding = DEFAULT_PAD, Bas
dst[0] = buf[1] >> 2 | buf[0] << 3;
n++;
default:
return encoding::INVALID_CHARACTER?;
return DecodingFailure.INVALID_CHARACTER?;
}
if (dst.len < 5) break;
dst = dst[5..];
@@ -160,12 +162,12 @@ fn char[]? decode_buffer(char[] src, char[] dst, char padding = DEFAULT_PAD, Bas
<*
Encode the content of src into dst, which must be properly sized.
@param [in] src : "The input to be encoded."
@param [inout] dst : "The encoded input."
@param padding : "The padding character or 0 if none"
@param alphabet : "The alphabet to use"
@require padding < 0xFF : "Invalid padding character"
@require dst.len >= encode_len(src.len, padding) : "Destination buffer too small"
@param [in] src "The input to be encoded."
@param [inout] dst "The encoded input."
@param padding "The padding character or 0 if none"
@param alphabet "The alphabet to use"
@require padding < 0xFF "Invalid padding character"
@require dst.len >= encode_len(src.len, padding) "Destination buffer too small"
@return "The encoded size."
*>
fn String encode_buffer(char[] src, char[] dst, char padding = DEFAULT_PAD, Base32Alphabet* alphabet = &STANDARD)
@@ -242,7 +244,138 @@ const char INVALID @private = 0xff;
const int STD_PADDING = '=';
const int NO_PADDING = -1;
typedef Alphabet = char[32];
fault Base32Error
{
DUPLICATE_IN_ALPHABET,
PADDING_IN_ALPHABET,
INVALID_CHARACTER_IN_ALPHABET,
DESTINATION_TOO_SMALL,
INVALID_PADDING,
CORRUPT_INPUT
}
struct Base32Encoder @deprecated
{
Base32Alphabet alphabet;
char padding;
}
<*
@param encoder "The 32-character alphabet for encoding."
@param padding "Set to a negative value to disable padding."
@require padding < 256
*>
fn void! Base32Encoder.init(&self, Alphabet encoder = STD_ALPHABET, int padding = STD_PADDING)
{
encoder.validate(padding)!;
*self = { .alphabet = { .encoding = (char[32])encoder }, .padding = padding < 0 ? (char)0 : (char)padding};
}
<*
Calculate the length in bytes of the encoded data.
@param n "Length in bytes on input."
@return "Length in bytes of the encoded data."
*>
fn usz Base32Encoder.encode_len(&self, usz n)
{
return encode_len(n, self.padding);
}
<*
Encode the content of src into dst, which must be properly sized.
@param [in] src "The input to be encoded."
@param [inout] dst "The encoded input."
@return "The encoded size."
@return! Base32Error.DESTINATION_TOO_SMALL
*>
fn usz! Base32Encoder.encode(&self, char[] src, char[] dst)
{
usz dn = self.encode_len(src.len);
if (dst.len < dn) return Base32Error.DESTINATION_TOO_SMALL?;
return encode_buffer(src, dst, self.padding, &self.alphabet).len;
}
struct Base32Decoder @deprecated
{
Base32Alphabet alphabet;
char padding;
}
<*
@param decoder "The alphabet used for decoding."
@param padding "Set to a negative value to disable padding."
@require padding < 256
*>
fn void! Base32Decoder.init(&self, Alphabet decoder = STD_ALPHABET, int padding = STD_PADDING)
{
decoder.validate(padding)!;
*self = { .alphabet = { .encoding = (char[32])decoder }, .padding = padding < 0 ? (char)0 : (char)padding };
self.alphabet.reverse[..] = INVALID;
foreach (char i, c : decoder)
{
self.alphabet.reverse[c] = i;
}
}
<*
Calculate the length in bytes of the decoded data.
@param n "Length in bytes of input."
@return "Length in bytes of the decoded data."
*>
fn usz Base32Decoder.decode_len(&self, usz n)
{
return decode_len(n, self.padding);
}
<*
Decode the content of src into dst, which must be properly sized.
@param src "The input to be decoded."
@param dst "The decoded input."
@return "The decoded size."
@return! Base32Error.DESTINATION_TOO_SMALL, Base32Error.CORRUPT_INPUT
*>
fn usz! Base32Decoder.decode(&self, char[] src, char[] dst)
{
if (src.len == 0) return 0;
usz dn = self.decode_len(src.len);
if (dst.len < dn) return Base32Error.DESTINATION_TOO_SMALL?;
return decode_buffer(src, dst, self.padding, &self.alphabet).len;
}
// Validate the 32-character alphabet to make sure that no character occurs
// twice and that the padding is not present in the alphabet.
fn void! Alphabet.validate(&self, int padding)
{
bool[256] checked;
foreach (c : self)
{
if (checked[c])
{
return Base32Error.DUPLICATE_IN_ALPHABET?;
}
checked[c] = true;
if (c == '\r' || c == '\n')
{
return Base32Error.INVALID_CHARACTER_IN_ALPHABET?;
}
}
if (padding >= 0)
{
char pad = (char)padding;
if (pad == '\r' || pad == '\n')
{
return Base32Error.INVALID_PADDING?;
}
if (checked[pad])
{
return Base32Error.PADDING_IN_ALPHABET?;
}
}
}
distinct Alphabet = char[32];
// Standard base32 Alphabet
const Alphabet STD_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
// Extended Hex Alphabet

View File

@@ -43,27 +43,29 @@ const Base64Alphabet URL = {
const STD_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
const URL_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
fn String encode(Allocator allocator, char[] src, char padding = DEFAULT_PAD, Base64Alphabet* alphabet = &STANDARD)
fn String encode(char[] src, Allocator allocator, char padding = DEFAULT_PAD, Base64Alphabet* alphabet = &STANDARD)
{
char[] dst = allocator::alloc_array(allocator, char, encode_len(src.len, padding));
return encode_buffer(src, dst, padding, alphabet);
}
fn char[]? decode(Allocator allocator, char[] src, char padding = DEFAULT_PAD, Base64Alphabet* alphabet = &STANDARD)
fn char[]! decode(char[] src, Allocator allocator, char padding = DEFAULT_PAD, Base64Alphabet* alphabet = &STANDARD)
{
char[] dst = allocator::alloc_array(allocator, char, decode_len(src.len, padding))!;
return decode_buffer(src, dst, padding, alphabet);
}
fn String tencode(char[] code, char padding = DEFAULT_PAD, Base64Alphabet* alphabet = &STANDARD) @inline => encode(tmem, code, padding, alphabet);
fn char[]? tdecode(char[] code, char padding = DEFAULT_PAD, Base64Alphabet* alphabet = &STANDARD) @inline => decode(tmem, code, padding, alphabet);
fn String encode_new(char[] code, char padding = DEFAULT_PAD, Base64Alphabet* alphabet = &STANDARD) @inline => encode(code, allocator::heap(), padding, alphabet);
fn String encode_temp(char[] code, char padding = DEFAULT_PAD, Base64Alphabet* alphabet = &STANDARD) @inline => encode(code, allocator::temp(), padding, alphabet);
fn char[]! decode_new(char[] code, char padding = DEFAULT_PAD, Base64Alphabet* alphabet = &STANDARD) @inline => decode(code, allocator::heap(), padding, alphabet);
fn char[]! decode_temp(char[] code, char padding = DEFAULT_PAD, Base64Alphabet* alphabet = &STANDARD) @inline => decode(code, allocator::temp(), padding, alphabet);
<*
Calculate the size of the encoded data.
@param n : "Size of the input to be encoded."
@param padding : "The padding character or 0 if none"
@require padding < 0xFF : "Invalid padding character"
@param n "Size of the input to be encoded."
@param padding "The padding character or 0 if none"
@require padding < 0xFF "Invalid padding character"
@return "The size of the input once encoded."
*>
fn usz encode_len(usz n, char padding)
@@ -75,34 +77,35 @@ fn usz encode_len(usz n, char padding)
<*
Calculate the size of the decoded data.
@param n : "Size of the input to be decoded."
@param padding : "The padding character or 0 if none"
@require padding < 0xFF : "Invalid padding character"
@param n "Size of the input to be decoded."
@param padding "The padding character or 0 if none"
@require padding < 0xFF "Invalid padding character"
@return "The size of the input once decoded."
@return? encoding::INVALID_PADDING
@return! DecodingFailure.INVALID_PADDING
*>
fn usz? decode_len(usz n, char padding)
fn usz! decode_len(usz n, char padding)
{
usz dn = n / 4 * 3;
usz trailing = n % 4;
if (padding)
{
if (trailing != 0) return encoding::INVALID_PADDING?;
if (trailing != 0) return DecodingFailure.INVALID_PADDING?;
// source size is multiple of 4
return dn;
}
if (trailing == 1) return encoding::INVALID_PADDING?;
if (trailing == 1) return DecodingFailure.INVALID_PADDING?;
return dn + trailing * 3 / 4;
}
<*
Encode the content of src into dst, which must be properly sized.
@param src : "The input to be encoded."
@param dst : "The encoded input."
@param padding : "The padding character or 0 if none"
@param alphabet : "The alphabet to use"
@require padding < 0xFF : "Invalid padding character"
@param src "The input to be encoded."
@param dst "The encoded input."
@param padding "The padding character or 0 if none"
@param alphabet "The alphabet to use"
@require padding < 0xFF "Invalid padding character"
@return "The encoded size."
@return! Base64Error.DESTINATION_TOO_SMALL
*>
fn String encode_buffer(char[] src, char[] dst, char padding = DEFAULT_PAD, Base64Alphabet* alphabet = &STANDARD)
{
@@ -156,16 +159,16 @@ fn String encode_buffer(char[] src, char[] dst, char padding = DEFAULT_PAD, Base
<*
Decode the content of src into dst, which must be properly sized.
@param src : "The input to be decoded."
@param dst : "The decoded input."
@param padding : "The padding character or 0 if none"
@param alphabet : "The alphabet to use"
@require (decode_len(src.len, padding) ?? 0) <= dst.len : "Destination buffer too small"
@require padding < 0xFF : "Invalid padding character"
@param src "The input to be decoded."
@param dst "The decoded input."
@param padding "The padding character or 0 if none"
@param alphabet "The alphabet to use"
@require (decode_len(src.len, padding) ?? 0) <= dst.len "Destination buffer too small"
@require padding < 0xFF "Invalid padding character"
@return "The decoded data."
@return? encoding::INVALID_CHARACTER, encoding::INVALID_PADDING
@return! DecodingFailure
*>
fn char[]? decode_buffer(char[] src, char[] dst, char padding = DEFAULT_PAD, Base64Alphabet* alphabet = &STANDARD)
fn char[]! decode_buffer(char[] src, char[] dst, char padding = DEFAULT_PAD, Base64Alphabet* alphabet = &STANDARD)
{
if (src.len == 0) return dst[:0];
usz dn = decode_len(src.len, padding)!;
@@ -196,7 +199,7 @@ fn char[]? decode_buffer(char[] src, char[] dst, char padding = DEFAULT_PAD, Bas
case c1:
case c2:
case c3:
return encoding::INVALID_CHARACTER?;
return DecodingFailure.INVALID_CHARACTER?;
}
uint group = (uint)c0 << 18 | (uint)c1 << 12 | (uint)c2 << 6 | (uint)c3;
dst[0] = (char)(group >> 16);
@@ -211,7 +214,7 @@ fn char[]? decode_buffer(char[] src, char[] dst, char padding = DEFAULT_PAD, Bas
src = src[^trailing..];
char c0 = alphabet.reverse[src[0]];
char c1 = alphabet.reverse[src[1]];
if (c0 == 0xFF || c1 == 0xFF) return encoding::INVALID_PADDING?;
if (c0 == 0xFF || c1 == 0xFF) return DecodingFailure.INVALID_PADDING?;
if (!padding)
{
switch (src.len)
@@ -221,7 +224,7 @@ fn char[]? decode_buffer(char[] src, char[] dst, char padding = DEFAULT_PAD, Bas
dst[0] = (char)(group >> 16);
case 3:
char c2 = alphabet.reverse[src[2]];
if (c2 == 0xFF) return encoding::INVALID_CHARACTER?;
if (c2 == 0xFF) return DecodingFailure.INVALID_CHARACTER?;
uint group = (uint)c0 << 18 | (uint)c1 << 12 | (uint)c2 << 6;
dst[0] = (char)(group >> 16);
dst[1] = (char)(group >> 8);
@@ -235,13 +238,13 @@ fn char[]? decode_buffer(char[] src, char[] dst, char padding = DEFAULT_PAD, Bas
switch (padding)
{
case src[2]:
if (src[3] != padding) return encoding::INVALID_PADDING?;
if (src[3] != padding) return DecodingFailure.INVALID_PADDING?;
uint group = (uint)c0 << 18 | (uint)c1 << 12;
dst[0] = (char)(group >> 16);
dn -= 2;
case src[3]:
char c2 = alphabet.reverse[src[2]];
if (c2 == 0xFF) return encoding::INVALID_CHARACTER?;
if (c2 == 0xFF) return DecodingFailure.INVALID_CHARACTER?;
uint group = (uint)c0 << 18 | (uint)c1 << 12 | (uint)c2 << 6;
dst[0] = (char)(group >> 16);
dst[1] = (char)(group >> 8);
@@ -253,3 +256,146 @@ fn char[]? decode_buffer(char[] src, char[] dst, char padding = DEFAULT_PAD, Bas
const MASK @private = 0b111111;
struct Base64Encoder @deprecated
{
char padding;
String alphabet;
}
fault Base64Error
{
DUPLICATE_IN_ALPHABET,
PADDING_IN_ALPHABET,
DESTINATION_TOO_SMALL,
INVALID_PADDING,
INVALID_CHARACTER,
}
<*
@param alphabet "The alphabet used for encoding."
@param padding "Set to a negative value to disable padding."
@require alphabet.len == 64
@require padding < 256
@return! Base64Error.DUPLICATE_IN_ALPHABET, Base64Error.PADDING_IN_ALPHABET
*>
fn Base64Encoder*! Base64Encoder.init(&self, String alphabet, int padding = '=')
{
check_alphabet(alphabet, padding)!;
*self = { .padding = padding < 0 ? 0 : (char)padding, .alphabet = alphabet };
return self;
}
<*
Calculate the size of the encoded data.
@param n "Size of the input to be encoded."
@return "The size of the input once encoded."
*>
fn usz Base64Encoder.encode_len(&self, usz n)
{
return encode_len(n, self.padding);
}
<*
Encode the content of src into dst, which must be properly sized.
@param src "The input to be encoded."
@param dst "The encoded input."
@return "The encoded size."
@return! Base64Error.DESTINATION_TOO_SMALL
*>
fn usz! Base64Encoder.encode(&self, char[] src, char[] dst)
{
if (src.len == 0) return 0;
usz dn = self.encode_len(src.len);
if (dst.len < dn) return Base64Error.DESTINATION_TOO_SMALL?;
Base64Alphabet a = { .encoding = self.alphabet[:64] };
return encode_buffer(src, dst, self.padding, &a).len;
}
struct Base64Decoder @deprecated
{
char padding;
Base64Alphabet encoding;
bool init_done;
}
import std;
<*
@param alphabet "The alphabet used for encoding."
@param padding "Set to a negative value to disable padding."
@require alphabet.len == 64
@require padding < 256
@return! Base64Error.DUPLICATE_IN_ALPHABET, Base64Error.PADDING_IN_ALPHABET
*>
fn void! Base64Decoder.init(&self, String alphabet, int padding = '=')
{
self.init_done = true;
check_alphabet(alphabet, padding)!;
*self = { .padding = padding < 0 ? 0 : (char)padding, .encoding.encoding = alphabet[:64] };
self.encoding.reverse[..] = 0xFF;
foreach (i, c : alphabet)
{
self.encoding.reverse[c] = (char)i;
}
}
<*
Calculate the size of the decoded data.
@param n "Size of the input to be decoded."
@return "The size of the input once decoded."
@return! Base64Error.INVALID_PADDING
*>
fn usz! Base64Decoder.decode_len(&self, usz n)
{
return decode_len(n, self.padding) ?? Base64Error.INVALID_PADDING?;
}
<*
Decode the content of src into dst, which must be properly sized.
@param src "The input to be decoded."
@param dst "The decoded input."
@return "The decoded size."
@return! Base64Error.DESTINATION_TOO_SMALL, Base64Error.INVALID_PADDING, Base64Error.INVALID_CHARACTER
*>
fn usz! Base64Decoder.decode(&self, char[] src, char[] dst)
{
if (src.len == 0) return 0;
usz dn = self.decode_len(src.len)!;
if (dst.len < dn) return Base64Error.DESTINATION_TOO_SMALL?;
char[]! decoded = decode_buffer(src, dst, self.padding, &self.encoding);
if (catch err = decoded)
{
case DecodingFailure.INVALID_PADDING:
return Base64Error.INVALID_PADDING?;
case DecodingFailure.INVALID_CHARACTER:
return Base64Error.INVALID_CHARACTER?;
default:
return err?;
}
return decoded.len;
}
// Make sure that all bytes in the alphabet are unique and
// the padding is not present in the alphabet.
fn void! check_alphabet(String alphabet, int padding) @local
{
bool[256] checked;
if (padding < 0)
{
foreach (c : alphabet)
{
if (checked[c]) return Base64Error.DUPLICATE_IN_ALPHABET?;
checked[c] = true;
}
return;
}
char pad = (char)padding;
foreach (c : alphabet)
{
if (c == pad) return Base64Error.PADDING_IN_ALPHABET?;
if (checked[c]) return Base64Error.DUPLICATE_IN_ALPHABET?;
checked[c] = true;
}
}

View File

@@ -15,7 +15,7 @@ struct CsvRow (Printable)
Allocator allocator;
}
fn usz? CsvRow.to_format(&self, Formatter* f) @dynamic
fn usz! CsvRow.to_format(&self, Formatter* f) @dynamic
{
return f.printf("%s", self.list);
}
@@ -38,24 +38,30 @@ fn void CsvReader.init(&self, InStream stream, String separator = ",")
self.stream = stream;
self.separator = separator;
}
fn CsvRow! CsvReader.read_new_row(self)
{
return self.read_row(allocator::heap()) @inline;
}
<*
@param [&inout] allocator
*>
fn CsvRow? CsvReader.read_row(self, Allocator allocator)
fn CsvRow! CsvReader.read_row(self, Allocator allocator)
{
String row = io::readline(allocator, self.stream)!;
String row = io::readline(self.stream, allocator: allocator)!;
defer catch allocator::free(allocator, row);
String[] list = row.split(allocator, self.separator);
String[] list = row.split(self.separator, allocator: allocator);
return { list, row, allocator };
}
fn CsvRow? CsvReader.tread_row(self)
fn CsvRow! CsvReader.read_temp_row(self)
{
return self.read_row(tmem) @inline;
return self.read_row(allocator::temp()) @inline;
}
<*
@require self.allocator != null : `Row already freed`
@require self.allocator `Row already freed`
*>
fn void CsvRow.free(&self)
{
@@ -64,32 +70,29 @@ fn void CsvRow.free(&self)
self.allocator = null;
}
fn void? CsvReader.skip_row(self) @maydiscard => @pool()
fn void! CsvReader.skip_row(self) @maydiscard
{
(void)io::treadline(self.stream);
@pool()
{
(void)io::treadline(self.stream);
};
}
macro void? @each_row(InStream stream, String separator = ",", int max_rows = int.max; @body(String[] row)) @maydiscard
macro CsvReader.@each_row(self, int rows = int.max; @body(String[] row))
{
while (max_rows--)
InStream stream = self.stream;
String sep = self.separator;
while (rows--)
{
@stack_mem(512; mem)
@stack_mem(512; Allocator mem)
{
String? s = io::readline(mem, stream);
String! s = io::readline(stream, mem);
if (catch err = s)
{
if (err == io::EOF) return;
if (err == IoError.EOF) return;
return err?;
}
@body(s.split(mem, separator));
@body(s.split(sep, allocator: mem));
};
}
}
macro void? CsvReader.@each_row(self, int rows = int.max; @body(String[] row)) @maydiscard
{
return @each_row(self.stream, self.separator, rows; row)
{
@body(row);
};
}

View File

@@ -1,3 +1,7 @@
module std::encoding;
faultdef INVALID_CHARACTER, INVALID_PADDING;
fault DecodingFailure
{
INVALID_CHARACTER,
INVALID_PADDING,
}

View File

@@ -8,40 +8,41 @@ fn String encode_buffer(char[] code, char[] buffer)
return (String)buffer[:encode_bytes(code, buffer)];
}
fn char[]? decode_buffer(char[] code, char[] buffer)
fn char[]! decode_buffer(char[] code, char[] buffer)
{
return buffer[:decode_bytes(code, buffer)!];
}
fn String encode(Allocator allocator, char[] code)
fn String encode(char[] code, Allocator allocator)
{
char[] data = allocator::alloc_array(allocator, char, encode_len(code.len));
return (String)data[:encode_bytes(code, data)];
}
fn char[]? decode(Allocator allocator, char[] code)
fn char[]! decode(char[] code, Allocator allocator)
{
char[] data = allocator::alloc_array(allocator, char, decode_len(code.len));
return data[:decode_bytes(code, data)!];
}
fn String tencode(char[] code) @inline => encode(tmem, code);
fn char[]? tdecode(char[] code) @inline => decode(tmem, code);
fn String encode_new(char[] code) @inline => encode(code, allocator::heap());
fn String encode_temp(char[] code) @inline => encode(code, allocator::temp());
fn char[]! decode_new(char[] code) @inline => decode(code, allocator::heap());
fn char[]! decode_temp(char[] code) @inline => decode(code, allocator::temp());
<*
Calculate the size of the encoded data.
@param n : "Size of the input to be encoded."
@param n "Size of the input to be encoded."
@return "The size of the input once encoded."
*>
fn usz encode_len(usz n) => n * 2;
<*
Encode the content of src into dst, which must be properly sized.
@param src : "The input to be encoded."
@param dst : "The encoded input."
@param src "The input to be encoded."
@param dst "The encoded input."
@return "The encoded size."
@require dst.len >= encode_len(src.len) : "Destination array is not large enough"
@require dst.len >= encode_len(src.len) "Destination array is not large enough"
*>
fn usz encode_bytes(char[] src, char[] dst)
{
@@ -57,7 +58,7 @@ fn usz encode_bytes(char[] src, char[] dst)
<*
Calculate the size of the decoded data.
@param n : "Size of the input to be decoded."
@param n "Size of the input to be decoded."
@return "The size of the input once decoded."
*>
macro usz decode_len(usz n) => n / 2;
@@ -68,20 +69,20 @@ macro usz decode_len(usz n) => n / 2;
Expects that src only contains hexadecimal characters and that src has even
length.
@param src : "The input to be decoded."
@param dst : "The decoded input."
@require src.len % 2 == 0 : "src is not of even length"
@require dst.len >= decode_len(src.len) : "Destination array is not large enough"
@return? encoding::INVALID_CHARACTER
@param src "The input to be decoded."
@param dst "The decoded input."
@require src.len % 2 == 0 "src is not of even length"
@require dst.len >= decode_len(src.len) "Destination array is not large enough"
@return! DecodingFailure.INVALID_CHARACTER
*>
fn usz? decode_bytes(char[] src, char[] dst)
fn usz! decode_bytes(char[] src, char[] dst)
{
usz i;
for (usz j = 1; j < src.len; j += 2)
{
char a = HEXREVERSE[src[j - 1]];
char b = HEXREVERSE[src[j]];
if (a > 0x0f || b > 0x0f) return encoding::INVALID_CHARACTER?;
if (a > 0x0f || b > 0x0f) return DecodingFailure.INVALID_CHARACTER?;
dst[i] = (a << 4) | b;
i++;
}

View File

@@ -6,33 +6,40 @@ import std::io;
import std::ascii;
import std::collections::object;
faultdef UNEXPECTED_CHARACTER, INVALID_ESCAPE_SEQUENCE, DUPLICATE_MEMBERS, INVALID_NUMBER;
fn Object*? parse_string(Allocator allocator, String s)
fault JsonParsingError
{
return parse(allocator, (ByteReader){}.init(s));
EOF,
UNEXPECTED_CHARACTER,
INVALID_ESCAPE_SEQUENCE,
DUPLICATE_MEMBERS,
INVALID_NUMBER,
}
fn Object*? tparse_string(String s)
fn Object*! parse_string(String s, Allocator allocator = allocator::heap())
{
return parse(tmem, (ByteReader){}.init(s));
return parse(ByteReader{}.init(s), allocator);
}
fn Object*? parse(Allocator allocator, InStream s)
fn Object*! temp_parse_string(String s)
{
@stack_mem(512; Allocator smem)
return parse(ByteReader{}.init(s), allocator::temp());
}
fn Object*! parse(InStream s, Allocator allocator = allocator::heap())
{
@stack_mem(512; Allocator mem)
{
JsonContext context = { .last_string = dstring::new_with_capacity(smem, 64), .stream = s, .allocator = allocator };
@pool()
JsonContext context = { .last_string = dstring::new_with_capacity(64, mem), .stream = s, .allocator = allocator };
@pool(allocator)
{
return parse_any(&context);
};
};
}
fn Object*? tparse(InStream s)
fn Object*! temp_parse(InStream s)
{
return parse(tmem, s);
return parse(s, allocator::temp());
}
// -- Implementation follows --
@@ -71,7 +78,7 @@ struct JsonContext @local
}
fn Object*? parse_from_token(JsonContext* context, JsonTokenType token) @local
fn Object*! parse_from_token(JsonContext* context, JsonTokenType token) @local
{
switch (token)
{
@@ -81,25 +88,25 @@ fn Object*? parse_from_token(JsonContext* context, JsonTokenType token) @local
case COMMA:
case RBRACE:
case RBRACKET:
case COLON: return UNEXPECTED_CHARACTER?;
case COLON: return JsonParsingError.UNEXPECTED_CHARACTER?;
case STRING: return object::new_string(context.last_string.str_view(), context.allocator);
case NUMBER: return object::new_float(context.last_number, context.allocator);
case TRUE: return object::new_bool(true);
case FALSE: return object::new_bool(false);
case NULL: return object::new_null();
case EOF: return io::EOF?;
case EOF: return JsonParsingError.EOF?;
}
}
fn Object*? parse_any(JsonContext* context) @local
fn Object*! parse_any(JsonContext* context) @local
{
return parse_from_token(context, advance(context));
}
fn JsonTokenType? lex_number(JsonContext *context, char c) @local
fn JsonTokenType! lex_number(JsonContext *context, char c) @local
{
@stack_mem(256; Allocator mem)
{
DString t = dstring::new_with_capacity(mem, 32);
DString t = dstring::new_with_capacity(32, allocator: mem);
bool negate = c == '-';
if (negate)
{
@@ -130,7 +137,7 @@ fn JsonTokenType? lex_number(JsonContext *context, char c) @local
t.append(c);
c = read_next(context)!;
}
if (!c.is_digit()) return INVALID_NUMBER?;
if (!c.is_digit()) return JsonParsingError.INVALID_NUMBER?;
while (c.is_digit())
{
t.append(c);
@@ -138,13 +145,13 @@ fn JsonTokenType? lex_number(JsonContext *context, char c) @local
}
}
pushback(context, c);
double? d = t.str_view().to_double() ?? INVALID_NUMBER?;
double! d = t.str_view().to_double() ?? JsonParsingError.INVALID_NUMBER?;
context.last_number = d!;
return NUMBER;
};
}
fn Object*? parse_map(JsonContext* context) @local
fn Object*! parse_map(JsonContext* context) @local
{
Object* map = object::new_obj(context.allocator);
defer catch map.free();
@@ -152,12 +159,12 @@ fn Object*? parse_map(JsonContext* context) @local
@stack_mem(256; Allocator mem)
{
DString temp_key = dstring::new_with_capacity(mem, 32);
DString temp_key = dstring::new_with_capacity(32, mem);
while (token != JsonTokenType.RBRACE)
{
if (token != JsonTokenType.STRING) return UNEXPECTED_CHARACTER?;
if (token != JsonTokenType.STRING) return JsonParsingError.UNEXPECTED_CHARACTER?;
DString string = context.last_string;
if (map.has_key(string.str_view())) return DUPLICATE_MEMBERS?;
if (map.has_key(string.str_view())) return JsonParsingError.DUPLICATE_MEMBERS?;
// Copy the key to our temp holder, since our
// last_string may be used in parse_any
temp_key.clear();
@@ -171,13 +178,13 @@ fn Object*? parse_map(JsonContext* context) @local
token = advance(context)!;
continue;
}
if (token != JsonTokenType.RBRACE) return UNEXPECTED_CHARACTER?;
if (token != JsonTokenType.RBRACE) return JsonParsingError.UNEXPECTED_CHARACTER?;
}
return map;
};
}
fn Object*? parse_array(JsonContext* context) @local
fn Object*! parse_array(JsonContext* context) @local
{
Object* list = object::new_obj(context.allocator);
defer catch list.free();
@@ -192,7 +199,7 @@ fn Object*? parse_array(JsonContext* context) @local
token = advance(context)!;
continue;
}
if (token != JsonTokenType.RBRACKET) return UNEXPECTED_CHARACTER?;
if (token != JsonTokenType.RBRACKET) return JsonParsingError.UNEXPECTED_CHARACTER?;
}
return list;
}
@@ -207,7 +214,7 @@ fn void pushback(JsonContext* context, char c) @local
}
}
fn char? read_next(JsonContext* context) @local
fn char! read_next(JsonContext* context) @local
{
if (context.reached_end) return '\0';
if (context.pushed_back)
@@ -215,15 +222,14 @@ fn char? read_next(JsonContext* context) @local
context.pushed_back = false;
return context.current;
}
char? c = context.stream.read_byte();
char! c = context.stream.read_byte();
if (catch err = c)
{
if (err == io::EOF)
{
case IoError.EOF:
context.reached_end = true;
return '\0';
}
return err?;
default:
return err?;
}
if (c == 0)
{
@@ -232,7 +238,7 @@ fn char? read_next(JsonContext* context) @local
return c;
}
fn JsonTokenType? advance(JsonContext* context) @local
fn JsonTokenType! advance(JsonContext* context) @local
{
char c;
// Skip whitespace
@@ -280,7 +286,7 @@ fn JsonTokenType? advance(JsonContext* context) @local
switch (c)
{
case '\0':
return io::EOF?;
return IoError.EOF?;
case '{':
return LBRACE;
case '}':
@@ -308,25 +314,25 @@ fn JsonTokenType? advance(JsonContext* context) @local
match(context, "ull")!;
return NULL;
default:
return UNEXPECTED_CHARACTER?;
return JsonParsingError.UNEXPECTED_CHARACTER?;
}
}
fn void? match(JsonContext* context, String str) @local
fn void! match(JsonContext* context, String str) @local
{
foreach (c : str)
{
char l = read_next(context)!;
if (l != c) return UNEXPECTED_CHARACTER?;
if (l != c) return JsonParsingError.UNEXPECTED_CHARACTER?;
}
}
fn void? parse_expected(JsonContext* context, JsonTokenType token) @local
fn void! parse_expected(JsonContext* context, JsonTokenType token) @local
{
if (advance(context)! != token) return UNEXPECTED_CHARACTER?;
if (advance(context)! != token) return JsonParsingError.UNEXPECTED_CHARACTER?;
}
fn JsonTokenType? lex_string(JsonContext* context)
fn JsonTokenType! lex_string(JsonContext* context)
{
context.last_string.clear();
while LOOP: (true)
@@ -335,9 +341,9 @@ fn JsonTokenType? lex_string(JsonContext* context)
switch (c)
{
case '\0':
return io::EOF?;
return JsonParsingError.EOF?;
case 1..31:
return UNEXPECTED_CHARACTER?;
return JsonParsingError.UNEXPECTED_CHARACTER?;
case '"':
break LOOP;
case '\\':
@@ -350,9 +356,9 @@ fn JsonTokenType? lex_string(JsonContext* context)
switch (c)
{
case '\0':
return io::EOF?;
return JsonParsingError.EOF?;
case 1..31:
return UNEXPECTED_CHARACTER?;
return JsonParsingError.UNEXPECTED_CHARACTER?;
case '"':
case '\\':
case '/':
@@ -372,13 +378,13 @@ fn JsonTokenType? lex_string(JsonContext* context)
for (int i = 0; i < 4; i++)
{
c = read_next(context)!;
if (!c.is_xdigit()) return INVALID_ESCAPE_SEQUENCE?;
if (!c.is_xdigit()) return JsonParsingError.INVALID_ESCAPE_SEQUENCE?;
val = val << 4 + (c > '9' ? (c | 32) - 'a' + 10 : c - '0');
}
context.last_string.append_char32(val);
continue;
default:
return INVALID_ESCAPE_SEQUENCE?;
return JsonParsingError.INVALID_ESCAPE_SEQUENCE?;
}
context.last_string.append(c);
}

View File

@@ -1,94 +0,0 @@
module std::experimental::scheduler{Event};
import std::collections, std::thread, std::time;
struct DelayedSchedulerEvent @local
{
inline Event event;
Clock execution_time;
}
fn int DelayedSchedulerEvent.compare_to(self, DelayedSchedulerEvent other) @local
{
switch
{
case self.execution_time < other.execution_time: return -1;
case self.execution_time > other.execution_time: return 1;
default: return 0;
}
}
struct FrameScheduler
{
PriorityQueue{DelayedSchedulerEvent} delayed_events;
List{Event} events;
List{Event} pending_events;
bool pending;
Mutex mtx;
}
fn void FrameScheduler.init(&self)
{
self.events.init(mem);
self.pending_events.init(mem);
self.delayed_events.init(mem);
(void)self.mtx.init();
bool pending;
}
macro void FrameScheduler.@destroy(&self; @destruct(Event e))
{
foreach (e : self.events) @destruct(e);
foreach (e : self.pending_events) @destruct(e);
foreach (e : self.delayed_events.heap) @destruct(e.event);
self.events.free();
self.pending_events.free();
self.delayed_events.free();
(void)self.mtx.destroy();
}
fn void FrameScheduler.queue_delayed_event(&self, Event event, Duration delay)
{
self.mtx.@in_lock()
{
self.delayed_events.push({ event, clock::now().add_duration(delay)});
@atomic_store(self.pending, true);
};
}
fn bool FrameScheduler.has_delayed(&self)
{
self.mtx.@in_lock()
{
return @ok(self.delayed_events.first());
};
}
fn void FrameScheduler.queue_event(&self, Event event)
{
self.mtx.@in_lock()
{
self.pending_events.push(event);
@atomic_store(self.pending, true);
};
}
fn Event? FrameScheduler.pop_event(&self)
{
while (true)
{
if (try event = self.events.pop()) return event;
if (!@atomic_load(self.pending)) return NO_MORE_ELEMENT?;
self.mtx.@in_lock()
{
self.events.add_all(&self.pending_events);
self.pending_events.clear();
Clock c = clock::now();
while (try top = self.delayed_events.first())
{
if (top.execution_time > c) break;
self.events.push(self.delayed_events.pop()!!);
}
@atomic_store(self.pending, self.delayed_events.len() > 0);
if (!self.events.len()) return NO_MORE_ELEMENT?;
};
}
}

View File

@@ -40,7 +40,7 @@ fn uint Adler32.final(&self)
return (self.b << 16) | self.a;
}
fn uint hash(char[] data)
fn uint encode(char[] data)
{
uint a = 1;
uint b = 0;

View File

@@ -33,7 +33,7 @@ fn uint Crc32.final(&self)
return ~self.result;
}
fn uint hash(char[] data)
fn uint encode(char[] data)
{
uint result = ~(uint)(0);
foreach (char x : data)

View File

@@ -33,7 +33,7 @@ fn ulong Crc64.final(&self)
return self.result;
}
fn ulong hash(char[] data)
fn ulong encode(char[] data)
{
ulong result = (ulong)(0);
foreach (char x : data)

View File

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

View File

@@ -3,12 +3,12 @@
// a copy of which can be found in the LICENSE_STDLIB file.
module std::hash::fnv64a;
typedef Fnv64a = ulong;
distinct Fnv64a = ulong;
const FNV64A_START @private = 0xcbf29ce484222325;
const FNV64A_MUL @private = 0x00000100000001b3;
macro void update(h, char x) @private => *h = (*h ^ ($typeof(*h))x) * FNV64A_MUL;
macro void @update(&h, char x) @private => *h = (*h ^ ($typeof(*h))x) * FNV64A_MUL;
fn void Fnv64a.init(&self)
{
@@ -20,22 +20,22 @@ fn void Fnv64a.update(&self, char[] data)
Fnv64a h = *self;
foreach (char x : data)
{
update(&h, x);
@update(h, x);
}
*self = h;
}
macro void Fnv64a.update_char(&self, char c)
{
update(self, c);
@update(*self, c);
}
fn ulong hash(char[] data)
fn ulong encode(char[] data)
{
ulong h = FNV64A_START;
foreach (char x : data)
{
update(&h, x);
@update(h, x);
}
return h;
}

View File

@@ -1,4 +1,4 @@
module std::hash::hmac{HashAlg, HASH_BYTES, BLOCK_BYTES};
module std::hash::hmac(<HashAlg, HASH_BYTES, BLOCK_BYTES>);
import std::crypto;
struct Hmac
@@ -15,8 +15,8 @@ fn char[HASH_BYTES] hash(char[] key, char[] message)
}
<*
@require output.len > 0 : "Output must be greater than zero"
@require output.len < int.max / HASH_BYTES : "Output is too large"
@require output.len > 0 "Output must be greater than zero"
@require output.len < int.max / HASH_BYTES "Output is too large"
*>
fn void pbkdf2(char[] pw, char[] salt, uint iterations, char[] output)
{

View File

@@ -13,9 +13,9 @@ struct Md5
uint[16] block;
}
alias HmacMd5 = Hmac{Md5, HASH_BYTES, BLOCK_BYTES};
alias hmac = hmac::hash{Md5, HASH_BYTES, BLOCK_BYTES};
alias pbkdf2 = hmac::pbkdf2{Md5, HASH_BYTES, BLOCK_BYTES};
def HmacMd5 = Hmac(<Md5, HASH_BYTES, BLOCK_BYTES>);
def hmac = hmac::hash(<Md5, HASH_BYTES, BLOCK_BYTES>);
def pbkdf2 = hmac::pbkdf2(<Md5, HASH_BYTES, BLOCK_BYTES>);
fn char[HASH_BYTES] hash(char[] data)
{
@@ -106,9 +106,9 @@ macro @h(x, y, z) => (x ^ y) ^ z;
macro @h2(x, y, z) => x ^ (y ^ z);
macro @i(x, y, z) => y ^ (x | ~z);
macro @step(#f, a, b, c, d, ptr, n, t, s)
macro @step(#f, &a, b, c, d, ptr, n, t, s)
{
*a += #f(b, c, d) + @unaligned_load(*(uint *)&ptr[n * 4], 2) + t;
*a += #f(b, c, d) + *(uint *)&ptr[n * 4] + t;
*a = (*a << s) | ((*a & 0xffffffff) >> (32 - s));
*a += b;
}
@@ -133,76 +133,76 @@ fn char* body(Md5* ctx, void* data, usz size)
saved_d = d;
/* Round 1 */
@step(@f, &a, b, c, d, ptr, 0, 0xd76aa478, 7) ;
@step(@f, &d, a, b, c, ptr, 1, 0xe8c7b756, 12) ;
@step(@f, &c, d, a, b, ptr, 2, 0x242070db, 17) ;
@step(@f, &b, c, d, a, ptr, 3, 0xc1bdceee, 22) ;
@step(@f, &a, b, c, d, ptr, 4, 0xf57c0faf, 7) ;
@step(@f, &d, a, b, c, ptr, 5, 0x4787c62a, 12) ;
@step(@f, &c, d, a, b, ptr, 6, 0xa8304613, 17) ;
@step(@f, &b, c, d, a, ptr, 7, 0xfd469501, 22) ;
@step(@f, &a, b, c, d, ptr, 8, 0x698098d8, 7) ;
@step(@f, &d, a, b, c, ptr, 9, 0x8b44f7af, 12) ;
@step(@f, &c, d, a, b, ptr, 10, 0xffff5bb1, 17);
@step(@f, &b, c, d, a, ptr, 11, 0x895cd7be, 22);
@step(@f, &a, b, c, d, ptr, 12, 0x6b901122, 7) ;
@step(@f, &d, a, b, c, ptr, 13, 0xfd987193, 12);
@step(@f, &c, d, a, b, ptr, 14, 0xa679438e, 17);
@step(@f, &b, c, d, a, ptr, 15, 0x49b40821, 22);
@step(@f, a, b, c, d, ptr, 0, 0xd76aa478, 7) ;
@step(@f, d, a, b, c, ptr, 1, 0xe8c7b756, 12) ;
@step(@f, c, d, a, b, ptr, 2, 0x242070db, 17) ;
@step(@f, b, c, d, a, ptr, 3, 0xc1bdceee, 22) ;
@step(@f, a, b, c, d, ptr, 4, 0xf57c0faf, 7) ;
@step(@f, d, a, b, c, ptr, 5, 0x4787c62a, 12) ;
@step(@f, c, d, a, b, ptr, 6, 0xa8304613, 17) ;
@step(@f, b, c, d, a, ptr, 7, 0xfd469501, 22) ;
@step(@f, a, b, c, d, ptr, 8, 0x698098d8, 7) ;
@step(@f, d, a, b, c, ptr, 9, 0x8b44f7af, 12) ;
@step(@f, c, d, a, b, ptr, 10, 0xffff5bb1, 17);
@step(@f, b, c, d, a, ptr, 11, 0x895cd7be, 22);
@step(@f, a, b, c, d, ptr, 12, 0x6b901122, 7) ;
@step(@f, d, a, b, c, ptr, 13, 0xfd987193, 12);
@step(@f, c, d, a, b, ptr, 14, 0xa679438e, 17);
@step(@f, b, c, d, a, ptr, 15, 0x49b40821, 22);
/* Round 2 */
@step(@g, &a, b, c, d, ptr, 1, 0xf61e2562, 5) ;
@step(@g, &d, a, b, c, ptr, 6, 0xc040b340, 9) ;
@step(@g, &c, d, a, b, ptr, 11, 0x265e5a51, 14);
@step(@g, &b, c, d, a, ptr, 0, 0xe9b6c7aa, 20) ;
@step(@g, &a, b, c, d, ptr, 5, 0xd62f105d, 5) ;
@step(@g, &d, a, b, c, ptr, 10, 0x02441453, 9) ;
@step(@g, &c, d, a, b, ptr, 15, 0xd8a1e681, 14);
@step(@g, &b, c, d, a, ptr, 4, 0xe7d3fbc8, 20) ;
@step(@g, &a, b, c, d, ptr, 9, 0x21e1cde6, 5) ;
@step(@g, &d, a, b, c, ptr, 14, 0xc33707d6, 9) ;
@step(@g, &c, d, a, b, ptr, 3, 0xf4d50d87, 14) ;
@step(@g, &b, c, d, a, ptr, 8, 0x455a14ed, 20) ;
@step(@g, &a, b, c, d, ptr, 13, 0xa9e3e905, 5) ;
@step(@g, &d, a, b, c, ptr, 2, 0xfcefa3f8, 9) ;
@step(@g, &c, d, a, b, ptr, 7, 0x676f02d9, 14) ;
@step(@g, &b, c, d, a, ptr, 12, 0x8d2a4c8a, 20);
@step(@g, a, b, c, d, ptr, 1, 0xf61e2562, 5) ;
@step(@g, d, a, b, c, ptr, 6, 0xc040b340, 9) ;
@step(@g, c, d, a, b, ptr, 11, 0x265e5a51, 14);
@step(@g, b, c, d, a, ptr, 0, 0xe9b6c7aa, 20) ;
@step(@g, a, b, c, d, ptr, 5, 0xd62f105d, 5) ;
@step(@g, d, a, b, c, ptr, 10, 0x02441453, 9) ;
@step(@g, c, d, a, b, ptr, 15, 0xd8a1e681, 14);
@step(@g, b, c, d, a, ptr, 4, 0xe7d3fbc8, 20) ;
@step(@g, a, b, c, d, ptr, 9, 0x21e1cde6, 5) ;
@step(@g, d, a, b, c, ptr, 14, 0xc33707d6, 9) ;
@step(@g, c, d, a, b, ptr, 3, 0xf4d50d87, 14) ;
@step(@g, b, c, d, a, ptr, 8, 0x455a14ed, 20) ;
@step(@g, a, b, c, d, ptr, 13, 0xa9e3e905, 5) ;
@step(@g, d, a, b, c, ptr, 2, 0xfcefa3f8, 9) ;
@step(@g, c, d, a, b, ptr, 7, 0x676f02d9, 14) ;
@step(@g, b, c, d, a, ptr, 12, 0x8d2a4c8a, 20);
/* Round 3 */
@step(@h, &a, b, c, d, ptr, 5, 0xfffa3942, 4);
@step(@h2, &d, a, b, c, ptr, 8, 0x8771f681, 11);
@step(@h, &c, d, a, b, ptr, 11, 0x6d9d6122, 16);
@step(@h2, &b, c, d, a, ptr, 14, 0xfde5380c, 23);
@step(@h, &a, b, c, d, ptr, 1, 0xa4beea44, 4);
@step(@h2, &d, a, b, c, ptr, 4, 0x4bdecfa9, 11);
@step(@h, &c, d, a, b, ptr, 7, 0xf6bb4b60, 16);
@step(@h2, &b, c, d, a, ptr, 10, 0xbebfbc70, 23);
@step(@h, &a, b, c, d, ptr, 13, 0x289b7ec6, 4) ;
@step(@h2, &d, a, b, c, ptr, 0, 0xeaa127fa, 11) ;
@step(@h, &c, d, a, b, ptr, 3, 0xd4ef3085, 16) ;
@step(@h2, &b, c, d, a, ptr, 6, 0x04881d05, 23) ;
@step(@h, &a, b, c, d, ptr, 9, 0xd9d4d039, 4) ;
@step(@h2, &d, a, b, c, ptr, 12, 0xe6db99e5, 11) ;
@step(@h, &c, d, a, b, ptr, 15, 0x1fa27cf8, 16) ;
@step(@h2, &b, c, d, a, ptr, 2, 0xc4ac5665, 23) ;
@step(@h, a, b, c, d, ptr, 5, 0xfffa3942, 4);
@step(@h2, d, a, b, c, ptr, 8, 0x8771f681, 11);
@step(@h, c, d, a, b, ptr, 11, 0x6d9d6122, 16);
@step(@h2, b, c, d, a, ptr, 14, 0xfde5380c, 23);
@step(@h, a, b, c, d, ptr, 1, 0xa4beea44, 4);
@step(@h2, d, a, b, c, ptr, 4, 0x4bdecfa9, 11);
@step(@h, c, d, a, b, ptr, 7, 0xf6bb4b60, 16);
@step(@h2, b, c, d, a, ptr, 10, 0xbebfbc70, 23);
@step(@h, a, b, c, d, ptr, 13, 0x289b7ec6, 4) ;
@step(@h2, d, a, b, c, ptr, 0, 0xeaa127fa, 11) ;
@step(@h, c, d, a, b, ptr, 3, 0xd4ef3085, 16) ;
@step(@h2, b, c, d, a, ptr, 6, 0x04881d05, 23) ;
@step(@h, a, b, c, d, ptr, 9, 0xd9d4d039, 4) ;
@step(@h2, d, a, b, c, ptr, 12, 0xe6db99e5, 11) ;
@step(@h, c, d, a, b, ptr, 15, 0x1fa27cf8, 16) ;
@step(@h2, b, c, d, a, ptr, 2, 0xc4ac5665, 23) ;
/* Round 4 */
@step(@i, &a, b, c, d, ptr, 0, 0xf4292244, 6) ;
@step(@i, &d, a, b, c, ptr, 7, 0x432aff97, 10) ;
@step(@i, &c, d, a, b, ptr, 14, 0xab9423a7, 15) ;
@step(@i, &b, c, d, a, ptr, 5, 0xfc93a039, 21) ;
@step(@i, &a, b, c, d, ptr, 12, 0x655b59c3, 6) ;
@step(@i, &d, a, b, c, ptr, 3, 0x8f0ccc92, 10) ;
@step(@i, &c, d, a, b, ptr, 10, 0xffeff47d, 15) ;
@step(@i, &b, c, d, a, ptr, 1, 0x85845dd1, 21) ;
@step(@i, &a, b, c, d, ptr, 8, 0x6fa87e4f, 6) ;
@step(@i, &d, a, b, c, ptr, 15, 0xfe2ce6e0, 10) ;
@step(@i, &c, d, a, b, ptr, 6, 0xa3014314, 15) ;
@step(@i, &b, c, d, a, ptr, 13, 0x4e0811a1, 21) ;
@step(@i, &a, b, c, d, ptr, 4, 0xf7537e82, 6) ;
@step(@i, &d, a, b, c, ptr, 11, 0xbd3af235, 10) ;
@step(@i, &c, d, a, b, ptr, 2, 0x2ad7d2bb, 15) ;
@step(@i, &b, c, d, a, ptr, 9, 0xeb86d391, 21) ;
@step(@i, a, b, c, d, ptr, 0, 0xf4292244, 6) ;
@step(@i, d, a, b, c, ptr, 7, 0x432aff97, 10) ;
@step(@i, c, d, a, b, ptr, 14, 0xab9423a7, 15) ;
@step(@i, b, c, d, a, ptr, 5, 0xfc93a039, 21) ;
@step(@i, a, b, c, d, ptr, 12, 0x655b59c3, 6) ;
@step(@i, d, a, b, c, ptr, 3, 0x8f0ccc92, 10) ;
@step(@i, c, d, a, b, ptr, 10, 0xffeff47d, 15) ;
@step(@i, b, c, d, a, ptr, 1, 0x85845dd1, 21) ;
@step(@i, a, b, c, d, ptr, 8, 0x6fa87e4f, 6) ;
@step(@i, d, a, b, c, ptr, 15, 0xfe2ce6e0, 10) ;
@step(@i, c, d, a, b, ptr, 6, 0xa3014314, 15) ;
@step(@i, b, c, d, a, ptr, 13, 0x4e0811a1, 21) ;
@step(@i, a, b, c, d, ptr, 4, 0xf7537e82, 6) ;
@step(@i, d, a, b, c, ptr, 11, 0xbd3af235, 10) ;
@step(@i, c, d, a, b, ptr, 2, 0x2ad7d2bb, 15) ;
@step(@i, b, c, d, a, ptr, 9, 0xeb86d391, 21) ;
a += saved_a;
b += saved_b;

View File

@@ -18,9 +18,9 @@ struct Sha1
char[BLOCK_BYTES] buffer;
}
alias HmacSha1 = Hmac{Sha1, HASH_BYTES, BLOCK_BYTES};
alias hmac = hmac::hash{Sha1, HASH_BYTES, BLOCK_BYTES};
alias pbkdf2 = hmac::pbkdf2{Sha1, HASH_BYTES, BLOCK_BYTES};
def HmacSha1 = Hmac(<Sha1, HASH_BYTES, BLOCK_BYTES>);
def hmac = hmac::hash(<Sha1, HASH_BYTES, BLOCK_BYTES>);
def pbkdf2 = hmac::pbkdf2(<Sha1, HASH_BYTES, BLOCK_BYTES>);
fn char[HASH_BYTES] hash(char[] data)
{
@@ -78,10 +78,10 @@ fn char[HASH_BYTES] Sha1.final(&self)
{
finalcount[i] = (char)((self.count[(i >= 4 ? 0 : 1)] >> ((3 - (i & 3)) * 8)) & 0xFF);
}
self.update((char[]){ 0o200 });
self.update(char[] { 0o200 });
while ((self.count[0] & 504) != 448)
{
self.update((char[]){ 0 });
self.update(char[] { 0 });
}
self.update(&finalcount);
@@ -103,13 +103,13 @@ union Long16 @local
uint[16] l;
}
macro blk(Long16* block, i) @local
macro @blk(&block, i) @local
{
return (block.l[i & 15] = (block.l[(i + 13) & 15] ^ block.l[(i + 8) & 15]
^ block.l[(i + 2) & 15] ^ block.l[i & 15]).rotl(1));
}
macro blk0(Long16* block, i) @local
macro @blk0(&block, i) @local
{
$if env::BIG_ENDIAN:
return block.l[i];
@@ -119,38 +119,38 @@ macro blk0(Long16* block, i) @local
$endif
}
macro r0(Long16* block, uint v, uint* wref, uint x, uint y, uint* z, uint i) @local
macro @r0(&block, v, &wref, x, y, &z, i) @local
{
var w = *wref;
*z += ((w & (x ^ y)) ^ y) + blk0(block, i) + 0x5A827999 + v.rotl(5);
*z += ((w & (x ^ y)) ^ y) + @blk0(*block, i) + 0x5A827999 + v.rotl(5);
*wref = w.rotl(30);
}
macro r1(Long16* block, uint v, uint* wref, uint x, uint y, uint* z, uint i) @local
macro @r1(&block, v, &wref, x, y, &z, i) @local
{
var w = *wref;
*z += ((w & (x ^ y)) ^ y) + blk(block, i) + 0x5A827999 + v.rotl(5);
*z += ((w & (x ^ y)) ^ y) + @blk(*block, i) + 0x5A827999 + v.rotl(5);
*wref = w.rotl(30);
}
macro r2(Long16* block, uint v, uint* wref, uint x, uint y, uint* z, uint i) @local
macro @r2(&block, v, &wref, x, y, &z, i) @local
{
var w = *wref;
*z += (w ^ x ^ y) + blk(block, i) + 0x6ED9EBA1 + v.rotl(5);
*z += (w ^ x ^ y) + @blk(*block, i) + 0x6ED9EBA1 + v.rotl(5);
*wref = w.rotl(30);
}
macro r3(Long16* block, uint v, uint* wref, uint x, uint y, uint* z, uint i) @local
macro @r3(&block, v, &wref, x, y, &z, i) @local
{
var w = *wref;
*z += (((w | x) & y) | (w & x)) + blk(block, i) + 0x8F1BBCDC + v.rotl(5);
*z += (((w | x) & y) | (w & x)) + @blk(*block, i) + 0x8F1BBCDC + v.rotl(5);
*wref = w.rotl(30);
}
macro r4(Long16* block, uint v, uint* wref, uint x, uint y, uint* z, uint i) @local
macro @r4(&block, v, &wref, x, y, &z, i) @local
{
var w = *wref;
*z += (w ^ x ^ y) + blk(block, i) + 0xCA62C1D6 + v.rotl(5);
*z += (w ^ x ^ y) + @blk(*block, i) + 0xCA62C1D6 + v.rotl(5);
*wref = w.rotl(30);
}
@@ -158,100 +158,100 @@ macro r4(Long16* block, uint v, uint* wref, uint x, uint y, uint* z, uint i) @lo
@param [&inout] state
@param [&in] buffer
*>
fn void sha1_transform(uint[5]* state, char* buffer) @local
fn void sha1_transform(uint* state, char* buffer) @local
{
Long16 block;
block.c[..] = buffer[:64];
uint a = (*state)[0];
uint b = (*state)[1];
uint c = (*state)[2];
uint d = (*state)[3];
uint e = (*state)[4];
r0(&block, a, &b, c, d, &e, 0);
r0(&block, e, &a, b, c, &d, 1);
r0(&block, d, &e, a, b, &c, 2);
r0(&block, c, &d, e, a, &b, 3);
r0(&block, b, &c, d, e, &a, 4);
r0(&block, a, &b, c, d, &e, 5);
r0(&block, e, &a, b, c, &d, 6);
r0(&block, d, &e, a, b, &c, 7);
r0(&block, c, &d, e, a, &b, 8);
r0(&block, b, &c, d, e, &a, 9);
r0(&block, a, &b, c, d, &e, 10);
r0(&block, e, &a, b, c, &d, 11);
r0(&block, d, &e, a, b, &c, 12);
r0(&block, c, &d, e, a, &b, 13);
r0(&block, b, &c, d, e, &a, 14);
r0(&block, a, &b, c, d, &e, 15);
r1(&block, e, &a, b, c, &d, 16);
r1(&block, d, &e, a, b, &c, 17);
r1(&block, c, &d, e, a, &b, 18);
r1(&block, b, &c, d, e, &a, 19);
r2(&block, a, &b, c, d, &e, 20);
r2(&block, e, &a, b, c, &d, 21);
r2(&block, d, &e, a, b, &c, 22);
r2(&block, c, &d, e, a, &b, 23);
r2(&block, b, &c, d, e, &a, 24);
r2(&block, a, &b, c, d, &e, 25);
r2(&block, e, &a, b, c, &d, 26);
r2(&block, d, &e, a, b, &c, 27);
r2(&block, c, &d, e, a, &b, 28);
r2(&block, b, &c, d, e, &a, 29);
r2(&block, a, &b, c, d, &e, 30);
r2(&block, e, &a, b, c, &d, 31);
r2(&block, d, &e, a, b, &c, 32);
r2(&block, c, &d, e, a, &b, 33);
r2(&block, b, &c, d, e, &a, 34);
r2(&block, a, &b, c, d, &e, 35);
r2(&block, e, &a, b, c, &d, 36);
r2(&block, d, &e, a, b, &c, 37);
r2(&block, c, &d, e, a, &b, 38);
r2(&block, b, &c, d, e, &a, 39);
r3(&block, a, &b, c, d, &e, 40);
r3(&block, e, &a, b, c, &d, 41);
r3(&block, d, &e, a, b, &c, 42);
r3(&block, c, &d, e, a, &b, 43);
r3(&block, b, &c, d, e, &a, 44);
r3(&block, a, &b, c, d, &e, 45);
r3(&block, e, &a, b, c, &d, 46);
r3(&block, d, &e, a, b, &c, 47);
r3(&block, c, &d, e, a, &b, 48);
r3(&block, b, &c, d, e, &a, 49);
r3(&block, a, &b, c, d, &e, 50);
r3(&block, e, &a, b, c, &d, 51);
r3(&block, d, &e, a, b, &c, 52);
r3(&block, c, &d, e, a, &b, 53);
r3(&block, b, &c, d, e, &a, 54);
r3(&block, a, &b, c, d, &e, 55);
r3(&block, e, &a, b, c, &d, 56);
r3(&block, d, &e, a, b, &c, 57);
r3(&block, c, &d, e, a, &b, 58);
r3(&block, b, &c, d, e, &a, 59);
r4(&block, a, &b, c, d, &e, 60);
r4(&block, e, &a, b, c, &d, 61);
r4(&block, d, &e, a, b, &c, 62);
r4(&block, c, &d, e, a, &b, 63);
r4(&block, b, &c, d, e, &a, 64);
r4(&block, a, &b, c, d, &e, 65);
r4(&block, e, &a, b, c, &d, 66);
r4(&block, d, &e, a, b, &c, 67);
r4(&block, c, &d, e, a, &b, 68);
r4(&block, b, &c, d, e, &a, 69);
r4(&block, a, &b, c, d, &e, 70);
r4(&block, e, &a, b, c, &d, 71);
r4(&block, d, &e, a, b, &c, 72);
r4(&block, c, &d, e, a, &b, 73);
r4(&block, b, &c, d, e, &a, 74);
r4(&block, a, &b, c, d, &e, 75);
r4(&block, e, &a, b, c, &d, 76);
r4(&block, d, &e, a, b, &c, 77);
r4(&block, c, &d, e, a, &b, 78);
r4(&block, b, &c, d, e, &a, 79);
(*state)[0] += a;
(*state)[1] += b;
(*state)[2] += c;
(*state)[3] += d;
(*state)[4] += e;
uint a = state[0];
uint b = state[1];
uint c = state[2];
uint d = state[3];
uint e = state[4];
@r0(block, a, b, c, d, e, 0);
@r0(block, e, a, b, c, d, 1);
@r0(block, d, e, a, b, c, 2);
@r0(block, c, d, e, a, b, 3);
@r0(block, b, c, d, e, a, 4);
@r0(block, a, b, c, d, e, 5);
@r0(block, e, a, b, c, d, 6);
@r0(block, d, e, a, b, c, 7);
@r0(block, c, d, e, a, b, 8);
@r0(block, b, c, d, e, a, 9);
@r0(block, a, b, c, d, e, 10);
@r0(block, e, a, b, c, d, 11);
@r0(block, d, e, a, b, c, 12);
@r0(block, c, d, e, a, b, 13);
@r0(block, b, c, d, e, a, 14);
@r0(block, a, b, c, d, e, 15);
@r1(block, e, a, b, c, d, 16);
@r1(block, d, e, a, b, c, 17);
@r1(block, c, d, e, a, b, 18);
@r1(block, b, c, d, e, a, 19);
@r2(block, a, b, c, d, e, 20);
@r2(block, e, a, b, c, d, 21);
@r2(block, d, e, a, b, c, 22);
@r2(block, c, d, e, a, b, 23);
@r2(block, b, c, d, e, a, 24);
@r2(block, a, b, c, d, e, 25);
@r2(block, e, a, b, c, d, 26);
@r2(block, d, e, a, b, c, 27);
@r2(block, c, d, e, a, b, 28);
@r2(block, b, c, d, e, a, 29);
@r2(block, a, b, c, d, e, 30);
@r2(block, e, a, b, c, d, 31);
@r2(block, d, e, a, b, c, 32);
@r2(block, c, d, e, a, b, 33);
@r2(block, b, c, d, e, a, 34);
@r2(block, a, b, c, d, e, 35);
@r2(block, e, a, b, c, d, 36);
@r2(block, d, e, a, b, c, 37);
@r2(block, c, d, e, a, b, 38);
@r2(block, b, c, d, e, a, 39);
@r3(block, a, b, c, d, e, 40);
@r3(block, e, a, b, c, d, 41);
@r3(block, d, e, a, b, c, 42);
@r3(block, c, d, e, a, b, 43);
@r3(block, b, c, d, e, a, 44);
@r3(block, a, b, c, d, e, 45);
@r3(block, e, a, b, c, d, 46);
@r3(block, d, e, a, b, c, 47);
@r3(block, c, d, e, a, b, 48);
@r3(block, b, c, d, e, a, 49);
@r3(block, a, b, c, d, e, 50);
@r3(block, e, a, b, c, d, 51);
@r3(block, d, e, a, b, c, 52);
@r3(block, c, d, e, a, b, 53);
@r3(block, b, c, d, e, a, 54);
@r3(block, a, b, c, d, e, 55);
@r3(block, e, a, b, c, d, 56);
@r3(block, d, e, a, b, c, 57);
@r3(block, c, d, e, a, b, 58);
@r3(block, b, c, d, e, a, 59);
@r4(block, a, b, c, d, e, 60);
@r4(block, e, a, b, c, d, 61);
@r4(block, d, e, a, b, c, 62);
@r4(block, c, d, e, a, b, 63);
@r4(block, b, c, d, e, a, 64);
@r4(block, a, b, c, d, e, 65);
@r4(block, e, a, b, c, d, 66);
@r4(block, d, e, a, b, c, 67);
@r4(block, c, d, e, a, b, 68);
@r4(block, b, c, d, e, a, 69);
@r4(block, a, b, c, d, e, 70);
@r4(block, e, a, b, c, d, 71);
@r4(block, d, e, a, b, c, 72);
@r4(block, c, d, e, a, b, 73);
@r4(block, b, c, d, e, a, 74);
@r4(block, a, b, c, d, e, 75);
@r4(block, e, a, b, c, d, 76);
@r4(block, d, e, a, b, c, 77);
@r4(block, c, d, e, a, b, 78);
@r4(block, b, c, d, e, a, 79);
state[0] += a;
state[1] += b;
state[2] += c;
state[3] += d;
state[4] += e;
a = b = c = d = e = 0;
block = {};
buffer[:64] = 0;
}

View File

@@ -34,9 +34,9 @@ struct Sha256
char[BLOCK_SIZE] buffer;
}
alias HmacSha256 = Hmac{Sha256, HASH_SIZE, BLOCK_SIZE};
alias hmac = hmac::hash{Sha256, HASH_SIZE, BLOCK_SIZE};
alias pbkdf2 = hmac::pbkdf2{Sha256, HASH_SIZE, BLOCK_SIZE};
def HmacSha256 = Hmac(<Sha256, HASH_SIZE, BLOCK_SIZE>);
def hmac = hmac::hash(<Sha256, HASH_SIZE, BLOCK_SIZE>);
def pbkdf2 = hmac::pbkdf2(<Sha256, HASH_SIZE, BLOCK_SIZE>);
fn char[HASH_SIZE] hash(char[] data)
{

View File

@@ -21,7 +21,7 @@ fn void BitReader.clear(&self) @inline
@require nbits <= 8
@require self.len + nbits <= uint.sizeof * 8
*>
fn char? BitReader.read_bits(&self, uint nbits)
fn char! BitReader.read_bits(&self, uint nbits)
{
uint bits = self.bits;
if (self.len < nbits)
@@ -54,7 +54,7 @@ fn void BitWriter.init(&self, OutStream byte_writer)
*self = { .writer = byte_writer };
}
fn void? BitWriter.flush(&self)
fn void! BitWriter.flush(&self)
{
if (self.len == 0) return;
@@ -70,7 +70,7 @@ fn void? BitWriter.flush(&self)
<*
@require nbits <= 32
*>
fn void? BitWriter.write_bits(&self, uint bits, uint nbits)
fn void! BitWriter.write_bits(&self, uint bits, uint nbits)
{
if (nbits == 0) return;
while (self.len + nbits > WRITER_BITS)

View File

@@ -9,21 +9,16 @@ struct File (InStream, OutStream)
module std::io::file;
import libc, std::io::path, std::io::os;
fn File? open(String filename, String mode)
fn File! open(String filename, String mode)
{
return from_handle(os::native_fopen(filename, mode));
}
fn File? open_path(Path path, String mode)
fn File! open_path(Path path, String mode)
{
return from_handle(os::native_fopen(path.str_view(), mode));
}
fn bool exists(String file) => @pool()
{
return os::native_file_or_dir_exists(file);
}
fn File from_handle(CFile file)
{
return { .file = file };
@@ -34,26 +29,18 @@ fn bool is_file(String path)
return os::native_is_file(path);
}
fn bool is_dir(String path)
{
return os::native_is_dir(path);
}
fn usz? get_size(String path)
fn usz! get_size(String path)
{
return os::native_file_size(path);
}
fn void? delete(String filename)
{
return os::native_remove(filename) @inline;
}
fn void! delete(String filename) => os::native_remove(filename) @inline;
<*
@require self.file != null
*>
fn void? File.reopen(&self, String filename, String mode)
fn void! File.reopen(&self, String filename, String mode)
{
self.file = os::native_freopen(self.file, filename, mode)!;
}
@@ -61,7 +48,7 @@ fn void? File.reopen(&self, String filename, String mode)
<*
@require self.file != null
*>
fn usz? File.seek(&self, isz offset, Seek seek_mode = Seek.SET) @dynamic
fn usz! File.seek(&self, isz offset, Seek seek_mode = Seek.SET) @dynamic
{
os::native_fseek(self.file, offset, seek_mode)!;
return os::native_ftell(self.file);
@@ -73,11 +60,11 @@ Implement later
<*
@require self.file == null
*>
fn void? File.memopen(File* file, char[] data, String mode)
fn void! File.memopen(File* file, char[] data, String mode)
{
@pool()
{
file.file = libc::memopen(data.ptr, data.len, mode.to_temp_zstr(), file.file);
file.file = libc::memopen(data.ptr, data.len, mode.zstr_tcopy(), file.file);
// TODO errors
};
}
@@ -87,7 +74,7 @@ fn void? File.memopen(File* file, char[] data, String mode)
<*
@require self.file != null
*>
fn void? File.write_byte(&self, char c) @dynamic
fn void! File.write_byte(&self, char c) @dynamic
{
return os::native_fputc(c, self.file);
}
@@ -95,15 +82,15 @@ fn void? File.write_byte(&self, char c) @dynamic
<*
@param [&inout] self
*>
fn void? File.close(&self) @inline @dynamic
fn void! File.close(&self) @inline @dynamic
{
if (self.file && libc::fclose(self.file))
{
switch (libc::errno())
{
case errno::ECONNRESET:
case errno::EBADF: return io::FILE_NOT_VALID?;
case errno::EINTR: return io::INTERRUPTED?;
case errno::EBADF: return IoError.FILE_NOT_VALID?;
case errno::EINTR: return IoError.INTERRUPTED?;
case errno::EDQUOT:
case errno::EFAULT:
case errno::EAGAIN:
@@ -111,15 +98,15 @@ fn void? File.close(&self) @inline @dynamic
case errno::ENETDOWN:
case errno::ENETUNREACH:
case errno::ENOSPC:
case errno::EIO: return io::INCOMPLETE_WRITE?;
default: return io::UNKNOWN_ERROR?;
case errno::EIO: return IoError.INCOMPLETE_WRITE?;
default: return IoError.UNKNOWN_ERROR?;
}
}
self.file = null;
}
<*
@require self.file != null
@require self.file
*>
fn bool File.eof(&self) @inline
{
@@ -129,50 +116,41 @@ fn bool File.eof(&self) @inline
<*
@param [in] buffer
*>
fn usz? File.read(&self, char[] buffer) @dynamic
fn usz! File.read(&self, char[] buffer) @dynamic
{
return os::native_fread(self.file, buffer);
}
<*
@param [out] buffer
@require self.file != null : `File must be initialized`
@require self.file `File must be initialized`
*>
fn usz? File.write(&self, char[] buffer) @dynamic
fn usz! File.write(&self, char[] buffer) @dynamic
{
return os::native_fwrite(self.file, buffer);
}
fn Fd File.fd(self) @if(env::LIBC)
{
return libc::fileno(self.file);
}
fn bool File.isatty(self) @if(env::LIBC)
{
return libc::isatty(self.fd()) > 0;
}
fn char? File.read_byte(&self) @dynamic
fn char! File.read_byte(&self) @dynamic
{
int c = libc::fgetc(self.file);
if (c == -1) return io::EOF?;
if (c == -1) return IoError.EOF?;
return (char)c;
}
<*
Load up to buffer.len characters. Returns io::OVERFLOW if the file is longer
Load up to buffer.len characters. Returns IoError.OVERFLOW if the file is longer
than the buffer.
@param filename : "The path to the file to read"
@param [in] buffer : "The buffer to read to"
@param filename "The path to the file to read"
@param [in] buffer "The buffer to read to"
*>
fn char[]? load_buffer(String filename, char[] buffer)
fn char[]! load_buffer(String filename, char[] buffer)
{
File file = open(filename, "rb")!;
defer (void)file.close();
usz len = file.seek(0, END)!;
if (len > buffer.len) return io::OVERFLOW?;
if (len > buffer.len) return IoError.OVERFLOW?;
file.seek(0, SET)!;
usz read = 0;
while (read < len)
@@ -183,7 +161,7 @@ fn char[]? load_buffer(String filename, char[] buffer)
}
fn char[]? load(Allocator allocator, String filename)
fn char[]! load_new(String filename, Allocator allocator = allocator::heap())
{
File file = open(filename, "rb")!;
defer (void)file.close();
@@ -199,13 +177,12 @@ fn char[]? load(Allocator allocator, String filename)
return data[:len];
}
fn char[]? load_path(Allocator allocator, Path path) => load(allocator, path.str_view());
fn char[]! load_temp(String filename)
{
return load_new(filename, allocator::temp());
}
fn char[]? load_temp(String filename) => load(tmem, filename);
fn char[]? load_path_temp(Path path) => load_temp(path.str_view());
fn void? save(String filename, char[] data)
fn void! save(String filename, char[] data)
{
File file = open(filename, "wb")!;
defer (void)file.close();
@@ -217,9 +194,9 @@ fn void? save(String filename, char[] data)
}
<*
@require self.file != null : `File must be initialized`
@require self.file `File must be initialized`
*>
fn void? File.flush(&self) @dynamic
fn void! File.flush(&self) @dynamic
{
libc::fflush(self.file);
}

View File

@@ -6,34 +6,42 @@ const int PRINTF_NTOA_BUFFER_SIZE = 256;
interface Printable
{
fn String to_constant_string() @optional;
fn usz? to_format(Formatter* formatter) @optional;
fn String to_string(Allocator allocator) @optional;
fn String to_new_string(Allocator allocator) @optional @deprecated("Use to_string");
fn usz! to_format(Formatter* formatter) @optional;
}
faultdef BUFFER_EXCEEDED, INTERNAL_BUFFER_EXCEEDED, INVALID_FORMAT,
NOT_ENOUGH_ARGUMENTS, INVALID_ARGUMENT;
fault PrintFault
{
BUFFER_EXCEEDED,
INTERNAL_BUFFER_EXCEEDED,
INVALID_FORMAT,
NOT_ENOUGH_ARGUMENTS,
INVALID_ARGUMENT,
}
alias OutputFn = fn void?(void* buffer, char c);
alias FloatType = double;
def OutputFn = fn void!(void* buffer, char c);
def FloatType = double;
macro bool is_struct_with_default_print($Type)
{
return $Type.kindof == STRUCT
&&& !$defined($Type.to_format)
&&& !$defined($Type.to_constant_string);
&&& !$defined($Type.to_new_string)
&&& !$defined($Type.to_string);
}
<*
Introspect a struct and print it to a formatter
@require @typekind(value) == STRUCT : `This macro is only valid on macros`
@require @typekind(value) == STRUCT `This macro is only valid on macros`
*>
macro usz? struct_to_format(value, Formatter* f, bool $force_dump)
macro usz! struct_to_format(value, Formatter* f, bool $force_dump)
{
var $Type = $typeof(value);
usz total = f.print("{ ")!;
$foreach $i, $member : $Type.membersof:
$foreach ($i, $member : $Type.membersof)
$if $i > 0:
total += f.print(", ")!;
$endif
@@ -50,12 +58,12 @@ macro usz? struct_to_format(value, Formatter* f, bool $force_dump)
return total + f.print(" }");
}
fn usz? ReflectedParam.to_format(&self, Formatter* f) @dynamic
fn usz! ReflectedParam.to_format(&self, Formatter* f) @dynamic
{
return f.printf("[Parameter '%s']", self.name);
}
fn usz? Formatter.printf(&self, String format, args...)
fn usz! Formatter.printf(&self, String format, args...)
{
return self.vprintf(format, args) @inline;
}
@@ -70,7 +78,7 @@ struct Formatter
uint width;
uint prec;
usz idx;
fault first_fault;
anyfault first_fault;
}
}
@@ -90,7 +98,7 @@ fn void Formatter.init(&self, OutputFn out_fn, void* data = null)
*self = { .data = data, .out_fn = out_fn};
}
fn usz? Formatter.out(&self, char c) @private
fn usz! Formatter.out(&self, char c) @private
{
if (catch err = self.out_fn(self.data, c))
{
@@ -101,7 +109,7 @@ fn usz? Formatter.out(&self, char c) @private
return 1;
}
fn usz? Formatter.print_with_function(&self, Printable arg)
fn usz! Formatter.print_with_function(&self, Printable arg)
{
if (&arg.to_format)
{
@@ -117,7 +125,7 @@ fn usz? Formatter.print_with_function(&self, Printable arg)
if (!arg) return self.out_substr("(null)");
return arg.to_format(self);
}
if (&arg.to_constant_string)
if (&arg.to_string)
{
PrintFlags old = self.flags;
uint old_width = self.width;
@@ -129,16 +137,16 @@ fn usz? Formatter.print_with_function(&self, Printable arg)
self.prec = old_prec;
}
if (!arg) return self.out_substr("(null)");
return self.out_substr(arg.to_constant_string());
@stack_mem(1024; Allocator mem)
{
return self.out_substr(arg.to_string(mem));
};
}
return NOT_FOUND?;
return SearchResult.MISSING?;
}
fn usz? Formatter.out_unknown(&self, String category, any arg) @private
{
return self.out_substr("[") + self.out_substr(category) + self.out_substr(" type:") + self.ntoa((iptr)arg.type, false, 16) + self.out_substr(", addr:") + self.ntoa((iptr)arg.ptr, false, 16) + self.out_substr("]");
}
fn usz? Formatter.out_str(&self, any arg) @private
fn usz! Formatter.out_str(&self, any arg) @private
{
switch (arg.type.kindof)
{
@@ -146,8 +154,9 @@ fn usz? Formatter.out_str(&self, any arg) @private
return self.out_substr("typeid");
case VOID:
return self.out_substr("void");
case ANYFAULT:
case FAULT:
return self.out_substr((*(fault*)arg.ptr).nameof);
return self.out_substr((*(anyfault*)arg.ptr).nameof);
case INTERFACE:
case ANY:
return self.out_str(*(any*)arg);
@@ -180,23 +189,23 @@ fn usz? Formatter.out_str(&self, any arg) @private
return self.out_substr(*(bool*)arg.ptr ? "true" : "false");
default:
}
usz? n = self.print_with_function((Printable)arg);
usz! n = self.print_with_function((Printable)arg);
if (try n) return n;
if (@catch(n) != NOT_FOUND) n!;
if (@catch(n) != SearchResult.MISSING) n!;
switch (arg.type.kindof)
{
case ENUM:
usz i = types::any_to_enum_ordinal(arg, usz)!!;
usz i = types::any_to_int(arg, usz)!!;
assert(i < arg.type.names.len, "Illegal enum value found, numerical value was %d.", i);
return self.out_substr(arg.type.names[i]);
case STRUCT:
return self.out_unknown("struct", arg);
return self.out_substr("<struct>");
case UNION:
return self.out_unknown("union", arg);
return self.out_substr("<union>");
case BITSTRUCT:
return self.out_unknown("bitstruct", arg);
return self.out_substr("<bitstruct>");
case FUNC:
return self.out_unknown("function", arg);
return self.out_substr("<function>");
case DISTINCT:
if (arg.type == String.typeid)
{
@@ -219,7 +228,7 @@ fn usz? Formatter.out_str(&self, any arg) @private
any deref = any_make(*pointer, inner);
n = self.print_with_function((Printable)deref);
if (try n) return n;
if (@catch(n) != NOT_FOUND) n!;
if (@catch(n) != SearchResult.MISSING) n!;
}
PrintFlags flags = self.flags;
uint width = self.width;
@@ -229,7 +238,8 @@ fn usz? Formatter.out_str(&self, any arg) @private
self.width = width;
}
self.width = 0;
return self.out_substr("0x")! + self.ntoa_any(arg, 16);
self.out_substr("0x")!;
return self.ntoa_any(arg, 16);
case ARRAY:
// this is SomeType[*] so grab the "SomeType"
PrintFlags flags = self.flags;
@@ -318,36 +328,33 @@ fn usz? Formatter.out_str(&self, any arg) @private
fn void? out_null_fn(void* data @unused, char c @unused) @private
fn void! out_null_fn(void* data @unused, char c @unused) @private
{
}
macro usz? @report_fault(Formatter* f, $fault)
macro usz! @report_fault(Formatter* f, $fault)
{
(void)f.out_substr($fault);
return INVALID_FORMAT?;
return PrintFault.INVALID_FORMAT?;
}
macro usz? @wrap_bad(Formatter* f, #action)
macro usz! @wrap_bad(Formatter* f, #action)
{
usz? len = #action;
usz! len = #action;
if (catch err = len)
{
switch (err)
{
case BUFFER_EXCEEDED:
case INTERNAL_BUFFER_EXCEEDED:
return f.first_err(err)?;
default:
err = f.first_err(INVALID_ARGUMENT);
f.out_substr("<INVALID>")!;
return err?;
}
case PrintFault.BUFFER_EXCEEDED:
case PrintFault.INTERNAL_BUFFER_EXCEEDED:
return f.first_err(err)?;
default:
err = f.first_err(PrintFault.INVALID_ARGUMENT);
f.out_substr("<INVALID>")!;
return err?;
}
return len;
}
fn usz? Formatter.vprintf(&self, String format, any[] anys)
fn usz! Formatter.vprintf(&self, String format, any[] anys)
{
self.first_fault = {};
if (!self.out_fn)
@@ -393,7 +400,7 @@ fn usz? Formatter.vprintf(&self, String format, any[] anys)
c = format[i];
}
// evaluate width field
int? w = printf_parse_format_field(anys.ptr, anys.len, &variant_index, format.ptr, format.len, &i);
int! w = printf_parse_format_field(anys.ptr, anys.len, &variant_index, format.ptr, format.len, &i);
if (catch w) return @report_fault(self, "%ERR");
c = format[i];
if (w < 0)
@@ -408,7 +415,7 @@ fn usz? Formatter.vprintf(&self, String format, any[] anys)
{
self.flags.precision = true;
if (++i >= format_len) return @report_fault(self, "<BAD FORMAT>");
int? prec = printf_parse_format_field(anys.ptr, anys.len, &variant_index, format.ptr, format.len, &i);
int! prec = printf_parse_format_field(anys.ptr, anys.len, &variant_index, format.ptr, format.len, &i);
if (catch prec) return @report_fault(self, "<BAD FORMAT>");
self.prec = prec < 0 ? 0 : prec;
c = format[i];
@@ -418,7 +425,7 @@ fn usz? Formatter.vprintf(&self, String format, any[] anys)
uint base = 0;
if (variant_index >= anys.len)
{
self.first_err(NOT_ENOUGH_ARGUMENTS);
self.first_err(PrintFault.NOT_ENOUGH_ARGUMENTS);
total_len += self.out_substr("<MISSING>")!;
continue;
}
@@ -470,38 +477,6 @@ fn usz? Formatter.vprintf(&self, String format, any[] anys)
case 'c':
total_len += self.out_char(current)!;
continue;
case 'H':
self.flags.uppercase = true;
nextcase;
case 'h':
char[] out @noinit;
switch (current.type)
{
case char[]:
case ichar[]:
out = *(char[]*)current;
default:
if (current.type.kindof == ARRAY && (current.type.inner == char.typeid || current.type.inner == ichar.typeid))
{
out = ((char*)current.ptr)[:current.type.sizeof];
break;
}
total_len += self.out_substr("<INVALID>")!;
continue;
}
if (self.flags.left)
{
usz len = print_hex_chars(self, out, self.flags.uppercase)!;
total_len += len;
total_len += self.pad(' ', self.width, len)!;
continue;
}
if (self.width)
{
total_len += self.pad(' ', self.width, out.len * 2)!;
}
total_len += print_hex_chars(self, out, self.flags.uppercase)!;
continue;
case 's':
if (self.flags.left)
{
@@ -510,14 +485,11 @@ fn usz? Formatter.vprintf(&self, String format, any[] anys)
total_len += self.pad(' ', self.width, len)!;
continue;
}
if (self.width)
{
OutputFn out_fn = self.out_fn;
self.out_fn = (OutputFn)&out_null_fn;
usz len = self.out_str(current)!;
self.out_fn = out_fn;
total_len += self.pad(' ', self.width, len)!;
}
OutputFn out_fn = self.out_fn;
self.out_fn = (OutputFn)&out_null_fn;
usz len = self.out_str(current)!;
self.out_fn = out_fn;
total_len += self.pad(' ', self.width, len)!;
total_len += self.out_str(current)!;
continue;
case 'p':
@@ -525,7 +497,7 @@ fn usz? Formatter.vprintf(&self, String format, any[] anys)
self.flags.hash = true;
base = 16;
default:
self.first_err(INVALID_FORMAT);
self.first_err(PrintFault.INVALID_FORMAT);
total_len += self.out_substr("<BAD FORMAT>")!;
continue;
}
@@ -549,7 +521,7 @@ fn usz? Formatter.vprintf(&self, String format, any[] anys)
}
fn usz? Formatter.print(&self, String str)
fn usz! Formatter.print(&self, String str)
{
if (!self.out_fn)
{

View File

@@ -4,38 +4,25 @@ import std::math;
const char[16] XDIGITS_H = "0123456789ABCDEF";
const char[16] XDIGITS_L = "0123456789abcdef";
faultdef BAD_FORMAT;
fn usz? print_hex_chars(Formatter* f, char[] out, bool uppercase) @inline
fault FormattingFault
{
char past_10 = (uppercase ? 'A' : 'a') - 10;
usz len = 0;
foreach (c : out)
{
char digit = c >> 4;
f.out(digit + (digit < 10 ? '0' : past_10))!;
len++;
digit = c & 0xf;
f.out(digit + (digit < 10 ? '0' : past_10))!;
len++;
}
return len;
BAD_FORMAT
}
macro Formatter.first_err(&self, fault f)
macro Formatter.first_err(&self, anyfault f)
{
if (self.first_fault) return self.first_fault;
self.first_fault = f;
return f;
}
fn usz? Formatter.adjust(&self, usz len) @local
fn usz! Formatter.adjust(&self, usz len) @local
{
if (!self.flags.left) return 0;
return self.pad(' ', self.width, len);
}
fn uint128? int_from_any(any arg, bool *is_neg) @private
fn uint128! int_from_any(any arg, bool *is_neg) @private
{
switch (arg.type.kindof)
{
@@ -43,52 +30,53 @@ fn uint128? int_from_any(any arg, bool *is_neg) @private
*is_neg = false;
return (uint128)(uptr)*(void**)arg.ptr;
case TypeKind.DISTINCT:
case TypeKind.ENUM:
return int_from_any(arg.as_inner(), is_neg);
default:
break;
}
*is_neg = false;
switch (arg.type)
switch (arg)
{
case bool:
return (uint128)*(bool*)arg;
return (uint128)*arg;
case ichar:
int val = *(ichar*)arg;
int val = *arg;
return (*is_neg = val < 0) ? (~(uint128)val) + 1 : (uint128)val;
case short:
int val = *(short*)arg;
int val = *arg;
return (*is_neg = val < 0) ? (~(uint128)val) + 1 : (uint128)val;
case int:
int val = *(int*)arg;
int val = *arg;
return (*is_neg = val < 0) ? (~(uint128)val) + 1 : (uint128)val;
case long:
long val = *(long*)arg;
long val = *arg;
return (*is_neg = val < 0) ? (~(uint128)val) + 1 : (uint128)val;
case int128:
int128 val = *(int128*)arg;
int128 val = *arg;
return (*is_neg = val < 0) ? (~(uint128)val) + 1 : (uint128)val;
case char:
return *(char*)arg;
return *arg;
case ushort:
return *(ushort*)arg;
return *arg;
case uint:
return *(uint*)arg;
return *arg;
case ulong:
return *(ulong*)arg;
return *arg;
case uint128:
return *(uint128*)arg;
return *arg;
case float:
float f = *(float*)arg;
float f = *arg;
return (uint128)((*is_neg = f < 0) ? -f : f);
case double:
double d = *(double*)arg;
double d = *arg;
return (uint128)((*is_neg = d < 0) ? -d : d);
default:
return BAD_FORMAT?;
return FormattingFault.BAD_FORMAT?;
}
}
fn FloatType? float_from_any(any arg) @private
fn FloatType! float_from_any(any arg) @private
{
$if env::F128_SUPPORT:
if (arg.type == float128.typeid) return (FloatType)*((float128*)arg.ptr);
@@ -97,36 +85,36 @@ fn FloatType? float_from_any(any arg) @private
{
return float_from_any(arg.as_inner());
}
switch (arg.type)
switch (arg)
{
case bool:
return (FloatType)*(bool*)arg;
return (FloatType)*arg;
case ichar:
return *(ichar*)arg;
return *arg;
case short:
return *(short*)arg;
return *arg;
case int:
return *(int*)arg;
return *arg;
case long:
return *(long*)arg;
return *arg;
case int128:
return *(int128*)arg;
return *arg;
case char:
return *(char*)arg;
return *arg;
case ushort:
return *(ushort*)arg;
return *arg;
case uint:
return *(uint*)arg;
return *arg;
case ulong:
return *(ulong*)arg;
return *arg;
case uint128:
return *(uint128*)arg;
return *arg;
case float:
return (FloatType)*(float*)arg;
return (FloatType)*arg;
case double:
return (FloatType)*(double*)arg;
return (FloatType)*arg;
default:
return BAD_FORMAT?;
return FormattingFault.BAD_FORMAT?;
}
}
@@ -134,9 +122,9 @@ fn FloatType? float_from_any(any arg) @private
<*
Read a simple integer value, typically for formatting.
@param [inout] len_ptr : "the length remaining."
@param [in] buf : "the buf to read from."
@param maxlen : "the maximum len that can be read."
@param [inout] len_ptr "the length remaining."
@param [in] buf "the buf to read from."
@param maxlen "the maximum len that can be read."
@return "The result of the atoi."
*>
fn uint simple_atoi(char* buf, usz maxlen, usz* len_ptr) @inline @private
@@ -154,7 +142,7 @@ fn uint simple_atoi(char* buf, usz maxlen, usz* len_ptr) @inline @private
return i;
}
fn usz? Formatter.out_substr(&self, String str) @private
fn usz! Formatter.out_substr(&self, String str) @private
{
usz l = conv::utf8_codepoints(str);
uint prec = self.prec;
@@ -173,7 +161,7 @@ fn usz? Formatter.out_substr(&self, String str) @private
return index;
}
fn usz? Formatter.pad(&self, char c, isz width, isz len) @inline
fn usz! Formatter.pad(&self, char c, isz width, isz len) @inline
{
isz delta = width - len;
for (isz i = 0; i < delta; i++) self.out(c)!;
@@ -187,7 +175,7 @@ fn char* fmt_u(uint128 x, char* s)
return s;
}
fn usz? Formatter.out_chars(&self, char[] s)
fn usz! Formatter.out_chars(&self, char[] s)
{
foreach (c : s) self.out(c)!;
return s.len;
@@ -201,12 +189,12 @@ enum FloatFormatting
HEX
}
fn usz? Formatter.etoa(&self, double y) => self.floatformat(EXPONENTIAL, y);
fn usz? Formatter.ftoa(&self, double y) => self.floatformat(FLOAT, y);
fn usz? Formatter.gtoa(&self, double y) => self.floatformat(ADAPTIVE, y);
fn usz? Formatter.atoa(&self, double y) => self.floatformat(HEX, y);
fn usz! Formatter.etoa(&self, double y) => self.floatformat(EXPONENTIAL, y);
fn usz! Formatter.ftoa(&self, double y) => self.floatformat(FLOAT, y);
fn usz! Formatter.gtoa(&self, double y) => self.floatformat(ADAPTIVE, y);
fn usz! Formatter.atoa(&self, double y) => self.floatformat(HEX, y);
fn usz? Formatter.floatformat(&self, FloatFormatting formatting, double y) @private
fn usz! Formatter.floatformat(&self, FloatFormatting formatting, double y) @private
{
// This code is heavily based on musl's printf code
const BUF_SIZE = (math::DOUBLE_MANT_DIG + 28) / 29 + 1
@@ -227,6 +215,7 @@ fn usz? Formatter.floatformat(&self, FloatFormatting formatting, double y) @priv
if (!self.flags.left) len += self.pad(' ', self.width, 3 + pl)!;
String s = self.flags.uppercase ? "INF" : "inf";
if (math::is_nan(y)) s = self.flags.uppercase ? "NAN" : "nan";
len += s.len;
if (pl) len += self.out(is_neg ? '-' : '+')!;
len += self.out_chars(s)!;
if (self.flags.left) len += self.pad(' ', self.width, 3 + pl)!;
@@ -281,7 +270,7 @@ fn usz? Formatter.floatformat(&self, FloatFormatting formatting, double y) @priv
} while (y);
isz outlen = s - buf;
isz explen = ebuf - estr;
if (p > int.max - 2 - explen - pl) return INTERNAL_BUFFER_EXCEEDED?;
if (p > int.max - 2 - explen - pl) return PrintFault.INTERNAL_BUFFER_EXCEEDED?;
usz len;
usz l = p && outlen - 2 < p
? p + 2 + explen
@@ -449,12 +438,12 @@ fn usz? Formatter.floatformat(&self, FloatFormatting formatting, double y) @priv
}
}
}
if (p > int.max - 1 - (isz)(p || self.flags.hash)) return INTERNAL_BUFFER_EXCEEDED?;
if (p > int.max - 1 - (isz)(p || self.flags.hash)) return PrintFault.INTERNAL_BUFFER_EXCEEDED?;
int l = (int)(1 + p + (isz)(p || self.flags.hash));
char* estr @noinit;
if (formatting == FLOAT)
{
if (e > int.max - l) return INTERNAL_BUFFER_EXCEEDED?;
if (e > int.max - l) return PrintFault.INTERNAL_BUFFER_EXCEEDED?;
if (e > 0) l += e;
}
else
@@ -463,10 +452,10 @@ fn usz? Formatter.floatformat(&self, FloatFormatting formatting, double y) @priv
while (ebuf - estr < 2) (--estr)[0] = '0';
*--estr = (e < 0 ? '-' : '+');
*--estr = self.flags.uppercase ? 'E' : 'e';
if (ebuf - estr > (isz)int.max - l) return INTERNAL_BUFFER_EXCEEDED?;
if (ebuf - estr > (isz)int.max - l) return PrintFault.INTERNAL_BUFFER_EXCEEDED?;
l += (int)(ebuf - estr);
}
if (l > int.max - pl) return INTERNAL_BUFFER_EXCEEDED?;
if (l > int.max - pl) return PrintFault.INTERNAL_BUFFER_EXCEEDED?;
usz len;
if (!self.flags.left && !self.flags.zeropad) len += self.pad(' ', self.width, pl + l)!;
if (is_neg || self.flags.plus) len += self.out(is_neg ? '-' : '+')!;
@@ -524,7 +513,7 @@ fn usz? Formatter.floatformat(&self, FloatFormatting formatting, double y) @priv
return len;
}
fn usz? Formatter.ntoa(&self, uint128 value, bool negative, uint base) @private
fn usz! Formatter.ntoa(&self, uint128 value, bool negative, uint base) @private
{
char[PRINTF_NTOA_BUFFER_SIZE] buf @noinit;
usz len;
@@ -538,7 +527,7 @@ fn usz? Formatter.ntoa(&self, uint128 value, bool negative, uint base) @private
char past_10 = (self.flags.uppercase ? 'A' : 'a') - 10;
do
{
if (len >= PRINTF_NTOA_BUFFER_SIZE) return INTERNAL_BUFFER_EXCEEDED?;
if (len >= PRINTF_NTOA_BUFFER_SIZE) return PrintFault.INTERNAL_BUFFER_EXCEEDED?;
char digit = (char)(value % base);
buf[len++] = digit + (digit < 10 ? '0' : past_10);
value /= base;
@@ -548,7 +537,7 @@ fn usz? Formatter.ntoa(&self, uint128 value, bool negative, uint base) @private
return self.ntoa_format((String)buf[:PRINTF_NTOA_BUFFER_SIZE], len, negative, base);
}
fn usz? Formatter.ntoa_format(&self, String buf, usz len, bool negative, uint base) @private
fn usz! Formatter.ntoa_format(&self, String buf, usz len, bool negative, uint base) @private
{
// pad leading zeros
if (!self.flags.left)
@@ -556,12 +545,12 @@ fn usz? Formatter.ntoa_format(&self, String buf, usz len, bool negative, uint ba
if (self.width && self.flags.zeropad && (negative || self.flags.plus || self.flags.space)) self.width--;
while (len < self.prec)
{
if (len >= buf.len) return INTERNAL_BUFFER_EXCEEDED?;
if (len >= buf.len) return PrintFault.INTERNAL_BUFFER_EXCEEDED?;
buf[len++] = '0';
}
while (self.flags.zeropad && len < self.width)
{
if (len >= buf.len) return INTERNAL_BUFFER_EXCEEDED?;
if (len >= buf.len) return PrintFault.INTERNAL_BUFFER_EXCEEDED?;
buf[len++] = '0';
}
}
@@ -576,7 +565,7 @@ fn usz? Formatter.ntoa_format(&self, String buf, usz len, bool negative, uint ba
}
if (base != 10)
{
if (len + 1 >= buf.len) return INTERNAL_BUFFER_EXCEEDED?;
if (len + 1 >= buf.len) return PrintFault.INTERNAL_BUFFER_EXCEEDED?;
switch (base)
{
case 16:
@@ -595,13 +584,13 @@ fn usz? Formatter.ntoa_format(&self, String buf, usz len, bool negative, uint ba
switch (true)
{
case negative:
if (len >= buf.len) return INTERNAL_BUFFER_EXCEEDED?;
if (len >= buf.len) return PrintFault.INTERNAL_BUFFER_EXCEEDED?;
buf[len++] = '-';
case self.flags.plus:
if (len >= buf.len) return INTERNAL_BUFFER_EXCEEDED?;
if (len >= buf.len) return PrintFault.INTERNAL_BUFFER_EXCEEDED?;
buf[len++] = '+';
case self.flags.space:
if (len >= buf.len) return INTERNAL_BUFFER_EXCEEDED?;
if (len >= buf.len) return PrintFault.INTERNAL_BUFFER_EXCEEDED?;
buf[len++] = ' ';
}
if (len) self.out_reverse(buf[:len])!;
@@ -609,18 +598,14 @@ fn usz? Formatter.ntoa_format(&self, String buf, usz len, bool negative, uint ba
}
fn usz? Formatter.ntoa_any(&self, any arg, uint base) @private
fn usz! Formatter.ntoa_any(&self, any arg, uint base) @private
{
bool is_neg;
return self.ntoa(int_from_any(arg, &is_neg)!!, is_neg, base) @inline;
}
fn usz? Formatter.out_char(&self, any arg) @private
fn usz! Formatter.out_char(&self, any arg) @private
{
if (!arg.type.kindof.is_int())
{
return self.out_substr("<NOT CHAR>");
}
usz len = 1;
uint l = 1;
// pre padding
@@ -649,7 +634,7 @@ fn usz? Formatter.out_char(&self, any arg) @private
}
fn usz? Formatter.out_reverse(&self, char[] buf) @private
fn usz! Formatter.out_reverse(&self, char[] buf) @private
{
usz n;
usz buffer_start_idx = self.idx;
@@ -668,7 +653,7 @@ fn usz? Formatter.out_reverse(&self, char[] buf) @private
}
fn int? printf_parse_format_field(
fn int! printf_parse_format_field(
any* args_ptr, usz args_len, usz* args_index_ptr,
char* format_ptr, usz format_len, usz* index_ptr) @inline @private
{
@@ -676,10 +661,10 @@ fn int? printf_parse_format_field(
if (c.is_digit()) return simple_atoi(format_ptr, format_len, index_ptr);
if (c != '*') return 0;
usz len = ++(*index_ptr);
if (len >= format_len) return BAD_FORMAT?;
if (*args_index_ptr >= args_len) return BAD_FORMAT?;
if (len >= format_len) return FormattingFault.BAD_FORMAT?;
if (*args_index_ptr >= args_len) return FormattingFault.BAD_FORMAT?;
any val = args_ptr[(*args_index_ptr)++];
if (!val.type.kindof.is_int()) return BAD_FORMAT?;
uint? intval = types::any_to_int(val, int);
return intval ?? BAD_FORMAT?;
if (!val.type.kindof.is_int()) return FormattingFault.BAD_FORMAT?;
uint! intval = types::any_to_int(val, int);
return intval ?? FormattingFault.BAD_FORMAT?;
}

View File

@@ -11,7 +11,8 @@ enum Seek
END
}
faultdef
fault IoError
{
ALREADY_EXISTS,
BUSY,
CANNOT_READ_DIR,
@@ -40,7 +41,8 @@ faultdef
UNEXPECTED_EOF,
UNKNOWN_ERROR,
UNSUPPORTED_OPERATION,
WOULD_BLOCK;
WOULD_BLOCK,
}
<*
@@ -48,12 +50,12 @@ faultdef
or to the end of the stream, whatever comes first.
"\r" will be filtered from the String.
@param stream : `The stream to read from.`
@require @is_instream(stream) : `The stream must implement InStream.`
@param [inout] allocator : `the allocator to use.`
@param stream `The stream to read from.`
@require @is_instream(stream) `The stream must implement InStream.`
@param [inout] allocator `the allocator to use.`
@return `The string containing the data read.`
*>
macro String? readline(Allocator allocator, stream = io::stdin())
macro String! readline(stream = io::stdin(), Allocator allocator = allocator::heap())
{
bool $is_stream = @typeis(stream, InStream);
$if $is_stream:
@@ -64,20 +66,20 @@ macro String? readline(Allocator allocator, stream = io::stdin())
$endif
if (val == '\n') return "";
@pool()
@pool(allocator)
{
DString str = dstring::temp_with_capacity(256);
if (val != '\r') str.append(val);
while (1)
{
$if $is_stream:
char? c = func((void*)stream);
char! c = func((void*)stream);
$else
char? c = stream.read_byte();
char! c = stream.read_byte();
$endif
if (catch err = c)
{
if (err == io::EOF) break;
if (err == IoError.EOF) break;
return err?;
}
if (c == '\r') continue;
@@ -92,27 +94,27 @@ macro String? readline(Allocator allocator, stream = io::stdin())
Reads a string, see `readline`, except the it is allocated
on the temporary allocator and does not need to be freed.
@param stream : `The stream to read from.`
@require @is_instream(stream) : `The stream must implement InStream.`
@param stream `The stream to read from.`
@require @is_instream(stream) `The stream must implement InStream.`
@return `The temporary string containing the data read.`
*>
macro String? treadline(stream = io::stdin())
macro String! treadline(stream = io::stdin())
{
return readline(tmem, stream) @inline;
return readline(stream, allocator::temp()) @inline;
}
<*
Print a value to a stream.
@param out : `the stream to print to`
@param x : `the value to print`
@require @is_outstream(out) : `The output must implement OutStream.`
@param out `the stream to print to`
@param x `the value to print`
@require @is_outstream(out) `The output must implement OutStream.`
@return `the number of bytes printed.`
*>
macro usz? fprint(out, x)
macro usz! fprint(out, x)
{
var $Type = $typeof(x);
$switch $Type:
$switch ($Type)
$case String: return out.write(x);
$case ZString: return out.write(x.str_view());
$case DString: return out.write(x.str_view());
@@ -135,11 +137,11 @@ macro usz? fprint(out, x)
Prints using a 'printf'-style formatting string.
See `printf` for details on formatting.
@param [inout] out : `The OutStream to print to`
@param [in] format : `The printf-style format string`
@param [inout] out `The OutStream to print to`
@param [in] format `The printf-style format string`
@return `the number of characters printed`
*>
fn usz? fprintf(OutStream out, String format, args...) @format(1)
fn usz! fprintf(OutStream out, String format, args...)
{
Formatter formatter;
formatter.init(&out_putstream_fn, &out);
@@ -150,11 +152,11 @@ fn usz? fprintf(OutStream out, String format, args...) @format(1)
Prints using a 'printf'-style formatting string,
appending '\n' at the end. See `printf`.
@param [inout] out : `The OutStream to print to`
@param [in] format : `The printf-style format string`
@param [inout] out `The OutStream to print to`
@param [in] format `The printf-style format string`
@return `the number of characters printed`
*>
fn usz? fprintfn(OutStream out, String format, args...) @format(1) @maydiscard
fn usz! fprintfn(OutStream out, String format, args...) @maydiscard
{
Formatter formatter;
formatter.init(&out_putstream_fn, &out);
@@ -165,13 +167,13 @@ fn usz? fprintfn(OutStream out, String format, args...) @format(1) @maydiscard
}
<*
@require @is_outstream(out) : "The output must implement OutStream"
@require @is_outstream(out) "The output must implement OutStream"
*>
macro usz? fprintn(out, x = "")
macro usz! fprintn(out, x = "")
{
usz len = fprint(out, x)!;
out.write_byte('\n')!;
$switch:
$switch
$case @typeid(out) == OutStream.typeid:
if (&out.flush) out.flush()!;
$case $defined(out.flush):
@@ -191,7 +193,7 @@ macro void print(x)
<*
Print any value to stdout, appending an '\n after.
@param x : "The value to print"
@param x "The value to print"
*>
macro void printn(x = "")
{
@@ -209,7 +211,7 @@ macro void eprint(x)
<*
Print any value to stderr, appending an '\n after.
@param x : "The value to print"
@param x "The value to print"
*>
macro void eprintn(x)
{
@@ -217,22 +219,15 @@ macro void eprintn(x)
}
fn void? out_putstream_fn(void* data, char c) @private
fn void! out_putstream_fn(void* data, char c) @private
{
OutStream* stream = data;
return (*stream).write_byte(c);
}
fn void? out_putchar_fn(void* data @unused, char c) @private
fn void! out_putchar_fn(void* data @unused, char c) @private
{
$if env::TESTING:
// HACK: this is used for the purpose of unit test output hijacking
File* stdout = io::stdout();
assert(stdout.file);
libc::fputc(c, stdout.file);
$else
libc::putchar(c);
$endif
libc::putchar(c);
}
<*
@@ -246,10 +241,10 @@ fn void? out_putchar_fn(void* data @unused, char c) @private
To create a custom output for a type, implement
the Printable interface.
@param [in] format : `The printf-style format string`
@param [in] format `The printf-style format string`
@return `the number of characters printed`
*>
fn usz? printf(String format, args...) @format(0) @maydiscard
fn usz! printf(String format, args...) @maydiscard
{
Formatter formatter;
formatter.init(&out_putchar_fn);
@@ -260,15 +255,15 @@ fn usz? printf(String format, args...) @format(0) @maydiscard
Prints using a 'printf'-style formatting string,
appending '\n' at the end. See `printf`.
@param [in] format : `The printf-style format string`
@param [in] format `The printf-style format string`
@return `the number of characters printed`
*>
fn usz? printfn(String format, args...) @format(0) @maydiscard
fn usz! printfn(String format, args...) @maydiscard
{
Formatter formatter;
formatter.init(&out_putchar_fn);
usz? len = formatter.vprintf(format, args);
out_putchar_fn(null, '\n')!;
usz! len = formatter.vprintf(format, args);
putchar('\n');
io::stdout().flush()!;
return len + 1;
}
@@ -277,10 +272,10 @@ fn usz? printfn(String format, args...) @format(0) @maydiscard
Prints using a 'printf'-style formatting string
to stderr.
@param [in] format : `The printf-style format string`
@param [in] format `The printf-style format string`
@return `the number of characters printed`
*>
fn usz? eprintf(String format, args...) @maydiscard
fn usz! eprintf(String format, args...) @maydiscard
{
Formatter formatter;
OutStream stream = stderr();
@@ -293,15 +288,15 @@ fn usz? eprintf(String format, args...) @maydiscard
Prints using a 'printf'-style formatting string,
to stderr appending '\n' at the end. See `printf`.
@param [in] format : `The printf-style format string`
@param [in] format `The printf-style format string`
@return `the number of characters printed`
*>
fn usz? eprintfn(String format, args...) @maydiscard
fn usz! eprintfn(String format, args...) @maydiscard
{
Formatter formatter;
OutStream stream = stderr();
formatter.init(&out_putstream_fn, &stream);
usz? len = formatter.vprintf(format, args);
usz! len = formatter.vprintf(format, args);
stderr().write_byte('\n')!;
stderr().flush()!;
return len + 1;
@@ -311,11 +306,11 @@ fn usz? eprintfn(String format, args...) @maydiscard
Prints using a 'printf'-style formatting string,
to a string buffer. See `printf`.
@param [inout] buffer : `The buffer to print to`
@param [in] format : `The printf-style format string`
@param [inout] buffer `The buffer to print to`
@param [in] format `The printf-style format string`
@return `a slice formed from the "buffer" with the resulting length.`
*>
fn char[]? bprintf(char[] buffer, String format, args...) @maydiscard
fn char[]! bprintf(char[] buffer, String format, args...) @maydiscard
{
Formatter formatter;
BufferData data = { .buffer = buffer };
@@ -325,10 +320,10 @@ fn char[]? bprintf(char[] buffer, String format, args...) @maydiscard
}
// Used to print to a buffer.
fn void? out_buffer_fn(void *data, char c) @private
fn void! out_buffer_fn(void *data, char c) @private
{
BufferData *buffer_data = data;
if (buffer_data.written >= buffer_data.buffer.len) return BUFFER_EXCEEDED?;
if (buffer_data.written >= buffer_data.buffer.len) return PrintFault.BUFFER_EXCEEDED?;
buffer_data.buffer[buffer_data.written++] = c;
}

View File

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

View File

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

View File

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

View File

@@ -1,60 +1,66 @@
module std::io::os;
import libc, std::os, std::io;
fn void? native_stat(Stat* stat, String path) @if(env::DARWIN || env::LINUX || env::BSD_FAMILY) => @pool()
fn void! native_stat(Stat* stat, String path) @if(env::DARWIN || env::LINUX || env::BSD_FAMILY)
{
$if env::DARWIN || env::LINUX || env::BSD_FAMILY:
int res = libc::stat(path.zstr_tcopy(), stat);
$else
unreachable("Stat unimplemented");
int res = 0;
$endif
if (res != 0)
@pool()
{
switch (libc::errno())
$if env::DARWIN || env::LINUX || env::BSD_FAMILY:
int res = libc::stat(path.zstr_tcopy(), stat);
$else
unreachable("Stat unimplemented");
int res = 0;
$endif
if (res != 0)
{
case errno::EBADF:
return io::FILE_NOT_VALID?;
case errno::EFAULT:
unreachable("Invalid stat");
case errno::EIO:
return io::GENERAL_ERROR?;
case errno::EACCES:
return io::NO_PERMISSION?;
case errno::ELOOP:
return io::NO_PERMISSION?;
case errno::ENAMETOOLONG:
return io::NAME_TOO_LONG?;
case errno::ENOENT:
return io::FILE_NOT_FOUND?;
case errno::ENOTDIR:
return io::FILE_NOT_DIR?;
case errno::EOVERFLOW:
return io::GENERAL_ERROR?;
default:
return io::UNKNOWN_ERROR?;
switch (libc::errno())
{
case errno::EBADF:
return IoError.FILE_NOT_VALID?;
case errno::EFAULT:
unreachable("Invalid stat");
case errno::EIO:
return IoError.GENERAL_ERROR?;
case errno::EACCES:
return IoError.NO_PERMISSION?;
case errno::ELOOP:
return IoError.NO_PERMISSION?;
case errno::ENAMETOOLONG:
return IoError.NAME_TOO_LONG?;
case errno::ENOENT:
return IoError.FILE_NOT_FOUND?;
case errno::ENOTDIR:
return IoError.FILE_NOT_DIR?;
case errno::EOVERFLOW:
return IoError.GENERAL_ERROR?;
default:
return IoError.UNKNOWN_ERROR?;
}
}
}
};
}
fn usz? native_file_size(String path) @if(env::WIN32) => @pool()
fn usz! native_file_size(String path) @if(env::WIN32)
{
Win32_FILE_ATTRIBUTE_DATA data;
win32::getFileAttributesExW(path.to_temp_wstring()!, Win32_GET_FILEEX_INFO_LEVELS.STANDARD, &data);
Win32_LARGE_INTEGER size;
size.lowPart = data.nFileSizeLow;
size.highPart = data.nFileSizeHigh;
return (usz)size.quadPart;
@pool()
{
Win32_FILE_ATTRIBUTE_DATA data;
win32::getFileAttributesExW(path.to_temp_wstring()!, Win32_GET_FILEEX_INFO_LEVELS.STANDARD, &data);
Win32_LARGE_INTEGER size;
size.lowPart = data.nFileSizeLow;
size.highPart = data.nFileSizeHigh;
return (usz)size.quadPart;
};
}
fn usz? native_file_size(String path) @if(!env::WIN32 && !env::DARWIN)
fn usz! native_file_size(String path) @if(!env::WIN32 && !env::DARWIN)
{
File f = file::open(path, "r")!;
defer (void)f.close();
return f.seek(0, Seek.END)!;
}
fn usz? native_file_size(String path) @if(env::DARWIN)
fn usz! native_file_size(String path) @if(env::DARWIN)
{
Stat stat;
native_stat(&stat, path)!;
@@ -63,7 +69,7 @@ fn usz? native_file_size(String path) @if(env::DARWIN)
fn bool native_file_or_dir_exists(String path)
{
$switch:
$switch
$case env::DARWIN:
$case env::FREEBSD:
$case env::NETBSD:
@@ -88,7 +94,7 @@ fn bool native_file_or_dir_exists(String path)
fn bool native_is_file(String path)
{
$switch:
$switch
$case env::DARWIN:
$case env::FREEBSD:
$case env::NETBSD:
@@ -97,7 +103,7 @@ fn bool native_is_file(String path)
Stat stat;
return @ok(native_stat(&stat, path)) && libc_S_ISTYPE(stat.st_mode, libc::S_IFREG);
$default:
File? f = file::open(path, "r");
File! f = file::open(path, "r");
defer (void)f.close();
return @ok(f);
$endswitch

View File

@@ -1,9 +1,9 @@
module std::io::os;
import libc, std::os;
macro String? getcwd(Allocator allocator)
macro String! getcwd(Allocator allocator = allocator::heap())
{
$switch:
$switch
$case env::WIN32:
const DEFAULT_BUFFER = 256;
Char16[DEFAULT_BUFFER] buffer;
@@ -12,12 +12,12 @@ macro String? getcwd(Allocator allocator)
defer if (free) libc::free(res);
if (!res)
{
if (libc::errno() != errno::ERANGE) return io::GENERAL_ERROR?;
if (libc::errno() != errno::ERANGE) return IoError.GENERAL_ERROR?;
res = win32::_wgetcwd(null, 0);
free = true;
}
Char16[] str16 = res[:win32::wcslen(res)];
return string::from_utf16(allocator, str16);
return string::new_from_utf16(str16, allocator);
$case env::POSIX:
const usz DEFAULT_BUFFER = 256;
@@ -27,7 +27,7 @@ macro String? getcwd(Allocator allocator)
if (!res)
{
// Improve error
if (libc::errno() != errno::ERANGE) return io::GENERAL_ERROR?;
if (libc::errno() != errno::ERANGE) return IoError.GENERAL_ERROR?;
res = posix::getcwd(null, 0);
free = true;
}
@@ -35,7 +35,7 @@ macro String? getcwd(Allocator allocator)
return res.copy(allocator);
$default:
return io::UNSUPPORTED_OPERATION?;
return IoError.UNSUPPORTED_OPERATION?;
$endswitch
}

View File

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

View File

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

View File

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

View File

@@ -2,13 +2,13 @@ module std::io::os @if(env::POSIX);
import std::io, std::os, libc;
<*
@require dir.str_view().len > 0
@require dir.str_view()
*>
fn void? native_rmtree(Path dir)
fn void! native_rmtree(Path dir)
{
DIRPtr directory = posix::opendir(dir.as_zstr());
defer if (directory) posix::closedir(directory);
if (!directory) return path::is_dir(dir) ? io::CANNOT_READ_DIR? : io::FILE_NOT_DIR?;
if (!directory) return path::is_dir(dir) ? IoError.CANNOT_READ_DIR? : IoError.FILE_NOT_DIR?;
Posix_dirent* entry;
while ((entry = posix::readdir(directory)))
{
@@ -16,7 +16,7 @@ fn void? native_rmtree(Path dir)
{
String name = ((ZString)&entry.name).str_view();
if (!name || name == "." || name == "..") continue;
Path new_path = dir.tappend(name)!;
Path new_path = dir.temp_append(name)!;
if (entry.d_type == posix::DT_DIR)
{
native_rmtree(new_path)!;
@@ -25,7 +25,7 @@ fn void? native_rmtree(Path dir)
if (libc::remove(new_path.as_zstr()))
{
// TODO improve
return io::GENERAL_ERROR?;
return IoError.GENERAL_ERROR?;
}
};
}
@@ -35,21 +35,21 @@ fn void? native_rmtree(Path dir)
module std::io::os @if(env::WIN32);
import std::io, std::time, std::os;
fn void? native_rmtree(Path path)
fn void! native_rmtree(Path path)
{
Win32_WIN32_FIND_DATAW find_data;
String s = path.str_view().tconcat("\\*");
Win32_HANDLE find = win32::findFirstFileW(s.to_temp_utf16(), &find_data)!;
if (find == win32::INVALID_HANDLE_VALUE) return io::CANNOT_READ_DIR?;
if (find == win32::INVALID_HANDLE_VALUE) return IoError.CANNOT_READ_DIR?;
defer win32::findClose(find);
do
{
@pool()
{
String filename = string::tfrom_wstring((WString)&find_data.cFileName)!;
String filename = string::new_from_wstring((WString)&find_data.cFileName, allocator::temp())!;
if (filename == "." || filename == "..") continue;
Path file_path = path.tappend(filename)!;
Path file_path = path.temp_append(filename)!;
if (find_data.dwFileAttributes & win32::FILE_ATTRIBUTE_DIRECTORY)
{
native_rmtree(file_path)!;

View File

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

View File

@@ -2,22 +2,25 @@ module std::io::path;
import std::collections::list, std::io::os;
import std::os::win32;
const PathEnv DEFAULT_ENV = env::WIN32 ? PathEnv.WIN32 : PathEnv.POSIX;
const PathEnv DEFAULT_PATH_ENV = env::WIN32 ? PathEnv.WIN32 : PathEnv.POSIX;
const char PREFERRED_SEPARATOR_WIN32 = '\\';
const char PREFERRED_SEPARATOR_POSIX = '/';
const char PREFERRED_SEPARATOR = env::WIN32 ? PREFERRED_SEPARATOR_WIN32 : PREFERRED_SEPARATOR_POSIX;
alias PathList = List { Path };
def PathList = List(<Path>);
faultdef INVALID_PATH, NO_PARENT;
fault PathResult
{
INVALID_PATH,
NO_PARENT,
}
alias Path = PathImp;
def Path = PathImp;
struct PathImp (Printable)
{
String path_string;
PathEnv env;
Allocator allocator;
}
enum PathEnv
@@ -26,55 +29,63 @@ enum PathEnv
POSIX
}
fn Path? cwd(Allocator allocator)
fn Path! new_cwd(Allocator allocator = allocator::heap())
{
@pool()
@pool(allocator)
{
return new(allocator, os::getcwd(tmem));
return new(os::getcwd(allocator::temp()), allocator);
};
}
fn Path! getcwd(Allocator allocator = allocator::heap()) @deprecated("Use new_cwd()")
{
@pool(allocator)
{
return new(os::getcwd(allocator::temp()), allocator);
};
}
fn bool is_dir(Path path) => os::native_is_dir(path.str_view());
fn bool is_file(Path path) => os::native_is_file(path.str_view());
fn usz? file_size(Path path) => os::native_file_size(path.str_view());
fn usz! file_size(Path path) => os::native_file_size(path.str_view());
fn bool exists(Path path) => os::native_file_or_dir_exists(path.str_view());
fn Path? tcwd() => cwd(tmem) @inline;
fn Path! temp_cwd() => new_cwd(allocator::temp()) @inline;
fn Path! tgetcwd() @deprecated("Use temp_cwd()") => new_cwd(allocator::temp()) @inline;
fn void! chdir(Path path) => os::native_chdir(path) @inline;
fn Path! temp_directory(Allocator allocator = allocator::heap()) => os::native_temp_directory(allocator);
fn void! delete(Path path) => os::native_remove(path.str_view()) @inline;
<*
@require @is_pathlike(path) : "Expected a Path or String to chdir"
*>
macro void? chdir(path)
{
$if @typeis(path, String):
@pool()
{
return os::native_chdir(temp(path));
};
$else
return os::native_chdir(path) @inline;
$endif
}
fn Path? temp_directory(Allocator allocator) => os::native_temp_directory(allocator);
fn void? delete(Path path) => os::native_remove(path.str_view()) @inline;
macro bool @is_pathlike(#path) => @typeis(#path, String) || @typeis(#path, Path);
macro bool is_separator(char c, PathEnv path_env = DEFAULT_ENV)
macro bool is_separator(char c, PathEnv path_env = DEFAULT_PATH_ENV)
{
return c == '/' || (c == '\\' && path_env == PathEnv.WIN32);
}
macro bool is_posix_separator(char c) => c == '/';
macro bool is_win32_separator(char c) => c == '/' || c == '\\';
macro bool is_posix_separator(char c)
{
return c == '/' || c == '\\';
}
fn PathList? ls(Allocator allocator, Path dir, bool no_dirs = false, bool no_symlinks = false, String mask = "")
macro bool is_win32_separator(char c)
{
return c == '/' || c == '\\';
}
fn PathList! ls(Path dir, bool no_dirs = false, bool no_symlinks = false, String mask = "", Allocator allocator = allocator::heap()) @deprecated("use new_ls")
{
return new_ls(dir, no_dirs, no_symlinks, mask, allocator);
}
fn PathList! temp_ls(Path dir, bool no_dirs = false, bool no_symlinks = false, String mask = "")
{
return new_ls(dir, no_dirs, no_symlinks, mask, allocator::temp()) @inline;
}
fn PathList! new_ls(Path dir, bool no_dirs = false, bool no_symlinks = false, String mask = "", Allocator allocator = allocator::heap())
{
$if $defined(os::native_ls):
return os::native_ls(dir, no_dirs, no_symlinks, mask, allocator);
$else
return io::UNSUPPORTED_OPERATION?;
return IoError.UNSUPPORTED_OPERATION?;
$endif
}
@@ -88,84 +99,87 @@ enum MkdirPermissions
<*
Create a directory on a given path, optionally recursive.
@param path : `The path to create`
@require @is_pathlike(path) : "Expected a Path or String to chdir"
@param recursive : `If directories in between should be created if they're missing, defaults to false`
@param permissions : `The permissions to set on the directory`
@param path `The path to create`
@param recursive `If directories in between should be created if they're missing, defaults to false`
@param permissions `The permissions to set on the directory`
*>
macro bool? mkdir(Path path, bool recursive = false, MkdirPermissions permissions = NORMAL)
fn bool! mkdir(Path path, bool recursive = false, MkdirPermissions permissions = NORMAL)
{
$if @typeis(path, String):
@pool() { return _mkdir(temp(path), recursive, permissions); };
$else
return _mkdir(path, recursive, permissions);
$endif
}
if (!path.path_string.len) return PathResult.INVALID_PATH?;
if (is_dir(path)) return false;
if (exists(path)) return IoError.FILE_NOT_DIR?;
if (recursive)
{
if (try parent = path.parent()) mkdir(parent, true, permissions)!;
}
if (!is_dir(path.parent()) ?? false) return IoError.CANNOT_READ_DIR?;
return os::native_mkdir(path, permissions);
}
<*
Tries to delete directory, which must be empty.
@param path : `The path to delete`
@require @is_pathlike(path) : "Expected a Path or String to chdir"
@param path `The path to delete`
@return `true if there was a directory to delete, false otherwise`
@return? INVALID_PATH : `if the path was invalid`
@return! PathResult.INVALID_PATH `if the path was invalid`
*>
macro bool? rmdir(path)
fn bool! rmdir(Path path)
{
$if @typeis(path, String):
@pool() { return _rmdir(temp(path)); };
$else
return _mkdir(path);
$endif
if (!path.path_string.len) return PathResult.INVALID_PATH?;
return os::native_rmdir(path);
}
<*
Like [rmdir] but deletes a directory even if it contains items.
*>
fn void? rmtree(Path path)
fn void! rmtree(Path path)
{
if (!path.path_string.len) return INVALID_PATH?;
if (!path.path_string.len) return PathResult.INVALID_PATH?;
$if $defined(os::native_rmtree):
return os::native_rmtree(path);
$else
return io::UNSUPPORTED_OPERATION?;
return IoError.UNSUPPORTED_OPERATION?;
$endif
}
<*
Creates a new path.
@return? INVALID_PATH : `if the path was invalid`
@return! PathResult.INVALID_PATH `if the path was invalid`
*>
fn Path? new(Allocator allocator, String path, PathEnv path_env = DEFAULT_ENV)
fn Path! new(String path, Allocator allocator = allocator::heap(), PathEnv path_env = DEFAULT_PATH_ENV)
{
return { normalize(path.copy(allocator), path_env), path_env, allocator };
return { normalize(path.copy(allocator), path_env), path_env };
}
<*
Creates a new path using the temp allocator.
@return? INVALID_PATH : `if the path was invalid`
@return! PathResult.INVALID_PATH `if the path was invalid`
*>
fn Path? temp(String path, PathEnv path_env = DEFAULT_ENV)
fn Path! temp_new(String path, PathEnv path_env = DEFAULT_PATH_ENV)
{
return new(tmem, path, path_env);
return new(path, allocator::temp(), path_env);
}
fn Path? from_win32_wstring(Allocator allocator, WString path) => @pool()
fn Path! new_win32_wstring(WString path, Allocator allocator = allocator::heap())
{
return path::new(allocator, string::tfrom_wstring(path)!);
@pool(allocator)
{
return path::new(string::temp_from_wstring(path)!, allocator: allocator);
};
}
fn Path? for_windows(Allocator allocator, String path)
fn Path! new_windows(String path, Allocator allocator = allocator::heap())
{
return new(allocator, path, WIN32);
return new(path, allocator, WIN32);
}
fn Path? for_posix(Allocator allocator, String path)
fn Path! new_posix(String path, Allocator allocator = allocator::heap())
{
return new(allocator, path, POSIX);
return new(path, allocator, POSIX);
}
fn bool Path.equals(self, Path p2)
@@ -173,56 +187,59 @@ fn bool Path.equals(self, Path p2)
return self.env == p2.env && self.path_string == p2.path_string;
}
fn Path! Path.append(self, String filename, Allocator allocator = allocator::heap()) @deprecated("Use path.new_append(...)")
{
return self.new_append(filename, allocator) @inline;
}
<*
Append the string to the current path.
@param [in] filename
*>
fn Path? Path.append(self, Allocator allocator, String filename)
fn Path! Path.new_append(self, String filename, Allocator allocator = allocator::heap())
{
if (!self.path_string.len) return new(allocator, filename, self.env)!;
if (!self.path_string.len) return new(filename, allocator, self.env)!;
assert(!is_separator(self.path_string[^1], self.env));
@pool()
@pool(allocator)
{
DString dstr = dstring::temp_with_capacity(self.path_string.len + 1 + filename.len);
dstr.append(self.path_string);
dstr.append(PREFERRED_SEPARATOR);
dstr.append(filename);
return new(allocator, dstr.str_view(), self.env);
return { normalize(dstr.copy_str(allocator), self.env), self.env };
};
}
fn Path? Path.tappend(self, String filename) => self.append(tmem, filename);
fn Path! Path.temp_append(self, String filename) => self.new_append(filename, allocator::temp());
fn usz? start_of_base_name(String str, PathEnv path_env) @local
fn Path! Path.tappend(self, String filename) @deprecated("Use path.temp_append(...)") => self.new_append(filename, allocator::temp());
fn usz Path.start_of_base_name(self) @local
{
if (!str.len) return 0;
usz? start_slash = str.rindex_of_char('/');
if (path_env != PathEnv.WIN32) return start_slash + 1 ?? 0;
if (try index = str.rindex_of_char('\\'))
String path_str = self.path_string;
if (!path_str.len) return 0;
if (self.env == PathEnv.WIN32)
{
if (try start_slash && start_slash > index) return start_slash + 1;
// c:\ style path, we're done!
if (str[0] != '\\') return index + 1;
// Handle \\server\foo
// Find the \ before "foo"
usz last_index = 2 + str[2..].index_of_char('\\')!;
// If they don't match, we're done
if (last_index > index) return INVALID_PATH?;
if (last_index != index) return index + 1;
// Otherwise just default to the volume length.
if (try index = path_str.rindex_of_char('\\'))
{
// c:\ style path, we're done!
if (path_str[0] != '\\') return index + 1;
// Handle \\server\foo
// Find the \ before "foo"
usz last_index = 2 + path_str[2..].index_of_char('\\')!!;
// If they don't match, we're done
assert(last_index <= index, "Invalid normalized, path %d vs %s in %s", last_index, index, path_str);
if (last_index != index) return index + 1;
// Otherwise just default to the volume length.
}
return volume_name_len(path_str, self.env)!!;
}
return volume_name_len(str, path_env)!!;
return path_str.rindex_of_char('/') + 1 ?? 0;
}
fn bool? String.is_absolute_path(self) => @pool()
{
return temp(self).is_absolute();
}
fn bool? Path.is_absolute(self)
fn bool! Path.is_absolute(self)
{
String path_str = self.str_view();
if (!path_str.len) return false;
@@ -231,69 +248,55 @@ fn bool? Path.is_absolute(self)
return path_start < path_str.len && is_separator(path_str[path_start], self.env);
}
fn Path? String.to_absolute_path(self, Allocator allocator) => @pool()
fn Path! Path.absolute(self, Allocator allocator = allocator::heap()) @deprecated("Use path.new_absolute()")
{
return temp(self).absolute(allocator);
return self.new_absolute(allocator) @inline;
}
<*
@require self.env == DEFAULT_ENV : "This method is only available on native paths"
@require self.env == DEFAULT_PATH_ENV : "This method is only available on native paths"
*>
fn Path? Path.absolute(self, Allocator allocator)
fn Path! Path.new_absolute(self, Allocator allocator = allocator::heap())
{
String path_str = self.str_view();
if (!path_str.len) return INVALID_PATH?;
if (self.is_absolute()!) return new(allocator, path_str, self.env);
if (!path_str.len) return PathResult.INVALID_PATH?;
if (self.is_absolute()!) return new(path_str, allocator, self.env);
if (path_str == ".")
{
@pool()
@pool(allocator)
{
String cwd = os::getcwd(tmem)!;
return new(allocator, cwd, self.env);
String cwd = os::getcwd(allocator::temp())!;
return new(cwd, allocator, self.env);
};
}
$if DEFAULT_ENV == WIN32:
@pool()
$if DEFAULT_PATH_ENV == WIN32:
@pool(allocator)
{
const usz BUFFER_LEN = 4096;
WString buffer = (WString)mem::talloc_array(Char16, BUFFER_LEN);
WString buffer = (WString)mem::temp_alloc_array(Char16, BUFFER_LEN);
buffer = win32::_wfullpath(buffer, path_str.to_temp_wstring()!, BUFFER_LEN);
if (!buffer) return INVALID_PATH?;
return { string::from_wstring(allocator, buffer), WIN32, allocator };
if (!buffer) return PathResult.INVALID_PATH?;
return { string::new_from_wstring(buffer, allocator), WIN32 };
};
$else
String cwd = os::getcwd(tmem)!;
return (Path){ cwd, self.env, tmem }.append(allocator, path_str)!;
String cwd = os::getcwd(allocator::temp())!;
return Path { cwd, self.env }.new_append(path_str, allocator)!;
$endif
}
fn String? String.file_basename(self, Allocator allocator) => @pool()
{
return temp(self).basename().copy(allocator);
}
fn String? String.file_tbasename(self) => self.file_basename(tmem);
fn String Path.basename(self)
{
usz basename_start = start_of_base_name(self.path_string, self.env)!!;
usz basename_start = self.start_of_base_name();
String path_str = self.path_string;
if (basename_start == path_str.len) return "";
return path_str[basename_start..];
}
fn String? String.path_tdirname(self) => self.path_dirname(tmem);
fn String? String.path_dirname(self, Allocator allocator) => @pool()
{
return temp(self).dirname().copy(allocator);
}
fn String Path.dirname(self)
{
usz basename_start = self.start_of_base_name();
String path_str = self.path_string;
usz basename_start = start_of_base_name(path_str, self.env)!!;
if (basename_start == 0) return ".";
usz start = volume_name_len(path_str, self.env)!!;
if (basename_start <= start + 1)
@@ -307,12 +310,11 @@ fn String Path.dirname(self)
return path_str[:basename_start - 1];
}
<*
Test if the path has the given extension, so given the path /foo/bar.c3
this would be true matching the extension "c3"
@param [in] extension : `The extension name (not including the leading '.')`
@param [in] extension `The extension name (not including the leading '.')`
@require extension.len > 0 : `The extension cannot be empty`
@return `true if the extension matches`
*>
@@ -324,12 +326,13 @@ fn bool Path.has_extension(self, String extension)
return basename[^extension.len..] == extension;
}
fn String? Path.extension(self)
fn String! Path.extension(self)
{
String basename = self.basename();
usz index = basename.rindex_of(".")!;
// Plain ".foo" does not have an extension
if (index == 0 || index == basename.len) return "";
// Plain ".foo" does not have an
if (index == 0) return SearchResult.MISSING?;
if (index == basename.len) return "";
return basename[index + 1..];
}
@@ -340,17 +343,7 @@ fn String Path.volume_name(self)
return self.path_string[:len];
}
fn Path? String.to_path(self, Allocator allocator)
{
return new(allocator, self);
}
fn Path? String.to_tpath(self)
{
return new(tmem, self);
}
fn usz? volume_name_len(String path, PathEnv path_env) @local
fn usz! volume_name_len(String path, PathEnv path_env) @local
{
usz len = path.len;
if (len < 2 || path_env != PathEnv.WIN32) return 0;
@@ -374,10 +367,10 @@ fn usz? volume_name_len(String path, PathEnv path_env) @local
base_found = i;
continue;
}
if (is_reserved_win32_path_char(c)) return INVALID_PATH?;
if (is_reserved_win32_path_char(c)) return PathResult.INVALID_PATH?;
}
if (base_found > 0 && base_found + 1 < len) return len;
return INVALID_PATH?;
return PathResult.INVALID_PATH?;
case 'A'..'Z':
case 'a'..'z':
return path[1] == ':' ? 2 : 0;
@@ -391,24 +384,24 @@ fn usz? volume_name_len(String path, PathEnv path_env) @local
of the path itself.
@return `The parent of the path as a non-allocated path`
@return? NO_PARENT : `if this path does not have a parent`
@return! PathResult.NO_PARENT `if this path does not have a parent`
*>
fn Path? Path.parent(self)
fn Path! Path.parent(self)
{
if (self.path_string.len == 1 && is_separator(self.path_string[0], self.env)) return NO_PARENT?;
if (self.path_string.len == 1 && is_separator(self.path_string[0], self.env)) return PathResult.NO_PARENT?;
foreach_r(i, c : self.path_string)
{
if (is_separator(c, self.env))
{
return { self.path_string[:i], self.env, null };
return { self.path_string[:i], self.env };
}
}
return NO_PARENT?;
return PathResult.NO_PARENT?;
}
fn String? normalize(String path_str, PathEnv path_env = DEFAULT_ENV)
fn String! normalize(String path_str, PathEnv path_env = DEFAULT_PATH_ENV)
{
if (!path_str.len) return path_str;
if (!path_str.len) return "";
usz path_start = volume_name_len(path_str, path_env)!;
if (path_start > 0 && path_env == PathEnv.WIN32)
{
@@ -445,7 +438,7 @@ fn String? normalize(String path_str, PathEnv path_env = DEFAULT_ENV)
// The rest are names of the path elements, so check that the
// characters are valid.
if (is_reserved_path_char(c, path_env)) return INVALID_PATH?;
if (is_reserved_path_char(c, path_env)) return PathResult.INVALID_PATH?;
// If we have '.' after a separator
if (c == '.' && previous_was_separator)
@@ -478,7 +471,7 @@ fn String? normalize(String path_str, PathEnv path_env = DEFAULT_ENV)
continue;
case 2:
// This is an error: /a/../..
if (len == path_start && has_root) return INVALID_PATH?;
if (len == path_start && has_root) return PathResult.INVALID_PATH?;
// If this .. at the start, or after ../? If so, we just copy ..
if (len == path_start ||
@@ -519,11 +512,7 @@ fn String? normalize(String path_str, PathEnv path_env = DEFAULT_ENV)
if (len > path_start + 1 && is_separator(path_str[len - 1], path_env)) len--;
if (path_str.len > len) path_str.ptr[len] = 0;
// Empty path after normalization -> "."
if (!len)
{
path_str[0] = '.';
return path_str[:1];
}
if (!len) return ".";
return path_str[:len];
}
@@ -552,24 +541,24 @@ fn String Path.root_directory(self)
return path_str;
}
alias PathWalker = fn bool? (Path, bool is_dir, void*);
def PathWalker = fn bool! (Path, bool is_dir, void*);
<*
Walk the path recursively. PathWalker is run on every file and
directory found. Return true to abort the walk.
@require self.env == DEFAULT_ENV : "This method is only available on native paths"
@require self.env == DEFAULT_PATH_ENV : "This method is only available on native paths"
*>
fn bool? Path.walk(self, PathWalker w, void* data)
fn bool! Path.walk(self, PathWalker w, void* data)
{
const PATH_MAX = 512;
@stack_mem(PATH_MAX; Allocator allocator)
{
Path abs = self.absolute(allocator)!;
PathList files = ls(allocator, abs)!;
Path abs = self.new_absolute(allocator)!;
PathList files = new_ls(abs, allocator: allocator)!;
foreach (f : files)
{
if (f.str_view() == "." || f.str_view() == "..") continue;
f = abs.append(allocator, f.str_view())!;
f = abs.new_append(f.str_view(), allocator)!;
bool is_directory = is_dir(f);
if (w(f, is_directory, data)!) return true;
if (is_directory && f.walk(w, data)!) return true;
@@ -578,35 +567,6 @@ fn bool? Path.walk(self, PathWalker w, void* data)
return false;
}
alias TraverseCallback = fn bool? (Path, bool is_dir, any data);
<*
Walk the path recursively. TraverseCallback is run for every file and
directory found. Return true to abort the walk.
@require path.env == DEFAULT_ENV : "This method is only available on native paths"
*>
fn bool? traverse(Path path, TraverseCallback callback, any data)
{
const PATH_MAX = 512;
@stack_mem(PATH_MAX; Allocator allocator)
{
Path abs = path.absolute(allocator)!;
PathList files = ls(allocator, abs)!;
foreach (f : files)
{
if (f.str_view() == "." || f.str_view() == "..") continue;
@stack_mem(128; Allocator smem)
{
f = abs.append(smem, f.str_view())!;
bool is_directory = is_dir(f);
if (callback(f, is_directory, data)!) return true;
if (is_directory && traverse(f, callback, data)!) return true;
};
}
};
return false;
}
fn String Path.str_view(self) @inline
{
return self.path_string;
@@ -618,19 +578,26 @@ fn bool Path.has_suffix(self, String str)
return self.str_view().ends_with(str);
}
<*
@require self.allocator != null : "This Path should never be freed"
*>
fn void Path.free(self)
fn void Path.free_with_allocator(self, Allocator allocator)
{
allocator::free(self.allocator, self.path_string.ptr);
allocator::free(allocator, self.path_string.ptr);
}
fn usz? Path.to_format(&self, Formatter* formatter) @dynamic
fn void Path.free(self)
{
free(self.path_string.ptr);
}
fn usz! Path.to_format(&self, Formatter* formatter) @dynamic
{
return formatter.print(self.str_view());
}
fn String Path.to_new_string(&self, Allocator allocator = allocator::heap()) @dynamic
{
return self.str_view().copy(allocator);
}
const bool[256] RESERVED_PATH_CHAR_POSIX = {
[0] = true,
@@ -654,29 +621,9 @@ macro bool is_reserved_win32_path_char(char c)
return RESERVED_PATH_CHAR_WIN32[c];
}
macro bool is_reserved_path_char(char c, PathEnv path_env = DEFAULT_ENV)
macro bool is_reserved_path_char(char c, PathEnv path_env = DEFAULT_PATH_ENV)
{
return path_env == PathEnv.WIN32
? RESERVED_PATH_CHAR_WIN32[c]
: RESERVED_PATH_CHAR_POSIX[c];
}
fn bool? _mkdir(Path path, bool recursive = false, MkdirPermissions permissions = NORMAL) @private
{
if (!path.path_string.len) return INVALID_PATH?;
if (is_dir(path)) return false;
if (exists(path)) return io::FILE_NOT_DIR?;
if (recursive)
{
if (try parent = path.parent()) mkdir(parent, true, permissions)!;
}
if (!is_dir(path.parent()) ?? false) return io::CANNOT_READ_DIR?;
return os::native_mkdir(path, permissions);
}
fn bool? _rmdir(Path path) @private
{
if (!path.path_string.len) return INVALID_PATH?;
return os::native_rmdir(path);
}
}

View File

@@ -3,28 +3,28 @@ import std::math;
interface InStream
{
fn void? close() @optional;
fn usz? seek(isz offset, Seek seek) @optional;
fn void! close() @optional;
fn usz! seek(isz offset, Seek seek) @optional;
fn usz len() @optional;
fn usz? available() @optional;
fn usz? read(char[] buffer);
fn char? read_byte();
fn usz? write_to(OutStream out) @optional;
fn void? pushback_byte() @optional;
fn usz! available() @optional;
fn usz! read(char[] buffer);
fn char! read_byte();
fn usz! write_to(OutStream out) @optional;
fn void! pushback_byte() @optional;
}
interface OutStream
{
fn void? destroy() @optional;
fn void? close() @optional;
fn void? flush() @optional;
fn usz? write(char[] bytes);
fn void? write_byte(char c);
fn usz? read_to(InStream in) @optional;
fn void! destroy() @optional;
fn void! close() @optional;
fn void! flush() @optional;
fn usz! write(char[] bytes);
fn void! write_byte(char c);
fn usz! read_to(InStream in) @optional;
}
fn usz? available(InStream s)
fn usz! available(InStream s)
{
if (&s.available) return s.available();
if (&s.seek)
@@ -51,17 +51,17 @@ macro bool @is_outstream(#expr)
@param [&out] ref
@require @is_instream(stream)
*>
macro usz? read_any(stream, any ref)
macro usz! read_any(stream, any ref)
{
return read_all(stream, ((char*)ref)[:ref.type.sizeof]);
}
<*
@param [&in] ref : "the object to write."
@param [&in] ref "the object to write."
@require @is_outstream(stream)
@ensure return == ref.type.sizeof
*>
macro usz? write_any(stream, any ref)
macro usz! write_any(stream, any ref)
{
return write_all(stream, ((char*)ref)[:ref.type.sizeof]);
}
@@ -69,18 +69,18 @@ macro usz? write_any(stream, any ref)
<*
@require @is_instream(stream)
*>
macro usz? read_all(stream, char[] buffer)
macro usz! read_all(stream, char[] buffer)
{
if (buffer.len == 0) return 0;
usz n = stream.read(buffer)!;
if (n != buffer.len) return UNEXPECTED_EOF?;
if (n != buffer.len) return IoError.UNEXPECTED_EOF?;
return n;
}
<*
@require @is_instream(stream)
*>
macro char[]? read_fully(Allocator allocator, stream)
macro char[]! read_new_fully(stream, Allocator allocator = allocator::heap())
{
usz len = available(stream)!;
char* data = allocator::malloc_try(allocator, len)!;
@@ -96,24 +96,24 @@ macro char[]? read_fully(Allocator allocator, stream)
<*
@require @is_outstream(stream)
*>
macro usz? write_all(stream, char[] buffer)
macro usz! write_all(stream, char[] buffer)
{
if (buffer.len == 0) return 0;
usz n = stream.write(buffer)!;
if (n != buffer.len) return INCOMPLETE_WRITE?;
if (n != buffer.len) return IoError.INCOMPLETE_WRITE?;
return n;
}
macro usz? read_using_read_byte(s, char[] buffer)
macro usz! @read_using_read_byte(&s, char[] buffer)
{
usz len = 0;
foreach (&cptr : buffer)
{
char? c = s.read_byte();
char! c = s.read_byte();
if (catch err = c)
{
if (err == io::EOF) return len;
return err?;
case IoError.EOF: return len;
default: return err?;
}
*cptr = c;
len++;
@@ -121,65 +121,66 @@ macro usz? read_using_read_byte(s, char[] buffer)
return len;
}
macro void? write_byte_using_write(s, char c)
macro void! @write_byte_using_write(&s, char c)
{
char[1] buff = { c };
s.write(&buff)!;
(*s).write(&buff)!;
}
macro char? read_byte_using_read(s)
macro char! @read_byte_using_read(&s)
{
char[1] buffer;
usz read = s.read(&buffer)!;
if (read != 1) return io::EOF?;
usz read = (*s).read(&buffer)!;
if (read != 1) return IoError.EOF?;
return buffer[0];
}
alias ReadByteFn = fn char?();
def ReadByteFn = fn char!();
macro usz? write_using_write_byte(s, char[] bytes)
macro usz! @write_using_write_byte(&s, char[] bytes)
{
foreach (c : bytes) s.write_byte(self, c)!;
return bytes.len;
}
macro void? pushback_using_seek(s)
macro void! @pushback_using_seek(&s)
{
s.seek(-1, CURSOR)!;
}
fn usz? copy_to(InStream in, OutStream dst, char[] buffer = {})
fn usz! copy_to(InStream in, OutStream dst, char[] buffer = {})
{
if (buffer.len) return copy_through_buffer(in, dst, buffer);
if (&in.write_to) return in.write_to(dst);
if (&dst.read_to) return dst.read_to(in);
$switch env::MEMORY_ENV:
$switch (env::MEMORY_ENV)
$case NORMAL:
return copy_through_buffer(in, dst, &&(char[4096]){});
return copy_through_buffer(in, dst, &&char[4096]{});
$case SMALL:
return copy_through_buffer(in, dst, &&(char[1024]){});
return copy_through_buffer(in, dst, &&char[1024]{});
$case TINY:
$case NONE:
return copy_through_buffer(in, dst, &&(char[256]){});
return copy_through_buffer(in, dst, &&(char[256]{}));
$endswitch
}
macro usz? copy_through_buffer(InStream in, OutStream dst, char[] buffer) @local
macro usz! copy_through_buffer(InStream in, OutStream dst, char[] buffer) @local
{
usz total_copied;
while (true)
{
usz? len = in.read(buffer);
usz! len = in.read(buffer);
if (catch err = len)
{
if (err == io::EOF) return total_copied;
return err?;
case IoError.EOF: return total_copied;
default: return err?;
}
if (!len) return total_copied;
usz written = dst.write(buffer[:len])!;
total_copied += len;
if (written != len) return INCOMPLETE_WRITE?;
if (written != len) return IoError.INCOMPLETE_WRITE?;
}
}
@@ -189,7 +190,7 @@ const char[*] MAX_VARS @private = { [2] = 3, [4] = 5, [8] = 10 };
@require @is_instream(stream)
@require @typekind(x_ptr) == POINTER && $typeof(x_ptr).inner.kindof.is_int()
*>
macro usz? read_varint(stream, x_ptr)
macro usz! read_varint(stream, x_ptr)
{
var $Type = $typefrom($typeof(x_ptr).inner);
const MAX = MAX_VARS[$Type.sizeof];
@@ -198,11 +199,13 @@ macro usz? read_varint(stream, x_ptr)
usz n;
for (usz i = 0; i < MAX; i++)
{
char? c = stream.read_byte();
char! c = stream.read_byte();
if (catch err = c)
{
if (err == io::EOF) return io::UNEXPECTED_EOF?;
return err?;
case IoError.EOF:
return IoError.UNEXPECTED_EOF?;
default:
return err?;
}
n++;
if (c & 0x80 == 0)
@@ -218,13 +221,13 @@ macro usz? read_varint(stream, x_ptr)
x |= (c & 0x7F) << shift;
shift += 7;
}
return math::OVERFLOW?;
return MathError.OVERFLOW?;
}
<*
@require @is_outstream(stream)
@require @typekind(x).is_int()
*>
macro usz? write_varint(stream, x)
macro usz! write_varint(stream, x)
{
var $Type = $typeof(x);
const MAX = MAX_VARS[$Type.sizeof];
@@ -243,7 +246,7 @@ macro usz? write_varint(stream, x)
<*
@require @is_instream(stream)
*>
macro ushort? read_be_ushort(stream)
macro ushort! read_be_ushort(stream)
{
char hi_byte = stream.read_byte()!;
char lo_byte = stream.read_byte()!;
@@ -253,7 +256,7 @@ macro ushort? read_be_ushort(stream)
<*
@require @is_instream(stream)
*>
macro short? read_be_short(stream)
macro short! read_be_short(stream)
{
return read_be_ushort(stream);
}
@@ -261,7 +264,7 @@ macro short? read_be_short(stream)
<*
@require @is_outstream(stream)
*>
macro void? write_be_short(stream, ushort s)
macro void! write_be_short(stream, ushort s)
{
stream.write_byte((char)(s >> 8))!;
stream.write_byte((char)s)!;
@@ -270,7 +273,7 @@ macro void? write_be_short(stream, ushort s)
<*
@require @is_instream(stream)
*>
macro uint? read_be_uint(stream)
macro uint! read_be_uint(stream)
{
uint val = stream.read_byte()! << 24;
val += stream.read_byte()! << 16;
@@ -281,7 +284,7 @@ macro uint? read_be_uint(stream)
<*
@require @is_instream(stream)
*>
macro int? read_be_int(stream)
macro int! read_be_int(stream)
{
return read_be_uint(stream);
}
@@ -289,7 +292,7 @@ macro int? read_be_int(stream)
<*
@require @is_outstream(stream)
*>
macro void? write_be_int(stream, uint s)
macro void! write_be_int(stream, uint s)
{
stream.write_byte((char)(s >> 24))!;
stream.write_byte((char)(s >> 16))!;
@@ -300,7 +303,7 @@ macro void? write_be_int(stream, uint s)
<*
@require @is_instream(stream)
*>
macro ulong? read_be_ulong(stream)
macro ulong! read_be_ulong(stream)
{
ulong val = (ulong)stream.read_byte()! << 56;
val += (ulong)stream.read_byte()! << 48;
@@ -315,7 +318,7 @@ macro ulong? read_be_ulong(stream)
<*
@require @is_instream(stream)
*>
macro long? read_be_long(stream)
macro long! read_be_long(stream)
{
return read_be_ulong(stream);
}
@@ -323,7 +326,7 @@ macro long? read_be_long(stream)
<*
@require @is_outstream(stream)
*>
macro void? write_be_long(stream, ulong s)
macro void! write_be_long(stream, ulong s)
{
stream.write_byte((char)(s >> 56))!;
stream.write_byte((char)(s >> 48))!;
@@ -338,7 +341,7 @@ macro void? write_be_long(stream, ulong s)
<*
@require @is_instream(stream)
*>
macro uint128? read_be_uint128(stream)
macro uint128! read_be_uint128(stream)
{
uint128 val = (uint128)stream.read_byte()! << 120;
val += (uint128)stream.read_byte()! << 112;
@@ -361,7 +364,7 @@ macro uint128? read_be_uint128(stream)
<*
@require @is_instream(stream)
*>
macro int128? read_be_int128(stream)
macro int128! read_be_int128(stream)
{
return read_be_uint128(stream);
}
@@ -369,7 +372,7 @@ macro int128? read_be_int128(stream)
<*
@require @is_outstream(stream)
*>
macro void? write_be_int128(stream, uint128 s)
macro void! write_be_int128(stream, uint128 s)
{
stream.write_byte((char)(s >> 120))!;
stream.write_byte((char)(s >> 112))!;
@@ -391,9 +394,9 @@ macro void? write_be_int128(stream, uint128 s)
<*
@require @is_outstream(stream)
@require data.len < 256 : "Data exceeded 255"
@require data.len < 256 "Data exceeded 255"
*>
macro usz? write_tiny_bytearray(stream, char[] data)
macro usz! write_tiny_bytearray(stream, char[] data)
{
stream.write_byte((char)data.len)!;
return stream.write(data) + 1;
@@ -402,7 +405,7 @@ macro usz? write_tiny_bytearray(stream, char[] data)
<*
@require @is_instream(stream)
*>
macro char[]? read_tiny_bytearray(stream, Allocator allocator)
macro char[]! read_tiny_bytearray(stream, Allocator allocator)
{
int len = stream.read_byte()!;
if (!len) return {};
@@ -413,9 +416,9 @@ macro char[]? read_tiny_bytearray(stream, Allocator allocator)
<*
@require @is_outstream(stream)
@require data.len < 0x1000 : "Data exceeded 65535"
@require data.len < 0x1000 "Data exceeded 65535"
*>
macro usz? write_short_bytearray(stream, char[] data)
macro usz! write_short_bytearray(stream, char[] data)
{
io::write_be_short(stream, (ushort)data.len)!;
return stream.write(data) + 2;
@@ -424,7 +427,7 @@ macro usz? write_short_bytearray(stream, char[] data)
<*
@require @is_instream(stream)
*>
macro char[]? read_short_bytearray(stream, Allocator allocator)
macro char[]! read_short_bytearray(stream, Allocator allocator)
{
int len = io::read_be_ushort(stream)!;
if (!len) return {};

View File

@@ -12,7 +12,7 @@ struct ReadBuffer (InStream)
Buffer reads from a stream.
@param [inout] self
@require bytes.len > 0
@require self.bytes.len == 0 : "Init may not run on already initialized data"
@require self.bytes.len == 0 "Init may not run on already initialized data"
*>
fn ReadBuffer* ReadBuffer.init(&self, InStream wrapped_stream, char[] bytes)
{
@@ -24,12 +24,12 @@ fn String ReadBuffer.str_view(&self) @inline
return (String)self.bytes[self.read_idx:self.write_idx - self.read_idx];
}
fn void? ReadBuffer.close(&self) @dynamic
fn void! ReadBuffer.close(&self) @dynamic
{
if (&self.wrapped_stream.close) self.wrapped_stream.close()!;
}
fn usz? ReadBuffer.read(&self, char[] bytes) @dynamic
fn usz! ReadBuffer.read(&self, char[] bytes) @dynamic
{
if (self.read_idx == self.write_idx)
{
@@ -46,16 +46,16 @@ fn usz? ReadBuffer.read(&self, char[] bytes) @dynamic
return n;
}
fn char? ReadBuffer.read_byte(&self) @dynamic
fn char! ReadBuffer.read_byte(&self) @dynamic
{
if (self.read_idx == self.write_idx) self.refill()!;
if (self.read_idx == self.write_idx) return io::EOF?;
if (self.read_idx == self.write_idx) return IoError.EOF?;
char c = self.bytes[self.read_idx];
self.read_idx++;
return c;
}
fn void? ReadBuffer.refill(&self) @local @inline
fn void! ReadBuffer.refill(&self) @local @inline
{
self.read_idx = 0;
self.write_idx = self.wrapped_stream.read(self.bytes)!;
@@ -71,8 +71,8 @@ struct WriteBuffer (OutStream)
<*
Buffer writes to a stream. Call `flush` when done writing to the buffer.
@param [inout] self
@require bytes.len > 0 : "Non-empty buffer required"
@require self.bytes.len == 0 : "Init may not run on already initialized data"
@require bytes.len > 0 "Non-empty buffer required"
@require self.bytes.len == 0 "Init may not run on already initialized data"
*>
fn WriteBuffer* WriteBuffer.init(&self, OutStream wrapped_stream, char[] bytes)
{
@@ -85,18 +85,18 @@ fn String WriteBuffer.str_view(&self) @inline
return (String)self.bytes[:self.index];
}
fn void? WriteBuffer.close(&self) @dynamic
fn void! WriteBuffer.close(&self) @dynamic
{
if (&self.wrapped_stream.close) return self.wrapped_stream.close();
}
fn void? WriteBuffer.flush(&self) @dynamic
fn void! WriteBuffer.flush(&self) @dynamic
{
self.write_pending()!;
if (&self.wrapped_stream.flush) self.wrapped_stream.flush()!;
}
fn usz? WriteBuffer.write(&self, char[] bytes) @dynamic
fn usz! WriteBuffer.write(&self, char[] bytes) @dynamic
{
usz n = self.bytes.len - self.index;
if (bytes.len < n)
@@ -118,7 +118,7 @@ fn usz? WriteBuffer.write(&self, char[] bytes) @dynamic
return bytes.len;
}
fn void? WriteBuffer.write_byte(&self, char c) @dynamic
fn void! WriteBuffer.write_byte(&self, char c) @dynamic
{
usz n = self.bytes.len - self.index;
if (n == 0)
@@ -129,8 +129,8 @@ fn void? WriteBuffer.write_byte(&self, char c) @dynamic
self.index += 1;
}
fn void? WriteBuffer.write_pending(&self) @local
fn void! WriteBuffer.write_pending(&self) @local
{
self.index -= self.wrapped_stream.write(self.bytes[:self.index])!;
if (self.index != 0) return INCOMPLETE_WRITE?;
if (self.index != 0) return IoError.INCOMPLETE_WRITE?;
}

View File

@@ -14,26 +14,26 @@ struct ByteBuffer (InStream, OutStream)
<*
ByteBuffer provides a streamable read/write buffer.
max_read defines how many bytes might be kept before its internal buffer is shrinked.
@require self.bytes.len == 0 : "Buffer already initialized."
@require self.bytes.len == 0 "Buffer already initialized."
*>
fn ByteBuffer* ByteBuffer.init(&self, Allocator allocator, usz max_read, usz initial_capacity = 16)
fn ByteBuffer*! ByteBuffer.new_init(&self, usz max_read, usz initial_capacity = 16, Allocator allocator = allocator::heap())
{
*self = { .allocator = allocator, .max_read = max_read };
initial_capacity = max(initial_capacity, 16);
self.grow(initial_capacity);
self.grow(initial_capacity)!;
return self;
}
fn ByteBuffer* ByteBuffer.tinit(&self, usz max_read, usz initial_capacity = 16)
fn ByteBuffer*! ByteBuffer.temp_init(&self, usz max_read, usz initial_capacity = 16)
{
return self.init(tmem, max_read, initial_capacity);
return self.new_init(max_read, initial_capacity, allocator::temp());
}
<*
@require buf.len > 0
@require self.bytes.len == 0 : "Buffer already initialized."
@require self.bytes.len == 0 "Buffer already initialized."
*>
fn ByteBuffer* ByteBuffer.init_with_buffer(&self, char[] buf)
fn ByteBuffer*! ByteBuffer.init_with_buffer(&self, char[] buf)
{
*self = { .max_read = buf.len, .bytes = buf };
return self;
@@ -45,30 +45,30 @@ fn void ByteBuffer.free(&self)
*self = {};
}
fn usz? ByteBuffer.write(&self, char[] bytes) @dynamic
fn usz! ByteBuffer.write(&self, char[] bytes) @dynamic
{
usz cap = self.bytes.len - self.write_idx;
if (cap < bytes.len) self.grow(bytes.len);
if (cap < bytes.len) self.grow(bytes.len)!;
self.bytes[self.write_idx:bytes.len] = bytes[..];
self.write_idx += bytes.len;
return bytes.len;
}
fn void? ByteBuffer.write_byte(&self, char c) @dynamic
fn void! ByteBuffer.write_byte(&self, char c) @dynamic
{
usz cap = self.bytes.len - self.write_idx;
if (cap == 0) self.grow(1);
if (cap == 0) self.grow(1)!;
self.bytes[self.write_idx] = c;
self.write_idx++;
}
fn usz? ByteBuffer.read(&self, char[] bytes) @dynamic
fn usz! ByteBuffer.read(&self, char[] bytes) @dynamic
{
usz readable = self.write_idx - self.read_idx;
if (readable == 0)
{
self.has_last = false;
return io::EOF?;
return IoError.EOF?;
}
usz n = min(readable, bytes.len);
bytes[:n] = self.bytes[self.read_idx:n];
@@ -78,13 +78,13 @@ fn usz? ByteBuffer.read(&self, char[] bytes) @dynamic
return n;
}
fn char? ByteBuffer.read_byte(&self) @dynamic
fn char! ByteBuffer.read_byte(&self) @dynamic
{
usz readable = self.write_idx - self.read_idx;
if (readable == 0)
{
self.has_last = false;
return io::EOF?;
return IoError.EOF?;
}
char c = self.bytes[self.read_idx];
self.read_idx++;
@@ -96,42 +96,42 @@ fn char? ByteBuffer.read_byte(&self) @dynamic
<*
Only the last byte of a successful read can be pushed back.
*>
fn void? ByteBuffer.pushback_byte(&self) @dynamic
fn void! ByteBuffer.pushback_byte(&self) @dynamic
{
if (!self.has_last) return io::EOF?;
if (!self.has_last) return IoError.EOF?;
assert(self.read_idx > 0);
self.read_idx--;
self.has_last = false;
}
fn usz? ByteBuffer.seek(&self, isz offset, Seek seek) @dynamic
fn usz! ByteBuffer.seek(&self, isz offset, Seek seek) @dynamic
{
switch (seek)
{
case SET:
if (offset < 0 || offset > self.write_idx) return INVALID_POSITION?;
if (offset < 0 || offset > self.write_idx) return IoError.INVALID_POSITION?;
self.read_idx = offset;
return offset;
case CURSOR:
if ((offset < 0 && self.read_idx < -offset) ||
(offset > 0 && self.read_idx + offset > self.write_idx)) return INVALID_POSITION?;
(offset > 0 && self.read_idx + offset > self.write_idx)) return IoError.INVALID_POSITION?;
self.read_idx += offset;
case END:
if (offset < 0 || offset > self.write_idx) return INVALID_POSITION?;
if (offset < 0 || offset > self.write_idx) return IoError.INVALID_POSITION?;
self.read_idx = self.write_idx - offset;
}
return self.read_idx;
}
fn usz? ByteBuffer.available(&self) @inline @dynamic
fn usz! ByteBuffer.available(&self) @inline @dynamic
{
return self.write_idx - self.read_idx;
}
fn void ByteBuffer.grow(&self, usz n)
fn void! ByteBuffer.grow(&self, usz n)
{
n = math::next_power_of_2(n);
char* p = allocator::realloc(self.allocator, self.bytes, n);
char* p = allocator::realloc_aligned(self.allocator, self.bytes, n, alignment: char.alignof)!;
self.bytes = p[:n];
}

View File

@@ -17,9 +17,9 @@ fn ByteReader* ByteReader.init(&self, char[] bytes)
return self;
}
fn usz? ByteReader.read(&self, char[] bytes) @dynamic
fn usz! ByteReader.read(&self, char[] bytes) @dynamic
{
if (self.index >= self.bytes.len) return io::EOF?;
if (self.index >= self.bytes.len) return IoError.EOF?;
usz len = min(self.bytes.len - self.index, bytes.len);
if (len == 0) return 0;
mem::copy(bytes.ptr, &self.bytes[self.index], len);
@@ -27,19 +27,19 @@ fn usz? ByteReader.read(&self, char[] bytes) @dynamic
return len;
}
fn char? ByteReader.read_byte(&self) @dynamic
fn char! ByteReader.read_byte(&self) @dynamic
{
if (self.index >= self.bytes.len) return io::EOF?;
if (self.index >= self.bytes.len) return IoError.EOF?;
return self.bytes[self.index++];
}
fn void? ByteReader.pushback_byte(&self) @dynamic
fn void! ByteReader.pushback_byte(&self) @dynamic
{
if (!self.index) return INVALID_PUSHBACK?;
if (!self.index) return IoError.INVALID_PUSHBACK?;
self.index--;
}
fn usz? ByteReader.seek(&self, isz offset, Seek seek) @dynamic
fn usz! ByteReader.seek(&self, isz offset, Seek seek) @dynamic
{
isz new_index;
switch (seek)
@@ -48,12 +48,12 @@ fn usz? ByteReader.seek(&self, isz offset, Seek seek) @dynamic
case CURSOR: new_index = self.index + offset;
case END: new_index = self.bytes.len + offset;
}
if (new_index < 0) return INVALID_POSITION?;
if (new_index < 0) return IoError.INVALID_POSITION?;
self.index = new_index;
return new_index;
}
fn usz? ByteReader.write_to(&self, OutStream writer) @dynamic
fn usz! ByteReader.write_to(&self, OutStream writer) @dynamic
{
if (self.index >= self.bytes.len) return 0;
usz written = writer.write(self.bytes[self.index..])!;
@@ -62,7 +62,7 @@ fn usz? ByteReader.write_to(&self, OutStream writer) @dynamic
return written;
}
fn usz? ByteReader.available(&self) @inline @dynamic
fn usz! ByteReader.available(&self) @inline @dynamic
{
return max(0, self.bytes.len - self.index);
}

View File

@@ -11,10 +11,10 @@ struct ByteWriter (OutStream)
<*
@param [&inout] self
@param [&inout] allocator
@require self.bytes.len == 0 : "Init may not run on already initialized data"
@require self.bytes.len == 0 "Init may not run on already initialized data"
@ensure (bool)allocator, self.index == 0
*>
fn ByteWriter* ByteWriter.init(&self, Allocator allocator)
fn ByteWriter* ByteWriter.new_init(&self, Allocator allocator = allocator::heap())
{
*self = { .bytes = {}, .allocator = allocator };
return self;
@@ -22,12 +22,12 @@ fn ByteWriter* ByteWriter.init(&self, Allocator allocator)
<*
@param [&inout] self
@require self.bytes.len == 0 : "Init may not run on already initialized data"
@require self.bytes.len == 0 "Init may not run on already initialized data"
@ensure self.index == 0
*>
fn ByteWriter* ByteWriter.tinit(&self)
fn ByteWriter* ByteWriter.temp_init(&self)
{
return self.init(tmem) @inline;
return self.new_init(allocator::temp()) @inline;
}
fn ByteWriter* ByteWriter.init_with_buffer(&self, char[] data)
@@ -36,7 +36,7 @@ fn ByteWriter* ByteWriter.init_with_buffer(&self, char[] data)
return self;
}
fn void? ByteWriter.destroy(&self) @dynamic
fn void! ByteWriter.destroy(&self) @dynamic
{
if (!self.allocator) return;
if (void* ptr = self.bytes.ptr) allocator::free(self.allocator, ptr);
@@ -48,17 +48,17 @@ fn String ByteWriter.str_view(&self) @inline
return (String)self.bytes[:self.index];
}
fn void? ByteWriter.ensure_capacity(&self, usz len) @inline
fn void! ByteWriter.ensure_capacity(&self, usz len) @inline
{
if (self.bytes.len > len) return;
if (!self.allocator) return OUT_OF_SPACE?;
if (!self.allocator) return IoError.OUT_OF_SPACE?;
if (len < 16) len = 16;
usz new_capacity = math::next_power_of_2(len);
char* new_ptr = allocator::realloc_try(self.allocator, self.bytes.ptr, new_capacity)!;
self.bytes = new_ptr[:new_capacity];
}
fn usz? ByteWriter.write(&self, char[] bytes) @dynamic
fn usz! ByteWriter.write(&self, char[] bytes) @dynamic
{
self.ensure_capacity(self.index + bytes.len)!;
mem::copy(&self.bytes[self.index], bytes.ptr, bytes.len);
@@ -66,7 +66,7 @@ fn usz? ByteWriter.write(&self, char[] bytes) @dynamic
return bytes.len;
}
fn void? ByteWriter.write_byte(&self, char c) @dynamic
fn void! ByteWriter.write_byte(&self, char c) @dynamic
{
self.ensure_capacity(self.index + 1)!;
self.bytes[self.index++] = c;
@@ -76,7 +76,7 @@ fn void? ByteWriter.write_byte(&self, char c) @dynamic
@param [&inout] self
@param reader
*>
fn usz? ByteWriter.read_from(&self, InStream reader) @dynamic
fn usz! ByteWriter.read_from(&self, InStream reader) @dynamic
{
usz start_index = self.index;
if (&reader.available)

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