Compare commits

..

1 Commits

Author SHA1 Message Date
Christoffer Lerno
3348f4dc7d Test of overloading. 2022-09-15 22:31:31 +02:00
1672 changed files with 83674 additions and 116688 deletions

14
.github/FUNDING.yml vendored
View File

@@ -1,14 +0,0 @@
# These are supported funding model platforms
github: [c3lang]
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
polar: # Replace with a single Polar username
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']

View File

@@ -2,12 +2,12 @@ name: CI
on:
push:
branches: [ master, dev, ci_testing, experiments ]
branches: [ master, dev, ci_testing ]
pull_request:
branches: [ master ]
env:
LLVM_RELEASE_VERSION: 16
LLVM_RELEASE_VERSION: 14
jobs:
@@ -23,7 +23,7 @@ jobs:
run:
shell: cmd
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v3
- name: CMake
run: |
@@ -33,57 +33,25 @@ jobs:
- name: Compile and run some examples
run: |
cd resources
..\build\${{ matrix.build_type }}\c3c.exe compile-run --print-linking examples\hello_world_many.c3
..\build\${{ matrix.build_type }}\c3c.exe compile-run --print-linking examples\time.c3
..\build\${{ matrix.build_type }}\c3c.exe compile-run --print-linking examples\fannkuch-redux.c3
..\build\${{ matrix.build_type }}\c3c.exe compile-run examples\hello_world_many.c3
..\build\${{ matrix.build_type }}\c3c.exe compile-run examples\fannkuch-redux.c3
..\build\${{ matrix.build_type }}\c3c.exe compile-run examples\contextfree\boolerr.c3
..\build\${{ matrix.build_type }}\c3c.exe compile-run examples\ls.c3
..\build\${{ matrix.build_type }}\c3c.exe compile-run examples\load_world.c3
..\build\${{ matrix.build_type }}\c3c.exe compile-run examples\process.c3
..\build\${{ matrix.build_type }}\c3c.exe compile --test -g -O0 --threads 1 --target macos-x64 examples\constants.c3
..\build\${{ matrix.build_type }}\c3c.exe compile-run msvc_stack.c3
- name: Build testproject
run: |
cd resources/testproject
..\..\build\${{ matrix.build_type }}\c3c.exe --debug-log --emit-llvm run hello_world_win32
dir build\llvm_ir
..\..\build\${{ matrix.build_type }}\c3c.exe clean
dir build\llvm_ir
..\..\build\${{ matrix.build_type }}\c3c.exe --debug-log run hello_world_win32
- name: Build testproject lib
run: |
cd resources/testproject
..\..\build\${{ matrix.build_type }}\c3c.exe --debug-log build hello_world_win32_lib
- name: Vendor-fetch
run: |
build\${{ matrix.build_type }}\c3c.exe vendor-fetch raylib
- name: Try raylib
run: |
cd resources
..\build\${{ matrix.build_type }}\c3c.exe vendor-fetch raylib
..\build\${{ matrix.build_type }}\c3c.exe compile --lib raylib --wincrt=none examples\raylib\raylib_arkanoid.c3
..\build\${{ matrix.build_type }}\c3c.exe compile --lib raylib --wincrt=none examples\raylib\raylib_snake.c3
..\build\${{ matrix.build_type }}\c3c.exe compile --lib raylib --wincrt=none 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: Test python script
run: |
py msvc_build_libraries.py --accept-license
dir msvc_sdk
- name: upload artifacts
uses: actions/upload-artifact@v3
with:
@@ -92,7 +60,6 @@ jobs:
build-msys2-mingw:
runs-on: windows-latest
# if: ${{ false }}
strategy:
# Don't abort runners if a single one fails
fail-fast: false
@@ -103,41 +70,34 @@ jobs:
run:
shell: msys2 {0}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v3
- uses: msys2/setup-msys2@v2
with:
msystem: MINGW64
update: true
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
install: git binutils 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-18.1.6-1-any.pkg.tar.zst
pacman --noconfirm -U https://mirror.msys2.org/mingw/mingw64/mingw-w64-x86_64-lld-18.1.6-1-any.pkg.tar.zst
pacman --noconfirm -U https://mirror.msys2.org/mingw/mingw64/mingw-w64-x86_64-llvm-13.0.1-2-any.pkg.tar.zst
pacman --noconfirm -U https://mirror.msys2.org/mingw/mingw64/mingw-w64-x86_64-lld-13.0.1-2-any.pkg.tar.zst
- name: CMake
run: |
cmake -B build -G Ninja -DCMAKE_C_COMPILER=clang -DCMAKE_BUILD_TYPE=${{ matrix.build_type }}
cmake -B build -G Ninja -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/time.c3
../build/c3c compile-run --print-linking examples/fannkuch-redux.c3
../build/c3c compile-run --print-linking examples/contextfree/boolerr.c3
../build/c3c compile-run --print-linking examples/load_world.c3
../build/c3c compile --test -g -O0 --threads 1 --target macos-x64 examples/constants.c3
../build/c3c compile-run examples/hello_world_many.c3
../build/c3c compile-run examples/fannkuch-redux.c3
../build/c3c compile-run examples/contextfree/boolerr.c3
- name: Build testproject
run: |
cd resources/testproject
../../build/c3c run --debug-log
- name: Vendor-fetch
run: |
./build/c3c vendor-fetch raylib
- name: Build testproject lib
run: |
cd resources/testproject
@@ -151,7 +111,7 @@ jobs:
build-msys2-clang:
runs-on: windows-latest
#if: ${{ false }}
if: ${{ false }}
strategy:
# Don't abort runners if a single one fails
fail-fast: false
@@ -162,7 +122,7 @@ jobs:
run:
shell: msys2 {0}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v3
- uses: msys2/setup-msys2@v2
with:
@@ -179,11 +139,9 @@ jobs:
run: |
cd resources
../build/c3c compile-run examples/hello_world_many.c3
../build/c3c compile-run examples/time.c3
../build/c3c compile-run examples/fannkuch-redux.c3
../build/c3c compile-run examples/contextfree/boolerr.c3
../build/c3c compile-run examples/load_world.c3
../build/c3c compile --test -g -O0 --threads 1 --target macos-x64 examples/constants.c3
- name: Build testproject
run: |
cd resources/testproject
@@ -206,36 +164,30 @@ jobs:
fail-fast: false
matrix:
build_type: [Release, Debug]
llvm_version: [15, 16, 17, 18, 19]
llvm_version: [12, 13, 14, 15, 16]
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v3
- name: Install common deps
run: |
sudo apt-get install zlib1g zlib1g-dev python3 ninja-build curl
sudo apt-get install zlib1g zlib1g-dev python3 ninja-build
- name: Install Clang ${{matrix.llvm_version}}
run: |
wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add -
if [[ "${{matrix.llvm_version}}" < 16 ]]; then
sudo apt remove libllvm15
fi
if [[ "${{matrix.llvm_version}}" < 19 ]]; then
sudo add-apt-repository "deb http://apt.llvm.org/focal/ llvm-toolchain-focal-${{matrix.llvm_version}} main"
sudo apt-get update
sudo apt-get install -y -t llvm-toolchain-focal-${{matrix.llvm_version}} libpolly-${{matrix.llvm_version}}-dev \
clang-${{matrix.llvm_version}} llvm-${{matrix.llvm_version}} llvm-${{matrix.llvm_version}}-dev \
lld-${{matrix.llvm_version}} liblld-${{matrix.llvm_version}}-dev libmlir-${{matrix.llvm_version}} \
libmlir-${{matrix.llvm_version}}-dev mlir-${{matrix.llvm_version}}-tools
else
sudo add-apt-repository "deb http://apt.llvm.org/focal/ llvm-toolchain-focal main"
sudo apt-get install -y -t llvm-toolchain-focal libpolly-${{matrix.llvm_version}}-dev \
clang-${{matrix.llvm_version}} llvm-${{matrix.llvm_version}} llvm-${{matrix.llvm_version}}-dev \
lld-${{matrix.llvm_version}} liblld-${{matrix.llvm_version}}-dev libmlir-${{matrix.llvm_version}} \
libmlir-${{matrix.llvm_version}}-dev mlir-${{matrix.llvm_version}}-tools
fi
sudo apt-get update
sudo apt-get install -y clang-${{matrix.llvm_version}} llvm-${{matrix.llvm_version}} llvm-${{matrix.llvm_version}}-dev lld-${{matrix.llvm_version}} liblld-${{matrix.llvm_version}}-dev
if [[ "${{matrix.llvm_version}}" > 12 ]]; then
sudo apt-get install -y libmlir-${{matrix.llvm_version}} libmlir-${{matrix.llvm_version}}-dev mlir-${{matrix.llvm_version}}-tools
fi
- name: CMake
if: matrix.llvm_version != 18
run: |
cmake -B build \
-G Ninja \
@@ -248,58 +200,13 @@ jobs:
-DCMAKE_DLLTOOL=llvm-dlltool-${{matrix.llvm_version}} \
-DC3_LLVM_VERSION=${{matrix.llvm_version}}
cmake --build build
- name: CMake18
if: matrix.llvm_version == 18
run: |
cmake -B build \
-G Ninja \
-DCMAKE_BUILD_TYPE=${{matrix.build_type}} \
-DCMAKE_C_COMPILER=clang-${{matrix.llvm_version}} \
-DCMAKE_CXX_COMPILER=clang++-${{matrix.llvm_version}} \
-DCMAKE_LINKER=lld-link-${{matrix.llvm_version}} \
-DCMAKE_OBJCOPY=llvm-objcopy-${{matrix.llvm_version}} \
-DCMAKE_STRIP=llvm-strip-${{matrix.llvm_version}} \
-DCMAKE_DLLTOOL=llvm-dlltool-${{matrix.llvm_version}} \
-DC3_LLVM_VERSION=18.1
cmake --build build
- name: Compile and run some examples
run: |
cd resources
../build/c3c compile examples/base64.c3
../build/c3c compile examples/binarydigits.c3
../build/c3c compile examples/brainfk.c3
../build/c3c compile examples/factorial_macro.c3
../build/c3c compile examples/fasta.c3
../build/c3c compile examples/gameoflife.c3
../build/c3c compile examples/hash.c3
../build/c3c compile examples/levenshtein.c3
../build/c3c compile examples/load_world.c3
../build/c3c compile examples/map.c3
../build/c3c compile examples/mandelbrot.c3
../build/c3c compile examples/plus_minus.c3
../build/c3c compile examples/nbodies.c3
../build/c3c compile examples/spectralnorm.c3
../build/c3c compile examples/swap.c3
../build/c3c compile examples/contextfree/boolerr.c3
../build/c3c compile examples/contextfree/dynscope.c3
../build/c3c compile examples/contextfree/guess_number.c3
../build/c3c compile examples/contextfree/multi.c3
../build/c3c compile examples/contextfree/cleanup.c3
cd resources
../build/c3c compile-run examples/hello_world_many.c3
../build/c3c compile-run examples/time.c3
../build/c3c compile-run examples/fannkuch-redux.c3
../build/c3c compile-run examples/contextfree/boolerr.c3
../build/c3c compile-run examples/load_world.c3
../build/c3c compile-run examples/process.c3
../build/c3c compile-run examples/ls.c3
../build/c3c compile-run --linker=builtin linux_stack.c3
../build/c3c compile-run linux_stack.c3
- name: Compile run unit tests
run: |
cd test
../build/c3c compile-test unit
- name: Build testproject
run: |
@@ -309,15 +216,19 @@ jobs:
- name: Build testproject direct linker
run: |
cd resources/testproject
../../build/c3c run --debug-log --linker=builtin
../../build/c3c run --debug-log --forcelinker
- name: run compiler tests
run: |
cd test
python3 src/tester.py ../build/c3c test_suite/
if [[ "${{matrix.llvm_version}}" < 15 ]]; then
python3 src/tester.py ../build/c3c test_suite/
else
python3 src/tester.py ../build/c3c test_suite2/
fi
- name: bundle_output
if: matrix.llvm_version == 16
if: matrix.llvm_version == env.LLVM_RELEASE_VERSION
run: |
mkdir linux
cp -r lib linux
@@ -326,118 +237,12 @@ jobs:
tar czf c3-linux-${{matrix.build_type}}.tar.gz linux
- name: upload artifacts
if: matrix.llvm_version == 16
if: matrix.llvm_version == env.LLVM_RELEASE_VERSION
uses: actions/upload-artifact@v3
with:
name: c3-linux-${{matrix.build_type}}
path: c3-linux-${{matrix.build_type}}.tar.gz
build-linux-ubuntu20:
runs-on: ubuntu-20.04
strategy:
# Don't abort runners if a single one fails
fail-fast: false
matrix:
build_type: [Release, Debug]
llvm_version: [16]
steps:
- uses: actions/checkout@v4
- name: Install common deps
run: |
sudo apt-get install zlib1g zlib1g-dev python3 ninja-build curl
- name: Install Clang ${{matrix.llvm_version}}
run: |
wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add -
if [[ "${{matrix.llvm_version}}" < 17 ]]; then
sudo add-apt-repository "deb http://apt.llvm.org/focal/ llvm-toolchain-focal-${{matrix.llvm_version}} main"
else
sudo add-apt-repository "deb http://apt.llvm.org/focal/ llvm-toolchain-focal main"
fi
sudo apt-get update
sudo apt-get install -y clang-${{matrix.llvm_version}} llvm-${{matrix.llvm_version}} llvm-${{matrix.llvm_version}}-dev lld-${{matrix.llvm_version}} liblld-${{matrix.llvm_version}}-dev
sudo apt-get install -y libmlir-${{matrix.llvm_version}} libmlir-${{matrix.llvm_version}}-dev mlir-${{matrix.llvm_version}}-tools
sudo apt-get install -y libpolly-${{matrix.llvm_version}}-dev
- name: CMake
run: |
cmake -B build \
-G Ninja \
-DCMAKE_BUILD_TYPE=${{matrix.build_type}} \
-DCMAKE_C_COMPILER=clang-${{matrix.llvm_version}} \
-DCMAKE_CXX_COMPILER=clang++-${{matrix.llvm_version}} \
-DCMAKE_LINKER=lld-link-${{matrix.llvm_version}} \
-DCMAKE_OBJCOPY=llvm-objcopy-${{matrix.llvm_version}} \
-DCMAKE_STRIP=llvm-strip-${{matrix.llvm_version}} \
-DCMAKE_DLLTOOL=llvm-dlltool-${{matrix.llvm_version}} \
-DC3_LLVM_VERSION=${{matrix.llvm_version}}
cmake --build build
- name: Compile and run some examples
run: |
cd resources
../build/c3c compile examples/gameoflife.c3
../build/c3c compile examples/levenshtein.c3
../build/c3c compile examples/map.c3
../build/c3c compile examples/mandelbrot.c3
../build/c3c compile examples/plus_minus.c3
../build/c3c compile examples/spectralnorm.c3
../build/c3c compile examples/swap.c3
../build/c3c compile examples/contextfree/guess_number.c3
../build/c3c compile-run examples/hash.c3
../build/c3c compile-run examples/nbodies.c3
../build/c3c compile-run examples/contextfree/boolerr.c3
../build/c3c compile-run examples/contextfree/dynscope.c3
../build/c3c compile-run examples/contextfree/multi.c3
../build/c3c compile-run examples/contextfree/cleanup.c3
../build/c3c compile-run examples/hello_world_many.c3
../build/c3c compile-run examples/time.c3
../build/c3c compile-run examples/fannkuch-redux.c3
../build/c3c compile-run examples/load_world.c3
../build/c3c compile-run examples/base64.c3
../build/c3c compile-run examples/binarydigits.c3
../build/c3c compile-run examples/brainfk.c3
../build/c3c compile-run examples/factorial_macro.c3
../build/c3c compile-run examples/fasta.c3
../build/c3c compile-run examples/process.c3
../build/c3c compile-run --linker=builtin linux_stack.c3
../build/c3c compile-run linux_stack.c3
- name: Compile run unit tests
run: |
cd test
../build/c3c compile-test unit
- name: Build testproject
run: |
cd resources/testproject
../../build/c3c run --debug-log
- name: Build testproject direct linker
run: |
cd resources/testproject
../../build/c3c run --debug-log --linker=builtin
- name: run compiler tests
run: |
cd test
python3 src/tester.py ../build/c3c test_suite/
- name: bundle_output
if: matrix.llvm_version == 16
run: |
mkdir linux
cp -r lib linux
cp msvc_build_libraries.py linux
cp build/c3c linux
tar czf c3-ubuntu-20-${{matrix.build_type}}.tar.gz linux
- name: upload artifacts
if: matrix.llvm_version == 16
uses: actions/upload-artifact@v3
with:
name: c3-ubuntu-20-${{matrix.build_type}}
path: c3-ubuntu-20-${{matrix.build_type}}.tar.gz
build-mac:
runs-on: macos-latest
@@ -446,13 +251,13 @@ jobs:
fail-fast: false
matrix:
build_type: [Release, Debug]
llvm_version: [15, 16, 17]
llvm_version: [12, 13, 14]
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v3
- name: Download LLVM
run: |
brew install llvm@${{ matrix.llvm_version }} ninja curl
echo "/opt/homebrew/opt/llvm@${{ matrix.llvm_version }}/bin" >> $GITHUB_PATH
brew install llvm@${{ matrix.llvm_version }} botan ninja
echo "/usr/local/opt/llvm@${{ matrix.llvm_version }}/bin" >> $GITHUB_PATH
TMP_PATH=$(xcrun --show-sdk-path)/user/include
echo "CPATH=$TMP_PATH" >> $GITHUB_ENV
@@ -461,25 +266,12 @@ jobs:
cmake -B build -G Ninja -DC3_LLVM_VERSION=${{matrix.llvm_version}} -DCMAKE_BUILD_TYPE=${{ matrix.build_type }}
cmake --build build
- name: Vendor-fetch
run: |
./build/c3c vendor-fetch raylib
- name: Compile and run some examples
run: |
cd resources
../build/c3c compile-run examples/hello_world_many.c3
../build/c3c compile-run examples/time.c3
../build/c3c compile-run examples/fannkuch-redux.c3
../build/c3c compile-run examples/contextfree/boolerr.c3
../build/c3c compile-run examples/process.c3
../build/c3c compile-run examples/load_world.c3
../build/c3c compile-run -O5 examples/load_world.c3
- name: Compile run unit tests
run: |
cd test
../build/c3c compile-test unit
- name: Build testproject
run: |
@@ -489,7 +281,7 @@ jobs:
- name: Build testproject direct linker
run: |
cd resources/testproject
../../build/c3c run --debug-log --linker=builtin
../../build/c3c run --debug-log --forcelinker
- name: Build testproject lib
run: |
@@ -524,7 +316,7 @@ jobs:
if: github.ref == 'refs/heads/master'
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v3
- name: delete tag
continue-on-error: true
uses: actions/github-script@v6
@@ -552,6 +344,8 @@ jobs:
- 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 install_win_reqs.bat c3-windows-Release
- run: cp install_win_reqs.bat c3-windows-Debug
- run: zip -r c3-windows-Release.zip c3-windows-Release
- run: zip -r c3-windows-Debug.zip c3-windows-Debug
@@ -605,27 +399,6 @@ jobs:
asset_name: c3-linux-debug.tar.gz
asset_content_type: application/gzip
- name: upload ubuntu 20
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: c3-ubuntu-20-Release/c3-ubuntu-20-Release.tar.gz
asset_name: c3-ubuntu-20.tar.gz
asset_content_type: application/gzip
- name: upload ubuntu 20 debug
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: c3-ubuntu-20-Debug/c3-ubuntu-20-Debug.tar.gz
asset_name: c3-ubuntu-20-debug.tar.gz
asset_content_type: application/gzip
- name: upload macos
uses: actions/upload-release-asset@v1
env:

3
.gitmodules vendored
View File

@@ -1,3 +0,0 @@
[submodule "tilde-backend"]
path = tilde-backend
url = https://github.com/c3lang/tilde-backend

View File

@@ -1,61 +1,32 @@
cmake_minimum_required(VERSION 3.15)
# Grab the version
file(READ "src/version.h" ver)
if (NOT ${ver} MATCHES "COMPILER_VERSION \"([0-9]+.[0-9]+.[0-9]+)\"")
message(FATAL_ERROR "Compiler version could not be parsed from version.h")
endif()
# Set the project and version
project(c3c VERSION ${CMAKE_MATCH_1})
message("C3C version: ${CMAKE_PROJECT_VERSION}")
if (NOT DEFINED CMAKE_INSTALL_LIBDIR)
if (MSVC)
set(CMAKE_INSTALL_LIBDIR "c:\\c3c\\lib")
set(CMAKE_INSTALL_BINDIR "c:\\c3c")
else ()
set(CMAKE_INSTALL_LIBDIR "/usr/local/lib/c3")
set(CMAKE_INSTALL_BINDIR "/usr/local/bin/c3c")
endif()
endif ()
# Enable fetching (for Windows)
project(c3c)
include(FetchContent)
include(FeatureSummary)
set(CMAKE_FIND_PACKAGE_SORT_ORDER NATURAL)
set(CMAKE_FIND_PACKAGE_SORT_DIRECTION DEC)
# We use C11 and C++17
set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 17)
if(MSVC)
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /O2 /EHsc")
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} /O2 /EHsc")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /Od /Zi /EHa")
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} /Od /Zi /EHa")
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /O2")
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} /O2")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /Od /Zi")
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} /Od /Zi")
else()
if (true)
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3 -fno-exceptions")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -gdwarf-3 -fno-exceptions")
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -gdwarf-3 -O3 -fno-exceptions")
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -gdwarf-3 -fno-exceptions")
else()
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -gdwarf-3 -O3 -fsanitize=undefined,address -fno-exceptions")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -gdwarf-3 -O1 -fsanitize=undefined,address -fno-exceptions")
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -gdwarf-3 -O3 -fsanitize=undefined,address -fno-exceptions")
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -gdwarf-3 -O1 -fsanitize=undefined,address -fno-exceptions")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=undefined,address -fno-exceptions")
endif()
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3")
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -gdwarf-3 -O3")
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -gdwarf-3")
endif()
#set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O1 -fsanitize=undefined")
#set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -O1 -fsanitize=undefined")
#set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -gdwarf-3 -O3 -fsanitize=undefined")
#set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -gdwarf-3 -fsanitize=undefined")
#set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=undefined")
option(C3_LINK_DYNAMIC "link dynamically with LLVM/LLD libs")
option(C3_USE_TB "Enable TB" OFF)
set(C3_LLVM_VERSION "auto" CACHE STRING "Use LLVM version [default: auto]")
option(C3_USE_MIMALLOC "Use built-in mimalloc" OFF)
option(C3_USE_TB "Use TB" OFF)
set(C3_MIMALLOC_TAG "v1.7.3" CACHE STRING "Used version of mimalloc")
set(C3_USE_MIMALLOC OFF)
@@ -71,41 +42,24 @@ if(C3_USE_MIMALLOC)
)
FetchContent_MakeAvailable(mimalloc)
endif()
if (NOT WIN32)
find_package(CURL)
endif()
if (NOT C3_LLVM_VERSION STREQUAL "auto")
if (${C3_LLVM_VERSION} VERSION_LESS 15 OR ${C3_LLVM_VERSION} VERSION_GREATER 19)
message(FATAL_ERROR "LLVM ${C3_LLVM_VERSION} is not supported!")
endif()
endif()
find_package(Git QUIET)
if(C3_USE_TB AND GIT_FOUND AND EXISTS "${CMAKE_SOURCE_DIR}/.git")
# Update submodules as needed
option(GIT_SUBMODULE "Check submodules during build" ON)
if(GIT_SUBMODULE)
message(STATUS "Submodule update")
execute_process(COMMAND ${GIT_EXECUTABLE} submodule update --init --recursive
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
RESULT_VARIABLE GIT_SUBMOD_RESULT)
if(NOT GIT_SUBMOD_RESULT EQUAL "0")
message(FATAL_ERROR "git submodule update --init --recursive failed with ${GIT_SUBMOD_RESULT}, please checkout submodules")
endif()
if (NOT C3_LLVM_VERSION STREQUAL "auto")
if (${C3_LLVM_VERSION} VERSION_LESS 12 OR ${C3_LLVM_VERSION} VERSION_GREATER 16)
message(FATAL_ERROR "LLVM ${C3_LLVM_VERSION} is not supported!")
endif()
endif()
if(CMAKE_C_COMPILER_ID STREQUAL "MSVC")
if (C3_LLVM_VERSION STREQUAL "auto")
set(C3_LLVM_VERSION "16")
set(C3_LLVM_VERSION "14")
endif()
FetchContent_Declare(
LLVM_Windows
URL https://github.com/c3lang/win-llvm/releases/download/llvm_16_0_2/llvm-16.0.2-windows-amd64-msvc17-libcmt.7z
URL https://github.com/c3lang/win-llvm/releases/download/llvm1406/llvm-14.0.6-windows-amd64-msvc17-libcmt.7z
)
FetchContent_Declare(
LLVM_Windows_debug
URL https://github.com/c3lang/win-llvm/releases/download/llvm_16_0_2/llvm-16.0.2-windows-amd64-msvc17-libcmt-dbg.7z
URL https://github.com/c3lang/win-llvm/releases/download/llvm1406/llvm-14.0.6-windows-amd64-msvc17-libcmt-dbg.7z
)
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
message("Loading Windows LLVM debug libraries, this may take a while...")
@@ -129,96 +83,82 @@ endif()
message(STATUS "Found LLVM ${LLVM_PACKAGE_VERSION}")
message(STATUS "Using LLVMConfig.cmake in: ${LLVM_DIR}")
message(STATUS "Libraries located in: ${LLVM_LIBRARY_DIRS}")
if(LLVM_ENABLE_RTTI)
message(STATUS "LLVM was built with RTTI")
else()
message(STATUS "LLVM was not built with RTTI")
endif()
include_directories(${LLVM_INCLUDE_DIRS})
link_directories(${LLVM_LIBRARY_DIRS})
add_definitions(${LLVM_DEFINITIONS})
if(NOT C3_LINK_DYNAMIC)
set(LLVM_LINK_COMPONENTS
AllTargetsAsmParsers
AllTargetsCodeGens
AllTargetsDescs
AllTargetsDisassemblers
AllTargetsInfos
Analysis
AsmPrinter
BitReader
Core
DebugInfoPDB
InstCombine
IrReader
LibDriver
Linker
LTO
MC
MCDisassembler
native
nativecodegen
Object
Option
ScalarOpts
Support
Target
TransformUtils
WindowsManifest
WindowsDriver
)
set(LLVM_LINK_COMPONENTS
AllTargetsAsmParsers
AllTargetsCodeGens
AllTargetsDescs
AllTargetsDisassemblers
AllTargetsInfos
Analysis
AsmPrinter
BitReader
Core
DebugInfoPDB
InstCombine
IrReader
LibDriver
Linker
LTO
MC
MCDisassembler
native
nativecodegen
Object
Option
ScalarOpts
Support
Target
TransformUtils
WindowsManifest
)
llvm_map_components_to_libnames(llvm_libs ${LLVM_LINK_COMPONENTS})
# These don't seem to be reliable on windows.
message(STATUS "using find_library")
find_library(LLD_COFF NAMES lldCOFF.lib lldCOFF.a liblldCOFF.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
find_library(LLD_COMMON NAMES lldCommon.lib lldCommon.a liblldCommon.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
find_library(LLD_ELF NAMES lldELF.lib lldELF.a liblldELF.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
find_library(LLD_MACHO NAMES lldMachO.lib lldMachO.a liblldMachO.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
find_library(LLD_MINGW NAMES lldMinGW.lib lldMinGW.a liblldMinGW.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
find_library(LLD_WASM NAMES lldWasm.lib lldWasm.a liblldWasm.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
else()
find_library(LLVM NAMES libLLVM.so PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
set(llvm_libs ${LLVM})
# These don't seem to be reliable on windows.
message(STATUS "using find_library")
find_library(LLD_COFF NAMES liblldCOFF.so PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
find_library(LLD_COMMON NAMES liblldCommon.so PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
find_library(LLD_ELF NAMES liblldELF.so PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
find_library(LLD_MACHO NAMES liblldMachO.so PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
find_library(LLD_MINGW NAMES liblldMinGW.so PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
find_library(LLD_WASM NAMES liblldWasm.so PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
if (${LLVM_PACKAGE_VERSION} VERSION_GREATER 14.1)
set(LLVM_LINK_COMPONENTS ${LLVM_LINK_COMPONENTS} WindowsDriver)
endif()
file(REMOVE_RECURSE ${CMAKE_BINARY_DIR}/lib)
llvm_map_components_to_libnames(llvm_libs ${LLVM_LINK_COMPONENTS})
file(COPY ${CMAKE_SOURCE_DIR}/lib DESTINATION ${CMAKE_BINARY_DIR})
if (${LLVM_PACKAGE_VERSION} VERSION_GREATER_EQUAL 16)
find_library(LLD_LOONG NAMES libLLVMLoongArchCodeGen.lib libLLVMLoongArchAsmParser.lib libLLVMLoongArchCodeGen.a libLLVMLoongArchAsmParser.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
set(lld_libs
${LLD_COFF}
${LLD_COMMON}
${LLD_WASM}
${LLD_MINGW}
${LLD_ELF}
${LLD_MACHO}
)
else()
set(lld_libs
${LLD_COFF}
${LLD_COMMON}
${LLD_WASM}
${LLD_MINGW}
${LLD_ELF}
${LLD_MACHO}
)
endif()
# These don't seem to be reliable on windows.
message(STATUS "using find_library")
if(C3_USE_TB)
find_library(TB_LIB NAMES tildebackend.a tildebackend.lib PATHS ${CMAKE_SOURCE_DIR}/tb/)
endif()
find_library(LLD_COFF NAMES lldCOFF.lib lldCOFF.a liblldCOFF.a PATHS ${LLVM_LIBRARY_DIRS})
find_library(LLD_COMMON NAMES lldCommon.lib lldCommon.a liblldCommon.a PATHS ${LLVM_LIBRARY_DIRS})
find_library(LLD_ELF NAMES lldELF.lib lldELF.a liblldELF.a PATHS ${LLVM_LIBRARY_DIRS})
if (${LLVM_PACKAGE_VERSION} VERSION_LESS 14)
find_library(LLD_MACHO NAMES lldMachO2.lib lldMachO2.a liblldMachO2.a PATHS ${LLVM_LIBRARY_DIRS})
else ()
find_library(LLD_MACHO NAMES lldMachO.lib lldMachO.a liblldMachO.a PATHS ${LLVM_LIBRARY_DIRS})
endif ()
find_library(LLD_MINGW NAMES lldMinGW.lib lldMinGW.a liblldMinGW.a PATHS ${LLVM_LIBRARY_DIRS})
find_library(LLD_WASM NAMES lldWasm.lib lldWasm.a liblldWasm.a PATHS ${LLVM_LIBRARY_DIRS})
if (${LLVM_PACKAGE_VERSION} VERSION_LESS 14)
find_library(LLD_CORE NAMES lldCore.lib lldCore.a liblldCore.a PATHS ${LLVM_LIBRARY_DIRS})
find_library(LLD_DRIVER NAMES lldDriver.lib lldDriver.a liblldDriver.a PATHS ${LLVM_LIBRARY_DIRS})
find_library(LLD_READER_WRITER NAMES lldReaderWriter.lib lldReaderWriter.a liblldReaderWriter.a PATHS ${LLVM_LIBRARY_DIRS})
find_library(LLD_YAML NAMES lldYAML.lib lldYAML.a liblldYAML.a PATHS ${LLVM_LIBRARY_DIRS})
endif ()
set(lld_libs
${LLD_COFF}
${LLD_COMMON}
${LLD_WASM}
${LLD_MINGW}
${LLD_ELF}
${LLD_DRIVER}
${LLD_READER_WRITER}
${LLD_MACHO}
${LLD_YAML}
${LLD_CORE}
)
if (APPLE)
set(lld_libs ${lld_libs} xar)
endif ()
@@ -226,9 +166,7 @@ endif ()
message(STATUS "linking to llvm libs ${lld_libs}")
message(STATUS "Found lld libs ${lld_libs}")
add_library(c3c_wrappers STATIC wrapper/src/wrapper.cpp)
add_library(miniz STATIC dependencies/miniz/miniz.c)
add_executable(c3c
src/build/builder.c
@@ -236,26 +174,28 @@ add_executable(c3c
src/build/project_creation.c
src/compiler/ast.c
src/compiler/bigint.c
src/compiler/c_abi_internal.h
src/compiler/codegen_general.c
src/compiler/compiler.c
src/compiler/compiler.h
src/compiler/context.c
src/compiler/copying.c
src/compiler/diagnostics.c
src/compiler/dwarf.h
src/compiler/enums.h
src/compiler/float.c
src/compiler/headers.c
src/compiler/json_output.c
src/compiler/lexer.c
src/compiler/libraries.c
src/compiler/linker.c
src/compiler/llvm_codegen.c
src/compiler/abi/c_abi_aarch64.c
src/compiler/abi/c_abi.c
src/compiler/abi/c_abi_riscv.c
src/compiler/abi/c_abi_wasm.c
src/compiler/abi/c_abi_win64.c
src/compiler/abi/c_abi_x64.c
src/compiler/abi/c_abi_x86.c
src/compiler/llvm_codegen_c_abi_aarch64.c
src/compiler/llvm_codegen_c_abi.c
src/compiler/llvm_codegen_c_abi_riscv.c
src/compiler/llvm_codegen_c_abi_wasm.c
src/compiler/llvm_codegen_c_abi_win64.c
src/compiler/llvm_codegen_c_abi_x64.c
src/compiler/llvm_codegen_c_abi_x86.c
src/compiler/llvm_codegen_debug_info.c
src/compiler/llvm_codegen_expr.c
src/compiler/llvm_codegen_function.c
@@ -276,9 +216,6 @@ add_executable(c3c
src/compiler/sema_expr.c
src/compiler/sema_internal.h
src/compiler/sema_name_resolution.c
src/compiler/sema_errors.c
src/compiler/sema_builtins.c
src/compiler/sema_initializers.c
src/compiler/semantic_analyser.c
src/compiler/sema_passes.c
src/compiler/sema_stmts.c
@@ -287,6 +224,11 @@ add_executable(c3c
src/compiler/symtab.c
src/compiler/target.c
src/compiler/sema_asm.c
src/compiler/tb_codegen.c
src/compiler/tilde_codegen.c
src/compiler/tilde_codegen_instr.c
src/compiler/tilde_codegen_value.c
src/compiler/tilde_codegen_storeload.c
src/compiler_tests/benchmark.c
src/compiler_tests/tests.c
src/compiler/tokens.c
@@ -303,87 +245,42 @@ add_executable(c3c
src/utils/vmem.c
src/utils/vmem.h
src/utils/whereami.c
src/utils/cpus.c
src/utils/unzipper.c
src/compiler/decltable.c
src/compiler/mac_support.c
src/compiler/tilde_codegen_storeload.c
src/compiler/llvm_codegen_storeload.c
src/compiler/tilde_codegen_expr.c
src/compiler/tilde_codegen_stmt.c
src/compiler/tilde_codegen_type.c
src/compiler/windows_support.c
src/compiler/codegen_asm.c
src/compiler/asm_target.c
src/compiler/llvm_codegen_builtins.c
src/compiler/expr.c
src/utils/time.c
src/utils/http.c
src/compiler/sema_liveness.c)
if (C3_USE_TB)
file(GLOB tilde-sources
tilde-backend/src/tb/*.c
tilde-backend/src/tb/codegen/*.c
tilde-backend/src/tb/bigint/*.c
tilde-backend/src/tb/objects/*.c
tilde-backend/src/tb/system/*.c
tilde-backend/src/tb/debug/cv/*.c
tilde-backend/src/tb/opt/*.c
tilde-backend/src/tb/x64/*.c
tilde-backend/src/tb/wasm/*.c
tilde-backend/src/tb/aarch64/*.c
)
target_sources(c3c PRIVATE
src/compiler/tilde_codegen.c
src/compiler/tilde_codegen_instr.c
src/compiler/tilde_codegen_value.c
src/compiler/tilde_codegen_storeload.c
src/compiler/tilde_codegen_expr.c
src/compiler/tilde_codegen_stmt.c
src/compiler/tilde_codegen_type.c
src/compiler/tilde_codegen_abi.c
src/compiler/tilde_codegen_storeload.c)
target_compile_definitions(c3c PUBLIC TB_AVAILABLE=1)
target_link_libraries(c3c tilde-backend)
add_library(tilde-backend STATIC ${tilde-sources})
target_include_directories(tilde-backend PRIVATE
"${CMAKE_SOURCE_DIR}/tilde-backend/src/" "${CMAKE_SOURCE_DIR}/tilde-backend/include")
target_include_directories(c3c PRIVATE
"${CMAKE_SOURCE_DIR}/tilde-backend/include/")
else()
target_compile_definitions(c3c PUBLIC TB_AVAILABLE=0)
endif()
src/compiler/asm_target.c)
target_include_directories(c3c PRIVATE
"${CMAKE_SOURCE_DIR}/src/")
target_include_directories(c3c PRIVATE
"${CMAKE_SOURCE_DIR}/tb/")
target_include_directories(c3c_wrappers PRIVATE
"${CMAKE_SOURCE_DIR}/wrapper/src/")
target_include_directories(miniz PUBLIC
"${CMAKE_SOURCE_DIR}/dependencies/miniz/")
target_link_libraries(c3c_wrappers ${llvm_libs} ${lld_libs})
target_link_libraries(c3c ${llvm_libs} miniz c3c_wrappers ${lld_libs})
target_link_libraries(c3c ${llvm_libs} c3c_wrappers ${lld_libs})
if(C3_USE_TB)
target_link_libraries(c3c c3c_wrappers ${TB_LIB})
target_compile_definitions(c3c PUBLIC TB_BACKEND=1)
else()
target_compile_definitions(c3c PUBLIC TB_BACKEND=0)
endif()
if(C3_USE_MIMALLOC)
target_link_libraries(c3c mimalloc-static)
endif()
if (WIN32)
target_link_libraries(c3c Winhttp.lib)
endif()
if (CURL_FOUND)
target_link_libraries(c3c ${CURL_LIBRARIES})
target_include_directories(c3c PRIVATE ${CURL_INCLUDES})
target_compile_definitions(c3c PUBLIC CURL_FOUND=1)
else()
target_compile_definitions(c3c PUBLIC CURL_FOUND=0)
endif()
if(MSVC)
message("Adding MSVC options")
target_compile_options(c3c PRIVATE /wd4068 /wd4090 /WX /Wv:18)
@@ -392,28 +289,21 @@ if(MSVC)
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
target_compile_options(c3c PUBLIC /MTd)
target_compile_options(c3c_wrappers PUBLIC /MTd)
target_compile_options(miniz PUBLIC /MTd)
if (C3_USE_TB)
target_compile_options(tilde-backend PUBLIC /MTd)
endif()
else()
target_compile_options(c3c PUBLIC /MT)
target_compile_options(c3c_wrappers PUBLIC /MT)
target_compile_options(miniz PUBLIC /MT)
if (C3_USE_TB)
target_compile_options(tilde-backend PUBLIC /MT)
endif()
endif()
else()
message(STATUS "using gcc/clang warning switches")
target_link_options(c3c PRIVATE -pthread)
target_compile_options(c3c PRIVATE -pthread -Wall -Werror -Wno-unknown-pragmas -Wno-unused-result
target_compile_options(c3c PRIVATE -Wall -Werror -Wno-unknown-pragmas -Wno-unused-result
-Wno-unused-function -Wno-unused-variable -Wno-unused-parameter)
if (WIN32)
target_link_options(c3c PRIVATE -pthread)
target_compile_definitions(c3c PRIVATE USE_PTHREAD=1)
endif()
endif()
install(TARGETS c3c DESTINATION bin)
install(DIRECTORY lib/ DESTINATION lib/c3)
feature_summary(WHAT ALL)

View File

@@ -116,8 +116,7 @@ more tests as you go along.
### Don't bring in dependencies
External libraries has maintainability issues. Try to depend on as few libraries
as possible. Currently, c3c only depends on LLVM and libc with an optional
dependency on libcurl.
as possible. Currently c3c only depends on LLVM and libc.
Do use rewrites of subsets of other libraries to bring in functionality, but avoid
copying in libraries that needs to be updated separately.

141
README.md
View File

@@ -1,16 +1,8 @@
# C3 Language
C3 is a programming language that builds on the syntax and semantics of the C language,
with the goal of evolving it while still retaining familiarity for C programmers.
It's an evolution, not a revolution: the C-like
for programmers who like C.
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).
- MacOS x64 [download](https://github.com/c3lang/c3c/releases/download/latest/c3-macos.zip), [install instructions](#installing-on-mac-with-precompiled-binaries).
C3 is a C-like language striving to be an evolution of C, rather than a
completely new language. As an alternative in the C/C++ niche it
aims to be fast and close to the metal.
The manual for C3 can be found at [www.c3-lang.org](http://www.c3-lang.org).
@@ -32,16 +24,16 @@ whole new language.
### Example code
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/).
The following code shows [generic modules](http://www.c3-lang.org/generics/) (more examples can be found at http://www.c3-lang.org/examples/).
```c++
module stack (<Type>);
module stack <Type>;
// Above: the parameterized type is applied to the entire module.
struct Stack
{
usz capacity;
usz size;
usize capacity;
usize size;
Type* elems;
}
@@ -54,8 +46,7 @@ fn void Stack.push(Stack* this, Type element)
if (this.capacity == this.size)
{
this.capacity *= 2;
if (this.capacity < 16) this.capacity = 16;
this.elems = realloc(this.elems, Type.sizeof * this.capacity);
this.elems = mem::realloc(this.elems, Type.sizeof * this.capacity);
}
this.elems[this.size++] = element;
}
@@ -79,20 +70,20 @@ import stack;
// Define our new types, the first will implicitly create
// a complete copy of the entire Stack module with "Type" set to "int"
def IntStack = Stack(<int>);
define IntStack = Stack<int>;
// The second creates another copy with "Type" set to "double"
def DoubleStack = Stack(<double>);
define DoubleStack = Stack<double>;
// If we had added "define IntStack2 = Stack(<int>)"
// If we had added "define IntStack2 = Stack<int>"
// no additional copy would have been made (since we already
// have an parameterization of Stack(<int>)) so it would
// have an parameterization of Stack<int>) so it would
// be same as declaring IntStack2 an alias of IntStack
// Importing an external C function is straightforward
// here is an example of importing libc's printf:
extern fn int printf(char* format, ...);
fn void main()
fn void test()
{
IntStack stack;
// Note that C3 uses zero initialization by default
@@ -112,17 +103,17 @@ fn void main()
dstack.push(2.3);
dstack.push(3.141);
dstack.push(1.1235);
// Prints pop: 1.123500
// Prints pop: 1.1235
printf("pop: %f\n", dstack.pop());
}
```
### In what ways does C3 differ from C?
### In what ways do C3 differ from C?
- No mandatory header files
- New semantic macro system
- Module based name spacing
- Slices
- Subarrays (slices)
- Compile time reflection
- Enhanced compile time execution
- Generics based on generic modules
@@ -131,15 +122,20 @@ fn void main()
- Value methods
- Associated enum data
- No preprocessor
- Less undefined behaviour and added runtime checks in "safe" mode
- Less undefined behaviour and runtime checks in "safe" mode
- Limited operator overloading to enable userland dynamic arrays
- Optional pre and post conditions
### Current status
The current stable version of the compiler is **version 0.5**.
The current version of the compiler is alpha release 0.2.
The upcoming 0.6 release will focus on expanding the standard library.
It's possible to try out the current C3 compiler in the browser: https://ide.judge0.com/ this is courtesy of the
developer of Judge0.
Design work on C3 complete aside from fleshing out details, such as
inline asm. As the standard library work progresses, changes and improvements
to the language will happen continuously.
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)
@@ -147,38 +143,7 @@ or discuss C3 on its dedicated Discord: [https://discord.gg/qN76R87](https://dis
The compiler is currently verified to compile on Linux, Windows and MacOS.
**Support matrix**
| Platform | Native C3 compiler available? | Target supported | Stack trace | Threads | Sockets | Inline asm |
|--------------------------|-------------------------------|-------------------------|-------------|----------|----------|------------|
| Win32 x64 | Yes | Yes + cross compilation | Yes | Yes | Yes | Yes* |
| Win32 Aarch64 | Untested | Untested | Untested | Untested | Untested | Yes* |
| MacOS x64 | Yes | Yes + cross compilation | Yes | Yes | Yes | Yes* |
| MacOS Aarch64 | Yes | Yes + cross compilation | Yes | Yes | Yes | Yes* |
| iOS Aarch64 | No | Untested | Untested | Yes | Yes | Yes* |
| Linux x86 | Yes | Yes | Yes | Yes | Yes | Yes* |
| Linux x64 | Yes | Yes | Yes | Yes | Yes | Yes* |
| Linux Aarch64 | Yes | Yes | Yes | Yes | Yes | Yes* |
| Linux Riscv32 | Yes | Yes | Yes | Yes | Yes | Untested |
| Linux Riscv64 | Yes | Yes | Yes | Yes | Yes | Untested |
| ELF freestanding x86 | No | Untested | No | No | No | Yes* |
| ELF freestanding x64 | No | Untested | No | No | No | Yes* |
| ELF freestanding Aarch64 | No | Untested | No | No | No | Yes* |
| ELF freestanding Riscv64 | No | Untested | No | No | No | Untested |
| ELF freestanding Riscv32 | No | Untested | No | No | No | Untested |
| FreeBSD x86 | Untested | Untested | No | Yes | Untested | Yes* |
| FreeBSD x64 | Untested | Untested | No | Yes | Untested | Yes* |
| NetBSD x86 | Untested | Untested | No | Yes | Untested | Yes* |
| NetBSD x64 | Untested | Untested | No | Yes | Untested | Yes* |
| OpenBSD x86 | Untested | Untested | No | Yes | Untested | Yes* |
| OpenBSD x64 | Untested | Untested | No | Yes | Untested | Yes* |
| MCU x86 | No | Untested | No | No | No | Yes* |
| Wasm32 | No | Yes | No | No | No | No |
| Wasm64 | No | Untested | No | No | No | No |
*\* Inline asm is still a work in progress*
More platforms will be supported in the future.
#### What can you help with?
@@ -193,10 +158,10 @@ More platforms will be supported in the future.
### Installing
#### Installing on Windows with precompiled binaries
1. Download the zip file: [https://github.com/c3lang/c3c/releases/download/latest/c3-windows.zip](https://github.com/c3lang/c3c/releases/download/latest/c3-windows.zip)
1. Make sure you have Visual Studio 17 2022 installed or alternatively install the "Buildtools for Visual Studio" (https://aka.ms/vs/17/release/vs_BuildTools.exe) and then select "Desktop development with C++" (there is also `c3c/resources/install_win_reqs.bat` to automate this)
2. Download the zip file: [https://github.com/c3lang/c3c/releases/download/latest/c3-windows.zip](https://github.com/c3lang/c3c/releases/download/latest/c3-windows.zip)
(debug version [here](https://github.com/c3lang/c3c/releases/download/latest/c3-windows-debug.zip))
2. Unzip exe and standard lib.
3. If you don't have Visual Studio 17 installed you can either do so, or run the `msvc_build_libraries.py` Python script which will download the necessary files to compile on Windows.
3. Unzip exe and standard lib.
4. Run `c3c.exe`.
#### Installing on Debian with precompiled binaries
@@ -212,21 +177,9 @@ More platforms will be supported in the future.
3. Unzip executable and standard lib.
4. Run `./c3c`.
(*Note that there is a known issue with debug symbol generation on MacOS 13, see issue #1086)
#### Installing on Arch Linux
There is an AUR package for the c3c compiler : [c3c-git](https://aur.archlinux.org/packages/c3c-git).
Due to some issues with the LLVM packaged for Arch Linux, the AUR package will download and use LLVM 16 for Ubuntu-23.04 to compile the c3c compiler.
You can use your AUR package manager:
```sh
paru -S c3c-git
# or yay -S c3c-git
# or aura -A c3c-git
```
Or clone it manually:
There is an AUR package for the c3c compiler : [c3c-git](https://aur.archlinux.org/packages/c3c-git)
You can use your AUR package manager or clone it manually:
```sh
git clone https://aur.archlinux.org/c3c-git.git
cd c3c-git
@@ -253,7 +206,7 @@ A `c3c` executable will be found under `bin/`.
#### Installing on OS X using Homebrew
2. Install CMake: `brew install cmake`
3. Install LLVM 15: `brew install llvm`
3. Install LLVM 13: `brew install llvm`
4. Clone the C3C github repository: `git clone https://github.com/c3lang/c3c.git`
5. Enter the C3C directory `cd c3c`.
6. Create a build directory `mkdir build`
@@ -270,7 +223,7 @@ import std::io;
fn void main()
{
io::printn("Hello, world!");
io::println("Hello, world!");
}
```
@@ -308,7 +261,7 @@ You can try it out by running some sample code: `c3c.exe compile ../resources/ex
1. Make sure you have a C compiler that handles C11 and a C++ compiler, such as GCC or Clang. Git also needs to be installed.
2. Install CMake: `sudo apt install cmake`
3. Install LLVM 15 (or greater: C3C supports LLVM 15-17): `sudo apt-get install clang-15 zlib1g zlib1g-dev libllvm15 llvm-15 llvm-15-dev llvm-15-runtime liblld-15-dev liblld-15`
3. Install LLVM 12 (or greater: C3C supports LLVM 12-16): `sudo apt-get install clang-12 zlib1g zlib1g-dev libllvm12 llvm-12 llvm-12-dev llvm-12-runtime liblld-12-dev liblld-12`
4. Clone the C3C github repository: `git clone https://github.com/c3lang/c3c.git`
5. Enter the C3C directory `cd c3c`.
6. Create a build directory `mkdir build`
@@ -321,25 +274,10 @@ You should now have a `c3c` executable.
You can try it out by running some sample code: `./c3c compile ../resources/examples/hash.c3`
#### Compiling on Void Linux
1. As root, ensure that all project dependencies are installed: `xbps-install git cmake llvm15 lld-devel libcurl-devel ncurses-devel zlib-devel libzstd-devel libxml2-devel`
2. Clone the C3C repository: `git clone https://github.com/c3lang/c3c.git`
- If you only need the latest commit, you may want to make a shallow clone instead: `git clone https://github.com/c3lang/c3c.git --depth=1`
3. Enter the directory: `cd c3c`
4. Create a build directory: `mkdir build`
5. Enter the build directory: `cd build`
6. Create the CMake build cache: `cmake ..`
7. Build: `cmake --build .`
Your c3c executable should have compiled properly. You may want to test it: `./c3c compile ../resources/examples/hash.c3`
For a sytem-wide installation, run the following as root: `cmake --install .`
#### Compiling on other Linux / Unix variants
1. Install CMake.
2. Install or compile LLVM and LLD *libraries* (version 15+ or higher)
2. Install or compile LLVM and LLD *libraries* (version 12+ or higher)
3. Clone the C3C github repository: `git clone https://github.com/c3lang/c3c.git`
4. Enter the C3C directory `cd c3c`.
5. Create a build directory `mkdir build`
@@ -348,10 +286,6 @@ For a sytem-wide installation, run the following as root: `cmake --install .`
provide the link path to the LLVM CMake directories, e.g. `cmake -DLLVM_DIR=/usr/local/opt/llvm/lib/cmake/llvm/ ..`
8. Build: `cmake --build .`
*A note on compiling for Linux/Unix/MacOS: to be able to fetch vendor libraries
libcurl is needed. The CMake script should detect it if it is available. Note that
this functionality is non-essential and it is perfectly fine to user the compiler without it.*
#### Licensing
The C3 compiler is licensed under LGPL 3.0, the standard library itself is
@@ -360,12 +294,3 @@ MIT licensed.
#### Editor plugins
Editor plugins can be found at https://github.com/c3lang/editor-plugins.
#### Contributing unit tests
1. Write the test, either adding to existing test files in `/test/unit/` or add
a new file. (If testing the standard library, put it in the `/test/unit/stdlib/` subdirectory).
2. Make sure that the test functions have the `@test` attribute.
3. Run tests and see that they pass. (Recommended settings: `c3c compile-test -O0 test/unit`.
- in this example `test/unit/` is the relative path to the test directory, so adjust as required)
4. Make a pull request for the new tests.

View File

@@ -1,13 +1,15 @@
#!/bin/bash
## build-with-docker.sh
## @author gdm85
## @modified by Kenta
##
## Script to build c3c for Ubuntu 22
## Script to build c3c for either Ubuntu 20, 21 or 22.
##
#
read -p "Select Build Type: Debug/Release: " config
if [ $# -ne 1 -a $# -ne 2 ]; then
echo "Usage: build-with-docker.sh (20|21|22) [Debug|Release]" 1>&2
exit 1
fi
set -e
@@ -15,29 +17,40 @@ DOCKER=docker
DOCKER_RUN=""
IMAGE="c3c-builder"
if type podman 2>/dev/null >/dev/null; then
DOCKER=podman
DOCKER_RUN="--userns=keep-id"
IMAGE="localhost/$IMAGE"
DOCKER=podman
DOCKER_RUN="--userns=keep-id"
IMAGE="localhost/$IMAGE"
fi
if [ $config == "Debug" ]; then
CMAKE_BUILD_TYPE=Debug
if [ -z "$2" ]; then
CMAKE_BUILD_TYPE=Debug
else
CMAKE_BUILD_TYPE="$config"
CMAKE_BUILD_TYPE="$2"
fi
UBUNTU_VERSION="22.10"
LLVM_VERSION="15"
TAG="$1"
if [ "$1" = 20 ]; then
UBUNTU_VERSION="20.04"
LLVM_VERSION="12"
elif [ "$1" = 21 ]; then
UBUNTU_VERSION="21.10"
LLVM_VERSION="13"
elif [ "$1" = 22 ]; then
UBUNTU_VERSION="22.04"
LLVM_VERSION="14"
else
echo "ERROR: expected 20, 21 or 22 as Ubuntu version argument" 1>&2
exit 2
fi
IMAGE="$IMAGE:$TAG"
IMAGE="$IMAGE:22"
cd docker && $DOCKER build -t $IMAGE\
--build-arg DEPS="llvm-$LLVM_VERSION-dev liblld-$LLVM_VERSION-dev clang-$LLVM_VERSION libllvm$LLVM_VERSION llvm-$LLVM_VERSION-runtime" \
--build-arg UBUNTU_VERSION="$UBUNTU_VERSION" .
cd docker && $DOCKER build -t $IMAGE --build-arg UID=$(id -u) --build-arg GID=$(id -g) \
--build-arg DEPS="llvm-$LLVM_VERSION-dev liblld-$LLVM_VERSION-dev clang-$LLVM_VERSION libllvm$LLVM_VERSION llvm-$LLVM_VERSION-runtime" \
--build-arg UBUNTU_VERSION="$UBUNTU_VERSION" .
cd ..
rm -rf build bin
mkdir -p build bin
exec $DOCKER run -ti --rm --tmpfs=/tmp $DOCKER_RUN -v "$PWD":/home/c3c/source -w /home/c3c/source $IMAGE bash -c \
"cd build && cmake -DCMAKE_BUILD_TYPE=$CMAKE_BUILD_TYPE -DC3_LLVM_VERSION=$LLVM_VERSION .. && cmake --build . && mv c3c lib ../bin/"
"cd build && cmake -DCMAKE_BUILD_TYPE=$CMAKE_BUILD_TYPE -DC3_LLVM_VERSION=$LLVM_VERSION .. && cmake --build . && mv c3c lib ../bin/"

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -11,6 +11,6 @@ RUN export DEBIAN_FRONTEND=noninteractive && export TERM=xterm && apt-get update
ARG GID=1000
ARG UID=1000
RUN groupadd -o --gid=$GID c3c && useradd --gid=$GID --uid=$GID --create-home --shell /bin/bash c3c
RUN groupadd --gid=$GID c3c && useradd --gid=$GID --uid=$GID --create-home --shell /bin/bash c3c
USER c3c

17
install_win_reqs.bat Normal file
View File

@@ -0,0 +1,17 @@
@echo off
set DOWNLOAD_URL=https://aka.ms/vs/17/release
mkdir tmp 2> NUL
if not exist "tmp\vs_buildtools.exe" (
bitsadmin /transfer /download /priority foreground %DOWNLOAD_URL%/vs_buildtools.exe %CD%\tmp\vs_buildtools.exe
)
echo Preparing Build Tools, please wait...
tmp\vs_BuildTools.exe --quiet --wait --layout tmp\ --add Microsoft.VisualStudio.Component.Windows10SDK.19041
echo Installing Build Tools, please wait...
tmp\vs_BuildTools.exe --quiet --wait --noweb --add Microsoft.VisualStudio.Component.Windows10SDK.19041
REM rmdir tmp /s /q

View File

@@ -1,77 +0,0 @@
module std::ascii;
macro bool in_range_m(c, start, len) => (uint)(c - start) < len;
macro bool is_lower_m(c) => in_range_m(c, 0x61, 26);
macro bool is_upper_m(c) => in_range_m(c, 0x41, 26);
macro bool is_digit_m(c) => in_range_m(c, 0x30, 10);
macro bool is_bdigit_m(c) => in_range_m(c, 0x30, 2);
macro bool is_odigit_m(c) => in_range_m(c, 0x30, 8);
macro bool is_xdigit_m(c) => in_range_m(c | 32, 0x61, 6) || is_digit_m(c);
macro bool is_alpha_m(c) => in_range_m(c | 32, 0x61, 26);
macro bool is_print_m(c) => in_range_m(c, 0x20, 95);
macro bool is_graph_m(c) => in_range_m(c, 0x21, 94);
macro bool is_space_m(c) => in_range_m(c, 0x9, 5) || c == 0x20;
macro bool is_alnum_m(c) => is_alpha_m(c) || is_digit_m(c);
macro bool is_punct_m(c) => !is_alnum_m(c) && is_graph_m(c);
macro bool is_blank_m(c) => c == 0x20 || c == 0x9;
macro bool is_cntrl_m(c) => c < 0x20 || c == 0x7f;
macro to_lower_m(c) => is_upper_m(c) ? c + 0x20 : c;
macro to_upper_m(c) => is_lower_m(c) ? c - 0x20 : c;
fn bool in_range(char c, char start, char len) => in_range_m(c, start, len);
fn bool is_lower(char c) => is_lower_m(c);
fn bool is_upper(char c) => is_upper_m(c);
fn bool is_digit(char c) => is_digit_m(c);
fn bool is_bdigit(char c) => is_bdigit_m(c);
fn bool is_odigit(char c) => is_odigit_m(c);
fn bool is_xdigit(char c) => is_xdigit_m(c);
fn bool is_alpha(char c) => is_alpha_m(c);
fn bool is_print(char c) => is_print_m(c);
fn bool is_graph(char c) => is_graph_m(c);
fn bool is_space(char c) => is_space_m(c);
fn bool is_alnum(char c) => is_alnum_m(c);
fn bool is_punct(char c) => is_punct_m(c);
fn bool is_blank(char c) => is_blank_m(c);
fn bool is_cntrl(char c) => is_cntrl_m(c);
fn char to_lower(char c) => (char)to_lower_m(c);
fn char to_upper(char c) => (char)to_upper_m(c);
fn bool char.in_range(char c, char start, char len) => in_range_m(c, start, len);
fn bool char.is_lower(char c) => is_lower_m(c);
fn bool char.is_upper(char c) => is_upper_m(c);
fn bool char.is_digit(char c) => is_digit_m(c);
fn bool char.is_bdigit(char c) => is_bdigit_m(c);
fn bool char.is_odigit(char c) => is_odigit_m(c);
fn bool char.is_xdigit(char c) => is_xdigit_m(c);
fn bool char.is_alpha(char c) => is_alpha_m(c);
fn bool char.is_print(char c) => is_print_m(c);
fn bool char.is_graph(char c) => is_graph_m(c);
fn bool char.is_space(char c) => is_space_m(c);
fn bool char.is_alnum(char c) => is_alnum_m(c);
fn bool char.is_punct(char c) => is_punct_m(c);
fn bool char.is_blank(char c) => is_blank_m(c);
fn bool char.is_cntrl(char c) => is_cntrl_m(c);
fn char char.to_lower(char c) => (char)to_lower_m(c);
fn char char.to_upper(char c) => (char)to_upper_m(c);
/**
* @require c.is_xdigit()
**/
fn char char.from_hex(char c) => c.is_digit() ? c - '0' : 10 + (c | 0x20) - 'a';
fn bool uint.in_range(uint c, uint start, uint len) => in_range_m(c, start, len);
fn bool uint.is_lower(uint c) => is_lower_m(c);
fn bool uint.is_upper(uint c) => is_upper_m(c);
fn bool uint.is_digit(uint c) => is_digit_m(c);
fn bool uint.is_bdigit(uint c) => is_bdigit_m(c);
fn bool uint.is_odigit(uint c) => is_odigit_m(c);
fn bool uint.is_xdigit(uint c) => is_xdigit_m(c);
fn bool uint.is_alpha(uint c) => is_alpha_m(c);
fn bool uint.is_print(uint c) => is_print_m(c);
fn bool uint.is_graph(uint c) => is_graph_m(c);
fn bool uint.is_space(uint c) => is_space_m(c);
fn bool uint.is_alnum(uint c) => is_alnum_m(c);
fn bool uint.is_punct(uint c) => is_punct_m(c);
fn bool uint.is_blank(uint c) => is_blank_m(c);
fn bool uint.is_cntrl(uint c) => is_cntrl_m(c);
fn uint uint.to_lower(uint c) => (uint)to_lower_m(c);
fn uint uint.to_upper(uint c) => (uint)to_upper_m(c);

View File

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

View File

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

View File

@@ -3,169 +3,76 @@ module std::bits;
/**
* @require types::is_intlike($typeof(i)) `The input must be an integer or integer vector`
**/
macro reverse(i) => $$bitreverse(i);
macro popcount(i) @operator(intvec)
{
return $$popcount(i);
}
/**
* @require types::is_intlike($typeof(i)) `The input must be an integer or integer vector`
**/
macro bswap(i) @builtin => $$bswap(i);
macro reverse(i)
{
return $$bitreverse(i);
}
macro uint[<*>].popcount(self) => $$popcount(self);
macro uint[<*>].ctz(self) => $$ctz(self);
macro uint[<*>].clz(self) => $$clz(self);
macro uint[<*>] uint[<*>].fshl(hi, uint[<*>] lo, uint[<*>] shift) => $$fshl(hi, lo, shift);
macro uint[<*>] uint[<*>].fshr(hi, uint[<*>] lo, uint[<*>] shift) => $$fshr(hi, lo, shift);
macro uint[<*>] uint[<*>].rotl(self, uint[<*>] shift) => $$fshl(self, self, shift);
macro uint[<*>] uint[<*>].rotr(self, uint[<*>] shift) => $$fshr(self, self, shift);
/**
* @require types::is_intlike($typeof(i)) `The input must be an integer or integer vector`
**/
macro bswap(i) @builtin
{
return $$bswap(i);
}
macro int[<*>].popcount(self) => $$popcount(self);
macro int[<*>].ctz(self) => $$ctz(self);
macro int[<*>].clz(self) => $$clz(self);
macro int[<*>] int[<*>].fshl(hi, int[<*>] lo, int[<*>] shift) => $$fshl(hi, lo, shift);
macro int[<*>] int[<*>].fshr(hi, int[<*>] lo, int[<*>] shift) => $$fshr(hi, lo, shift);
macro int[<*>] int[<*>].rotl(self, int[<*>] shift) => $$fshl(self, self, shift);
macro int[<*>] int[<*>].rotr(self, int[<*>] shift) => $$fshr(self, self, shift);
/**
* @require types::is_intlike($typeof(i)) `The input must be an integer or integer vector`
**/
macro ctz(i) @operator(intvec) @builtin
{
return $$ctz(i);
}
macro ushort[<*>].popcount(self) => $$popcount(self);
macro ushort[<*>].ctz(self) => $$ctz(self);
macro ushort[<*>].clz(self) => $$clz(self);
macro ushort[<*>] ushort[<*>].fshl(hi, ushort[<*>] lo, ushort[<*>] shift) => $$fshl(hi, lo, shift);
macro ushort[<*>] ushort[<*>].fshr(hi, ushort[<*>] lo, ushort[<*>] shift) => $$fshr(hi, lo, shift);
macro ushort[<*>] ushort[<*>].rotl(self, ushort[<*>] shift) => $$fshl(self, self, shift);
macro ushort[<*>] ushort[<*>].rotr(self, ushort[<*>] shift) => $$fshr(self, self, shift);
/**
* @require types::is_intlike($typeof(i)) `The input must be an integer or integer vector`
**/
macro clz(i) @operator(intvec) @builtin
{
return $$clz(i);
}
macro short[<*>].popcount(self) => $$popcount(self);
macro short[<*>].ctz(self) => $$ctz(self);
macro short[<*>].clz(self) => $$clz(self);
macro short[<*>] short[<*>].fshl(hi, short[<*>] lo, short[<*>] shift) => $$fshl(hi, lo, shift);
macro short[<*>] short[<*>].fshr(hi, short[<*>] lo, short[<*>] shift) => $$fshr(hi, lo, shift);
macro short[<*>] short[<*>].rotl(self, short[<*>] shift) => $$fshl(self, self, shift);
macro short[<*>] short[<*>].rotr(self, short[<*>] shift) => $$fshr(self, self, shift);
/**
* @require types::is_intlike($typeof(hi)) && types::is_intlike($typeof(lo)) && types::is_intlike($typeof(shift)) `The input must be an integer or integer vector`
* @require types::@has_same(hi, lo, shift) `Hi, low and shift arguments must have the same type`
**/
macro fshl(hi, lo, shift) @builtin
{
return $$fshl(hi, lo, ($typeof(hi))shift);
}
macro char[<*>].popcount(self) => $$popcount(self);
macro char[<*>].ctz(self) => $$ctz(self);
macro char[<*>].clz(self) => $$clz(self);
macro char[<*>] char[<*>].fshl(hi, char[<*>] lo, char[<*>] shift) => $$fshl(hi, lo, shift);
macro char[<*>] char[<*>].fshr(hi, char[<*>] lo, char[<*>] shift) => $$fshr(hi, lo, shift);
macro char[<*>] char[<*>].rotl(self, char[<*>] shift) => $$fshl(self, self, shift);
macro char[<*>] char[<*>].rotr(self, char[<*>] shift) => $$fshr(self, self, shift);
/**
* @require types::is_intlike($typeof(hi)) && types::is_intlike($typeof(lo)) && types::is_intlike($typeof(shift)) `The input must be an integer or integer vector`
* @require types::@has_same(hi, lo, shift) `Hi, low and shift arguments must have the same type`
**/
macro fshr(hi, lo, shift) @builtin
{
return $$fshr(hi, lo, ($typeof(hi))shift);
}
macro ichar[<*>].popcount(self) => $$popcount(self);
macro ichar[<*>].ctz(self) => $$ctz(self);
macro ichar[<*>].clz(self) => $$clz(self);
macro ichar[<*>] ichar[<*>].fshl(hi, ichar[<*>] lo, ichar[<*>] shift) => $$fshl(hi, lo, shift);
macro ichar[<*>] ichar[<*>].fshr(hi, ichar[<*>] lo, ichar[<*>] shift) => $$fshr(hi, lo, shift);
macro ichar[<*>] ichar[<*>].rotl(self, ichar[<*>] shift) => $$fshl(self, self, shift);
macro ichar[<*>] ichar[<*>].rotr(self, ichar[<*>] shift) => $$fshr(self, self, shift);
/**
* @require types::is_intlike($typeof(i)) && types::is_intlike($typeof(shift)) `The input must be an integer or integer vector`
* @require types::@has_same(i, shift) `The shift value must have the same type as shifted types`
**/
macro rotl(i, shift) @operator(intvec) @builtin
{
return $$fshl(i, i, shift);
}
macro ulong[<*>].popcount(self) => $$popcount(self);
macro ulong[<*>].ctz(self) => $$ctz(self);
macro ulong[<*>].clz(self) => $$clz(self);
macro ulong[<*>] ulong[<*>].fshl(hi, ulong[<*>] lo, ulong[<*>] shift) => $$fshl(hi, lo, shift);
macro ulong[<*>] ulong[<*>].fshr(hi, ulong[<*>] lo, ulong[<*>] shift) => $$fshr(hi, lo, shift);
macro ulong[<*>] ulong[<*>].rotl(self, ulong[<*>] shift) => $$fshl(self, self, shift);
macro ulong[<*>] ulong[<*>].rotr(self, ulong[<*>] shift) => $$fshr(self, self, shift);
/**
* @require types::is_intlike($typeof(i)) && types::is_intlike($typeof(shift)) `The input must be an integer or integer vector`
* @require types::@has_same(i, shift) `The shift value must have the same type as shifted types`
**/
macro rotr(i, shift) @operator(intvec) @builtin
{
return $$fshr(i, i, shift);
}
macro long[<*>].popcount(self) => $$popcount(self);
macro long[<*>].ctz(self) => $$ctz(self);
macro long[<*>].clz(self) => $$clz(self);
macro long[<*>] long[<*>].fshl(hi, long[<*>] lo, long[<*>] shift) => $$fshl(hi, lo, shift);
macro long[<*>] long[<*>].fshr(hi, long[<*>] lo, long[<*>] shift) => $$fshr(hi, lo, shift);
macro long[<*>] long[<*>].rotl(self, long[<*>] shift) => $$fshl(self, self, shift);
macro long[<*>] long[<*>].rotr(self, long[<*>] shift) => $$fshr(self, self, shift);
macro uint128[<*>].popcount(self) => $$popcount(self);
macro uint128[<*>].ctz(self) => $$ctz(self);
macro uint128[<*>].clz(self) => $$clz(self);
macro uint128[<*>] uint128[<*>].fshl(hi, uint128[<*>] lo, uint128[<*>] shift) => $$fshl(hi, lo, shift);
macro uint128[<*>] uint128[<*>].fshr(hi, uint128[<*>] lo, uint128[<*>] shift) => $$fshr(hi, lo, shift);
macro uint128[<*>] uint128[<*>].rotl(self, uint128[<*>] shift) => $$fshl(self, self, shift);
macro uint128[<*>] uint128[<*>].rotr(self, uint128[<*>] shift) => $$fshr(self, self, shift);
macro int128[<*>].popcount(self) => $$popcount(self);
macro int128[<*>].ctz(self) => $$ctz(self);
macro int128[<*>].clz(self) => $$clz(self);
macro int128[<*>] int128[<*>].fshl(hi, int128[<*>] lo, int128[<*>] shift) => $$fshl(hi, lo, shift);
macro int128[<*>] int128[<*>].fshr(hi, int128[<*>] lo, int128[<*>] shift) => $$fshr(hi, lo, shift);
macro int128[<*>] int128[<*>].rotl(self, int128[<*>] shift) => $$fshl(self, self, shift);
macro int128[<*>] int128[<*>].rotr(self, int128[<*>] shift) => $$fshr(self, self, shift);
macro uint.popcount(self) => $$popcount(self);
macro uint.ctz(self) => $$ctz(self);
macro uint.clz(self) => $$clz(self);
macro uint uint.fshl(hi, uint lo, uint shift) => $$fshl(hi, lo, shift);
macro uint uint.fshr(hi, uint lo, uint shift) => $$fshr(hi, lo, shift);
macro uint uint.rotl(self, uint shift) => $$fshl(self, self, shift);
macro uint uint.rotr(self, uint shift) => $$fshr(self, self, shift);
macro int.popcount(self) => $$popcount(self);
macro int.ctz(self) => $$ctz(self);
macro int.clz(self) => $$clz(self);
macro int int.fshl(hi, int lo, int shift) => $$fshl(hi, lo, shift);
macro int int.fshr(hi, int lo, int shift) => $$fshr(hi, lo, shift);
macro int int.rotl(self, int shift) => $$fshl(self, self, shift);
macro int int.rotr(self, int shift) => $$fshr(self, self, shift);
macro ushort.popcount(self) => $$popcount(self);
macro ushort.ctz(self) => $$ctz(self);
macro ushort.clz(self) => $$clz(self);
macro ushort ushort.fshl(hi, ushort lo, ushort shift) => $$fshl(hi, lo, shift);
macro ushort ushort.fshr(hi, ushort lo, ushort shift) => $$fshr(hi, lo, shift);
macro ushort ushort.rotl(self, ushort shift) => $$fshl(self, self, shift);
macro ushort ushort.rotr(self, ushort shift) => $$fshr(self, self, shift);
macro short.popcount(self) => $$popcount(self);
macro short.ctz(self) => $$ctz(self);
macro short.clz(self) => $$clz(self);
macro short short.fshl(hi, short lo, short shift) => $$fshl(hi, lo, shift);
macro short short.fshr(hi, short lo, short shift) => $$fshr(hi, lo, shift);
macro short short.rotl(self, short shift) => $$fshl(self, self, shift);
macro short short.rotr(self, short shift) => $$fshr(self, self, shift);
macro char.popcount(self) => $$popcount(self);
macro char.ctz(self) => $$ctz(self);
macro char.clz(self) => $$clz(self);
macro char char.fshl(hi, char lo, char shift) => $$fshl(hi, lo, shift);
macro char char.fshr(hi, char lo, char shift) => $$fshr(hi, lo, shift);
macro char char.rotl(self, char shift) => $$fshl(self, self, shift);
macro char char.rotr(self, char shift) => $$fshr(self, self, shift);
macro ichar.popcount(self) => $$popcount(self);
macro ichar.ctz(self) => $$ctz(self);
macro ichar.clz(self) => $$clz(self);
macro ichar ichar.fshl(hi, ichar lo, ichar shift) => $$fshl(hi, lo, shift);
macro ichar ichar.fshr(hi, ichar lo, ichar shift) => $$fshr(hi, lo, shift);
macro ichar ichar.rotl(self, ichar shift) => $$fshl(self, self, shift);
macro ichar ichar.rotr(self, ichar shift) => $$fshr(self, self, shift);
macro ulong.popcount(self) => $$popcount(self);
macro ulong.ctz(self) => $$ctz(self);
macro ulong.clz(self) => $$clz(self);
macro ulong ulong.fshl(hi, ulong lo, ulong shift) => $$fshl(hi, lo, shift);
macro ulong ulong.fshr(hi, ulong lo, ulong shift) => $$fshr(hi, lo, shift);
macro ulong ulong.rotl(self, ulong shift) => $$fshl(self, self, shift);
macro ulong ulong.rotr(self, ulong shift) => $$fshr(self, self, shift);
macro long.popcount(self) => $$popcount(self);
macro long.ctz(self) => $$ctz(self);
macro long.clz(self) => $$clz(self);
macro long long.fshl(hi, long lo, long shift) => $$fshl(hi, lo, shift);
macro long long.fshr(hi, long lo, long shift) => $$fshr(hi, lo, shift);
macro long long.rotl(self, long shift) => $$fshl(self, self, shift);
macro long long.rotr(self, long shift) => $$fshr(self, self, shift);
macro uint128.popcount(self) => $$popcount(self);
macro uint128.ctz(self) => $$ctz(self);
macro uint128.clz(self) => $$clz(self);
macro uint128 uint128.fshl(hi, uint128 lo, uint128 shift) => $$fshl(hi, lo, shift);
macro uint128 uint128.fshr(hi, uint128 lo, uint128 shift) => $$fshr(hi, lo, shift);
macro uint128 uint128.rotl(self, uint128 shift) => $$fshl(self, self, shift);
macro uint128 uint128.rotr(self, uint128 shift) => $$fshr(self, self, shift);
macro int128.popcount(self) => $$popcount(self);
macro int128.ctz(self) => $$ctz(self);
macro int128.clz(self) => $$clz(self);
macro int128 int128.fshl(hi, int128 lo, int128 shift) => $$fshl(hi, lo, shift);
macro int128 int128.fshr(hi, int128 lo, int128 shift) => $$fshr(hi, lo, shift);
macro int128 int128.rotl(self, int128 shift) => $$fshl(self, self, shift);
macro int128 int128.rotr(self, int128 shift) => $$fshr(self, self, shift);

View File

@@ -1,458 +0,0 @@
// 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;
def AnyPredicate = fn bool(any value);
def AnyTest = fn bool(any type, any context);
struct AnyList (Printable)
{
usz size;
usz capacity;
Allocator allocator;
any* entries;
}
/**
* @param initial_capacity "The initial capacity to reserve"
* @param [&inout] allocator "The allocator to use, defaults to the heap allocator"
**/
fn AnyList* AnyList.new_init(&self, usz initial_capacity = 16, Allocator allocator = allocator::heap())
{
self.allocator = allocator;
self.size = 0;
if (initial_capacity > 0)
{
initial_capacity = math::next_power_of_2(initial_capacity);
self.entries = allocator::alloc_array(allocator, any, initial_capacity);
}
else
{
self.entries = null;
}
self.capacity = initial_capacity;
return self;
}
/**
* Initialize the list using the temp allocator.
*
* @param initial_capacity "The initial capacity to reserve"
**/
fn AnyList* AnyList.temp_init(&self, usz initial_capacity = 16)
{
return self.new_init(initial_capacity, allocator::temp()) @inline;
}
fn usz! AnyList.to_format(&self, Formatter* formatter) @dynamic
{
switch (self.size)
{
case 0:
return formatter.print("[]")!;
case 1:
return formatter.printf("[%s]", self.entries[0])!;
default:
usz n = formatter.print("[")!;
foreach (i, element : self.entries[:self.size])
{
if (i != 0) formatter.print(", ")!;
n += formatter.printf("%s", element)!;
}
n += formatter.print("]")!;
return n;
}
}
fn String AnyList.to_new_string(&self, Allocator allocator = allocator::heap()) @dynamic
{
return string::new_format("%s", *self, .allocator = allocator);
}
fn String AnyList.to_tstring(&self)
{
return string::tformat("%s", *self);
}
/**
* Push an element on the list by cloning it.
**/
macro void AnyList.push(&self, element)
{
if (!self.allocator) self.allocator = allocator::heap();
self.append_internal(allocator::clone(self.allocator, element));
}
fn void AnyList.append_internal(&self, any element) @local
{
self.ensure_capacity();
self.entries[self.size++] = element;
}
/**
* Free a retained element removed using *_retained.
**/
fn void AnyList.free_element(&self, any element) @inline
{
allocator::free(self.allocator, element.ptr);
}
/**
* Pop a value who's type is known. If the type is incorrect, this
* will still pop the element.
*
* @return! CastResult.TYPE_MISMATCH, IteratorResult.NO_MORE_ELEMENT
**/
macro AnyList.pop(&self, $Type)
{
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! IteratorResult.NO_MORE_ELEMENT
**/
fn any! AnyList.new_pop(&self, Allocator allocator = allocator::heap())
{
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 temp allocator
* @return! IteratorResult.NO_MORE_ELEMENT
**/
fn any! AnyList.temp_pop(&self) => self.new_pop(allocator::temp());
/**
* Pop the last value. It must later be released using list.free_element()
* @return! IteratorResult.NO_MORE_ELEMENT
**/
fn any! AnyList.pop_retained(&self)
{
if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
return self.entries[--self.size];
}
fn void AnyList.clear(&self)
{
for (usz i = 0; i < self.size; i++)
{
self.free_element(self.entries[i]);
}
self.size = 0;
}
/**
* Same as pop() but pops the first value instead.
**/
macro AnyList.pop_first(&self, $Type)
{
if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
defer self.remove_at(0);
return *anycast(self.entries[0], $Type);
}
/**
* Same as pop_retained() but pops the first value instead.
**/
fn any! AnyList.pop_first_retained(&self)
{
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.
**/
fn any! AnyList.new_pop_first(&self, Allocator allocator = allocator::heap())
{
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]);
}
/**
* Same as temp_pop() but pops the first value instead.
**/
fn any! AnyList.temp_pop_first(&self) => self.new_pop_first(allocator::temp());
/**
* @require index < self.size
**/
fn void AnyList.remove_at(&self, usz index)
{
if (!--self.size || index == self.size) return;
self.free_element(self.entries[index]);
self.entries[index .. self.size - 1] = self.entries[index + 1 .. self.size];
}
fn void AnyList.add_all(&self, AnyList* other_list)
{
if (!other_list.size) return;
self.reserve(other_list.size);
foreach (value : other_list)
{
self.entries[self.size++] = allocator::clone_any(self.allocator, value);
}
}
/**
* Reverse the elements in a list.
**/
fn void AnyList.reverse(&self)
{
if (self.size < 2) return;
usz half = self.size / 2U;
usz end = self.size - 1;
for (usz i = 0; i < half; i++)
{
self.swap(i, end - i);
}
}
fn any[] AnyList.array_view(&self)
{
return self.entries[:self.size];
}
/**
* Push an element to the front of the list.
**/
macro void AnyList.push_front(&self, type)
{
self.insert_at(0, type);
}
/**
* @require index < self.size
**/
macro void AnyList.insert_at(&self, usz index, type) @local
{
any value = allocator::copy(self.allocator, type);
self.insert_at_internal(self, index, value);
}
/**
* @require index < self.size
**/
fn void AnyList.insert_at_internal(&self, usz index, any value) @local
{
self.ensure_capacity();
for (usz i = self.size; i > index; i--)
{
self.entries[i] = self.entries[i - 1];
}
self.size++;
self.entries[index] = value;
}
/**
* @require self.size > 0
**/
fn void AnyList.remove_last(&self)
{
self.free_element(self.entries[--self.size]);
}
/**
* @require self.size > 0
**/
fn void AnyList.remove_first(&self)
{
self.remove_at(0);
}
macro AnyList.first(&self, $Type)
{
return *anycast(self.first_any(), $Type);
}
fn any! AnyList.first_any(&self) @inline
{
return self.size ? self.entries[0] : IteratorResult.NO_MORE_ELEMENT?;
}
macro AnyList.last(&self, $Type)
{
return *anycast(self.last_any(), $Type);
}
fn any! AnyList.last_any(&self) @inline
{
return self.size ? self.entries[self.size - 1] : IteratorResult.NO_MORE_ELEMENT?;
}
fn bool AnyList.is_empty(&self) @inline
{
return !self.size;
}
fn usz AnyList.len(&self) @operator(len) @inline
{
return self.size;
}
/**
* @require index < self.size "Index out of range"
**/
macro AnyList.get(&self, usz index, $Type)
{
return *anycast(self.entries[index], $Type);
}
/**
* @require index < self.size "Index out of range"
**/
fn any AnyList.get_any(&self, usz index) @inline
{
return self.entries[index];
}
fn void AnyList.free(&self)
{
if (!self.allocator) return;
self.clear();
allocator::free(self.allocator, self.entries);
self.capacity = 0;
self.entries = null;
}
fn void AnyList.swap(&self, usz i, usz j)
{
any temp = self.entries[i];
self.entries[i] = self.entries[j];
self.entries[j] = temp;
}
/**
* @param filter "The function to determine if it should be removed or not"
* @return "the number of deleted elements"
**/
fn usz AnyList.remove_if(&self, AnyPredicate filter)
{
return self._remove_if(filter, false);
}
/**
* @param selection "The function to determine if it should be kept or not"
* @return "the number of deleted elements"
**/
fn usz AnyList.retain_if(&self, AnyPredicate selection)
{
return self._remove_if(selection, true);
}
macro usz AnyList._remove_if(&self, AnyPredicate filter, bool $invert) @local
{
usz size = self.size;
for (usz i = size, usz k = size; k > 0; k = i)
{
// Find last index of item to be deleted.
$if $invert:
while (i > 0 && !filter(&self.entries[i - 1])) i--;
$else
while (i > 0 && filter(&self.entries[i - 1])) i--;
$endif
// Remove the items from this index up to the one not to be deleted.
usz n = self.size - k;
for (usz j = i; j < k; j++) self.free_element(self.entries[j]);
self.entries[i:n] = self.entries[k:n];
self.size -= k - i;
// Find last index of item not to be deleted.
$if $invert:
while (i > 0 && filter(&self.entries[i - 1])) i--;
$else
while (i > 0 && !filter(&self.entries[i - 1])) i--;
$endif
}
return size - self.size;
}
fn usz AnyList.remove_using_test(&self, AnyTest filter, any context)
{
return self._remove_using_test(filter, false, context);
}
fn usz AnyList.retain_using_test(&self, AnyTest filter, any context)
{
return self._remove_using_test(filter, true, context);
}
macro usz AnyList._remove_using_test(&self, AnyTest filter, bool $invert, ctx) @local
{
usz size = self.size;
for (usz i = size, usz k = size; k > 0; k = i)
{
// Find last index of item to be deleted.
$if $invert:
while (i > 0 && !filter(&self.entries[i - 1], ctx)) i--;
$else
while (i > 0 && filter(&self.entries[i - 1], ctx)) i--;
$endif
// Remove the items from this index up to the one not to be deleted.
usz n = self.size - k;
for (usz j = i; j < k; j++) self.free_element(self.entries[j]);
self.entries[i:n] = self.entries[k:n];
self.size -= k - i;
// Find last index of item not to be deleted.
$if $invert:
while (i > 0 && filter(&self.entries[i - 1], ctx)) i--;
$else
while (i > 0 && !filter(&self.entries[i - 1], ctx)) i--;
$endif
}
return size - self.size;
}
/**
* Reserve at least min_capacity
**/
fn void AnyList.reserve(&self, usz min_capacity)
{
if (!min_capacity) return;
if (self.capacity >= min_capacity) return;
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;
}
macro any AnyList.@item_at(&self, usz index) @operator([])
{
return self.entries[index];
}
/**
* @require index <= self.size "Index out of range"
**/
macro void AnyList.set(&self, usz index, value)
{
if (index == self.size)
{
self.push(value);
return;
}
self.free_element(self.entries[index]);
self.entries[index] = allocator::copy(self.allocator, value);
}
fn void AnyList.ensure_capacity(&self, usz added = 1) @inline @private
{
usz new_size = self.size + added;
if (self.capacity >= new_size) return;
assert(new_size < usz.max / 2U);
usz new_capacity = self.capacity ? 2U * self.capacity : 16U;
while (new_capacity < new_size) new_capacity *= 2U;
self.reserve(new_capacity);
}

View File

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

View File

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

View File

@@ -1,172 +0,0 @@
// 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.
/**
* @require Enum.kindof == TypeKind.ENUM : "Only enums maybe be used with an enumset"
**/
module std::collections::enumset(<Enum>);
import std::io;
def EnumSetType = $typefrom(private::type_for_enum_elements(Enum.elements)) @private;
const IS_CHAR_ARRAY = Enum.elements > 128;
distinct EnumSet (Printable) = EnumSetType;
fn void EnumSet.add(&self, Enum v)
{
$if IS_CHAR_ARRAY:
(*self)[(usz)v / 8] |= (char)(1u << ((usz)v % 8));
$else
*self = (EnumSet)((EnumSetType)*self | 1u << (EnumSetType)v);
$endif
}
fn void EnumSet.clear(&self)
{
$if IS_CHAR_ARRAY:
*self = {};
$else
*self = 0;
$endif
}
fn bool EnumSet.remove(&self, Enum v)
{
$if IS_CHAR_ARRAY:
if (!self.has(v) @inline) return false;
(*self)[(usz)v / 8] &= (char)~(1u << ((usz)v % 8));
return true;
$else
EnumSetType old = (EnumSetType)*self;
EnumSetType new = old & ~(1u << (EnumSetType)v);
*self = (EnumSet)new;
return old != new;
$endif
}
fn bool EnumSet.has(&self, Enum v)
{
$if IS_CHAR_ARRAY:
return (bool)(((*self)[(usz)v / 8] << ((usz)v % 8)) & 0x01);
$else
return ((EnumSetType)*self & (1u << (EnumSetType)v)) != 0;
$endif
}
fn void EnumSet.add_all(&self, EnumSet s)
{
$if IS_CHAR_ARRAY:
foreach (i, c : s) (*self)[i] |= c;
$else
*self = (EnumSet)((EnumSetType)*self | (EnumSetType)s);
$endif
}
fn void EnumSet.retain_all(&self, EnumSet s)
{
$if IS_CHAR_ARRAY:
foreach (i, c : s) (*self)[i] &= c;
$else
*self = (EnumSet)((EnumSetType)*self & (EnumSetType)s);
$endif
}
fn void EnumSet.remove_all(&self, EnumSet s)
{
$if IS_CHAR_ARRAY:
foreach (i, c : s) (*self)[i] &= ~c;
$else
*self = (EnumSet)((EnumSetType)*self & ~(EnumSetType)s);
$endif
}
fn EnumSet EnumSet.and_of(&self, EnumSet s)
{
$if IS_CHAR_ARRAY:
EnumSet copy = *self;
copy.retain_all(s);
return copy;
$else
return (EnumSet)((EnumSetType)*self & (EnumSetType)s);
$endif
}
fn EnumSet EnumSet.or_of(&self, EnumSet s)
{
$if IS_CHAR_ARRAY:
EnumSet copy = *self;
copy.add_all(s);
return copy;
$else
return (EnumSet)((EnumSetType)*self | (EnumSetType)s);
$endif
}
fn EnumSet EnumSet.diff_of(&self, EnumSet s)
{
$if IS_CHAR_ARRAY:
EnumSet copy = *self;
copy.remove_all(s);
return copy;
$else
return (EnumSet)((EnumSetType)*self & ~(EnumSetType)s);
$endif
}
fn EnumSet EnumSet.xor_of(&self, EnumSet s)
{
$if IS_CHAR_ARRAY:
EnumSet copy = *self;
foreach (i, c : s) copy[i] ^= c;
return copy;
$else
return (EnumSet)((EnumSetType)*self ^ (EnumSetType)s);
$endif
}
fn usz! EnumSet.to_format(&set, Formatter* formatter) @dynamic
{
usz n = formatter.print("[")!;
bool found;
foreach (value : Enum.values)
{
if (!set.has(value)) continue;
if (found) n += formatter.print(", ")!;
found = true;
n += formatter.printf("%s", value)!;
}
n += formatter.print("]")!;
return n;
}
fn String EnumSet.to_new_string(&set, Allocator allocator = allocator::heap()) @dynamic
{
return string::new_format("%s", *set, .allocator = allocator);
}
fn String EnumSet.to_tstring(&set) @dynamic
{
return string::tformat("%s", *set);
}
module std::collections::enumset::private;
macro typeid type_for_enum_elements(usz $elements)
{
$switch
$case ($elements > 128):
return char[($elements + 7) / 8].typeid;
$case ($elements > 64):
return uint128.typeid;
$case ($elements > 32 || $$C_INT_SIZE > 32):
return ulong.typeid;
$case ($elements > 16 || $$C_INT_SIZE > 16):
return uint.typeid;
$case ($elements > 8 || $$C_INT_SIZE > 8):
return ushort.typeid;
$default:
return char.typeid;
$endswitch
}

View File

@@ -1,330 +0,0 @@
// 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>);
const ELEMENT_IS_EQUATABLE = types::is_equatable_type(Type);
struct Node @private
{
Node *next;
Node *prev;
Type value;
}
struct LinkedList
{
Allocator allocator;
usz size;
Node *_first;
Node *_last;
}
/**
* @param [&inout] allocator "The allocator to use, defaults to the heap allocator"
* @return "the initialized list"
**/
fn LinkedList* LinkedList.new_init(&self, Allocator allocator = allocator::heap())
{
*self = { .allocator = allocator };
return self;
}
fn LinkedList* LinkedList.temp_init(&self)
{
return self.new_init(allocator::temp()) @inline;
}
/**
* @require self.allocator
**/
macro void LinkedList.free_node(&self, Node* node) @private
{
allocator::free(self.allocator, node);
}
macro Node* LinkedList.alloc_node(&self) @private
{
if (!self.allocator) self.allocator = allocator::heap();
return allocator::alloc(self.allocator, Node);
}
fn void LinkedList.push_front(&self, Type value)
{
Node *first = self._first;
Node *new_node = self.alloc_node();
*new_node = { .next = first, .value = value };
self._first = new_node;
if (!first)
{
self._last = new_node;
}
else
{
first.prev = new_node;
}
self.size++;
}
fn void LinkedList.push(&self, Type value)
{
Node *last = self._last;
Node *new_node = self.alloc_node();
*new_node = { .prev = last, .value = value };
self._last = new_node;
if (!last)
{
self._first = new_node;
}
else
{
last.next = new_node;
}
self.size++;
}
fn Type! LinkedList.peek(&self) => self.first() @inline;
fn Type! LinkedList.peek_last(&self) => self.last() @inline;
fn Type! LinkedList.first(&self)
{
if (!self._first) return IteratorResult.NO_MORE_ELEMENT?;
return self._first.value;
}
fn Type! LinkedList.last(&self)
{
if (!self._last) return IteratorResult.NO_MORE_ELEMENT?;
return self._last.value;
}
fn void LinkedList.free(&self) => self.clear() @inline;
fn void LinkedList.clear(&self)
{
for (Node* node = self._first; node != null;)
{
Node* next = node.next;
self.free_node(node);
node = next;
}
self._first = null;
self._last = null;
self.size = 0;
}
fn usz LinkedList.len(&self) @inline => self.size;
/**
* @require index < self.size
**/
macro Node* LinkedList.node_at_index(&self, usz index)
{
if (index * 2 >= self.size)
{
Node* node = self._last;
index = self.size - index - 1;
while (index--) node = node.prev;
return node;
}
Node* node = self._first;
while (index--) node = node.next;
return node;
}
/**
* @require index < self.size
**/
fn Type LinkedList.get(&self, usz index)
{
return self.node_at_index(index).value;
}
/**
* @require index < self.size
**/
fn void LinkedList.set(&self, usz index, Type element)
{
self.node_at_index(index).value = element;
}
/**
* @require index < self.size
**/
fn void LinkedList.remove_at(&self, usz index)
{
self.unlink(self.node_at_index(index));
}
/**
* @require index <= self.size
**/
fn void LinkedList.insert_at(&self, usz index, Type element)
{
switch (index)
{
case 0:
self.push_front(element);
case self.size:
self.push(element);
default:
self.link_before(self.node_at_index(index), element);
}
}
/**
* @require succ != null
**/
fn void LinkedList.link_before(&self, Node *succ, Type value) @private
{
Node* pred = succ.prev;
Node* new_node = self.alloc_node();
*new_node = { .prev = pred, .next = succ, .value = value };
succ.prev = new_node;
if (!pred)
{
self._first = new_node;
}
else
{
pred.next = new_node;
}
self.size++;
}
/**
* @require self._first
**/
fn void LinkedList.unlink_first(&self) @private
{
Node* f = self._first;
Node* next = f.next;
self.free_node(f);
self._first = next;
if (!next)
{
self._last = null;
}
else
{
next.prev = null;
}
self.size--;
}
fn usz LinkedList.remove(&self, Type t) @if(ELEMENT_IS_EQUATABLE)
{
usz start = self.size;
Node* node = self._first;
while (node)
{
switch
{
case equals(node.value, t):
Node* next = node.next;
self.unlink(node);
node = next;
default:
node = node.next;
}
}
return start - self.size;
}
fn Type! LinkedList.pop(&self)
{
if (!self._last) return IteratorResult.NO_MORE_ELEMENT?;
defer self.unlink_last();
return self._last.value;
}
fn Type! LinkedList.pop_front(&self)
{
if (!self._first) return IteratorResult.NO_MORE_ELEMENT?;
defer self.unlink_first();
return self._first.value;
}
fn void! LinkedList.remove_last(&self) @maydiscard
{
if (!self._first) return IteratorResult.NO_MORE_ELEMENT?;
self.unlink_last();
}
fn void! LinkedList.remove_first(&self) @maydiscard
{
if (!self._first) return IteratorResult.NO_MORE_ELEMENT?;
self.unlink_first();
}
fn bool LinkedList.remove_first_match(&self, Type t) @if(ELEMENT_IS_EQUATABLE)
{
for (Node* node = self._first; node != null; node = node.next)
{
if (node.value == t)
{
self.unlink(node);
return true;
}
}
return false;
}
fn bool LinkedList.remove_last_match(&self, Type t) @if(ELEMENT_IS_EQUATABLE)
{
for (Node* node = self._last; node != null; node = node.prev)
{
if (node.value == t)
{
self.unlink(node);
return true;
}
}
return false;
}
/**
* @require self._last
**/
fn void LinkedList.unlink_last(&self) @inline @private
{
Node* l = self._last;
Node* prev = l.prev;
self._last = prev;
self.free_node(l);
if (!prev)
{
self._first = null;
}
else
{
prev.next = null;
}
self.size--;
}
/**
* @require x != null
**/
fn void LinkedList.unlink(&self, Node* x) @private
{
Node* next = x.next;
Node* prev = x.prev;
if (!prev)
{
self._first = next;
}
else
{
prev.next = next;
}
if (!next)
{
self._last = prev;
}
else
{
next.prev = prev;
}
self.free_node(x);
self.size--;
}

View File

@@ -1,502 +0,0 @@
// 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>);
import std::io,std::math;
def ElementPredicate = fn bool(Type *type);
def ElementTest = fn bool(Type *type, any context);
const ELEMENT_IS_EQUATABLE = types::is_equatable_type(Type);
const ELEMENT_IS_POINTER = Type.kindof == POINTER;
struct List (Printable)
{
usz size;
usz capacity;
Allocator allocator;
Type *entries;
}
/**
* @param initial_capacity "The initial capacity to reserve"
* @param [&inout] allocator "The allocator to use, defaults to the heap allocator"
**/
fn List* List.new_init(&self, usz initial_capacity = 16, Allocator allocator = allocator::heap())
{
self.allocator = allocator;
self.size = 0;
if (initial_capacity > 0)
{
initial_capacity = math::next_power_of_2(initial_capacity);
self.entries = allocator::malloc_aligned(allocator, Type.sizeof * initial_capacity, .alignment = Type[1].alignof)!!;
}
else
{
self.entries = null;
}
self.capacity = initial_capacity;
return self;
}
/**
* Initialize the list using the temp allocator.
*
* @param initial_capacity "The initial capacity to reserve"
**/
fn List* List.temp_init(&self, usz initial_capacity = 16)
{
return self.new_init(initial_capacity, allocator::temp()) @inline;
}
/**
* @require self.size == 0 "The List must be empty"
**/
fn void List.init_wrapping_array(&self, Type[] types, Allocator allocator = allocator::heap())
{
self.allocator = allocator;
self.size = types.len;
self.capacity = types.len;
self.entries = types.ptr;
}
fn usz! List.to_format(&self, Formatter* formatter) @dynamic
{
switch (self.size)
{
case 0:
return formatter.print("[]")!;
case 1:
return formatter.printf("[%s]", self.entries[0])!;
default:
usz n = formatter.print("[")!;
foreach (i, element : self.entries[:self.size])
{
if (i != 0) formatter.print(", ")!;
n += formatter.printf("%s", element)!;
}
n += formatter.print("]")!;
return n;
}
}
fn String List.to_new_string(&self, Allocator allocator = allocator::heap()) @dynamic
{
return string::new_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.ensure_capacity();
self.entries[self.size++] = element;
}
fn Type! List.pop(&self)
{
if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
return self.entries[--self.size];
}
fn void List.clear(&self)
{
self.size = 0;
}
/**
* @require self.size > 0
**/
fn Type! List.pop_first(&self)
{
if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
defer self.remove_at(0);
return self.entries[0];
}
/**
* @require index < self.size
**/
fn void List.remove_at(&self, usz index)
{
if (!--self.size || index == self.size) return;
self.entries[index .. self.size - 1] = self.entries[index + 1 .. self.size];
}
fn void List.add_all(&self, List* other_list)
{
if (!other_list.size) return;
self.reserve(other_list.size);
foreach (&value : other_list)
{
self.entries[self.size++] = *value;
}
}
fn Type[] List.to_new_array(&self, Allocator allocator = allocator::heap())
{
if (!self.size) return Type[] {};
Type[] result = allocator::alloc_array(allocator, Type, self.size);
result[..] = self.entries[:self.size];
return result;
}
fn Type[] List.to_tarray(&self)
{
return self.to_new_array(allocator::temp());
}
/**
* Reverse the elements in a list.
**/
fn void List.reverse(&self)
{
if (self.size < 2) return;
usz half = self.size / 2U;
usz end = self.size - 1;
for (usz i = 0; i < half; i++)
{
@swap(self.entries[i], self.entries[end - i]);
}
}
fn Type[] List.array_view(&self)
{
return self.entries[:self.size];
}
fn void List.add_array(&self, Type[] array)
{
if (!array.len) return;
self.reserve(array.len);
foreach (&value : array)
{
self.entries[self.size++] = *value;
}
}
fn void List.push_front(&self, Type type) @inline
{
self.insert_at(0, type);
}
/**
* @require index < self.size
**/
fn void List.insert_at(&self, usz index, Type type)
{
self.ensure_capacity();
for (usz i = self.size; i > index; i--)
{
self.entries[i] = self.entries[i - 1];
}
self.size++;
self.entries[index] = type;
}
/**
* @require index < self.size
**/
fn void List.set_at(&self, usz index, Type type)
{
self.entries[index] = type;
}
fn void! List.remove_last(&self) @maydiscard
{
if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
self.size--;
}
fn void! List.remove_first(&self) @maydiscard
{
if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
self.remove_at(0);
}
fn Type! List.first(&self)
{
if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
return self.entries[0];
}
fn Type! List.last(&self)
{
if (!self.size) return IteratorResult.NO_MORE_ELEMENT?;
return self.entries[self.size - 1];
}
fn bool List.is_empty(&self) @inline
{
return !self.size;
}
fn usz List.byte_size(&self) @inline
{
return Type.sizeof * self.size;
}
fn usz List.len(&self) @operator(len) @inline
{
return self.size;
}
fn Type List.get(&self, usz index) @inline
{
return self.entries[index];
}
fn void List.free(&self)
{
if (!self.allocator) return;
allocator::free_aligned(self.allocator, self.entries);
self.capacity = 0;
self.size = 0;
self.entries = null;
}
fn void List.swap(&self, usz i, usz j)
{
@swap(self.entries[i], self.entries[j]);
}
/**
* @param filter "The function to determine if it should be removed or not"
* @return "the number of deleted elements"
**/
fn usz List.remove_if(&self, ElementPredicate filter)
{
return self._remove_if(filter, false);
}
/**
* @param selection "The function to determine if it should be kept or not"
* @return "the number of deleted elements"
**/
fn usz List.retain_if(&self, ElementPredicate selection)
{
return self._remove_if(selection, true);
}
macro usz List._remove_if(&self, ElementPredicate filter, bool $invert) @local
{
usz size = self.size;
for (usz i = size, usz k = size; k > 0; k = i)
{
// Find last index of item to be deleted.
$if $invert:
while (i > 0 && !filter(&self.entries[i - 1])) i--;
$else
while (i > 0 && filter(&self.entries[i - 1])) i--;
$endif
// Remove the items from this index up to the one not to be deleted.
usz n = self.size - k;
self.entries[i:n] = self.entries[k:n];
self.size -= k - i;
// Find last index of item not to be deleted.
$if $invert:
while (i > 0 && filter(&self.entries[i - 1])) i--;
$else
while (i > 0 && !filter(&self.entries[i - 1])) i--;
$endif
}
return size - self.size;
}
fn usz List.remove_using_test(&self, ElementTest filter, any context)
{
return self._remove_using_test(filter, false, context);
}
fn usz List.retain_using_test(&self, ElementTest filter, any context)
{
return self._remove_using_test(filter, true, context);
}
macro usz List._remove_using_test(&self, ElementTest filter, bool $invert, ctx) @local
{
usz size = self.size;
for (usz i = size, usz k = size; k > 0; k = i)
{
// Find last index of item to be deleted.
$if $invert:
while (i > 0 && !filter(&self.entries[i - 1], ctx)) i--;
$else
while (i > 0 && filter(&self.entries[i - 1], ctx)) i--;
$endif
// Remove the items from this index up to the one not to be deleted.
usz n = self.size - k;
self.entries[i:n] = self.entries[k:n];
self.size -= k - i;
// Find last index of item not to be deleted.
$if $invert:
while (i > 0 && filter(&self.entries[i - 1], ctx)) i--;
$else
while (i > 0 && !filter(&self.entries[i - 1], ctx)) i--;
$endif
}
return size - self.size;
}
/**
* Reserve at least min_capacity
**/
fn void List.reserve(&self, usz min_capacity)
{
if (!min_capacity) return;
if (self.capacity >= min_capacity) return;
if (!self.allocator) self.allocator = allocator::heap();
min_capacity = math::next_power_of_2(min_capacity);
self.entries = allocator::realloc_aligned(self.allocator, self.entries, Type.sizeof * min_capacity, .alignment = Type[1].alignof) ?? null;
self.capacity = min_capacity;
}
macro Type List.@item_at(&self, usz index) @operator([])
{
return self.entries[index];
}
fn Type* List.get_ref(&self, usz index) @operator(&[]) @inline
{
return &self.entries[index];
}
fn void List.set(&self, usz index, Type value) @operator([]=)
{
self.entries[index] = value;
}
fn void List.ensure_capacity(&self, usz added = 1) @inline @private
{
usz new_size = self.size + added;
if (self.capacity >= new_size) return;
assert(new_size < usz.max / 2U);
usz new_capacity = self.capacity ? 2U * self.capacity : 16U;
while (new_capacity < new_size) new_capacity *= 2U;
self.reserve(new_capacity);
}
// Functions for equatable types
fn usz! List.index_of(&self, Type type) @if(ELEMENT_IS_EQUATABLE)
{
foreach (i, v : self)
{
if (equals(v, type)) return i;
}
return SearchResult.MISSING?;
}
fn usz! List.rindex_of(&self, Type type) @if(ELEMENT_IS_EQUATABLE)
{
foreach_r (i, v : self)
{
if (equals(v, type)) return i;
}
return SearchResult.MISSING?;
}
fn bool List.equals(&self, List other_list) @if(ELEMENT_IS_EQUATABLE)
{
if (self.size != other_list.size) return false;
foreach (i, v : self)
{
if (!equals(v, other_list.entries[i])) return false;
}
return true;
}
/**
* Check for presence of a value in a list.
*
* @param [&in] self "the list to find elements in"
* @param value "The value to search for"
* @return "True if the value is found, false otherwise"
**/
fn bool List.contains(&self, Type value) @if(ELEMENT_IS_EQUATABLE)
{
foreach (i, v : self)
{
if (equals(v, value)) return true;
}
return false;
}
/**
* @param [&inout] self "The list to remove elements from"
* @param value "The value to remove"
* @return "true if the value was found"
**/
fn bool List.remove_last_match(&self, Type value) @if(ELEMENT_IS_EQUATABLE)
{
return @ok(self.remove_at(self.rindex_of(value)));
}
/**
* @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)
{
return @ok(self.remove_at(self.index_of(value)));
}
/**
* @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)
{
usz size = self.size;
for (usz i = size; i > 0; i--)
{
if (!equals(self.entries[i - 1], value)) continue;
for (usz j = i; j < self.size; j++)
{
self.entries[j - 1] = self.entries[j];
}
self.size--;
}
return size - self.size;
}
fn void List.remove_all_from(&self, List* other_list) @if(ELEMENT_IS_EQUATABLE)
{
if (!other_list.size) return;
foreach (v : other_list) self.remove_all_matches(v);
}
/**
* @param [&in] self
* @return "The number non-null values in the list"
**/
fn usz List.compact_count(&self) @if(ELEMENT_IS_POINTER)
{
usz vals = 0;
foreach (v : self) if (v) vals++;
return vals;
}
fn usz List.compact(&self) @if(ELEMENT_IS_POINTER)
{
usz size = self.size;
for (usz i = size; i > 0; i--)
{
if (self.entries[i - 1]) continue;
for (usz j = i; j < size; j++)
{
self.entries[j - 1] = self.entries[j];
}
self.size--;
}
return size - self.size;
}

View File

@@ -1,424 +0,0 @@
// Copyright (c) 2023 Christoffer Lerno. All rights reserved.
// Use of this source code is governed by the MIT license
// a copy of which can be found in the LICENSE_STDLIB file.
module std::collections::map(<Key, Value>);
import std::math;
const uint DEFAULT_INITIAL_CAPACITY = 16;
const uint MAXIMUM_CAPACITY = 1u << 31;
const float DEFAULT_LOAD_FACTOR = 0.75;
const VALUE_IS_EQUATABLE = Value.is_eq;
const bool COPY_KEYS = types::implements_copy(Key);
struct HashMap
{
Entry*[] table;
Allocator allocator;
uint count; // Number of elements
uint threshold; // Resize limit
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 = allocator::heap())
{
capacity = math::next_power_of_2(capacity);
self.allocator = allocator;
self.load_factor = load_factor;
self.threshold = (uint)(capacity * load_factor);
self.table = allocator::new_array(allocator, Entry*, capacity);
return self;
}
/**
* @require capacity > 0 "The capacity must be 1 or higher"
* @require load_factor > 0.0 "The load factor must be higher than 0"
* @require !self.allocator "Map was already initialized"
* @require capacity < MAXIMUM_CAPACITY "Capacity cannot exceed maximum"
**/
fn HashMap* HashMap.temp_init(&self, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
{
return self.new_init(capacity, load_factor, allocator::temp()) @inline;
}
/**
* Has this hash map been initialized yet?
*
* @param [&in] map "The hash map we are testing"
* @return "Returns true if it has been initialized, false otherwise"
**/
fn bool HashMap.is_initialized(&map)
{
return (bool)map.allocator;
}
/**
* @param [&inout] allocator "The allocator to use"
* @param [&in] other_map "The map to copy from."
**/
fn HashMap* HashMap.new_init_from_map(&self, HashMap* other_map, Allocator allocator = allocator::heap())
{
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."
**/
fn HashMap* HashMap.temp_init_from_map(&map, HashMap* other_map)
{
return map.new_init_from_map(other_map, allocator::temp()) @inline;
}
fn bool HashMap.is_empty(&map) @inline
{
return !map.count;
}
fn usz HashMap.len(&map) @inline
{
return map.count;
}
fn Value*! HashMap.get_ref(&map, Key key)
{
if (!map.count) return SearchResult.MISSING?;
uint hash = rehash(key.hash());
for (Entry *e = map.table[index_for(hash, map.table.len)]; e != null; e = e.next)
{
if (e.hash == hash && equals(key, e.key)) return &e.value;
}
return SearchResult.MISSING?;
}
fn Entry*! HashMap.get_entry(&map, Key key)
{
if (!map.count) return SearchResult.MISSING?;
uint hash = rehash(key.hash());
for (Entry *e = map.table[index_for(hash, map.table.len)]; e != null; e = e.next)
{
if (e.hash == hash && equals(key, e.key)) return e;
}
return SearchResult.MISSING?;
}
/**
* Get the value or update and
* @require $assignable(#expr, Value)
**/
macro Value HashMap.@get_or_set(&map, Key key, Value #expr)
{
if (!map.count)
{
Value val = #expr;
map.set(key, val);
return val;
}
uint hash = rehash(key.hash());
uint index = index_for(hash, map.table.len);
for (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! HashMap.get(&map, Key key) @operator([])
{
return *map.get_ref(key) @inline;
}
fn bool HashMap.has_key(&map, Key key)
{
return @ok(map.get_ref(key));
}
fn bool HashMap.set(&map, Key key, Value value) @operator([]=)
{
// If the map isn't initialized, use the defaults to initialize it.
if (!map.allocator)
{
map.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)
{
if (e.hash == hash && equals(key, e.key))
{
e.value = value;
return true;
}
}
map.add_entry(hash, key, value, index);
return false;
}
fn void! HashMap.remove(&map, Key key) @maydiscard
{
if (!map.remove_entry_for_key(key)) return SearchResult.MISSING?;
}
fn void HashMap.clear(&map)
{
if (!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 HashMap.free(&map)
{
if (!map.allocator) return;
map.clear();
map.free_internal(map.table.ptr);
map.table = {};
}
fn Key[] HashMap.key_tlist(&map)
{
return map.key_new_list(allocator::temp()) @inline;
}
fn Key[] HashMap.key_new_list(&map, Allocator allocator = allocator::heap())
{
if (!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 HashMap.@each(map; @body(key, value))
{
map.@each_entry(; Entry* entry) {
@body(entry.key, entry.value);
};
}
macro HashMap.@each_entry(map; @body(entry))
{
if (map.count)
{
foreach (Entry* entry : map.table)
{
while (entry)
{
@body(entry);
entry = entry.next;
}
}
}
}
fn Value[] HashMap.value_tlist(&map)
{
return map.value_new_list(allocator::temp()) @inline;
}
fn Value[] HashMap.value_new_list(&map, Allocator allocator = allocator::heap())
{
if (!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 HashMap.has_value(&map, Value v) @if(VALUE_IS_EQUATABLE)
{
if (!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 HashMap.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 HashMap.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 HashMap.transfer(&map, Entry*[] new_table) @private
{
Entry*[] src = map.table;
uint new_capacity = new_table.len;
foreach (uint j, Entry *e : src)
{
if (!e) continue;
do
{
Entry* next = e.next;
uint i = index_for(e.hash, new_capacity);
e.next = new_table[i];
new_table[i] = e;
e = next;
}
while (e);
}
}
fn void HashMap.put_all_for_create(&map, HashMap* other_map) @private
{
if (!other_map.count) return;
foreach (Entry *e : other_map.table)
{
if (!e) continue;
map.put_for_create(e.key, e.value);
}
}
fn void HashMap.put_for_create(&map, Key key, Value value) @private
{
uint hash = rehash(key.hash());
uint i = index_for(hash, map.table.len);
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 HashMap.free_internal(&map, void* ptr) @inline @private
{
allocator::free(map.allocator, ptr);
}
fn bool HashMap.remove_entry_for_key(&map, Key key) @private
{
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 HashMap.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 HashMap.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,19 +0,0 @@
module std::collections::maybe(<Type>);
struct Maybe
{
Type value;
bool has_value;
}
fn Maybe value(Type val)
{
return { .value = val, .has_value = true };
}
const Maybe EMPTY = { };
macro Type! Maybe.get(self)
{
return self.has_value ? self.value : SearchResult.MISSING?;
}

View File

@@ -1,470 +0,0 @@
// Copyright (c) 2023 Christoffer Lerno. All rights reserved.
// Use of this source code is governed by the MIT license
// a copy of which can be found in the LICENSE_STDLIB file.
module std::collections::object;
import std::collections::map, std::collections::list, std::io;
const Object TRUE_OBJECT = { .b = true, .type = bool.typeid };
const Object FALSE_OBJECT = { .b = false, .type = bool.typeid };
const Object NULL_OBJECT = { .type = void*.typeid };
struct Object (Printable)
{
typeid type;
Allocator allocator;
union
{
uint128 i;
double f;
bool b;
String s;
void* other;
ObjectInternalList array;
ObjectInternalMap map;
}
}
fn usz! Object.to_format(&self, Formatter* formatter) @dynamic
{
switch (self.type)
{
case void:
return formatter.printf("{}")!;
case void*:
return formatter.printf("null")!;
case String:
return formatter.printf(`"%s"`, self.s)!;
case bool:
return formatter.printf(self.b ? "true" : "false")!;
case ObjectInternalList:
usz n = formatter.printf("[")!;
foreach (i, ol : self.array)
{
if (i > 0) n += formatter.printf(",")!;
n += ol.to_format(formatter)!;
}
n += formatter.printf("]")!;
return n;
case ObjectInternalMap:
usz n = formatter.printf("{")!;
@pool()
{
foreach (i, key : self.map.key_tlist())
{
if (i > 0) n += formatter.printf(",")!;
n += formatter.printf(`"%s":`, key)!;
n += self.map.get(key).to_format(formatter)!;
}
};
n += formatter.printf("}")!;
return n;
default:
switch (self.type.kindof)
{
case SIGNED_INT:
return formatter.printf("%d", self.i)!;
case UNSIGNED_INT:
return formatter.printf("%d", (uint128)self.i)!;
case FLOAT:
return formatter.printf("%d", self.f)!;
case ENUM:
return formatter.printf("%d", self.i)!;
default:
return formatter.printf("<>")!;
}
}
}
fn Object* new_obj(Allocator allocator)
{
return allocator::new(allocator, Object, { .allocator = allocator, .type = void.typeid });
}
fn Object* new_null()
{
return &NULL_OBJECT;
}
fn Object* new_int(int128 i, Allocator allocator)
{
return allocator::new(allocator, Object, { .i = i, .allocator = allocator, .type = int128.typeid });
}
macro Object* new_enum(e, Allocator allocator)
{
return allocator::new(allocator, Object, { .i = (int128)e, .allocator = allocator, .type = @typeid(e) });
}
fn Object* new_float(double f, Allocator allocator)
{
return allocator::new(allocator, Object, { .f = f, .allocator = allocator, .type = double.typeid });
}
fn Object* new_string(String s, Allocator allocator)
{
return allocator::new(allocator, Object, { .s = s.copy(allocator), .allocator = allocator, .type = String.typeid });
}
fn Object* new_bool(bool b)
{
return b ? &TRUE_OBJECT : &FALSE_OBJECT;
}
fn void Object.free(&self)
{
switch (self.type)
{
case void:
break;
case String:
allocator::free(self.allocator, self.s);
case ObjectInternalList:
foreach (ol : self.array)
{
ol.free();
}
self.array.free();
case ObjectInternalMap:
self.map.@each_entry(; ObjectInternalMapEntry* entry) {
allocator::free(self.allocator, entry.key);
entry.value.free();
};
default:
break;
}
if (self.allocator) allocator::free(self.allocator, self);
}
fn bool Object.is_null(&self) @inline => self == &NULL_OBJECT;
fn bool Object.is_empty(&self) @inline => self.type == void.typeid;
fn bool Object.is_map(&self) @inline => self.type == ObjectInternalMap.typeid;
fn bool Object.is_array(&self) @inline => self.type == ObjectInternalList.typeid;
fn bool Object.is_bool(&self) @inline => self.type == bool.typeid;
fn bool Object.is_string(&self) @inline => self.type == String.typeid;
fn bool Object.is_float(&self) @inline => self.type == double.typeid;
fn bool Object.is_int(&self) @inline => self.type == int128.typeid;
fn bool Object.is_keyable(&self) => self.is_empty() || self.is_map();
fn bool Object.is_indexable(&self) => self.is_empty() || self.is_array();
/**
* @require self.is_keyable()
**/
fn void Object.init_map_if_needed(&self) @private
{
if (self.is_empty())
{
self.type = ObjectInternalMap.typeid;
self.map.new_init(.allocator = self.allocator);
}
}
/**
* @require self.is_indexable()
**/
fn void Object.init_array_if_needed(&self) @private
{
if (self.is_empty())
{
self.type = ObjectInternalList.typeid;
self.array.new_init(.allocator = self.allocator);
}
}
/**
* @require self.is_keyable()
**/
fn void Object.set_object(&self, String key, Object* new_object) @private
{
self.init_map_if_needed();
ObjectInternalMapEntry*! entry = self.map.get_entry(key);
defer
{
(void)allocator::free(self.allocator, entry.key);
(void)entry.value.free();
}
self.map.set(key.copy(self.map.allocator), new_object);
}
macro Object* Object.object_from_value(&self, value) @private
{
var $Type = $typeof(value);
$switch
$case types::is_int($Type):
return new_int(value, self.allocator);
$case types::is_float($Type):
return new_float(value, self.allocator);
$case $Type.typeid == String.typeid:
return new_string(value, self.allocator);
$case $Type.typeid == bool.typeid:
return new_bool(value);
$case $Type.typeid == Object*.typeid:
return value;
$case $Type.typeid == void*.typeid:
if (value != null) return CastResult.TYPE_MISMATCH?;
return &NULL_OBJECT;
$case $assignable(value, String):
return new_string(value, self.allocator);
$default:
$error "Unsupported object type.";
$endswitch
}
macro Object* Object.set(&self, String key, value)
{
Object* val = self.object_from_value(value);
self.set_object(key, val);
return val;
}
/**
* @require self.is_indexable()
**/
macro Object* Object.set_at(&self, usz index, String key, value)
{
Object* val = self.object_from_value(value);
self.set_object_at(key, index, val);
return val;
}
/**
* @require self.is_indexable()
* @ensure return != null
**/
macro Object* Object.push(&self, value)
{
Object* val = self.object_from_value(value);
self.push_object(val);
return val;
}
/**
* @require self.is_keyable()
**/
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);
/**
* @require self.is_indexable()
**/
fn Object* Object.get_at(&self, usz index)
{
return self.array.get(index);
}
/**
* @require self.is_indexable()
**/
fn usz Object.get_len(&self)
{
return self.array.len();
}
/**
* @require self.is_indexable()
**/
fn void Object.push_object(&self, Object* to_append)
{
self.init_array_if_needed();
self.array.push(to_append);
}
/**
* @require self.is_indexable()
**/
fn void Object.set_object_at(&self, usz index, Object* to_set)
{
self.init_array_if_needed();
while (self.array.len() < index)
{
self.array.push(&NULL_OBJECT);
}
if (self.array.len() == index)
{
self.array.push(to_set);
return;
}
self.array.get(index).free();
self.array.set_at(index, to_set);
}
/**
* @require $Type.kindof.is_int() "Expected an integer type."
**/
macro get_integer_value(Object* value, $Type)
{
if (value.is_float())
{
return ($Type)value.f;
}
if (value.is_string())
{
$if $Type.kindof == TypeKind.SIGNED_INT:
return ($Type)value.s.to_int128();
$else
return ($Type)value.s.to_uint128();
$endif
}
if (!value.is_int()) return NumberConversion.MALFORMED_INTEGER?;
return ($Type)value.i;
}
/**
* @require self.is_indexable()
* @require $Type.kindof.is_int() : "Expected an integer type"
**/
macro Object.get_integer_at(&self, $Type, usz index) @private
{
return get_integer_value(self.get_at(index), $Type);
}
/**
* @require self.is_keyable()
* @require $Type.kindof.is_int() : "Expected an integer type"
**/
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_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_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)
{
Object* value = self.get(key)!;
if (!value.is_string()) return CastResult.TYPE_MISMATCH?;
return value.s;
}
/**
* @require self.is_indexable()
**/
fn String! Object.get_string_at(&self, usz index)
{
Object* value = self.get_at(index);
if (!value.is_string()) return CastResult.TYPE_MISMATCH?;
return value.s;
}
/**
* @require self.is_keyable()
**/
macro String! Object.get_enum(&self, $EnumType, String key)
{
Object value = self.get(key)!;
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)
{
Object value = self.get_at(index);
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)
{
Object* value = self.get(key)!;
if (!value.is_bool()) return CastResult.TYPE_MISMATCH?;
return value.b;
}
/**
* @require self.is_indexable()
**/
fn bool! Object.get_bool_at(&self, usz index)
{
Object* value = self.get_at(index);
if (!value.is_bool()) return CastResult.TYPE_MISMATCH?;
return value.b;
}
/**
* @require self.is_keyable()
**/
fn double! Object.get_float(&self, String key)
{
Object* value = self.get(key)!;
switch (value.type.kindof)
{
case SIGNED_INT:
return (double)value.i;
case UNSIGNED_INT:
return (double)(uint128)value.i;
case FLOAT:
return value.f;
default:
return CastResult.TYPE_MISMATCH?;
}
}
/**
* @require self.is_indexable()
**/
fn double! Object.get_float_at(&self, usz index)
{
Object* value = self.get_at(index);
switch (value.type.kindof)
{
case SIGNED_INT:
return (double)value.i;
case UNSIGNED_INT:
return (double)(uint128)value.i;
case FLOAT:
return value.f;
default:
return CastResult.TYPE_MISMATCH?;
}
}
fn Object* Object.get_or_create_obj(&self, String key)
{
if (try obj = self.get(key) && !obj.is_null()) return obj;
Object* container = new_obj(self.allocator);
self.set(key, container);
return container;
}
def ObjectInternalMap = HashMap(<String, Object*>) @private;
def ObjectInternalList = List(<Object*>) @private;
def ObjectInternalMapEntry = Entry(<String, Object*>) @private;

View File

@@ -1,148 +0,0 @@
// priorityqueue.c3
// A priority queue using a classic binary heap for C3.
//
// 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
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
module std::collections::priorityqueue(<Type>);
import std::collections::priorityqueue::private;
distinct PriorityQueue = inline PrivatePriorityQueue(<Type, false>);
distinct PriorityQueueMax = inline PrivatePriorityQueue(<Type, true>);
module std::collections::priorityqueue::private(<Type, MAX>);
import std::collections::list, std::io;
def Heap = List(<Type>);
struct PrivatePriorityQueue (Printable)
{
Heap heap;
}
fn void PrivatePriorityQueue.new_init(&self, usz initial_capacity = 16, Allocator allocator = allocator::heap()) @inline
{
self.heap.new_init(initial_capacity, allocator);
}
fn void PrivatePriorityQueue.temp_init(&self, usz initial_capacity = 16) @inline
{
self.heap.new_init(initial_capacity, allocator::temp()) @inline;
}
fn void PrivatePriorityQueue.push(&self, Type element)
{
self.heap.push(element);
usz i = self.heap.len() - 1;
while (i > 0)
{
usz parent = (i - 1) / 2;
Type item = self.heap[i];
Type parent_item = self.heap[parent];
$if MAX:
bool ok = greater(item, parent_item);
$else
bool ok = less(item, parent_item);
$endif
if (!ok) break;
self.heap.swap(i, parent);
i = parent;
}
}
/**
* @require self != null
*/
fn Type! PrivatePriorityQueue.pop(&self)
{
usz i = 0;
usz len = self.heap.len();
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)
{
usz j = 2 * i + 1;
Type left = self.heap[j];
Type item = self.heap[i];
switch
{
case j + 1 < new_count:
Type right = self.heap[j + 1];
$if MAX:
if (!greater(right, left)) nextcase;
if (!greater(right, item)) break OUTER;
$else
if (!greater(left, right)) nextcase;
if (!greater(item, right)) break OUTER;
$endif
j++;
default:
$if MAX:
if (!greater(left, item)) break OUTER;
$else
if (!greater(item, left)) break OUTER;
$endif
}
self.heap.swap(i, j);
i = j;
}
return self.heap.pop();
}
fn Type! PrivatePriorityQueue.first(&self)
{
if (!self.len()) return IteratorResult.NO_MORE_ELEMENT?;
return self.heap.get(0);
}
fn void PrivatePriorityQueue.free(&self)
{
self.heap.free();
}
fn usz PrivatePriorityQueue.len(&self) @operator(len)
{
return self.heap.len();
}
fn bool PrivatePriorityQueue.is_empty(&self)
{
return self.heap.is_empty();
}
/**
* @require index < self.len()
*/
fn Type PrivatePriorityQueue.get(&self, usz index) @operator([])
{
return self.heap[index];
}
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,85 +0,0 @@
/**
* @require Type.is_ordered : "The type must be ordered"
**/
module std::collections::range(<Type>);
import std::io;
struct Range (Printable)
{
Type start;
Type end;
}
fn usz Range.len(&self) @operator(len)
{
if (self.end < self.start) return 0;
return (usz)(self.end - self.start) + 1;
}
fn bool Range.contains(&self, Type value) @inline
{
return value >= self.start && value <= self.end;
}
/**
* @require index < self.len() : "Can't index into an empty range"
**/
fn Type Range.get(&self, usz index) @operator([])
{
return (Type)(self.start + (usz)index);
}
fn String Range.to_new_string(&self, Allocator allocator = allocator::heap()) @dynamic
{
return string::new_format("[%s..%s]", self.start, self.end, .allocator = allocator);
}
fn String Range.to_tstring(&self)
{
return self.to_new_string(allocator::temp());
}
fn usz! Range.to_format(&self, Formatter* formatter) @dynamic
{
return formatter.printf("[%s..%s]", self.start, self.end)!;
}
struct ExclusiveRange (Printable)
{
Type start;
Type end;
}
fn usz ExclusiveRange.len(&self) @operator(len)
{
if (self.end < self.start) return 0;
return (usz)(self.end - self.start);
}
fn bool ExclusiveRange.contains(&self, Type value) @inline
{
return value >= self.start && value < self.end;
}
fn usz! ExclusiveRange.to_format(&self, Formatter* formatter) @dynamic
{
return formatter.printf("[%s..<%s]", self.start, self.end)!;
}
fn String ExclusiveRange.to_new_string(&self, Allocator allocator = allocator::heap()) @dynamic
{
return string::new_format("[%s..<%s]", self.start, self.end, .allocator = allocator);
}
fn String ExclusiveRange.to_tstring(&self)
{
return self.to_new_string(allocator::temp());
}
/**
* @require index < self.len() : "Can't index into an empty range"
**/
fn Type ExclusiveRange.get(&self, usz index) @operator([])
{
return (Type)(self.start + index);
}

View File

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

View File

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

View File

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

View File

@@ -1,200 +1,198 @@
// 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.
module std::core::mem::allocator;
import std::math;
struct DynamicArenaAllocator (Allocator)
{
Allocator backing_allocator;
DynamicArenaPage* page;
DynamicArenaPage* unused_page;
usz page_size;
}
/**
* @param [&inout] allocator
* @require page_size >= 128
**/
fn void DynamicArenaAllocator.init(&self, usz page_size, Allocator allocator)
{
self.page = null;
self.unused_page = null;
self.page_size = page_size;
self.backing_allocator = allocator;
}
fn void DynamicArenaAllocator.free(&self)
{
DynamicArenaPage* page = self.page;
while (page)
{
DynamicArenaPage* next_page = page.prev_arena;
allocator::free(self.backing_allocator, page);
page = next_page;
}
page = self.unused_page;
while (page)
{
DynamicArenaPage* next_page = page.prev_arena;
allocator::free(self.backing_allocator, page);
page = next_page;
}
self.page = null;
self.unused_page = null;
}
struct DynamicArenaPage @local
private struct DynamicArenaPage
{
void* memory;
void* prev_arena;
usz total;
usz used;
void* current_stack_ptr;
usize total;
usize used;
void* last_ptr;
}
struct DynamicArenaChunk @local
private struct DynamicArenaChunk
{
usz size;
usize size;
}
/**
* @require ptr
* @require self.page `tried to free pointer on invalid allocator`
* @require ptr && this
* @require this.page `tried to free pointer on invalid allocator`
*/
fn void DynamicArenaAllocator.release(&self, void* ptr, bool) @dynamic
private fn void DynamicArenaAllocator.free(DynamicArenaAllocator* this, void* ptr)
{
DynamicArenaPage* current_page = self.page;
if (ptr == current_page.current_stack_ptr)
DynamicArenaPage* current_page = this.page;
if (ptr == current_page.last_ptr)
{
current_page.used = (usz)((ptr - DEFAULT_SIZE_PREFIX) - current_page.memory);
current_page.used = (usize)((ptr - DEFAULT_SIZE_PREFIX) - current_page.memory);
}
current_page.current_stack_ptr = null;
current_page.last_ptr = null;
}
/**
* @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`
* @require old_pointer && size > 0
* @require this.page `tried to realloc pointer on invalid allocator`
*/
fn void*! DynamicArenaAllocator.resize(&self, void* old_pointer, usz size, usz alignment) @dynamic
private fn void*! DynamicArenaAllocator._realloc(DynamicArenaAllocator* this, void* old_pointer, usize size, usize alignment, usize offset)
{
DynamicArenaPage* current_page = self.page;
DynamicArenaPage* current_page = this.page;
alignment = alignment_for_allocation(alignment);
usz* old_size_ptr = old_pointer - DEFAULT_SIZE_PREFIX;
usz old_size = *old_size_ptr;
usize* old_size_ptr = old_pointer - DEFAULT_SIZE_PREFIX;
usize old_size = *old_size_ptr;
// We have the old pointer and it's correctly aligned.
if (old_size >= size && mem::ptr_is_aligned(old_pointer, alignment))
{
*old_size_ptr = size;
if (current_page.current_stack_ptr == old_pointer)
if (current_page.last_ptr == old_pointer)
{
current_page.used = (usz)((old_pointer - DEFAULT_SIZE_PREFIX) - current_page.memory);
current_page.used = (usize)((old_pointer - DEFAULT_SIZE_PREFIX) - current_page.memory);
}
return old_pointer;
}
if REUSE: (current_page.current_stack_ptr == old_pointer && mem::ptr_is_aligned(old_pointer, alignment))
if REUSE: (current_page.last_ptr == old_pointer && mem::ptr_is_aligned(old_pointer, alignment))
{
assert(size > old_size);
usz add_size = size - old_size;
usize add_size = size - old_size;
if (add_size + current_page.used > current_page.total) break REUSE;
*old_size_ptr = size;
current_page.used += add_size;
return old_pointer;
}
void* new_mem = self.acquire(size, NO_ZERO, alignment)!;
mem::copy(new_mem, old_pointer, old_size, mem::DEFAULT_MEM_ALIGNMENT);
void* new_mem = this._alloc(size, alignment, offset)?;
mem::copy(new_mem, old_pointer, old_size, DEFAULT_MEM_ALIGNMENT);
return new_mem;
}
fn void DynamicArenaAllocator.reset(&self, usz mark = 0) @dynamic
private fn void DynamicArenaAllocator.reset(DynamicArenaAllocator* this)
{
assert(mark == 0, "Unexpectedly reset dynamic arena allocator with mark %d", mark);
DynamicArenaPage* page = self.page;
DynamicArenaPage** unused_page_ptr = &self.unused_page;
while (page)
{
DynamicArenaPage* next_page = page.prev_arena;
page.used = 0;
DynamicArenaPage* prev_unused = *unused_page_ptr;
*unused_page_ptr = page;
page.prev_arena = prev_unused;
page = next_page;
}
self.page = page;
DynamicArenaPage* page = this.page;
DynamicArenaPage** unused_page_ptr = &this.unused_page;
while (page)
{
DynamicArenaPage* next_page = page.prev_arena;
page.used = 0;
DynamicArenaPage* prev_unused = *unused_page_ptr;
*unused_page_ptr = page;
page.prev_arena = prev_unused;
page = next_page;
}
this.page = page;
}
/**
* @require math::is_power_of_2(alignment)
* @require size > 0
*/
fn void*! DynamicArenaAllocator._alloc_new(&self, usz size, usz alignment) @local
private fn void*! DynamicArenaAllocator._alloc_new(DynamicArenaAllocator* this, usize size, usize alignment, usize offset)
{
// 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));
usize page_size = max(this.page_size, mem::aligned_offset(size + DynamicArenaChunk.sizeof + offset, alignment) - offset);
// 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);
void* mem = this.backing_allocator.alloc(page_size)?;
DynamicArenaPage*! page = this.backing_allocator.alloc(DynamicArenaPage.sizeof);
if (catch err = page)
{
allocator::free(self.backing_allocator, mem);
return err?;
this.backing_allocator.free(mem)?;
return err!;
}
page.memory = mem;
void* mem_start = mem::aligned_pointer(mem + DynamicArenaChunk.sizeof, alignment);
assert(mem_start + size < mem + page_size);
DynamicArenaChunk* chunk = (DynamicArenaChunk*)mem_start - 1;
chunk.size = size;
page.prev_arena = self.page;
page.memory = mem;
void* mem_start = mem::aligned_pointer(mem + offset + DynamicArenaChunk.sizeof, alignment) - offset;
assert(mem_start + size < mem + page_size);
DynamicArenaChunk* chunk = (DynamicArenaChunk*)mem_start - 1;
chunk.size = size;
page.prev_arena = this.page;
page.total = page_size;
page.used = mem_start + size - page.memory;
self.page = page;
page.current_stack_ptr = mem_start;
this.page = page;
page.last_ptr = mem_start;
return mem_start;
}
/**
* @require size > 0 `acquire expects size > 0`
* @require !alignment || math::is_power_of_2(alignment)
* @require size > 0
* @require this
*/
fn void*! DynamicArenaAllocator.acquire(&self, usz size, AllocInitType init_type, usz alignment) @dynamic
private fn void*! DynamicArenaAllocator._alloc(DynamicArenaAllocator* this, usize size, usize alignment, usize offset)
{
alignment = alignment_for_allocation(alignment);
DynamicArenaPage* page = self.page;
void* ptr = {|
if (!page && self.unused_page)
{
self.page = page = self.unused_page;
self.unused_page = page.prev_arena;
page.prev_arena = null;
}
if (!page) return self._alloc_new(size, alignment);
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)
{
if ((page = self.unused_page))
{
start = mem::aligned_pointer(page.memory + page.used + DynamicArenaChunk.sizeof, alignment);
new_used = start + size - page.memory;
if (page.total >= new_used)
{
self.unused_page = page.prev_arena;
page.prev_arena = self.page;
self.page = page;
break ALLOCATE_NEW;
}
}
return self._alloc_new(size, alignment);
}
page.used = new_used;
assert(start + size == page.memory + page.used);
void* mem = start;
DynamicArenaChunk* chunk = (DynamicArenaChunk*)mem - 1;
chunk.size = size;
return mem;
|}!;
if (init_type == ZERO) mem::clear(ptr, size, mem::DEFAULT_MEM_ALIGNMENT);
return ptr;
DynamicArenaPage* page = this.page;
if (!page && this.unused_page)
{
this.page = page = this.unused_page;
this.unused_page = page.prev_arena;
page.prev_arena = null;
}
if (!page) return this._alloc_new(size, alignment, offset);
void* start = mem::aligned_pointer(page.memory + page.used + DynamicArenaChunk.sizeof + offset, alignment) - offset;
usize new_used = start - page.memory + size;
if ALLOCATE_NEW: (new_used > page.total)
{
if ((page = this.unused_page))
{
start = mem::aligned_pointer(page.memory + page.used + DynamicArenaChunk.sizeof + offset, alignment) - offset;
new_used = start + size - page.memory;
if (page.total >= new_used)
{
this.unused_page = page.prev_arena;
page.prev_arena = this.page;
this.page = page;
break ALLOCATE_NEW;
}
}
return this._alloc_new(size, alignment, offset);
}
page.used = new_used;
assert(start + size == page.memory + page.used);
void* mem = start;
DynamicArenaChunk* chunk = (DynamicArenaChunk*)mem - 1;
chunk.size = size;
return mem;
}
/**
* @require !alignment || math::is_power_of_2(alignment)
* @require data `unexpectedly missing the allocator`
*/
private fn void*! dynamic_arena_allocator_function(Allocator* data, usize size, usize alignment, usize offset, void* old_pointer, AllocationKind kind)
{
DynamicArenaAllocator* allocator = (DynamicArenaAllocator*)data;
switch (kind)
{
case CALLOC:
case ALIGNED_CALLOC:
assert(!old_pointer, "Unexpected no old pointer for calloc.");
if (!size) return null;
void* mem = allocator._alloc(size, alignment, offset)?;
mem::clear(mem, size, DEFAULT_MEM_ALIGNMENT);
return mem;
case ALLOC:
case ALIGNED_ALLOC:
assert(!old_pointer, "Unexpected no old pointer for alloc.");
if (!size) return null;
return allocator._alloc(size, alignment, offset);
case REALLOC:
case ALIGNED_REALLOC:
if (!size)
{
if (!old_pointer) return null;
allocator.free(old_pointer);
return null;
}
if (!old_pointer) return allocator._alloc(size, alignment, offset);
void* mem = allocator._realloc(old_pointer, size, alignment, offset)?;
return mem;
case ALIGNED_FREE:
case FREE:
if (!old_pointer) return null;
allocator.free(old_pointer);
return null;
case MARK:
unreachable("Tried to mark a dynamic arena");
case RESET:
allocator.reset();
return null;
}
unreachable();
}

View File

@@ -1,209 +0,0 @@
// 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.
module std::core::mem::allocator;
import std::math;
struct SimpleHeapAllocator (Allocator)
{
MemoryAllocFn alloc_fn;
Header* free_list;
}
/**
* @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)
{
self.alloc_fn = allocator;
self.free_list = null;
}
fn void*! SimpleHeapAllocator.acquire(&self, usz size, AllocInitType init_type, usz alignment) @dynamic
{
if (init_type == ZERO)
{
return alignment > 0 ? @aligned_alloc(self._calloc, size, alignment) : self._calloc(size);
}
return alignment > 0 ? @aligned_alloc(self._alloc, size, alignment) : self._alloc(size);
}
fn void*! SimpleHeapAllocator.resize(&self, void* old_pointer, usz size, usz alignment) @dynamic
{
return alignment > 0
? @aligned_realloc(self._calloc, self._free, old_pointer, size, alignment)
: self._realloc(old_pointer, size);
}
fn void SimpleHeapAllocator.release(&self, void* old_pointer, bool aligned) @dynamic
{
if (aligned)
{
@aligned_free(self._free, old_pointer)!!;
}
else
{
self._free(old_pointer);
}
}
/**
* @require old_pointer && bytes > 0
**/
fn void*! SimpleHeapAllocator._realloc(&self, void* old_pointer, usz bytes) @local
{
// Find the block header.
Header* block = (Header*)old_pointer - 1;
if (block.size >= bytes) return old_pointer;
void* new = self._alloc(bytes)!;
usz max_to_copy = math::min(block.size, bytes);
mem::copy(new, old_pointer, max_to_copy);
self._free(old_pointer);
return new;
}
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
{
usz aligned_bytes = mem::aligned_offset(bytes, mem::DEFAULT_MEM_ALIGNMENT);
if (!self.free_list)
{
self.add_block(aligned_bytes)!;
}
Header* current = self.free_list;
Header* previous = current;
while (current)
{
switch
{
case current.size >= aligned_bytes && current.size <= aligned_bytes + Header.sizeof + 64:
if (current == previous)
{
self.free_list = current.next;
}
else
{
previous.next = current.next;
}
current.next = null;
return current + 1;
case current.size > aligned_bytes:
Header* unallocated = (Header*)((char*)current + aligned_bytes + Header.sizeof);
unallocated.size = current.size - aligned_bytes;
unallocated.next = current.next;
if (current == self.free_list)
{
self.free_list = unallocated;
}
else
{
previous.next = unallocated;
}
current.size = aligned_bytes;
current.next = null;
return current + 1;
default:
previous = current;
current = current.next;
}
}
self.add_block(aligned_bytes)!;
return self._alloc(aligned_bytes);
}
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)!;
Header* new_block = (Header*)result.ptr;
new_block.size = result.len - Header.sizeof;
new_block.next = null;
self._free(new_block + 1);
}
fn void SimpleHeapAllocator._free(&self, void* ptr) @local
{
// Empty ptr -> do nothing.
if (!ptr) return;
// Find the block header.
Header* block = (Header*)ptr - 1;
// No free list? Then just return self.
if (!self.free_list)
{
self.free_list = block;
return;
}
// Find where in the list it should be inserted.
Header* current = self.free_list;
Header* prev = current;
while (current)
{
if (block < current)
{
// Between prev and current
if (block > prev) break;
// Before current
if (current == prev) break;
}
prev = current;
current = prev.next;
}
if (current)
{
// Insert after the current block.
// Are the blocks adjacent?
if (current == (Header*)((char*)(block + 1) + block.size))
{
// Merge
block.size += current.size + Header.sizeof;
block.next = current.next;
}
else
{
// Chain to current
block.next = current;
}
}
if (prev == current)
{
// Swap new start of free list
self.free_list = block;
}
else
{
// Prev adjacent?
if (block == (Header*)((char*)(prev + 1) + prev.size))
{
prev.size += block.size + Header.sizeof;
prev.next = block.next;
}
else
{
// Link prev to block
prev.next = block;
}
}
}
union Header @local
{
struct
{
Header* next;
usz size;
}
usz align;
}

View File

@@ -1,156 +0,0 @@
// 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.
module std::core::mem::allocator;
import libc;
const LibcAllocator LIBC_ALLOCATOR = {};
distinct LibcAllocator (Allocator) = uptr;
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
{
if (init_type == ZERO)
{
void* data @noinit;
if (alignment > mem::DEFAULT_MEM_ALIGNMENT)
{
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) ?: AllocationFailure.OUT_OF_MEMORY?;
}
else
{
void* data @noinit;
if (alignment > mem::DEFAULT_MEM_ALIGNMENT)
{
if (posix::posix_memalign(&data, alignment, bytes)) return AllocationFailure.OUT_OF_MEMORY?;
}
else
{
if (!(data = libc::malloc(bytes))) 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
{
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 AllocationFailure.OUT_OF_MEMORY?;
$switch
$case env::DARWIN:
usz old_usable_size = darwin::malloc_size(old_ptr);
$case env::LINUX:
usz old_usable_size = linux::malloc_usable_size(old_ptr);
$default:
usz old_usable_size = new_bytes;
$endswitch
usz copy_size = new_bytes < old_usable_size ? new_bytes : old_usable_size;
mem::copy(new_ptr, old_ptr, copy_size, mem::DEFAULT_MEM_ALIGNMENT, mem::DEFAULT_MEM_ALIGNMENT);
libc::free(old_ptr);
return new_ptr;
}
fn void LibcAllocator.release(&self, void* old_ptr, bool aligned) @dynamic
{
libc::free(old_ptr);
}
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
{
if (init_type == ZERO)
{
if (alignment > 0)
{
return win32::_aligned_recalloc(null, bytes, alignment) ?: AllocationFailure.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 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
{
if (alignment)
{
return win32::_aligned_realloc(old_ptr, new_bytes, alignment) ?: AllocationFailure.OUT_OF_MEMORY?;
}
return libc::realloc(old_ptr, new_bytes) ?: AllocationFailure.OUT_OF_MEMORY?;
}
fn void LibcAllocator.release(&self, void* old_ptr, bool aligned) @dynamic
{
if (aligned)
{
win32::_aligned_free(old_ptr);
return;
}
libc::free(old_ptr);
}
module std::core::mem::allocator @if(!env::WIN32 && !env::POSIX);
import libc;
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 ?: AllocationFailure.OUT_OF_MEMORY?;
}
else
{
void* data = alignment ? @aligned_alloc(libc::malloc, bytes, alignment)!! : libc::malloc(bytes);
if (!data) return AllocationFailure.OUT_OF_MEMORY?;
$if env::TESTING:
for (usz i = 0; i < bytes; i++) ((char*)data)[i] = 0xAA;
$endif
return data;
}
}
fn void*! LibcAllocator.resize(&self, void* old_ptr, usz new_bytes, usz alignment) @dynamic
{
if (alignment)
{
void* data = @aligned_realloc(fn void*(usz bytes) => libc::malloc(bytes), libc::free, old_ptr, new_bytes, alignment)!!;
return data ?: AllocationFailure.OUT_OF_MEMORY?;
}
return libc::realloc(old_ptr, new_bytes) ?: AllocationFailure.OUT_OF_MEMORY?;
}
fn void LibcAllocator.release(&self, void* old_ptr, bool aligned) @dynamic
{
if (aligned)
{
@aligned_free(libc::free, old_ptr)!!;
}
else
{
libc::free(old_ptr);
}
}

View File

@@ -0,0 +1,119 @@
module std::core::mem::allocator;
import libc;
private const Allocator _NULL_ALLOCATOR = { &null_allocator_fn };
private const Allocator _SYSTEM_ALLOCATOR = { &libc_allocator_fn };
private fn void*! null_allocator_fn(Allocator* this, usize bytes, usize alignment, usize offset, void* old_pointer, AllocationKind kind)
{
switch (kind)
{
case ALLOC:
case CALLOC:
case REALLOC:
case ALIGNED_ALLOC:
case ALIGNED_REALLOC:
case ALIGNED_CALLOC:
return AllocationFailure.OUT_OF_MEMORY!;
default:
return null;
}
}
private struct AlignedBlock
{
usize len;
void* start;
}
/**
* @require bytes > 0
* @require alignment > 0
**/
private fn void* _libc_aligned_alloc(usize bytes, usize alignment, usize offset) @inline
{
usize header = mem::aligned_offset(AlignedBlock.sizeof + offset, alignment) - offset;
void* data = libc::malloc(header + bytes);
void* mem = mem::aligned_pointer(data + offset, alignment) - offset;
assert(mem > data);
AlignedBlock* desc = (AlignedBlock*)mem - 1;
*desc = { bytes, data };
return mem;
}
/**
* @require bytes > 0
* @require alignment > 0
**/
private fn void* _libc_aligned_calloc(usize bytes, usize alignment, usize offset) @inline
{
usize header = mem::aligned_offset(AlignedBlock.sizeof + offset, alignment) - offset;
void* data = libc::calloc(header + bytes, 1);
void* mem = mem::aligned_pointer(data + offset, alignment) - offset;
AlignedBlock* desc = (AlignedBlock*)mem - 1;
assert(mem > data);
*desc = { bytes, data };
return mem;
}
/**
* @require bytes > 0
* @require alignment > 0
**/
private fn void* _libc_aligned_realloc(void* old_pointer, usize bytes, usize alignment, usize offset) @inline
{
AlignedBlock* desc = (AlignedBlock*)old_pointer - 1;
void* data_start = desc.start;
void* new_data = _libc_aligned_calloc(bytes, alignment, offset);
mem::copy(new_data, old_pointer, desc.len > bytes ? desc.len : bytes, DEFAULT_MEM_ALIGNMENT, DEFAULT_MEM_ALIGNMENT);
libc::free(data_start);
return new_data;
}
private fn void _libc_aligned_free(void* old_pointer) @inline
{
AlignedBlock* desc = (AlignedBlock*)old_pointer - 1;
libc::free(desc.start);
}
fn void*! libc_allocator_fn(Allocator* unused, usize bytes, usize alignment, usize offset, void* old_pointer, AllocationKind kind) @inline
{
if (!alignment) alignment = DEFAULT_MEM_ALIGNMENT;
assert(math::is_power_of_2(alignment), "Alignment was not a power of 2");
void* data;
switch (kind)
{
case ALIGNED_ALLOC:
if (alignment <= DEFAULT_MEM_ALIGNMENT) nextcase ALLOC;
data = _libc_aligned_alloc(bytes, alignment, offset);
case ALLOC:
data = libc::malloc(bytes);
case ALIGNED_CALLOC:
if (alignment <= DEFAULT_MEM_ALIGNMENT) nextcase CALLOC;
data = _libc_aligned_calloc(bytes, alignment, offset);
case CALLOC:
data = libc::calloc(bytes, 1);
case ALIGNED_REALLOC:
if (!bytes) nextcase ALIGNED_FREE;
if (!old_pointer) nextcase ALIGNED_CALLOC;
data = _libc_aligned_realloc(old_pointer, bytes, alignment, offset);
case REALLOC:
if (!bytes) nextcase FREE;
if (!old_pointer) nextcase CALLOC;
data = libc::realloc(old_pointer, bytes);
case RESET:
return AllocationFailure.UNSUPPORTED_OPERATION!;
case ALIGNED_FREE:
_libc_aligned_free(old_pointer);
return null;
case FREE:
libc::free(old_pointer);
return null;
default:
unreachable();
}
if (!data) return AllocationFailure.OUT_OF_MEMORY!;
return data;
}

View File

@@ -1,149 +0,0 @@
module std::core::mem::allocator;
struct OnStackAllocator (Allocator)
{
Allocator backing_allocator;
char[] data;
usz used;
OnStackAllocatorExtraChunk* chunk;
}
struct OnStackAllocatorExtraChunk @local
{
bool is_aligned;
OnStackAllocatorExtraChunk* prev;
void* data;
}
/**
* @param [&inout] allocator
* Initialize a memory arena for use using the provided bytes.
**/
fn void OnStackAllocator.init(&self, char[] data, Allocator allocator)
{
self.data = data;
self.backing_allocator = allocator;
self.used = 0;
}
fn void OnStackAllocator.free(&self)
{
OnStackAllocatorExtraChunk* chunk = self.chunk;
while (chunk)
{
if (chunk.is_aligned)
{
allocator::free_aligned(self.backing_allocator, chunk.data);
}
else
{
allocator::free(self.backing_allocator, chunk.data);
}
void* old = chunk;
chunk = chunk.prev;
allocator::free(self.backing_allocator, old);
}
self.chunk = null;
self.used = 0;
}
struct OnStackAllocatorHeader
{
usz size;
char[*] data;
}
/**
* @require old_pointer
**/
fn void OnStackAllocator.release(&self, void* old_pointer, bool aligned) @dynamic
{
if (allocation_in_stack_mem(self, old_pointer)) return;
on_stack_allocator_remove_chunk(self, old_pointer);
self.release(old_pointer, aligned);
}
fn bool allocation_in_stack_mem(OnStackAllocator* a, void* ptr) @local
{
return ptr >= a.data.ptr && ptr <= &a.data[^1];
}
fn void on_stack_allocator_remove_chunk(OnStackAllocator* a, void* ptr) @local
{
OnStackAllocatorExtraChunk* chunk = a.chunk;
OnStackAllocatorExtraChunk** addr = &a.chunk;
while (chunk)
{
if (chunk.data == ptr)
{
*addr = chunk.prev;
allocator::free(a.backing_allocator, chunk);
return;
}
addr = &chunk.prev;
chunk = *addr;
}
unreachable("Missing chunk");
}
fn OnStackAllocatorExtraChunk* on_stack_allocator_find_chunk(OnStackAllocator* a, void* ptr) @local
{
OnStackAllocatorExtraChunk* chunk = a.chunk;
while (chunk)
{
if (chunk.data == ptr) return chunk;
chunk = chunk.prev;
}
return null;
}
/**
* @require size > 0
* @require old_pointer != null
* @require alignment <= mem::MAX_MEMORY_ALIGNMENT `alignment too big`
**/
fn void*! OnStackAllocator.resize(&self, void* old_pointer, usz size, usz alignment) @dynamic
{
if (!allocation_in_stack_mem(self, old_pointer))
{
OnStackAllocatorExtraChunk* chunk = on_stack_allocator_find_chunk(self, old_pointer);
assert(chunk, "Tried to realloc pointer not belonging to the allocator");
return chunk.data = self.backing_allocator.resize(old_pointer, size, alignment)!;
}
OnStackAllocatorHeader* header = old_pointer - OnStackAllocatorHeader.sizeof;
usz old_size = header.size;
void* mem = self.acquire(size, NO_ZERO, alignment)!;
mem::copy(mem, old_pointer, old_size, mem::DEFAULT_MEM_ALIGNMENT, mem::DEFAULT_MEM_ALIGNMENT);
return mem;
}
/**
* @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
{
bool aligned = alignment > 0;
alignment = alignment_for_allocation(alignment);
usz total_len = self.data.len;
void* start_mem = self.data.ptr;
void* unaligned_pointer_to_offset = start_mem + self.used + OnStackAllocatorHeader.sizeof ;
void* mem = mem::aligned_pointer(unaligned_pointer_to_offset, alignment);
usz end = (usz)(mem - self.data.ptr) + size;
Allocator backing_allocator = self.backing_allocator;
if (end > total_len)
{
OnStackAllocatorExtraChunk* chunk = allocator::alloc_try(backing_allocator, OnStackAllocatorExtraChunk)!;
defer catch allocator::free(backing_allocator, chunk);
defer try self.chunk = chunk;
*chunk = { .prev = self.chunk, .is_aligned = aligned };
return chunk.data = backing_allocator.acquire(size, init_type, aligned ? alignment : 0)!;
}
self.used = end;
OnStackAllocatorHeader* header = mem - OnStackAllocatorHeader.sizeof;
header.size = size;
return mem;
}

View File

@@ -1,205 +1,258 @@
module std::core::mem::allocator;
import std::io, std::math;
import std::io;
struct TempAllocatorChunk @local
private struct TempAllocatorChunk
{
usz size;
usize size;
char[*] data;
}
struct TempAllocator (Allocator)
struct TempAllocator
{
Allocator backing_allocator;
inline Allocator allocator;
Allocator* backing_allocator;
TempAllocatorPage* last_page;
usz used;
usz capacity;
usize used;
usize capacity;
char[*] data;
}
const usz PAGE_IS_ALIGNED @private = (usz)isz.max + 1u;
private const usize PAGE_IS_ALIGNED = (usize)isize.max + 1;
struct TempAllocatorPage
{
TempAllocatorPage* prev_page;
void* start;
usz mark;
usz size;
usz ident;
usize mark;
usize size;
usize ident;
char[*] data;
}
macro usz TempAllocatorPage.pagesize(&self) => self.size & ~PAGE_IS_ALIGNED;
macro bool TempAllocatorPage.is_aligned(&self) => self.size & PAGE_IS_ALIGNED == PAGE_IS_ALIGNED;
macro usize TempAllocatorPage.pagesize(TempAllocatorPage* page) { return page.size & ~PAGE_IS_ALIGNED; }
macro bool TempAllocatorPage.is_aligned(TempAllocatorPage* page) { return page.size & PAGE_IS_ALIGNED == PAGE_IS_ALIGNED; }
/**
* @require size >= 16
**/
fn TempAllocator*! new_temp_allocator(usz size, Allocator allocator)
fn TempAllocator*! new_temp(usize size, Allocator* backing_allocator)
{
TempAllocator* temp = allocator::alloc_with_padding(allocator, TempAllocator, size)!;
temp.last_page = null;
temp.backing_allocator = allocator;
temp.used = 0;
temp.capacity = size;
return temp;
TempAllocator* allocator = backing_allocator.alloc(size + TempAllocator.sizeof)?;
allocator.last_page = null;
allocator.function = &temp_allocator_function;
allocator.backing_allocator = backing_allocator;
allocator.used = 0;
allocator.capacity = size;
return allocator;
}
fn usz TempAllocator.mark(&self) @dynamic => self.used;
fn void TempAllocator.release(&self, void* old_pointer, bool) @dynamic
/**
* @require !alignment || math::is_power_of_2(alignment)
* @require data `unexpectedly missing the allocator`
*/
private fn void*! temp_allocator_function(Allocator* data, usize size, usize alignment, usize offset, void* old_pointer, AllocationKind kind)
{
usz old_size = *(usz*)(old_pointer - DEFAULT_SIZE_PREFIX);
if (old_pointer + old_size == &self.data[self.used])
TempAllocator* arena = (TempAllocator*)data;
switch (kind)
{
self.used -= old_size;
case CALLOC:
case ALIGNED_CALLOC:
assert(!old_pointer, "Unexpected old pointer for alloc.");
if (!size) return null;
return arena._alloc(size, alignment, offset, true);
case ALLOC:
case ALIGNED_ALLOC:
assert(!old_pointer, "Unexpected old pointer for alloc.");
if (!size) return null;
return arena._alloc(size, alignment_for_allocation(alignment), offset, false);
case ALIGNED_REALLOC:
case REALLOC:
if (!size) nextcase FREE;
if (!old_pointer) nextcase ALLOC;
return arena._realloc(old_pointer, size, alignment_for_allocation(alignment), offset);
case FREE:
case ALIGNED_FREE:
if (!old_pointer) return null;
io::println("Freeing stuff\n");
arena._free(old_pointer)?;
return null;
case MARK:
return (void*)(uptr)arena.used;
case RESET:
arena._reset(size)?;
return null;
}
unreachable();
}
fn void TempAllocator.reset(&self, usz mark) @dynamic
private fn void! TempAllocator._free(TempAllocator* this, void* old_pointer)
{
TempAllocatorPage *last_page = self.last_page;
// TODO fix free
assert((uptr)old_pointer >= (uptr)&this.data, "Pointer originates from a different allocator.");
usize old_size = *(usize*)(old_pointer - DEFAULT_SIZE_PREFIX);
if (old_pointer + old_size == &this.data[this.used])
{
this.used -= old_size;
}
}
private fn void! TempAllocator._reset(TempAllocator* this, usize mark)
{
TempAllocatorPage *last_page = this.last_page;
while (last_page && last_page.mark > mark)
{
TempAllocatorPage *to_free = last_page;
last_page = last_page.prev_page;
self._free_page(to_free)!!;
this._free_page(to_free)?;
}
self.last_page = last_page;
self.used = mark;
this.last_page = last_page;
this.used = mark;
}
fn void! TempAllocator._free_page(&self, TempAllocatorPage* page) @inline @local
private fn void! TempAllocator._free_page(TempAllocator* this, TempAllocatorPage* page) @inline
{
void* mem = page.start;
return self.backing_allocator.release(mem, page.is_aligned());
if (page.is_aligned()) return this.backing_allocator.free_aligned(mem);
return this.backing_allocator.free(mem);
}
fn void*! TempAllocator._realloc_page(&self, TempAllocatorPage* page, usz size, usz alignment) @inline @local
private fn void*! TempAllocator._realloc_page(TempAllocator* this, TempAllocatorPage* page, usize size, usize alignment, usize offset) @inline
{
// Then the actual start pointer:
void* real_pointer = page.start;
// Walk backwards to find the pointer to this page.
TempAllocatorPage **pointer_to_prev = &self.last_page;
TempAllocatorPage **pointer_to_prev = &this.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();
usize 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());
void* data = this._alloc(size, alignment, offset, false)?;
mem::copy(data, &page.data[0], page_size, DEFAULT_MEM_ALIGNMENT, DEFAULT_MEM_ALIGNMENT);
if (page.is_aligned())
{
this.backing_allocator.free_aligned(real_pointer)?;
}
else
{
this.backing_allocator.free(real_pointer)?;
}
return data;
}
fn void*! TempAllocator.resize(&self, void* pointer, usz size, usz alignment) @dynamic
private fn void*! TempAllocator._realloc(TempAllocator* this, void* pointer, usize size, usize alignment, usize offset) @inline
{
TempAllocatorChunk *chunk = pointer - TempAllocatorChunk.sizeof;
if (chunk.size == (usz)-1)
if (chunk.size == (usize)-1)
{
assert(self.last_page, "Realloc of non temp pointer");
assert(this.last_page, "Realloc of non temp pointer");
// First grab the page
TempAllocatorPage *page = pointer - TempAllocatorPage.sizeof;
return self._realloc_page(page, size, alignment);
return this._realloc_page(page, size, alignment, offset);
}
assert(pointer < &this.data + this.capacity && pointer >= &this.data, "This is not a temp allocated pointer.");
assert(pointer < &this.data + this.used, "This is a stale temp pointer.");
TempAllocatorChunk* data = self.acquire(size, NO_ZERO, alignment)!;
mem::copy(data, pointer, chunk.size, mem::DEFAULT_MEM_ALIGNMENT, mem::DEFAULT_MEM_ALIGNMENT);
// TODO optimize last allocation
TempAllocatorChunk* data = this._alloc(size, alignment, offset, size > chunk.size)?;
mem::copy(data, pointer, chunk.size, DEFAULT_MEM_ALIGNMENT, DEFAULT_MEM_ALIGNMENT);
return data;
}
/**
* @require math::is_power_of_2(alignment)
* @require size > 0
* @require !alignment || math::is_power_of_2(alignment)
* @require alignment <= mem::MAX_MEMORY_ALIGNMENT `alignment too big`
* @require alignment <= MAX_MEMORY_ALIGNMENT `alignment too big`
* @require this != null
**/
fn void*! TempAllocator.acquire(&self, usz size, AllocInitType init_type, usz alignment) @dynamic
private fn void*! TempAllocator._alloc(TempAllocator* this, usize size, usize alignment, usize offset, bool clear)
{
alignment = alignment_for_allocation(alignment);
void* start_mem = &self.data;
void* starting_ptr = start_mem + self.used;
void* aligned_header_start = mem::aligned_pointer(starting_ptr, TempAllocatorChunk.alignof);
void* start_mem = &this.data;
void* starting_ptr = start_mem + this.used;
void* aligned_header_start = mem::aligned_pointer(starting_ptr, $alignof(TempAllocatorChunk));
void* mem = aligned_header_start + TempAllocatorChunk.sizeof;
if (alignment > TempAllocatorChunk.alignof)
if (alignment > $alignof(TempAllocatorChunk))
{
mem = mem::aligned_pointer(mem, alignment);
mem = mem::aligned_pointer(mem + offset, alignment) - offset;
}
usz new_usage = (usz)(mem - start_mem) + size;
usize new_usage = (usize)(mem - start_mem) + size;
// Arena alignment, simple!
if (new_usage <= self.capacity)
if (new_usage <= this.capacity)
{
TempAllocatorChunk* chunk_start = mem - TempAllocatorChunk.sizeof;
chunk_start.size = size;
self.used = new_usage;
if (init_type == ZERO) mem::clear(mem, size, mem::DEFAULT_MEM_ALIGNMENT);
return mem;
chunk_start.size = size;
this.used = new_usage;
if (clear) mem::clear(mem, size, DEFAULT_MEM_ALIGNMENT);
return mem;
}
// Fallback to backing allocator
TempAllocatorPage* page;
// We have something we need to align.
if (alignment > mem::DEFAULT_MEM_ALIGNMENT)
if (alignment > DEFAULT_MEM_ALIGNMENT || offset)
{
// This is actually simpler, since it will create the offset for us.
usz total_alloc_size = mem::aligned_offset(TempAllocatorPage.sizeof + size, alignment);
if (init_type == ZERO)
usize total_alloc_size = TempAllocatorPage.sizeof + size;
if (clear)
{
mem = allocator::calloc_aligned(self.backing_allocator, total_alloc_size, alignment)!;
page = this.backing_allocator.calloc_aligned(total_alloc_size, alignment, TempAllocatorPage.sizeof + offset)?;
}
else
{
mem = allocator::malloc_aligned(self.backing_allocator, total_alloc_size, alignment)!;
page = this.backing_allocator.alloc_aligned(total_alloc_size, alignment, TempAllocatorPage.sizeof + offset)?;
}
page = (TempAllocatorPage*)mem - 1;
page.start = mem;
page.start = page;
page.size = size | PAGE_IS_ALIGNED;
}
else
{
// Here we might need to pad
usz padded_header_size = mem::aligned_offset(TempAllocatorPage.sizeof, mem::DEFAULT_MEM_ALIGNMENT);
usz total_alloc_size = padded_header_size + size;
void* alloc = self.backing_allocator.acquire(total_alloc_size, init_type, 0)!;
usize padded_header_size = mem::aligned_offset(TempAllocatorPage.sizeof, DEFAULT_MEM_ALIGNMENT);
usize total_alloc_size = padded_header_size + size;
void* alloc = (clear ? this.backing_allocator.calloc(total_alloc_size) : this.backing_allocator.alloc(total_alloc_size))?;
// Find the page.
page = alloc + padded_header_size - TempAllocatorPage.sizeof;
assert(mem::ptr_is_aligned(page, TempAllocator.alignof));
assert(mem::ptr_is_aligned(&page.data[0], mem::DEFAULT_MEM_ALIGNMENT));
assert(mem::ptr_is_aligned(page, $alignof(TempAllocator)));
assert(mem::ptr_is_aligned(&page.data[0], DEFAULT_MEM_ALIGNMENT));
page.start = alloc;
page.size = size;
}
// Mark it as a page
page.ident = ~(usz)0;
page.ident = ~(usize)0;
// Store when it was created
page.mark = ++self.used;
page.mark = ++this.used;
// Hook up the page.
page.prev_page = self.last_page;
self.last_page = page;
page.prev_page = this.last_page;
this.last_page = page;
return &page.data[0];
}
fn void! TempAllocator.print_pages(&self, File* f)
fn void TempAllocator.print_pages(TempAllocator* this, File f)
{
TempAllocatorPage *last_page = self.last_page;
TempAllocatorPage *last_page = this.last_page;
if (!last_page)
{
io::fprintf(f, "No pages.\n")!;
f.printf("No pages.\n");
return;
}
io::fprintf(f, "---Pages----\n")!;
f.printf("---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]")!;
f.printf("%d. Alloc: %d %d at %p%s\n", ++index,
last_page.size & ~(1u64 << 63), last_page.mark, &last_page.data[0], is_not_aligned ? "" : " [aligned]");
last_page = last_page.prev_page;
}
}

View File

@@ -1,217 +0,0 @@
// 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.
module std::core::mem::allocator;
import std::collections, std::io, std::os::backtrace;
const MAX_BACKTRACE = 16;
struct Allocation
{
void* ptr;
usz size;
void*[MAX_BACKTRACE] backtrace;
}
def AllocMap = HashMap(<uptr, Allocation>);
// A simple tracking allocator.
// It tracks allocations using a hash map but
// is not compatible with allocators that uses mark()
struct TrackingAllocator (Allocator)
{
Allocator inner_allocator;
AllocMap map;
usz mem_total;
usz allocs_total;
}
/**
* Initialize a tracking allocator to wrap (and track) another allocator.
*
* @param [&inout] allocator "The allocator to track"
**/
fn void TrackingAllocator.init(&self, Allocator allocator)
{
*self = { .inner_allocator = allocator };
self.map.new_init(.allocator = allocator);
}
/**
* Free this tracking allocator.
**/
fn void TrackingAllocator.free(&self)
{
self.map.free();
*self = {};
}
/**
* @return "the total allocated memory not yet freed."
**/
fn usz TrackingAllocator.allocated(&self)
{
usz allocated = 0;
@pool()
{
foreach (&allocation : self.map.value_tlist()) allocated += allocation.size;
};
return allocated;
}
/**
* @return "the total memory allocated (freed or not)."
**/
fn usz TrackingAllocator.total_allocated(&self) => self.mem_total;
/**
* @return "the total number of allocations (freed or not)."
**/
fn usz TrackingAllocator.total_allocation_count(&self) => self.allocs_total;
fn Allocation[] TrackingAllocator.allocations_tlist(&self, Allocator allocator)
{
return self.map.value_tlist();
}
/**
* @return "the number of non-freed allocations."
**/
fn usz TrackingAllocator.allocation_count(&self) => self.map.count;
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++;
void*[MAX_BACKTRACE] bt;
backtrace::capture_current(&bt);
self.map.set((uptr)data, { data, size, bt });
self.mem_total += size;
self.allocs_total++;
return data;
}
fn void*! TrackingAllocator.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);
void*[MAX_BACKTRACE] bt;
backtrace::capture_current(&bt);
self.map.set((uptr)data, { data, size, bt });
self.mem_total += size;
self.allocs_total++;
return data;
}
fn void TrackingAllocator.release(&self, void* old_pointer, bool is_aligned) @dynamic
{
if (catch self.map.remove((uptr)old_pointer))
{
unreachable("Attempt to release untracked pointer %p, this is likely a bug.", old_pointer);
}
self.inner_allocator.release(old_pointer, is_aligned);
}
fn void TrackingAllocator.clear(&self)
{
self.map.clear();
}
fn void TrackingAllocator.print_report(&self) => self.fprint_report(io::stdout())!!;
fn void! TrackingAllocator.fprint_report(&self, OutStream out)
{
usz total = 0;
usz entries = 0;
bool leaks = false;
@pool()
{
Allocation[] allocs = self.map.value_tlist();
if (allocs.len)
{
if (!allocs[0].backtrace[0])
{
io::fprintn(out, "======== Memory Report ========")!;
io::fprintn(out, "Size in bytes Address")!;
foreach (i, &allocation : allocs)
{
entries++;
total += allocation.size;
io::fprintfn(out, "%13s %p", allocation.size, allocation.ptr)!;
}
io::fprintn(out, "===============================")!;
}
else
{
io::fprintn(out, "================================== Memory Report ==================================")!;
io::fprintn(out, "Size in bytes Address Function ")!;
foreach (i, &allocation : allocs)
{
entries++;
total += allocation.size;
BacktraceList backtraces = {};
Backtrace trace = backtrace::BACKTRACE_UNKNOWN;
if (allocation.backtrace[3])
{
trace = backtrace::symbolize_backtrace(allocation.backtrace[3:1], 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, "* NO ALLOCATIONS FOUND *")!;
}
io::fprintfn(out, "- Total currently allocated memory: %d", total)!;
io::fprintfn(out, "- Total current allocations: %d", entries)!;
io::fprintfn(out, "- Total allocations (freed and retained): %d", self.allocs_total)!;
io::fprintfn(out, "- Total allocated memory (freed and retained): %d", self.mem_total)!;
if (leaks)
{
io::fprintn(out)!;
io::fprintn(out, "Full leak report:")!;
foreach (i, &allocation : allocs)
{
if (!allocation.backtrace[3])
{
io::fprintfn(out, "Allocation %d (%d bytes) - no backtrace available.", i + 1, allocation.size)!;
continue;
}
BacktraceList backtraces = {};
usz end = MAX_BACKTRACE;
foreach (j, val : allocation.backtrace)
{
if (!val)
{
end = j;
break;
}
}
BacktraceList list = backtrace::symbolize_backtrace(allocation.backtrace[3..(end - 1)], allocator::temp())!;
io::fprintfn(out, "Allocation %d (%d bytes): ", i + 1, allocation.size)!;
foreach (trace : list)
{
if (trace.has_file())
{
io::fprintfn(out, " %s (in %s:%d)", trace.function, trace.file, trace.line);
continue;
}
if (trace.is_unknown())
{
io::fprintfn(out, " ??? (in unknown)");
continue;
}
io::fprintfn(out, " %s (source unavailable)", trace.function);
}
}
}
};
}

View File

@@ -1,147 +1,31 @@
module std::core::array;
import std::core::array::slice;
/**
* @param [in] array
* @param [in] element
* @return "the first index of the element"
* @return! SearchResult.MISSING
**/
macro index_of(array, element)
{
foreach (i, &e : array)
{
if (*e == element) return i;
}
return SearchResult.MISSING?;
}
/**
* @require @typekind(array) == VECTOR || @typekind(array) == ARRAY
* @require @typekind(array[0]) == VECTOR || @typekind(array[0]) == ARRAY
**/
macro slice2d(array, x = 0, xlen = 0, y = 0, ylen = 0)
{
if (xlen < 1) xlen = $typeof(array[0]).len + xlen;
if (ylen < 1) ylen = $typeof(array).len + ylen;
var $ElementType = $typeof(array[0][0]);
return Slice2d(<$ElementType>) { ($ElementType*)&array, $typeof(array[0]).len, y, ylen, x, xlen };
}
/**
* @param [in] array
* @param [in] element
* @return "the last index of the element"
* @return! SearchResult.MISSING
**/
macro rindex_of(array, element)
{
foreach_r (i, &e : array)
{
if (*e == element) return i;
}
return SearchResult.MISSING?;
}
/**
* Concatenate two arrays or 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())
macro tconcat(arr1, arr2)
{
var $Type = $typeof(arr1[0]);
$Type[] result = allocator::alloc_array(allocator, $Type, arr1.len + arr2.len);
$Type[] result = array::talloc($Type, arr1.len + arr2.len);
if (arr1.len > 0)
{
mem::copy(result.ptr, &arr1[0], arr1.len * $Type.sizeof, $Type.alignof, $Type.alignof);
mem::copy(result.ptr, &arr1[0], arr1.len * $Type.sizeof, $alignof($Type), $alignof($Type));
}
if (arr2.len > 0)
{
mem::copy(&result[arr1.len], &arr2[0], arr2.len * $Type.sizeof, $Type.alignof, $Type.alignof);
mem::copy(&result[arr1.len], &arr2[0], arr2.len * $Type.sizeof, $alignof($Type), $alignof($Type));
}
return result;
}
/**
* Concatenate two arrays or slices, returning a slice containing the concatenation of them,
* allocated using the temp allocator.
*
* @param [in] arr1
* @param [in] arr2
* @require @typekind(arr1) == SLICE || @typekind(arr1) == ARRAY
* @require @typekind(arr2) == SLICE || @typekind(arr2) == ARRAY
* @require @typeis(arr1[0], $typeof(arr2[0])) "Arrays must have the same type"
* @ensure result.len == arr1.len + arr2.len
**/
macro tconcat(arr1, arr2) => concat(arr1, arr2, allocator::temp());
module std::core::array::slice(<Type>);
struct Slice2d
macro concat(arr1, arr2)
{
Type* ptr;
usz inner_len;
usz ystart;
usz ylen;
usz xstart;
usz xlen;
}
fn usz Slice2d.len(&self) @operator(len)
{
return self.ylen;
}
fn usz Slice2d.count(&self)
{
return self.ylen * self.xlen;
}
macro void Slice2d.@each(&self; @body(usz[<2>], Type))
{
foreach (y, line : *self)
var $Type = $typeof(arr1[0]);
$Type[] result = array::alloc($Type, arr1.len + arr2.len);
if (arr1.len > 0)
{
foreach (x, val : line)
{
@body({ x, y }, val);
}
mem::copy(result.ptr, &arr1[0], arr1.len * $Type.sizeof, $alignof($Type), $alignof($Type));
}
}
macro void Slice2d.@each_ref(&self; @body(usz[<2>], Type*))
{
foreach (y, line : *self)
if (arr2.len > 0)
{
foreach (x, &val : line)
{
@body({ x, y }, val);
}
mem::copy(&result[arr1.len], &arr2[0], arr2.len * $Type.sizeof, $alignof($Type), $alignof($Type));
}
}
/**
* @require idy >= 0 && idy < self.ylen
**/
macro Type[] Slice2d.get(self, usz idy) @operator([])
{
return (self.ptr + self.inner_len * (idy + self.ystart))[self.xstart:self.xlen];
}
/**
* @require y >= 0 && y < self.ylen
* @require x >= 0 && x < self.xlen
**/
fn Slice2d Slice2d.slice(&self, isz x = 0, isz xlen = 0, isz y = 0, isz ylen = 0)
{
if (xlen < 1) xlen = self.xlen + xlen;
if (ylen < 1) ylen = self.ylen + ylen;
return { self.ptr, self.inner_len, y + self.ystart, ylen, x + self.xstart, xlen };
}
return result;
}

View File

@@ -1,12 +1,5 @@
// Copyright (c) 2023 Christoffer Lerno and contributors. All rights reserved.
// Use of this source code is governed by the MIT license
// a copy of which can be found in the LICENSE_STDLIB file.
module std::core::bitorder;
// This module contains types of different endianness.
// *BE types represent big-endian types
// *LE types represent little-endian types.
bitstruct ShortBE : short @bigendian
{
short val : 0..15;
@@ -37,16 +30,6 @@ bitstruct ULongBE : ulong @bigendian
ulong val : 0..63;
}
bitstruct Int128BE : int128 @bigendian
{
int128 val : 0..127;
}
bitstruct UInt128BE : uint128 @bigendian
{
uint128 val : 0..127;
}
bitstruct ShortLE : short @littleendian
{
short val : 0..15;
@@ -76,105 +59,3 @@ bitstruct ULongLE : ulong @littleendian
{
ulong val : 0..63;
}
bitstruct Int128LE : int128 @littleendian
{
int128 val : 0..127;
}
bitstruct UInt128LE : uint128 @littleendian
{
uint128 val : 0..127;
}
/**
* @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))
$case POINTER:
s = (*bytes)[:$Type.sizeof];
$default:
s = bytes[:$Type.sizeof];
$endswitch
return bitcast(*(char[$Type.sizeof]*)s.ptr, $Type).val;
}
/**
* @require is_arrayptr_or_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))
$case POINTER:
s = (*bytes)[:$Type.sizeof];
$default:
s = bytes[:$Type.sizeof];
$endswitch
*($typeof(x)*)s.ptr = bitcast(x, $Type).val;
}
macro is_bitorder($Type)
{
$switch ($Type)
$case UShortLE:
$case ShortLE:
$case UIntLE:
$case IntLE:
$case ULongLE:
$case LongLE:
$case UInt128LE:
$case Int128LE:
$case UShortBE:
$case ShortBE:
$case UIntBE:
$case IntBE:
$case ULongBE:
$case LongBE:
$case UInt128BE:
$case Int128BE:
return true;
$default:
return false;
$endswitch
}
macro bool is_array_or_slice_of_char(bytes)
{
$switch (@typekind(bytes))
$case POINTER:
var $Inner = $typefrom($typeof(bytes).inner);
$if $Inner.kindof == ARRAY:
var $Inner2 = $typefrom($Inner.inner);
return $Inner2.typeid == char.typeid;
$endif
$case ARRAY:
$case SLICE:
var $Inner = $typefrom($typeof(bytes).inner);
return $Inner.typeid == char.typeid;
$default:
return false;
$endswitch
}
macro bool is_arrayptr_or_slice_of_char(bytes)
{
$switch (@typekind(bytes))
$case POINTER:
var $Inner = $typefrom($typeof(bytes).inner);
$if $Inner.kindof == ARRAY:
var $Inner2 = $typefrom($Inner.inner);
return $Inner2.typeid == char.typeid;
$endif
$case SLICE:
var $Inner = $typefrom($typeof(bytes).inner);
return $Inner.typeid == char.typeid;
$default:
return false;
$endswitch
}

View File

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

View File

@@ -1,4 +1,4 @@
// Copyright (c) 2021-2024 Christoffer Lerno and contributors. All rights reserved.
// Copyright (c) 2021-2022 Christoffer Lerno and contributors. All rights reserved.
// Use of this source code is governed by the MIT license
// a copy of which can be found in the LICENSE_STDLIB file.
module std::core::builtin;
@@ -8,14 +8,13 @@ module std::core::builtin;
**/
macro less(a, b) @builtin
{
$switch
$case $defined(a.less):
$if ($defined(a.less)):
return a.less(b);
$case $defined(a.compare_to):
$elif ($defined(a.compare_to)):
return a.compare_to(b) < 0;
$default:
$else:
return a < b;
$endswitch
$endif;
}
/**
@@ -23,14 +22,13 @@ macro less(a, b) @builtin
**/
macro less_eq(a, b) @builtin
{
$switch
$case $defined(a.less):
$if ($defined(a.less)):
return !b.less(a);
$case $defined(a.compare_to):
$elif ($defined(a.compare_to)):
return a.compare_to(b) <= 0;
$default:
$else:
return a <= b;
$endswitch
$endif;
}
/**
@@ -38,43 +36,27 @@ macro less_eq(a, b) @builtin
**/
macro greater(a, b) @builtin
{
$switch
$case $defined(a.less):
$if ($defined(a.less)):
return b.less(a);
$case $defined(a.compare_to):
$elif ($defined(a.compare_to)):
return a.compare_to(b) > 0;
$default:
$else:
return a > b;
$endswitch
$endif;
}
/**
* @require types::is_comparable_value(a) && types::is_comparable_value(b)
**/
macro int compare_to(a, b) @builtin
{
$switch
$case $defined(a.compare_to):
return a.compare_to(b);
$case $defined(a.less):
return (int)b.less(a) - (int)a.less(b);
$default:
return (int)(a > b) - (int)(a < b);
$endswitch
}
/**
* @require types::is_comparable_value(a) && types::is_comparable_value(b)
**/
macro greater_eq(a, b) @builtin
{
$switch
$case $defined(a.less):
$if ($defined(a.less)):
return !a.less(b);
$case $defined(a.compare_to):
$elif ($defined(a.compare_to)):
return a.compare_to(b) >= 0;
$default:
$else:
return a >= b;
$endswitch
$endif;
}
/**
@@ -82,47 +64,13 @@ macro greater_eq(a, b) @builtin
**/
macro bool equals(a, b) @builtin
{
$switch
$case $defined(a.equals, a.equals(b)):
$if ($defined(a.equals)):
return a.equals(b);
$case $defined(a.compare_to, a.compare_to(b)):
$elif ($defined(a.compare_to)):
return a.compare_to(b) == 0;
$case $defined(a.less):
$elif ($defined(a.less)):
return !a.less(b) && !b.less(a);
$default:
$else:
return a == b;
$endswitch
$endif;
}
macro min(x, ...) @builtin
{
$if $vacount == 1:
return less(x, $vaarg(0)) ? x : $vaarg(0);
$else
var result = x;
$for (var $i = 0; $i < $vacount; $i++)
if (less($vaarg($i), result))
{
result = $vaarg($i);
}
$endfor
return result;
$endif
}
macro max(x, ...) @builtin
{
$if $vacount == 1:
return greater(x, $vaarg(0)) ? x : $vaarg(0);
$else
var result = x;
$for (var $i = 0; $i < $vacount; $i++)
if (greater($vaarg($i), result))
{
result = $vaarg($i);
}
$endfor
return result;
$endif
}

View File

@@ -1,4 +1,4 @@
// Copyright (c) 2021-2024 Christoffer Lerno. All rights reserved.
// Copyright (c) 2021 Christoffer Lerno. All rights reserved.
// Use of this source code is governed by the MIT license
// a copy of which can be found in the LICENSE_STDLIB file.
module std::core::cinterop;
@@ -8,48 +8,80 @@ const C_LONG_SIZE = $$C_LONG_SIZE;
const C_SHORT_SIZE = $$C_SHORT_SIZE;
const C_LONG_LONG_SIZE = $$C_LONG_LONG_SIZE;
$assert C_SHORT_SIZE < 32;
$assert C_INT_SIZE < 128;
$assert C_LONG_SIZE < 128;
$assert C_LONG_LONG_SIZE <= 128;
$assert C_SHORT_SIZE <= C_INT_SIZE;
$assert C_INT_SIZE <= C_LONG_SIZE;
$assert C_LONG_SIZE <= C_LONG_LONG_SIZE;
$assert (C_SHORT_SIZE < 32);
$assert (C_INT_SIZE < 128);
$assert (C_LONG_SIZE < 128);
$assert (C_LONG_LONG_SIZE <= 128);
$assert (C_SHORT_SIZE <= C_INT_SIZE);
$assert (C_INT_SIZE <= C_LONG_SIZE);
$assert (C_LONG_SIZE <= C_LONG_LONG_SIZE);
def CShort = $typefrom(signed_int_from_bitsize($$C_SHORT_SIZE));
def CUShort = $typefrom(unsigned_int_from_bitsize($$C_SHORT_SIZE));
def CInt = $typefrom(signed_int_from_bitsize($$C_INT_SIZE));
def CUInt = $typefrom(unsigned_int_from_bitsize($$C_INT_SIZE));
def CLong = $typefrom(signed_int_from_bitsize($$C_LONG_SIZE));
def CULong = $typefrom(unsigned_int_from_bitsize($$C_LONG_SIZE));
def CLongLong = $typefrom(signed_int_from_bitsize($$C_LONG_LONG_SIZE));
def CULongLong = $typefrom(unsigned_int_from_bitsize($$C_LONG_LONG_SIZE));
def CSChar = ichar;
def CUChar = char;
$switch ($$C_INT_SIZE):
$case 64:
define CInt = long;
define CUInt = ulong;
$case 32:
define CInt = int;
define CUInt = uint;
$case 16:
define CInt = short;
define CUInt = ushort;
$default:
$assert(false, "Invalid C int size");
$endswitch;
def CChar = $typefrom($$C_CHAR_IS_SIGNED ? ichar.typeid : char.typeid);
$switch ($$C_LONG_SIZE):
$case 64:
define CLong = long;
define CULong = ulong;
$case 32:
define CLong = int;
define CULong = uint;
$case 16:
define CLong = short;
define CULong = ushort;
$default:
$assert(false, "Invalid C long size");
$endswitch;
// Helper macros
macro typeid signed_int_from_bitsize(usz $bitsize) @private
{
$switch ($bitsize)
$case 128: return int128.typeid;
$case 64: return long.typeid;
$case 32: return int.typeid;
$case 16: return short.typeid;
$case 8: return ichar.typeid;
$default: $error("Invalid bitsize");
$endswitch
}
$switch ($$C_SHORT_SIZE):
$case 32:
define CShort = int;
define CUShort = uint;
$case 16:
define CShort = short;
define CUShort = ushort;
$case 8:
define CShort = ichar;
define CUShort = char;
$default:
$assert(false, "Invalid C short size");
$endswitch;
macro typeid unsigned_int_from_bitsize(usz $bitsize) @private
{
$switch ($bitsize)
$case 128: return uint128.typeid;
$case 64: return ulong.typeid;
$case 32: return uint.typeid;
$case 16: return ushort.typeid;
$case 8: return char.typeid;
$default: $error("Invalid bitsize");
$endswitch
}
$switch ($$C_LONG_LONG_SIZE):
$case 128:
define CLongLong = int128;
define CULongLong = uint128;
$case 64:
define CLongLong = long;
define CULongLong = ulong;
$case 32:
define CLongLong = int;
define CULongLong = uint;
$case 16:
define CLongLong = short;
define CULongLong = ushort;
$default:
$assert(false, "Invalid C long long size");
$endswitch;
define CSChar = ichar;
define CUChar = char;
$if ($$C_CHAR_IS_SIGNED):
define CChar = ichar;
$else:
define CChar = char;
$endif;

View File

@@ -1,47 +1,45 @@
module std::core::string::conv;
const uint UTF16_SURROGATE_OFFSET @private = 0x10000;
const uint UTF16_SURROGATE_GENERIC_MASK @private = 0xF800;
const uint UTF16_SURROGATE_GENERIC_VALUE @private = 0xD800;
const uint UTF16_SURROGATE_MASK @private = 0xFC00;
const uint UTF16_SURROGATE_CODEPOINT_MASK @private = 0x03FF;
const uint UTF16_SURROGATE_BITS @private = 10;
const uint UTF16_SURROGATE_LOW_VALUE @private = 0xDC00;
const uint UTF16_SURROGATE_HIGH_VALUE @private = 0xD800;
private const uint UTF16_SURROGATE_OFFSET = 0x10000;
private const uint UTF16_SURROGATE_GENERIC_MASK = 0xF800;
private const uint UTF16_SURROGATE_GENERIC_VALUE = 0xD800;
private const uint UTF16_SURROGATE_MASK = 0xFC00;
private const uint UTF16_SURROGATE_CODEPOINT_MASK = 0x03FF;
private const uint UTF16_SURROGATE_BITS = 10;
private const uint UTF16_SURROGATE_LOW_VALUE = 0xDC00;
private const uint UTF16_SURROGATE_HIGH_VALUE = 0xD800;
/**
* @param c `The utf32 codepoint to convert`
* @param [out] output `the resulting buffer`
* @param available `the size available`
**/
fn usz! char32_to_utf8(Char32 c, char[] output)
fn usize! char32_to_utf8(Char32 c, char* output, usize available)
{
if (!output.len) return UnicodeResult.CONVERSION_FAILED?;
if (!available) return UnicodeResult.CONVERSION_FAILED!;
switch (true)
{
case c <= 0x7f:
case c < 0x7f:
output[0] = (char)c;
return 1;
case c <= 0x7ff:
if (output.len < 2) return UnicodeResult.CONVERSION_FAILED?;
case c < 0x7ff:
if (available < 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 UnicodeResult.CONVERSION_FAILED?;
output[0] = (char)(0xE0 | c >> 12);
output[1] = (char)(0x80 | (c >> 6 & 0x3F));
output[2] = (char)(0x80 | (c & 0x3F));
return 3;
case c <= 0x10ffff:
if (output.len < 4) return UnicodeResult.CONVERSION_FAILED?;
output[0] = (char)(0xF0 | c >> 18);
output[1] = (char)(0x80 | (c >> 12 & 0x3F));
output[2] = (char)(0x80 | (c >> 6 & 0x3F));
output[3] = (char)(0x80 | (c & 0x3F));
return 4;
default:
// 0x10FFFF and above is not defined.
return UnicodeResult.CONVERSION_FAILED?;
output[1] = (char)(0x80 | (c & 0x3F));
return 2;
case c < 0xffff:
if (available < 3) return UnicodeResult.CONVERSION_FAILED!;
output[0] = (char)(0xE0 | c >> 12);
output[1] = (char)(0x80 | (c >> 6 & 0x3F));
output[2] = (char)(0x80 | (c & 0x3F));
return 3;
default:
if (available < 4) return UnicodeResult.CONVERSION_FAILED!;
output[0] = (char)(0xF0 | c >> 18);
output[1] = (char)(0x80 | (c >> 12 & 0x3F));
output[2] = (char)(0x80 | (c >> 6 & 0x3F));
output[3] = (char)(0x80 | (c & 0x3F));
return 4;
}
}
@@ -73,7 +71,7 @@ fn void char32_to_utf16_unsafe(Char32 c, Char16** output)
* @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, usize *available, char** output)
{
Char16 high = *ptr;
if (high & UTF16_SURROGATE_GENERIC_MASK != UTF16_SURROGATE_GENERIC_VALUE)
@@ -82,21 +80,21 @@ fn void! char16_to_utf8_unsafe(Char16 *ptr, usz *available, char** output)
*available = 1;
return;
}
// Low surrogate first is an error
if (high & UTF16_SURROGATE_MASK != UTF16_SURROGATE_HIGH_VALUE) return UnicodeResult.INVALID_UTF16?;
// Low surrogate first is an error
if (high & UTF16_SURROGATE_MASK != UTF16_SURROGATE_HIGH_VALUE) return UnicodeResult.INVALID_UTF16!;
// Unmatched high surrogate is an error
if (*available == 1) return UnicodeResult.INVALID_UTF16?;
if (*available == 1) return UnicodeResult.INVALID_UTF16!;
Char16 low = ptr[1];
// Unmatched high surrogate, invalid
if (low & UTF16_SURROGATE_MASK != UTF16_SURROGATE_LOW_VALUE) return UnicodeResult.INVALID_UTF16?;
if (low & UTF16_SURROGATE_MASK != UTF16_SURROGATE_LOW_VALUE) return UnicodeResult.INVALID_UTF16!;
// The high bits of the codepoint are the value bits of the high surrogate
// The low bits of the codepoint are the value bits of the low surrogate
Char32 uc = (high & UTF16_SURROGATE_CODEPOINT_MASK) << UTF16_SURROGATE_BITS
| (low & UTF16_SURROGATE_CODEPOINT_MASK) + UTF16_SURROGATE_OFFSET;
// The high bits of the codepoint are the value bits of the high surrogate
// The low bits of the codepoint are the value bits of the low surrogate
Char32 uc = (high & UTF16_SURROGATE_CODEPOINT_MASK) << UTF16_SURROGATE_BITS
| (low & UTF16_SURROGATE_CODEPOINT_MASK) + UTF16_SURROGATE_OFFSET;
char32_to_utf8_unsafe(uc, output);
*available = 2;
}
@@ -104,28 +102,24 @@ fn void! char16_to_utf8_unsafe(Char16 *ptr, usz *available, char** output)
* @param c `The utf32 codepoint to convert`
* @param [inout] output `the resulting buffer`
**/
fn usz char32_to_utf8_unsafe(Char32 c, char** output)
fn void char32_to_utf8_unsafe(Char32 c, char** output)
{
switch
switch (true)
{
case c < 0x7f:
(*output)++[0] = (char)c;
return 1;
case c < 0x7ff:
(*output)++[0] = (char)(0xC0 | c >> 6);
(*output)++[0] = (char)(0x80 | (c & 0x3F));
return 2;
case c < 0xffff:
(*output)++[0] = (char)(0xE0 | c >> 12);
(*output)++[0] = (char)(0x80 | (c >> 6 & 0x3F));
(*output)++[0] = (char)(0x80 | (c & 0x3F));
return 3;
default:
(*output)++[0] = (char)(0xF0 | c >> 18);
(*output)++[0] = (char)(0x80 | (c >> 12 & 0x3F));
(*output)++[0] = (char)(0x80 | (c >> 6 & 0x3F));
(*output)++[0] = (char)(0x80 | (c & 0x3F));
return 4;
(*output)++[0] = (char)(0x80 | (c & 0x3F));
case c < 0xffff:
(*output)++[0] = (char)(0xE0 | c >> 12);
(*output)++[0] = (char)(0x80 | (c >> 6 & 0x3F));
(*output)++[0] = (char)(0x80 | (c & 0x3F));
default:
(*output)++[0] = (char)(0xF0 | c >> 18);
(*output)++[0] = (char)(0x80 | (c >> 12 & 0x3F));
(*output)++[0] = (char)(0x80 | (c >> 6 & 0x3F));
(*output)++[0] = (char)(0x80 | (c & 0x3F));
}
}
@@ -134,53 +128,49 @@ fn usz char32_to_utf8_unsafe(Char32 c, char** output)
* @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, usize* size)
{
usz max_size = *size;
if (max_size < 1) return UnicodeResult.INVALID_UTF8?;
usize max_size = *size;
if (max_size < 1) return UnicodeResult.INVALID_UTF8!;
char c = (ptr++)[0];
if ((c & 0x80) == 0)
{
*size = 1;
return c;
}
if ((c & 0xE0) == 0xC0)
{
if (max_size < 2) return UnicodeResult.INVALID_UTF8?;
*size = 2;
Char32 uc = (c & 0x1F) << 6;
if ((c & 0x80) == 0)
{
*size = 1;
return c;
}
if ((c & 0xE0) == 0xC0)
{
if (max_size < 2) return UnicodeResult.INVALID_UTF8!;
*size = 2;
Char32 uc = (c & 0x1F) << 6;
c = *ptr;
// Overlong sequence or invalid second.
if (!uc || c & 0xC0 != 0x80) return UnicodeResult.INVALID_UTF8?;
if (c & 0xC0 != 0x80) return UnicodeResult.INVALID_UTF8!;
return uc + c & 0x3F;
}
if ((c & 0xF0) == 0xE0)
{
if (max_size < 3) return UnicodeResult.INVALID_UTF8?;
*size = 3;
Char32 uc = (c & 0x0F) << 12;
}
if ((c & 0xF0) == 0xE0)
{
if (max_size < 3) return UnicodeResult.INVALID_UTF8!;
*size = 3;
Char32 uc = (c & 0x0F) << 12;
c = ptr++[0];
if (c & 0xC0 != 0x80) return UnicodeResult.INVALID_UTF8?;
if (c & 0xC0 != 0x80) return UnicodeResult.INVALID_UTF8!;
uc += (c & 0x3F) << 6;
c = ptr++[0];
// Overlong sequence or invalid last
if (!uc || c & 0xC0 != 0x80) return UnicodeResult.INVALID_UTF8?;
if (c & 0xC0 != 0x80) return UnicodeResult.INVALID_UTF8!;
return uc + c & 0x3F;
}
if (max_size < 4) return UnicodeResult.INVALID_UTF8?;
if ((c & 0xF8) != 0xF0) return UnicodeResult.INVALID_UTF8?;
*size = 4;
Char32 uc = (c & 0x07) << 18;
}
if (max_size < 4) return UnicodeResult.INVALID_UTF8!;
*size = 4;
Char32 uc = (c & 0x07) << 18;
c = ptr++[0];
if (c & 0xC0 != 0x80) return UnicodeResult.INVALID_UTF8?;
if (c & 0xC0 != 0x80) return UnicodeResult.INVALID_UTF8!;
uc += (c & 0x3F) << 12;
c = ptr++[0];
if (c & 0xC0 != 0x80) return UnicodeResult.INVALID_UTF8?;
if (c & 0xC0 != 0x80) return UnicodeResult.INVALID_UTF8!;
uc += (c & 0x3F) << 6;
c = ptr++[0];
// Overlong sequence or invalid last
if (!uc || c & 0xC0 != 0x80) return UnicodeResult.INVALID_UTF8?;
if (c & 0xC0 != 0x80) return UnicodeResult.INVALID_UTF8!;
return uc + c & 0x3F;
}
@@ -188,9 +178,9 @@ fn Char32! utf8_to_char32(char* ptr, usz* size)
* @param utf8 `An UTF-8 encoded slice of bytes`
* @return `the number of encoded code points`
**/
fn usz utf8_codepoints(String utf8)
fn usize utf8_codepoints(char[] utf8)
{
usz len = 0;
usize len = 0;
foreach (char c : utf8)
{
if (c & 0xC0 != 0x80) len++;
@@ -203,9 +193,9 @@ fn usz utf8_codepoints(String utf8)
* @param [in] utf32 `the utf32 data to calculate from`
* @return `the length of the resulting UTF8 array`
**/
fn usz utf8len_for_utf32(Char32[] utf32)
fn usize utf8len_for_utf32(Char32[] utf32)
{
usz len = 0;
usize len = 0;
foreach (Char32 uc : utf32)
{
switch (true)
@@ -228,11 +218,11 @@ fn usz utf8len_for_utf32(Char32[] utf32)
* @param [in] utf16 `the utf16 data to calculate from`
* @return `the length of the resulting UTF8 array`
**/
fn usz utf8len_for_utf16(Char16[] utf16)
fn usize utf8len_for_utf16(Char16[] utf16)
{
usz len = 0;
usz len16 = utf16.len;
for (usz i = 0; i < len16; i++)
usize len = 0;
usize len16 = utf16.len;
for (usize i = 0; i < len16; i++)
{
Char16 c = utf16[i];
if (c & UTF16_SURROGATE_GENERIC_MASK != UTF16_SURROGATE_GENERIC_VALUE)
@@ -260,11 +250,11 @@ fn usz utf8len_for_utf16(Char16[] utf16)
* @param utf8 `the utf8 data to calculate from`
* @return `the length of the resulting UTF16 array`
**/
fn usz utf16len_for_utf8(String utf8)
fn usize utf16len_for_utf8(char[] utf8)
{
usz len = utf8.len;
usz len16 = 0;
for (usz i = 0; i < len; i++)
usize len = utf8.len;
usize len16 = 0;
for (usize i = 0; i < len; i++)
{
len16++;
char c = utf8[i];
@@ -275,17 +265,17 @@ fn usz utf16len_for_utf8(String utf8)
if (c & 0xF0 == 0xE0) continue;
i++;
len16++;
}
return len16;
}
return len16;
}
/**
* @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)
fn usize utf16len_for_utf32(Char32[] utf32)
{
usz len = utf32.len;
usize len = utf32.len;
foreach (Char32 uc : utf32)
{
if (uc >= UTF16_SURROGATE_OFFSET) len++;
@@ -300,17 +290,17 @@ 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 usize! utf32to8(Char32[] utf32, char[] utf8_buffer)
{
char[] buffer = utf8_buffer;
foreach (uc : utf32)
usize len = utf8_buffer.len;
char* ptr = utf8_buffer.ptr;
foreach (Char32 uc : utf32)
{
usz used = char32_to_utf8(uc, buffer) @inline!;
buffer = buffer[used..];
usize used = char32_to_utf8(uc, ptr, len) @inline?;
len -= used;
ptr += used;
}
// Zero terminate if there is space.
if (buffer.len > 0) buffer[0] = 0;
return utf8_buffer.len - buffer.len;
return utf8_buffer.len - len;
}
/**
@@ -320,23 +310,21 @@ 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 usize! utf8to32(char[] utf8, Char32[] utf32_buffer)
{
usz len = utf8.len;
usize len = utf8.len;
Char32* ptr = utf32_buffer.ptr;
usz len32 = 0;
usz buf_len = utf32_buffer.len;
for (usz i = 0; i < len;)
{
if (len32 == buf_len) return UnicodeResult.CONVERSION_FAILED?;
usz width = len - i;
Char32 uc = utf8_to_char32(&utf8[i], &width) @inline!;
i += width;
ptr[len32++] = uc;
}
// Zero terminate if possible
if (len32 + 1 < buf_len) ptr[len32] = 0;
return len32;
usize len32 = 0;
usize buf_len = utf32_buffer.len;
for (usize i = 0; i < len;)
{
if (len32 == buf_len) return UnicodeResult.CONVERSION_FAILED!;
usize width = len - i;
Char32 uc = utf8_to_char32(&utf8[i], &width) @inline?;
i += width;
ptr[len32++] = uc;
}
return len32;
}
/**
@@ -349,11 +337,11 @@ fn usz! utf8to32(String utf8, Char32[] utf32_buffer)
**/
fn void! utf16to8_unsafe(Char16[] utf16, char* utf8_buffer)
{
usz len16 = utf16.len;
for (usz i = 0; i < len16;)
usize len16 = utf16.len;
for (usize i = 0; i < len16;)
{
usz available = len16 - i;
char16_to_utf8_unsafe(&utf16[i], &available, &utf8_buffer) @inline!;
usize available = len16 - i;
char16_to_utf8_unsafe(&utf16[i], &available, &utf8_buffer) @inline?;
i += available;
}
}
@@ -366,16 +354,16 @@ fn void! utf16to8_unsafe(Char16[] utf16, char* utf8_buffer)
* @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(char[] utf8, Char32* utf32_buffer)
{
usz len = utf8.len;
for (usz i = 0; i < len;)
{
usz width = len - i;
Char32 uc = utf8_to_char32(&utf8[i], &width) @inline!;
i += width;
(utf32_buffer++)[0] = uc;
}
usize len = utf8.len;
for (usize i = 0; i < len;)
{
usize width = len - i;
Char32 uc = utf8_to_char32(&utf8[i], &width) @inline?;
i += width;
(utf32_buffer++)[0] = uc;
}
}
/**
@@ -386,16 +374,16 @@ fn void! utf8to32_unsafe(String utf8, Char32* utf32_buffer)
* @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(char[] utf8, Char16* utf16_buffer)
{
usz len = utf8.len;
for (usz i = 0; i < len;)
{
usz width = len - i;
Char32 uc = utf8_to_char32(&utf8[i], &width) @inline!;
char32_to_utf16_unsafe(uc, &utf16_buffer) @inline;
i += width;
}
usize len = utf8.len;
for (usize i = 0; i < len;)
{
usize width = len - i;
Char32 uc = utf8_to_char32(&utf8[i], &width) @inline?;
char32_to_utf16_unsafe(uc, &utf16_buffer) @inline;
i += width;
}
}
/**

View File

@@ -1,471 +0,0 @@
module std::core::dstring;
import std::io;
distinct DString (OutStream) = void*;
const usz MIN_CAPACITY @private = 16;
/**
* @require !self.data() "String already initialized"
**/
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)!!;
data.allocator = allocator;
data.len = 0;
data.capacity = capacity;
return *self = (DString)data;
}
/**
* @require !self.data() "String already initialized"
**/
fn DString DString.temp_init(&self, usz capacity = MIN_CAPACITY)
{
self.new_init(capacity, allocator::temp()) @inline;
return *self;
}
fn DString new_with_capacity(usz capacity, Allocator allocator = allocator::heap())
{
return DString{}.new_init(capacity, allocator);
}
fn DString temp_with_capacity(usz capacity) => new_with_capacity(capacity, allocator::temp()) @inline;
fn DString new(String c = "", Allocator allocator = allocator::heap())
{
usz len = c.len;
StringData* data = (StringData*)new_with_capacity(len, allocator);
if (len)
{
data.len = len;
mem::copy(&data.chars, c.ptr, len);
}
return (DString)data;
}
fn DString temp_new(String s = "") => new(s, allocator::temp()) @inline;
fn DString DString.new_concat(self, DString b, Allocator allocator = allocator::heap())
{
DString string;
string.new_init(self.len() + b.len(), allocator);
string.append(self);
string.append(b);
return string;
}
fn DString DString.temp_concat(self, DString b) => self.new_concat(b, allocator::temp());
fn ZString DString.zstr_view(&self)
{
StringData* data = self.data();
if (!data) return "";
if (data.capacity == data.len)
{
self.reserve(1);
data = self.data();
data.chars[data.len] = 0;
}
else if (data.chars[data.len] != 0)
{
data.chars[data.len] = 0;
}
return (ZString)&data.chars[0];
}
fn usz DString.capacity(self)
{
if (!self) return 0;
return self.data().capacity;
}
fn usz DString.len(&self) @dynamic
{
if (!*self) return 0;
return self.data().len;
}
/**
* @require new_size <= self.len()
*/
fn void DString.chop(self, usz new_size)
{
if (!self) return;
self.data().len = new_size;
}
fn String DString.str_view(self)
{
StringData* data = self.data();
if (!data) return "";
return (String)data.chars[:data.len];
}
fn void DString.append_utf32(&self, Char32[] chars)
{
self.reserve(chars.len);
foreach (Char32 c : chars)
{
self.append_char32(c);
}
}
/**
* @require index < self.len()
**/
fn void DString.set(self, usz index, char c)
{
self.data().chars[index] = c;
}
fn void DString.append_repeat(&self, char c, usz times)
{
if (times == 0) return;
self.reserve(times);
StringData* data = self.data();
for (usz i = 0; i < times; i++)
{
data.chars[data.len++] = c;
}
}
/**
* @require c <= 0x10ffff
*/
fn void DString.append_char32(&self, Char32 c)
{
char[4] buffer @noinit;
char* p = &buffer;
usz n = conv::char32_to_utf8_unsafe(c, &p);
self.reserve(n);
StringData* data = self.data();
data.chars[data.len:n] = buffer[:n];
data.len += n;
}
fn DString DString.tcopy(&self) => self.copy(allocator::temp());
fn DString DString.copy(self, Allocator allocator = null)
{
if (!self)
{
if (allocator) return new_with_capacity(0, allocator);
return (DString)null;
}
StringData* data = self.data();
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 = allocator::heap())
{
usz str_len = self.len();
if (!str_len)
{
return (ZString)allocator::calloc(allocator, 1);
}
char* zstr = allocator::malloc(allocator, str_len + 1);
StringData* data = self.data();
mem::copy(zstr, &data.chars, str_len);
zstr[str_len] = 0;
return (ZString)zstr;
}
fn String DString.copy_str(self, Allocator allocator = allocator::heap())
{
return (String)self.copy_zstr(allocator)[:self.len()];
}
fn String DString.tcopy_str(self) => self.copy_str(allocator::temp()) @inline;
fn bool DString.equals(self, DString other_string)
{
StringData *str1 = self.data();
StringData *str2 = other_string.data();
if (str1 == str2) return true;
if (!str1) return str2.len == 0;
if (!str2) return str1.len == 0;
usz str1_len = str1.len;
if (str1_len != str2.len) return false;
for (int i = 0; i < str1_len; i++)
{
if (str1.chars[i] != str2.chars[i]) return false;
}
return true;
}
fn void DString.free(&self)
{
if (!*self) return;
StringData* data = self.data();
if (!data) return;
allocator::free(data.allocator, data);
*self = (DString)null;
}
fn bool DString.less(self, DString other_string)
{
StringData* str1 = self.data();
StringData* str2 = other_string.data();
if (str1 == str2) return false;
if (!str1) return str2.len != 0;
if (!str2) return str1.len == 0;
usz str1_len = str1.len;
usz str2_len = str2.len;
if (str1_len != str2_len) return str1_len < str2_len;
for (int i = 0; i < str1_len; i++)
{
if (str1.chars[i] >= str2.chars[i]) return false;
}
return true;
}
fn void DString.append_chars(&self, String str)
{
usz other_len = str.len;
if (!other_len) return;
if (!*self)
{
*self = new(str);
return;
}
self.reserve(other_len);
StringData* data = self.data();
mem::copy(&data.chars[data.len], str.ptr, other_len);
data.len += other_len;
}
fn Char32[] DString.copy_utf32(&self, Allocator allocator = allocator::heap())
{
return self.str_view().to_new_utf32(allocator) @inline!!;
}
fn void DString.append_string(&self, DString str)
{
StringData* other = str.data();
if (!other) return;
self.append(str.str_view());
}
fn void DString.clear(self)
{
if (!self) return;
self.data().len = 0;
}
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
{
self.append_char(c);
}
fn void DString.append_char(&self, char c)
{
if (!*self)
{
*self = new_with_capacity(MIN_CAPACITY);
}
self.reserve(1);
StringData* data = self.data();
data.chars[data.len++] = c;
}
/**
* @require start < self.len()
* @require end < self.len()
* @require end >= start "End must be same or equal to the start"
**/
fn void DString.delete_range(&self, usz start, usz end)
{
self.delete(start, end - start + 1);
}
/**
* @require start < self.len()
* @require start + len <= self.len()
**/
fn void DString.delete(&self, usz start, usz len = 1)
{
if (!len) return;
StringData* data = self.data();
usz new_len = data.len - len;
if (new_len == 0)
{
data.len = 0;
return;
}
usz len_after = data.len - start - len;
if (len_after > 0)
{
data.chars[start:len_after] = data.chars[start + len:len_after];
}
data.len = new_len;
}
macro void DString.append(&self, value)
{
var $Type = $typeof(value);
$switch ($Type)
$case char:
$case ichar:
self.append_char(value);
$case DString:
self.append_string(value);
$case String:
self.append_chars(value);
$case Char32:
self.append_char32(value);
$default:
$switch
$case $defined((Char32)value):
self.append_char32((Char32)value);
$case $defined((String)value):
self.append_chars((String)value);
$default:
$error "Unsupported type for append use appendf instead.";
$endswitch
$endswitch
}
fn void DString.insert_at(&self, usz index, String s)
{
if (s.len == 0) return;
self.reserve(s.len);
StringData* data = self.data();
usz len = self.len();
if (data.chars[:len].ptr == s.ptr)
{
// Source and destination are the same: nothing to do.
return;
}
index = min(index, len);
data.len += s.len;
char* start = data.chars[index:s.len].ptr; // area to insert into
mem::move(start + s.len, start, len - index); // move existing data
switch
{
case s.ptr <= start && start < s.ptr + s.len:
// Overlapping areas.
foreach_r (i, c : s)
{
data.chars[index + i] = c;
}
case start <= s.ptr && s.ptr < start + len:
// Source has moved.
mem::move(start, s.ptr + s.len, s.len);
default:
mem::move(start, s, s.len);
}
}
fn usz! DString.appendf(&self, String format, args...) @maydiscard
{
Formatter formatter;
formatter.init(&out_string_append_fn, self);
return formatter.vprintf(format, args);
}
fn usz! DString.appendfn(&self, String format, args...) @maydiscard
{
Formatter formatter;
formatter.init(&out_string_append_fn, self);
usz len = formatter.vprintf(format, args)!;
self.append('\n');
return len + 1;
}
fn DString new_join(String[] s, String joiner, Allocator allocator = allocator::heap())
{
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(total_size, allocator);
res.append(s[0]);
foreach (String* &str : s[1..])
{
res.append(joiner);
res.append(*str);
}
return res;
}
fn void! out_string_append_fn(void* data, char c) @private
{
DString* s = data;
s.append_char(c);
}
fn StringData* DString.data(self) @inline @private
{
return (StringData*)self;
}
fn void DString.reserve(&self, usz addition)
{
StringData* data = self.data();
if (!data)
{
*self = dstring::new_with_capacity(addition);
return;
}
usz len = data.len + addition;
if (data.capacity >= len) return;
usz new_capacity = data.capacity * 2;
if (new_capacity < MIN_CAPACITY) new_capacity = MIN_CAPACITY;
while (new_capacity < len) new_capacity *= 2;
data.capacity = new_capacity;
*self = (DString)allocator::realloc(data.allocator, data, StringData.sizeof + new_capacity);
}
fn usz! DString.read_from_stream(&self, InStream reader)
{
if (&reader.available)
{
usz total_read = 0;
while (usz available = reader.available()!)
{
self.reserve(available);
StringData* data = self.data();
usz len = reader.read(data.chars[data.len..(data.capacity - 1)])!;
total_read += len;
data.len += len;
}
return total_read;
}
usz total_read = 0;
while (true)
{
// Reserve at least 16 bytes
self.reserve(16);
StringData* data = self.data();
// Read into the rest of the buffer
usz read = reader.read(data.chars[data.len..(data.capacity - 1)])!;
data.len += read;
// Ok, we reached the end.
if (read < 16) return total_read;
// Otherwise go another round
}
}
struct StringData @private
{
Allocator allocator;
usz len;
usz capacity;
char[*] chars;
}

View File

@@ -1,22 +1,14 @@
// Copyright (c) 2021-2024 Christoffer Lerno. All rights reserved.
// Copyright (c) 2021 Christoffer Lerno. All rights reserved.
// Use of this source code is governed by the MIT license
// a copy of which can be found in the LICENSE_STDLIB file.
module std::core::env;
enum CompilerOptLevel
{
O0,
O1,
O2,
O3
}
enum MemoryEnvironment
{
NORMAL,
SMALL,
TINY,
NONE
O0,
O1,
O2,
O3
}
enum OsType
@@ -32,7 +24,7 @@ enum OsType
KFREEBSD,
LINUX,
PS3,
MACOS,
MACOSX,
NETBSD,
OPENBSD,
SOLARIS,
@@ -59,128 +51,11 @@ enum OsType
EMSCRIPTEN,
}
enum ArchType
{
UNKNOWN,
ARM, // ARM (little endian): arm, armv.*, xscale
ARMB, // ARM (big endian): armeb
AARCH64, // AArch64 (little endian): aarch64
AARCH64_BE, // AArch64 (big endian): aarch64_be
AARCH64_32, // AArch64 (little endian) ILP32: aarch64_32
ARC, // ARC: Synopsys ARC
AVR, // AVR: Atmel AVR microcontroller
BPFEL, // eBPF or extended BPF or 64-bit BPF (little endian)
BPFEB, // eBPF or extended BPF or 64-bit BPF (big endian)
HEXAGON, // Hexagon: hexagon
MIPS, // MIPS: mips, mipsallegrex, mipsr6
MIPSEL, // MIPSEL: mipsel, mipsallegrexe, mipsr6el
MIPS64, // MIPS64: mips64, mips64r6, mipsn32, mipsn32r6
MIPS64EL, // MIPS64EL: mips64el, mips64r6el, mipsn32el, mipsn32r6el
MSP430, // MSP430: msp430
PPC, // PPC: powerpc
PPC64, // PPC64: powerpc64, ppu
PPC64LE, // PPC64LE: powerpc64le
R600, // R600: AMD GPUs HD2XXX - HD6XXX
AMDGCN, // AMDGCN: AMD GCN GPUs
RISCV32, // RISC-V (32-bit): riscv32
RISCV64, // RISC-V (64-bit): riscv64
SPARC, // Sparc: sparc
SPARCV9, // Sparcv9: Sparcv9
SPARCEL, // Sparc: (endianness = little). NB: 'Sparcle' is a CPU variant
SYSTEMZ, // SystemZ: s390x
TCE, // TCE (http://tce.cs.tut.fi/): tce
TCELE, // TCE little endian (http://tce.cs.tut.fi/): tcele
THUMB, // Thumb (little endian): thumb, thumbv.*
THUMBEB, // Thumb (big endian): thumbeb
X86, // X86: i[3-9]86
X86_64, // X86-64: amd64, x86_64
XCORE, // XCore: xcore
NVPTX, // NVPTX: 32-bit
NVPTX64, // NVPTX: 64-bit
LE32, // le32: generic little-endian 32-bit CPU (PNaCl)
LE64, // le64: generic little-endian 64-bit CPU (PNaCl)
AMDIL, // AMDIL
AMDIL64, // AMDIL with 64-bit pointers
HSAIL, // AMD HSAIL
HSAIL64, // AMD HSAIL with 64-bit pointers
SPIR, // SPIR: standard portable IR for OpenCL 32-bit version
SPIR64, // SPIR: standard portable IR for OpenCL 64-bit version
KALIMBA, // Kalimba: generic kalimba
SHAVE, // SHAVE: Movidius vector VLIW processors
LANAI, // Lanai: Lanai 32-bit
WASM32, // WebAssembly with 32-bit pointers
WASM64, // WebAssembly with 64-bit pointers
RSCRIPT32, // 32-bit RenderScript
RSCRIPT64, // 64-bit RenderScript
}
const OsType OS_TYPE = (OsType)$$OS_TYPE;
const ArchType ARCH_TYPE = (ArchType)$$ARCH_TYPE;
const bool LIBC = $$COMPILER_LIBC_AVAILABLE;
const bool NO_LIBC = !$$COMPILER_LIBC_AVAILABLE;
const CompilerOptLevel COMPILER_OPT_LEVEL = (CompilerOptLevel)$$COMPILER_OPT_LEVEL;
const OsType OS_TYPE = (OsType)($$OS_TYPE);
const CompilerOptLevel COMPILER_OPT_LEVEL = (CompilerOptLevel)($$COMPILER_OPT_LEVEL);
const bool BIG_ENDIAN = $$PLATFORM_BIG_ENDIAN;
const bool I128_NATIVE_SUPPORT = $$PLATFORM_I128_SUPPORTED;
const bool F16_SUPPORT = $$PLATFORM_F16_SUPPORTED;
const bool I128_SUPPORT = $$PLATFORM_I128_SUPPORTED;
const bool F128_SUPPORT = $$PLATFORM_F128_SUPPORTED;
const REGISTER_SIZE = $$REGISTER_SIZE;
const bool F16_SUPPORT = $$PLATFORM_F16_SUPPORTED;
const bool COMPILER_SAFE_MODE = $$COMPILER_SAFE_MODE;
const bool DEBUG_SYMBOLS = $$DEBUG_SYMBOLS;
const usz LLVM_VERSION = $$LLVM_VERSION;
const bool BENCHMARKING = $$BENCHMARKING;
const bool TESTING = $$TESTING;
const MemoryEnvironment MEMORY_ENV = (MemoryEnvironment)$$MEMORY_ENVIRONMENT;
const bool TRACK_MEMORY = DEBUG_SYMBOLS && (COMPILER_SAFE_MODE || TESTING);
const bool X86_64 = ARCH_TYPE == X86_64;
const bool X86 = ARCH_TYPE == X86;
const bool AARCH64 = ARCH_TYPE == AARCH64;
const bool NATIVE_STACKTRACE = LINUX || DARWIN || WIN32;
const bool LINUX = LIBC && OS_TYPE == LINUX;
const bool DARWIN = LIBC && os_is_darwin();
const bool WIN32 = LIBC && OS_TYPE == WIN32;
const bool POSIX = LIBC && os_is_posix();
const bool OPENBSD = LIBC && OS_TYPE == OPENBSD;
const bool FREEBSD = LIBC && OS_TYPE == FREEBSD;
const bool NETBSD = LIBC && OS_TYPE == NETBSD;
const bool WASI = LIBC && OS_TYPE == WASI;
const bool WASM_NOLIBC @builtin = !LIBC && ARCH_TYPE == ArchType.WASM32 || ARCH_TYPE == ArchType.WASM64;
macro bool os_is_darwin()
{
$switch (OS_TYPE)
$case IOS:
$case MACOS:
$case TVOS:
$case WATCHOS:
return true;
$default:
return false;
$endswitch
}
macro bool os_is_posix()
{
$switch (OS_TYPE)
$case IOS:
$case MACOS:
$case NETBSD:
$case LINUX:
$case KFREEBSD:
$case FREEBSD:
$case OPENBSD:
$case SOLARIS:
$case TVOS:
$case WATCHOS:
return true;
$case WIN32:
$case WASI:
$case EMSCRIPTEN:
return false;
$default:
$echo("Assuming non-Posix environment");
return false;
$endswitch
}
const BUILTIN_EXPECT_IS_DISABLED = $feature(DISABLE_BUILTIN_EXPECT);
const BUILTIN_PREFETCH_IS_DISABLED = $feature(DISABLE_BUILTIN_PREFETCH);
const usize TEMP_ALLOCATOR_SIZE = 128 * 1024;

View File

@@ -1,713 +1,262 @@
// Copyright (c) 2021-2023 Christoffer Lerno. All rights reserved.
// Copyright (c) 2021 Christoffer Lerno. All rights reserved.
// Use of this source code is governed by the MIT license
// a copy of which can be found in the LICENSE_STDLIB file.
module std::core::mem;
import std::core::mem::allocator @public;
import std::math;
const MAX_MEMORY_ALIGNMENT = 0x1000_0000;
const DEFAULT_MEM_ALIGNMENT = (void*.alignof) * 2;
/**
* Load a vector from memory according to a mask assuming default alignment.
*
* @param ptr "The pointer address to load from."
* @param mask "The mask for the load"
* @param passthru "The value to use for non masked values"
* @require $assignable(&&passthru, $typeof(ptr)) : "Pointer and passthru must match"
* @require @typekind(passthru) == VECTOR : "Expected passthru to be a vector"
* @require passthru.len == mask.len : "Mask and passthru must have the same length"
*
* @return "A vector with the loaded values where the mask is true, passthru where the mask is false"
**/
macro masked_load(ptr, bool[<*>] mask, passthru)
macro @volatile_load(&x)
{
return $$masked_load(ptr, mask, passthru, 0);
return $$volatile_load(&x);
}
/**
* Load a vector from memory according to a mask.
*
* @param ptr "The pointer address to load from."
* @param mask "The mask for the load"
* @param passthru "The value to use for non masked values"
* @param $alignment "The alignment to assume for the pointer"
*
* @require $assignable(&&passthru, $typeof(ptr)) : "Pointer and passthru must match"
* @require @typekind(passthru) == VECTOR : "Expected passthru to be a vector"
* @require passthru.len == mask.len : "Mask and passthru must have the same length"
* @require math::is_power_of_2($alignment) : "The alignment must be a power of two"
*
* @return "A vector with the loaded values where the mask is true, passthru where the mask is false"
**/
macro @masked_load_aligned(ptr, bool[<*>] mask, passthru, usz $alignment)
macro @volatile_store(&x, y)
{
return $$masked_load(ptr, mask, passthru, $alignment);
}
/**
* Load values from a pointer vector, assuming default alignment.
*
* @param ptrvec "The vector of pointers to load from."
* @param mask "The mask for the load"
* @param passthru "The value to use for non masked values"
*
* @require @typekind(ptrvec) == VECTOR : "Expected ptrvec to be a vector"
* @require @typekind(passthru) == VECTOR : "Expected passthru to be a vector"
* @require $assignable(&&passthru[0], $typeof(ptrvec[0])) : "Pointer and passthru must match"
* @require passthru.len == mask.len : "Mask and passthru must have the same length"
* @require mask.len == ptrvec.len : "Mask and ptrvec must have the same length"
*
* @return "A vector with the loaded values where the mask is true, passthru where the mask is false"
**/
macro gather(ptrvec, bool[<*>] mask, passthru)
{
return $$gather(ptrvec, mask, passthru, 0);
}
/**
* Load values from a pointer vector.
*
* @param ptrvec "The vector of pointers to load from."
* @param mask "The mask for the load"
* @param passthru "The value to use for non masked values"
* @param $alignment "The alignment to assume for the pointers"
*
* @require @typekind(ptrvec) == VECTOR : "Expected ptrvec to be a vector"
* @require @typekind(passthru) == VECTOR : "Expected passthru to be a vector"
* @require $assignable(&&passthru[0], $typeof(ptrvec[0])) : "Pointer and passthru must match"
* @require passthru.len == mask.len : "Mask and passthru must have the same length"
* @require mask.len == ptrvec.len : "Mask and ptrvec must have the same length"
* @require math::is_power_of_2($alignment) : "The alignment must be a power of two"
*
* @return "A vector with the loaded values where the mask is true, passthru where the mask is false"
**/
macro @gather_aligned(ptrvec, bool[<*>] mask, passthru, usz $alignment)
{
return $$gather(ptrvec, mask, passthru, $alignment);
}
/**
* Store parts of a vector according to the mask, assuming default alignment.
*
* @param ptr "The pointer address to store to."
* @param value "The value to store masked"
* @param mask "The mask for the store"
*
* @require $assignable(&&value, $typeof(ptr)) : "Pointer and value must match"
* @require @typekind(value) == VECTOR : "Expected value to be a vector"
* @require value.len == mask.len : "Mask and value must have the same length"
**/
macro masked_store(ptr, value, bool[<*>] mask)
{
return $$masked_store(ptr, value, mask, 0);
}
/**
* @param ptr "The pointer address to store to."
* @param value "The value to store masked"
* @param mask "The mask for the store"
* @param $alignment "The alignment of the pointer"
*
* @require $assignable(&&value, $typeof(ptr)) : "Pointer and value must match"
* @require @typekind(value) == VECTOR : "Expected value to be a vector"
* @require value.len == mask.len : "Mask and value must have the same length"
* @require math::is_power_of_2($alignment) : "The alignment must be a power of two"
*
**/
macro @masked_store_aligned(ptr, value, bool[<*>] mask, usz $alignment)
{
return $$masked_store(ptr, value, mask, $alignment);
}
/**
* @param ptrvec "The vector pointer containing the addresses to store to."
* @param value "The value to store masked"
* @param mask "The mask for the store"
* @require @typekind(ptrvec) == VECTOR : "Expected ptrvec to be a vector"
* @require @typekind(value) == VECTOR : "Expected value to be a vector"
* @require $assignable(&&value[0], $typeof(ptrvec[0])) : "Pointer and value must match"
* @require value.len == mask.len : "Mask and value must have the same length"
* @require mask.len == ptrvec.len : "Mask and ptrvec must have the same length"
*
**/
macro scatter(ptrvec, value, bool[<*>] mask)
{
return $$scatter(ptrvec, value, mask, 0);
}
/**
* @param ptrvec "The vector pointer containing the addresses to store to."
* @param value "The value to store masked"
* @param mask "The mask for the store"
* @param $alignment "The alignment of the load"
*
* @require @typekind(ptrvec) == VECTOR : "Expected ptrvec to be a vector"
* @require @typekind(value) == VECTOR : "Expected value to be a vector"
* @require $assignable(&&value[0], $typeof(ptrvec[0])) : "Pointer and value must match"
* @require value.len == mask.len : "Mask and value must have the same length"
* @require mask.len == ptrvec.len : "Mask and ptrvec must have the same length"
* @require math::is_power_of_2($alignment) : "The alignment must be a power of two"
**/
macro @scatter_aligned(ptrvec, value, bool[<*>] mask, usz $alignment)
{
return $$scatter(ptrvec, value, mask, $alignment);
}
macro @volatile_load(&x) @builtin
{
return $$volatile_load(x);
}
/**
* @require $assignable(y, $typeof(*x)) : "The value doesn't match the variable"
**/
macro @volatile_store(&x, y) @builtin
{
return $$volatile_store(x, ($typeof(*x))y);
}
enum AtomicOrdering : int
{
NOT_ATOMIC, // Not atomic
UNORDERED, // No lock
RELAXED, // Consistent ordering
ACQUIRE, // Barrier locking load/store
RELEASE, // Barrier releasing load/store
ACQUIRE_RELEASE, // Barrier fence to load/store
SEQ_CONSISTENT, // Acquire semantics, ordered with other seq_consistent
}
/**
* @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 $ordering != AtomicOrdering.RELEASE "Release ordering is not valid for load."
* @require $ordering != AtomicOrdering.ACQUIRE_RELEASE "Acquire release is not valid for load."
* @require types::may_load_atomic($typeof(x)) "Only integer, float and pointers may be used."
* @require @typekind(x) == POINTER "You can only load from a pointer"
**/
macro @atomic_load(&x, AtomicOrdering $ordering = SEQ_CONSISTENT, $volatile = false) @builtin
{
return $$atomic_load(x, $volatile, (int)$ordering);
}
/**
* @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."
**/
macro void @atomic_store(&x, value, AtomicOrdering $ordering = SEQ_CONSISTENT, $volatile = false) @builtin
{
$$atomic_store(x, value, $volatile, (int)$ordering);
}
/**
* @require $success != AtomicOrdering.NOT_ATOMIC && $success != AtomicOrdering.UNORDERED "Acquire ordering is not valid."
* @require $failure != AtomicOrdering.RELEASE && $failure != AtomicOrdering.ACQUIRE_RELEASE "Acquire release is not valid."
**/
macro compare_exchange(ptr, compare, value, AtomicOrdering $success = SEQ_CONSISTENT, AtomicOrdering $failure = SEQ_CONSISTENT, bool $volatile = true, bool $weak = false, usz $alignment = 0)
{
return $$compare_exchange(ptr, compare, value, $volatile, $weak, $success.ordinal, $failure.ordinal, $alignment);
}
/**
* @require $success != AtomicOrdering.NOT_ATOMIC && $success != AtomicOrdering.UNORDERED "Acquire ordering is not valid."
* @require $failure != AtomicOrdering.RELEASE && $failure != AtomicOrdering.ACQUIRE_RELEASE "Acquire release is not valid."
**/
macro compare_exchange_volatile(ptr, compare, value, AtomicOrdering $success = SEQ_CONSISTENT, AtomicOrdering $failure = SEQ_CONSISTENT)
{
return compare_exchange(ptr, compare, value, $success, $failure, true);
return $$volatile_store(&x, y);
}
/**
* @require math::is_power_of_2(alignment)
**/
fn usz aligned_offset(usz offset, usz alignment)
fn usize aligned_offset(usize offset, usize alignment)
{
return alignment * ((offset + alignment - 1) / alignment);
}
macro void* aligned_pointer(void* ptr, usz alignment)
macro void* aligned_pointer(void* ptr, usize alignment)
{
return (void*)(uptr)aligned_offset((uptr)ptr, alignment);
}
/**
* @require math::is_power_of_2(alignment)
**/
fn bool ptr_is_aligned(void* ptr, usz alignment) @inline
fn bool ptr_is_aligned(void* ptr, usize alignment) @inline
{
return (uptr)ptr & ((uptr)alignment - 1) == 0;
}
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);
}
macro void clear_inline(void* dst, usz $len, usz $dst_align = 0, bool $is_volatile = false)
{
$$memset_inline(dst, (char)0, $len, $is_volatile, $dst_align);
}
/**
* 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."
*
* @require len == 0 || dst + len <= src || src + len <= dst : "Ranges may not overlap"
**/
macro void copy(void* dst, void* src, usz len, usz $dst_align = 0, usz $src_align = 0, bool $is_volatile = false, bool $inlined = false)
macro void copy(void* dst, void* src, usize len, usize $dst_align = 0, usize $src_align = 0, bool $is_volatile = false)
{
$$memcpy(dst, src, len, $is_volatile, $dst_align, $src_align);
}
/**
* 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."
*
* @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)
{
$$memcpy_inline(dst, src, $len, $is_volatile, $dst_align, $src_align);
}
/**
* Copy memory from src to dst but correctly handle the possibility of overlapping ranges.
*
* @param [&out] dst "The destination to copy to"
* @param [&in] src "The source to copy from"
* @param len "The number of bytes to copy"
* @param $dst_align "the alignment of the destination if different from the default, 0 assumes the default"
* @param $src_align "the alignment of the destination if different from the default, 0 assumes the default"
* @param $is_volatile "True if this copy should be treated as volatile, i.e. it can't be optimized away."
**/
macro void move(void* dst, void* src, usz len, usz $dst_align = 0, usz $src_align = 0, bool $is_volatile = false)
{
$$memmove(dst, src, len, $is_volatile, $dst_align, $src_align);
}
/**
* Sets all memory in a region to that of the provided byte.
*
* @param [&out] dst "The destination to copy to"
* @param val "The value to copy into memory"
* @param len "The number of bytes to copy"
* @param $dst_align "the alignment of the destination if different from the default, 0 assumes the default"
* @param $is_volatile "True if this copy should be treated as volatile, i.e. it can't be optimized away."
*
* @ensure !len || (dst[0] == val && dst[len - 1] == val)
**/
macro void set(void* dst, char val, usz len, usz $dst_align = 0, bool $is_volatile = false)
macro void set(void* dst, char val, usize len, usize $dst_align = 0, bool $is_volatile = false)
{
$$memset(dst, val, len, $is_volatile, $dst_align);
}
/**
* 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."
*
* @ensure !$len || (dst[0] == val && dst[$len - 1] == val)
**/
macro void set_inline(void* dst, char val, usz $len, usz $dst_align = 0, bool $is_volatile = false)
macro void clear(void* dst, usize len, usize $dst_align = 0, bool $is_volatile = false)
{
$$memset_inline(dst, val, $len, $is_volatile, $dst_align);
$$memset(dst, (char)0, len, $is_volatile, $dst_align);
}
/**
* @require values::@inner_kind(a) == TypeKind.SLICE || values::@inner_kind(a) == TypeKind.POINTER
* @require values::@inner_kind(b) == TypeKind.SLICE || values::@inner_kind(b) == TypeKind.POINTER
* @require values::@inner_kind(a) != TypeKind.SLICE || len == -1
* @require values::@inner_kind(a) != TypeKind.POINTER || len > -1
* @require values::@assign_to(a, b) && values::@assign_to(b, a)
* @require $typeof(a).kind == TypeKind.SUBARRAY || $typeof(a).kind == TypeKind.POINTER
* @require $typeof(b).kind == TypeKind.SUBARRAY || $typeof(b).kind == TypeKind.POINTER
* @require $typeof(a).kind != TypeKind.SUBARRAY || len == -1
* @require $typeof(a).kind != TypeKind.POINTER || len > -1
* @checked (a = b), (b = a)
**/
macro bool equals(a, b, isz len = -1, usz $align = 0)
macro bool equals(a, b, isize len = -1, usize $align = 0)
{
$if !$align:
$align = $typeof(a[0]).alignof;
$endif
void* x @noinit;
void* y @noinit;
$if values::@inner_kind(a) == TypeKind.SLICE:
$if (!$align):
$align = $alignof($typeof(a[0]));
$endif;
void* x = void;
void* y = void;
$if ($typeof(a).kind == TypeKind.SUBARRAY):
len = a.len;
if (len != b.len) return false;
x = a.ptr;
y = b.ptr;
$else
$else:
x = a;
y = b;
assert(len >= 0, "A zero or positive length must be given when comparing pointers.");
$endif
$endif;
if (!len) return true;
var $Type;
$switch ($align)
$switch ($align):
$case 1:
$Type = char;
var $Type = char;
$case 2:
$Type = ushort;
var $Type = ushort;
$case 4:
$Type = uint;
var $Type = uint;
$case 8:
$default:
$Type = ulong;
$endswitch
var $Type = ulong;
$endswitch;
var $step = $Type.sizeof;
usz end = len / $step;
for (usz i = 0; i < end; i++)
usize end = len / $step;
for (usize i = 0; i < end; i++)
{
if ((($Type*)x)[i] != (($Type*)y)[i]) return false;
}
usz last = len % $align;
for (usz i = len - last; i < len; i++)
usize last = len % $align;
for (usize i = len - last; i < len; i++)
{
if (((char*)x)[i] != ((char*)y)[i]) return false;
}
return true;
}
macro type_alloc_must_be_aligned($Type)
macro @clone(&value) @builtin
{
return $Type.alignof > DEFAULT_MEM_ALIGNMENT;
$typeof(value)* x = malloc($typeof(value));
*x = value;
return x;
}
macro @tclone(&value) @builtin
{
$typeof(value)* x = talloc($typeof(value));
*x = value;
return x;
}
fn void* malloc(usize size) @builtin @inline
{
return thread_allocator.alloc(size)!!;
}
fn void*! malloc_checked(usize size) @builtin @inline
{
return thread_allocator.alloc(size);
}
/**
* @require alignment && math::is_power_of_2(alignment)
*/
fn void*! malloc_aligned(usize size, usize alignment) @builtin @inline
{
return thread_allocator.alloc_aligned(size, alignment);
}
fn char[] alloc_bytes(usize bytes) @inline
{
return ((char*)thread_allocator.alloc(bytes))[:bytes]!!;
}
macro alloc($Type)
{
return ($Type*)thread_allocator.alloc($Type.sizeof)!!;
}
fn void* calloc(usize size) @builtin @inline
{
return thread_allocator.calloc(size)!!;
}
fn void*! calloc_checked(usize size) @builtin @inline
{
return thread_allocator.calloc(size);
}
/**
* @require alignment && math::is_power_of_2(alignment)
*/
fn void*! calloc_aligned(usize size, usize alignment) @builtin @inline
{
return thread_allocator.calloc_aligned(size, alignment);
}
fn void* realloc(void *ptr, usize new_size) @builtin @inline
{
return thread_allocator.realloc(ptr, new_size)!!;
}
fn void*! realloc_checked(void *ptr, usize new_size) @builtin @inline
{
return thread_allocator.realloc(ptr, new_size);
}
/**
* @require alignment && math::is_power_of_2(alignment)
*/
fn void*! realloc_aligned(void *ptr, usize new_size, usize alignment) @builtin @inline
{
return thread_allocator.realloc_aligned(ptr, new_size, alignment);
}
fn void free(void* ptr) @builtin @inline
{
return thread_allocator.free(ptr)!!;
}
fn void free_aligned(void* ptr) @builtin @inline
{
return thread_allocator.free_aligned(ptr)!!;
}
/**
* Run with a specific allocator inside of the macro body.
**/
macro void @scoped(Allocator allocator; @body())
macro void @scoped(Allocator* allocator; @body())
{
Allocator old_allocator = allocator::thread_allocator;
allocator::thread_allocator = allocator;
defer allocator::thread_allocator = old_allocator;
Allocator* old_allocator = thread_allocator;
thread_allocator = allocator;
defer thread_allocator = old_allocator;
@body();
}
macro void @report_heap_allocs_in_scope(;@body())
macro void @tscoped(;@body())
{
TrackingAllocator tracker;
tracker.init(allocator::thread_allocator);
Allocator old_allocator = allocator::thread_allocator;
allocator::thread_allocator = &tracker;
defer
{
allocator::thread_allocator = old_allocator;
tracker.print_report();
tracker.free();
}
Allocator* old_allocator = thread_allocator;
TempAllocator* temp = temp_allocator();
usize mark = temp.mark()!!;
thread_allocator = temp;
defer temp.reset(mark);
defer thread_allocator = old_allocator;
@body();
}
macro void @stack_mem(usz $size; @body(Allocator mem)) @builtin
macro talloc($Type) @builtin
{
char[$size] buffer;
OnStackAllocator allocator;
allocator.init(&buffer, allocator::heap());
defer allocator.free();
@body(&allocator);
return temp_allocator().alloc_aligned($Type.sizeof, $alignof($Type))!!;
}
macro void @stack_pool(usz $size; @body) @builtin
fn void* tmalloc(usize size, usize alignment = allocator::DEFAULT_MEM_ALIGNMENT) @builtin @inline
{
char[$size] buffer;
OnStackAllocator allocator;
allocator.init(&buffer, allocator::heap());
defer allocator.free();
mem::@scoped(&allocator)
{
@body();
};
return temp_allocator().alloc_aligned(size, alignment)!!;
}
struct TempState
fn void* tcalloc(usize size, usize alignment = allocator::DEFAULT_MEM_ALIGNMENT) @builtin @inline
{
TempAllocator* old;
TempAllocator* current;
usz mark;
return temp_allocator().calloc_aligned(size, alignment)!!;
}
/**
* Push the current temp allocator. A push must always be balanced with a pop using the current state.
**/
fn TempState temp_push(TempAllocator* other = null)
fn void* trealloc(void* ptr, usize size, usize alignment = allocator::DEFAULT_MEM_ALIGNMENT) @builtin @inline
{
TempAllocator* current = allocator::temp();
TempAllocator* old = current;
if (other == current)
{
current = allocator::temp_allocator_next();
}
return { old, current, current.used };
return temp_allocator().realloc_aligned(ptr, size, alignment)!!;
}
/**
* Pop the current temp allocator. A pop must always be balanced with a push.
**/
fn void temp_pop(TempState old_state)
macro void @pool(;@body) @builtin
{
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(TempAllocator* #other_temp = null; @body) @builtin
{
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
{
current.reset(mark);
$if $has_arg:
allocator::thread_temp_allocator = original;
$endif;
}
TempAllocator* temp = temp_allocator();
usize mark = temp.used;
defer temp.reset(mark);
@body();
}
private tlocal Allocator* thread_allocator = allocator::LIBC_ALLOCATOR;
private tlocal TempAllocator* thread_temp_allocator = null;
import libc;
module std::core::mem @if(WASM_NOLIBC);
SimpleHeapAllocator wasm_allocator @private;
extern int __heap_base;
fn void initialize_wasm_mem() @init(1) @private
macro TempAllocator* temp_allocator()
{
allocator::wasm_memory.allocate_block(mem::DEFAULT_MEM_ALIGNMENT)!!; // Give us a valid null.
// Check if we need to move the heap.
uptr start = (uptr)&__heap_base;
if (start > mem::DEFAULT_MEM_ALIGNMENT) allocator::wasm_memory.use = start;
wasm_allocator.init(fn (x) => allocator::wasm_memory.allocate_block(x));
temp_base_allocator = &wasm_allocator;
allocator::thread_allocator = &wasm_allocator;
if (!thread_temp_allocator)
{
thread_temp_allocator = allocator::new_temp(env::TEMP_ALLOCATOR_SIZE, allocator::LIBC_ALLOCATOR)!!;
}
return thread_temp_allocator;
}
module std::core::mem;
macro TrackingEnv* get_tracking_env()
macro Allocator* current_allocator()
{
$if env::TRACK_MEMORY:
return &&TrackingEnv { $$FILE, $$FUNC, $$LINE };
$else
return null;
$endif
}
macro @clone(value) @builtin @nodiscard
{
return allocator::clone(allocator::heap(), value);
}
macro @tclone(value) @builtin @nodiscard
{
return temp_new($typeof(value), value);
}
fn void* malloc(usz size) @builtin @inline @nodiscard
{
return allocator::malloc(allocator::heap(), size);
}
fn void* tmalloc(usz size, usz alignment = 0) @builtin @inline @nodiscard
{
if (!size) return null;
return allocator::temp().acquire(size, NO_ZERO, alignment)!!;
}
/**
* @require $vacount < 2 : "Too many arguments."
* @require $or($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($Type, ...) @nodiscard
{
$if $vacount == 0:
return ($Type*)calloc($Type.sizeof);
$else
$Type* val = malloc($Type.sizeof);
*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.
* @require $vacount < 2 : "Too many arguments."
* @require $or($vacount == 0, $assignable($vaexpr(0), $Type)) : "The second argument must be an initializer for the type"
**/
macro new_aligned($Type, ...) @nodiscard
{
$if $vacount == 0:
return ($Type*)calloc_aligned($Type.sizeof, $Type.alignof);
$else
$Type* val = malloc_aligned($Type.sizeof, $Type.alignof);
*val = $vaexpr(0);
return val;
$endif
}
/**
* @require $Type.alignof <= DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'alloc_aligned' instead"
**/
macro alloc($Type) @nodiscard
{
return ($Type*)malloc($Type.sizeof);
}
/**
* Allocate using an aligned allocation. This is necessary for types with a default memory alignment
* exceeding DEFAULT_MEM_ALIGNMENT. IMPORTANT! It must be freed using free_aligned.
**/
macro alloc_aligned($Type) @nodiscard
{
return ($Type*)malloc_aligned($Type.sizeof, $Type.alignof);
}
/**
* @require $vacount < 2 : "Too many arguments."
* @require $or($vacount == 0, $assignable($vaexpr(0), $Type)) : "The second argument must be an initializer for the type"
**/
macro temp_new($Type, ...) @nodiscard
{
$if $vacount == 0:
return ($Type*)tcalloc($Type.sizeof) @inline;
$else
$Type* val = tmalloc($Type.sizeof) @inline;
*val = $vaexpr(0);
return val;
$endif
}
macro temp_alloc($Type) @nodiscard
{
return tmalloc($Type.sizeof);
return thread_allocator;
}
/**
* @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(allocator::heap(), $Type, elements);
}
/**
* Allocate using an aligned allocation. This is necessary for types with a default memory alignment
* exceeding DEFAULT_MEM_ALIGNMENT. IMPORTANT! It must be freed using free_aligned.
**/
macro new_array_aligned($Type, usz elements) @nodiscard
{
return allocator::new_array_aligned(allocator::heap(), $Type, elements);
}
/**
* @require $Type.alignof <= DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'alloc_array_aligned' instead"
**/
macro alloc_array($Type, usz elements) @nodiscard
{
return allocator::alloc_array(allocator::heap(), $Type, elements);
}
/**
* Allocate using an aligned allocation. This is necessary for types with a default memory alignment
* exceeding DEFAULT_MEM_ALIGNMENT. IMPORTANT! It must be freed using free_aligned.
**/
macro alloc_array_aligned($Type, usz elements) @nodiscard
{
return allocator::alloc_array(allocator::heap(), $Type, elements);
}
macro temp_alloc_array($Type, usz elements) @nodiscard
{
return (($Type*)tmalloc($Type.sizeof * elements, $Type.alignof))[:elements];
}
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(allocator::heap(), size);
}
fn void* calloc_aligned(usz size, usz alignment) @builtin @inline @nodiscard
{
return allocator::calloc_aligned(allocator::heap(), size, alignment)!!;
}
fn void* tcalloc(usz size, usz alignment = 0) @builtin @inline @nodiscard
{
if (!size) return null;
return allocator::temp().acquire(size, ZERO, alignment)!!;
}
fn void* realloc(void *ptr, usz new_size) @builtin @inline @nodiscard
{
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(allocator::heap(), ptr, new_size, alignment)!!;
}
fn void free(void* ptr) @builtin @inline
{
return allocator::free(allocator::heap(), ptr);
}
fn void free_aligned(void* ptr) @builtin @inline
{
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 allocator::temp().resize(ptr, size, alignment)!!;
}

View File

@@ -1,347 +1,181 @@
module std::core::mem::allocator;
const DEFAULT_SIZE_PREFIX = usz.sizeof;
const DEFAULT_SIZE_PREFIX_ALIGNMENT = usz.alignof;
const MAX_MEMORY_ALIGNMENT = 0x1000_0000;
const DEFAULT_MEM_ALIGNMENT = $alignof(void*) * 2;
const DEFAULT_SIZE_PREFIX = usize.sizeof;
const DEFAULT_SIZE_PREFIX_ALIGNMENT = $alignof(usize);
struct TrackingEnv
const Allocator* NULL_ALLOCATOR = &_NULL_ALLOCATOR;
const Allocator* LIBC_ALLOCATOR = &_SYSTEM_ALLOCATOR;
define AllocatorFunction = fn void*!(Allocator* allocator, usize new_size, usize alignment, usize offset, void* old_pointer, AllocationKind kind);
struct Allocator
{
String file;
String function;
uint line;
AllocatorFunction function;
}
enum AllocInitType
enum AllocationKind
{
NO_ZERO,
ZERO
ALLOC,
CALLOC,
REALLOC,
FREE,
ALIGNED_ALLOC,
ALIGNED_CALLOC,
ALIGNED_REALLOC,
ALIGNED_FREE,
RESET,
MARK,
}
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 size > 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 ptr != null
* @require new_size > 0
**/
fn void*! resize(void* ptr, usz new_size, usz alignment = 0);
/**
* @require ptr != null
**/
fn void release(void* ptr, bool aligned);
}
def MemoryAllocFn = fn char[]!(usz);
fault AllocationFailure
{
OUT_OF_MEMORY,
CHUNK_TOO_LARGE,
OUT_OF_MEMORY,
UNSUPPORTED_OPERATION,
CHUNK_TOO_LARGE,
}
fn usz alignment_for_allocation(usz alignment) @inline @private
{
return alignment < mem::DEFAULT_MEM_ALIGNMENT ? alignment = mem::DEFAULT_MEM_ALIGNMENT : alignment;
}
macro void* malloc(Allocator allocator, usz size) @nodiscard
{
return malloc_try(allocator, size)!!;
}
macro void*! malloc_try(Allocator allocator, usz size) @nodiscard
fn void*! Allocator.alloc(Allocator* allocator, usize size) @inline
{
if (!size) return null;
$if env::TESTING:
char* data = allocator.acquire(size, NO_ZERO)!;
mem::set(data, 0xAA, size, mem::DEFAULT_MEM_ALIGNMENT);
return data;
$else
return allocator.acquire(size, NO_ZERO);
$endif
}
macro void* calloc(Allocator allocator, usz size) @nodiscard
{
return calloc_try(allocator, size)!!;
}
macro void*! calloc_try(Allocator allocator, usz size) @nodiscard
{
if (!size) return null;
return allocator.acquire(size, ZERO);
}
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
{
if (!new_size)
{
free(allocator, ptr);
return null;
}
if (!ptr) return allocator.acquire(new_size, NO_ZERO);
return allocator.resize(ptr, new_size);
}
macro void free(Allocator allocator, void* ptr)
{
if (!ptr) return;
$if env::TESTING:
((char*)ptr)[0] = 0xBA;
$endif
allocator.release(ptr, false);
}
macro void*! malloc_aligned(Allocator allocator, usz size, usz alignment) @nodiscard
{
if (!size) return null;
$if env::TESTING:
char* data = allocator.acquire(size, NO_ZERO, alignment)!;
mem::set(data, 0xAA, size, mem::DEFAULT_MEM_ALIGNMENT);
return data;
$else
return allocator.acquire(size, NO_ZERO, alignment);
$endif
}
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
{
if (!new_size)
{
free_aligned(allocator, ptr);
return null;
}
if (!ptr)
{
return malloc_aligned(allocator, new_size, alignment);
}
return allocator.resize(ptr, new_size, alignment);
}
macro void free_aligned(Allocator allocator, void* ptr)
{
if (!ptr) return;
$if env::TESTING:
((char*)ptr)[0] = 0xBA;
$endif
allocator.release(ptr, .aligned = true);
return allocator.function(allocator, size, 0, 0, null, ALLOC);
}
/**
* @require $vacount < 2 : "Too many arguments."
* @require $or($vacount == 0, $assignable($vaexpr(0), $Type)) : "The second argument must be an initializer for the type"
**/
macro new(Allocator allocator, $Type, ...) @nodiscard
* @require alignment && math::is_power_of_2(alignment)
*/
fn void*! Allocator.alloc_aligned(Allocator* allocator, usize size, usize alignment, usize offset = 0) @inline
{
$if $vacount == 0:
return ($Type*)calloc(allocator, $Type.sizeof);
$else
$Type* val = malloc(allocator, $Type.sizeof);
*val = $vaexpr(0);
return val;
$endif
return allocator.function(allocator, size, alignment, offset, null, ALIGNED_ALLOC);
}
fn void*! Allocator.realloc(Allocator* allocator, void* old_pointer, usize size) @inline
{
return allocator.function(allocator, size, 0, 0, old_pointer, REALLOC);
}
/**
* @require $vacount < 2 : "Too many arguments."
* @require $or($vacount == 0, $assignable($vaexpr(0), $Type)) : "The second argument must be an initializer for the type"
**/
macro new_try(Allocator allocator, $Type, ...) @nodiscard
* @require alignment && math::is_power_of_2(alignment)
*/
fn void*! Allocator.realloc_aligned(Allocator* allocator, void* old_pointer, usize size, usize alignment, usize offset = 0) @inline
{
$if $vacount == 0:
return ($Type*)calloc_try(allocator, $Type.sizeof);
$else
$Type* val = malloc_try(allocator, $Type.sizeof)!;
*val = $vaexpr(0);
return val;
$endif
return allocator.function(allocator, size, alignment, offset, old_pointer, ALIGNED_REALLOC);
}
macro new_with_padding(Allocator allocator, $Type, usz padding) @nodiscard
fn usize! Allocator.mark(Allocator* allocator) @inline
{
return ($Type*)calloc_try(allocator, $Type.sizeof + padding);
}
macro alloc(Allocator allocator, $Type) @nodiscard
{
return ($Type*)malloc(allocator, $Type.sizeof);
}
macro alloc_try(Allocator allocator, $Type) @nodiscard
{
return ($Type*)malloc_try(allocator, $Type.sizeof);
}
macro alloc_with_padding(Allocator allocator, $Type, usz padding) @nodiscard
{
return ($Type*)malloc_try(allocator, $Type.sizeof + padding);
}
macro new_array(Allocator allocator, $Type, usz elements) @nodiscard
{
return new_array_try(allocator, $Type, elements)!!;
}
macro new_array_try(Allocator allocator, $Type, usz elements) @nodiscard
{
return (($Type*)calloc_try(allocator, $Type.sizeof * elements))[:elements];
}
macro new_array_aligned(Allocator allocator, $Type, usz elements) @nodiscard
{
return ((Type*)calloc_aligned(allocator, $Type.sizeof * elements, $Type.alignof))[:elements]!!;
}
macro alloc_array(Allocator allocator, $Type, usz elements) @nodiscard
{
return alloc_array_try(allocator, $Type, elements)!!;
}
macro alloc_array_aligned(Allocator allocator, $Type, usz elements) @nodiscard
{
return ((Type*)malloc_aligned(allocator, $Type.sizeof * elements, $Type.alignof))[:elements]!!;
}
macro alloc_array_try(Allocator allocator, $Type, usz elements) @nodiscard
{
return (($Type*)malloc_try(allocator, $Type.sizeof * elements))[:elements];
}
macro clone(Allocator allocator, value) @nodiscard
{
return new(allocator, $typeof(value), value);
}
fn any clone_any(Allocator allocator, any value) @nodiscard
{
usz size = value.type.sizeof;
void* data = malloc(allocator, size);
mem::copy(data, value.ptr, size);
return any_make(data, value.type);
return (usize)(uptr)allocator.function(allocator, 0, 0, 0, null, MARK);
}
/**
* @require bytes > 0
* @require alignment > 0
* @require bytes <= isz.max
**/
macro void*! @aligned_alloc(#alloc_fn, usz bytes, usz alignment)
fn void*! Allocator.calloc(Allocator* allocator, usize size) @inline
{
if (alignment < void*.alignof) alignment = void*.alignof;
usz header = AlignedBlock.sizeof + alignment;
usz alignsize = bytes + header;
$if @typekind(#alloc_fn(bytes)) == OPTIONAL:
void* data = #alloc_fn(alignsize)!;
$else
void* data = #alloc_fn(alignsize);
$endif
void* mem = mem::aligned_pointer(data + AlignedBlock.sizeof, alignment);
AlignedBlock* desc = (AlignedBlock*)mem - 1;
assert(mem > data);
*desc = { bytes, data };
return mem;
}
struct AlignedBlock
{
usz len;
void* start;
}
macro void! @aligned_free(#free_fn, void* old_pointer)
{
AlignedBlock* desc = (AlignedBlock*)old_pointer - 1;
$if @typekind(#free_fn(desc.start)) == OPTIONAL:
#free_fn(desc.start)!;
$else
#free_fn(desc.start);
$endif
return allocator.function(allocator, size, 0, 0, null, CALLOC);
}
/**
* @require bytes > 0
* @require alignment > 0
**/
macro void*! @aligned_realloc(#calloc_fn, #free_fn, void* old_pointer, usz bytes, usz alignment)
* @require alignment && math::is_power_of_2(alignment)
*/
fn void*! Allocator.calloc_aligned(Allocator* allocator, usize size, usize alignment, usize offset = 0) @inline
{
AlignedBlock* desc = (AlignedBlock*)old_pointer - 1;
void* data_start = desc.start;
void* new_data = @aligned_alloc(#calloc_fn, bytes, alignment)!;
mem::copy(new_data, old_pointer, desc.len < bytes ? desc.len : bytes, 1, 1);
$if @typekind(#free_fn(data_start)) == OPTIONAL:
#free_fn(data_start)!;
$else
#free_fn(data_start);
$endif
return new_data;
return allocator.function(allocator, size, alignment, offset, null, ALIGNED_CALLOC);
}
// All allocators
tlocal Allocator thread_allocator @private = &allocator::LIBC_ALLOCATOR;
tlocal TempAllocator* thread_temp_allocator @private = null;
tlocal TempAllocator*[2] temp_allocator_pair @private;
Allocator temp_base_allocator @private = &allocator::LIBC_ALLOCATOR;
macro TempAllocator* create_default_sized_temp_allocator(Allocator allocator) @local
fn void! Allocator.free(Allocator* allocator, void* old_pointer) @inline
{
$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
allocator.function(allocator, 0, 0, 0, old_pointer, FREE)?;
}
macro Allocator heap() => thread_allocator;
macro TempAllocator* temp()
fn void! Allocator.free_aligned(Allocator* allocator, void* old_pointer) @inline
{
if (!thread_temp_allocator)
allocator.function(allocator, 0, 0, 0, old_pointer, ALIGNED_FREE)?;
}
fn void Allocator.reset(Allocator* allocator, usize mark = 0)
{
allocator.function(allocator, mark, 0, 0, null, RESET)!!;
}
private fn usize alignment_for_allocation(usize alignment) @inline
{
if (alignment < DEFAULT_MEM_ALIGNMENT)
{
init_default_temp_allocators();
alignment = DEFAULT_MEM_ALIGNMENT;
}
return thread_temp_allocator;
return alignment;
}
fn void init_default_temp_allocators() @private
struct DynamicArenaAllocator
{
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];
inline Allocator allocator;
Allocator* backing_allocator;
DynamicArenaPage* page;
DynamicArenaPage* unused_page;
usize page_size;
}
fn TempAllocator* temp_allocator_next() @private
/**
* @require page_size >= 128
* @require this != null
**/
fn void DynamicArenaAllocator.init(DynamicArenaAllocator* this, usize page_size, Allocator* backing_allocator = mem::current_allocator())
{
if (!thread_temp_allocator)
this.function = &dynamic_arena_allocator_function;
this.page = null;
this.unused_page = null;
this.page_size = page_size;
this.backing_allocator = backing_allocator;
}
/**
* @require this != null
**/
fn void DynamicArenaAllocator.destroy(DynamicArenaAllocator* this)
{
DynamicArenaPage* page = this.page;
while (page)
{
init_default_temp_allocators();
return thread_temp_allocator;
DynamicArenaPage* next_page = page.prev_arena;
this.backing_allocator.free(page)!!;
page = next_page;
}
usz index = thread_temp_allocator == temp_allocator_pair[0] ? 1 : 0;
return thread_temp_allocator = temp_allocator_pair[index];
page = this.unused_page;
while (page)
{
DynamicArenaPage* next_page = page.prev_arena;
this.backing_allocator.free(page)!!;
page = next_page;
}
this.page = null;
this.unused_page = null;
}
struct ArenaAllocator
{
inline Allocator allocator;
char[] data;
usize used;
}
/**
* Initialize a memory arena for use using the provided bytes.
*
* @require this != null
**/
fn void ArenaAllocator.init(ArenaAllocator* this, char[] data)
{
this.function = &arena_allocator_function;
this.data = data;
this.used = 0;
}
/**
* @require this != null
**/
fn void ArenaAllocator.reset(ArenaAllocator* this)
{
this.used = 0;
}

40
lib/std/core/mem_array.c3 Normal file
View File

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

17
lib/std/core/os/linux.c3 Normal file
View File

@@ -0,0 +1,17 @@
module std::core::os::linux;
$if (env::OS_TYPE == OsType.LINUX):
extern fn int* __errno_location();
fn int errno() @inline
{
return *__errno_location();
}
fn void errno_set(int err)
{
*(__errno_location()) = err;
}
$endif;

14
lib/std/core/os/macos.c3 Normal file
View File

@@ -0,0 +1,14 @@
module std::core::os::macos;
$if (env::OS_TYPE == OsType.MACOSX):
extern fn int* __error();
fn int errno() @inline
{
return *__error();
}
fn void errno_set(int err)
{
*(__error()) = err;
}
$endif;

View File

@@ -1,32 +0,0 @@
module std::core::mem::allocator;
const usz WASM_BLOCK_SIZE = 65536;
WasmMemory wasm_memory;
struct WasmMemory
{
usz allocation;
uptr use;
}
fn char[]! WasmMemory.allocate_block(&self, usz bytes)
{
if (!self.allocation)
{
self.allocation = $$wasm_memory_size(0) * WASM_BLOCK_SIZE;
}
isz bytes_required = bytes + self.use - self.allocation;
if (bytes_required <= 0)
{
defer self.use += bytes;
return ((char*)self.use)[:bytes];
}
usz blocks_required = (bytes_required + WASM_BLOCK_SIZE + 1) / WASM_BLOCK_SIZE;
if ($$wasm_memory_grow(0, blocks_required) == -1) return AllocationFailure.OUT_OF_MEMORY?;
self.allocation = $$wasm_memory_size(0) * WASM_BLOCK_SIZE;
defer self.use += bytes;
return ((char*)self.use)[:bytes];
}

View File

@@ -0,0 +1,10 @@
module std::core::os::windows;
$if (env::OS_TYPE == OsType.WIN32):
extern fn int getLastError() @stdcall @extname("GetLastError");
fn int errno() @inline
{
return getLastError();
}
$endif;

View File

@@ -1,258 +0,0 @@
module std::core::cpudetect @if(env::X86 || env::X86_64);
struct CpuId
{
uint eax, ebx, ecx, edx;
}
fn CpuId x86_cpuid(uint eax, uint ecx = 0)
{
int edx;
int ebx;
asm
{
movl $eax, eax;
movl $ecx, ecx;
cpuid;
movl eax, $eax;
movl ebx, $ebx;
movl ecx, $ecx;
movl edx, $edx;
}
return { eax, ebx, ecx, edx };
}
enum X86Feature
{
ADX,
AES,
AMX_BF16,
AMX_COMPLEX,
AMX_FP16,
AMX_INT8,
AMX_TILE,
AVX,
AVX10_1_256,
AVX10_1_512,
AVX2,
AVX5124FMAPS,
AVX5124VNNIW,
AVX512BF16,
AVX512BITALG,
AVX512BW,
AVX512CD,
AVX512DQ,
AVX512ER,
AVX512F,
AVX512FP16,
AVX512IFMA,
AVX512PF,
AVX512VBMI,
AVX512VBMI2,
AVX512VL,
AVX512VNNI,
AVX512VP2INTERSECT,
AVX512VPOPCNTDQ,
AVXIFMA,
AVXNECONVERT,
AVXVNNI,
AVXVNNIINT16,
AVXVNNIINT8,
BMI,
BMI2,
CLDEMOTE,
CLFLUSHOPT,
CLWB,
CLZERO,
CMOV,
CMPCCXADD,
CMPXCHG16B,
CX8,
ENQCMD,
F16C,
FMA,
FMA4,
FSGSBASE,
FXSR,
GFNI,
HRESET,
INVPCID,
KL,
LWP,
LZCNT,
MMX,
MOVBE,
MOVDIR64B,
MOVDIRI,
MWAITX,
PCLMUL,
PCONFIG,
PKU,
POPCNT,
PREFETCHI,
PREFETCHWT1,
PRFCHW,
PTWRITE,
RAOINT,
RDPID,
RDPRU,
RDRND,
RDSEED,
RTM,
SAHF,
SERIALIZE,
SGX,
SHA,
SHA512,
SHSTK,
SM3,
SM4,
SSE,
SSE2,
SSE3,
SSE4_1,
SSE4_2,
SSE4_A,
SSSE3,
TBM,
TSXLDTRK,
UINTR,
USERMSR,
VAES,
VPCLMULQDQ,
WAITPKG,
WBNOINVD,
WIDEKL,
X87,
XOP,
XSAVE,
XSAVEC,
XSAVEOPT,
XSAVES,
}
uint128 x86_features;
fn void add_feature_if_bit(X86Feature feature, uint register, int bit)
{
if (register & 1U << bit) x86_features |= 1u128 << feature.ordinal;
}
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 {};
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);
add_feature_if_bit(AMX_COMPLEX, leaf7s1.edx, 8);
add_feature_if_bit(AMX_FP16, leaf7s1.eax, 21);
add_feature_if_bit(AMX_INT8, leaf7.edx, 25);
add_feature_if_bit(AMX_TILE, leaf7.edx, 24);
add_feature_if_bit(AVX, feat.ecx, 28);
add_feature_if_bit(AVX10_1_256, leaf7s1.edx, 19);
add_feature_if_bit(AVX10_1_512, leaf_24.ebx, 18);
add_feature_if_bit(AVX2, leaf7.ebx, 5);
add_feature_if_bit(AVX5124FMAPS, leaf7.edx, 3);
add_feature_if_bit(AVX5124VNNIW, leaf7.edx, 2);
add_feature_if_bit(AVX512BF16, leaf7s1.eax, 5);
add_feature_if_bit(AVX512BITALG, leaf7.ecx, 12);
add_feature_if_bit(AVX512BW, leaf7.ebx, 30);
add_feature_if_bit(AVX512CD, leaf7.ebx, 28);
add_feature_if_bit(AVX512DQ, leaf7.ebx, 17);
add_feature_if_bit(AVX512ER, leaf7.ebx, 27);
add_feature_if_bit(AVX512F, leaf7.ebx, 16);
add_feature_if_bit(AVX512FP16, leaf7.edx, 23);
add_feature_if_bit(AVX512IFMA, leaf7.ebx, 21);
add_feature_if_bit(AVX512PF, leaf7.ebx, 26);
add_feature_if_bit(AVX512VBMI, leaf7.ecx, 1);
add_feature_if_bit(AVX512VBMI2, leaf7.ecx, 6);
add_feature_if_bit(AVX512VL, leaf7.ebx, 31);
add_feature_if_bit(AVX512VNNI, leaf7.ecx, 11);
add_feature_if_bit(AVX512VP2INTERSECT, leaf7.edx, 8);
add_feature_if_bit(AVX512VPOPCNTDQ, leaf7.ecx, 14);
add_feature_if_bit(AVXIFMA, leaf7s1.eax, 23);
add_feature_if_bit(AVXNECONVERT, leaf7s1.edx, 5);
add_feature_if_bit(AVXVNNI, leaf7s1.eax, 4);
add_feature_if_bit(AVXVNNIINT16, leaf7s1.edx, 10);
add_feature_if_bit(AVXVNNIINT8, leaf7s1.edx, 4);
add_feature_if_bit(BMI, leaf7.ebx, 3);
add_feature_if_bit(BMI2, leaf7.ebx, 8);
add_feature_if_bit(CLDEMOTE, leaf7.ecx, 25);
add_feature_if_bit(CLFLUSHOPT, leaf7.ebx, 23);
add_feature_if_bit(CLWB, leaf7.ebx, 24);
add_feature_if_bit(CLZERO, ext8.ecx, 0);
add_feature_if_bit(CMOV, feat.edx, 15);
add_feature_if_bit(CMPCCXADD, leaf7s1.eax, 7);
add_feature_if_bit(CMPXCHG16B, feat.ecx, 12);
add_feature_if_bit(CX8, feat.edx, 8);
add_feature_if_bit(ENQCMD, leaf7.ecx, 29);
add_feature_if_bit(F16C, feat.ecx, 29);
add_feature_if_bit(FMA, feat.ecx, 12);
add_feature_if_bit(FMA4, ext1.ecx, 16);
add_feature_if_bit(FSGSBASE, leaf7.ebx, 0);
add_feature_if_bit(FXSR, feat.edx, 24);
add_feature_if_bit(GFNI, leaf7.ecx, 8);
add_feature_if_bit(HRESET, leaf7s1.eax, 22);
add_feature_if_bit(INVPCID, leaf7.ebx, 10);
add_feature_if_bit(KL, leaf7.ecx, 23);
add_feature_if_bit(LWP, ext1.ecx, 15);
add_feature_if_bit(LZCNT, ext1.ecx, 5);
add_feature_if_bit(MMX, feat.edx, 23);
add_feature_if_bit(MOVBE, feat.ecx, 22);
add_feature_if_bit(MOVDIR64B, leaf7.ecx, 28);
add_feature_if_bit(MOVDIRI, leaf7.ecx, 27);
add_feature_if_bit(MWAITX, ext1.ecx, 29);
add_feature_if_bit(PCLMUL, feat.ecx, 1);
add_feature_if_bit(PCONFIG, leaf7.edx, 18);
add_feature_if_bit(PKU, leaf7.ecx, 4);
add_feature_if_bit(POPCNT, feat.ecx, 23);
add_feature_if_bit(PREFETCHI, leaf7s1.edx, 14);
add_feature_if_bit(PREFETCHWT1, leaf7.ecx, 0);
add_feature_if_bit(PRFCHW, ext1.ecx, 8);
add_feature_if_bit(PTWRITE, leaf_14.ebx, 4);
add_feature_if_bit(RAOINT, leaf7s1.eax, 3);
add_feature_if_bit(RDPID, leaf7.ecx, 22);
add_feature_if_bit(RDPRU, ext8.ecx, 4);
add_feature_if_bit(RDRND, feat.ecx, 30);
add_feature_if_bit(RDSEED, leaf7.ebx, 18);
add_feature_if_bit(RTM, leaf7.ebx, 11);
add_feature_if_bit(SAHF, ext1.ecx, 0);
add_feature_if_bit(SERIALIZE, leaf7.edx, 14);
add_feature_if_bit(SGX, leaf7.ebx, 2);
add_feature_if_bit(SHA, leaf7.ebx, 29);
add_feature_if_bit(SHA512, leaf7s1.eax, 0);
add_feature_if_bit(SHSTK, leaf7.ecx, 7);
add_feature_if_bit(SM3, leaf7s1.eax, 1);
add_feature_if_bit(SM4, leaf7s1.eax, 2);
add_feature_if_bit(SSE, feat.edx, 25);
add_feature_if_bit(SSE2, feat.edx, 26);
add_feature_if_bit(SSE3, feat.ecx, 0);
add_feature_if_bit(SSE4_1, feat.ecx, 19);
add_feature_if_bit(SSE4_2, feat.ecx, 20);
add_feature_if_bit(SSE4_A, ext1.ecx, 6);
add_feature_if_bit(SSSE3, feat.ecx, 9);
add_feature_if_bit(TBM, ext1.ecx, 21);
add_feature_if_bit(TSXLDTRK, leaf7.edx, 16);
add_feature_if_bit(UINTR, leaf7.edx, 5);
add_feature_if_bit(USERMSR, leaf7s1.edx, 15);
add_feature_if_bit(VAES, leaf7.ecx, 9);
add_feature_if_bit(VPCLMULQDQ, leaf7.ecx, 10);
add_feature_if_bit(WAITPKG, leaf7.ecx, 5);
add_feature_if_bit(WBNOINVD, ext8.ecx, 9);
add_feature_if_bit(WIDEKL, leaf_19.ebx, 2);
add_feature_if_bit(X87, feat.edx, 0);
add_feature_if_bit(XOP, ext1.ecx, 11);
add_feature_if_bit(XSAVE, feat.ecx, 26);
add_feature_if_bit(XSAVEC, leaf_d.eax, 1);
add_feature_if_bit(XSAVEOPT, leaf_d.eax, 0);
add_feature_if_bit(XSAVES, leaf_d.eax, 3);
}

View File

@@ -1,254 +0,0 @@
module std::core::machoruntime @if(env::DARWIN) @private;
struct SegmentCommand64
{
uint cmd;
uint cmdsize;
char[16] segname;
ulong vmaddr;
ulong vmsize;
ulong fileoff;
ulong filesize;
uint maxprot;
uint initprot;
uint nsects;
uint flags;
}
struct LoadCommand
{
uint cmd;
uint cmdsize;
}
struct Section64
{
char[16] sectname;
char[16] segname;
ulong addr;
ulong size;
uint offset;
uint align;
uint reloff;
uint nreloc;
uint flags;
uint reserved1;
uint reserved2;
uint reserved3;
}
struct MachHeader
{
uint magic;
uint cputype;
uint cpusubtype;
uint filetype;
uint ncmds;
uint sizeofcmds;
uint flags;
}
struct MachHeader64
{
inline MachHeader header;
uint reserved;
}
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++)
{
if (a[i] != (*b)[i]) return false;
if (a[i] == '\0') return true;
}
return false;
}
fn SegmentCommand64*! find_segment(MachHeader* header, char* segname)
{
LoadCommand* command = (void*)header + MachHeader64.sizeof;
for (uint i = 0; i < header.ncmds; i++)
{
if (command.cmd == LC_SEGMENT_64)
{
SegmentCommand64* segment = (SegmentCommand64*)command;
if (name_cmp(segname, &segment.segname)) return segment;
}
command = (void*)command + command.cmdsize;
}
return MachoSearch.NOT_FOUND?;
}
fn Section64*! find_section(SegmentCommand64* command, char* sectname)
{
Section64* section = (void*)command + SegmentCommand64.sizeof;
for (uint i = 0; i < command.nsects; i++)
{
if (name_cmp(sectname, &section.sectname)) return section;
section++;
}
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);
if (catch section)
{
return $Type[] {};
}
$Type* ptr = (void*)header + section.offset;
return ptr[:section.size / $Type.sizeof];
}
def DyldCallback = fn void (MachHeader* mh, isz vmaddr_slide);
extern fn void _dyld_register_func_for_add_image(DyldCallback);
struct DlInfo
{
char* dli_fname;
void* dli_fbase;
char* dli_sname;
void* dli_saddr;
}
extern fn void printf(char*, ...);
extern fn int dladdr(MachHeader* mh, DlInfo* dlinfo);
extern fn void* realloc(void* ptr, usz size);
extern fn void* malloc(usz size);
extern fn void free(void* ptr);
def CallbackFn = fn void();
struct Callback
{
uint priority;
CallbackFn xtor;
Callback* next;
}
struct DynamicMethod
{
void* fn_ptr;
char* sel;
union
{
DynamicMethod* next;
TypeId* type;
}
}
enum StartupState
{
NOT_STARTED,
INIT,
RUN_CTORS,
READ_DYLIB,
RUN_DYLIB_CTORS,
RUN_DTORS,
SHUTDOWN
}
StartupState runtime_state = NOT_STARTED;
Callback* ctor_first;
Callback* dtor_first;
fn void runtime_startup() @public @export("__c3_runtime_startup")
{
if (runtime_state != NOT_STARTED) return;
runtime_state = INIT;
_dyld_register_func_for_add_image(&dl_reg_callback);
assert(runtime_state == INIT);
runtime_state = RUN_CTORS;
Callback* ctor = ctor_first;
while (ctor)
{
ctor.xtor();
ctor = ctor.next;
}
assert(runtime_state == RUN_CTORS);
runtime_state = READ_DYLIB;
ctor = null;
}
fn void runtime_finalize() @public @export("__c3_runtime_finalize")
{
if (runtime_state != READ_DYLIB) return;
runtime_state = RUN_DTORS;
Callback* dtor = dtor_first;
while (dtor)
{
dtor.xtor();
dtor = dtor.next;
}
assert(runtime_state == RUN_DTORS);
runtime_state = SHUTDOWN;
}
fn void append_xxlizer(Callback** ref, Callback* cb)
{
while (Callback* current = *ref, current)
{
if (current.priority > cb.priority)
{
cb.next = current;
break;
}
ref = &current.next;
}
*ref = cb;
}
struct TypeId
{
char type;
TypeId* parentof;
DynamicMethod* dtable;
usz sizeof;
TypeId* inner;
usz len;
typeid[*] additional;
}
fn void dl_reg_callback(MachHeader* mh, isz vmaddr_slide)
{
usz size = 0;
assert(runtime_state == INIT || runtime_state == READ_DYLIB, "State was %s", runtime_state);
foreach (&dm : find_segment_section_body(mh, "__DATA", "__c3_dynamic", DynamicMethod))
{
TypeId* type = dm.type;
dm.next = type.dtable;
type.dtable = dm;
DynamicMethod* m = dm;
while (m)
{
m = m.next;
}
}
foreach (&cb : find_segment_section_body(mh, "__DATA", "__c3dtor", Callback))
{
append_xxlizer(&dtor_first, cb);
}
foreach (&cb : find_segment_section_body(mh, "__DATA", "__c3ctor", Callback))
{
append_xxlizer(&ctor_first, cb);
}
if (runtime_state != READ_DYLIB) return;
runtime_state = RUN_DYLIB_CTORS;
Callback* ctor = ctor_first;
ctor_first = null;
while (ctor)
{
ctor.xtor();
ctor = ctor.next;
}
assert(runtime_state == RUN_DYLIB_CTORS);
runtime_state = READ_DYLIB;
}

View File

@@ -1,166 +0,0 @@
module std::core::main_stub;
macro usz _strlen(ptr) @private
{
usz len = 0;
while (ptr[len]) len++;
return len;
}
macro int @main_to_err_main(#m, int, char**)
{
if (catch #m()) return 1;
return 0;
}
macro int @main_to_int_main(#m, int, char**) => #m();
macro int @main_to_void_main(#m, int, char**)
{
#m();
return 0;
}
macro String[] args_to_strings(int argc, char** argv) @private
{
String[] list = mem::alloc_array(String, argc);
for (int i = 0; i < argc; i++)
{
char* arg = argv[i];
usz len = 0;
list[i] = (String)arg[:_strlen(arg)];
}
return list;
}
macro int @main_to_err_main_args(#m, int argc, char** argv)
{
String[] list = args_to_strings(argc, argv);
defer free(list.ptr);
if (catch #m(list)) return 1;
return 0;
}
macro int @main_to_int_main_args(#m, int argc, char** argv)
{
String[] list = args_to_strings(argc, argv);
defer free(list.ptr);
return #m(list);
}
macro int @main_to_void_main_args(#m, int argc, char** argv)
{
String[] list = args_to_strings(argc, argv);
defer free(list.ptr);
#m(list);
return 0;
}
module std::core::main_stub @if(env::WIN32);
extern fn Char16** _win_command_line_to_argv_w(ushort* cmd_line, int* argc_ptr) @extern("CommandLineToArgvW");
macro String[] win_command_line_to_strings(ushort* cmd_line) @private
{
int argc;
Char16** argv = _win_command_line_to_argv_w(cmd_line, &argc);
return wargs_strings(argc, argv);
}
macro String[] wargs_strings(int argc, Char16** argv) @private
{
String[] list = mem::alloc_array(String, argc);
for (int i = 0; i < argc; i++)
{
Char16* arg = argv[i];
Char16[] argstring = arg[:_strlen(arg)];
list[i] = string::new_from_utf16(argstring) ?? "?".copy();
}
return list[:argc];
}
macro void release_wargs(String[] list) @private
{
foreach (s : list) free(s.ptr);
free(list.ptr);
}
macro int @win_to_err_main_noargs(#m, void* handle, Char16* cmd_line, int show_cmd)
{
if (catch #m()) return 1;
return 0;
}
macro int @win_to_int_main_noargs(#m, void* handle, Char16* cmd_line, int show_cmd) => #m();
macro int @win_to_void_main_noargs(#m, void* handle, Char16* cmd_line, int show_cmd)
{
#m();
return 0;
}
macro int @win_to_err_main_args(#m, void* handle, Char16* cmd_line, int show_cmd)
{
String[] args = win_command_line_to_strings(cmd_line);
defer release_wargs(args);
if (catch #m(args)) return 1;
return 0;
}
macro int @win_to_int_main_args(#m, void* handle, Char16* cmd_line, int show_cmd)
{
String[] args = win_command_line_to_strings(cmd_line);
defer release_wargs(args);
return #m(args);
}
macro int @win_to_void_main_args(#m, void* handle, Char16* cmd_line, int show_cmd)
{
String[] args = win_command_line_to_strings(cmd_line);
defer release_wargs(args);
#m(args);
return 0;
}
macro int @win_to_err_main(#m, void* handle, Char16* cmd_line, int show_cmd)
{
String[] args = win_command_line_to_strings(cmd_line);
defer release_wargs(args);
if (catch #m(handle, args, show_cmd)) return 1;
return 0;
}
macro int @win_to_int_main(#m, void* handle, Char16* cmd_line, int show_cmd)
{
String[] args = win_command_line_to_strings(cmd_line);
defer release_wargs(args);
return #m(handle, args, show_cmd);
}
macro int @win_to_void_main(#m, void* handle, Char16* cmd_line, int show_cmd)
{
String[] args = win_command_line_to_strings(cmd_line);
defer release_wargs(args);
#m(handle, args, show_cmd);
return 0;
}
macro int @wmain_to_err_main_args(#m, int argc, Char16** argv)
{
String[] args = wargs_strings(argc, argv);
defer release_wargs(args);
if (catch #m(args)) return 1;
return 1;
}
macro int @wmain_to_int_main_args(#m, int argc, Char16** argv)
{
String[] args = wargs_strings(argc, argv);
defer release_wargs(args);
return #m(args);
}
macro int @wmain_to_void_main_args(#m, int argc, Char16** argv)
{
String[] args = wargs_strings(argc, argv);
defer release_wargs(args);
#m(args);
return 0;
}

View File

@@ -1,250 +0,0 @@
// Copyright (c) 2021 Christoffer Lerno. All rights reserved.
// Use of this source code is governed by the MIT license
// a copy of which can be found in the LICENSE_STDLIB file.
module std::core::runtime;
import libc, std::time, std::io, std::sort;
struct AnyRaw
{
void* ptr;
typeid type;
}
struct SliceRaw
{
void* ptr;
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());
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);
extern fn void __wasm_call_ctors();
fn void wasm_initialize() @extern("_initialize") @wasm
{
// The linker synthesizes this to call constructors.
__wasm_call_ctors();
}

185
lib/std/core/str.c3 Normal file
View File

@@ -0,0 +1,185 @@
module std::core::str;
define ZString = distinct char*;
define Char32 = uint;
define Char16 = ushort;
private const uint SURROGATE_OFFSET = 0x10000;
private const uint SURROGATE_GENERIC_MASK = 0xF800;
private const uint SURROGATE_MASK = 0xFC00;
private const uint SURROGATE_CODEPOINT_MASK = 0x03FF;
private const uint SURROGATE_BITS = 10;
private const uint SURROGATE_LOW_VALUE = 0xDC00;
private const uint SURROGATE_HIGH_VALUE = 0xD800;
fn String join(char[][] s, char[] joiner)
{
if (!s.len) return (String)null;
usize total_size = joiner.len * s.len;
foreach (char[]* &str : s)
{
total_size += str.len;
}
String res = string::new_with_capacity(total_size);
res.append(s[0]);
foreach (char[]* &str : s[1..])
{
res.append(joiner);
res.append(*str);
}
return res;
}
fn usize! str_index_of(char[] s, char[] needle)
{
usize match = 0;
usize needed = needle.len;
if (!needed) return SearchResult.MISSING!;
usize index_start = 0;
char search = needle[0];
foreach (usize i, char c : s)
{
if (c == search)
{
if (!match) index_start = i;
match++;
if (match == needed) return i;
search = needle[match];
continue;
}
if (match)
{
match = 0;
search = needle[0];
}
}
return SearchResult.MISSING!;
}
fn ZString copy_zstring(char[] s)
{
usize len = s.len;
char* str = malloc(len + 1);
mem::copy(str, s.ptr, len);
str[len] = 0;
return (ZString)str;
}
fn ZString tcopy_zstring(char[] s)
{
usize len = s.len;
char* str = tmalloc(len + 1);
mem::copy(str, s.ptr, len);
str[len] = 0;
return (ZString)str;
}
fn bool compare(char[] a, char[] b)
{
if (a.len != b.len) return false;
foreach (i, c : a)
{
if (c != b[i]) return false;
}
return true;
}
fault UnicodeResult
{
INVALID_UTF8,
INVALID_UTF16,
CONVERSION_FAILED,
}
fn usize utf8_codepoints(char[] utf8)
{
usize len = 0;
foreach (char c : utf8)
{
if (c & 0xC0 != 0x80) len++;
}
return len;
}
fn Char32[]! utf8to32(char[] utf8, Allocator* allocator = mem::current_allocator)
{
usize codepoints = conv::utf8_codepoints(utf8);
Char32* data = allocator.alloc(Char32.sizeof * (codepoints + 1))?;
conv::utf8to32_unsafe(utf8, data)?;
data[codepoints] = 0;
return data[0..codepoints - 1];
}
fn char[] utf32to8(Char32[] utf32, Allocator* allocator = mem::current_allocator)
{
usize len = conv::utf8len_for_utf32(utf32);
char* data = allocator.alloc(len + 1)!!;
conv::utf32to8_unsafe(utf32, data);
data[len] = 0;
return data[0..len - 1];
}
fn Char16[]! utf8to16(char[] utf8, Allocator* allocator = mem::current_allocator)
{
usize len16 = conv::utf16len_for_utf8(utf8);
Char16* data = allocator.alloc((len16 + 1) * Char16.sizeof)?;
conv::utf8to16_unsafe(utf8, data)?;
data[len16] = 0;
return data[0..len16 - 1];
}
fn char[]! utf16to8(Char16[] utf16, Allocator* allocator = mem::current_allocator())
{
usize len = conv::utf8len_for_utf16(utf16);
char* data = allocator.alloc(len + 1)?;
conv::utf16to8_unsafe(utf16, data)?;
return data[0 .. len - 1];
}
fn char[] copy(char[] s)
{
usize len = s.len;
ZString str_copy = copy_zstring(s) @inline;
return str_copy[..len];
}
fn char[] tcopy(char[] s)
{
usize len = s.len;
ZString str_copy = tcopy_zstring(s) @inline;
return str_copy[..len];
}
fn char[] tconcat(char[] s1, char[] s2)
{
usize full_len = s1.len + s2.len;
char* str = tmalloc(full_len + 1);
usize s1_len = s1.len;
mem::copy(str, s1.ptr, s1_len);
mem::copy(str + s1_len, s2.ptr, s2.len);
str[full_len] = 0;
return str[..full_len];
}
fn char[] concat(char[] s1, char[] s2)
{
usize full_len = s1.len + s2.len;
char* str = malloc(full_len + 1);
usize s1_len = s1.len;
mem::copy(str, s1.ptr, s1_len);
mem::copy(str + s1_len, s2.ptr, s2.len);
str[full_len] = 0;
return str[..full_len];
}
fn usize ZString.len(ZString *str)
{
usize len = 0;
char* ptr = (char*)*str;
while (char c = ptr++[0])
{
if (c & 0xC0 != 0x80) len++;
}
return len;
}

View File

@@ -1,739 +1,299 @@
module std::core::string;
import std::ascii;
import libc;
distinct ZString = inline char*;
distinct WString = inline Char16*;
def Char32 = uint;
def Char16 = ushort;
define String = distinct void*;
fault UnicodeResult
private struct StringData
{
INVALID_UTF8,
INVALID_UTF16,
CONVERSION_FAILED,
Allocator* allocator;
usize len;
usize capacity;
char[*] chars;
}
const uint SURROGATE_OFFSET @private = 0x10000;
const uint SURROGATE_GENERIC_MASK @private = 0xF800;
const uint SURROGATE_MASK @private = 0xFC00;
const uint SURROGATE_CODEPOINT_MASK @private = 0x03FF;
const uint SURROGATE_BITS @private = 10;
const uint SURROGATE_LOW_VALUE @private = 0xDC00;
const uint SURROGATE_HIGH_VALUE @private = 0xD800;
const usize MIN_CAPACITY = 16;
fault NumberConversion
fn String new_with_capacity(usize capacity, Allocator* allocator = mem::current_allocator())
{
EMPTY_STRING,
NEGATIVE_VALUE,
MALFORMED_INTEGER,
INTEGER_OVERFLOW,
MALFORMED_FLOAT,
FLOAT_OUT_OF_RANGE,
if (capacity < MIN_CAPACITY) capacity = MIN_CAPACITY;
StringData* data = allocator.alloc(StringData.sizeof + capacity)!!;
data.allocator = allocator;
data.len = 0;
data.capacity = capacity;
return (String)data;
}
/**
* Return a temporary String created using the formatting function.
*
* @param [in] fmt `The formatting string`
**/
macro String tformat(String fmt, ...)
fn String new(char[] c)
{
DString str = dstring::temp_with_capacity(fmt.len + $vacount * 8);
str.appendf(fmt, $vasplat());
return str.str_view();
}
/**
* Return a temporary ZString created using the formatting function.
*
* @param [in] fmt `The formatting string`
**/
macro ZString tformat_zstr(String fmt, ...)
{
DString str = dstring::temp_with_capacity(fmt.len + $vacount * 8);
str.appendf(fmt, $vasplat());
return str.zstr_view();
}
/**
* Return a new String created using the formatting function.
*
* @param [in] fmt `The formatting string`
* @param [inout] allocator `The allocator to use`
**/
macro String new_format(String fmt, ..., Allocator allocator = allocator::heap())
{
@pool(allocator)
usize len = c.len;
String str = new_with_capacity(len);
StringData* data = str.data();
if (len)
{
DString str = dstring::temp_with_capacity(fmt.len + $vacount * 8);
str.appendf(fmt, $vasplat());
return str.copy_str(allocator);
};
}
/**
* Return a new ZString created using the formatting function.
*
* @param [in] fmt `The formatting string`
* @param [inout] allocator `The allocator to use`
**/
macro ZString new_format_zstr(String fmt, ..., Allocator allocator = allocator::heap())
{
@pool(allocator)
{
DString str = dstring::temp_with_capacity(fmt.len + $vacount * 8);
str.appendf(fmt, $vasplat());
return str.copy_zstr(allocator);
};
}
/**
* Check if a character is in a set.
*
* @param c `the character to check`
* @param [in] set `The formatting string`
* @pure
* @return `True if a character is in the set`
**/
macro bool char_in_set(char c, String set)
{
foreach (ch : set) if (ch == c) return true;
return false;
}
fn String join_new(String[] s, String joiner, Allocator allocator = allocator::heap())
{
if (!s)
{
return (String)allocator::new_array(allocator, char, 2)[:0];
data.len = len;
mem::copy(&data.chars, c.ptr, len);
}
return (String)data;
}
usz total_size = joiner.len * s.len;
foreach (String* &str : s)
fn ZString String.zstr(String str)
{
StringData* data = str.data();
if (!data) return (ZString)"";
if (data.capacity == data.len)
{
total_size += str.len;
str.reserve(1);
data.chars[data.len] = 0;
}
@pool(allocator)
else if (data.chars[data.len] != 0)
{
DString res = dstring::temp_with_capacity(total_size);
res.append(s[0]);
foreach (String* &str : s[1..])
{
res.append(joiner);
res.append(*str);
}
return res.copy_str(allocator);
};
}
/**
* Remove characters from the front and end of a string.
*
* @param [in] string `The string to trim`
* @param [in] to_trim `The set of characters to trim, defaults to whitespace`
* @pure
* @return `a substring of the string passed in`
**/
fn String String.trim(string, String to_trim = "\t\n\r ")
{
usz start = 0;
usz len = string.len;
while (start < len && char_in_set(string[start], to_trim)) start++;
if (start == len) return string[:0];
usz end = len - 1;
while (end > start && char_in_set(string[end], to_trim)) end--;
return string[start..end];
}
/**
* Check if the String starts with the needle.
*
* @param [in] string
* @param [in] needle
* @pure
* @return `'true' if the string starts with the needle`
**/
fn bool String.starts_with(string, String needle)
{
if (needle.len > string.len) return false;
if (!needle.len) return true;
return string[:needle.len] == needle;
}
/**
* Check if the String ends with the needle.
*
* @param [in] string
* @param [in] needle
* @pure
* @return `'true' if the string ends with the needle`
**/
fn bool String.ends_with(string, String needle)
{
if (needle.len > string.len) return false;
if (!needle.len) return true;
return string[^needle.len..] == needle;
}
/**
* Strip the front of the string if the prefix exists.
*
* @param [in] string
* @param [in] needle
* @pure
* @return `the substring with the prefix removed`
**/
fn String String.strip(string, String needle)
{
if (!needle.len || !string.starts_with(needle)) return string;
return string[needle.len..];
}
/**
* Strip the end of the string if the suffix exists.
*
* @param [in] string
* @param [in] needle
* @pure
* @return `the substring with the suffix removed`
**/
fn String String.strip_end(string, String needle)
{
if (!needle.len || !string.ends_with(needle)) return string;
// Note that this is the safe way if we want to support zero length.
return string[:(string.len - needle.len)];
}
/**
* Split a string into parts, e.g "a|b|c" split with "|" yields { "a", "b", "c" }
*
* @param [in] s
* @param [in] needle
* @param [&inout] allocator "The allocator, defaults to the heap allocator"
* @param max "Max number of elements, 0 means no limit, defaults to 0"
* @require needle.len > 0 "The needle must be at least 1 character long"
* @ensure return.len > 0
**/
fn String[] String.split(s, String needle, usz max = 0, Allocator allocator = allocator::heap())
{
usz capacity = 16;
usz i = 0;
String* holder = allocator::alloc_array(allocator, String, capacity);
bool no_more = false;
while (!no_more)
{
usz! index = i == max - 1 ? SearchResult.MISSING? : s.index_of(needle);
String res @noinit;
if (try index)
{
res = s[:index];
s = s[index + needle.len..];
}
else
{
res = s;
no_more = true;
}
if (i == capacity)
{
capacity *= 2;
holder = allocator::realloc(allocator, holder, String.sizeof * capacity);
}
holder[i++] = res;
data.chars[data.len] = 0;
}
return holder[:i];
return (ZString)&data.chars[0];
}
fn usize String.len(String this)
{
if (!this) return 0;
return this.data().len;
}
/**
* This function is identical to String.split, but implicitly uses the
* temporary allocator.
*
* @param [in] s
* @param [in] needle
* @param max "Max number of elements, 0 means no limit, defaults to 0"
**/
fn String[] String.tsplit(s, String needle, usz max = 0)
* @require new_size <= this.len()
*/
fn void String.chop(String this, usize new_size)
{
return s.split(needle, max, allocator::temp()) @inline;
if (!this) return;
this.data().len = new_size;
}
/**
* Check if a substring is found in the string.
* @param [in] s
* @param [in] needle "The string to look for."
* @pure
* @return "true if the string contains the substring, false otherwise"
**/
fn bool String.contains(s, String needle)
fn char[] String.str(String str)
{
return @ok(s.index_of(needle));
StringData* data = (StringData*)str;
if (!data) return char[] {};
return data.chars[:data.len];
}
/**
* Find the index of the first incidence of a string.
*
* @param [in] s
* @param needle "The character to look for"
* @pure
* @ensure return < s.len
* @return "the index of the needle"
* @return! SearchResult.MISSING "if the needle cannot be found"
**/
fn usz! String.index_of_char(s, char needle)
fn void String.append_utf32(String* str, Char32[] chars)
{
foreach (i, c : s)
str.reserve(chars.len);
foreach (Char32 c : chars)
{
if (c == needle) return i;
str.append_char32(c);
}
return SearchResult.MISSING?;
}
/**
* Find the index of the first incidence of a character.
*
* @param [in] s
* @param needle "The character to look for"
* @param start_index "The index to start with, may exceed max index."
* @pure
* @ensure return < s.len
* @return "the index of the needle"
* @return! SearchResult.MISSING "if the needle cannot be found starting from the start_index"
* @require index < str.len()
**/
fn usz! String.index_of_char_from(s, char needle, usz start_index)
fn void String.set(String str, usize index, char c)
{
usz len = s.len;
if (len <= start_index) return SearchResult.MISSING?;
for (usz i = start_index; i < len; i++)
str.data().chars[index] = c;
}
fn void String.append_repeat(String* str, char c, usize times)
{
if (times == 0) return;
str.reserve(times);
StringData* data = str.data();
for (usize i = 0; i < times; i++)
{
if (s[i] == needle) return i;
data.chars[data.len++] = c;
}
return SearchResult.MISSING?;
}
/**
* Find the index of the first incidence of a character starting from the end.
*
* @param [in] s
* @param needle "the character to find"
* @pure
* @ensure return < s.len
* @return "the index of the needle"
* @return! SearchResult.MISSING "if the needle cannot be found"
**/
fn usz! String.rindex_of_char(s, char needle)
* @require c < 0x10ffff
*/
fn void String.append_char32(String* str, Char32 c)
{
foreach_r (i, c : s)
if (c < 0x7f)
{
if (c == needle) return i;
}
return SearchResult.MISSING?;
str.reserve(1);
StringData* data = str.data();
data.chars[data.len++] = (char)c;
return;
}
if (c < 0x7ff)
{
str.reserve(2);
StringData* data = str.data();
data.chars[data.len++] = (char)(0xC0 | c >> 6);
data.chars[data.len++] = (char)(0x80 | (c & 0x3F));
return;
}
if (c < 0xffff)
{
str.reserve(3);
StringData* data = str.data();
data.chars[data.len++] = (char)(0xE0 | c >> 12);
data.chars[data.len++] = (char)(0x80 | (c >> 6 & 0x3F));
data.chars[data.len++] = (char)(0x80 | (c & 0x3F));
return;
}
str.reserve(4);
StringData* data = str.data();
data.chars[data.len++] = (char)(0xF0 | c >> 18);
data.chars[data.len++] = (char)(0x80 | (c >> 12 & 0x3F));
data.chars[data.len++] = (char)(0x80 | (c >> 6 & 0x3F));
data.chars[data.len++] = (char)(0x80 | (c & 0x3F));
}
/**
* Find the index of the first incidence of a string.
*
* @param [in] s
* @param [in] needle
* @pure
* @ensure return < s.len
* @require needle.len > 0 : "The needle must be len 1 or more"
* @return "the index of the needle"
* @return! SearchResult.MISSING "if the needle cannot be found"
**/
fn usz! String.index_of(s, String needle)
fn String String.copy(String* str, Allocator* allocator = null)
{
usz needed = needle.len;
if (needed > 0 && s.len >= needed)
if (!str)
{
char first = needle[0];
foreach (i, c: s[..^needed])
{
if (c == first && s[i:needed] == needle) return i;
}
if (allocator) return new_with_capacity(0, allocator);
return (String)null;
}
return SearchResult.MISSING?;
if (!allocator) allocator = mem::current_allocator();
StringData* data = str.data();
String new_string = new_with_capacity(data.capacity, allocator);
mem::copy((char*)new_string.data(), (char*)data, StringData.sizeof + data.len);
return new_string;
}
/**
* Find the index of the last incidence of a string.
*
* @param [in] s
* @param [in] needle
* @pure
* @ensure return < s.len
* @require needle.len > 0 "The needle must be len 1 or more"
* @return "the index of the needle"
* @return! SearchResult.MISSING "if the needle cannot be found"
**/
fn usz! String.rindex_of(s, String needle)
fn ZString String.copy_zstr(String* str, Allocator* allocator = mem::current_allocator())
{
usz needed = needle.len;
if (needed > 0 && s.len >= needed)
usize str_len = str.len();
if (!str_len)
{
char first = needle[0];
foreach_r (i, c: s[..^needed])
{
if (c == first && s[i:needed] == needle) return i;
}
return (ZString)allocator.calloc(1)!!;
}
return SearchResult.MISSING?;
char* zstr = allocator.alloc(str_len + 1)!!;
StringData* data = str.data();
mem::copy(zstr, &data.chars, str_len);
zstr[str_len] = 0;
return (ZString)zstr;
}
fn String ZString.str_view(str)
fn char[] String.copy_str(String* str, Allocator* allocator = mem::current_allocator())
{
return (String)(str[:str.len()]);
return str.copy_zstr(allocator)[:str.len()];
}
fn usz ZString.char_len(str)
fn bool String.equals(String str, String other_string)
{
usz len = 0;
char* ptr = (char*)str;
while (char c = ptr++[0])
StringData *str1 = str.data();
StringData *str2 = other_string.data();
if (str1 == str2) return true;
if (!str1) return str2.len == 0;
if (!str2) return str1.len == 0;
usize str1_len = str1.len;
if (str1_len != str2.len) return false;
for (int i = 0; i < str1_len; i++)
{
if (c & 0xC0 != 0x80) len++;
if (str1.chars[i] != str2.chars[i]) return false;
}
return len;
return true;
}
fn usz ZString.len(str)
fn void String.destroy(String* str)
{
usz len = 0;
char* ptr = (char*)str;
while (char c = ptr++[0]) len++;
return len;
if (!*str) return;
StringData* data = str.data();
if (!data) return;
data.allocator.free(data)!!;
*str = (String)null;
}
fn ZString String.zstr_copy(s, Allocator allocator = allocator::heap())
fn bool String.less(String str, String other_string)
{
usz len = s.len;
char* str = allocator::malloc(allocator, len + 1);
mem::copy(str, s.ptr, len);
str[len] = 0;
return (ZString)str;
}
fn String String.concat(s1, String s2, Allocator allocator = allocator::heap())
{
usz full_len = s1.len + s2.len;
char* str = allocator::malloc(allocator, full_len + 1);
usz s1_len = s1.len;
mem::copy(str, s1.ptr, s1_len);
mem::copy(str + s1_len, s2.ptr, s2.len);
str[full_len] = 0;
return (String)str[:full_len];
}
fn String String.tconcat(s1, String s2) => s1.concat(s2, allocator::temp());
fn ZString String.zstr_tcopy(s) => s.zstr_copy(allocator::temp()) @inline;
fn String String.copy(s, Allocator allocator = allocator::heap())
{
usz len = s.len;
char* str = allocator::malloc(allocator, len + 1);
mem::copy(str, s.ptr, len);
str[len] = 0;
return (String)str[:len];
}
fn void String.free(&s, Allocator allocator = allocator::heap())
{
if (!s.len) return;
allocator::free(allocator, s.ptr);
*s = "";
}
fn String String.tcopy(s) => s.copy(allocator::temp()) @inline;
fn String ZString.copy(z, Allocator allocator = allocator::temp())
{
return z.str_view().copy(allocator) @inline;
}
fn String ZString.tcopy(z)
{
return z.str_view().copy(allocator::temp()) @inline;
}
/**
* Convert an UTF-8 string to UTF-16
* @return "The UTF-16 string as a slice, allocated using the given allocator"
* @return! UnicodeResult.INVALID_UTF8 "If the string contained an invalid UTF-8 sequence"
* @return! AllocationFailure "If allocation of the string fails"
**/
fn Char16[]! String.to_new_utf16(s, Allocator allocator = allocator::heap())
{
usz len16 = conv::utf16len_for_utf8(s);
Char16* data = allocator::alloc_array_try(allocator, Char16, len16 + 1)!;
conv::utf8to16_unsafe(s, data)!;
data[len16] = 0;
return data[:len16];
}
/**
* Convert an UTF-8 string to UTF-16
* @return "The UTF-16 string as a slice, allocated using the given allocator"
* @return! UnicodeResult.INVALID_UTF8 "If the string contained an invalid UTF-8 sequence"
* @return! AllocationFailure "If allocation of the string fails"
**/
fn Char16[]! String.to_temp_utf16(s)
{
return s.to_new_utf16(allocator::temp());
}
fn WString! String.to_new_wstring(s, Allocator allocator = allocator::heap())
{
return (WString)s.to_new_utf16(allocator).ptr;
}
fn WString! String.to_temp_wstring(s)
{
return (WString)s.to_temp_utf16().ptr;
}
fn Char32[]! String.to_new_utf32(s, Allocator allocator = allocator::heap())
{
usz codepoints = conv::utf8_codepoints(s);
Char32* data = allocator::alloc_array_try(allocator, Char32, codepoints + 1)!;
conv::utf8to32_unsafe(s, data)!;
data[codepoints] = 0;
return data[:codepoints];
}
fn Char32[]! String.to_temp_utf32(s)
{
return s.to_new_utf32(allocator::temp());
}
/**
* Convert a string to ASCII lower case.
*
* @param [inout] s
* @pure
**/
fn void String.convert_ascii_to_lower(s)
{
foreach (&c : s) if (c.is_upper() @pure) *c += 'a' - 'A';
}
fn String String.new_ascii_to_lower(s, Allocator allocator = allocator::heap())
{
String copy = s.copy(allocator);
copy.convert_ascii_to_lower();
return copy;
}
fn String String.temp_ascii_to_lower(s, Allocator allocator = allocator::heap())
{
return s.new_ascii_to_lower(allocator::temp());
}
/**
* Convert a string to ASCII upper case.
*
* @param [inout] s
* @pure
**/
fn void String.convert_ascii_to_upper(s)
{
foreach (&c : s) if (c.is_lower() @pure) *c -= 'a' - 'A';
}
/**
* Returns a string converted to ASCII upper case.
*
* @param [in] s
* @param [inout] allocator
*
* @return `a new String converted to ASCII upper case.`
**/
fn String String.new_ascii_to_upper(s, Allocator allocator = allocator::heap())
{
String copy = s.copy(allocator);
copy.convert_ascii_to_upper();
return copy;
}
fn StringIterator String.iterator(s)
{
return { s, 0 };
}
/**
* @param [in] s
* @return `a temporary String converted to ASCII upper case.`
**/
fn String String.temp_ascii_to_upper(s)
{
return s.new_ascii_to_upper(allocator::temp());
}
fn String! new_from_utf32(Char32[] utf32, Allocator allocator = allocator::heap())
{
usz len = conv::utf8len_for_utf32(utf32);
char* data = allocator::malloc_try(allocator, len + 1)!;
defer catch allocator::free(allocator, data);
conv::utf32to8_unsafe(utf32, data);
data[len] = 0;
return (String)data[:len];
}
fn String! new_from_utf16(Char16[] utf16, Allocator allocator = allocator::heap())
{
usz len = conv::utf8len_for_utf16(utf16);
char* data = allocator::malloc_try(allocator, len + 1)!;
defer catch allocator::free(allocator, data);
conv::utf16to8_unsafe(utf16, data)!;
data[len] = 0;
return (String)data[:len];
}
fn String! new_from_wstring(WString wstring, Allocator allocator = allocator::heap())
{
usz utf16_len;
while (wstring[utf16_len] != 0) utf16_len++;
Char16[] utf16 = wstring[:utf16_len];
return new_from_utf16(utf16, allocator);
}
fn String! temp_from_wstring(WString wstring) => new_from_wstring(wstring, allocator::temp()) @inline;
fn String! temp_from_utf16(Char16[] utf16) => new_from_utf16(utf16, allocator::temp()) @inline;
fn usz String.utf8_codepoints(s)
{
usz len = 0;
foreach (char c : s)
StringData* str1 = str.data();
StringData* str2 = other_string.data();
if (str1 == str2) return false;
if (!str1) return str2.len != 0;
if (!str2) return str1.len == 0;
usize str1_len = str1.len;
usize str2_len = str2.len;
if (str1_len != str2_len) return str1_len < str2_len;
for (int i = 0; i < str1_len; i++)
{
if (c & 0xC0 != 0x80) len++;
if (str1.chars[i] >= str2.chars[i]) return false;
}
return len;
return true;
}
/**
* @require (base <= 10 && base > 1) || base == 16 : "Unsupported base"
**/
macro String.to_integer(string, $Type, int base = 10)
fn void String.append_chars(String* this, char[] str)
{
usz len = string.len;
usz index = 0;
char* ptr = string.ptr;
while (index < len && ascii::is_blank_m(ptr[index])) index++;
if (len == index) return NumberConversion.EMPTY_STRING?;
bool is_negative;
switch (string[index])
usize other_len = str.len;
if (!other_len) return;
if (!*this)
{
case '-':
if ($Type.min == 0) return NumberConversion.NEGATIVE_VALUE?;
is_negative = true;
index++;
case '+':
index++;
default:
break;
*this = new(str);
return;
}
if (len == index) return NumberConversion.MALFORMED_INTEGER?;
$Type base_used = ($Type)base;
if (string[index] == '0' && base == 10)
this.reserve(other_len);
StringData* data = (StringData*)*this;
mem::copy(&data.chars[data.len], str.ptr, other_len);
data.len += other_len;
}
fn Char32[] String.copy_utf32(String* this, Allocator* allocator = mem::current_allocator())
{
return str::utf8to32(this.str(), allocator) @inline!!;
}
fn void String.append_string(String* this, String str)
{
StringData* other = (StringData*)str;
if (!other) return;
this.append(str.str());
}
fn void String.append_char(String* str, char c)
{
if (!*str)
{
index++;
if (index == len) return ($Type)0;
switch (string[index])
{
case 'x':
case 'X':
base_used = 16;
index++;
case 'b':
case 'B':
base_used = 2;
index++;
case 'o':
case 'O':
base_used = 8;
index++;
default:
break;
}
if (len == index) return NumberConversion.MALFORMED_INTEGER?;
*str = new_with_capacity(MIN_CAPACITY);
}
$Type value = 0;
while (index != len)
str.reserve(1);
StringData* data = (StringData*)*str;
data.chars[data.len++] = c;
}
macro void String.append(String* str, value)
{
var $Type = $typeof(value);
$switch ($Type):
$case char:
$case ichar:
str.append_char(value);
$case String:
str.append_string(value);
$case char[]:
str.append_chars(value);
$case Char32:
str.append_char32(value);
$default:
$if ($convertible($Type, Char32)):
str.append_char32(value);
$elif ($convertible($Type, char[])):
str.append_chars(value);
$else:
$assert("Unsupported type for appending");
$endif;
$endswitch;
}
private fn StringData* String.data(String str) @inline
{
return (StringData*)str;
}
private fn void String.reserve(String* str, usize addition)
{
StringData* data = str.data();
if (!data)
{
char c = {|
char ch = string[index++];
if (base_used != 16 || ch < 'A') return (char)(ch - '0');
if (ch <= 'F') return (char)(ch - 'A' + 10);
if (ch < 'a') return NumberConversion.MALFORMED_INTEGER?;
if (ch > 'f') return NumberConversion.MALFORMED_INTEGER?;
return (char)(ch - 'a' + 10);
|}!;
if (c >= base_used) return NumberConversion.MALFORMED_INTEGER?;
value = {|
if (is_negative)
{
$Type new_value = value * base_used - c;
if (new_value > value) return NumberConversion.INTEGER_OVERFLOW?;
return new_value;
}
$Type new_value = value * base_used + c;
if (new_value < value) return NumberConversion.INTEGER_OVERFLOW?;
return new_value;
|}!;
*str = string::new_with_capacity(addition);
return;
}
return value;
usize len = data.len + addition;
if (data.capacity >= len) return;
usize new_capacity = data.capacity * 2;
if (new_capacity < MIN_CAPACITY) new_capacity = MIN_CAPACITY;
*str = (String)data.allocator.realloc(data, StringData.sizeof + new_capacity)!!;
}
fn int128! String.to_int128(s, int base = 10) => s.to_integer(int128, base);
fn long! String.to_long(s, int base = 10) => s.to_integer(long, base);
fn int! String.to_int(s, int base = 10) => s.to_integer(int, base);
fn short! String.to_short(s, int base = 10) => s.to_integer(short, base);
fn ichar! String.to_ichar(s, int base = 10) => s.to_integer(ichar, base);
fn uint128! String.to_uint128(s, int base = 10) => s.to_integer(uint128, base);
fn ulong! String.to_ulong(s, int base = 10) => s.to_integer(ulong, base);
fn uint! String.to_uint(s, int base = 10) => s.to_integer(uint, base);
fn ushort! String.to_ushort(s, int base = 10) => s.to_integer(ushort, base);
fn char! String.to_uchar(s, int base = 10) => s.to_integer(char, base);
fn double! String.to_double(s) => s.to_real(double);
fn float! String.to_float(s) => s.to_real(float);
fn Splitter String.splitter(self, String split)
{
return Splitter { self, split, 0 };
}
struct Splitter
{
String string;
String split;
usz current;
}
fn void Splitter.reset(&self)
{
self.current = 0;
}
fn String! Splitter.next(&self)
{
usz len = self.string.len;
usz current = self.current;
if (current >= len) return IteratorResult.NO_MORE_ELEMENT?;
String remaining = self.string[current..];
usz! next = remaining.index_of(self.split);
if (try next)
{
defer self.current = current + next + self.split.len;
return remaining[:next];
}
self.current = len;
return remaining;
}

View File

@@ -1,23 +1,25 @@
module std::core::string::iterator;
struct StringIterator
{
String utf8;
usz current;
char[] utf8;
usize current;
}
fn void StringIterator.reset(&self)
fn void StringIterator.reset(StringIterator* this)
{
self.current = 0;
this.current = 0;
}
fn Char32! StringIterator.next(&self)
fn Char32! StringIterator.next(StringIterator* this)
{
usz len = self.utf8.len;
usz current = self.current;
if (current >= len) return IteratorResult.NO_MORE_ELEMENT?;
usz read = (len - current < 4 ? len - current : 4);
Char32 res = conv::utf8_to_char32(&self.utf8[current], &read)!;
self.current += read;
usize len = this.utf8.len;
usize current = this.current;
if (current >= len) return IteratorResult.NO_MORE_ELEMENT!;
usize read = (len - current < 4 ? len - current : 4);
Char32 res = conv::utf8_to_char32(&this.utf8[current], &read)?;
this.current += read;
return res;
}
}

View File

@@ -1,487 +0,0 @@
module std::core::string;
import std::math;
// Float parsing based on code in Musl floatscan.c by Rich Felker.
// Musl uses the MIT license, copied below:
// ----------------------------------------------------------------------
// Copyright © 2005-2014 Rich Felker, et al.
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ----------------------------------------------------------------------
const KMAX = 128;
const MASK = KMAX - 1;
const B1B_DIG = 2;
const uint[2] B1B_MAX = { 9007199, 254740991 };
/**
* @require chars.len > 0
**/
macro double! decfloat(char[] chars, int $bits, int $emin, int sign)
{
uint[KMAX] x;
const uint[2] TH = B1B_MAX;
int emax = - $emin - $bits + 3;
const int[*] P10S = { 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000 };
usz index;
bool got_digit = chars[0] == '0';
bool got_rad;
long lrp, dc;
int k, j, lnz;
usz len = chars.len;
usz last_char = len - 1;
assert(len);
char c @noinit;
// Skip past first characters
while ((c = chars[index]) == '0')
{
if (index == last_char) return sign * 0.0;
index++;
}
if (c == '.')
{
got_rad = true;
if (index == last_char)
{
if (!got_digit) return NumberConversion.MALFORMED_FLOAT?;
return sign * 0.0;
}
if (index != last_char && (c = chars[++index]) == '0')
{
lrp--;
got_digit = true;
while (last_char != index && (c = chars[++index]) == '0')
{
lrp--;
}
}
}
while (c - '0' < 10u || c == '.')
{
switch
{
case c == '.':
if (got_rad) return NumberConversion.MALFORMED_FLOAT?;
got_rad = true;
lrp = dc;
case k < KMAX - 3:
dc++;
if (c != '0') lnz = (int)dc;
if (j)
{
x[k] = x[k] * 10 + c - '0';
}
else
{
x[k] = c - '0';
}
if (++j == 9)
{
k++;
j = 0;
}
got_digit = true;
default:
dc++;
if (c != '0') x[KMAX - 4] |= 1;
}
if (index == last_char) break;
assert(index < last_char);
c = chars[++index];
}
if (!got_rad) lrp = dc;
if (!got_digit) return NumberConversion.MALFORMED_FLOAT?;
if ((c | 32) == 'e')
{
if (last_char == index) return NumberConversion.MALFORMED_FLOAT?;
long e10 = String.to_long((String)chars[index + 1..]) ?? NumberConversion.MALFORMED_FLOAT?!;
lrp += e10;
}
else if (index != last_char)
{
return NumberConversion.MALFORMED_FLOAT?;
}
// Handle zero specially to avoid nasty special cases later
if (!x[0]) return sign * 0.0;
// Optimize small integers (w/no exponent) and over/under-flow
if (lrp == dc && dc < 10 && ($bits > 30 || (ulong)x[0] >> $bits == 0)) return sign * (double)x[0];
if (lrp > - $emin / 2) return NumberConversion.FLOAT_OUT_OF_RANGE?;
if (lrp < $emin - 2 * math::DOUBLE_MANT_DIG) return NumberConversion.FLOAT_OUT_OF_RANGE?;
// Align incomplete final B1B digit
if (j)
{
for (; j < 9; j++) x[k] *= 10;
k++;
j = 0;
}
int a;
int z = k;
int e2;
long rp = lrp;
// Optimize small to mid-size integers (even in exp. notation)
if (lnz < 9 && lnz <= rp && rp < 18)
{
if (rp == 9) return sign * (double)x[0];
if (rp < 9) return sign * (double)x[0] / P10S[8 - rp];
int bitlim = $bits - 3 * (int)(rp - 9);
if (bitlim > 30 || x[0] >> bitlim == 0) return sign * (double)x[0] * P10S[rp - 10];
}
// Align radix point to B1B digit boundary
if (rp % 9)
{
long rpm9 = rp >= 0 ? rp % 9 : rp % 9 + 9;
int p10 = P10S[8 - rpm9];
uint carry = 0;
for (k = a; k != z; k++)
{
uint tmp = x[k] % p10;
x[k] = x[k] / p10 + carry;
carry = 1000000000 / p10 * tmp;
if (k == a && !x[k])
{
a = (a + 1) & MASK;
rp -= 9;
}
}
if (carry) x[z++] = carry;
rp += 9 - rpm9;
}
// Upscale until desired number of bits are left of radix point
while (rp < 9 * B1B_DIG || (rp == 9 * B1B_DIG && x[a] < TH[0]))
{
uint carry = 0;
e2 -= 29;
for (k = (z - 1) & MASK; ; k = (k - 1) & MASK)
{
ulong tmp = (ulong)x[k] << 29 + carry;
if (tmp > 1000000000)
{
carry = (uint)(tmp / 1000000000);
x[k] = (uint)(tmp % 1000000000);
}
else
{
carry = 0;
x[k] = (uint)tmp;
}
if (k == (z - 1) & MASK && k != a && !x[k]) z = k;
if (k == a) break;
}
if (carry)
{
rp += 9;
a = (a - 1) & MASK;
if (a == z)
{
z = (z - 1) & MASK;
x[(z - 1) & MASK] |= x[z];
}
x[a] = carry;
}
}
// Downscale until exactly number of bits are left of radix point
while (true)
{
uint carry = 0;
int sh = 1;
int i;
for (i = 0; i < B1B_DIG; i++)
{
k = (a + i) & MASK;
if (k == z || x[k] < TH[i])
{
i = B1B_DIG;
break;
}
if (x[(a + i) & MASK] > TH[i]) break;
}
if (i == B1B_DIG && rp == 9 * B1B_DIG) break;
if (rp > 9 + 9 * B1B_DIG) sh = 9;
e2 += sh;
for (k = a; k != z; k = (k+1) & MASK)
{
uint tmp = x[k] & (1 << sh - 1);
x[k] = x[k] >> sh + carry;
carry = (1000000000 >> sh) * tmp;
if (k == a && !x[k])
{
a = (a + 1) & MASK;
i--;
rp -= 9;
}
}
if (carry)
{
if ((z + 1) & MASK != a)
{
x[z] = carry;
z = (z + 1) & MASK;
}
else
{
x[(z - 1) & MASK] |= 1;
}
}
}
// Assemble desired bits into floating point variable
double y;
int i;
for (i = 0; i < B1B_DIG; i++)
{
if ((a + i) & MASK == z) x[(z = (z + 1) & MASK) - 1] = 0;
y = 1000000000.0 * y + x[(a + i) & MASK];
}
y *= sign;
bool denormal;
/* Limit precision for denormal results */
uint bits = $bits;
if (bits > math::DOUBLE_MANT_DIG + e2 - $emin)
{
bits = math::DOUBLE_MANT_DIG + e2 - $emin;
if (bits < 0) bits = 0;
denormal = true;
}
// Calculate bias term to force rounding, move out lower bits
double bias;
double frac;
if (bits < math::DOUBLE_MANT_DIG)
{
bias = math::copysign(math::scalbn(1, 2 * math::DOUBLE_MANT_DIG - bits - 1), y);
frac = y % math::scalbn(1, math::DOUBLE_MANT_DIG - bits);
y -= frac;
y += bias;
}
// Process tail of decimal input so it can affect rounding
if ((a + i) & MASK != z)
{
uint t = x[(a + i) & MASK];
switch
{
case t < 500000000 && (t || (a + i + 1) & MASK != z):
frac += 0.25 * sign;
case t > 500000000:
frac += 0.75 * sign;
case t == 500000000:
if ((a + i + 1) & MASK == z)
{
frac += 0.5 * sign;
}
else
{
frac += 0.75 * sign;
}
}
if (math::DOUBLE_MANT_DIG - bits >= 2 && !(frac % 1)) frac++;
}
y += frac;
y -= bias;
if (((e2 + math::DOUBLE_MANT_DIG) & int.max) > emax - 5)
{
if (math::abs(y) >= 0x1p53)
{
if (denormal && bits == math::DOUBLE_MANT_DIG + e2 - $emin) denormal = false;
y *= 0.5;
e2++;
}
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)
{
double scale = 1;
uint x;
long rp;
long dc;
char c @noinit;
bool got_rad;
bool got_digit;
bool got_tail;
usz len = chars.len;
usz last_char = len - 1;
usz index;
double y;
// Skip past first characters
while ((c = chars[index]) == '0')
{
if (index == last_char) return 0.0;
index++;
}
if (c == '.')
{
got_rad = true;
if (index == last_char)
{
if (!got_digit) return NumberConversion.MALFORMED_FLOAT?;
return sign * 0.0;
}
if (index != last_char && (c = chars[++index]) == '0')
{
rp--;
got_digit = true;
while (last_char != index && (c = chars[++index]) == '0')
{
rp--;
}
}
}
while ((c - '0') < 10u || ((c | 32) - 'a') < 6u || c == '.')
{
if (c == '.')
{
if (got_rad) return NumberConversion.MALFORMED_FLOAT?;
got_rad = true;
rp = dc;
}
else
{
got_digit = true;
int d = {|
if (c > '9') return (c | 32) + 10 - 'a';
return c - '0';
|};
switch
{
case dc < 8:
x = x * 16 + d;
case dc < math::DOUBLE_MANT_DIG / 4 + 1:
y += d * (scale /= 16);
got_tail = true;
case d && !got_tail:
y += 0.5 * scale;
got_tail = true;
}
dc++;
}
if (index == last_char) break;
c = chars[++index];
}
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..]) ?? (NumberConversion.MALFORMED_FLOAT?)!;
e2 = e2val;
}
e2 += 4 * rp - 32;
if (!x) return sign * 0.0;
if (e2 > -$emin) return NumberConversion.FLOAT_OUT_OF_RANGE?;
if (e2 < $emin - 2 * math::DOUBLE_MANT_DIG) return NumberConversion.FLOAT_OUT_OF_RANGE?;
while (x < 0x80000000)
{
if (y >= 0.5)
{
x += x + 1;
y += y - 1;
}
else
{
x += x;
y += y;
}
e2--;
}
int bits = $bits;
if ($bits > 32 + e2 - $emin)
{
bits = (int)(32 + e2 - $emin);
if (bits < 0) bits = 0;
}
double bias;
if (bits < math::DOUBLE_MANT_DIG)
{
bias = math::copysign(math::scalbn(1, 32 + math::DOUBLE_MANT_DIG - bits - 1), (double)sign);
}
if (bits < 32 && y && !(x & 1))
{
x++;
y = 0;
}
y = bias + sign * (double)x + sign * y;
y -= bias;
if (!y) return NumberConversion.FLOAT_OUT_OF_RANGE?;
return math::scalbn(y, (int)e2);
}
macro String.to_real(chars, $Type) @private
{
int sign = 1;
$switch ($Type)
$case float:
const int BITS = math::FLOAT_MANT_DIG;
const int EMIN = math::FLOAT_MIN_EXP - BITS;
$case double:
const int BITS = math::DOUBLE_MANT_DIG;
const int EMIN = math::DOUBLE_MIN_EXP - BITS;
$case float128:
$error "Not yet supported";
$default:
$error "Unexpected type";
$endswitch
while (chars.len && chars[0] == ' ') chars = chars[1..];
if (!chars.len) return NumberConversion.MALFORMED_FLOAT?;
switch (chars[0])
{
case '-':
sign = -1;
nextcase;
case '+':
chars = chars[1..];
}
if (chars == "infinity" || chars == "INFINITY") return sign * $Type.inf;
if (chars == "NAN" || chars == "nan") return $Type.nan;
if (chars.len > 2 && chars[0] == '0' && (chars[1] | 32) == 'x')
{
return ($Type)hexfloat((char[])chars[2..], BITS, EMIN, sign);
}
return ($Type)decfloat((char[])chars, BITS, EMIN, sign);
}

View File

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

View File

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

View File

@@ -1,2 +0,0 @@
module std::crypto;

View File

@@ -1,63 +0,0 @@
module std::crypto::rc4;
// Copyright (c) 2021 Christoffer Lerno. All rights reserved.
// Use of this source code is governed by the MIT license
// a copy of which can be found in the LICENSE_STDLIB file.
struct Rc4
{
uint i, j;
char[256] state;
}
/**
* Initialize the RC4 state.
*
* @param [in] key "The key to use"
* @require key.len > 0 "The key must be at least 1 byte long"
**/
fn void Rc4.init(&self, char[] key)
{
// Init the state matrix
foreach (char i, &c : self.state) *c = i;
for (int i = 0, int j = 0; i < 256; i++)
{
j = (j + self.state[i] + key[i % key.len]) & 0xFF;
@swap(self.state[i], self.state[j]);
}
self.i = 0;
self.j = 0;
}
/**
* 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"
**/
fn void Rc4.crypt(&self, char[] in, char[] out)
{
uint i = self.i;
uint j = self.j;
char* state = &self.state;
isz len = in.len;
foreach (idx, c : in)
{
i = (i + 1) & 0xFF;
j = (j + state[i]) & 0xFF;
@swap(state[i], state[j]);
out[idx] = in[idx] ^ state[(state[i] + state[j]) & 0xFF];
}
self.i = i;
self.j = j;
}
/**
* Clear the rc4 state.
*
* @param [&out] self "The RC4 State"
**/
fn void Rc4.destroy(&self)
{
*self = {};
}

View File

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

View File

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

View File

@@ -1,369 +0,0 @@
// Copyright (c) 2023 Christoffer Lerno. All rights reserved.
// Use of this source code is governed by the MIT license
// a copy of which can be found in the LICENSE_STDLIB file.
module std::encoding::json;
import std::io;
import std::ascii;
import std::collections::object;
fault JsonParsingError
{
EOF,
UNEXPECTED_CHARACTER,
INVALID_ESCAPE_SEQUENCE,
DUPLICATE_MEMBERS,
INVALID_NUMBER,
}
fn Object*! parse(InStream s, Allocator allocator = allocator::heap())
{
JsonContext context = { .last_string = dstring::new_with_capacity(64, allocator), .stream = s, .allocator = allocator };
defer context.last_string.free();
return parse_any(&context);
}
// -- Implementation follows --
enum JsonTokenType @local
{
NO_TOKEN,
LBRACE,
LBRACKET,
COMMA,
COLON,
RBRACE,
RBRACKET,
STRING,
NUMBER,
TRUE,
FALSE,
NULL,
EOF,
}
struct JsonContext @local
{
uint line;
InStream stream;
Allocator allocator;
JsonTokenType token;
DString last_string;
double last_number;
char current;
bitstruct : char {
bool skip_comments;
bool reached_end;
bool pushed_back;
}
}
fn Object*! parse_from_token(JsonContext* context, JsonTokenType token) @local
{
switch (token)
{
case NO_TOKEN: unreachable();
case LBRACE: return parse_map(context);
case LBRACKET: return parse_array(context);
case COMMA:
case RBRACE:
case RBRACKET:
case COLON: return JsonParsingError.UNEXPECTED_CHARACTER?;
case STRING: return object::new_string(context.last_string.str_view(), context.allocator);
case NUMBER: return object::new_float(context.last_number, context.allocator);
case TRUE: return object::new_bool(true);
case FALSE: return object::new_bool(false);
case NULL: return object::new_null();
case EOF: return JsonParsingError.EOF?;
}
}
fn Object*! parse_any(JsonContext* context) @local
{
return parse_from_token(context, advance(context));
}
fn JsonTokenType! lex_number(JsonContext *context, char c) @local
{
@pool()
{
DString t = dstring::temp_with_capacity(32);
bool negate = c == '-';
if (negate)
{
t.append(c);
c = read_next(context)!;
}
while (c.is_digit())
{
t.append(c);
c = read_next(context)!;
}
if (c == '.')
{
t.append(c);
while (c = read_next(context)!, c.is_digit())
{
t.append(c);
}
}
if ((c | 32) == 'e')
{
t.append(c);
c = read_next(context)!;
switch (c)
{
case '-':
case '+':
t.append(c);
c = read_next(context)!;
}
if (!c.is_digit()) return JsonParsingError.INVALID_NUMBER?;
while (c.is_digit())
{
t.append(c);
c = read_next(context)!;
}
}
pushback(context, c);
double! d = t.str_view().to_double() ?? JsonParsingError.INVALID_NUMBER?;
context.last_number = d!;
return NUMBER;
};
}
fn Object*! parse_map(JsonContext* context) @local
{
Object* map = object::new_obj(context.allocator);
JsonTokenType token = advance(context)!;
defer catch map.free();
DString temp_key = dstring::new_with_capacity(32, context.allocator);
defer temp_key.free();
while (token != JsonTokenType.RBRACE)
{
if (token != JsonTokenType.STRING) return JsonParsingError.UNEXPECTED_CHARACTER?;
DString string = context.last_string;
if (map.has_key(string.str_view())) return JsonParsingError.DUPLICATE_MEMBERS?;
// Copy the key to our temp holder. We do this to work around the issue
// if the temp allocator should be used as the default allocator.
temp_key.clear();
temp_key.append(string);
parse_expected(context, COLON)!;
Object* element = parse_any(context)!;
map.set(temp_key.str_view(), element);
token = advance(context)!;
if (token == JsonTokenType.COMMA)
{
token = advance(context)!;
continue;
}
if (token != JsonTokenType.RBRACE) return JsonParsingError.UNEXPECTED_CHARACTER?;
}
return map;
}
fn Object*! parse_array(JsonContext* context) @local
{
Object* list = object::new_obj(context.allocator);
defer catch list.free();
JsonTokenType token = advance(context)!;
while (token != JsonTokenType.RBRACKET)
{
Object* element = parse_from_token(context, token)!;
list.push(element);
token = advance(context)!;
if (token == JsonTokenType.COMMA)
{
token = advance(context)!;
continue;
}
if (token != JsonTokenType.RBRACKET) return JsonParsingError.UNEXPECTED_CHARACTER?;
}
return list;
}
fn void pushback(JsonContext* context, char c) @local
{
if (!context.reached_end)
{
assert(!context.pushed_back);
context.pushed_back = true;
context.current = c;
}
}
fn char! read_next(JsonContext* context) @local
{
if (context.reached_end) return '\0';
if (context.pushed_back)
{
context.pushed_back = false;
return context.current;
}
char! c = context.stream.read_byte();
if (catch err = c)
{
case IoError.EOF:
context.reached_end = true;
return '\0';
default:
return err?;
}
if (c == 0)
{
context.reached_end = true;
}
return c;
}
fn JsonTokenType! advance(JsonContext* context) @local
{
char c;
// Skip whitespace
while WS: (c = read_next(context)!)
{
switch (c)
{
case '\n':
context.line++;
nextcase;
case ' ':
case '\t':
case '\r':
case '\v':
continue;
case '/':
if (!context.skip_comments) break;
c = read_next(context)!;
if (c != '*')
{
pushback(context, c);
break WS;
}
while COMMENT: (true)
{
// Skip to */
while (c = read_next(context)!)
{
if (c == '\n') context.line++;
if (c != '*') continue;
// Skip through all the '*'
while (c = read_next(context)!)
{
if (c == '\n') context.line++;
if (c != '*') break;
}
if (c == '/') break COMMENT;
}
}
continue;
default:
break WS;
}
}
switch (c)
{
case '\0':
return IoError.EOF?;
case '{':
return LBRACE;
case '}':
return RBRACE;
case '[':
return LBRACKET;
case ']':
return RBRACKET;
case ':':
return COLON;
case ',':
return COMMA;
case '"':
return lex_string(context);
case '-':
case '0'..'9':
return lex_number(context, c);
case 't':
match(context, "rue")!;
return TRUE;
case 'f':
match(context, "alse")!;
return FALSE;
case 'n':
match(context, "ull")!;
return NULL;
default:
return JsonParsingError.UNEXPECTED_CHARACTER?;
}
}
fn void! match(JsonContext* context, String str) @local
{
foreach (c : str)
{
char l = read_next(context)!;
if (l != c) return JsonParsingError.UNEXPECTED_CHARACTER?;
}
}
fn void! parse_expected(JsonContext* context, JsonTokenType token) @local
{
if (advance(context)! != token) return JsonParsingError.UNEXPECTED_CHARACTER?;
}
fn JsonTokenType! lex_string(JsonContext* context)
{
context.last_string.clear();
while LOOP: (true)
{
char c = read_next(context)!;
switch (c)
{
case '\0':
return JsonParsingError.EOF?;
case 1..31:
return JsonParsingError.UNEXPECTED_CHARACTER?;
case '"':
break LOOP;
case '\\':
break;
default:
context.last_string.append(c);
continue;
}
c = read_next(context)!;
switch (c)
{
case '\0':
return JsonParsingError.EOF?;
case 1..31:
return JsonParsingError.UNEXPECTED_CHARACTER?;
case '"':
case '\\':
case '/':
break;
case 'b':
c = '\b';
case 'f':
c = '\f';
case 'n':
c = '\n';
case 'r':
c = '\r';
case 't':
c = '\t';
case 'u':
uint val;
for (int i = 0; i < 4; i++)
{
c = read_next(context)!;
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 JsonParsingError.INVALID_ESCAPE_SEQUENCE?;
}
}
return STRING;
}

83
lib/std/enumset.c3 Normal file
View File

@@ -0,0 +1,83 @@
// TODO: ensure the type is an enum first.
module std::enumset<Enum>;
$assert(Enum.elements < 64, "Maximum number of elements for an enum used as enum set is 63");
$switch ($$C_INT_SIZE):
$case 64:
private define EnumSetType = ulong;
$case 32:
$if (Enum.elements < 32):
private define EnumSetType = uint;
$else:
private define EnumSetType = ulong;
$endif;
$default:
$if (Enum.elements < 16):
private define EnumSetType = ushort;
$elif (Enum.elements < 31):
private define EnumSetType = uint;
$else:
private define EnumSetType = ulong;
$endif;
$endswitch;
define EnumSet = distinct EnumSetType;
fn void EnumSet.add(EnumSet* this, Enum v)
{
*this = (EnumSet)((EnumSetType)*this | 1u << (EnumSetType)v);
}
fn void EnumSet.clear(EnumSet* this)
{
*this = 0;
}
fn bool EnumSet.remove(EnumSet* this, Enum v)
{
EnumSetType old = (EnumSetType)*this;
EnumSetType new = old & ~(1u << (EnumSetType)v);
*this = (EnumSet)new;
return old != new;
}
fn bool EnumSet.has(EnumSet* this, Enum v)
{
return ((EnumSetType)*this & (1u << (EnumSetType)v)) != 0;
}
fn void EnumSet.add_all(EnumSet* this, EnumSet s)
{
*this = (EnumSet)((EnumSetType)*this | (EnumSetType)s);
}
fn void EnumSet.retain_all(EnumSet* this, EnumSet s)
{
*this = (EnumSet)((EnumSetType)*this & (EnumSetType)s);
}
fn void EnumSet.remove_all(EnumSet* this, EnumSet s)
{
*this = (EnumSet)((EnumSetType)*this & ~(EnumSetType)s);
}
fn EnumSet EnumSet.and_of(EnumSet* this, EnumSet s)
{
return (EnumSet)((EnumSetType)*this & (EnumSetType)s);
}
fn EnumSet EnumSet.or_of(EnumSet* this, EnumSet s)
{
return (EnumSet)((EnumSetType)*this | (EnumSetType)s);
}
fn EnumSet EnumSet.diff_of(EnumSet* this, EnumSet s)
{
return (EnumSet)((EnumSetType)*this & ~(EnumSetType)s);
}
fn EnumSet EnumSet.xor_of(EnumSet* this, EnumSet s)
{
return (EnumSet)((EnumSetType)*this ^ (EnumSetType)s);
}

View File

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

View File

@@ -5,45 +5,45 @@ module std::hash::crc32;
struct Crc32
{
uint result;
uint result;
}
fn void Crc32.init(&self, uint seed = 0)
fn void Crc32.init(Crc32* this, uint seed = 0)
{
self.result = ~seed;
this.result = ~seed;
}
fn void Crc32.updatec(&self, char c)
fn void Crc32.updatec(Crc32* this, char c)
{
self.result = (self.result >> 8) ^ CRC32_TABLE[(self.result ^ c) & 0xFF];
this.result = (this.result >> 8) ^ CRC32_TABLE[(this.result ^ c) & 0xFF];
}
fn void Crc32.update(&self, char[] data)
fn void Crc32.update(Crc32* this, char[] data)
{
uint result = self.result;
foreach (char x : data)
{
result = (result >> 8) ^ CRC32_TABLE[(result ^ x) & 0xFF];
}
self.result = result;
uint result = this.result;
foreach (char x : data)
{
result = (result >> 8) ^ CRC32_TABLE[(result ^ x) & 0xFF];
}
this.result = result;
}
fn uint Crc32.final(&self)
fn uint Crc32.final(Crc32* this)
{
return ~self.result;
return ~this.result;
}
fn uint encode(char[] data)
{
uint result = ~(uint)(0);
foreach (char x : data)
{
result = (result >> 8) ^ CRC32_TABLE[(result ^ x) & 0xFF];
}
uint result = ~(uint)(0);
foreach (char x : data)
{
result = (result >> 8) ^ CRC32_TABLE[(result ^ x) & 0xFF];
}
return ~result;
}
const uint[256] CRC32_TABLE @private = {
private const uint[256] CRC32_TABLE = {
0x00000000, 0x77073096, 0xee0e612c, 0x990951ba,
0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3,
0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,

View File

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

View File

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

View File

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

View File

@@ -1,241 +0,0 @@
// Copyright (c) 2021 Christoffer Lerno. All rights reserved.
// Use of this source code is governed by the MIT license
// a copy of which can be found in the LICENSE_STDLIB file.
//
// Implementation was off Steve Reid's SHA-1 C implementation
module std::hash::sha1;
import std::bits;
struct Sha1
{
uint[5] state;
uint[2] count;
char[64] buffer;
}
fn void Sha1.init(&self)
{
// SHA1 initialization constants
*self = {
.state = {
0x67452301,
0xEFCDAB89,
0x98BADCFE,
0x10325476,
0xC3D2E1F0
}
};
}
/**
* @param [in] data
* @require data.len <= uint.max
**/
fn void Sha1.update(&self, char[] data)
{
uint j = self.count[0];
uint len = data.len;
if ((self.count[0] += len << 3) < j) self.count[1]++;
self.count[1] += len >> 29;
j = (j >> 3) & 63;
uint i;
if (j + len > 63)
{
i = 64 - j;
self.buffer[j..] = data[:i];
sha1_transform(&self.state, &self.buffer);
for (; i + 63 < len; i += 64)
{
sha1_transform(&self.state, &data[i]);
}
j = 0;
}
self.buffer[j:len - i] = data[i..];
}
fn char[20] Sha1.final(&self)
{
char[8] finalcount;
for (uint i = 0; i < 8; i++)
{
finalcount[i] = (char)((self.count[(i >= 4 ? 0 : 1)] >> ((3 - (i & 3)) * 8)) & 0xFF);
}
self.update(char[] { 0o200 });
while ((self.count[0] & 504) != 448)
{
self.update(char[] { 0 });
}
self.update(&finalcount);
char[20] digest;
for (uint i = 0; i < 20; i++)
{
digest[i] = (char)((self.state[i >> 2] >> ((3 - (i & 3)) * 8)) & 0xFF);
}
// Clear mem
mem::clear(self, Sha1.sizeof);
finalcount = {};
return digest;
}
union Long16 @local
{
char[64] c;
uint[16] l;
}
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(&block, i) @local
{
$if env::BIG_ENDIAN:
return block.l[i];
$else
return block.l[i] = (block.l[i].rotl(24) & 0xFF00FF00)
| (block.l[i].rotl(8) & 0x00FF00FF);
$endif
}
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);
*wref = w.rotl(30);
}
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);
*wref = w.rotl(30);
}
macro @r2(&block, v, &wref, x, y, &z, i) @local
{
var w = *wref;
*z += (w ^ x ^ y) + @blk(*block, i) + 0x6ED9EBA1 + v.rotl(5);
*wref = w.rotl(30);
}
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);
*wref = w.rotl(30);
}
macro @r4(&block, v, &wref, x, y, &z, i) @local
{
var w = *wref;
*z += (w ^ x ^ y) + @blk(*block, i) + 0xCA62C1D6 + v.rotl(5);
*wref = w.rotl(30);
}
/**
* @param [&inout] state
* @param [&in] buffer
**/
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;
a = b = c = d = e = 0;
buffer[:64] = 0;
}

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

@@ -0,0 +1,239 @@
// Copyright (c) 2021-2022 Christoffer Lerno. All rights reserved.
// Use of this source code is governed by the MIT license
// a copy of which can be found in the LICENSE_STDLIB file.
module std::io;
import libc;
struct File
{
CFile file;
}
fn int putchar(char c) @inline
{
return libc::putchar(c);
}
/**
* @param [&in] message
* @return `number of bytes printed.`
*/
fn int print(char* message)
{
char* pointer = message;
while (*pointer != '\0')
{
if (!putchar(*pointer)) return 0;
pointer++;
}
return 1;
}
/**
* @param [&in] message
* @return `number of bytes printed.`
*/
fn int println(char *message = "") @inline
{
return libc::puts(message);
}
fn void! File.open(File* file, char[] filename, char[] mode)
{
@pool()
{
char* filename_copy = tmalloc(filename.len + 1);
char* mode_copy = tmalloc(mode.len + 1);
mem::copy(filename_copy, (char*)(filename), filename.len);
mem::copy(mode_copy, (char*)(mode), mode.len);
filename_copy[filename.len] = 0;
mode_copy[filename.len] = 0;
file.file = libc::fopen(filename_copy, mode_copy);
if (!file.file) return IoError.FILE_NOT_FOUND!;
};
}
enum Seek
{
SET,
CURSOR,
END
}
fault IoError
{
FILE_NOT_FOUND,
FILE_NOT_SEEKABLE,
FILE_NOT_VALID,
FILE_INVALID_POSITION,
FILE_OVERFLOW,
FILE_IS_PIPE,
FILE_EOF,
FILE_INCOMPLETE_WRITE,
INTERRUPTED,
UNKNOWN_ERROR,
}
/**
* @require file.file != null
**/
fn void! File.seek(File *file, long offset, Seek seekMode = Seek.SET)
{
if (libc::fseek(file.file, (SeekIndex)(offset), (int)(seekMode)))
{
switch (libc::errno())
{
case errno::EBADF: return IoError.FILE_NOT_SEEKABLE!;
case errno::EINVAL: return IoError.FILE_INVALID_POSITION!;
case errno::EOVERFLOW: return IoError.FILE_OVERFLOW!;
case errno::ESPIPE: return IoError.FILE_IS_PIPE!;
default: return IoError.UNKNOWN_ERROR!;
}
}
}
/**
* @require file && file.file != null
*/
fn void! File.putc(File *file, char c)
{
if (!libc::fputc(c, file.file)) return IoError.FILE_EOF!;
}
/**
* @require file != null
*/
fn void! File.close(File *file) @inline
{
if (file.file && libc::fclose(file.file))
{
switch (libc::errno())
{
case errno::ECONNRESET:
case errno::EBADF: return IoError.FILE_NOT_VALID!;
case errno::EINTR: return IoError.INTERRUPTED!;
case errno::EDQUOT:
case errno::EFAULT:
case errno::EAGAIN:
case errno::EFBIG:
case errno::ENETDOWN:
case errno::ENETUNREACH:
case errno::ENOSPC:
case errno::EIO: return IoError.FILE_INCOMPLETE_WRITE!;
default: return IoError.UNKNOWN_ERROR!;
}
}
file.file = null;
}
/**
* @require file && file.file
*/
fn bool File.eof(File* file) @inline
{
return libc::feof(file.file) != 0;
}
/**
* @require file && file.file
*/
fn usize File.read(File* file, void* buffer, usize items, usize element_size = 1)
{
return libc::fread(buffer, element_size, items, file.file);
}
/**
* @param [&in] file
* @param [&out] buffer
* @param items
* @param element_size
* @require file.file `File must be initialized`
* @require element_size > 1
*/
fn usize File.write(File* file, void* buffer, usize items, usize element_size = 1)
{
return libc::fwrite(buffer, element_size, items, file.file);
}
/**
* @param [&in] file
* @require file.file `File must be initialized`
*/
fn usize! File.println(File* file, char[] string)
{
usize len = string.len;
if (len != libc::fwrite(string.ptr, 1, len, file.file)) return IoError.UNKNOWN_ERROR!;
if (!libc::putc('\n', file.file)) return IoError.UNKNOWN_ERROR!;
return len + 1;
}
/**
* @param [&in] file
* @require file.file `File must be initialized`
*/
fn String File.getline(File* file, Allocator* allocator = mem::current_allocator())
{
String s = string::new_with_capacity(120, allocator);
while (!file.eof())
{
int c = libc::fgetc(file.file);
if (c == '\n') break;
s.append_char((char)c);
}
return s;
}
/**
* @param [&in] file
* @require file.file `File must be initialized`
*/
fn void File.flush(File* file)
{
libc::fflush(file.file);
}
fn File stdout()
{
return { libc::stdout() };
}
fn File stderr()
{
return { libc::stderr() };
}
fn File stdin()
{
return { libc::stdin() };
}
/*
error FileError
{
ulong errno;
}
fn FileError errorFromErrno()
{
return FileError { };
}
pubic fn void! File.clearerr(File *file) @inline
{
clearerr(file->file);
}
fn void File.error(File *file) @inline
{
int err = ferror
}
*/

View File

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

View File

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

View File

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

View File

@@ -1,667 +0,0 @@
module std::io;
import std::math;
const char[16] XDIGITS_H = "0123456789ABCDEF";
const char[16] XDIGITS_L = "0123456789abcdef";
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
{
switch (arg.type.kindof)
{
case TypeKind.POINTER:
*is_neg = false;
return (uint128)(uptr)*(void**)arg.ptr;
case TypeKind.DISTINCT:
case TypeKind.ENUM:
return int_from_any(arg.as_inner(), is_neg);
default:
}
*is_neg = false;
switch (arg)
{
case bool:
return (uint128)*arg;
case ichar:
int val = *arg;
return (*is_neg = val < 0) ? (~(uint128)val) + 1 : (uint128)val;
case short:
int val = *arg;
return (*is_neg = val < 0) ? (~(uint128)val) + 1 : (uint128)val;
case int:
int val = *arg;
return (*is_neg = val < 0) ? (~(uint128)val) + 1 : (uint128)val;
case long:
long val = *arg;
return (*is_neg = val < 0) ? (~(uint128)val) + 1 : (uint128)val;
case int128:
int128 val = *arg;
return (*is_neg = val < 0) ? (~(uint128)val) + 1 : (uint128)val;
case char:
return *arg;
case ushort:
return *arg;
case uint:
return *arg;
case ulong:
return *arg;
case uint128:
return *arg;
case float:
float f = *arg;
return (uint128)((*is_neg = f < 0) ? -f : f);
case double:
double d = *arg;
return (uint128)((*is_neg = d < 0) ? -d : d);
default:
return PrintFault.INVALID_ARGUMENT_TYPE?;
}
}
fn FloatType! float_from_any(any arg) @private
{
$if env::F128_SUPPORT:
if (arg.type == float128.typeid) return (FloatType)*((float128*)arg.ptr);
$endif
if (arg.type.kindof == TypeKind.DISTINCT)
{
return float_from_any(arg.as_inner());
}
switch (arg)
{
case bool:
return (FloatType)*arg;
case ichar:
return *arg;
case short:
return *arg;
case int:
return *arg;
case long:
return *arg;
case int128:
return *arg;
case char:
return *arg;
case ushort:
return *arg;
case uint:
return *arg;
case ulong:
return *arg;
case uint128:
return *arg;
case float:
return (FloatType)*arg;
case double:
return (FloatType)*arg;
default:
return PrintFault.INVALID_ARGUMENT_TYPE?;
}
}
/**
* 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."
* @return "The result of the atoi."
**/
fn uint simple_atoi(char* buf, usz maxlen, usz* len_ptr) @inline @private
{
uint i = 0;
usz len = *len_ptr;
while (len < maxlen)
{
char c = buf[len];
if (!c.is_digit()) break;
i = i * 10 + c - '0';
len++;
}
*len_ptr = len;
return i;
}
fn usz! Formatter.out_substr(&self, String str) @private
{
usz l = conv::utf8_codepoints(str);
uint prec = self.prec;
if (self.flags.precision && l < prec) l = prec;
usz index = 0;
usz chars = str.len;
char* ptr = str.ptr;
while (index < chars)
{
char c = ptr[index];
// Break if we have precision set and we ran out...
if (c & 0xC0 != 0x80 && self.flags.precision && !prec--) break;
self.out(c)!;
index++;
}
return index;
}
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)!;
return max(0, delta);
}
fn char* fmt_u(uint128 x, char* s)
{
for (; x > ulong.max; x /= 10) *--s = '0' + (char)(x % 10);
for (ulong y = (ulong)x; y; y /= 10) *--s = '0' + (char)(y % 10);
return s;
}
fn usz! Formatter.out_chars(&self, char[] s)
{
foreach (c : s) self.out(c)!;
return s.len;
}
enum FloatFormatting
{
FLOAT,
EXPONENTIAL,
ADAPTIVE,
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.floatformat(&self, FloatFormatting formatting, double y) @private
{
// This code is heavily based on musl's printf code
const BUF_SIZE = (math::DOUBLE_MANT_DIG + 28) / 29 + 1
+ (math::DOUBLE_MAX_EXP + math::DOUBLE_MANT_DIG + 28 + 8) / 9;
uint[BUF_SIZE] big;
bool is_neg = false;
if (math::signbit(y))
{
is_neg = true;
y = -y;
}
isz pl = is_neg || self.flags.plus ? 1 : 0;
// Print inf/nan
if (!math::is_finite(y))
{
usz len;
// Add padding
if (!self.flags.left) len += self.pad(' ', self.width, 3 + pl)!;
String s = self.flags.uppercase ? "INF" : "inf";
if (y != y) s = self.flags.uppercase ? "NAN" : "nan";
len += s.len;
if (pl) len += self.out(is_neg ? '-' : '+')!;
len += self.out_chars(s)!;
if (self.flags.left) len += self.pad(' ', self.width, 3 + pl)!;
return len;
}
// Rescale
int e2;
y = math::frexp(y, &e2) * 2;
if (y) e2--;
char[12] ebuf0;
char* ebuf = 12 + (char*)&ebuf0;
char[9 + math::DOUBLE_MANT_DIG / 4] buf_array;
char* buf = &buf_array;
isz p = self.flags.precision ? self.prec : -1;
if (formatting == HEX)
{
double round = 8.0;
// 0x / 0X
pl += 2;
if (p > 0 && p < math::DOUBLE_MANT_DIG / 4 - 1)
{
int re = math::DOUBLE_MANT_DIG / 4 - 1 - (int)p;
round *= 1 << (math::DOUBLE_MANT_DIG % 4);
while (re--) round *= 16;
if (is_neg)
{
y = -y;
y -= round;
y += round;
y = -y;
}
else
{
y += round;
y -= round;
}
}
// Reverse print
char* estr = fmt_u(e2 < 0 ? (int128)-e2 : (int128)e2, ebuf);
if (estr == ebuf) *--estr = '0';
*--estr = (e2 < 0 ? '-' : '+');
*--estr = self.flags.uppercase ? 'P' : 'p';
char* s = buf;
char* xdigits = self.flags.uppercase ? &XDIGITS_H : &XDIGITS_L;
do
{
int x = (int)y;
*s++ = xdigits[x];
y = 16 * (y - x);
if (s - buf == 1 && (y || p > 0 || self.flags.hash)) *s++ = '.';
} while (y);
isz outlen = s - buf;
isz explen = ebuf - estr;
if (p > int.max - 2 - explen - pl) return PrintFault.INTERNAL_BUFFER_EXCEEDED?;
usz len;
usz l = p && outlen - 2 < p
? p + 2 + explen
: outlen + explen;
if (!self.flags.left && !self.flags.zeropad) len += self.pad(' ', self.width, pl + l)!;
if (is_neg || self.flags.plus) len += self.out(is_neg ? '-' : '+')!;
len += self.out_chars(self.flags.uppercase ? "0X" : "0x")!;
if (self.flags.zeropad) len += self.pad('0', self.width, pl + l)!;
len += self.out_chars(buf[:outlen])!;
len += self.pad('0', l - outlen - explen, 0)!;
len += self.out_chars(estr[:explen])!;
if (self.flags.left) len += self.pad(' ', self.width, pl + l)!;
return len;
}
if (p < 0) p = 6;
if (y)
{
y *= 0x1p28;
e2 -= 28;
}
uint* a, z, r;
if (e2 < 0)
{
a = r = z = &big;
}
else
{
a = r = z = (uint*)&big + big.len - math::DOUBLE_MANT_DIG - 1;
}
do
{
uint v = z++[0] = (uint)y;
y = 1000000000 * (y - v);
} while (y);
while (e2 > 0)
{
uint carry = 0;
int sh = math::min(29, e2);
for (uint* d = z - 1; d >= a; d--)
{
ulong x = (ulong)*d << sh + carry;
*d = (uint)(x % 1000000000);
carry = (uint)(x / 1000000000);
}
if (carry) *--a = carry;
while (z > a && !z[-1]) z--;
e2 -= sh;
}
while (e2 < 0)
{
uint carry = 0;
uint* b;
int sh = math::min(9, -e2);
int need = (int)(1 + (p + math::DOUBLE_MANT_DIG / 3u + 8) / 9);
for (uint* d = a; d < z; d++)
{
// CHECK THIS
uint rm = *d & ((1 << sh) - 1);
*d = (*d >> sh) + carry;
carry = (1000000000 >> sh) * rm;
}
if (!a[0]) a++;
if (carry) z++[0] = carry;
// Avoid (slow!) computation past requested precision
b = formatting == FLOAT ? r : a;
if (z - b > need) z = b + need;
e2 += sh;
}
int e;
if (a < z)
{
for (int i = 10, e = (int)(9 * (r - a)); *a >= i; i *= 10, e++);
}
// Perform rounding: j is precision after the radix (possibly neg)
int j = (int)(p - (isz)(formatting == FLOAT ? 0 : e - (int)(formatting == ADAPTIVE && p)));
if (j < 9 * (z - r - 1))
{
uint x;
// We avoid C's broken division of negative numbers
uint* d = r + 1 + ((j + 9 * math::DOUBLE_MAX_EXP) / 9 - math::DOUBLE_MAX_EXP);
j += 9 * math::DOUBLE_MAX_EXP;
j %= 9;
int i;
for (i = 10, j++; j < 9; i *= 10, j++);
x = *d % i;
// Are there any significant digits past j?
if (x || (d + 1) != z)
{
double round = 2 / math::DOUBLE_EPSILON;
double small;
if (((*d / i) & 1) || (i == 1000000000 && d > a && (d[-1] & 1)))
{
round += 2;
}
switch
{
case x < i / 2:
small = 0x0.8p0;
case x == i / 2 && d + 1 == z:
small = 0x1.0p0;
default:
small = 0x1.8p0;
}
if (pl && is_neg)
{
round *= -1;
small *= -1;
}
*d -= x;
// Decide whether to round by probing round+small
if (round + small != round)
{
*d = *d + i;
while (*d > 999999999)
{
*d-- = 0;
if (d < a) *--a = 0;
(*d)++;
}
for (i = 10, e = (int)(9 * (r - a)); *a >= i; i *= 10, e++);
}
}
if (z > d + 1) z = d + 1;
}
for (; z>a && !z[-1]; z--);
if (formatting == ADAPTIVE)
{
if (!p) p++;
if (p > e && e >= -4)
{
formatting = FLOAT;
p -= (isz)e + 1;
}
else
{
formatting = EXPONENTIAL;
p--;
}
if (!self.flags.hash)
{
// Count trailing zeros in last place
if (z > a && z[-1])
{
for (int i = 10, j = 0; z[-1] % i == 0; i *= 10, j++);
}
else
{
j = 9;
}
if (formatting == FLOAT)
{
p = math::min(p, math::max((isz)0, 9 * (z - r - 1) - j));
}
else
{
p = math::min(p, math::max((isz)0, 9 * (z - r - 1) + e - j));
}
}
}
if (p > int.max - 1 - (isz)(p || self.flags.hash)) return PrintFault.INTERNAL_BUFFER_EXCEEDED?;
int l = (int)(1 + p + (isz)(p || self.flags.hash));
char* estr @noinit;
if (formatting == FLOAT)
{
if (e > int.max - l) return PrintFault.INTERNAL_BUFFER_EXCEEDED?;
if (e > 0) l += e;
}
else
{
estr = fmt_u((uint128)(e < 0 ? -e : e), ebuf);
while (ebuf - estr < 2) (--estr)[0] = '0';
*--estr = (e < 0 ? '-' : '+');
*--estr = self.flags.uppercase ? 'E' : 'e';
if (ebuf - estr > (isz)int.max - l) return PrintFault.INTERNAL_BUFFER_EXCEEDED?;
l += (int)(ebuf - estr);
}
if (l > int.max - pl) return PrintFault.INTERNAL_BUFFER_EXCEEDED?;
usz len;
if (!self.flags.left && !self.flags.zeropad) len += self.pad(' ', self.width, pl + l)!;
if (is_neg || self.flags.plus) len += self.out(is_neg ? '-' : '+')!;
if (self.flags.zeropad) len += self.pad('0', self.width, pl + l)!;
if (formatting == FLOAT)
{
if (a > r) a = r;
uint* d = a;
for (; d <= r; d++)
{
char* s = fmt_u(*d, buf + 9);
switch
{
case d != a:
while (s > buf) (--s)[0] = '0';
case s == buf + 9:
*--s = '0';
}
len += self.out_chars(s[:buf + 9 - s])!;
}
if (p || self.flags.hash) len += self.out('.')!;
for (; d < z && p > 0; d++, p -= 9)
{
char* s = fmt_u(*d, buf + 9);
while (s > buf) *--s = '0';
len += self.out_chars(s[:math::min((isz)9, p)])!;
}
len += self.pad('0', p + 9, 9)!;
}
else
{
if (z <= a) z = a + 1;
for (uint* d = a; d < z && p >= 0; d++)
{
char* s = fmt_u(*d, buf + 9);
if (s == buf + 9) (--s)[0] = '0';
if (d != a)
{
while (s > buf) (--s)[0] = '0';
}
else
{
len += self.out(s++[0])!;
if (p > 0 || self.flags.hash) len += self.out('.')!;
}
len += self.out_chars(s[:math::min(buf + 9 - s, p)])!;
p -= buf + 9 - s;
}
len += self.pad('0', p + 18, 18)!;
len += self.out_chars(estr[:ebuf - estr])!;
}
if (self.flags.left) len += self.pad(' ', self.width, pl + l)!;
return len;
}
fn usz! Formatter.ntoa(&self, uint128 value, bool negative, uint base) @private
{
char[PRINTF_NTOA_BUFFER_SIZE] buf @noinit;
usz len;
// no hash for 0 values
if (!value) self.flags.hash = false;
// write if precision != 0 or value is != 0
if (!self.flags.precision || value)
{
char past_10 = (self.flags.uppercase ? 'A' : 'a') - 10;
do
{
if (len >= PRINTF_NTOA_BUFFER_SIZE) return PrintFault.INTERNAL_BUFFER_EXCEEDED?;
char digit = (char)(value % base);
buf[len++] = digit + (digit < 10 ? '0' : past_10);
value /= base;
}
while (value);
}
return 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
{
// pad leading zeros
if (!self.flags.left)
{
if (self.width && self.flags.zeropad && (negative || self.flags.plus || self.flags.space)) self.width--;
while (len < self.prec)
{
if (len >= buf.len) return PrintFault.INTERNAL_BUFFER_EXCEEDED?;
buf[len++] = '0';
}
while (self.flags.zeropad && len < self.width)
{
if (len >= buf.len) return PrintFault.INTERNAL_BUFFER_EXCEEDED?;
buf[len++] = '0';
}
}
// handle hash
if (self.flags.hash && base != 10)
{
if (!self.flags.precision && len && len == self.prec && len == self.width)
{
len--;
if (len) len--;
}
if (base != 10)
{
if (len + 1 >= buf.len) return PrintFault.INTERNAL_BUFFER_EXCEEDED?;
switch (base)
{
case 16:
buf[len++] = self.flags.uppercase ? 'X' : 'x';
case 8:
buf[len++] = self.flags.uppercase ? 'O' : 'o';
case 2:
buf[len++] = self.flags.uppercase ? 'B' : 'b';
default:
unreachable();
}
buf[len++] = '0';
}
}
switch (true)
{
case negative:
if (len >= buf.len) return PrintFault.INTERNAL_BUFFER_EXCEEDED?;
buf[len++] = '-';
case self.flags.plus:
if (len >= buf.len) return PrintFault.INTERNAL_BUFFER_EXCEEDED?;
buf[len++] = '+';
case self.flags.space:
if (len >= buf.len) return PrintFault.INTERNAL_BUFFER_EXCEEDED?;
buf[len++] = ' ';
}
if (len) self.out_reverse(buf[:len])!;
return len;
}
fn usz! Formatter.ntoa_any(&self, any arg, uint base) @private
{
bool is_neg;
uint128 val = int_from_any(arg, &is_neg)!!;
return self.ntoa(val, is_neg, base) @inline;
}
fn usz! Formatter.out_char(&self, any arg) @private
{
usz len = 1;
uint l = 1;
// pre padding
len += self.adjust(l)!;
// char output
Char32 c = types::any_to_int(arg, uint) ?? 0xFFFD;
switch (true)
{
case c < 0x7f:
self.out((char)c)!;
case c < 0x7ff:
self.out((char)(0xC0 | c >> 6))!;
self.out((char)(0x80 | (c & 0x3F)))!;
case c < 0xffff:
self.out((char)(0xE0 | c >> 12))!;
self.out((char)(0x80 | (c >> 6 & 0x3F)))!;
self.out((char)(0x80 | (c & 0x3F)))!;
default:
self.out((char)(0xF0 | c >> 18))!;
self.out((char)(0x80 | (c >> 12 & 0x3F)))!;
self.out((char)(0x80 | (c >> 6 & 0x3F)))!;
self.out((char)(0x80 | (c & 0x3F)))!;
}
len += self.adjust(l)!;
return len;
}
fn usz! Formatter.out_reverse(&self, char[] buf) @private
{
usz n;
usz buffer_start_idx = self.idx;
usz len = buf.len;
// pad spaces up to given width
if (!self.flags.zeropad)
{
n += self.adjust(len)!;
}
// reverse string
while (len) n += self.out(buf[--len])!;
// append pad spaces up to given width
n += self.adjust(self.idx - buffer_start_idx)!;
return n;
}
fn void! printf_advance_format(usz format_len, usz *index_ptr) @inline @private
{
usz val = ++(*index_ptr);
if (val >= format_len) return FormattingFault.UNTERMINATED_FORMAT?;
}
fn any! next_any(any* args_ptr, usz args_len, usz* arg_index_ptr) @inline @private
{
if (*arg_index_ptr >= args_len) return FormattingFault.MISSING_ARG?;
return args_ptr[(*arg_index_ptr)++];
}
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
{
char c = format_ptr[*index_ptr];
if (c.is_digit()) return simple_atoi(format_ptr, format_len, index_ptr);
if (c != '*') return 0;
printf_advance_format(format_len, index_ptr)!;
any val = next_any(args_ptr, args_len, args_index_ptr)!;
if (!val.type.kindof.is_int()) return FormattingFault.INVALID_WIDTH_ARG?;
uint! intval = types::any_to_int(val, int);
return intval ?? FormattingFault.INVALID_WIDTH_ARG?;
}

View File

@@ -1,290 +0,0 @@
// Copyright (c) 2021-2022 Christoffer Lerno. All rights reserved.
// Use of this source code is governed by the MIT license
// a copy of which can be found in the LICENSE_STDLIB file.
module std::io;
import libc;
enum Seek
{
SET,
CURSOR,
END
}
fault IoError
{
ALREADY_EXISTS,
BUSY,
CANNOT_READ_DIR,
DIR_NOT_EMPTY,
EOF,
FILE_CANNOT_DELETE,
FILE_IS_DIR,
FILE_IS_PIPE,
FILE_NOT_DIR,
FILE_NOT_FOUND,
FILE_NOT_VALID,
GENERAL_ERROR,
ILLEGAL_ARGUMENT,
INCOMPLETE_WRITE,
INTERRUPTED,
INVALID_POSITION,
INVALID_PUSHBACK,
NAME_TOO_LONG,
NOT_SEEKABLE,
NO_PERMISSION,
OUT_OF_SPACE,
OVERFLOW,
READ_ONLY,
SYMLINK_FAILED,
TOO_MANY_DESCRIPTORS,
UNEXPECTED_EOF,
UNKNOWN_ERROR,
UNSUPPORTED_OPERATION,
WOULD_BLOCK,
}
/**
* @param stream
* @require @is_instream(stream)
**/
macro String! readline(stream = io::stdin(), Allocator allocator = allocator::heap())
{
bool $is_stream = @typeid(stream) == InStream.typeid;
$if $is_stream:
$typeof(&stream.read_byte) func = &stream.read_byte;
char val = func((void*)stream)!;
$else
char val = stream.read_byte()!;
$endif
if (val == '\n') return "";
@pool(allocator)
{
DString str = dstring::temp_with_capacity(256);
if (val != '\r') str.append(val);
while (1)
{
$if $is_stream:
char! c = func((void*)stream);
$else
char! c = stream.read_byte();
$endif
if (catch err = c)
{
if (err == IoError.EOF) break;
return err?;
}
if (c == '\r') continue;
if (c == '\n') break;
str.append_char(c);
}
return str.copy_str(allocator);
};
}
macro String! treadline(stream = io::stdin()) => readline(stream, allocator::temp()) @inline;
/**
* @require @is_outstream(out) "The output must implement OutStream"
*/
macro usz! fprint(out, x)
{
var $Type = $typeof(x);
$switch ($Type)
$case String:
return out.write(x);
$case ZString:
return out.write(x.str_view());
$case DString:
return out.write(x.str_view());
$default:
$if $assignable(x, String):
return out.write((String)x);
$else
return fprintf(out, "%s", x);
$endif
$endswitch
}
fn usz! fprintf(OutStream out, String format, args...)
{
Formatter formatter;
formatter.init(&out_putstream_fn, &out);
return formatter.vprintf(format, args);
}
fn usz! fprintfn(OutStream out, String format, args...) @maydiscard
{
Formatter formatter;
formatter.init(&out_putstream_fn, &out);
usz len = formatter.vprintf(format, args)!;
out.write_byte('\n')!;
if (&out.flush) out.flush()!;
return len + 1;
}
/**
* @require @is_outstream(out) "The output must implement OutStream"
*/
macro usz! fprintn(out, x = "")
{
usz len = fprint(out, x)!;
out.write_byte('\n')!;
$switch
$case @typeid(out) == OutStream.typeid:
if (&out.flush) out.flush()!;
$case $defined(out.flush):
out.flush()!;
$endswitch
return len + 1;
}
macro void print(x)
{
(void)fprint(io::stdout(), x);
}
macro void printn(x = "")
{
(void)fprintn(io::stdout(), x);
}
macro void eprint(x)
{
(void)fprint(io::stderr(), x);
}
macro void eprintn(x)
{
(void)fprintn(io::stderr(), x);
}
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
{
libc::putchar(c);
}
fn usz! printf(String format, args...) @maydiscard
{
Formatter formatter;
formatter.init(&out_putchar_fn);
return formatter.vprintf(format, args);
}
fn usz! printfn(String format, args...) @maydiscard
{
Formatter formatter;
formatter.init(&out_putchar_fn);
usz len = formatter.vprintf(format, args)!;
putchar('\n');
io::stdout().flush()!;
return len + 1;
}
fn usz! eprintf(String format, args...) @maydiscard
{
Formatter formatter;
OutStream stream = stderr();
formatter.init(&out_putstream_fn, &stream);
return formatter.vprintf(format, args);
}
fn usz! eprintfn(String format, args...) @maydiscard
{
Formatter formatter;
OutStream stream = stderr();
formatter.init(&out_putstream_fn, &stream);
usz len = formatter.vprintf(format, args)! + 1;
stderr().write_byte('\n')!;
stderr().flush()!;
return len;
}
fn char[]! bprintf(char[] buffer, String format, args...) @maydiscard
{
Formatter formatter;
BufferData data = { .buffer = buffer };
formatter.init(&out_buffer_fn, &data);
usz size = formatter.vprintf(format, args)!;
return buffer[:data.written];
}
fn void! out_buffer_fn(void *data, char c) @private
{
BufferData *buffer_data = data;
if (buffer_data.written >= buffer_data.buffer.len) return PrintFault.BUFFER_EXCEEDED?;
buffer_data.buffer[buffer_data.written++] = c;
}
struct BufferData @private
{
char[] buffer;
usz written;
}
module std::io @if (env::LIBC);
import libc;
fn void putchar(char c) @inline
{
libc::putchar(c);
}
fn File* stdout()
{
static File file;
if (!file.file) file = file::from_handle(libc::stdout());
return &file;
}
fn File* stderr()
{
static File file;
if (!file.file) file = file::from_handle(libc::stderr());
return &file;
}
fn File* stdin()
{
static File file;
if (!file.file) file = file::from_handle(libc::stdin());
return &file;
}
module std::io @if(!env::LIBC);
File stdin_file;
File stdout_file;
File stderr_file;
fn void putchar(char c) @inline
{
(void)stdout_file.putc(c);
}
fn File* stdout()
{
return &stdout_file;
}
fn File* stderr()
{
return &stderr_file;
}
fn File* stdin()
{
return &stdin_file;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,51 +0,0 @@
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)
{
PathList list;
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) ? IoError.CANNOT_READ_DIR : IoError.FILE_NOT_DIR)?;
Posix_dirent* entry;
while ((entry = posix::readdir(directory)))
{
String name = ((ZString)&entry.name).str_view();
if (!name || name == "." || name == "..") continue;
if (entry.d_type == posix::DT_LNK && no_symlinks) continue;
if (entry.d_type == posix::DT_DIR && no_dirs) continue;
Path path = path::new(name, allocator)!!;
list.push(path);
}
return list;
}
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)
{
PathList list;
list.new_init(.allocator = allocator);
@pool(allocator)
{
WString result = dir.str_view().tconcat(`\*`).to_temp_wstring()!!;
Win32_WIN32_FIND_DATAW find_data;
Win32_HANDLE find = win32::findFirstFileW(result, &find_data);
if (find == win32::INVALID_HANDLE_VALUE) return IoError.CANNOT_READ_DIR?;
defer win32::findClose(find);
do
{
if (no_dirs && (find_data.dwFileAttributes & win32::FILE_ATTRIBUTE_DIRECTORY)) continue;
@pool(allocator)
{
String filename = string::temp_from_wstring((WString)&find_data.cFileName)!;
if (filename == ".." || filename == ".") continue;
list.push(path::new(filename, allocator)!);
};
} while (win32::findNextFileW(find, &find_data));
return list;
};
}

View File

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

View File

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

View File

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

View File

@@ -1,31 +0,0 @@
module std::io::os @if(env::LIBC);
import std::io::path, std::os;
fn Path! native_temp_directory(Allocator allocator = allocator::heap()) @if(!env::WIN32)
{
foreach (String env : { "TMPDIR", "TMP", "TEMP", "TEMPDIR" })
{
String tmpdir = env::get_var(env) ?? "";
if (tmpdir) return path::new(tmpdir, allocator);
}
return path::new("/tmp", allocator);
}
fn Path! native_temp_directory(Allocator allocator = allocator::heap()) @if(env::WIN32)
{
@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);
macro Path! native_temp_directory(Allocator allocator = allocator::heap())
{
return IoError.UNSUPPORTED_OPERATION?;
}

View File

@@ -1,546 +0,0 @@
module std::io::path;
import std::collections::list, std::io::os;
import std::os::win32;
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;
def PathList = List(<Path>);
fault PathResult
{
INVALID_PATH,
NO_PARENT,
}
struct Path (Printable)
{
String path_string;
PathEnv env;
}
enum PathEnv
{
WIN32,
POSIX
}
fn Path! getcwd(Allocator allocator = allocator::heap())
{
@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 bool exists(Path path) => os::native_file_or_dir_exists(path.str_view());
fn Path! tgetcwd() => getcwd(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;
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)
{
return c == '/' || c == '\\';
}
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())
{
$if $defined(os::native_ls):
return os::native_ls(dir, no_dirs, no_symlinks, mask, allocator);
$else
return IoError.UNSUPPORTED_OPERATION?;
$endif
}
enum MkdirPermissions
{
NORMAL,
USER_ONLY,
USER_AND_ADMIN
}
fn bool! mkdir(Path path, bool recursive = false, MkdirPermissions permissions = NORMAL)
{
if (!path.path_string.len) return PathResult.INVALID_PATH?;
if (is_dir(path)) return false;
if (exists(path)) return IoError.FILE_NOT_DIR?;
if (recursive)
{
if (try parent = path.parent()) mkdir(parent, true, permissions)!;
}
if (!is_dir(path.parent()) ?? false) return IoError.CANNOT_READ_DIR?;
return os::native_mkdir(path, permissions);
}
fn bool! rmdir(Path path)
{
if (!path.path_string.len) return PathResult.INVALID_PATH?;
return os::native_rmdir(path);
}
fn void! rmtree(Path path)
{
if (!path.path_string.len) return PathResult.INVALID_PATH?;
$if $defined(os::native_rmtree):
return os::native_rmtree(path);
$else
return IoError.UNSUPPORTED_OPERATION?;
$endif
}
fn Path! new(String path, Allocator allocator = allocator::heap(), PathEnv path_env = DEFAULT_PATH_ENV)
{
return { normalize(path.copy(allocator), path_env), path_env };
}
fn Path! temp_new(String path, PathEnv path_env = DEFAULT_PATH_ENV)
{
return new(path, allocator::temp(), path_env);
}
fn Path! new_win32_wstring(WString path, Allocator allocator = allocator::heap())
{
@pool(allocator)
{
return path::new(string::temp_from_wstring(path)!, .allocator = allocator);
};
}
fn Path! new_windows(String path, Allocator allocator = allocator::heap())
{
return new(path, allocator, WIN32);
}
fn Path! new_posix(String path, Allocator allocator = allocator::heap())
{
return new(path, allocator, POSIX);
}
fn bool Path.equals(self, Path p2)
{
return self.env == p2.env && self.path_string == p2.path_string;
}
/**
* Append the string to the current path.
*
* @param [in] filename
**/
fn Path! Path.append(self, String filename, Allocator allocator = allocator::heap())
{
if (!self.path_string.len) return new(filename, allocator, self.env)!;
assert(!is_separator(self.path_string[^1], self.env));
@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 { normalize(dstr.copy_str(allocator), self.env), self.env };
};
}
fn Path! Path.tappend(self, String filename) => self.append(filename, allocator::temp());
fn usz Path.start_of_base_name(self) @local
{
String path_str = self.path_string;
if (!path_str.len) return 0;
if (self.env == PathEnv.WIN32)
{
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 path_str.rindex_of_char('/') + 1 ?? 0;
}
fn bool! Path.is_absolute(self)
{
String path_str = self.str_view();
if (!path_str.len) return false;
usz path_start = volume_name_len(path_str, self.env)!;
if (path_start > 0 && path_str[0] == '\\') return true;
return path_start < path_str.len && is_separator(path_str[path_start], self.env);
}
/**
* @require self.env == DEFAULT_PATH_ENV : "This method is only available on native paths"
**/
fn Path! Path.absolute(self, Allocator allocator = allocator::heap())
{
String path_str = self.str_view();
if (!path_str.len) return PathResult.INVALID_PATH?;
if (self.is_absolute()!) return new(path_str, allocator, self.env);
if (path_str == ".")
{
@pool(allocator)
{
String cwd = os::getcwd(allocator::temp())!;
return new(cwd, allocator, self.env);
};
}
$if DEFAULT_PATH_ENV == WIN32:
@pool(allocator)
{
const usz BUFFER_LEN = 4096;
WString buffer = (WString)mem::temp_alloc_array(Char16, BUFFER_LEN);
buffer = win32::_wfullpath(buffer, path_str.to_temp_wstring()!, BUFFER_LEN);
if (!buffer) return PathResult.INVALID_PATH?;
return { string::new_from_wstring(buffer, allocator), WIN32 };
};
$else
String cwd = os::getcwd(allocator::temp())!;
return Path { cwd, self.env }.append(path_str, allocator)!;
$endif
}
fn String Path.basename(self)
{
usz basename_start = self.start_of_base_name();
String path_str = self.path_string;
if (basename_start == path_str.len) return "";
return path_str[basename_start..];
}
fn String Path.dirname(self)
{
usz basename_start = self.start_of_base_name();
String path_str = self.path_string;
if (basename_start == 0) return ".";
usz start = volume_name_len(path_str, self.env)!!;
if (basename_start <= start + 1)
{
if (self.env == WIN32 && basename_start > start && path_str[0..1] == `\\`)
{
return path_str[:basename_start - 1];
}
return path_str[:basename_start];
}
return path_str[:basename_start - 1];
}
fn String! Path.extension(self)
{
String basename = self.basename();
usz index = basename.rindex_of(".")!;
// Plain ".foo" does not have an
if (index == 0) return SearchResult.MISSING?;
if (index == basename.len) return "";
return basename[index + 1..];
}
fn String Path.volume_name(self)
{
usz len = volume_name_len(self.str_view(), self.env)!!;
if (!len) return "";
return self.path_string[:len];
}
fn usz! volume_name_len(String path, PathEnv path_env) @local
{
usz len = path.len;
if (len < 2 || path_env != PathEnv.WIN32) return 0;
switch (path[0])
{
case '\\':
// "\\" paths.. must be longer than 2
if (len == 2) return 0;
int count = 1;
while (count < len && path[count] == '\\') count++;
// Not 2 => folded paths
if (count != 2) return 0;
// Check that we have a name followed by '\'
isz base_found = 0;
for (usz i = 2; i < len; i++)
{
char c = path[i];
if (is_win32_separator(c))
{
if (base_found) return i;
base_found = i;
continue;
}
if (is_reserved_win32_path_char(c)) return PathResult.INVALID_PATH?;
}
if (base_found > 0 && base_found + 1 < len) return len;
return PathResult.INVALID_PATH?;
case 'A'..'Z':
case 'a'..'z':
return path[1] == ':' ? 2 : 0;
default:
return 0;
}
}
fn Path! Path.parent(self)
{
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 };
}
}
return PathResult.NO_PARENT?;
}
fn String! normalize(String path_str, PathEnv path_env = DEFAULT_PATH_ENV)
{
if (!path_str.len) return "";
usz path_start = volume_name_len(path_str, path_env)!;
if (path_start > 0 && path_env == PathEnv.WIN32)
{
for (usz i = 0; i < path_start; i++) if (path_str[i] == '/') path_str[i] = '\\';
}
usz path_len = path_str.len;
if (path_start == path_len) return path_str;
char path_separator = path_env == PathEnv.WIN32 ? PREFERRED_SEPARATOR_WIN32 : PREFERRED_SEPARATOR_POSIX;
usz len = path_start;
bool has_root = is_separator(path_str[path_start], path_env);
if (has_root)
{
path_str[len++] = path_separator;
path_start++;
}
// It is safe to write it as true, since we already dealt with /foo.
// This allows us to avoid checking whether it is the start of the path.
bool previous_was_separator = true;
for (usz i = path_start; i < path_len; i++)
{
char c = path_str[i];
// Fold foo///bar into foo/bar
if (is_separator(c, path_env))
{
// Fold //
if (previous_was_separator) continue;
// New /, so mark and rewrite
path_str.ptr[len++] = path_separator;
previous_was_separator = true;
continue;
}
// The rest are names of the path elements, so check that the
// characters are valid.
if (is_reserved_path_char(c, path_env)) return PathResult.INVALID_PATH?;
// If we have '.' after a separator
if (c == '.' && previous_was_separator)
{
// Get the number of dots until next separator, expecting 1 or 2
bool is_last = i == path_len - 1;
int dots = 1;
if (!is_last)
{
char next = path_str[i + 1];
switch
{
case next == '.':
dots = 2;
is_last = i == path_len - 2;
if (!is_last && !is_separator(path_str[i + 2], path_env))
{
dots = 0;
}
case !is_separator(next, path_env):
dots = 0;
}
}
switch (dots)
{
case 1:
// /./abc -> skip to /./abc
// ^ ^
i++;
continue;
case 2:
// This is an error: /a/../..
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 ||
(len - path_start >= 3 && path_str[len - 1] == path_separator
&& path_str[len - 3] == '.' && path_str[len - 3] == '.' &&
(len - 3 == 0 || path_str[len - 4] == path_separator)))
{
if (i != len)
{
path_str[len] = '.';
path_str[len + 1] = '.';
}
len += 2;
if (len < path_len) path_str[len++] = path_separator;
i += 2;
continue;
}
// Step back, now looking at '/' abc/def/. -> abc/def/
len--;
// Step back until finding a separator or the start.
while (len > path_start && !is_separator(path_str[len - 1], path_env))
{
len--;
}
// Reading, we go from /../abc to /../abc
// ^ ^
i += 2;
continue;
default:
break;
}
}
if (i != len) path_str[len] = c;
previous_was_separator = false;
len++;
}
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) return ".";
return path_str[:len];
}
fn ZString Path.as_zstr(self) => (ZString)self.path_string.ptr;
fn String Path.root_directory(self)
{
String path_str = self.str_view();
usz len = path_str.len;
if (!len) return "";
if (path_str == ".") return ".";
if (self.env == PathEnv.WIN32)
{
usz root_len = volume_name_len(path_str, self.env)!!;
if (root_len == len || !is_win32_separator(path_str[root_len])) return "";
return path_str[root_len..root_len];
}
if (!is_posix_separator(path_str[0])) return "";
for (usz i = 1; i < len; i++)
{
if (is_posix_separator(path_str[i]))
{
return path_str[:i];
}
}
return path_str;
}
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_PATH_ENV : "This method is only available on native paths"
*/
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(abs, .allocator = allocator)!;
foreach (f : files)
{
if (f.str_view() == "." || f.str_view() == "..") continue;
f = abs.append(f.str_view(), allocator)!;
bool is_directory = is_dir(f);
if (w(f, is_directory, data)!) return true;
if (is_directory && f.walk(w, data)!) return true;
}
};
return false;
}
fn String Path.str_view(self) @inline
{
return self.path_string;
}
fn bool Path.has_suffix(self, String str)
{
return self.str_view().ends_with(str);
}
fn void Path.free_with_allocator(self, Allocator allocator)
{
allocator::free(allocator, self.path_string.ptr);
}
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,
['/'] = true,
};
const bool[256] RESERVED_PATH_CHAR_WIN32 = {
[0..31] = true,
['>'] = true,
['<'] = true,
[':'] = true,
['\"'] = true,
['/'] = true,
['\\'] = true,
['|'] = true,
['?'] = true,
['*'] = true,
};
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_PATH_ENV)
{
return path_env == PathEnv.WIN32
? RESERVED_PATH_CHAR_WIN32[c]
: RESERVED_PATH_CHAR_POSIX[c];
}

View File

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

View File

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

View File

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

View File

@@ -1,68 +0,0 @@
module std::io;
struct ByteReader (InStream)
{
char[] bytes;
usz index;
}
fn usz ByteReader.len(&self) @dynamic
{
return self.bytes.len;
}
fn ByteReader* ByteReader.init(&self, char[] bytes)
{
*self = { .bytes = bytes };
return self;
}
fn usz! ByteReader.read(&self, char[] bytes) @dynamic
{
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);
self.index += len;
return len;
}
fn char! ByteReader.read_byte(&self) @dynamic
{
if (self.index >= self.bytes.len) return IoError.EOF?;
return self.bytes[self.index++];
}
fn void! ByteReader.pushback_byte(&self) @dynamic
{
if (!self.index) return IoError.INVALID_PUSHBACK?;
self.index--;
}
fn usz! ByteReader.seek(&self, isz offset, Seek seek) @dynamic
{
isz new_index;
switch (seek)
{
case SET: new_index = offset;
case CURSOR: new_index = self.index + offset;
case END: new_index = self.bytes.len + offset;
}
if (new_index < 0) return IoError.INVALID_POSITION?;
self.index = new_index;
return new_index;
}
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..])!;
self.index += written;
assert(self.index <= self.bytes.len);
return written;
}
fn usz! ByteReader.available(&self) @inline @dynamic
{
return max(0, self.bytes.len - self.index);
}

View File

@@ -1,113 +0,0 @@
module std::io;
import std::math;
struct ByteWriter (OutStream)
{
char[] bytes;
usz index;
Allocator allocator;
}
/**
* @param [&inout] self
* @param [&inout] allocator
* @require self.bytes.len == 0 "Init may not run on on already initialized data"
* @ensure (bool)allocator, self.index == 0
**/
fn ByteWriter* ByteWriter.new_init(&self, Allocator allocator = allocator::heap())
{
*self = { .bytes = {}, .allocator = allocator };
return self;
}
/**
* @param [&inout] self
* @require self.bytes.len == 0 "Init may not run on on already initialized data"
* @ensure self.index == 0
**/
fn ByteWriter* ByteWriter.temp_init(&self)
{
return self.new_init(allocator::temp()) @inline;
}
fn ByteWriter* ByteWriter.init_with_buffer(&self, char[] data)
{
*self = { .bytes = data, .allocator = null };
return self;
}
fn void! ByteWriter.destroy(&self) @dynamic
{
if (!self.allocator) return;
if (void* ptr = self.bytes.ptr) allocator::free(self.allocator, ptr);
*self = { };
}
fn String ByteWriter.str_view(&self) @inline
{
return (String)self.bytes[:self.index];
}
fn void! ByteWriter.ensure_capacity(&self, usz len) @inline
{
if (self.bytes.len > len) return;
if (!self.allocator) return IoError.OUT_OF_SPACE?;
if (len < 16) len = 16;
usz new_capacity = math::next_power_of_2(len);
char* new_ptr = allocator::realloc_try(self.allocator, self.bytes.ptr, new_capacity)!;
self.bytes = new_ptr[:new_capacity];
}
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);
self.index += bytes.len;
return bytes.len;
}
fn void! ByteWriter.write_byte(&self, char c) @dynamic
{
self.ensure_capacity(self.index + 1)!;
self.bytes[self.index++] = c;
}
/**
* @param [&inout] self
* @param reader
**/
fn usz! ByteWriter.read_from(&self, InStream reader) @dynamic
{
usz start_index = self.index;
if (&reader.available)
{
while (usz available = reader.available()!)
{
self.ensure_capacity(self.index + available)!;
usz read = reader.read(self.bytes[self.index..])!;
self.index += read;
}
return self.index - start_index;
}
if (self.bytes.len == 0)
{
self.ensure_capacity(16)!;
}
while (true)
{
// See how much we can read.
usz len_to_read = self.bytes.len - self.index;
// Less than 16 bytes? Double the capacity
if (len_to_read < 16)
{
self.ensure_capacity(self.bytes.len * 2)!;
len_to_read = self.bytes.len - self.index;
}
// Read into the rest of the buffer
usz read = reader.read(self.bytes[self.index..])!;
self.index += read;
// Ok, we reached the end.
if (read < len_to_read) return self.index - start_index;
// Otherwise go another round
}
}

View File

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

View File

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

929
lib/std/io_printf.c3 Normal file
View File

@@ -0,0 +1,929 @@
module std::io;
import libc;
const int PRINTF_NTOA_BUFFER_SIZE = 256;
const int PRINTF_FTOA_BUFFER_SIZE = 256;
const float PRINTF_MAX_FLOAT = 1e9;
const uint PRINTF_DEFAULT_FLOAT_PRECISION = 6;
fault PrintFault
{
BUFFER_EXCEEDED,
INTERNAL_BUFFER_EXCEEDED,
INVALID_FORMAT_STRING,
MISSING_ARG,
}
bitstruct PrintFlags : uint
{
bool zeropad : 0;
bool left : 1;
bool plus : 2;
bool space : 3;
bool hash : 4;
bool uppercase : 5;
bool precision : 6;
bool adapt_exp : 7;
}
struct PrintParam
{
OutputFn outfn;
void* buffer;
PrintFlags flags;
uint width;
uint prec;
usize idx;
}
define OutputFn = fn void!(char c, void* buffer, usize buffer_idx);
fn void! PrintParam.out(PrintParam* param, char c)
{
param.outfn(c, param.buffer, param.idx++)?;
}
private fn void! out_str(PrintParam* param, variant arg)
{
switch (arg.type.kind)
{
case TYPEID:
return out_substr(param, "<typeid>");
case VOID:
return out_substr(param, "void");
case ANYERR:
case FAULT:
return out_substr(param, (*(anyerr*)arg.ptr).nameof);
case VARIANT:
return out_substr(param, "<variant>");
case ENUM:
return out_substr(param, arg.type.names[types::variant_to_int(arg, usize)!!]);
case STRUCT:
return out_substr(param, "<struct>");
case UNION:
return out_substr(param, "<union>");
case BITSTRUCT:
return out_substr(param, "<bitstruct>");
case FUNC:
return out_substr(param, "<func>");
case FAILABLE:
unreachable();
case DISTINCT:
if (arg.type == String.typeid)
{
return out_substr(param, ((String*)arg).str());
}
return out_str(param, variant { arg.ptr, arg.type.inner });
case POINTER:
typeid inner = arg.type.inner;
if (inner.kind == TypeKind.ARRAY && inner.inner == char.typeid)
{
char *ptr = *(char**)arg.ptr;
return out_substr(param, ptr[:inner.len]);
}
return ntoa_variant(param, arg, 16);
case SIGNED_INT:
case UNSIGNED_INT:
return ntoa_variant(param, arg, 10);
case FLOAT:
return ftoa(param, float_from_variant(arg));
case ARRAY:
// this is SomeType[*] so grab the "SomeType"
typeid inner = arg.type.inner;
usize size = inner.sizeof;
usize len = arg.type.len;
// Pretend this is a char[]
void* ptr = (void*)arg.ptr;
param.out('[')?;
for (usize i = 0; i < len; i++)
{
if (i != 0) out_substr(param, ", ")?;
out_str(param, variant { ptr, inner })?;
ptr += size;
}
return param.out(']');
case VECTOR:
// this is SomeType[*] so grab the "SomeType"
typeid inner = arg.type.inner;
usize size = inner.sizeof;
usize len = arg.type.len;
// Pretend this is a char[]
void* ptr = (void*)arg.ptr;
out_substr(param, "[<")?;
for (usize i = 0; i < len; i++)
{
if (i != 0) out_substr(param, ", ")?;
out_str(param, variant { ptr, inner })?;
ptr += size;
}
return out_substr(param, ">]");
case SUBARRAY:
// this is SomeType[] so grab the "SomeType"
typeid inner = arg.type.inner;
if (inner == char.typeid)
{
return out_substr(param, *(char[]*)arg);
}
usize size = inner.sizeof;
// Pretend this is a char[]
char[]* temp = (void*)arg.ptr;
void* ptr = (void*)temp.ptr;
usize len = temp.len;
param.out('[')?;
for (usize i = 0; i < len; i++)
{
if (i != 0) out_substr(param, ", ")?;
out_str(param, variant { ptr, inner })?;
ptr += size;
}
param.out(']')?;
case BOOL:
if (*(bool*)arg.ptr)
{
return out_substr(param, "true");
}
else
{
return out_substr(param, "false");
}
default:
return out_substr(param, "Invalid type");
}
}
private fn uint simple_atoi(char* buf, usize maxlen, usize* len_ptr) @inline
{
uint i = 0;
usize len = *len_ptr;
while (len < maxlen)
{
char c = buf[len];
if (c < '0' || c > '9') break;
i = i * 10 + c - '0';
len++;
}
*len_ptr = len;
return i;
}
fault FormattingFault
{
UNTERMINATED_FORMAT,
MISSING_ARG,
INVALID_WIDTH_ARG,
INVALID_FORMAT_TYPE,
}
private fn void! printf_advance_format(usize format_len, usize *index_ptr) @inline
{
usize val = ++(*index_ptr);
if (val >= format_len) return FormattingFault.UNTERMINATED_FORMAT!;
}
private fn variant! next_variant(variant* args_ptr, usize args_len, usize* arg_index_ptr) @inline
{
if (*arg_index_ptr >= args_len) return FormattingFault.MISSING_ARG!;
return args_ptr[(*arg_index_ptr)++];
}
private fn int! printf_parse_format_field(variant* args_ptr, usize args_len, usize* args_index_ptr, char* format_ptr, usize format_len, usize* index_ptr) @inline
{
char c = format_ptr[*index_ptr];
if (c >= '0' && c <= '9') return simple_atoi(format_ptr, format_len, index_ptr);
if (c != '*') return 0;
printf_advance_format(format_len, index_ptr)?;
variant val = next_variant(args_ptr, args_len, args_index_ptr)?;
if (!val.type.kind.is_int()) return FormattingFault.INVALID_WIDTH_ARG!;
uint! intval = types::variant_to_int(val, int);
if (catch intval) return FormattingFault.INVALID_WIDTH_ARG!;
return intval;
}
private fn void! out_buffer_fn(char c, char[] buffer, usize buffer_idx)
{
if (buffer_idx >= buffer.len) return PrintFault.BUFFER_EXCEEDED!;
buffer[buffer_idx] = c;
}
private fn void! out_null_fn(char c @unused, void* data @unused, usize idx @unused)
{
}
private fn void! out_putchar_fn(char c, void* data @unused, usize idx @unused)
{
libc::putchar(c);
}
private fn void! out_fputchar_fn(char c, void* data, usize idx @unused)
{
File* f = data;
f.putc(c)?;
}
private fn void! out_string_append_fn(char c, void* data, usize idx @unused)
{
String* s = data;
s.append_char(c);
}
private fn void! PrintParam.out_reverse(PrintParam* param, char[] buf)
{
usize buffer_start_idx = param.idx;
usize len = buf.len;
// pad spaces up to given width
if (!param.flags.left && !param.flags.zeropad)
{
for (usize i = len; i < param.width; i++)
{
param.out(' ')?;
}
}
// reverse string
while (len) param.out(buf[--len])?;
// append pad spaces up to given width
return param.left_adjust(param.idx - buffer_start_idx);
}
private fn void! out_char(PrintParam* param, variant arg)
{
uint l = 1;
// pre padding
param.right_adjust(l)?;
// char output
Char32 c = types::variant_to_int(arg, uint) ?? 0xFFFD;
switch (true)
{
case c < 0x7f:
param.out((char)c)?;
case c < 0x7ff:
param.out((char)(0xC0 | c >> 6))?;
param.out((char)(0x80 | (c & 0x3F)))?;
case c < 0xffff:
param.out((char)(0xE0 | c >> 12))?;
param.out((char)(0x80 | (c >> 6 & 0x3F)))?;
param.out((char)(0x80 | (c & 0x3F)))?;
default:
param.out((char)(0xF0 | c >> 18))?;
param.out((char)(0x80 | (c >> 12 & 0x3F)))?;
param.out((char)(0x80 | (c >> 6 & 0x3F)))?;
param.out((char)(0x80 | (c & 0x3F)))?;
}
return param.left_adjust(l);
}
private fn void! ntoa_format(PrintParam* param, char[] buf, usize len, bool negative, uint base)
{
// pad leading zeros
if (!param.flags.left)
{
if (param.width && param.flags.zeropad && (negative || param.flags.plus || param.flags.space)) param.width--;
while (len < param.prec)
{
if (len >= buf.len) return PrintFault.INTERNAL_BUFFER_EXCEEDED!;
buf[len++] = '0';
}
while (param.flags.zeropad && len < param.width)
{
if (len >= buf.len) return PrintFault.INTERNAL_BUFFER_EXCEEDED!;
buf[len++] = '0';
}
}
// handle hash
if (param.flags.hash && base != 10)
{
if (!param.flags.precision && len && len == param.prec && len == param.width)
{
len--;
if (len) len--;
}
if (base != 10)
{
if (len + 1 >= buf.len) return PrintFault.INTERNAL_BUFFER_EXCEEDED!;
switch (base)
{
case 16:
buf[len++] = param.flags.uppercase ? 'X' : 'x';
case 8:
buf[len++] = param.flags.uppercase ? 'O' : 'o';
case 2:
buf[len++] = param.flags.uppercase ? 'B' : 'b';
default:
unreachable();
}
buf[len++] = '0';
}
}
switch (true)
{
case negative:
if (len >= buf.len) return PrintFault.INTERNAL_BUFFER_EXCEEDED!;
buf[len++] = '-';
case param.flags.plus:
if (len >= buf.len) return PrintFault.INTERNAL_BUFFER_EXCEEDED!;
buf[len++] = '+';
case param.flags.space:
if (len >= buf.len) return PrintFault.INTERNAL_BUFFER_EXCEEDED!;
buf[len++] = ' ';
}
if (!len) return;
return param.out_reverse(buf[:len]);
}
$if (env::I128_SUPPORT):
define NtoaType = uint128;
$else:
define NtoaType = ulong;
$endif;
private fn void! ntoa_variant(PrintParam* param, variant arg, uint base)
{
bool is_neg;
NtoaType val = int_from_variant(arg, &is_neg);
return ntoa(param, val, is_neg, base) @inline;
}
private fn void! ntoa(PrintParam* param, NtoaType value, bool negative, uint base)
{
char[PRINTF_NTOA_BUFFER_SIZE] buf = void;
usize len = 0;
// no hash for 0 values
if (!value) param.flags.hash = false;
// write if precision != 0 or value is != 0
if (!param.flags.precision || value)
{
char past_10 = (param.flags.uppercase ? 'A' : 'a') - 10;
do
{
if (len >= PRINTF_NTOA_BUFFER_SIZE) return PrintFault.INTERNAL_BUFFER_EXCEEDED!;
char digit = (char)(value % base);
buf[len++] = digit + (digit < 10 ? '0' : past_10);
value /= base;
}
while (value);
}
return ntoa_format(param, buf[:PRINTF_NTOA_BUFFER_SIZE], len, negative, base);
}
define FloatType = double;
// internal ftoa for fixed decimal floating point
private fn void! ftoa(PrintParam* param, FloatType value)
{
char[PRINTF_FTOA_BUFFER_SIZE] buf = void;
usize len = 0;
const FloatType[] POW10 = { 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000 };
FloatType diff = 0.0;
// powers of 10
// test for special values
if (value != value)
{
return param.out_reverse("nan");
}
if (value < -FloatType.max)
{
return param.out_reverse("fni-");
}
if (value > FloatType.max)
{
if (param.flags.plus)
{
return param.out_reverse("fni+");
}
return param.out_reverse("fni");
}
// test for very large values
// standard printf behavior is to print EVERY whole number digit -- which could be 100s of characters overflowing your buffers == bad
if (value > PRINTF_MAX_FLOAT || value < -PRINTF_MAX_FLOAT)
{
return etoa(param, value);
}
// test for negative
bool negative = value < 0;
if (negative) value = 0 - value;
// set default precision, if not set explicitly
if (!param.flags.precision) param.prec = PRINTF_DEFAULT_FLOAT_PRECISION;
// limit precision to 9, cause a prec >= 10 can lead to overflow errors
while (param.prec > 9)
{
if (len >= PRINTF_FTOA_BUFFER_SIZE) return PrintFault.INTERNAL_BUFFER_EXCEEDED!;
buf[len++] = '0';
param.prec--;
}
// Safe due to 1e9 limit.
int whole = (int)value;
FloatType tmp = (value - whole) * POW10[param.prec];
ulong frac = (ulong)tmp;
diff = tmp - frac;
switch (true)
{
case diff > 0.5:
++frac;
// handle rollover, e.g. case 0.99 with prec 1 is 1.0
if (frac >= POW10[param.prec])
{
frac = 0;
++whole;
}
case diff < 0.5:
break;
case !frac && (frac & 1):
// if halfway, round up if odd OR if last digit is 0
++frac;
}
if (!param.prec)
{
diff = value - (FloatType)whole;
if ((!(diff < 0.5) || diff > 0.5) && (whole & 1))
{
// exactly 0.5 and ODD, then round up
// 1.5 -> 2, but 2.5 -> 2
++whole;
}
}
else
{
uint count = param.prec;
// now do fractional part, as an unsigned number
do
{
if (len >= PRINTF_FTOA_BUFFER_SIZE) return PrintFault.INTERNAL_BUFFER_EXCEEDED!;
--count;
buf[len++] = (char)(48 + (frac % 10));
}
while (frac /= 10);
// add extra 0s
while (count-- > 0)
{
if (len >= PRINTF_FTOA_BUFFER_SIZE) return PrintFault.INTERNAL_BUFFER_EXCEEDED!;
buf[len++] = '0';
}
if (len >= PRINTF_FTOA_BUFFER_SIZE) return PrintFault.INTERNAL_BUFFER_EXCEEDED!;
// add decimal
buf[len++] = '.';
}
// do whole part, number is reversed
do
{
if (len >= PRINTF_FTOA_BUFFER_SIZE) return PrintFault.INTERNAL_BUFFER_EXCEEDED!;
buf[len++] = (char)(48 + (whole % 10));
}
while (whole /= 10);
// pad leading zeros
if (!param.flags.left && param.flags.zeropad)
{
if (param.width && (negative || param.flags.plus || param.flags.space)) param.width--;
while (len < param.width)
{
if (len >= PRINTF_FTOA_BUFFER_SIZE) return PrintFault.INTERNAL_BUFFER_EXCEEDED!;
buf[len++] = '0';
}
}
char next = {|
if (negative) return '-';
if (param.flags.plus) return '+';
if (param.flags.space) return ' ';
return 0;
|};
if (next)
{
if (len >= PRINTF_FTOA_BUFFER_SIZE) return PrintFault.INTERNAL_BUFFER_EXCEEDED!;
buf[len++] = next;
}
return param.out_reverse(buf[:len]);
}
union ConvUnion
{
ulong u;
double f;
}
private fn void! etoa(PrintParam* param, FloatType value)
{
// check for NaN and special values
if (value != value || value < FloatType.min || value > FloatType.max)
{
return ftoa(param, value);
}
// determine the sign
bool negative = value < 0;
if (negative) value = -value;
// default precision
if (!param.flags.precision)
{
param.prec = PRINTF_DEFAULT_FLOAT_PRECISION;
}
// determine the decimal exponent
// based on the algorithm by David Gay (https://www.ampl.com/netlib/fp/dtoa.c)
ConvUnion conv;
conv.f = (double)value;
int exp2 = (int)(conv.u >> 52 & 0x7FF) - 1023; // effectively log2
conv.u = (conv.u & (1u64 << 52 - 1)) | (1023u64 << 52); // drop the exponent so conv.F is now in [1,2)
// now approximate log10 from the log2 integer part and an expansion of ln around 1.5
int expval = (int)(0.1760912590558 + exp2 * 0.301029995663981 + (conv.f - 1.5) * 0.289529654602168);
// now we want to compute 10^expval but we want to be sure it won't overflow
exp2 = (int)(expval * 3.321928094887362 + 0.5);
double z = expval * 2.302585092994046 - exp2 * 0.6931471805599453;
double z2 = z * z;
conv.u = (ulong)(exp2 + 1023) << 52;
// compute exp(z) using continued fractions, see https://en.wikipedia.org/wiki/Exponential_function#Continued_fractions_for_ex
conv.f *= 1 + 2 * z / (2 - z + (z2 / (6 + (z2 / (10 + z2 / 14)))));
// correct for rounding errors
if (value < conv.f)
{
expval--;
conv.f /= 10;
}
// the exponent format is "%+03d" and largest value is "307", so set aside 4-5 characters
uint minwidth = ((expval < 100) && (expval > -100)) ? 4 : 5;
// in "%g" mode, "prec" is the number of *significant figures* not decimals
if (param.flags.adapt_exp)
{
// do we want to fall-back to "%f" mode?
if (value >= 1e-4 && value < 1e6)
{
param.prec = param.prec > expval ? param.prec - expval - 1 : 0;
param.flags.precision = true; // make sure ftoa respects precision
// no characters in exponent
minwidth = 0;
expval = 0;
}
else
{
// we use one sigfig for the whole part
if (param.prec > 0 && param.flags.precision) param.prec--;
}
}
// Adjust width
uint fwidth = param.width > minwidth ? param.width - minwidth : 0;
// if we're padding on the right, DON'T pad the floating part
if (param.flags.left && minwidth) fwidth = 0;
// rescale the float value
if (expval) value /= conv.f;
// output the floating part
usize start_idx = param.idx;
PrintFlags old = param.flags;
param.flags.adapt_exp = false;
param.width = fwidth;
ftoa(param, negative ? -value : value)?;
param.flags = old;
// output the exponent part
if (minwidth)
{
// output the exponential symbol
param.out(param.flags.uppercase ? 'E' : 'e')?;
// output the exponent value
param.flags = { .zeropad = true, .plus = true };
param.width = minwidth - 1;
param.prec = 0;
ntoa(param, (NtoaType)(expval < 0 ? -expval : expval), expval < 0, 10)?;
param.flags = old;
// might need to right-pad spaces
param.left_adjust(param.idx - start_idx)?;
}
}
private fn FloatType float_from_variant(variant arg)
{
$if (env::I128_SUPPORT):
switch (arg)
{
case int128:
return *arg;
case uint128:
return *arg;
}
$endif;
$if (env::F128_SUPPORT):
if (arg.type == float128.typeid) return *((float128*)arg.ptr);
$endif;
$if (env::F16_SUPPORT):
if (arg.type == float16.typeid) return *((float16*)arg.ptr);
$endif;
if (arg.type.kind == TypeKind.POINTER)
{
return (FloatType)(uptr)(void*)arg.ptr;
}
switch (arg)
{
case bool:
return (FloatType)*arg;
case ichar:
return *arg;
case short:
return *arg;
case int:
return *arg;
case long:
return *arg;
case char:
return *arg;
case ushort:
return *arg;
case uint:
return *arg;
case ulong:
return *arg;
case float:
return (FloatType)*arg;
case double:
return (FloatType)*arg;
default:
return 0;
}
}
private fn NtoaType int_from_variant(variant arg, bool *is_neg)
{
*is_neg = false;
$if (NtoaType.typeid == uint128.typeid):
switch (arg)
{
case int128:
int128 val = *arg;
return (*is_neg = val < 0) ? (~(NtoaType)val) + 1 : val;
case uint128:
return *arg;
}
$endif;
if (arg.type.kind == TypeKind.POINTER)
{
return (NtoaType)(uptr)*(void**)arg.ptr;
}
switch (arg)
{
case bool:
return (NtoaType)*arg;
case ichar:
int val = *arg;
return (*is_neg = val < 0) ? (~(NtoaType)val) + 1 : (NtoaType)val;
case short:
int val = *arg;
return (*is_neg = val < 0) ? (~(NtoaType)val) + 1 : (NtoaType)val;
case int:
int val = *arg;
return (*is_neg = val < 0) ? (~(NtoaType)val) + 1 : (NtoaType)val;
case long:
long val = *arg;
return (*is_neg = val < 0) ? (~(NtoaType)val) + 1 : (NtoaType)val;
case char:
return *arg;
case ushort:
return *arg;
case uint:
return *arg;
case ulong:
return *arg;
case float:
float f = *arg;
return (NtoaType)((*is_neg = f < 0) ? -f : f);
case double:
double d = *arg;
return (NtoaType)((*is_neg = d < 0) ? -d : d);
default:
return 0;
}
}
fn usize! printf(char[] format, args...) @maydiscard
{
return vsnprintf(&out_putchar_fn, null, format, args);
}
fn usize! printfln(char[] format, args...) @maydiscard
{
usize size = vsnprintf(&out_putchar_fn, null, format, args)?;
putchar('\n');
return size + 1;
}
fn usize! String.printf(String* str, char[] format, args...) @maydiscard
{
return vsnprintf(&out_string_append_fn, str, format, args);
}
fn usize! String.printfln(String* str, char[] format, args...) @maydiscard
{
usize size = vsnprintf(&out_string_append_fn, str, format, args)?;
str.append('\n');
return size + 1;
}
fn usize! File.printf(File file, char[] format, args...) @maydiscard
{
return vsnprintf(&out_putchar_fn, &file, format, args);
}
fn usize! File.printfln(File file, char[] format, args...) @maydiscard
{
usize size = vsnprintf(&out_putchar_fn, &file, format, args)?;
file.putc('\n')?;
file.flush();
return size + 1;
}
private fn void! PrintParam.left_adjust(PrintParam* param, usize len)
{
if (!param.flags.left) return;
for (usize l = len; l < param.width; l++) param.out(' ')?;
}
private fn void! PrintParam.right_adjust(PrintParam* param, usize len)
{
if (param.flags.left) return;
for (usize l = len; l < param.width; l++) param.out(' ')?;
}
private fn void! out_substr(PrintParam* param, char[] str)
{
usize l = conv::utf8_codepoints(str);
uint prec = param.prec;
if (param.flags.precision && l < prec) l = prec;
param.right_adjust(' ')?;
usize index = 0;
usize chars = str.len;
char* ptr = str.ptr;
while (index < chars)
{
char c = ptr[index];
// Break if we have precision set and we ran out...
if (c & 0xC0 != 0x80 && param.flags.precision && !prec--) break;
param.out(c)?;
index++;
}
return param.left_adjust(l);
}
private fn usize! vsnprintf(OutputFn out, void* data, char[] format, variant[] variants)
{
if (!out)
{
// use null output function
out = &out_null_fn;
}
PrintParam param = { .outfn = out, .buffer = data };
usize format_len = format.len;
usize variant_index = 0;
for (usize i = 0; i < format_len; i++)
{
// format specifier? %[flags][width][.precision][length]
char c = format[i];
if (c != '%')
{
// no
param.out(c)?;
continue;
}
i++;
if (i >= format_len) return PrintFault.INVALID_FORMAT_STRING!;
c = format[i];
if (c == '%')
{
param.out(c)?;
continue;
}
// evaluate flags
param.flags = {};
while FLAG_EVAL: (true)
{
switch (c)
{
case '0': param.flags.zeropad = true;
case '-': param.flags.left = true;
case '+': param.flags.plus = true;
case ' ': param.flags.space = true;
case '#': param.flags.hash = true;
default: break FLAG_EVAL;
}
if (++i >= format_len) return PrintFault.INVALID_FORMAT_STRING!;
c = format[i];
}
// evaluate width field
int w = printf_parse_format_field(variants.ptr, variants.len, &variant_index, format.ptr, format.len, &i)?;
c = format[i];
if (w < 0)
{
param.flags.left = true;
w = -w;
}
param.width = w;
// evaluate precision field
param.prec = 0;
if (c == '.')
{
param.flags.precision = true;
if (++i >= format_len) return PrintFault.INVALID_FORMAT_STRING!;
int prec = printf_parse_format_field(variants.ptr, variants.len, &variant_index, format.ptr, format.len, &i)?;
param.prec = prec < 0 ? 0 : prec;
c = format[i];
}
// evaluate specifier
uint base = 0;
if (variant_index >= variants.len) return PrintFault.MISSING_ARG!;
variant current = variants[variant_index++];
switch (c)
{
case 'd':
base = 10;
param.flags.hash = false;
case 'X' :
param.flags.uppercase = true;
nextcase;
case 'x' :
base = 16;
case 'O':
param.flags.uppercase = true;
nextcase;
case 'o' :
base = 8;
case 'B':
param.flags.uppercase = true;
nextcase;
case 'b' :
base = 2;
case 'F' :
param.flags.uppercase = true;
nextcase;
case 'f':
ftoa(&param, float_from_variant(current))?;
continue;
case 'E':
param.flags.uppercase = true;
nextcase;
case 'e':
etoa(&param, float_from_variant(current))?;
continue;
case 'G':
param.flags.uppercase = true;
nextcase;
case 'g':
param.flags.adapt_exp = true;
etoa(&param, float_from_variant(current))?;
continue;
case 'c':
out_char(&param, current)?;
continue;
case 's':
out_str(&param, current)?;
continue;
case 'p':
param.flags.zeropad = true;
param.flags.hash = true;
base = 16;
default:
return PrintFault.INVALID_FORMAT_STRING!;
}
if (base != 10)
{
param.flags.plus = false;
param.flags.space = false;
}
// ignore '0' flag when precision is given
if (param.flags.precision) param.flags.zeropad = false;
bool is_neg;
NtoaType v = int_from_variant(current, &is_neg);
ntoa(&param, v, is_neg, base)?;
}
// termination
// out((char)0, buffer, idx < maxlen ? idx : maxlen - 1U, maxlen);
// return written chars without terminating \0
return param.idx;
}

368
lib/std/libc.c3 Normal file
View File

@@ -0,0 +1,368 @@
// Copyright (c) 2021 Christoffer Lerno. All rights reserved.
// Use of this source code is governed by the MIT license
// a copy of which can be found in the LICENSE_STDLIB file.
module libc;
// stdlib
// Constants need to be per os/arch
const int EXIT_FAILURE = 1;
const int EXIT_SUCCESS = 0;
const int RAND_MAX = 0x7fffffff;
struct DivResult
{
int quot;
int rem;
}
struct LongDivResult
{
long quot;
long rem;
}
fn Errno errno()
{
$if (env::OS_TYPE == OsType.WIN32):
return (Errno)windows::errno();
$elif (env::OS_TYPE == OsType.MACOSX):
return (Errno)macos::errno();
$elif (env::OS_TYPE == OsType.LINUX):
return (Errno)linux::errno();
$else:
return errno::ENOTRECOVERABLE;
$endif;
}
define TerminateFunction = fn void();
define CompareFunction = fn int(void*, void*);
extern fn double atof(char* str);
extern fn int atoi(char* str);
extern fn CLongLong atoll(char* str);
extern fn double strtod(char* str, char** endptr);
extern fn CLong strtol(char* str, char** endptr, int base);
extern fn CULong stroul(char* str, char** endptr, int base);
extern fn void abort();
extern fn void atexit(TerminateFunction f);
extern fn void exit(int status);
extern fn char* getenv(char* name);
extern fn int system(char* str);
extern fn void bsearch(void* key, void *base, usize items, usize size, CompareFunction compare);
extern fn void qsort(void* base, usize items, usize size, CompareFunction compare);
extern fn int abs(int x);
extern fn DivResult div(int numer, int denom);
extern fn long labs(long x);
extern fn LongDivResult ldiv(long number, long denom);
extern fn int rand();
extern fn void srand(uint seed);
// MB functions omitted
// string
extern fn void* memchr(void* str, int c, usize n);
extern fn int memcmp(void* str1, void* str2, usize n);
extern fn void* memcpy(void* dest, void* src, usize n);
extern fn void* memmove(void* dest, void* src, usize n);
extern fn void* memset(void* dest, usize n);
extern fn char* strcat(char* dest, char* src);
extern fn char* strncat(char* dest, char* src, usize n);
extern fn char* strchr(char* str, int c);
extern fn int strcmp(char* str1, char* str2);
extern fn int strncmp(char* str1, char* str2, usize n);
extern fn int strcoll(char* str1, char* str2);
extern fn char* strcpy(char* dst, char* src);
extern fn char* strncpy(char* dst, char* src, usize n);
extern fn usize strcspn(char* str1, char* str2);
extern fn char* strerror(int errn);
extern fn usize strlen(char* str);
extern fn char* strpbrk(char* str1, char* str2);
extern fn usize strspn(char* str1, char* str2);
extern fn char* strstr(char* haystack, char* needle);
extern fn char* strtok(char* str, char* delim);
extern fn usize strxfrm(char* dest, char* src, usize n);
// malloc
extern fn void* malloc(usize size);
extern fn void* calloc(usize count, usize size);
extern fn void* free(void*);
extern fn void* realloc(void* ptr, usize size);
// stdio
define Fpos = long;
define CFile = void*;
$switch (env::OS_TYPE):
$case OsType.LINUX:
extern CFile __stdin @extname("stdin");
extern CFile __stdout @extname("stdout");
extern CFile __stderr @extname("stderr");
extern fn usize malloc_usable_size(void* ptr);
macro usize malloc_size(void* ptr) { return malloc_usable_size(ptr); }
extern fn void* aligned_alloc(usize align, usize size);
macro CFile stdin() { return __stdin; }
macro CFile stdout() { return __stdout; }
macro CFile stderr() { return __stderr; }
$case OsType.MACOSX:
extern CFile __stdinp;
extern CFile __stdoutp;
extern CFile __stderrp;
extern fn usize malloc_size(void* ptr);
extern fn void* aligned_alloc(usize align, usize size);
macro CFile stdin() { return __stdinp; }
macro CFile stdout() { return __stdoutp; }
macro CFile stderr() { return __stderrp; }
$case OsType.WIN32:
extern fn CFile __acrt_iob_func(CInt c);
extern fn usize _msize(void* ptr);
macro usize malloc_size(void* ptr) { return _msize(ptr); }
macro CFile stdin() { return __acrt_iob_func(0); }
macro CFile stdout() { return __acrt_iob_func(1); }
macro CFile stderr() { return __acrt_iob_func(2); }
$default:
macro CFile stdin() { return (CFile*)(uptr)0; }
macro CFile stdout() { return (CFile*)(uptr)1; }
macro CFile stderr() { return (CFile*)(uptr)2; }
$endswitch;
const HAS_MALLOC_SIZE =
env::OS_TYPE == OsType.LINUX
|| env::OS_TYPE == OsType.WIN32
|| env::OS_TYPE == OsType.MACOSX;
// The following needs to be set per arch+os
// For now I have simply pulled the defaults from MacOS
const int SEEK_SET = 0;
const int SEEK_CUR = 1;
const int SEEK_END = 2;
const int _IOFBF = 0; // Fully buffered
const int _IOLBF = 1; // Line buffered
const int _IONBF = 2; // Unbuffered
const int BUFSIZ = 1024;
const int EOF = -1;
const int FOPEN_MAX = 20;
const int FILENAME_MAX = 1024;
define Errno = distinct CInt;
define SeekIndex = CLong;
extern fn int fclose(CFile stream);
extern fn void clearerr(CFile stream);
extern fn int feof(CFile stream);
extern fn int ferror(CFile stream);
extern fn int fflush(CFile stream);
extern fn int fgetpos(CFile stream, Fpos* pos);
extern fn CFile fopen(char* filename, char* mode);
extern fn usize fread(void* ptr, usize size, usize nmemb, CFile stream);
extern fn CFile freopen(char* filename, char* mode, CFile stream);
extern fn int fseek(CFile stream, SeekIndex offset, int whence);
extern fn int fsetpos(CFile stream, Fpos* pos);
extern fn SeekIndex ftell(CFile stream);
extern fn usize fwrite(void* ptr, usize size, usize nmemb, CFile stream);
extern fn int remove(char* filename);
extern fn int rename(char* old_name, char* new_name);
extern fn void rewind(CFile stream);
extern fn void setbuf(CFile stream, char* buffer);
extern fn void setvbuf(CFile stream, char* buffer, int mode, usize size);
extern fn CFile tmpnam(char* str);
extern fn int fprintf(CFile stream, char* format, ...);
extern fn int printf(char* format, ...);
extern fn int sprintf(char* str, char* format, ...);
extern fn int snprintf(char* str, usize size, char* format, ...);
extern fn int fscanf(CFile stream, char* format, ...);
extern fn int scanf(char* format, ...);
extern fn int sscanf(char* str, char* format, ...);
extern fn int fgetc(CFile stream);
extern fn char* fgets(char* str, int n, CFile stream);
extern fn int fputc(int c, CFile stream);
extern fn int getc(CFile stream);
extern fn int getchar();
extern fn int putc(char c, CFile stream);
extern fn int putchar(int c);
extern fn int puts(char* str);
extern fn int ungetc(int c, CFile stream);
extern fn void perror(char* str);
extern fn isize getline(char** linep, usize* linecapp, CFile stream);
// vsprintf vprintf not supported
// time.h
define TimeOffset = CLong;
struct Tm
{
int tm_sec; /* seconds after the minute [0-60] */
int tm_min; /* minutes after the hour [0-59] */
int tm_hour; /* hours since midnight [0-23] */
int tm_mday; /* day of the month [1-31] */
int tm_mon; /* months since January [0-11] */
int tm_year; /* years since 1900 */
int tm_wday; /* days since Sunday [0-6] */
int tm_yday; /* days since January 1 [0-365] */
int tm_isdst; /* Daylight Savings Time flag */
TimeOffset tm_gmtoff; /* offset from UTC in seconds */
char *tm_zone; /* timezone abbreviation */
}
// Likely wrong, must be per platform.
const CLOCKS_PER_SEC = 1000000;
// Time also needs to be per platform
define Time = long;
define Clock = ulong;
extern fn char* asctime(Tm *timeptr);
extern fn Clock clock();
extern fn char* ctime(Time *timer);
extern fn double difftime(Time time1, Time time2);
extern fn Tm* gmtime(Time *timer);
extern fn Tm* localtime(Time *timer);
extern fn Time mktime(Tm *timeptr);
extern fn usize strftime(char* str, usize maxsize, char* format, Tm *timeptr);
extern fn Time time(Time *timer);
// signal
define SignalFunction = fn void(int);
extern fn SignalFunction signal(int sig, SignalFunction function);
// Incomplete
module libc::errno;
const Errno EPERM = 1; /* Operation not permitted */
const Errno ENOENT = 2; /* No such file or directory */
const Errno ESRCH = 3; /* No such process */
const Errno EINTR = 4; /* Interrupted system call */
const Errno EIO = 5; /* I/O error */
const Errno ENXIO = 6; /* No such device or address */
const Errno E2BIG = 7; /* Argument list too long */
const Errno ENOEXEC = 8; /* Exec format error */
const Errno EBADF = 9; /* Bad file number */
const Errno ECHILD = 10; /* No child processes */
const Errno EAGAIN = 11; /* Try again */
const Errno ENOMEM = 12; /* Out of memory */
const Errno EACCES = 13; /* Permission denied */
const Errno EFAULT = 14; /* Bad address */
const Errno ENOTBLK = 15; /* Block device required */
const Errno EBUSY = 16; /* Device or resource busy */
const Errno EEXIST = 17; /* File exists */
const Errno EXDEV = 18; /* Cross-device link */
const Errno ENODEV = 19; /* No such device */
const Errno ENOTDIR = 20; /* Not a directory */
const Errno EISDIR = 21; /* Is a directory */
const Errno EINVAL = 22; /* Invalid argument */
const Errno ENFILE = 23; /* File table overflow */
const Errno EMFILE = 24; /* Too many open files */
const Errno ENOTTY = 25; /* Not a typewriter */
const Errno ETXTBSY = 26; /* Text file busy */
const Errno EFBIG = 27; /* File too large */
const Errno ENOSPC = 28; /* No space left on device */
const Errno ESPIPE = 29; /* Illegal seek */
const Errno EROFS = 30; /* Read-only file system */
const Errno EMLINK = 31; /* Too many links */
const Errno EPIPE = 32; /* Broken pipe */
const Errno EDOM = 33; /* Math argument out of domain of func */
const Errno ERANGE = 34; /* Math result not representable */
const Errno EDEADLK = 35; /* Resource deadlock would occur */
const Errno ENAMETOOLONG = 36; /* File name too long */
const Errno ENOLCK = 37; /* No record locks available */
const Errno ENOSYS = 38; /* Function not implemented */
const Errno ENOTEMPTY = 39; /* Directory not empty */
const Errno ELOOP = 40; /* Too many symbolic links encountered */
const Errno ENOMSG = 42; /* No message of desired type */
const Errno EIDRM = 43; /* Identifier removed */
const Errno ECHRNG = 44; /* Channel number out of range */
const Errno EL2NSYNC = 45; /* Level 2 not synchronized */
const Errno EL3HLT = 46; /* Level 3 halted */
const Errno EL3RST = 47; /* Level 3 reset */
const Errno ELNRNG = 48; /* Link number out of range */
const Errno EUNATCH = 49; /* Protocol driver not attached */
const Errno ENOCSI = 50; /* No CSI structure available */
const Errno EL2HLT = 51; /* Level 2 halted */
const Errno EBADE = 52; /* Invalid exchange */
const Errno EBADR = 53; /* Invalid request descriptor */
const Errno EXFULL = 54; /* Exchange full */
const Errno ENOANO = 55; /* No anode */
const Errno EBADRQC = 56; /* Invalid request code */
const Errno EBADSLT = 57; /* Invalid slot */
const Errno EBFONT = 59; /* Bad font file format */
const Errno ENOSTR = 60; /* Device not a stream */
const Errno ENODATA = 61; /* No data available */
const Errno ETIME = 62; /* Timer expired */
const Errno ENOSR = 63; /* Out of streams resources */
const Errno ENONET = 64; /* Machine is not on the network */
const Errno ENOPKG = 65; /* Package not installed */
const Errno EREMOTE = 66; /* Object is remote */
const Errno ENOLINK = 67; /* Link has been severed */
const Errno EADV = 68; /* Advertise error */
const Errno ESRMNT = 69; /* Srmount error */
const Errno ECOMM = 70; /* Communication error on send */
const Errno EPROTO = 71; /* Protocol error */
const Errno EMULTIHOP = 72; /* Multihop attempted */
const Errno EDOTDOT = 73; /* RFS specific error */
const Errno EBADMSG = 74; /* Not a data message */
const Errno EOVERFLOW = 75; /* Value too large for defined data type */
const Errno ENOTUNIQ = 76; /* Name not unique on network */
const Errno EBADFD = 77; /* File descriptor in bad state */
const Errno EREMCHG = 78; /* Remote address changed */
const Errno ELIBACC = 79; /* Can not access a needed shared library */
const Errno ELIBBAD = 80; /* Accessing a corrupted shared library */
const Errno ELIBSCN = 81; /* .lib section in a.out corrupted */
const Errno ELIBMAX = 82; /* Attempting to link in too many shared libraries */
const Errno ELIBEXEC = 83; /* Cannot exec a shared library directly */
const Errno EILSEQ = 84; /* Illegal byte sequence */
const Errno ERESTART = 85; /* Interrupted system call should be restarted */
const Errno ESTRPIPE = 86; /* Streams pipe error */
const Errno EUSERS = 87; /* Too many users */
const Errno ENOTSOCK = 88; /* Socket operation on non-socket */
const Errno EDESTADDRREQ = 89; /* Destination address required */
const Errno EMSGSIZE = 90; /* Message too long */
const Errno EPROTOTYPE = 91; /* Protocol wrong type for socket */
const Errno ENOPROTOOPT = 92; /* Protocol not available */
const Errno EPROTONOSUPPORT = 93; /* Protocol not supported */
const Errno ESOCKTNOSUPPORT = 94; /* Socket type not supported */
const Errno EOPNOTSUPP = 95; /* Operation not supported on transport endpoint */
const Errno EPFNOSUPPORT = 96; /* Protocol family not supported */
const Errno EAFNOSUPPORT = 97; /* Address family not supported by protocol */
const Errno EADDRINUSE = 98; /* Address already in use */
const Errno EADDRNOTAVAIL = 99; /* Cannot assign requested address */
const Errno ENETDOWN = 100; /* Network is down */
const Errno ENETUNREACH = 101; /* Network is unreachable */
const Errno ENETRESET = 102; /* Network dropped connection because of reset */
const Errno ECONNABORTED = 103; /* Software caused connection abort */
const Errno ECONNRESET = 104; /* Connection reset by peer */
const Errno ENOBUFS = 105; /* No buffer space available */
const Errno EISCONN = 106; /* Transport endpoint is already connected */
const Errno ENOTCONN = 107; /* Transport endpoint is not connected */
const Errno ESHUTDOWN = 108; /* Cannot send after transport endpoint shutdown */
const Errno ETOOMANYREFS = 109; /* Too many references: cannot splice */
const Errno ETIMEDOUT = 110; /* Connection timed out */
const Errno ECONNREFUSED = 111; /* Connection refused */
const Errno EHOSTDOWN = 112; /* Host is down */
const Errno EHOSTUNREACH = 113; /* No route to host */
const Errno EALREADY = 114; /* Operation already in progress */
const Errno EINPROGRESS = 115; /* Operation now in progress */
const Errno ESTALE = 116; /* Stale NFS file handle */
const Errno EUCLEAN = 117; /* Structure needs cleaning */
const Errno ENOTNAM = 118; /* Not a XENIX named type file */
const Errno ENAVAIL = 119; /* No XENIX semaphores available */
const Errno EISNAM = 120; /* Is a named type file */
const Errno EREMOTEIO = 121; /* Remote I/O error */
const Errno EDQUOT = 122; /* Quota exceeded */
const Errno ENOMEDIUM = 123; /* No medium found */
const Errno EMEDIUMTYPE = 124; /* Wrong medium type */
const Errno ECANCELED = 125; /* Operation Canceled */
const Errno ENOKEY = 126; /* Required key not available */
const Errno EKEYEXPIRED = 127; /* Key has expired */
const Errno EKEYREVOKED = 128; /* Key has been revoked */
const Errno EKEYREJECTED = 129; /* Key was rejected by service */
const Errno EOWNERDEAD = 130; /* Owner died */
const Errno ENOTRECOVERABLE = 131; /* State not recoverable */

View File

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

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