diff --git a/.circleci/config.yml b/.circleci/config.yml
index 2999c861fbcb..574d390f3bc3 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -8,11 +8,11 @@ parameters:
distribution-scripts-version:
description: "Git ref for version of https://github.com/crystal-lang/distribution-scripts/"
type: string
- default: "96e431e170979125018bd4fd90111a3147477eec"
+ default: "da59efb2dfd70dcd7272eaecceffb636ef547427"
previous_crystal_base_url:
description: "Prefix for URLs to Crystal bootstrap compiler"
type: string
- default: "https://github.com/crystal-lang/crystal/releases/download/1.13.1/crystal-1.13.1-1"
+ default: "https://github.com/crystal-lang/crystal/releases/download/1.14.0/crystal-1.14.0-1"
defaults:
environment: &env
@@ -81,7 +81,7 @@ jobs:
test_darwin:
macos:
- xcode: 13.4.1
+ xcode: 15.4.0
environment:
<<: *env
TRAVIS_OS_NAME: osx
diff --git a/.github/workflows/aarch64.yml b/.github/workflows/aarch64.yml
index da252904fa37..14e7c3d9f564 100644
--- a/.github/workflows/aarch64.yml
+++ b/.github/workflows/aarch64.yml
@@ -2,19 +2,21 @@ name: AArch64 CI
on: [push, pull_request]
+permissions: {}
+
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.ref != 'refs/heads/master' }}
jobs:
aarch64-musl-build:
- runs-on: [linux, ARM64]
+ runs-on: [runs-on, runner=2cpu-linux-arm64, "run-id=${{ github.run_id }}"]
if: github.repository == 'crystal-lang/crystal'
steps:
- name: Download Crystal source
uses: actions/checkout@v4
- name: Build Crystal
- uses: docker://jhass/crystal:1.0.0-alpine-build
+ uses: docker://crystallang/crystal:1.13.2-alpine-84codes-build
with:
args: make crystal
- name: Upload Crystal executable
@@ -26,7 +28,7 @@ jobs:
src/llvm/ext/llvm_ext.o
aarch64-musl-test-stdlib:
needs: aarch64-musl-build
- runs-on: [linux, ARM64]
+ runs-on: [runs-on, runner=4cpu-linux-arm64, "family=m7g", ram=16, "run-id=${{ github.run_id }}"]
if: github.repository == 'crystal-lang/crystal'
steps:
- name: Download Crystal source
@@ -38,12 +40,12 @@ jobs:
- name: Mark downloaded compiler as executable
run: chmod +x .build/crystal
- name: Run stdlib specs
- uses: docker://jhass/crystal:1.0.0-alpine-build
+ uses: docker://crystallang/crystal:1.13.2-alpine-84codes-build
with:
- args: make std_spec FLAGS=-Duse_pcre
+ args: make std_spec
aarch64-musl-test-compiler:
needs: aarch64-musl-build
- runs-on: [linux, ARM64]
+ runs-on: [runs-on, runner=2cpu-linux-arm64, "family=m7g", ram=8, "run-id=${{ github.run_id }}"]
if: github.repository == 'crystal-lang/crystal'
steps:
- name: Download Crystal source
@@ -55,17 +57,17 @@ jobs:
- name: Mark downloaded compiler as executable
run: chmod +x .build/crystal
- name: Run compiler specs
- uses: docker://jhass/crystal:1.0.0-alpine-build
+ uses: docker://crystallang/crystal:1.13.2-alpine-84codes-build
with:
args: make primitives_spec compiler_spec FLAGS=-Dwithout_ffi
aarch64-gnu-build:
- runs-on: [linux, ARM64]
+ runs-on: [runs-on, runner=2cpu-linux-arm64, "run-id=${{ github.run_id }}"]
if: github.repository == 'crystal-lang/crystal'
steps:
- name: Download Crystal source
uses: actions/checkout@v4
- name: Build Crystal
- uses: docker://jhass/crystal:1.0.0-build
+ uses: docker://crystallang/crystal:1.13.2-ubuntu-84codes-build
with:
args: make crystal
- name: Upload Crystal executable
@@ -77,7 +79,7 @@ jobs:
src/llvm/ext/llvm_ext.o
aarch64-gnu-test-stdlib:
needs: aarch64-gnu-build
- runs-on: [linux, ARM64]
+ runs-on: [runs-on, runner=4cpu-linux-arm64, "family=m7g", ram=16, "run-id=${{ github.run_id }}"]
if: github.repository == 'crystal-lang/crystal'
steps:
- name: Download Crystal source
@@ -89,12 +91,12 @@ jobs:
- name: Mark downloaded compiler as executable
run: chmod +x .build/crystal
- name: Run stdlib specs
- uses: docker://jhass/crystal:1.0.0-build
+ uses: docker://crystallang/crystal:1.13.2-ubuntu-84codes-build
with:
args: make std_spec
aarch64-gnu-test-compiler:
needs: aarch64-gnu-build
- runs-on: [linux, ARM64]
+ runs-on: [runs-on, runner=2cpu-linux-arm64, "family=m7g", ram=8, "run-id=${{ github.run_id }}"]
if: github.repository == 'crystal-lang/crystal'
steps:
- name: Download Crystal source
@@ -106,6 +108,6 @@ jobs:
- name: Mark downloaded compiler as executable
run: chmod +x .build/crystal
- name: Run compiler specs
- uses: docker://jhass/crystal:1.0.0-build
+ uses: docker://crystallang/crystal:1.13.2-ubuntu-84codes-build
with:
args: make primitives_spec compiler_spec
diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml
new file mode 100644
index 000000000000..9e576303f479
--- /dev/null
+++ b/.github/workflows/docs.yml
@@ -0,0 +1,45 @@
+name: Docs
+
+on:
+ push:
+ branches:
+ - master
+
+permissions: {}
+
+env:
+ TRAVIS_OS_NAME: linux
+
+jobs:
+ deploy_api_docs:
+ if: github.repository_owner == 'crystal-lang'
+ env:
+ ARCH: x86_64
+ ARCH_CMD: linux64
+ runs-on: ubuntu-latest
+ steps:
+ - name: Download Crystal source
+ uses: actions/checkout@v4
+
+ - name: Prepare System
+ run: bin/ci prepare_system
+
+ - name: Prepare Build
+ run: bin/ci prepare_build
+
+ - name: Build docs
+ run: bin/ci with_build_env 'make crystal docs threads=1'
+
+ - name: Set revision
+ run: echo $GITHUB_SHA > ./docs/revision.txt
+
+ - name: Configure AWS Credentials
+ uses: aws-actions/configure-aws-credentials@v4
+ with:
+ aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
+ aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
+ aws-region: us-east-1
+
+ - name: Deploy API docs to S3
+ run: |
+ aws s3 sync ./docs s3://crystal-api/api/master --delete
diff --git a/.github/workflows/interpreter.yml b/.github/workflows/interpreter.yml
index 8828efe88a10..103dc766509b 100644
--- a/.github/workflows/interpreter.yml
+++ b/.github/workflows/interpreter.yml
@@ -2,6 +2,8 @@ name: Interpreter Test
on: [push, pull_request]
+permissions: {}
+
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.ref != 'refs/heads/master' }}
@@ -13,7 +15,7 @@ jobs:
test-interpreter_spec:
runs-on: ubuntu-22.04
container:
- image: crystallang/crystal:1.13.1-build
+ image: crystallang/crystal:1.14.0-build
name: "Test Interpreter"
steps:
- uses: actions/checkout@v4
@@ -24,7 +26,7 @@ jobs:
build-interpreter:
runs-on: ubuntu-22.04
container:
- image: crystallang/crystal:1.13.1-build
+ image: crystallang/crystal:1.14.0-build
name: Build interpreter
steps:
- uses: actions/checkout@v4
@@ -43,7 +45,7 @@ jobs:
needs: build-interpreter
runs-on: ubuntu-22.04
container:
- image: crystallang/crystal:1.13.1-build
+ image: crystallang/crystal:1.14.0-build
strategy:
matrix:
part: [0, 1, 2, 3]
@@ -67,7 +69,7 @@ jobs:
needs: build-interpreter
runs-on: ubuntu-22.04
container:
- image: crystallang/crystal:1.13.1-build
+ image: crystallang/crystal:1.14.0-build
name: "Test primitives_spec with interpreter"
steps:
- uses: actions/checkout@v4
diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml
index 32761dbb8c75..79c3f143d303 100644
--- a/.github/workflows/linux.yml
+++ b/.github/workflows/linux.yml
@@ -2,6 +2,8 @@ name: Linux CI
on: [push, pull_request]
+permissions: {}
+
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.ref != 'refs/heads/master' }}
@@ -19,7 +21,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
- crystal_bootstrap_version: [1.7.3, 1.8.2, 1.9.2, 1.10.1, 1.11.2, 1.12.2, 1.13.1]
+ crystal_bootstrap_version: [1.7.3, 1.8.2, 1.9.2, 1.10.1, 1.11.2, 1.12.2, 1.13.3, 1.14.0]
flags: [""]
include:
# libffi is only available starting from the 1.2.2 build images
@@ -106,36 +108,3 @@ jobs:
- name: Check Format
run: bin/ci format
-
- deploy_api_docs:
- if: github.repository_owner == 'crystal-lang' && github.event_name == 'push' && github.ref == 'refs/heads/master'
- env:
- ARCH: x86_64
- ARCH_CMD: linux64
- runs-on: ubuntu-latest
- steps:
- - name: Download Crystal source
- uses: actions/checkout@v4
-
- - name: Prepare System
- run: bin/ci prepare_system
-
- - name: Prepare Build
- run: bin/ci prepare_build
-
- - name: Build docs
- run: bin/ci with_build_env 'make crystal docs threads=1'
-
- - name: Set revision
- run: echo $GITHUB_SHA > ./docs/revision.txt
-
- - name: Configure AWS Credentials
- uses: aws-actions/configure-aws-credentials@v4
- with:
- aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
- aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
- aws-region: us-east-1
-
- - name: Deploy API docs to S3
- run: |
- aws s3 sync ./docs s3://crystal-api/api/master --delete
diff --git a/.github/workflows/llvm.yml b/.github/workflows/llvm.yml
index 767d401138e7..a69383319542 100644
--- a/.github/workflows/llvm.yml
+++ b/.github/workflows/llvm.yml
@@ -2,6 +2,8 @@ name: LLVM CI
on: [push, pull_request]
+permissions: {}
+
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.ref != 'refs/heads/master' }}
@@ -11,54 +13,34 @@ env:
jobs:
llvm_test:
- runs-on: ubuntu-22.04
+ runs-on: ${{ matrix.runs-on }}
strategy:
fail-fast: false
matrix:
include:
- - llvm_version: "13.0.0"
- llvm_ubuntu_version: "20.04"
- - llvm_version: "14.0.0"
- llvm_ubuntu_version: "18.04"
- - llvm_version: "15.0.6"
- llvm_ubuntu_version: "18.04"
- - llvm_version: "16.0.3"
- llvm_ubuntu_version: "22.04"
- - llvm_version: "17.0.6"
- llvm_ubuntu_version: "22.04"
- - llvm_version: "18.1.4"
- llvm_ubuntu_version: "18.04"
+ - {llvm_version: 13, runs-on: ubuntu-22.04, codename: jammy}
+ - {llvm_version: 14, runs-on: ubuntu-22.04, codename: jammy}
+ - {llvm_version: 15, runs-on: ubuntu-22.04, codename: jammy}
+ - {llvm_version: 16, runs-on: ubuntu-22.04, codename: jammy}
+ - {llvm_version: 17, runs-on: ubuntu-24.04, codename: noble}
+ - {llvm_version: 18, runs-on: ubuntu-24.04, codename: noble}
+ - {llvm_version: 19, runs-on: ubuntu-24.04, codename: noble}
name: "LLVM ${{ matrix.llvm_version }}"
steps:
- name: Checkout Crystal source
uses: actions/checkout@v4
- - name: Cache LLVM
- id: cache-llvm
- uses: actions/cache@v4
- with:
- path: ./llvm
- key: llvm-${{ matrix.llvm_version }}
- if: "${{ !env.ACT }}"
-
- name: Install LLVM ${{ matrix.llvm_version }}
run: |
- mkdir -p llvm
- curl -L "https://github.com/llvm/llvm-project/releases/download/llvmorg-${{ matrix.llvm_version }}/clang+llvm-${{ matrix.llvm_version }}-x86_64-linux-gnu-ubuntu-${{ matrix.llvm_ubuntu_version }}.tar.xz" > llvm.tar.xz
- tar x --xz -C llvm --strip-components=1 -f llvm.tar.xz
- if: steps.cache-llvm.outputs.cache-hit != 'true'
-
- - name: Set up LLVM
- run: |
- sudo apt-get install -y libtinfo5
- echo "PATH=$(pwd)/llvm/bin:$PATH" >> $GITHUB_ENV
- echo "LLVM_CONFIG=$(pwd)/llvm/bin/llvm-config" >> $GITHUB_ENV
- echo "LD_LIBRARY_PATH=$(pwd)/llvm/lib:$LD_LIBRARY_PATH" >> $GITHUB_ENV
+ sudo apt remove 'llvm-*' 'libllvm*'
+ wget -qO- https://apt.llvm.org/llvm-snapshot.gpg.key | sudo tee /etc/apt/trusted.gpg.d/apt.llvm.org.asc
+ sudo apt-add-repository -y deb http://apt.llvm.org/${{ matrix.codename }}/ llvm-toolchain-${{ matrix.codename }}-${{ matrix.llvm_version }} main
+ sudo apt install -y llvm-${{ matrix.llvm_version }}-dev lld
- name: Install Crystal
uses: crystal-lang/install-crystal@v1
with:
- crystal: "1.13.1"
+ crystal: "1.14.0"
- name: Build libllvm_ext
run: make -B deps
diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml
index 7f27b3cc9c14..8ae3ac28209e 100644
--- a/.github/workflows/macos.yml
+++ b/.github/workflows/macos.yml
@@ -2,6 +2,8 @@ name: macOS CI
on: [push, pull_request]
+permissions: {}
+
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.ref != 'refs/heads/master' }}
@@ -11,18 +13,26 @@ env:
CI_NIX_SHELL: true
jobs:
- x86_64-darwin-test:
- runs-on: macos-13
+ darwin-test:
+ runs-on: ${{ matrix.runs-on }}
+ name: ${{ matrix.arch }}
+ strategy:
+ matrix:
+ include:
+ - runs-on: macos-13
+ arch: x86_64-darwin
+ - runs-on: macos-14
+ arch: aarch64-darwin
steps:
- name: Download Crystal source
uses: actions/checkout@v4
- - uses: cachix/install-nix-action@v26
+ - uses: cachix/install-nix-action@v27
with:
install_url: https://releases.nixos.org/nix/nix-2.9.2/install
extra_nix_config: |
experimental-features = nix-command
- - uses: cachix/cachix-action@v14
+ - uses: cachix/cachix-action@v15
with:
name: crystal-ci
signingKey: '${{ secrets.CACHIX_SIGNING_KEY }}'
diff --git a/.github/workflows/mingw-w64.yml b/.github/workflows/mingw-w64.yml
new file mode 100644
index 000000000000..10841a325bf5
--- /dev/null
+++ b/.github/workflows/mingw-w64.yml
@@ -0,0 +1,162 @@
+name: MinGW-w64 CI
+
+on: [push, pull_request]
+
+permissions: {}
+
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: ${{ github.ref != 'refs/heads/master' }}
+
+env:
+ SPEC_SPLIT_DOTS: 160
+
+jobs:
+ x86_64-mingw-w64-cross-compile:
+ runs-on: ubuntu-24.04
+ steps:
+ - name: Download Crystal source
+ uses: actions/checkout@v4
+
+ - name: Install LLVM 18
+ run: |
+ sudo apt remove 'llvm-*' 'libllvm*'
+ wget -qO- https://apt.llvm.org/llvm-snapshot.gpg.key | sudo tee /etc/apt/trusted.gpg.d/apt.llvm.org.asc
+ sudo apt-add-repository -y deb http://apt.llvm.org/noble/ llvm-toolchain-noble-18 main
+ sudo apt install -y llvm-18-dev
+
+ - name: Install Crystal
+ uses: crystal-lang/install-crystal@v1
+ with:
+ crystal: "1.14.0"
+
+ - name: Cross-compile Crystal
+ run: make && make -B target=x86_64-windows-gnu release=1 interpreter=1
+
+ - name: Upload crystal.obj
+ uses: actions/upload-artifact@v4
+ with:
+ name: x86_64-mingw-w64-crystal-obj
+ path: .build/crystal.obj
+
+ - name: Upload standard library
+ uses: actions/upload-artifact@v4
+ with:
+ name: x86_64-mingw-w64-crystal-stdlib
+ path: src
+
+ x86_64-mingw-w64-link:
+ runs-on: windows-2022
+ needs: [x86_64-mingw-w64-cross-compile]
+ steps:
+ - name: Setup MSYS2
+ id: msys2
+ uses: msys2/setup-msys2@ddf331adaebd714795f1042345e6ca57bd66cea8 # v2.24.1
+ with:
+ msystem: UCRT64
+ update: true
+ install: >-
+ mingw-w64-ucrt-x86_64-pkgconf
+ mingw-w64-ucrt-x86_64-cc
+ mingw-w64-ucrt-x86_64-gc
+ mingw-w64-ucrt-x86_64-pcre2
+ mingw-w64-ucrt-x86_64-libiconv
+ mingw-w64-ucrt-x86_64-zlib
+ mingw-w64-ucrt-x86_64-llvm
+ mingw-w64-ucrt-x86_64-libffi
+
+ - name: Download crystal.obj
+ uses: actions/download-artifact@v4
+ with:
+ name: x86_64-mingw-w64-crystal-obj
+
+ - name: Download standard library
+ uses: actions/download-artifact@v4
+ with:
+ name: x86_64-mingw-w64-crystal-stdlib
+ path: share/crystal/src
+
+ - name: Link Crystal executable
+ shell: msys2 {0}
+ run: |
+ mkdir bin
+ cc crystal.obj -o bin/crystal.exe \
+ $(pkg-config bdw-gc libpcre2-8 iconv zlib libffi --libs) \
+ $(llvm-config --libs --system-libs --ldflags) \
+ -lDbgHelp -lole32 -lWS2_32 -Wl,--stack,0x800000
+ ldd bin/crystal.exe | grep -iv /c/windows/system32 | sed 's/.* => //; s/ (.*//' | xargs -t -i cp '{}' bin/
+
+ - name: Upload Crystal
+ uses: actions/upload-artifact@v4
+ with:
+ name: x86_64-mingw-w64-crystal
+ path: |
+ bin/
+ share/
+
+ x86_64-mingw-w64-test:
+ runs-on: windows-2022
+ needs: [x86_64-mingw-w64-link]
+ steps:
+ - name: Setup MSYS2
+ id: msys2
+ uses: msys2/setup-msys2@ddf331adaebd714795f1042345e6ca57bd66cea8 # v2.24.1
+ with:
+ msystem: UCRT64
+ update: true
+ install: >-
+ git
+ make
+ mingw-w64-ucrt-x86_64-pkgconf
+ mingw-w64-ucrt-x86_64-cc
+ mingw-w64-ucrt-x86_64-gc
+ mingw-w64-ucrt-x86_64-pcre2
+ mingw-w64-ucrt-x86_64-libiconv
+ mingw-w64-ucrt-x86_64-zlib
+ mingw-w64-ucrt-x86_64-llvm
+ mingw-w64-ucrt-x86_64-gmp
+ mingw-w64-ucrt-x86_64-libxml2
+ mingw-w64-ucrt-x86_64-libyaml
+ mingw-w64-ucrt-x86_64-openssl
+ mingw-w64-ucrt-x86_64-libffi
+
+ - name: Disable CRLF line ending substitution
+ run: |
+ git config --global core.autocrlf false
+
+ - name: Download Crystal source
+ uses: actions/checkout@v4
+
+ - name: Download Crystal executable
+ uses: actions/download-artifact@v4
+ with:
+ name: x86_64-mingw-w64-crystal
+ path: crystal
+
+ - name: Run stdlib specs
+ shell: msys2 {0}
+ run: |
+ export PATH="$(pwd)/crystal/bin:$PATH"
+ export CRYSTAL_SPEC_COMPILER_BIN="$(pwd)/crystal/bin/crystal.exe"
+ make std_spec
+
+ - name: Run compiler specs
+ shell: msys2 {0}
+ run: |
+ export PATH="$(pwd)/crystal/bin:$PATH"
+ export CRYSTAL_SPEC_COMPILER_BIN="$(pwd)/crystal/bin/crystal.exe"
+ make compiler_spec
+
+ - name: Run interpreter specs
+ shell: msys2 {0}
+ run: |
+ export PATH="$(pwd)/crystal/bin:$PATH"
+ export CRYSTAL_SPEC_COMPILER_BIN="$(pwd)/crystal/bin/crystal.exe"
+ make interpreter_spec
+
+ - name: Run primitives specs
+ shell: msys2 {0}
+ run: |
+ export PATH="$(pwd)/crystal/bin:$PATH"
+ export CRYSTAL_SPEC_COMPILER_BIN="$(pwd)/crystal/bin/crystal.exe"
+ make -o .build/crystal.exe primitives_spec # we know the compiler is fresh; do not rebuild it here
diff --git a/.github/workflows/openssl.yml b/.github/workflows/openssl.yml
index 46d440d1f6e7..611413e7e678 100644
--- a/.github/workflows/openssl.yml
+++ b/.github/workflows/openssl.yml
@@ -2,57 +2,41 @@ name: OpenSSL CI
on: [push, pull_request]
+permissions: {}
+
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.ref != 'refs/heads/master' }}
jobs:
- openssl3:
- runs-on: ubuntu-latest
- name: "OpenSSL 3.0"
- container: crystallang/crystal:1.13.1-alpine
- steps:
- - name: Download Crystal source
- uses: actions/checkout@v4
- - name: Uninstall openssl
- run: apk del openssl-dev libxml2-static
- - name: Upgrade alpine-keys
- run: apk upgrade alpine-keys
- - name: Install openssl 3.0
- run: apk add "openssl-dev=~3.0" --repository=http://dl-cdn.alpinelinux.org/alpine/v3.17/main
- - name: Check LibSSL version
- run: bin/crystal eval 'require "openssl"; p! LibSSL::OPENSSL_VERSION, LibSSL::LIBRESSL_VERSION'
- - name: Run OpenSSL specs
- run: bin/crystal spec --order=random spec/std/openssl/
- openssl111:
- runs-on: ubuntu-latest
- name: "OpenSSL 1.1.1"
- container: crystallang/crystal:1.13.1-alpine
- steps:
- - name: Download Crystal source
- uses: actions/checkout@v4
- - name: Uninstall openssl
- run: apk del openssl-dev
- - name: Install openssl 1.1.1
- run: apk add "openssl1.1-compat-dev=~1.1.1" --repository=http://dl-cdn.alpinelinux.org/alpine/v3.18/community
- - name: Check LibSSL version
- run: bin/crystal eval 'require "openssl"; p! LibSSL::OPENSSL_VERSION, LibSSL::LIBRESSL_VERSION'
- - name: Run OpenSSL specs
- run: bin/crystal spec --order=random spec/std/openssl/
- libressl34:
+ libssl_test:
runs-on: ubuntu-latest
- name: "LibreSSL 3.4"
- container: crystallang/crystal:1.13.1-alpine
+ name: "${{ matrix.pkg }}"
+ container: crystallang/crystal:1.14.0-alpine
+ strategy:
+ fail-fast: false
+ matrix:
+ include:
+ - pkg: "openssl1.1-compat-dev=~1.1.1"
+ repository: http://dl-cdn.alpinelinux.org/alpine/v3.18/community
+ - pkg: "openssl-dev=~3.0"
+ repository: http://dl-cdn.alpinelinux.org/alpine/v3.17/main
+ - pkg: "openssl-dev=~3.3"
+ repository: http://dl-cdn.alpinelinux.org/alpine/v3.20/main
+ - pkg: "libressl-dev=~3.4"
+ repository: http://dl-cdn.alpinelinux.org/alpine/v3.15/community
+ - pkg: "libressl-dev=~3.5"
+ repository: http://dl-cdn.alpinelinux.org/alpine/v3.16/community
+ - pkg: "libressl-dev=~3.8"
+ repository: http://dl-cdn.alpinelinux.org/alpine/v3.20/community
steps:
- name: Download Crystal source
uses: actions/checkout@v4
- - name: Uninstall openssl
- run: apk del openssl-dev openssl-libs-static
- - name: Upgrade alpine-keys
- run: apk upgrade alpine-keys
- - name: Install libressl 3.4
- run: apk add "libressl-dev=~3.4" --repository=http://dl-cdn.alpinelinux.org/alpine/v3.15/community
- - name: Check LibSSL version
+ - name: Uninstall openssl and conflicts
+ run: apk del openssl-dev openssl-libs-static libxml2-static
+ - name: Install ${{ matrix.pkg }}
+ run: apk add "${{ matrix.pkg }}" --repository=${{ matrix.repository }}
+ - name: Print LibSSL version
run: bin/crystal eval 'require "openssl"; p! LibSSL::OPENSSL_VERSION, LibSSL::LIBRESSL_VERSION'
- name: Run OpenSSL specs
run: bin/crystal spec --order=random spec/std/openssl/
diff --git a/.github/workflows/regex-engine.yml b/.github/workflows/regex-engine.yml
index 8816c31dc9b0..26b406b84d3f 100644
--- a/.github/workflows/regex-engine.yml
+++ b/.github/workflows/regex-engine.yml
@@ -2,6 +2,8 @@ name: Regex Engine CI
on: [push, pull_request]
+permissions: {}
+
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.ref != 'refs/heads/master' }}
@@ -10,7 +12,7 @@ jobs:
pcre:
runs-on: ubuntu-latest
name: "PCRE"
- container: crystallang/crystal:1.13.1-alpine
+ container: crystallang/crystal:1.14.0-alpine
steps:
- name: Download Crystal source
uses: actions/checkout@v4
@@ -25,7 +27,7 @@ jobs:
pcre2:
runs-on: ubuntu-latest
name: "PCRE2"
- container: crystallang/crystal:1.13.1-alpine
+ container: crystallang/crystal:1.14.0-alpine
steps:
- name: Download Crystal source
uses: actions/checkout@v4
diff --git a/.github/workflows/smoke.yml b/.github/workflows/smoke.yml
index 8deffd149dbd..5a83a26e815a 100644
--- a/.github/workflows/smoke.yml
+++ b/.github/workflows/smoke.yml
@@ -31,6 +31,8 @@ name: Smoke tests
on: [push, pull_request]
+permissions: {}
+
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.ref != 'refs/heads/master' }}
@@ -51,7 +53,6 @@ jobs:
matrix:
target:
- aarch64-linux-android
- - aarch64-darwin
- arm-linux-gnueabihf
- i386-linux-gnu
- i386-linux-musl
diff --git a/.github/workflows/wasm32.yml b/.github/workflows/wasm32.yml
index 2b446ec6726f..9a6472ca2d6e 100644
--- a/.github/workflows/wasm32.yml
+++ b/.github/workflows/wasm32.yml
@@ -2,6 +2,8 @@ name: WebAssembly CI
on: [push, pull_request]
+permissions: {}
+
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.ref != 'refs/heads/master' }}
@@ -11,8 +13,8 @@ env:
jobs:
wasm32-test:
- runs-on: ubuntu-latest
- container: crystallang/crystal:1.13.1-build
+ runs-on: ubuntu-24.04
+ container: crystallang/crystal:1.14.0-build
steps:
- name: Download Crystal source
uses: actions/checkout@v4
@@ -25,10 +27,11 @@ jobs:
- name: Install LLVM
run: |
apt-get update
- apt-get install -y curl lsb-release wget software-properties-common gnupg
- curl -O https://apt.llvm.org/llvm.sh
- chmod +x llvm.sh
- ./llvm.sh 18
+ apt-get remove -y 'llvm-*' 'libllvm*'
+ apt-get install -y curl software-properties-common
+ wget -qO- https://apt.llvm.org/llvm-snapshot.gpg.key | tee /etc/apt/trusted.gpg.d/apt.llvm.org.asc
+ apt-add-repository -y deb http://apt.llvm.org/noble/ llvm-toolchain-noble-18 main
+ apt-get install -y llvm-18-dev lld-18
ln -s $(which wasm-ld-18) /usr/bin/wasm-ld
- name: Download wasm32 libs
diff --git a/.github/workflows/win.yml b/.github/workflows/win.yml
index 05f74b6378c6..03aac8e2f0b1 100644
--- a/.github/workflows/win.yml
+++ b/.github/workflows/win.yml
@@ -2,11 +2,14 @@ name: Windows CI
on: [push, pull_request]
+permissions: {}
+
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.ref != 'refs/heads/master' }}
env:
+ SPEC_SPLIT_DOTS: 160
CI_LLVM_VERSION: "18.1.1"
jobs:
@@ -20,6 +23,13 @@ jobs:
- name: Enable Developer Command Prompt
uses: ilammy/msvc-dev-cmd@0b201ec74fa43914dc39ae48a89fd1d8cb592756 # v1.13.0
+ - name: Set up Cygwin
+ uses: cygwin/cygwin-install-action@006ad0b0946ca6d0a3ea2d4437677fa767392401 # v4
+ with:
+ packages: make
+ install-dir: C:\cygwin64
+ add-to-path: false
+
- name: Download Crystal source
uses: actions/checkout@v4
@@ -49,10 +59,10 @@ jobs:
run: .\etc\win-ci\build-pcre2.ps1 -BuildTree deps\pcre2 -Version 10.43
- name: Build libiconv
if: steps.cache-libs.outputs.cache-hit != 'true'
- run: .\etc\win-ci\build-iconv.ps1 -BuildTree deps\iconv
+ run: .\etc\win-ci\build-iconv.ps1 -BuildTree deps\iconv -Version 1.17
- name: Build libffi
if: steps.cache-libs.outputs.cache-hit != 'true'
- run: .\etc\win-ci\build-ffi.ps1 -BuildTree deps\ffi -Version 3.3
+ run: .\etc\win-ci\build-ffi.ps1 -BuildTree deps\ffi -Version 3.4.6
- name: Build zlib
if: steps.cache-libs.outputs.cache-hit != 'true'
run: .\etc\win-ci\build-z.ps1 -BuildTree deps\z -Version 1.3.1
@@ -92,6 +102,13 @@ jobs:
- name: Enable Developer Command Prompt
uses: ilammy/msvc-dev-cmd@0b201ec74fa43914dc39ae48a89fd1d8cb592756 # v1.13.0
+ - name: Set up Cygwin
+ uses: cygwin/cygwin-install-action@006ad0b0946ca6d0a3ea2d4437677fa767392401 # v4
+ with:
+ packages: make
+ install-dir: C:\cygwin64
+ add-to-path: false
+
- name: Download Crystal source
uses: actions/checkout@v4
@@ -111,7 +128,7 @@ jobs:
libs/xml2-dynamic.lib
dlls/pcre.dll
dlls/pcre2-8.dll
- dlls/libiconv.dll
+ dlls/iconv-2.dll
dlls/gc.dll
dlls/libffi.dll
dlls/zlib1.dll
@@ -130,10 +147,10 @@ jobs:
run: .\etc\win-ci\build-pcre2.ps1 -BuildTree deps\pcre2 -Version 10.43 -Dynamic
- name: Build libiconv
if: steps.cache-dlls.outputs.cache-hit != 'true'
- run: .\etc\win-ci\build-iconv.ps1 -BuildTree deps\iconv -Dynamic
+ run: .\etc\win-ci\build-iconv.ps1 -BuildTree deps\iconv -Version 1.17 -Dynamic
- name: Build libffi
if: steps.cache-dlls.outputs.cache-hit != 'true'
- run: .\etc\win-ci\build-ffi.ps1 -BuildTree deps\ffi -Version 3.3 -Dynamic
+ run: .\etc\win-ci\build-ffi.ps1 -BuildTree deps\ffi -Version 3.4.6 -Dynamic
- name: Build zlib
if: steps.cache-dlls.outputs.cache-hit != 'true'
run: .\etc\win-ci\build-z.ps1 -BuildTree deps\z -Version 1.3.1 -Dynamic
@@ -213,16 +230,16 @@ jobs:
if: steps.cache-llvm-dlls.outputs.cache-hit != 'true'
run: .\etc\win-ci\build-llvm.ps1 -BuildTree deps\llvm -Version ${{ env.CI_LLVM_VERSION }} -TargetsToBuild X86,AArch64 -Dynamic
- x86_64-windows:
+ x86_64-windows-release:
needs: [x86_64-windows-libs, x86_64-windows-dlls, x86_64-windows-llvm-libs, x86_64-windows-llvm-dlls]
uses: ./.github/workflows/win_build_portable.yml
with:
- release: false
+ release: true
llvm_version: "18.1.1"
x86_64-windows-test:
runs-on: windows-2022
- needs: [x86_64-windows]
+ needs: [x86_64-windows-release]
steps:
- name: Disable CRLF line ending substitution
run: |
@@ -265,13 +282,40 @@ jobs:
- name: Build samples
run: make -f Makefile.win samples
- x86_64-windows-release:
- if: github.repository_owner == 'crystal-lang' && (startsWith(github.ref, 'refs/tags/') || startsWith(github.ref, 'refs/heads/ci/'))
- needs: [x86_64-windows-libs, x86_64-windows-dlls, x86_64-windows-llvm-libs, x86_64-windows-llvm-dlls]
- uses: ./.github/workflows/win_build_portable.yml
- with:
- release: true
- llvm_version: "18.1.1"
+ x86_64-windows-test-interpreter:
+ runs-on: windows-2022
+ needs: [x86_64-windows-release]
+ steps:
+ - name: Disable CRLF line ending substitution
+ run: |
+ git config --global core.autocrlf false
+
+ - name: Download Crystal source
+ uses: actions/checkout@v4
+
+ - name: Download Crystal executable
+ uses: actions/download-artifact@v4
+ with:
+ name: crystal
+ path: build
+
+ - name: Restore LLVM
+ uses: actions/cache/restore@v4
+ with:
+ path: llvm
+ key: llvm-libs-${{ env.CI_LLVM_VERSION }}-msvc
+ fail-on-cache-miss: true
+
+ - name: Set up environment
+ run: |
+ Add-Content $env:GITHUB_PATH "$(pwd)\build"
+ Add-Content $env:GITHUB_ENV "CRYSTAL_SPEC_COMPILER_BIN=$(pwd)\build\crystal.exe"
+
+ - name: Run stdlib specs with interpreter
+ run: bin\crystal i spec\std_spec.cr
+
+ - name: Run primitives specs with interpreter
+ run: bin\crystal i spec\primitives_spec.cr
x86_64-windows-installer:
if: github.repository_owner == 'crystal-lang' && (startsWith(github.ref, 'refs/tags/') || startsWith(github.ref, 'refs/heads/ci/'))
@@ -288,7 +332,7 @@ jobs:
- name: Download Crystal executable
uses: actions/download-artifact@v4
with:
- name: crystal-release
+ name: crystal
path: etc/win-ci/portable
- name: Restore LLVM
diff --git a/.github/workflows/win_build_portable.yml b/.github/workflows/win_build_portable.yml
index 6e36608d608d..a81b9e8083ed 100644
--- a/.github/workflows/win_build_portable.yml
+++ b/.github/workflows/win_build_portable.yml
@@ -10,6 +10,8 @@ on:
required: true
type: string
+permissions: {}
+
jobs:
build:
runs-on: windows-2022
@@ -23,8 +25,9 @@ jobs:
- name: Install Crystal
uses: crystal-lang/install-crystal@v1
+ id: install-crystal
with:
- crystal: "1.13.1"
+ crystal: "1.14.0"
- name: Download Crystal source
uses: actions/checkout@v4
@@ -68,7 +71,7 @@ jobs:
libs/xml2-dynamic.lib
dlls/pcre.dll
dlls/pcre2-8.dll
- dlls/libiconv.dll
+ dlls/iconv-2.dll
dlls/gc.dll
dlls/libffi.dll
dlls/zlib1.dll
@@ -107,6 +110,10 @@ jobs:
run: |
echo "CRYSTAL_LIBRARY_PATH=$(pwd)\libs" >> ${env:GITHUB_ENV}
echo "LLVM_CONFIG=$(pwd)\llvm\bin\llvm-config.exe" >> ${env:GITHUB_ENV}
+ # NOTE: the name of the libiconv DLL has changed, so we manually copy
+ # the new one to the existing Crystal installation; remove after
+ # updating the base compiler to 1.14
+ cp dlls/iconv-2.dll ${{ steps.install-crystal.outputs.path }}
- name: Build LLVM extensions
run: make -f Makefile.win deps
@@ -114,7 +121,7 @@ jobs:
- name: Build Crystal
run: |
bin/crystal.bat env
- make -f Makefile.win -B ${{ inputs.release && 'release=1' || '' }}
+ make -f Makefile.win -B ${{ inputs.release && 'release=1' || '' }} interpreter=1
- name: Download shards release
uses: actions/checkout@v4
@@ -140,5 +147,5 @@ jobs:
- name: Upload Crystal binaries
uses: actions/upload-artifact@v4
with:
- name: ${{ inputs.release && 'crystal-release' || 'crystal' }}
+ name: crystal
path: crystal
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 382f76969ec0..76272bb1679b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,411 @@
# Changelog
+## [1.14.0] (2024-10-09)
+
+[1.14.0]: https://github.com/crystal-lang/crystal/releases/1.14.0
+
+### Features
+
+#### lang
+
+- Allow `^` in constant numeric expressions ([#14951], thanks @HertzDevil)
+
+[#14951]: https://github.com/crystal-lang/crystal/pull/14951
+
+#### stdlib
+
+- Add support for Windows on aarch64 ([#14911], thanks @HertzDevil)
+- *(collection)* **[breaking]** Add support for negative start index in `Slice#[start, count]` ([#14778], thanks @ysbaddaden)
+- *(collection)* Add `Slice#same?` ([#14728], thanks @straight-shoota)
+- *(concurrency)* Add `WaitGroup.wait` and `WaitGroup#spawn` ([#14837], thanks @jgaskins)
+- *(concurrency)* Open non-blocking regular files as overlapped on Windows ([#14921], thanks @HertzDevil)
+- *(concurrency)* Support non-blocking `File#read` and `#write` on Windows ([#14940], thanks @HertzDevil)
+- *(concurrency)* Support non-blocking `File#read_at` on Windows ([#14958], thanks @HertzDevil)
+- *(concurrency)* Support non-blocking `Process.run` standard streams on Windows ([#14941], thanks @HertzDevil)
+- *(concurrency)* Support `IO::FileDescriptor#flock_*` on non-blocking files on Windows ([#14943], thanks @HertzDevil)
+- *(concurrency)* Emulate non-blocking `STDIN` console on Windows ([#14947], thanks @HertzDevil)
+- *(concurrency)* Async DNS resolution on Windows ([#14979], thanks @HertzDevil)
+- *(crypto)* Update `LibCrypto` bindings for LibreSSL 3.5+ ([#14872], thanks @straight-shoota)
+- *(llvm)* Expose LLVM instruction builder for `neg` and `fneg` ([#14774], thanks @JarnaChao09)
+- *(llvm)* **[experimental]** Add minimal LLVM OrcV2 bindings ([#14887], thanks @HertzDevil)
+- *(llvm)* Add `LLVM::Builder#finalize` ([#14892], thanks @JarnaChao09)
+- *(llvm)* Support LLVM 19.1 ([#14842], thanks @HertzDevil)
+- *(macros)* Add `Crystal::Macros::TypeNode#has_inner_pointers?` ([#14847], thanks @HertzDevil)
+- *(macros)* Add `HashLiteral#has_key?` and `NamedTupleLiteral#has_key?` ([#14890], thanks @kamil-gwozdz)
+- *(numeric)* Implement floating-point manipulation functions for `BigFloat` ([#11007], thanks @HertzDevil)
+- *(runtime)* Stop & start the world (undocumented API) ([#14729], thanks @ysbaddaden)
+- *(runtime)* Add `Pointer::Appender#to_slice` ([#14874], thanks @straight-shoota)
+- *(serialization)* Add `URI.from_json_object_key?` and `URI#to_json_object_key` ([#14834], thanks @nobodywasishere)
+- *(serialization)* Add `URI::Params::Serializable` ([#14684], thanks @Blacksmoke16)
+- *(system)* Enable full backtrace for exception in process spawn ([#14796], thanks @straight-shoota)
+- *(system)* Implement `System::User` on Windows ([#14933], thanks @HertzDevil)
+- *(system)* Implement `System::Group` on Windows ([#14945], thanks @HertzDevil)
+- *(system)* Add methods to `Crystal::EventLoop` ([#14977], thanks @ysbaddaden)
+- *(text)* Add `underscore_to_space` option to `String#titleize` ([#14822], thanks @Blacksmoke16)
+- *(text)* Support Unicode 16.0.0 ([#14997], thanks @HertzDevil)
+
+[#14911]: https://github.com/crystal-lang/crystal/pull/14911
+[#14778]: https://github.com/crystal-lang/crystal/pull/14778
+[#14728]: https://github.com/crystal-lang/crystal/pull/14728
+[#14837]: https://github.com/crystal-lang/crystal/pull/14837
+[#14921]: https://github.com/crystal-lang/crystal/pull/14921
+[#14940]: https://github.com/crystal-lang/crystal/pull/14940
+[#14958]: https://github.com/crystal-lang/crystal/pull/14958
+[#14941]: https://github.com/crystal-lang/crystal/pull/14941
+[#14943]: https://github.com/crystal-lang/crystal/pull/14943
+[#14947]: https://github.com/crystal-lang/crystal/pull/14947
+[#14979]: https://github.com/crystal-lang/crystal/pull/14979
+[#14872]: https://github.com/crystal-lang/crystal/pull/14872
+[#14774]: https://github.com/crystal-lang/crystal/pull/14774
+[#14887]: https://github.com/crystal-lang/crystal/pull/14887
+[#14892]: https://github.com/crystal-lang/crystal/pull/14892
+[#14842]: https://github.com/crystal-lang/crystal/pull/14842
+[#14847]: https://github.com/crystal-lang/crystal/pull/14847
+[#14890]: https://github.com/crystal-lang/crystal/pull/14890
+[#11007]: https://github.com/crystal-lang/crystal/pull/11007
+[#14729]: https://github.com/crystal-lang/crystal/pull/14729
+[#14874]: https://github.com/crystal-lang/crystal/pull/14874
+[#14834]: https://github.com/crystal-lang/crystal/pull/14834
+[#14684]: https://github.com/crystal-lang/crystal/pull/14684
+[#14796]: https://github.com/crystal-lang/crystal/pull/14796
+[#14933]: https://github.com/crystal-lang/crystal/pull/14933
+[#14945]: https://github.com/crystal-lang/crystal/pull/14945
+[#14977]: https://github.com/crystal-lang/crystal/pull/14977
+[#14822]: https://github.com/crystal-lang/crystal/pull/14822
+[#14997]: https://github.com/crystal-lang/crystal/pull/14997
+
+#### compiler
+
+- *(cli)* Adds initial support for external commands ([#14953], thanks @bcardiff)
+- *(interpreter)* Add `Crystal::Repl::Value#runtime_type` ([#14156], thanks @bcardiff)
+- *(interpreter)* Implement `Reference.pre_initialize` in the interpreter ([#14968], thanks @HertzDevil)
+- *(interpreter)* Enable the interpreter on Windows ([#14964], thanks @HertzDevil)
+
+[#14953]: https://github.com/crystal-lang/crystal/pull/14953
+[#14156]: https://github.com/crystal-lang/crystal/pull/14156
+[#14968]: https://github.com/crystal-lang/crystal/pull/14968
+[#14964]: https://github.com/crystal-lang/crystal/pull/14964
+
+### Bugfixes
+
+#### lang
+
+- Fix `Slice.literal` for multiple calls with identical signature ([#15009], thanks @HertzDevil)
+- *(macros)* Add location info to some `MacroIf` nodes ([#14885], thanks @Blacksmoke16)
+
+[#15009]: https://github.com/crystal-lang/crystal/pull/15009
+[#14885]: https://github.com/crystal-lang/crystal/pull/14885
+
+#### stdlib
+
+- *(collection)* Fix `Range#size` return type to `Int32` ([#14588], thanks @straight-shoota)
+- *(concurrency)* Update `DeallocationStack` for Windows context switch ([#15032], thanks @HertzDevil)
+- *(concurrency)* Fix race condition in `pthread_create` handle initialization ([#15043], thanks @HertzDevil)
+- *(files)* **[regression]** Fix `File#truncate` and `#lock` for Win32 append-mode files ([#14706], thanks @HertzDevil)
+- *(files)* **[breaking]** Avoid flush in finalizers for `Socket` and `IO::FileDescriptor` ([#14882], thanks @straight-shoota)
+- *(files)* Make `IO::Buffered#buffer_size=` idempotent ([#14855], thanks @jgaskins)
+- *(macros)* Implement `#sort_by` inside macros using `Enumerable#sort_by` ([#14895], thanks @HertzDevil)
+- *(macros)* Fix internal error when calling `#is_a?` on `External` nodes ([#14918], thanks @HertzDevil)
+- *(networking)* Use correct timeout for `Socket#connect` on Windows ([#14961], thanks @HertzDevil)
+- *(numeric)* Fix handle empty string in `String#to_f(whitespace: false)` ([#14902], thanks @Blacksmoke16)
+- *(numeric)* Fix exponent wrapping in `Math.frexp(BigFloat)` for very large values ([#14971], thanks @HertzDevil)
+- *(numeric)* Fix exponent overflow in `BigFloat#to_s` for very large values ([#14982], thanks @HertzDevil)
+- *(numeric)* Add missing `@[Link(dll:)]` annotation to MPIR ([#15003], thanks @HertzDevil)
+- *(runtime)* Add missing return type of `LibC.VirtualQuery` ([#15036], thanks @HertzDevil)
+- *(runtime)* Fix main stack top detection on musl-libc ([#15047], thanks @HertzDevil)
+- *(serialization)* **[breaking]** Remove `XML::Error.errors` ([#14936], thanks @straight-shoota)
+- *(specs)* **[regression]** Fix `Expectations::Be` for module type ([#14926], thanks @straight-shoota)
+- *(system)* Fix return type restriction for `ENV.fetch` ([#14919], thanks @straight-shoota)
+- *(system)* `#file_descriptor_close` should set `@closed` (UNIX) ([#14973], thanks @ysbaddaden)
+- *(system)* reinit event loop first after fork (UNIX) ([#14975], thanks @ysbaddaden)
+- *(text)* Fix avoid linking `libpcre` when unused ([#14891], thanks @kojix2)
+- *(text)* Add type restriction to `String#byte_index` `offset` parameter ([#14981], thanks @straight-shoota)
+
+[#14588]: https://github.com/crystal-lang/crystal/pull/14588
+[#15032]: https://github.com/crystal-lang/crystal/pull/15032
+[#15043]: https://github.com/crystal-lang/crystal/pull/15043
+[#14706]: https://github.com/crystal-lang/crystal/pull/14706
+[#14882]: https://github.com/crystal-lang/crystal/pull/14882
+[#14855]: https://github.com/crystal-lang/crystal/pull/14855
+[#14895]: https://github.com/crystal-lang/crystal/pull/14895
+[#14918]: https://github.com/crystal-lang/crystal/pull/14918
+[#14961]: https://github.com/crystal-lang/crystal/pull/14961
+[#14902]: https://github.com/crystal-lang/crystal/pull/14902
+[#14971]: https://github.com/crystal-lang/crystal/pull/14971
+[#14982]: https://github.com/crystal-lang/crystal/pull/14982
+[#15003]: https://github.com/crystal-lang/crystal/pull/15003
+[#15036]: https://github.com/crystal-lang/crystal/pull/15036
+[#15047]: https://github.com/crystal-lang/crystal/pull/15047
+[#14936]: https://github.com/crystal-lang/crystal/pull/14936
+[#14926]: https://github.com/crystal-lang/crystal/pull/14926
+[#14919]: https://github.com/crystal-lang/crystal/pull/14919
+[#14973]: https://github.com/crystal-lang/crystal/pull/14973
+[#14975]: https://github.com/crystal-lang/crystal/pull/14975
+[#14891]: https://github.com/crystal-lang/crystal/pull/14891
+[#14981]: https://github.com/crystal-lang/crystal/pull/14981
+
+#### compiler
+
+- *(cli)* Add error handling for linker flag sub commands ([#14932], thanks @straight-shoota)
+- *(codegen)* Allow returning `Proc`s from top-level funs ([#14917], thanks @HertzDevil)
+- *(codegen)* Fix CRT static-dynamic linking conflict in specs with C sources ([#14970], thanks @HertzDevil)
+- *(interpreter)* Fix Linux `getrandom` failure in interpreted code ([#15035], thanks @HertzDevil)
+- *(interpreter)* Fix undefined behavior in interpreter mixed union upcast ([#15042], thanks @HertzDevil)
+- *(semantic)* Fix `TopLevelVisitor` adding existing `ClassDef` type to current scope ([#15067], thanks @straight-shoota)
+
+[#14932]: https://github.com/crystal-lang/crystal/pull/14932
+[#14917]: https://github.com/crystal-lang/crystal/pull/14917
+[#14970]: https://github.com/crystal-lang/crystal/pull/14970
+[#15035]: https://github.com/crystal-lang/crystal/pull/15035
+[#15042]: https://github.com/crystal-lang/crystal/pull/15042
+[#15067]: https://github.com/crystal-lang/crystal/pull/15067
+
+#### tools
+
+- *(dependencies)* Fix `crystal tool dependencies` format flat ([#14927], thanks @straight-shoota)
+- *(dependencies)* Fix `crystal tool dependencies` filters for Windows paths ([#14928], thanks @straight-shoota)
+- *(docs-generator)* Fix doc comment above annotation with macro expansion ([#14849], thanks @Blacksmoke16)
+- *(unreachable)* Fix `crystal tool unreachable` & co visiting circular hierarchies ([#15065], thanks @straight-shoota)
+
+[#14927]: https://github.com/crystal-lang/crystal/pull/14927
+[#14928]: https://github.com/crystal-lang/crystal/pull/14928
+[#14849]: https://github.com/crystal-lang/crystal/pull/14849
+[#15065]: https://github.com/crystal-lang/crystal/pull/15065
+
+### Chores
+
+#### stdlib
+
+- **[deprecation]** Use `Time::Span` in `Benchmark.ips` ([#14805], thanks @HertzDevil)
+- **[deprecation]** Deprecate `::sleep(Number)` ([#14962], thanks @HertzDevil)
+- *(runtime)* **[deprecation]** Deprecate `Pointer.new(Int)` ([#14875], thanks @straight-shoota)
+
+[#14805]: https://github.com/crystal-lang/crystal/pull/14805
+[#14962]: https://github.com/crystal-lang/crystal/pull/14962
+[#14875]: https://github.com/crystal-lang/crystal/pull/14875
+
+#### compiler
+
+- *(interpreter)* Remove TODO in `Crystal::Loader` on Windows ([#14988], thanks @HertzDevil)
+- *(interpreter:repl)* Update REPLy version ([#14950], thanks @HertzDevil)
+
+[#14988]: https://github.com/crystal-lang/crystal/pull/14988
+[#14950]: https://github.com/crystal-lang/crystal/pull/14950
+
+### Performance
+
+#### stdlib
+
+- *(collection)* Always use unstable sort for simple types ([#14825], thanks @HertzDevil)
+- *(collection)* Optimize `Hash#transform_{keys,values}` ([#14502], thanks @jgaskins)
+- *(numeric)* Optimize arithmetic between `BigFloat` and integers ([#14944], thanks @HertzDevil)
+- *(runtime)* **[regression]** Cache `Exception::CallStack.empty` to avoid repeat `Array` allocation ([#15025], thanks @straight-shoota)
+
+[#14825]: https://github.com/crystal-lang/crystal/pull/14825
+[#14502]: https://github.com/crystal-lang/crystal/pull/14502
+[#14944]: https://github.com/crystal-lang/crystal/pull/14944
+[#15025]: https://github.com/crystal-lang/crystal/pull/15025
+
+#### compiler
+
+- Avoid unwinding the stack on hot path in method call lookups ([#15002], thanks @ggiraldez)
+- *(codegen)* Reduce calls to `Crystal::Type#remove_indirection` in module dispatch ([#14992], thanks @HertzDevil)
+- *(codegen)* Compiler: enable parallel codegen with MT ([#14748], thanks @ysbaddaden)
+
+[#15002]: https://github.com/crystal-lang/crystal/pull/15002
+[#14992]: https://github.com/crystal-lang/crystal/pull/14992
+[#14748]: https://github.com/crystal-lang/crystal/pull/14748
+
+### Refactor
+
+#### stdlib
+
+- *(concurrency)* Extract `select` from `src/channel.cr` ([#14912], thanks @straight-shoota)
+- *(concurrency)* Make `Crystal::IOCP::OverlappedOperation` abstract ([#14987], thanks @HertzDevil)
+- *(files)* Move `#evented_read`, `#evented_write` into `Crystal::LibEvent::EventLoop` ([#14883], thanks @straight-shoota)
+- *(networking)* Simplify `Socket::Addrinfo.getaddrinfo(&)` ([#14956], thanks @HertzDevil)
+- *(networking)* Add `Crystal::System::Addrinfo` ([#14957], thanks @HertzDevil)
+- *(runtime)* Add `Exception::CallStack.empty` ([#15017], thanks @straight-shoota)
+- *(system)* Refactor cancellation of `IOCP::OverlappedOperation` ([#14754], thanks @straight-shoota)
+- *(system)* Include `Crystal::System::Group` instead of extending it ([#14930], thanks @HertzDevil)
+- *(system)* Include `Crystal::System::User` instead of extending it ([#14929], thanks @HertzDevil)
+- *(system)* Fix: `Crystal::SpinLock` doesn't need to be allocated on the HEAP ([#14972], thanks @ysbaddaden)
+- *(system)* Don't involve evloop after fork in `System::Process.spawn` (UNIX) ([#14974], thanks @ysbaddaden)
+- *(system)* Refactor `EventLoop` interface for sleeps & select timeouts ([#14980], thanks @ysbaddaden)
+
+[#14912]: https://github.com/crystal-lang/crystal/pull/14912
+[#14987]: https://github.com/crystal-lang/crystal/pull/14987
+[#14883]: https://github.com/crystal-lang/crystal/pull/14883
+[#14956]: https://github.com/crystal-lang/crystal/pull/14956
+[#14957]: https://github.com/crystal-lang/crystal/pull/14957
+[#15017]: https://github.com/crystal-lang/crystal/pull/15017
+[#14754]: https://github.com/crystal-lang/crystal/pull/14754
+[#14930]: https://github.com/crystal-lang/crystal/pull/14930
+[#14929]: https://github.com/crystal-lang/crystal/pull/14929
+[#14972]: https://github.com/crystal-lang/crystal/pull/14972
+[#14974]: https://github.com/crystal-lang/crystal/pull/14974
+[#14980]: https://github.com/crystal-lang/crystal/pull/14980
+
+#### compiler
+
+- *(codegen)* Compiler: refactor codegen ([#14760], thanks @ysbaddaden)
+- *(interpreter)* Refactor interpreter stack code to avoid duplicate macro expansion ([#14876], thanks @straight-shoota)
+
+[#14760]: https://github.com/crystal-lang/crystal/pull/14760
+[#14876]: https://github.com/crystal-lang/crystal/pull/14876
+
+### Documentation
+
+#### stdlib
+
+- *(collection)* **[breaking]** Hide `Hash::Entry` from public API docs ([#14881], thanks @Blacksmoke16)
+- *(collection)* Fix typos in docs for `Set` and `Hash` ([#14889], thanks @philipp-classen)
+- *(llvm)* Add `@[Experimental]` to `LLVM::DIBuilder` ([#14854], thanks @HertzDevil)
+- *(networking)* Add documentation about synchronous DNS resolution ([#15027], thanks @straight-shoota)
+- *(networking)* Add `uri/json` to `docs_main` ([#15069], thanks @straight-shoota)
+- *(runtime)* Add docs about `Pointer`'s alignment requirement ([#14853], thanks @HertzDevil)
+- *(runtime)* Reword `Pointer#memcmp`'s documentation ([#14818], thanks @HertzDevil)
+- *(runtime)* Add documentation for `NoReturn` and `Void` ([#14817], thanks @nobodywasishere)
+
+[#14881]: https://github.com/crystal-lang/crystal/pull/14881
+[#14889]: https://github.com/crystal-lang/crystal/pull/14889
+[#14854]: https://github.com/crystal-lang/crystal/pull/14854
+[#15027]: https://github.com/crystal-lang/crystal/pull/15027
+[#15069]: https://github.com/crystal-lang/crystal/pull/15069
+[#14853]: https://github.com/crystal-lang/crystal/pull/14853
+[#14818]: https://github.com/crystal-lang/crystal/pull/14818
+[#14817]: https://github.com/crystal-lang/crystal/pull/14817
+
+### Specs
+
+#### stdlib
+
+- Remove some uses of deprecated overloads in standard library specs ([#14963], thanks @HertzDevil)
+- *(collection)* Disable `Tuple#to_static_array` spec on AArch64 ([#14844], thanks @HertzDevil)
+- *(serialization)* Add JSON parsing UTF-8 spec ([#14823], thanks @Blacksmoke16)
+- *(text)* Add specs for `String#index`, `#rindex` search for `Char::REPLACEMENT` ([#14946], thanks @straight-shoota)
+
+[#14963]: https://github.com/crystal-lang/crystal/pull/14963
+[#14844]: https://github.com/crystal-lang/crystal/pull/14844
+[#14823]: https://github.com/crystal-lang/crystal/pull/14823
+[#14946]: https://github.com/crystal-lang/crystal/pull/14946
+
+#### compiler
+
+- *(codegen)* Support return types in codegen specs ([#14888], thanks @HertzDevil)
+- *(codegen)* Fix codegen spec for `ProcPointer` of virtual type ([#14903], thanks @HertzDevil)
+- *(codegen)* Support LLVM OrcV2 codegen specs ([#14886], thanks @HertzDevil)
+- *(codegen)* Don't spawn subprocess if codegen spec uses flags but not the prelude ([#14904], thanks @HertzDevil)
+
+[#14888]: https://github.com/crystal-lang/crystal/pull/14888
+[#14903]: https://github.com/crystal-lang/crystal/pull/14903
+[#14886]: https://github.com/crystal-lang/crystal/pull/14886
+[#14904]: https://github.com/crystal-lang/crystal/pull/14904
+
+### Infrastructure
+
+- Changelog for 1.14.0 ([#14969], thanks @straight-shoota)
+- Update previous Crystal release 1.13.1 ([#14810], thanks @straight-shoota)
+- Refactor GitHub changelog generator print special infra ([#14795], thanks @straight-shoota)
+- Update distribution-scripts ([#14877], thanks @straight-shoota)
+- Update version in `shard.yml` ([#14909], thanks @straight-shoota)
+- Merge `release/1.13`@1.13.2 ([#14924], thanks @straight-shoota)
+- Update previous Crystal release 1.13.2 ([#14925], thanks @straight-shoota)
+- Merge `release/1.13`@1.13.3 ([#15012], thanks @straight-shoota)
+- Update previous Crystal release 1.13.3 ([#15016], thanks @straight-shoota)
+- **[regression]** Fix `SOURCE_DATE_EPOCH` in `Makefile.win` ([#14922], thanks @HertzDevil)
+- *(ci)* Update actions/checkout action to v4 - autoclosed ([#14896], thanks @renovate)
+- *(ci)* Update LLVM 18 for `wasm32-test` ([#14821], thanks @straight-shoota)
+- *(ci)* Pin package repos for OpenSSL packages ([#14831], thanks @straight-shoota)
+- *(ci)* Upgrade XCode 15.4.0 ([#14794], thanks @straight-shoota)
+- *(ci)* Update GH Actions ([#14535], thanks @renovate)
+- *(ci)* Add test for OpenSSL 3.3 ([#14873], thanks @straight-shoota)
+- *(ci)* Update GitHub runner to `macos-14` ([#14833], thanks @straight-shoota)
+- *(ci)* Refactor SSL workflow with job matrix ([#14899], thanks @straight-shoota)
+- *(ci)* Drop the non-release Windows compiler artifact ([#15000], thanks @HertzDevil)
+- *(ci)* Fix compiler artifact name in WindowsCI ([#15021], thanks @straight-shoota)
+- *(ci)* Restrict CI runners from runs-on to 8GB ([#15030], thanks @straight-shoota)
+- *(ci)* Increase memory for stdlib CI runners from runs-on to 16GB ([#15044], thanks @straight-shoota)
+- *(ci)* Use Cygwin to build libiconv on Windows CI ([#14999], thanks @HertzDevil)
+- *(ci)* Use our own `libffi` repository on Windows CI ([#14998], thanks @HertzDevil)
+
+[#14969]: https://github.com/crystal-lang/crystal/pull/14969
+[#14810]: https://github.com/crystal-lang/crystal/pull/14810
+[#14795]: https://github.com/crystal-lang/crystal/pull/14795
+[#14877]: https://github.com/crystal-lang/crystal/pull/14877
+[#14909]: https://github.com/crystal-lang/crystal/pull/14909
+[#14924]: https://github.com/crystal-lang/crystal/pull/14924
+[#14925]: https://github.com/crystal-lang/crystal/pull/14925
+[#15012]: https://github.com/crystal-lang/crystal/pull/15012
+[#15016]: https://github.com/crystal-lang/crystal/pull/15016
+[#14922]: https://github.com/crystal-lang/crystal/pull/14922
+[#14896]: https://github.com/crystal-lang/crystal/pull/14896
+[#14821]: https://github.com/crystal-lang/crystal/pull/14821
+[#14831]: https://github.com/crystal-lang/crystal/pull/14831
+[#14794]: https://github.com/crystal-lang/crystal/pull/14794
+[#14535]: https://github.com/crystal-lang/crystal/pull/14535
+[#14873]: https://github.com/crystal-lang/crystal/pull/14873
+[#14833]: https://github.com/crystal-lang/crystal/pull/14833
+[#14899]: https://github.com/crystal-lang/crystal/pull/14899
+[#15000]: https://github.com/crystal-lang/crystal/pull/15000
+[#15021]: https://github.com/crystal-lang/crystal/pull/15021
+[#15030]: https://github.com/crystal-lang/crystal/pull/15030
+[#15044]: https://github.com/crystal-lang/crystal/pull/15044
+[#14999]: https://github.com/crystal-lang/crystal/pull/14999
+[#14998]: https://github.com/crystal-lang/crystal/pull/14998
+
+## [1.13.3] (2024-09-18)
+
+[1.13.3]: https://github.com/crystal-lang/crystal/releases/1.13.3
+
+### Bugfixes
+
+#### stdlib
+
+- **[regression]** Fix use global paths in macro bodies ([#14965], thanks @straight-shoota)
+- *(system)* **[regression]** Fix `Process.exec` stream redirection on Windows ([#14986], thanks @HertzDevil)
+- *(text)* **[regression]** Fix `String#index` and `#rindex` for `Char::REPLACEMENT` ([#14937], thanks @HertzDevil)
+
+[#14965]: https://github.com/crystal-lang/crystal/pull/14965
+[#14986]: https://github.com/crystal-lang/crystal/pull/14986
+[#14937]: https://github.com/crystal-lang/crystal/pull/14937
+
+### Infrastructure
+
+- Changelog for 1.13.3 ([#14991], thanks @straight-shoota)
+- *(ci)* Enable runners from `runs-on.com` for Aarch64 CI ([#15007], thanks @straight-shoota)
+
+[#14991]: https://github.com/crystal-lang/crystal/pull/14991
+[#15007]: https://github.com/crystal-lang/crystal/pull/15007
+
+## [1.13.2] (2024-08-20)
+
+[1.13.2]: https://github.com/crystal-lang/crystal/releases/1.13.2
+
+### Bugfixes
+
+#### stdlib
+
+- *(collection)* Fix explicitly clear deleted `Hash::Entry` ([#14862], thanks @HertzDevil)
+
+[#14862]: https://github.com/crystal-lang/crystal/pull/14862
+
+#### compiler
+
+- *(codegen)* Fix `ReferenceStorage(T)` atomic if `T` has no inner pointers ([#14845], thanks @HertzDevil)
+- *(codegen)* Fix misaligned store in `Bool` to union upcasts ([#14906], thanks @HertzDevil)
+- *(interpreter)* Fix misaligned stack access in the interpreter ([#14843], thanks @HertzDevil)
+
+[#14845]: https://github.com/crystal-lang/crystal/pull/14845
+[#14906]: https://github.com/crystal-lang/crystal/pull/14906
+[#14843]: https://github.com/crystal-lang/crystal/pull/14843
+
+### Infrastructure
+
+- Changelog for 1.13.2 ([#14914], thanks @straight-shoota)
+
+[#14914]: https://github.com/crystal-lang/crystal/pull/14914
+
## [1.13.1] (2024-07-12)
[1.13.1]: https://github.com/crystal-lang/crystal/releases/1.13.1
diff --git a/Makefile b/Makefile
index e56a14a27c1c..d30db53464f7 100644
--- a/Makefile
+++ b/Makefile
@@ -21,21 +21,22 @@ all:
## Run generators (Unicode, SSL config, ...)
## $ make -B generate_data
-CRYSTAL ?= crystal ## which previous crystal compiler use
+CRYSTAL ?= crystal## which previous crystal compiler use
LLVM_CONFIG ?= ## llvm-config command path to use
-release ?= ## Compile in release mode
-stats ?= ## Enable statistics output
-progress ?= ## Enable progress output
-threads ?= ## Maximum number of threads to use
-debug ?= ## Add symbolic debug info
-verbose ?= ## Run specs in verbose mode
-junit_output ?= ## Path to output junit results
-static ?= ## Enable static linking
-target ?= ## Cross-compilation target
-interpreter ?= ## Enable interpreter feature
-check ?= ## Enable only check when running format
-order ?=random ## Enable order for spec execution (values: "default" | "random" | seed number)
+release ?= ## Compile in release mode
+stats ?= ## Enable statistics output
+progress ?= ## Enable progress output
+threads ?= ## Maximum number of threads to use
+debug ?= ## Add symbolic debug info
+verbose ?= ## Run specs in verbose mode
+junit_output ?= ## Path to output junit results
+static ?= ## Enable static linking
+target ?= ## Cross-compilation target
+interpreter ?= ## Enable interpreter feature
+check ?= ## Enable only check when running format
+order ?=random ## Enable order for spec execution (values: "default" | "random" | seed number)
+deref_symlinks ?= ## Deference symbolic links for `make install`
O := .build
SOURCES := $(shell find src -name '*.cr')
@@ -48,7 +49,8 @@ CRYSTAL_CONFIG_BUILD_COMMIT ?= $(shell git rev-parse --short HEAD 2> /dev/null)
CRYSTAL_CONFIG_PATH := '$$ORIGIN/../share/crystal/src'
CRYSTAL_VERSION ?= $(shell cat src/VERSION)
SOURCE_DATE_EPOCH ?= $(shell (cat src/SOURCE_DATE_EPOCH || (git show -s --format=%ct HEAD || stat -c "%Y" Makefile || stat -f "%m" Makefile)) 2> /dev/null)
-ifeq ($(shell command -v ld.lld >/dev/null && uname -s),Linux)
+check_lld := command -v ld.lld >/dev/null && case "$$(uname -s)" in MINGW32*|MINGW64*|Linux) echo 1;; esac
+ifeq ($(shell $(check_lld)),1)
EXPORT_CC ?= CC="$(CC) -fuse-ld=lld"
endif
EXPORTS := \
@@ -60,11 +62,21 @@ EXPORTS_BUILD := \
CRYSTAL_CONFIG_LIBRARY_PATH=$(CRYSTAL_CONFIG_LIBRARY_PATH)
SHELL = sh
LLVM_CONFIG := $(shell src/llvm/ext/find-llvm-config)
-LLVM_VERSION := $(if $(LLVM_CONFIG),$(shell $(LLVM_CONFIG) --version 2> /dev/null))
+LLVM_VERSION := $(if $(LLVM_CONFIG),$(shell "$(LLVM_CONFIG)" --version 2> /dev/null))
LLVM_EXT_DIR = src/llvm/ext
LLVM_EXT_OBJ = $(LLVM_EXT_DIR)/llvm_ext.o
CXXFLAGS += $(if $(debug),-g -O0)
+# MSYS2 support (native Windows should use `Makefile.win` instead)
+ifeq ($(OS),Windows_NT)
+ EXE := .exe
+ WINDOWS := 1
+else
+ EXE :=
+ WINDOWS :=
+endif
+CRYSTAL_BIN := crystal$(EXE)
+
DESTDIR ?=
PREFIX ?= /usr/local
BINDIR ?= $(DESTDIR)$(PREFIX)/bin
@@ -74,9 +86,9 @@ DATADIR ?= $(DESTDIR)$(PREFIX)/share/crystal
INSTALL ?= /usr/bin/install
ifeq ($(or $(TERM),$(TERM),dumb),dumb)
- colorize = $(shell printf >&2 "$1")
+ colorize = $(shell printf "%s" "$1" >&2)
else
- colorize = $(shell printf >&2 "\033[33m$1\033[0m\n")
+ colorize = $(shell printf "\033[33m%s\033[0m\n" "$1" >&2)
endif
DEPS = $(LLVM_EXT_OBJ)
@@ -102,28 +114,28 @@ test: spec ## Run tests
spec: std_spec primitives_spec compiler_spec
.PHONY: std_spec
-std_spec: $(O)/std_spec ## Run standard library specs
- $(O)/std_spec $(SPEC_FLAGS)
+std_spec: $(O)/std_spec$(EXE) ## Run standard library specs
+ $(O)/std_spec$(EXE) $(SPEC_FLAGS)
.PHONY: compiler_spec
-compiler_spec: $(O)/compiler_spec ## Run compiler specs
- $(O)/compiler_spec $(SPEC_FLAGS)
+compiler_spec: $(O)/compiler_spec$(EXE) ## Run compiler specs
+ $(O)/compiler_spec$(EXE) $(SPEC_FLAGS)
.PHONY: primitives_spec
-primitives_spec: $(O)/primitives_spec ## Run primitives specs
- $(O)/primitives_spec $(SPEC_FLAGS)
+primitives_spec: $(O)/primitives_spec$(EXE) ## Run primitives specs
+ $(O)/primitives_spec$(EXE) $(SPEC_FLAGS)
.PHONY: interpreter_spec
-interpreter_spec: $(O)/interpreter_spec ## Run interpreter specs
- $(O)/interpreter_spec $(SPEC_FLAGS)
+interpreter_spec: $(O)/interpreter_spec$(EXE) ## Run interpreter specs
+ $(O)/interpreter_spec$(EXE) $(SPEC_FLAGS)
.PHONY: smoke_test
smoke_test: ## Build specs as a smoke test
-smoke_test: $(O)/std_spec $(O)/compiler_spec $(O)/crystal
+smoke_test: $(O)/std_spec$(EXE) $(O)/compiler_spec$(EXE) $(O)/$(CRYSTAL_BIN)
.PHONY: all_spec
-all_spec: $(O)/all_spec ## Run all specs (note: this builds a huge program; `test` recipe builds individual binaries and is recommended for reduced resource usage)
- $(O)/all_spec $(SPEC_FLAGS)
+all_spec: $(O)/all_spec$(EXE) ## Run all specs (note: this builds a huge program; `test` recipe builds individual binaries and is recommended for reduced resource usage)
+ $(O)/all_spec$(EXE) $(SPEC_FLAGS)
.PHONY: samples
samples: ## Build example programs
@@ -136,7 +148,7 @@ docs: ## Generate standard library documentation
cp -av doc/ docs/
.PHONY: crystal
-crystal: $(O)/crystal ## Build the compiler
+crystal: $(O)/$(CRYSTAL_BIN) ## Build the compiler
.PHONY: deps llvm_ext
deps: $(DEPS) ## Build dependencies
@@ -151,12 +163,12 @@ generate_data: ## Run generator scripts for Unicode, SSL config, ...
$(MAKE) -B -f scripts/generate_data.mk
.PHONY: install
-install: $(O)/crystal man/crystal.1.gz ## Install the compiler at DESTDIR
+install: $(O)/$(CRYSTAL_BIN) man/crystal.1.gz ## Install the compiler at DESTDIR
$(INSTALL) -d -m 0755 "$(BINDIR)/"
- $(INSTALL) -m 0755 "$(O)/crystal" "$(BINDIR)/crystal"
+ $(INSTALL) -m 0755 "$(O)/$(CRYSTAL_BIN)" "$(BINDIR)/$(CRYSTAL_BIN)"
$(INSTALL) -d -m 0755 $(DATADIR)
- cp -av src "$(DATADIR)/src"
+ cp $(if $(deref_symlinks),-rvL --preserve=all,-av) src "$(DATADIR)/src"
rm -rf "$(DATADIR)/$(LLVM_EXT_OBJ)" # Don't install llvm_ext.o
$(INSTALL) -d -m 0755 "$(MANDIR)/man1/"
@@ -171,9 +183,16 @@ install: $(O)/crystal man/crystal.1.gz ## Install the compiler at DESTDIR
$(INSTALL) -d -m 0755 "$(DESTDIR)$(PREFIX)/share/fish/vendor_completions.d/"
$(INSTALL) -m 644 etc/completion.fish "$(DESTDIR)$(PREFIX)/share/fish/vendor_completions.d/crystal.fish"
+ifeq ($(WINDOWS),1)
+.PHONY: install_dlls
+install_dlls: $(O)/$(CRYSTAL_BIN) ## Install the compiler's dependent DLLs at DESTDIR (Windows only)
+ $(INSTALL) -d -m 0755 "$(BINDIR)/"
+ @ldd $(O)/$(CRYSTAL_BIN) | grep -iv ' => /c/windows/system32' | sed 's/.* => //; s/ (.*//' | xargs -t -i $(INSTALL) -m 0755 '{}' "$(BINDIR)/"
+endif
+
.PHONY: uninstall
uninstall: ## Uninstall the compiler from DESTDIR
- rm -f "$(BINDIR)/crystal"
+ rm -f "$(BINDIR)/$(CRYSTAL_BIN)"
rm -rf "$(DATADIR)/src"
@@ -195,36 +214,39 @@ uninstall_docs: ## Uninstall docs from DESTDIR
rm -rf "$(DATADIR)/docs"
rm -rf "$(DATADIR)/examples"
-$(O)/all_spec: $(DEPS) $(SOURCES) $(SPEC_SOURCES)
+$(O)/all_spec$(EXE): $(DEPS) $(SOURCES) $(SPEC_SOURCES)
$(call check_llvm_config)
@mkdir -p $(O)
$(EXPORT_CC) $(EXPORTS) ./bin/crystal build $(FLAGS) $(SPEC_WARNINGS_OFF) -o $@ spec/all_spec.cr
-$(O)/std_spec: $(DEPS) $(SOURCES) $(SPEC_SOURCES)
+$(O)/std_spec$(EXE): $(DEPS) $(SOURCES) $(SPEC_SOURCES)
$(call check_llvm_config)
@mkdir -p $(O)
$(EXPORT_CC) ./bin/crystal build $(FLAGS) $(SPEC_WARNINGS_OFF) -o $@ spec/std_spec.cr
-$(O)/compiler_spec: $(DEPS) $(SOURCES) $(SPEC_SOURCES)
+$(O)/compiler_spec$(EXE): $(DEPS) $(SOURCES) $(SPEC_SOURCES)
$(call check_llvm_config)
@mkdir -p $(O)
$(EXPORT_CC) $(EXPORTS) ./bin/crystal build $(FLAGS) $(SPEC_WARNINGS_OFF) -o $@ spec/compiler_spec.cr --release
-$(O)/primitives_spec: $(O)/crystal $(DEPS) $(SOURCES) $(SPEC_SOURCES)
+$(O)/primitives_spec$(EXE): $(O)/$(CRYSTAL_BIN) $(DEPS) $(SOURCES) $(SPEC_SOURCES)
@mkdir -p $(O)
$(EXPORT_CC) ./bin/crystal build $(FLAGS) $(SPEC_WARNINGS_OFF) -o $@ spec/primitives_spec.cr
-$(O)/interpreter_spec: $(DEPS) $(SOURCES) $(SPEC_SOURCES)
+$(O)/interpreter_spec$(EXE): $(DEPS) $(SOURCES) $(SPEC_SOURCES)
$(eval interpreter=1)
@mkdir -p $(O)
$(EXPORT_CC) ./bin/crystal build $(FLAGS) $(SPEC_WARNINGS_OFF) -o $@ spec/compiler/interpreter_spec.cr
-$(O)/crystal: $(DEPS) $(SOURCES)
+$(O)/$(CRYSTAL_BIN): $(DEPS) $(SOURCES)
$(call check_llvm_config)
@mkdir -p $(O)
@# NOTE: USE_PCRE1 is only used for testing compatibility with legacy environments that don't provide libpcre2.
@# Newly built compilers should never be distributed with libpcre to ensure syntax consistency.
- $(EXPORTS) $(EXPORTS_BUILD) ./bin/crystal build $(FLAGS) -o $@ src/compiler/crystal.cr -D without_openssl -D without_zlib $(if $(USE_PCRE1),-D use_pcre,-D use_pcre2)
+ $(EXPORTS) $(EXPORTS_BUILD) ./bin/crystal build $(FLAGS) -o $(if $(WINDOWS),$(O)/crystal-next.exe,$@) src/compiler/crystal.cr -D without_openssl -D without_zlib $(if $(USE_PCRE1),-D use_pcre,-D use_pcre2)
+ @# NOTE: on MSYS2 it is not possible to overwrite a running program, so the compiler must be first built with
+ @# a different filename and then moved to the final destination.
+ $(if $(WINDOWS),mv $(O)/crystal-next.exe $@)
$(LLVM_EXT_OBJ): $(LLVM_EXT_DIR)/llvm_ext.cc
$(call check_llvm_config)
diff --git a/Makefile.win b/Makefile.win
index 89c0f9972a14..0613acc8a207 100644
--- a/Makefile.win
+++ b/Makefile.win
@@ -64,7 +64,7 @@ CRYSTAL_CONFIG_LIBRARY_PATH := $$ORIGIN\lib
CRYSTAL_CONFIG_BUILD_COMMIT := $(shell git rev-parse --short HEAD)
CRYSTAL_CONFIG_PATH := $$ORIGIN\src
CRYSTAL_VERSION ?= $(shell type src\VERSION)
-SOURCE_DATE_EPOCH ?= $(shell type src/SOURCE_DATE_EPOCH || git show -s --format=%ct HEAD)
+SOURCE_DATE_EPOCH ?= $(or $(shell type src\SOURCE_DATE_EPOCH 2>NUL),$(shell git show -s --format=%ct HEAD))
export_vars = $(eval export CRYSTAL_CONFIG_BUILD_COMMIT CRYSTAL_CONFIG_PATH SOURCE_DATE_EPOCH)
export_build_vars = $(eval export CRYSTAL_CONFIG_LIBRARY_PATH)
LLVM_CONFIG ?=
diff --git a/bin/ci b/bin/ci
index 74a1f228ceff..03d8a20a19e4 100755
--- a/bin/ci
+++ b/bin/ci
@@ -135,8 +135,8 @@ format() {
prepare_build() {
on_linux verify_linux_environment
- on_osx curl -L https://github.com/crystal-lang/crystal/releases/download/1.13.1/crystal-1.13.1-1-darwin-universal.tar.gz -o ~/crystal.tar.gz
- on_osx 'pushd ~;gunzip -c ~/crystal.tar.gz | tar xopf -;mv crystal-1.13.1-1 crystal;popd'
+ on_osx curl -L https://github.com/crystal-lang/crystal/releases/download/1.14.0/crystal-1.14.0-1-darwin-universal.tar.gz -o ~/crystal.tar.gz
+ on_osx 'pushd ~;gunzip -c ~/crystal.tar.gz | tar xopf -;mv crystal-1.14.0-1 crystal;popd'
# These commands may take a few minutes to run due to the large size of the repositories.
# This restriction has been made on GitHub's request because updating shallow
@@ -189,7 +189,7 @@ with_build_env() {
on_linux verify_linux_environment
- export DOCKER_TEST_PREFIX="${DOCKER_TEST_PREFIX:=crystallang/crystal:1.13.1}"
+ export DOCKER_TEST_PREFIX="${DOCKER_TEST_PREFIX:=crystallang/crystal:1.14.0}"
case $ARCH in
x86_64)
diff --git a/bin/crystal b/bin/crystal
index 3f7ceb1b88f4..a1fddf1c58b4 100755
--- a/bin/crystal
+++ b/bin/crystal
@@ -137,7 +137,7 @@ SCRIPT_ROOT="$(dirname "$SCRIPT_PATH")"
CRYSTAL_ROOT="$(dirname "$SCRIPT_ROOT")"
CRYSTAL_DIR="$CRYSTAL_ROOT/.build"
-export CRYSTAL_PATH="${CRYSTAL_PATH:-lib:$CRYSTAL_ROOT/src}"
+export CRYSTAL_PATH="${CRYSTAL_PATH:-./lib:$CRYSTAL_ROOT/src}"
if [ -n "${CRYSTAL_PATH##*"$CRYSTAL_ROOT"/src*}" ]; then
__warning_msg "CRYSTAL_PATH env variable does not contain $CRYSTAL_ROOT/src"
fi
@@ -184,10 +184,19 @@ fi
# with symlinks resolved as well (see https://github.com/crystal-lang/crystal/issues/12969).
cd "$(realpath "$(pwd)")"
-if [ -x "$CRYSTAL_DIR/crystal" ]; then
- __warning_msg "Using compiled compiler at ${CRYSTAL_DIR#"$PWD/"}/crystal"
- exec "$CRYSTAL_DIR/crystal" "$@"
-elif !($PARENT_CRYSTAL_EXISTS); then
+case "$(uname -s)" in
+ CYGWIN*|MSYS_NT*|MINGW32_NT*|MINGW64_NT*)
+ CRYSTAL_BIN="crystal.exe"
+ ;;
+ *)
+ CRYSTAL_BIN="crystal"
+ ;;
+esac
+
+if [ -x "$CRYSTAL_DIR/${CRYSTAL_BIN}" ]; then
+ __warning_msg "Using compiled compiler at ${CRYSTAL_DIR#"$PWD/"}/${CRYSTAL_BIN}"
+ exec "$CRYSTAL_DIR/${CRYSTAL_BIN}" "$@"
+elif (! $PARENT_CRYSTAL_EXISTS); then
__error_msg 'You need to have a crystal executable in your path! or set CRYSTAL env variable'
exit 1
else
diff --git a/etc/completion.bash b/etc/completion.bash
index 9263289b5b4e..b64bd110a205 100644
--- a/etc/completion.bash
+++ b/etc/completion.bash
@@ -66,7 +66,7 @@ _crystal()
_crystal_compgen_options "${opts}" "${cur}"
else
if [[ "${prev}" == "tool" ]] ; then
- local subcommands="context dependencies flags format hierarchy implementations types"
+ local subcommands="context dependencies expand flags format hierarchy implementations types unreachable"
_crystal_compgen_options "${subcommands}" "${cur}"
else
_crystal_compgen_sources "${cur}"
diff --git a/etc/completion.fish b/etc/completion.fish
index 64fc6a97b45a..a74d6ecf3cac 100644
--- a/etc/completion.fish
+++ b/etc/completion.fish
@@ -1,5 +1,5 @@
set -l crystal_commands init build clear_cache docs env eval i interactive play run spec tool help version
-set -l tool_subcommands context expand flags format hierarchy implementations types
+set -l tool_subcommands context dependencies expand flags format hierarchy implementations types unreachable
complete -c crystal -s h -l help -d "Show help" -x
@@ -206,6 +206,21 @@ complete -c crystal -n "__fish_seen_subcommand_from implementations" -s p -l pro
complete -c crystal -n "__fish_seen_subcommand_from implementations" -s t -l time -d "Enable execution time output"
complete -c crystal -n "__fish_seen_subcommand_from implementations" -l stdin-filename -d "Source file name to be read from STDIN"
+complete -c crystal -n "__fish_seen_subcommand_from tool; and not __fish_seen_subcommand_from $tool_subcommands" -a "unreachable" -d "show methods that are never called" -x
+complete -c crystal -n "__fish_seen_subcommand_from unreachable" -s D -l define -d "Define a compile-time flag"
+complete -c crystal -n "__fish_seen_subcommand_from unreachable" -s f -l format -d "Output format text (default), json, csv, codecov" -a "text json csv codecov" -f
+complete -c crystal -n "__fish_seen_subcommand_from unreachable" -l tallies -d "Print reachable methods and their call counts as well"
+complete -c crystal -n "__fish_seen_subcommand_from unreachable" -l check -d "Exits with error if there is any unreachable code"
+complete -c crystal -n "__fish_seen_subcommand_from unreachable" -l error-trace -d "Show full error trace"
+complete -c crystal -n "__fish_seen_subcommand_from unreachable" -s i -l include -d "Include path"
+complete -c crystal -n "__fish_seen_subcommand_from unreachable" -s e -l exclude -d "Exclude path (default: lib)"
+complete -c crystal -n "__fish_seen_subcommand_from unreachable" -l no-color -d "Disable colored output"
+complete -c crystal -n "__fish_seen_subcommand_from unreachable" -l prelude -d "Use given file as prelude"
+complete -c crystal -n "__fish_seen_subcommand_from unreachable" -s s -l stats -d "Enable statistics output"
+complete -c crystal -n "__fish_seen_subcommand_from unreachable" -s p -l progress -d "Enable progress output"
+complete -c crystal -n "__fish_seen_subcommand_from unreachable" -s t -l time -d "Enable execution time output"
+complete -c crystal -n "__fish_seen_subcommand_from unreachable" -l stdin-filename -d "Source file name to be read from STDIN"
+
complete -c crystal -n "__fish_seen_subcommand_from tool; and not __fish_seen_subcommand_from $tool_subcommands" -a "types" -d "show type of main variables" -x
complete -c crystal -n "__fish_seen_subcommand_from types" -s D -l define -d "Define a compile-time flag"
complete -c crystal -n "__fish_seen_subcommand_from types" -s f -l format -d "Output format text (default) or json" -a "text json" -f
diff --git a/etc/completion.zsh b/etc/completion.zsh
index ffa12798ca18..0d9ff58a67c2 100644
--- a/etc/completion.zsh
+++ b/etc/completion.zsh
@@ -41,7 +41,8 @@ local -a exec_args; exec_args=(
'(\*)'{-D+,--define=}'[define a compile-time flag]:' \
'(--error-trace)--error-trace[show full error trace]' \
'(-s --stats)'{-s,--stats}'[enable statistics output]' \
- '(-t --time)'{-t,--time}'[enable execution time output]'
+ '(-t --time)'{-t,--time}'[enable execution time output]' \
+ '(-p --progress)'{-p,--progress}'[enable progress output]'
)
local -a format_args; format_args=(
@@ -61,11 +62,15 @@ local -a cursor_args; cursor_args=(
'(-c --cursor)'{-c,--cursor}'[cursor location with LOC as path/to/file.cr:line:column]:LOC'
)
-local -a include_exclude_args; cursor_args=(
+local -a include_exclude_args; include_exclude_args=(
'(-i --include)'{-i,--include}'[Include path in output]' \
'(-i --exclude)'{-i,--exclude}'[Exclude path in output]'
)
+local -a stdin_filename_args; stdin_filename_args=(
+ '(--stdin-filename)--stdin-filename[source file name to be read from STDIN]'
+)
+
local -a programfile; programfile='*:Crystal File:_files -g "*.cr(.)"'
# TODO make 'emit' allow completion with more than one
@@ -170,6 +175,7 @@ _crystal-tool() {
"hierarchy:show type hierarchy"
"implementations:show implementations for given call in location"
"types:show type of main variables"
+ "unreachable:show methods that are never called"
)
_describe -t commands 'Crystal tool command' commands
@@ -187,6 +193,7 @@ _crystal-tool() {
$exec_args \
$format_args \
$prelude_args \
+ $stdin_filename_args \
$cursor_args
;;
@@ -198,6 +205,7 @@ _crystal-tool() {
$exec_args \
'(-f --format)'{-f,--format}'[output format 'tree' (default), 'flat', 'dot', or 'mermaid']:' \
$prelude_args \
+ $stdin_filename_args \
$include_exclude_args
;;
@@ -209,12 +217,14 @@ _crystal-tool() {
$exec_args \
$format_args \
$prelude_args \
+ $stdin_filename_args \
$cursor_args
;;
(flags)
_arguments \
$programfile \
+ $no_color_args \
$help_args
;;
@@ -223,8 +233,9 @@ _crystal-tool() {
$programfile \
$help_args \
$no_color_args \
- $format_args \
+ $include_exclude_args \
'(--check)--check[checks that formatting code produces no changes]' \
+ '(--show-backtrace)--show-backtrace[show backtrace on a bug (used only for debugging)]'
;;
(hierarchy)
@@ -235,6 +246,7 @@ _crystal-tool() {
$exec_args \
$format_args \
$prelude_args \
+ $stdin_filename_args \
'(-e)-e[filter types by NAME regex]:'
;;
@@ -246,7 +258,22 @@ _crystal-tool() {
$exec_args \
$format_args \
$prelude_args \
- $cursor_args
+ $cursor_args \
+ $stdin_filename_args
+ ;;
+
+ (unreachable)
+ _arguments \
+ $programfile \
+ $help_args \
+ $no_color_args \
+ $exec_args \
+ $include_exclude_args \
+ '(-f --format)'{-f,--format}'[output format: text (default), json, csv, codecov]:' \
+ $prelude_args \
+ '(--check)--check[exits with error if there is any unreachable code]' \
+ '(--tallies)--tallies[print reachable methods and their call counts as well]' \
+ $stdin_filename_args
;;
(types)
@@ -256,7 +283,8 @@ _crystal-tool() {
$no_color_args \
$exec_args \
$format_args \
- $prelude_args
+ $prelude_args \
+ $stdin_filename_args
;;
esac
;;
diff --git a/etc/win-ci/build-ffi.ps1 b/etc/win-ci/build-ffi.ps1
index 4340630bea64..eb5ec1e5403c 100644
--- a/etc/win-ci/build-ffi.ps1
+++ b/etc/win-ci/build-ffi.ps1
@@ -7,40 +7,17 @@ param(
. "$(Split-Path -Parent $MyInvocation.MyCommand.Path)\setup.ps1"
[void](New-Item -Name (Split-Path -Parent $BuildTree) -ItemType Directory -Force)
-Setup-Git -Path $BuildTree -Url https://github.com/winlibs/libffi.git -Ref libffi-$Version
+Setup-Git -Path $BuildTree -Url https://github.com/crystal-lang/libffi.git -Ref v$Version
Run-InDirectory $BuildTree {
+ $args = "-DCMAKE_FIND_USE_SYSTEM_ENVIRONMENT_PATH=OFF"
if ($Dynamic) {
- Replace-Text win32\vs16_x64\libffi\libffi.vcxproj 'StaticLibrary' 'DynamicLibrary'
+ $args = "-DBUILD_SHARED_LIBS=ON $args"
+ } else {
+ $args = "-DBUILD_SHARED_LIBS=OFF -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded $args"
}
-
- echo '
-
- $(MsbuildThisFileDirectory)\Override.props
-
- ' > 'Directory.Build.props'
-
- echo "
-
- false
-
-
-
- $(if ($Dynamic) {
- 'FFI_BUILDING_DLL;%(PreprocessorDefinitions)'
- } else {
- 'MultiThreaded'
- })
- None
- false
-
-
- false
-
-
- " > 'Override.props'
-
- MSBuild.exe /p:PlatformToolset=v143 /p:Platform=x64 /p:Configuration=Release win32\vs16_x64\libffi-msvc.sln -target:libffi:Rebuild
+ & $cmake . $args.split(' ')
+ & $cmake --build . --config Release
if (-not $?) {
Write-Host "Error: Failed to build libffi" -ForegroundColor Red
Exit 1
@@ -48,8 +25,8 @@ Run-InDirectory $BuildTree {
}
if ($Dynamic) {
- mv -Force $BuildTree\win32\vs16_x64\x64\Release\libffi.lib libs\ffi-dynamic.lib
- mv -Force $BuildTree\win32\vs16_x64\x64\Release\libffi.dll dlls\
+ mv -Force $BuildTree\Release\libffi.lib libs\ffi-dynamic.lib
+ mv -Force $BuildTree\Release\libffi.dll dlls\
} else {
- mv -Force $BuildTree\win32\vs16_x64\x64\Release\libffi.lib libs\ffi.lib
+ mv -Force $BuildTree\Release\libffi.lib libs\ffi.lib
}
diff --git a/etc/win-ci/build-iconv.ps1 b/etc/win-ci/build-iconv.ps1
index 56d0417bd729..541066c6327f 100644
--- a/etc/win-ci/build-iconv.ps1
+++ b/etc/win-ci/build-iconv.ps1
@@ -1,47 +1,20 @@
param(
[Parameter(Mandatory)] [string] $BuildTree,
+ [Parameter(Mandatory)] [string] $Version,
[switch] $Dynamic
)
. "$(Split-Path -Parent $MyInvocation.MyCommand.Path)\setup.ps1"
[void](New-Item -Name (Split-Path -Parent $BuildTree) -ItemType Directory -Force)
-Setup-Git -Path $BuildTree -Url https://github.com/pffang/libiconv-for-Windows.git -Ref 1353455a6c4e15c9db6865fd9c2bf7203b59c0ec # master@{2022-10-11}
+Invoke-WebRequest "https://ftp.gnu.org/pub/gnu/libiconv/libiconv-${Version}.tar.gz" -OutFile libiconv.tar.gz
+tar -xzf libiconv.tar.gz
+mv libiconv-* $BuildTree
+rm libiconv.tar.gz
Run-InDirectory $BuildTree {
- Replace-Text libiconv\include\iconv.h '__declspec (dllimport) ' ''
-
- echo '
-
- $(MsbuildThisFileDirectory)\Override.props
-
- ' > 'Directory.Build.props'
-
- echo "
-
- false
-
-
-
- None
- false
-
-
- false
-
-
-
-
- MultiThreadedDLL
-
-
- " > 'Override.props'
-
- if ($Dynamic) {
- MSBuild.exe /p:Platform=x64 /p:Configuration=Release libiconv.vcxproj
- } else {
- MSBuild.exe /p:Platform=x64 /p:Configuration=ReleaseStatic libiconv.vcxproj
- }
+ $env:CHERE_INVOKING = 1
+ & 'C:\cygwin64\bin\bash.exe' --login "$PSScriptRoot\cygwin-build-iconv.sh" "$Version" "$(if ($Dynamic) { 1 })"
if (-not $?) {
Write-Host "Error: Failed to build libiconv" -ForegroundColor Red
Exit 1
@@ -49,8 +22,8 @@ Run-InDirectory $BuildTree {
}
if ($Dynamic) {
- mv -Force $BuildTree\output\x64\Release\libiconv.lib libs\iconv-dynamic.lib
- mv -Force $BuildTree\output\x64\Release\libiconv.dll dlls\
+ mv -Force $BuildTree\iconv\lib\iconv.dll.lib libs\iconv-dynamic.lib
+ mv -Force $BuildTree\iconv\bin\iconv-2.dll dlls\
} else {
- mv -Force $BuildTree\output\x64\ReleaseStatic\libiconvStatic.lib libs\iconv.lib
+ mv -Force $BuildTree\iconv\lib\iconv.lib libs\
}
diff --git a/etc/win-ci/cygwin-build-iconv.sh b/etc/win-ci/cygwin-build-iconv.sh
new file mode 100644
index 000000000000..204427be66fa
--- /dev/null
+++ b/etc/win-ci/cygwin-build-iconv.sh
@@ -0,0 +1,36 @@
+#!/bin/sh
+
+set -eo pipefail
+
+Version=$1
+Dynamic=$2
+
+export PATH="$(pwd)/build-aux:$PATH"
+export CC="$(pwd)/build-aux/compile cl -nologo"
+export CXX="$(pwd)/build-aux/compile cl -nologo"
+export AR="$(pwd)/build-aux/ar-lib lib"
+export LD="link"
+export NM="dumpbin -symbols"
+export STRIP=":"
+export RANLIB=":"
+if [ -n "$Dynamic" ]; then
+ export CFLAGS="-MD"
+ export CXXFLAGS="-MD"
+ enable_shared=yes
+ enable_static=no
+else
+ export CFLAGS="-MT"
+ export CXXFLAGS="-MT"
+ enable_shared=no
+ enable_static=yes
+ # GNU libiconv appears to define `BUILDING_DLL` unconditionally, so the static
+ # library contains `/EXPORT` directives that make any executable also export
+ # the iconv symbols, which we don't want
+ find . '(' -name '*.h' -or -name '*.h.build.in' ')' -print0 | xargs -0 -i sed -i 's/__declspec(dllexport)//' '{}'
+fi
+export CPPFLAGS="-O2 -D_WIN32_WINNT=_WIN32_WINNT_WIN7 -I$(pwd)/iconv/include"
+export LDFLAGS="-L$(pwd)/iconv/lib"
+
+./configure --host=x86_64-w64-mingw32 --prefix="$(pwd)/iconv" --enable-shared="${enable_shared}" --enable-static="${enable_static}"
+make
+make install
diff --git a/lib/.shards.info b/lib/.shards.info
index 7f03bb906410..b6371e9397c4 100644
--- a/lib/.shards.info
+++ b/lib/.shards.info
@@ -6,4 +6,4 @@ shards:
version: 0.5.0
reply:
git: https://github.com/i3oris/reply.git
- version: 0.3.1+git.commit.90a7eb5a76048884d5d56bf6b9369f1e67fdbcd7
+ version: 0.3.1+git.commit.db423dae3dd34c6ba5e36174653a0c109117a167
diff --git a/lib/reply/shard.yml b/lib/reply/shard.yml
index e6cd9dab283a..02a0d3490923 100644
--- a/lib/reply/shard.yml
+++ b/lib/reply/shard.yml
@@ -5,7 +5,7 @@ description: "Shard to create a REPL interface"
authors:
- I3oris
-crystal: 1.5.0
+crystal: 1.13.0
license: MIT
diff --git a/lib/reply/spec/reader_spec.cr b/lib/reply/spec/reader_spec.cr
index 4e9f446f3de0..4dbc53cbb51b 100644
--- a/lib/reply/spec/reader_spec.cr
+++ b/lib/reply/spec/reader_spec.cr
@@ -254,7 +254,7 @@ module Reply
reader.auto_completion.verify(open: true, entries: %w(hello hey), name_filter: "h", selection_pos: 0)
reader.editor.verify("42.hello")
- SpecHelper.send(pipe_in, "\e\t") # shit_tab
+ SpecHelper.send(pipe_in, "\e\t") # shift_tab
reader.auto_completion.verify(open: true, entries: %w(hello hey), name_filter: "h", selection_pos: 1)
reader.editor.verify("42.hey")
@@ -298,6 +298,37 @@ module Reply
SpecHelper.send(pipe_in, '\0')
end
+ it "retriggers auto-completion when current word ends with ':'" do
+ reader = SpecHelper.reader(SpecReaderWithAutoCompletionRetrigger)
+ pipe_out, pipe_in = IO.pipe
+
+ spawn do
+ reader.read_next(from: pipe_out)
+ end
+
+ SpecHelper.send(pipe_in, "fo")
+ SpecHelper.send(pipe_in, '\t')
+ reader.auto_completion.verify(open: true, entries: %w(foo foobar), name_filter: "fo")
+ reader.editor.verify("foo")
+
+ SpecHelper.send(pipe_in, ':')
+ SpecHelper.send(pipe_in, ':')
+ reader.auto_completion.verify(open: true, entries: %w(foo::foo foo::foobar foo::bar), name_filter: "foo::")
+ reader.editor.verify("foo::")
+
+ SpecHelper.send(pipe_in, 'b')
+ SpecHelper.send(pipe_in, '\t')
+ reader.auto_completion.verify(open: true, entries: %w(foo::bar), name_filter: "foo::b", selection_pos: 0)
+ reader.editor.verify("foo::bar")
+
+ SpecHelper.send(pipe_in, ':')
+ SpecHelper.send(pipe_in, ':')
+ reader.auto_completion.verify(open: true, entries: %w(foo::bar::foo foo::bar::foobar foo::bar::bar), name_filter: "foo::bar::")
+ reader.editor.verify("foo::bar::")
+
+ SpecHelper.send(pipe_in, '\0')
+ end
+
it "uses escape" do
reader = SpecHelper.reader
pipe_out, pipe_in = IO.pipe
diff --git a/lib/reply/spec/spec_helper.cr b/lib/reply/spec/spec_helper.cr
index 432220b98f98..7e0a93052320 100644
--- a/lib/reply/spec/spec_helper.cr
+++ b/lib/reply/spec/spec_helper.cr
@@ -94,6 +94,27 @@ module Reply
getter auto_completion
end
+ class SpecReaderWithAutoCompletionRetrigger < Reader
+ def initialize
+ super
+ self.word_delimiters.delete(':')
+ end
+
+ def auto_complete(current_word : String, expression_before : String)
+ if current_word.ends_with? "::"
+ return "title", ["#{current_word}foo", "#{current_word}foobar", "#{current_word}bar"]
+ else
+ return "title", %w(foo foobar bar)
+ end
+ end
+
+ def auto_completion_retrigger_when(current_word : String) : Bool
+ current_word.ends_with? ':'
+ end
+
+ getter auto_completion
+ end
+
module SpecHelper
def self.auto_completion(returning results)
results = results.clone
diff --git a/lib/reply/src/char_reader.cr b/lib/reply/src/char_reader.cr
index 3da5ca06d804..c4ab01ca802e 100644
--- a/lib/reply/src/char_reader.cr
+++ b/lib/reply/src/char_reader.cr
@@ -43,20 +43,9 @@ module Reply
@slice_buffer = Bytes.new(buffer_size)
end
- def read_char(from io : T = STDIN) forall T
- {% if flag?(:win32) && T <= IO::FileDescriptor %}
- handle = LibC._get_osfhandle(io.fd)
- raise RuntimeError.from_errno("_get_osfhandle") if handle == -1
-
- raw(io) do
- LibC.ReadConsoleA(LibC::HANDLE.new(handle), @slice_buffer, @slice_buffer.size, out nb_read, nil)
-
- parse_escape_sequence(@slice_buffer[0...nb_read])
- end
- {% else %}
+ def read_char(from io : IO = STDIN)
nb_read = raw(io, &.read(@slice_buffer))
parse_escape_sequence(@slice_buffer[0...nb_read])
- {% end %}
end
private def parse_escape_sequence(chars : Bytes) : Char | Sequence | String?
@@ -184,15 +173,3 @@ module Reply
end
end
end
-
-{% if flag?(:win32) %}
- lib LibC
- STD_INPUT_HANDLE = -10
-
- fun ReadConsoleA(hConsoleInput : Void*,
- lpBuffer : Void*,
- nNumberOfCharsToRead : UInt32,
- lpNumberOfCharsRead : UInt32*,
- pInputControl : Void*) : UInt8
- end
-{% end %}
diff --git a/lib/reply/src/reader.cr b/lib/reply/src/reader.cr
index f8bb5bbb03fd..01228cf7027a 100644
--- a/lib/reply/src/reader.cr
+++ b/lib/reply/src/reader.cr
@@ -168,6 +168,13 @@ module Reply
@auto_completion.default_display_selected_entry(io, entry)
end
+ # Override to retrigger auto completion when condition is met.
+ #
+ # default: `false`
+ def auto_completion_retrigger_when(current_word : String) : Bool
+ false
+ end
+
# Override to enable line re-indenting.
#
# This methods is called each time a character is entered.
@@ -240,8 +247,11 @@ module Reply
if read.is_a?(CharReader::Sequence) && (read.tab? || read.enter? || read.alt_enter? || read.shift_tab? || read.escape? || read.backspace? || read.ctrl_c?)
else
if @auto_completion.open?
- auto_complete_insert_char(read)
- @editor.update
+ replacement = auto_complete_insert_char(read)
+ # Replace the current_word by the replacement word
+ @editor.update do
+ @editor.current_word = replacement if replacement
+ end
end
end
end
@@ -362,12 +372,6 @@ module Reply
end
private def on_tab(shift_tab = false)
- line = @editor.current_line
-
- # Retrieve the word under the cursor
- word_begin, word_end = @editor.current_word_begin_end
- current_word = line[word_begin..word_end]
-
if @auto_completion.open?
if shift_tab
replacement = @auto_completion.selection_previous
@@ -375,15 +379,7 @@ module Reply
replacement = @auto_completion.selection_next
end
else
- # Get whole expression before cursor, allow auto-completion to deduce the receiver type
- expr = @editor.expression_before_cursor(x: word_begin)
-
- # Compute auto-completion, return `replacement` (`nil` if no entry, full name if only one entry, or the begin match of entries otherwise)
- replacement = @auto_completion.complete_on(current_word, expr)
-
- if replacement && @auto_completion.entries.size >= 2
- @auto_completion.open
- end
+ replacement = compute_completions
end
# Replace the current_word by the replacement word
@@ -405,14 +401,40 @@ module Reply
@editor.move_cursor_to_end
end
- private def auto_complete_insert_char(char)
+ private def compute_completions : String?
+ line = @editor.current_line
+
+ # Retrieve the word under the cursor
+ word_begin, word_end = @editor.current_word_begin_end
+ current_word = line[word_begin..word_end]
+
+ expr = @editor.expression_before_cursor(x: word_begin)
+
+ # Compute auto-completion, return `replacement` (`nil` if no entry, full name if only one entry, or the begin match of entries otherwise)
+ replacement = @auto_completion.complete_on(current_word, expr)
+
+ if replacement
+ if @auto_completion.entries.size >= 2
+ @auto_completion.open
+ else
+ @auto_completion.name_filter = replacement
+ end
+ end
+
+ replacement
+ end
+
+ private def auto_complete_insert_char(char) : String?
if char.is_a? Char && !char.in?(@editor.word_delimiters)
- @auto_completion.name_filter = @editor.current_word
+ @auto_completion.name_filter = current_word = @editor.current_word
+
+ return compute_completions if auto_completion_retrigger_when(current_word + char)
elsif @editor.expression_scrolled? || char.is_a?(String)
@auto_completion.close
else
@auto_completion.clear
end
+ nil
end
private def auto_complete_remove_char
diff --git a/lib/reply/src/term_size.cr b/lib/reply/src/term_size.cr
index fd0c60421c4f..3af381101543 100644
--- a/lib/reply/src/term_size.cr
+++ b/lib/reply/src/term_size.cr
@@ -120,10 +120,7 @@ end
dwMaximumWindowSize : COORD
end
- STD_OUTPUT_HANDLE = -11
-
fun GetConsoleScreenBufferInfo(hConsoleOutput : Void*, lpConsoleScreenBufferInfo : CONSOLE_SCREEN_BUFFER_INFO*) : Void
- fun GetStdHandle(nStdHandle : UInt32) : Void*
end
{% else %}
lib LibC
diff --git a/man/crystal.1 b/man/crystal.1
index 04f183dd11e3..9134b8fcc8ef 100644
--- a/man/crystal.1
+++ b/man/crystal.1
@@ -369,7 +369,7 @@ Disable colored output.
.Op --
.Op arguments
.Pp
-Run a tool. The available tools are: context, dependencies, flags, format, hierarchy, implementations, and types.
+Run a tool. The available tools are: context, dependencies, expand, flags, format, hierarchy, implementations, types, and unreachable.
.Pp
Tools:
.Bl -tag -offset indent
@@ -442,7 +442,7 @@ Options:
.It Fl D Ar FLAG, Fl -define= Ar FLAG
Define a compile-time flag. This is useful to conditionally define types, methods, or commands based on flags available at compile time. The default flags are from the target triple given with --target-triple or the hosts default, if none is given.
.It Fl f Ar FORMAT, Fl -format= Ar FORMAT
-Output format 'text' (default), 'json', or 'csv'.
+Output format 'text' (default), 'json', 'codecov', or 'csv'.
.It Fl -tallies
Print reachable methods and their call counts as well.
.It Fl -check
diff --git a/samples/channel_select.cr b/samples/channel_select.cr
index 1ad24e1ff779..25ef96c7db16 100644
--- a/samples/channel_select.cr
+++ b/samples/channel_select.cr
@@ -2,7 +2,7 @@ def generator(n : T) forall T
channel = Channel(T).new
spawn do
loop do
- sleep n
+ sleep n.seconds
channel.send n
end
end
diff --git a/samples/conway.cr b/samples/conway.cr
index b1d9d9089bb0..5178d48f9bd0 100644
--- a/samples/conway.cr
+++ b/samples/conway.cr
@@ -78,7 +78,7 @@ struct ConwayMap
end
end
-PAUSE_MILLIS = 20
+PAUSE = 20.milliseconds
DEFAULT_COUNT = 300
INITIAL_MAP = [
" 1 ",
@@ -99,6 +99,6 @@ spawn { gets; exit }
1.upto(DEFAULT_COUNT) do |i|
puts map
puts "n = #{i}\tPress ENTER to exit"
- sleep PAUSE_MILLIS * 0.001
+ sleep PAUSE
map.next
end
diff --git a/samples/tcp_client.cr b/samples/tcp_client.cr
index 95392dc72601..f4f02d5bdf05 100644
--- a/samples/tcp_client.cr
+++ b/samples/tcp_client.cr
@@ -6,5 +6,5 @@ socket = TCPSocket.new "127.0.0.1", 9000
10.times do |i|
socket.puts i
puts "Server response: #{socket.gets}"
- sleep 0.5
+ sleep 0.5.seconds
end
diff --git a/scripts/github-changelog.cr b/scripts/github-changelog.cr
index f7ae12e74dad..2f89bd923153 100755
--- a/scripts/github-changelog.cr
+++ b/scripts/github-changelog.cr
@@ -367,32 +367,37 @@ puts
puts "[#{milestone.title}]: https://github.com/#{repository}/releases/#{milestone.title}"
puts
+def print_items(prs)
+ prs.each do |pr|
+ puts "- #{pr}"
+ end
+ puts
+
+ prs.each(&.print_ref_label(STDOUT))
+ puts
+end
+
SECTION_TITLES.each do |id, title|
prs = sections[id]? || next
puts "### #{title}"
puts
- topics = prs.group_by(&.primary_topic)
+ if id == "infra"
+ prs.sort_by!(&.infra_sort_tuple)
+ print_items prs
+ else
+ topics = prs.group_by(&.primary_topic)
- topic_titles = topics.keys.sort_by! { |k| TOPIC_ORDER.index(k) || Int32::MAX }
+ topic_titles = topics.keys.sort_by! { |k| TOPIC_ORDER.index(k) || Int32::MAX }
- topic_titles.each do |topic_title|
- topic_prs = topics[topic_title]? || next
+ topic_titles.each do |topic_title|
+ topic_prs = topics[topic_title]? || next
- if id == "infra"
- topic_prs.sort_by!(&.infra_sort_tuple)
- else
- topic_prs.sort!
puts "#### #{topic_title}"
puts
- end
- topic_prs.each do |pr|
- puts "- #{pr}"
+ topic_prs.sort!
+ print_items topic_prs
end
- puts
-
- topic_prs.each(&.print_ref_label(STDOUT))
- puts
end
end
diff --git a/scripts/release-update.sh b/scripts/release-update.sh
index c9fa180f6578..b6216ce3d6df 100755
--- a/scripts/release-update.sh
+++ b/scripts/release-update.sh
@@ -16,6 +16,9 @@ minor_branch="${CRYSTAL_VERSION%.*}"
next_minor="$((${minor_branch#*.} + 1))"
echo "${CRYSTAL_VERSION%%.*}.${next_minor}.0-dev" > src/VERSION
+# Update shard.yml
+sed -i -E "s/version: .*/version: $(cat src/VERSION)/" shard.yml
+
# Remove SOURCE_DATE_EPOCH (only used in source tree of a release)
rm -f src/SOURCE_DATE_EPOCH
diff --git a/scripts/update-changelog.sh b/scripts/update-changelog.sh
index 6fe0fa2839f3..763e63670f43 100755
--- a/scripts/update-changelog.sh
+++ b/scripts/update-changelog.sh
@@ -44,6 +44,10 @@ git switch $branch 2>/dev/null || git switch -c $branch;
echo "${VERSION}" > src/VERSION
git add src/VERSION
+# Update shard.yml
+sed -i -E "s/version: .*/version: ${VERSION}/" shard.yml
+git add shard.yml
+
# Write release date into src/SOURCE_DATE_EPOCH
release_date=$(head -n1 $current_changelog | grep -o -P '(?<=\()[^)]+')
echo "$(date --utc --date="${release_date}" +%s)" > src/SOURCE_DATE_EPOCH
diff --git a/shard.lock b/shard.lock
index e7f2ddc86d10..697bfe23b3c3 100644
--- a/shard.lock
+++ b/shard.lock
@@ -6,5 +6,5 @@ shards:
reply:
git: https://github.com/i3oris/reply.git
- version: 0.3.1+git.commit.90a7eb5a76048884d5d56bf6b9369f1e67fdbcd7
+ version: 0.3.1+git.commit.db423dae3dd34c6ba5e36174653a0c109117a167
diff --git a/shard.yml b/shard.yml
index 396d91bdffe2..4ddf0dcfb0df 100644
--- a/shard.yml
+++ b/shard.yml
@@ -1,5 +1,5 @@
name: crystal
-version: 1.13.0-dev
+version: 1.15.0-dev
authors:
- Crystal Core Team
@@ -14,7 +14,7 @@ dependencies:
github: icyleaf/markd
reply:
github: I3oris/reply
- commit: 90a7eb5a76048884d5d56bf6b9369f1e67fdbcd7
+ commit: db423dae3dd34c6ba5e36174653a0c109117a167
license: Apache-2.0
diff --git a/shell.nix b/shell.nix
index 8db6d1ebc1d4..9aacbed2575b 100644
--- a/shell.nix
+++ b/shell.nix
@@ -53,18 +53,18 @@ let
# Hashes obtained using `nix-prefetch-url --unpack `
latestCrystalBinary = genericBinary ({
x86_64-darwin = {
- url = "https://github.com/crystal-lang/crystal/releases/download/1.13.1/crystal-1.13.1-1-darwin-universal.tar.gz";
- sha256 = "sha256:0wrfv7bgqwfi76p9s48zg4j953kvjsj5cv59slhhc62lllx926zm";
+ url = "https://github.com/crystal-lang/crystal/releases/download/1.14.0/crystal-1.14.0-1-darwin-universal.tar.gz";
+ sha256 = "sha256:09mp3mngj4wik4v2bffpms3x8dksmrcy0a7hs4cg8b13hrfdrgww";
};
aarch64-darwin = {
- url = "https://github.com/crystal-lang/crystal/releases/download/1.13.1/crystal-1.13.1-1-darwin-universal.tar.gz";
- sha256 = "sha256:0wrfv7bgqwfi76p9s48zg4j953kvjsj5cv59slhhc62lllx926zm";
+ url = "https://github.com/crystal-lang/crystal/releases/download/1.14.0/crystal-1.14.0-1-darwin-universal.tar.gz";
+ sha256 = "sha256:09mp3mngj4wik4v2bffpms3x8dksmrcy0a7hs4cg8b13hrfdrgww";
};
x86_64-linux = {
- url = "https://github.com/crystal-lang/crystal/releases/download/1.13.1/crystal-1.13.1-1-linux-x86_64.tar.gz";
- sha256 = "sha256:1dghcv8qgjcbq1r0d2saa21xzp4h7pkan6fnmn6hpickib678g7x";
+ url = "https://github.com/crystal-lang/crystal/releases/download/1.14.0/crystal-1.14.0-1-linux-x86_64.tar.gz";
+ sha256 = "sha256:0p5b22ivggf9xlw91cbhib7n4lzd8is1shd3480jjp14rn1kiy5z";
};
}.${pkgs.stdenv.system});
diff --git a/spec/compiler/codegen/and_spec.cr b/spec/compiler/codegen/and_spec.cr
index 337cceb138eb..7aa3cdfd6c7b 100644
--- a/spec/compiler/codegen/and_spec.cr
+++ b/spec/compiler/codegen/and_spec.cr
@@ -2,42 +2,42 @@ require "../../spec_helper"
describe "Code gen: and" do
it "codegens and with bool false and false" do
- run("false && false").to_b.should be_false
+ run("false && false", Bool).should be_false
end
it "codegens and with bool false and true" do
- run("false && true").to_b.should be_false
+ run("false && true", Bool).should be_false
end
it "codegens and with bool true and true" do
- run("true && true").to_b.should be_true
+ run("true && true", Bool).should be_true
end
it "codegens and with bool true and false" do
- run("true && false").to_b.should be_false
+ run("true && false", Bool).should be_false
end
it "codegens and with bool and int 1" do
- run("struct Bool; def to_i!; 0; end; end; (false && 2).to_i!").to_i.should eq(0)
+ run("struct Bool; def to_i!; 0; end; end; (false && 2).to_i!", Int32).should eq(0)
end
it "codegens and with bool and int 2" do
- run("struct Bool; def to_i!; 0; end; end; (true && 2).to_i!").to_i.should eq(2)
+ run("struct Bool; def to_i!; 0; end; end; (true && 2).to_i!", Int32).should eq(2)
end
it "codegens and with primitive type other than bool" do
- run("1 && 2").to_i.should eq(2)
+ run("1 && 2", Int32).should eq(2)
end
it "codegens and with primitive type other than bool with union" do
- run("(1 && 1.5).to_f").to_f64.should eq(1.5)
+ run("(1 && 1.5).to_f", Float64).should eq(1.5)
end
it "codegens and with primitive type other than bool" do
run(%(
struct Nil; def to_i!; 0; end; end
(nil && 2).to_i!
- )).to_i.should eq(0)
+ ), Int32).should eq(0)
end
it "codegens and with nilable as left node 1" do
@@ -47,7 +47,7 @@ describe "Code gen: and" do
a = Reference.new
a = nil
(a && 2).to_i!
- ").to_i.should eq(0)
+ ", Int32).should eq(0)
end
it "codegens and with nilable as left node 2" do
@@ -56,7 +56,7 @@ describe "Code gen: and" do
a = nil
a = Reference.new
(a && 2).to_i!
- ").to_i.should eq(2)
+ ", Int32).should eq(2)
end
it "codegens and with non-false union as left node" do
@@ -64,7 +64,7 @@ describe "Code gen: and" do
a = 1.5
a = 1
(a && 2).to_i!
- ").to_i.should eq(2)
+ ", Int32).should eq(2)
end
it "codegens and with nil union as left node 1" do
@@ -73,7 +73,7 @@ describe "Code gen: and" do
a = nil
a = 1
(a && 2).to_i!
- ").to_i.should eq(2)
+ ", Int32).should eq(2)
end
it "codegens and with nil union as left node 2" do
@@ -82,7 +82,7 @@ describe "Code gen: and" do
a = 1
a = nil
(a && 2).to_i!
- ").to_i.should eq(0)
+ ", Int32).should eq(0)
end
it "codegens and with bool union as left node 1" do
@@ -91,7 +91,7 @@ describe "Code gen: and" do
a = false
a = 1
(a && 2).to_i!
- ").to_i.should eq(2)
+ ", Int32).should eq(2)
end
it "codegens and with bool union as left node 2" do
@@ -100,7 +100,7 @@ describe "Code gen: and" do
a = 1
a = false
(a && 2).to_i!
- ").to_i.should eq(0)
+ ", Int32).should eq(0)
end
it "codegens and with bool union as left node 3" do
@@ -109,7 +109,7 @@ describe "Code gen: and" do
a = 1
a = true
(a && 2).to_i!
- ").to_i.should eq(2)
+ ", Int32).should eq(2)
end
it "codegens and with bool union as left node 1" do
@@ -120,7 +120,7 @@ describe "Code gen: and" do
a = nil
a = 2
(a && 3).to_i!
- ").to_i.should eq(3)
+ ", Int32).should eq(3)
end
it "codegens and with bool union as left node 2" do
@@ -131,7 +131,7 @@ describe "Code gen: and" do
a = 2
a = false
(a && 3).to_i!
- ").to_i.should eq(1)
+ ", Int32).should eq(1)
end
it "codegens and with bool union as left node 3" do
@@ -142,7 +142,7 @@ describe "Code gen: and" do
a = 2
a = true
(a && 3).to_i!
- ").to_i.should eq(3)
+ ", Int32).should eq(3)
end
it "codegens and with bool union as left node 4" do
@@ -153,14 +153,14 @@ describe "Code gen: and" do
a = true
a = nil
(a && 3).to_i!
- ").to_i.should eq(0)
+ ", Int32).should eq(0)
end
it "codegens assign in right node, after must be nilable" do
run("
a = 1 == 2 && (b = Reference.new)
b.nil?
- ").to_b.should be_true
+ ", Bool).should be_true
end
it "codegens assign in right node, inside if must not be nil" do
@@ -173,7 +173,7 @@ describe "Code gen: and" do
else
0
end
- ").to_i.should eq(1)
+ ", Int32).should eq(1)
end
it "codegens assign in right node, after if must be nilable" do
@@ -181,6 +181,6 @@ describe "Code gen: and" do
if 1 == 2 && (b = Reference.new)
end
b.nil?
- ").to_b.should be_true
+ ", Bool).should be_true
end
end
diff --git a/spec/compiler/codegen/c_enum_spec.cr b/spec/compiler/codegen/c_enum_spec.cr
index c5197799d2cf..75c9966c6c10 100644
--- a/spec/compiler/codegen/c_enum_spec.cr
+++ b/spec/compiler/codegen/c_enum_spec.cr
@@ -20,15 +20,22 @@ describe "Code gen: c enum" do
end
[
+ {"+1", 1},
+ {"-1", -1},
+ {"~1", -2},
{"1 + 2", 3},
{"3 - 2", 1},
{"3 * 2", 6},
+ {"1 &+ 2", 3},
+ {"3 &- 2", 1},
+ {"3 &* 2", 6},
# {"10 / 2", 5}, # MathInterpreter only works with Integer and 10 / 2 : Float
{"10 // 2", 5},
{"1 << 3", 8},
{"100 >> 3", 12},
{"10 & 3", 2},
{"10 | 3", 11},
+ {"10 ^ 3", 9},
{"(1 + 2) * 3", 9},
{"10 % 3", 1},
].each do |(code, expected)|
diff --git a/spec/compiler/codegen/debug_spec.cr b/spec/compiler/codegen/debug_spec.cr
index 4a57056fc7a3..0032fcb64b4c 100644
--- a/spec/compiler/codegen/debug_spec.cr
+++ b/spec/compiler/codegen/debug_spec.cr
@@ -160,8 +160,6 @@ describe "Code gen: debug" do
it "has debug info in closure inside if (#5593)" do
codegen(%(
- require "prelude"
-
def foo
if true && true
yield 1
diff --git a/spec/compiler/codegen/macro_spec.cr b/spec/compiler/codegen/macro_spec.cr
index 0cae55711568..fcf1092192b4 100644
--- a/spec/compiler/codegen/macro_spec.cr
+++ b/spec/compiler/codegen/macro_spec.cr
@@ -1885,4 +1885,9 @@ describe "Code gen: macro" do
{% end %}
)).to_i.should eq(10)
end
+
+ it "accepts compile-time flags" do
+ run("{{ flag?(:foo) ? 1 : 0 }}", flags: %w(foo)).to_i.should eq(1)
+ run("{{ flag?(:foo) ? 1 : 0 }}", Int32, flags: %w(foo)).should eq(1)
+ end
end
diff --git a/spec/compiler/codegen/pointer_spec.cr b/spec/compiler/codegen/pointer_spec.cr
index 1230d80cb5f6..da132cdee406 100644
--- a/spec/compiler/codegen/pointer_spec.cr
+++ b/spec/compiler/codegen/pointer_spec.cr
@@ -492,28 +492,33 @@ describe "Code gen: pointer" do
)).to_b.should be_true
end
- it "takes pointerof lib external var" do
- test_c(
- %(
- int external_var = 0;
- ),
- %(
- lib LibFoo
- $external_var : Int32
- end
-
- LibFoo.external_var = 1
-
- ptr = pointerof(LibFoo.external_var)
- x = ptr.value
-
- ptr.value = 10
- y = ptr.value
-
- ptr.value = 100
- z = LibFoo.external_var
-
- x + y + z
- ), &.to_i.should eq(111))
- end
+ # FIXME: `$external_var` implies __declspec(dllimport), but we only have an
+ # object file, so MinGW-w64 fails linking (actually MSVC also emits an
+ # LNK4217 linker warning)
+ {% unless flag?(:win32) && flag?(:gnu) %}
+ it "takes pointerof lib external var" do
+ test_c(
+ %(
+ int external_var = 0;
+ ),
+ %(
+ lib LibFoo
+ $external_var : Int32
+ end
+
+ LibFoo.external_var = 1
+
+ ptr = pointerof(LibFoo.external_var)
+ x = ptr.value
+
+ ptr.value = 10
+ y = ptr.value
+
+ ptr.value = 100
+ z = LibFoo.external_var
+
+ x + y + z
+ ), &.to_i.should eq(111))
+ end
+ {% end %}
end
diff --git a/spec/compiler/codegen/proc_spec.cr b/spec/compiler/codegen/proc_spec.cr
index 217f2b8ba9a5..65b2731e5ac6 100644
--- a/spec/compiler/codegen/proc_spec.cr
+++ b/spec/compiler/codegen/proc_spec.cr
@@ -862,6 +862,59 @@ describe "Code gen: proc" do
))
end
+ it "returns proc as function pointer inside top-level fun (#14691)" do
+ run(<<-CRYSTAL, Int32).should eq(8)
+ def raise(msg)
+ while true
+ end
+ end
+
+ fun add : Int32, Int32 -> Int32
+ ->(x : Int32, y : Int32) { x &+ y }
+ end
+
+ add.call(3, 5)
+ CRYSTAL
+ end
+
+ it "returns ProcPointer inside top-level fun (#14691)" do
+ run(<<-CRYSTAL, Int32).should eq(8)
+ def raise(msg)
+ while true
+ end
+ end
+
+ fun foo(x : Int32) : Int32
+ x &+ 5
+ end
+
+ fun bar : Int32 -> Int32
+ ->foo(Int32)
+ end
+
+ bar.call(3)
+ CRYSTAL
+ end
+
+ it "raises if returning closure from top-level fun (#14691)" do
+ run(<<-CRYSTAL).to_b.should be_true
+ require "prelude"
+
+ @[Raises]
+ fun foo(x : Int32) : -> Int32
+ -> { x }
+ end
+
+ begin
+ foo(1)
+ rescue
+ true
+ else
+ false
+ end
+ CRYSTAL
+ end
+
it "closures var on ->var.call (#8584)" do
run(%(
def bar(x)
@@ -966,7 +1019,6 @@ describe "Code gen: proc" do
)).to_i.should eq(1)
end
- # FIXME: JIT compilation of this spec is broken, forcing normal compilation (#10961)
it "doesn't crash when taking a proc pointer to a virtual type (#9823)" do
run(%(
abstract struct Parent
@@ -990,7 +1042,7 @@ describe "Code gen: proc" do
end
Child1.new.as(Parent).get
- ), flags: [] of String)
+ ), Proc(Int32, Int32, Int32))
end
it "doesn't crash when taking a proc pointer that multidispatches on the top-level (#3822)" do
diff --git a/spec/compiler/codegen/thread_local_spec.cr b/spec/compiler/codegen/thread_local_spec.cr
index 694cb430b8c1..386043f2c5fd 100644
--- a/spec/compiler/codegen/thread_local_spec.cr
+++ b/spec/compiler/codegen/thread_local_spec.cr
@@ -1,4 +1,4 @@
-{% skip_file if flag?(:openbsd) %}
+{% skip_file if flag?(:openbsd) || (flag?(:win32) && flag?(:gnu)) %}
require "../../spec_helper"
diff --git a/spec/compiler/codegen/union_type_spec.cr b/spec/compiler/codegen/union_type_spec.cr
index eb561a92dbdd..8ea7d058bff9 100644
--- a/spec/compiler/codegen/union_type_spec.cr
+++ b/spec/compiler/codegen/union_type_spec.cr
@@ -215,4 +215,23 @@ describe "Code gen: union type" do
Union(Nil, Int32).foo
)).to_string.should eq("TupleLiteral")
end
+
+ it "respects union payload alignment when upcasting Bool (#14898)" do
+ mod = codegen(<<-CRYSTAL)
+ x = uninitialized Bool | UInt8[64]
+ x = true
+ CRYSTAL
+
+ str = mod.to_s
+ {% if LibLLVM::IS_LT_150 %}
+ str.should contain("store i512 1, i512* %2, align 8")
+ {% else %}
+ str.should contain("store i512 1, ptr %1, align 8")
+ {% end %}
+
+ # an i512 store defaults to 16-byte alignment, which is undefined behavior
+ # as it overestimates the actual alignment of `x`'s data field (x86 in
+ # particular segfaults on misaligned 16-byte stores)
+ str.should_not contain("align 16")
+ end
end
diff --git a/spec/compiler/crystal/tools/doc/project_info_spec.cr b/spec/compiler/crystal/tools/doc/project_info_spec.cr
index 61bf20c2da67..c92ee9d12f9d 100644
--- a/spec/compiler/crystal/tools/doc/project_info_spec.cr
+++ b/spec/compiler/crystal/tools/doc/project_info_spec.cr
@@ -5,6 +5,8 @@ private alias ProjectInfo = Crystal::Doc::ProjectInfo
private def run_git(command)
Process.run(%(git -c user.email="" -c user.name="spec" #{command}), shell: true)
+rescue IO::Error
+ pending! "Git is not available"
end
private def assert_with_defaults(initial, expected, *, file = __FILE__, line = __LINE__)
diff --git a/spec/compiler/crystal/tools/init_spec.cr b/spec/compiler/crystal/tools/init_spec.cr
index 71bbd8de9d35..9149986a673c 100644
--- a/spec/compiler/crystal/tools/init_spec.cr
+++ b/spec/compiler/crystal/tools/init_spec.cr
@@ -41,9 +41,17 @@ private def run_init_project(skeleton_type, name, author, email, github_name, di
).run
end
+private def git_available?
+ Process.run(Crystal::Git.executable).success?
+rescue IO::Error
+ false
+end
+
module Crystal
describe Init::InitProject do
it "correctly uses git config" do
+ pending! "Git is not available" unless git_available?
+
within_temporary_directory do
File.write(".gitconfig", <<-INI)
[user]
@@ -212,9 +220,11 @@ module Crystal
)
end
- with_file "example/.git/config" { }
+ if git_available?
+ with_file "example/.git/config" { }
- with_file "other-example-directory/.git/config" { }
+ with_file "other-example-directory/.git/config" { }
+ end
end
end
end
diff --git a/spec/compiler/crystal/tools/repl_spec.cr b/spec/compiler/crystal/tools/repl_spec.cr
index 3a1e1275ef12..7a387624f8fa 100644
--- a/spec/compiler/crystal/tools/repl_spec.cr
+++ b/spec/compiler/crystal/tools/repl_spec.cr
@@ -17,4 +17,53 @@ describe Crystal::Repl do
success_value(repl.parse_and_interpret("def foo; 1 + 2; end")).value.should eq(nil)
success_value(repl.parse_and_interpret("foo")).value.should eq(3)
end
+
+ describe "can return static and runtime type information for" do
+ it "Non Union" do
+ repl = Crystal::Repl.new
+ repl.prelude = "primitives"
+ repl.load_prelude
+
+ repl_value = success_value(repl.parse_and_interpret("1"))
+ repl_value.type.to_s.should eq("Int32")
+ repl_value.runtime_type.to_s.should eq("Int32")
+ end
+
+ it "MixedUnionType" do
+ repl = Crystal::Repl.new
+ repl.prelude = "primitives"
+ repl.load_prelude
+
+ repl_value = success_value(repl.parse_and_interpret("1 || \"a\""))
+ repl_value.type.to_s.should eq("(Int32 | String)")
+ repl_value.runtime_type.to_s.should eq("Int32")
+ end
+
+ it "UnionType" do
+ repl = Crystal::Repl.new
+ repl.prelude = "primitives"
+ repl.load_prelude
+
+ repl_value = success_value(repl.parse_and_interpret("true || 1"))
+ repl_value.type.to_s.should eq("(Bool | Int32)")
+ repl_value.runtime_type.to_s.should eq("Bool")
+ end
+
+ it "VirtualType" do
+ repl = Crystal::Repl.new
+ repl.prelude = "primitives"
+ repl.load_prelude
+
+ repl.parse_and_interpret <<-CRYSTAL
+ class Foo
+ end
+
+ class Bar < Foo
+ end
+ CRYSTAL
+ repl_value = success_value(repl.parse_and_interpret("Bar.new || Foo.new"))
+ repl_value.type.to_s.should eq("Foo+") # Maybe should Foo to match typeof
+ repl_value.runtime_type.to_s.should eq("Bar")
+ end
+ end
end
diff --git a/spec/compiler/crystal/tools/unreachable_spec.cr b/spec/compiler/crystal/tools/unreachable_spec.cr
index 12ed82499740..f94277348e6c 100644
--- a/spec/compiler/crystal/tools/unreachable_spec.cr
+++ b/spec/compiler/crystal/tools/unreachable_spec.cr
@@ -112,6 +112,14 @@ describe "unreachable" do
CRYSTAL
end
+ it "handles circular hierarchy references (#14034)" do
+ assert_unreachable <<-CRYSTAL
+ class Foo
+ alias Bar = Foo
+ end
+ CRYSTAL
+ end
+
it "finds initializer" do
assert_unreachable <<-CRYSTAL
class Foo
diff --git a/spec/compiler/ffi/ffi_spec.cr b/spec/compiler/ffi/ffi_spec.cr
index ec644e45870d..a4718edb3501 100644
--- a/spec/compiler/ffi/ffi_spec.cr
+++ b/spec/compiler/ffi/ffi_spec.cr
@@ -27,7 +27,7 @@ private def dll_search_paths
{% end %}
end
-{% if flag?(:unix) %}
+{% if flag?(:unix) || (flag?(:win32) && flag?(:gnu)) %}
class Crystal::Loader
def self.new(search_paths : Array(String), *, dll_search_paths : Nil)
new(search_paths)
@@ -39,9 +39,17 @@ describe Crystal::FFI::CallInterface do
before_all do
FileUtils.mkdir_p(SPEC_CRYSTAL_LOADER_LIB_PATH)
build_c_dynlib(compiler_datapath("ffi", "sum.c"))
+
+ {% if flag?(:win32) && flag?(:gnu) %}
+ ENV["PATH"] = "#{SPEC_CRYSTAL_LOADER_LIB_PATH}#{Process::PATH_DELIMITER}#{ENV["PATH"]}"
+ {% end %}
end
after_all do
+ {% if flag?(:win32) && flag?(:gnu) %}
+ ENV["PATH"] = ENV["PATH"].delete_at(0, ENV["PATH"].index!(Process::PATH_DELIMITER) + 1)
+ {% end %}
+
FileUtils.rm_rf(SPEC_CRYSTAL_LOADER_LIB_PATH)
end
diff --git a/spec/compiler/formatter/formatter_spec.cr b/spec/compiler/formatter/formatter_spec.cr
index 7c332aac3b0a..02d140088c2d 100644
--- a/spec/compiler/formatter/formatter_spec.cr
+++ b/spec/compiler/formatter/formatter_spec.cr
@@ -203,8 +203,8 @@ describe Crystal::Formatter do
assert_format "def foo ( x , y , ) \n end", "def foo(x, y)\nend"
assert_format "def foo ( x , y ,\n) \n end", "def foo(x, y)\nend"
assert_format "def foo ( x ,\n y ) \n end", "def foo(x,\n y)\nend"
- assert_format "def foo (\nx ,\n y ) \n end", "def foo(\n x,\n y\n)\nend"
- assert_format "class Foo\ndef foo (\nx ,\n y ) \n end\nend", "class Foo\n def foo(\n x,\n y\n )\n end\nend"
+ assert_format "def foo (\nx ,\n y ) \n end", "def foo(\n x,\n y,\n)\nend"
+ assert_format "class Foo\ndef foo (\nx ,\n y ) \n end\nend", "class Foo\n def foo(\n x,\n y,\n )\n end\nend"
assert_format "def foo ( @x) \n end", "def foo(@x)\nend"
assert_format "def foo ( @x, @y) \n end", "def foo(@x, @y)\nend"
assert_format "def foo ( @@x) \n end", "def foo(@@x)\nend"
@@ -277,7 +277,7 @@ describe Crystal::Formatter do
assert_format "def foo(@[AnnOne] @[AnnTwo] &block : Int32 -> ); end", "def foo(@[AnnOne] @[AnnTwo] &block : Int32 ->); end"
assert_format <<-CRYSTAL
def foo(
- @[MyAnn] bar
+ @[MyAnn] bar,
); end
CRYSTAL
@@ -321,14 +321,14 @@ describe Crystal::Formatter do
); end
CRYSTAL
def foo(
- @[MyAnn] bar
+ @[MyAnn] bar,
); end
CRYSTAL
assert_format <<-CRYSTAL
def foo(
@[MyAnn]
- bar
+ bar,
); end
CRYSTAL
@@ -336,7 +336,7 @@ describe Crystal::Formatter do
def foo(
@[MyAnn]
@[MyAnn]
- bar
+ bar,
); end
CRYSTAL
@@ -345,7 +345,7 @@ describe Crystal::Formatter do
@[MyAnn]
@[MyAnn]
bar,
- @[MyAnn] baz
+ @[MyAnn] baz,
); end
CRYSTAL
@@ -355,7 +355,7 @@ describe Crystal::Formatter do
@[MyAnn]
bar,
- @[MyAnn] baz
+ @[MyAnn] baz,
); end
CRYSTAL
@@ -367,7 +367,7 @@ describe Crystal::Formatter do
CRYSTAL
def foo(
@[MyAnn]
- bar
+ bar,
); end
CRYSTAL
@@ -379,7 +379,7 @@ describe Crystal::Formatter do
CRYSTAL
def foo(
@[MyAnn]
- bar
+ bar,
); end
CRYSTAL
@@ -391,7 +391,7 @@ describe Crystal::Formatter do
@[MyAnn] @[MyAnn] baz,
@[MyAnn]
@[MyAnn]
- biz
+ biz,
); end
CRYSTAL
@@ -405,7 +405,7 @@ describe Crystal::Formatter do
@[MyAnn]
@[MyAnn]
- biz
+ biz,
); end
CRYSTAL
@@ -433,7 +433,7 @@ describe Crystal::Formatter do
@[MyAnn]
@[MyAnn]
- biz
+ biz,
); end
CRYSTAL
@@ -568,7 +568,7 @@ describe Crystal::Formatter do
assert_format "with foo yield bar"
context "adds `&` to yielding methods that don't have a block parameter (#8764)" do
- assert_format <<-CRYSTAL, <<-CRYSTAL, flags: %w[method_signature_yield]
+ assert_format <<-CRYSTAL, <<-CRYSTAL
def foo
yield
end
@@ -578,7 +578,7 @@ describe Crystal::Formatter do
end
CRYSTAL
- assert_format <<-CRYSTAL, <<-CRYSTAL, flags: %w[method_signature_yield]
+ assert_format <<-CRYSTAL, <<-CRYSTAL
def foo()
yield
end
@@ -588,7 +588,7 @@ describe Crystal::Formatter do
end
CRYSTAL
- assert_format <<-CRYSTAL, <<-CRYSTAL, flags: %w[method_signature_yield]
+ assert_format <<-CRYSTAL, <<-CRYSTAL
def foo(
)
yield
@@ -600,7 +600,7 @@ describe Crystal::Formatter do
CRYSTAL
# #13091
- assert_format <<-CRYSTAL, <<-CRYSTAL, flags: %w[method_signature_yield]
+ assert_format <<-CRYSTAL, <<-CRYSTAL
def foo # bar
yield
end
@@ -610,7 +610,7 @@ describe Crystal::Formatter do
end
CRYSTAL
- assert_format <<-CRYSTAL, <<-CRYSTAL, flags: %w[method_signature_yield]
+ assert_format <<-CRYSTAL, <<-CRYSTAL
def foo(x)
yield
end
@@ -620,7 +620,7 @@ describe Crystal::Formatter do
end
CRYSTAL
- assert_format <<-CRYSTAL, <<-CRYSTAL, flags: %w[method_signature_yield]
+ assert_format <<-CRYSTAL, <<-CRYSTAL
def foo(x ,)
yield
end
@@ -630,7 +630,7 @@ describe Crystal::Formatter do
end
CRYSTAL
- assert_format <<-CRYSTAL, <<-CRYSTAL, flags: %w[method_signature_yield]
+ assert_format <<-CRYSTAL, <<-CRYSTAL
def foo(x,
y)
yield
@@ -642,7 +642,7 @@ describe Crystal::Formatter do
end
CRYSTAL
- assert_format <<-CRYSTAL, <<-CRYSTAL, flags: %w[method_signature_yield]
+ assert_format <<-CRYSTAL, <<-CRYSTAL
def foo(x,
y,)
yield
@@ -654,7 +654,7 @@ describe Crystal::Formatter do
end
CRYSTAL
- assert_format <<-CRYSTAL, <<-CRYSTAL, flags: %w[method_signature_yield]
+ assert_format <<-CRYSTAL, <<-CRYSTAL
def foo(x
)
yield
@@ -666,7 +666,7 @@ describe Crystal::Formatter do
end
CRYSTAL
- assert_format <<-CRYSTAL, <<-CRYSTAL, flags: %w[method_signature_yield]
+ assert_format <<-CRYSTAL, <<-CRYSTAL
def foo(x,
)
yield
@@ -678,7 +678,7 @@ describe Crystal::Formatter do
end
CRYSTAL
- assert_format <<-CRYSTAL, <<-CRYSTAL, flags: %w[method_signature_yield]
+ assert_format <<-CRYSTAL, <<-CRYSTAL
def foo(
x)
yield
@@ -691,7 +691,7 @@ describe Crystal::Formatter do
end
CRYSTAL
- assert_format <<-CRYSTAL, <<-CRYSTAL, flags: %w[method_signature_yield]
+ assert_format <<-CRYSTAL, <<-CRYSTAL
def foo(
x, y)
yield
@@ -704,7 +704,7 @@ describe Crystal::Formatter do
end
CRYSTAL
- assert_format <<-CRYSTAL, <<-CRYSTAL, flags: %w[method_signature_yield]
+ assert_format <<-CRYSTAL, <<-CRYSTAL
def foo(
x,
y)
@@ -719,7 +719,7 @@ describe Crystal::Formatter do
end
CRYSTAL
- assert_format <<-CRYSTAL, <<-CRYSTAL, flags: %w[method_signature_yield]
+ assert_format <<-CRYSTAL, <<-CRYSTAL
def foo(
x,
)
@@ -734,7 +734,7 @@ describe Crystal::Formatter do
end
CRYSTAL
- assert_format <<-CRYSTAL, <<-CRYSTAL, flags: %w[method_signature_yield]
+ assert_format <<-CRYSTAL, <<-CRYSTAL
def foo(a, **b)
yield
end
@@ -744,172 +744,9 @@ describe Crystal::Formatter do
end
CRYSTAL
- assert_format "macro f\n yield\n {{ yield }}\nend", flags: %w[method_signature_yield]
+ assert_format "macro f\n yield\n {{ yield }}\nend"
end
- context "does not add `&` without flag `method_signature_yield`" do
- assert_format <<-CRYSTAL
- def foo
- yield
- end
- CRYSTAL
-
- assert_format <<-CRYSTAL, <<-CRYSTAL
- def foo()
- yield
- end
- CRYSTAL
- def foo
- yield
- end
- CRYSTAL
-
- assert_format <<-CRYSTAL, <<-CRYSTAL
- def foo(
- )
- yield
- end
- CRYSTAL
- def foo
- yield
- end
- CRYSTAL
-
- # #13091
- assert_format <<-CRYSTAL
- def foo # bar
- yield
- end
- CRYSTAL
-
- assert_format <<-CRYSTAL
- def foo(x)
- yield
- end
- CRYSTAL
-
- assert_format <<-CRYSTAL, <<-CRYSTAL
- def foo(x ,)
- yield
- end
- CRYSTAL
- def foo(x)
- yield
- end
- CRYSTAL
-
- assert_format <<-CRYSTAL
- def foo(x,
- y)
- yield
- end
- CRYSTAL
-
- assert_format <<-CRYSTAL, <<-CRYSTAL
- def foo(x,
- y,)
- yield
- end
- CRYSTAL
- def foo(x,
- y,)
- yield
- end
- CRYSTAL
-
- assert_format <<-CRYSTAL, <<-CRYSTAL
- def foo(x
- )
- yield
- end
- CRYSTAL
- def foo(x)
- yield
- end
- CRYSTAL
-
- assert_format <<-CRYSTAL, <<-CRYSTAL
- def foo(x,
- )
- yield
- end
- CRYSTAL
- def foo(x)
- yield
- end
- CRYSTAL
-
- assert_format <<-CRYSTAL, <<-CRYSTAL
- def foo(
- x)
- yield
- end
- CRYSTAL
- def foo(
- x
- )
- yield
- end
- CRYSTAL
-
- assert_format <<-CRYSTAL, <<-CRYSTAL
- def foo(
- x, y)
- yield
- end
- CRYSTAL
- def foo(
- x, y
- )
- yield
- end
- CRYSTAL
-
- assert_format <<-CRYSTAL, <<-CRYSTAL
- def foo(
- x,
- y)
- yield
- end
- CRYSTAL
- def foo(
- x,
- y
- )
- yield
- end
- CRYSTAL
-
- assert_format <<-CRYSTAL, <<-CRYSTAL
- def foo(
- x,
- )
- yield
- end
- CRYSTAL
- def foo(
- x,
- )
- yield
- end
- CRYSTAL
-
- assert_format <<-CRYSTAL
- def foo(a, **b)
- yield
- end
- CRYSTAL
- end
-
- # Allows trailing commas, but doesn't enforce them
- assert_format <<-CRYSTAL
- def foo(
- a,
- b
- )
- end
- CRYSTAL
-
assert_format <<-CRYSTAL
def foo(
a,
@@ -935,7 +772,7 @@ describe Crystal::Formatter do
CRYSTAL
context "adds trailing comma to def multi-line normal, splat, and double splat parameters" do
- assert_format <<-CRYSTAL, <<-CRYSTAL, flags: %w[def_trailing_comma]
+ assert_format <<-CRYSTAL, <<-CRYSTAL
macro foo(
a,
b
@@ -949,7 +786,7 @@ describe Crystal::Formatter do
end
CRYSTAL
- assert_format <<-CRYSTAL, <<-CRYSTAL, flags: %w[def_trailing_comma]
+ assert_format <<-CRYSTAL, <<-CRYSTAL
macro foo(
a,
*b
@@ -963,7 +800,7 @@ describe Crystal::Formatter do
end
CRYSTAL
- assert_format <<-CRYSTAL, <<-CRYSTAL, flags: %w[def_trailing_comma]
+ assert_format <<-CRYSTAL, <<-CRYSTAL
fun foo(
a : Int32,
b : Int32
@@ -977,7 +814,7 @@ describe Crystal::Formatter do
end
CRYSTAL
- assert_format <<-CRYSTAL, flags: %w[def_trailing_comma]
+ assert_format <<-CRYSTAL
fun foo(
a : Int32,
...
@@ -985,7 +822,7 @@ describe Crystal::Formatter do
end
CRYSTAL
- assert_format <<-CRYSTAL, <<-CRYSTAL, flags: %w[def_trailing_comma]
+ assert_format <<-CRYSTAL, <<-CRYSTAL
def foo(
a,
b
@@ -999,7 +836,7 @@ describe Crystal::Formatter do
end
CRYSTAL
- assert_format <<-CRYSTAL, <<-CRYSTAL, flags: %w[def_trailing_comma]
+ assert_format <<-CRYSTAL, <<-CRYSTAL
def foo(
a : Int32,
b : Int32
@@ -1013,7 +850,7 @@ describe Crystal::Formatter do
end
CRYSTAL
- assert_format <<-CRYSTAL, <<-CRYSTAL, flags: %w[def_trailing_comma]
+ assert_format <<-CRYSTAL, <<-CRYSTAL
def foo(
a : Int32,
b : Int32 = 1
@@ -1027,7 +864,7 @@ describe Crystal::Formatter do
end
CRYSTAL
- assert_format <<-CRYSTAL, <<-CRYSTAL, flags: %w[def_trailing_comma]
+ assert_format <<-CRYSTAL, <<-CRYSTAL
def foo(
a,
b c
@@ -1041,7 +878,7 @@ describe Crystal::Formatter do
end
CRYSTAL
- assert_format <<-CRYSTAL, <<-CRYSTAL, flags: %w[def_trailing_comma]
+ assert_format <<-CRYSTAL, <<-CRYSTAL
def foo(
a,
@[Ann] b
@@ -1055,7 +892,7 @@ describe Crystal::Formatter do
end
CRYSTAL
- assert_format <<-CRYSTAL, <<-CRYSTAL, flags: %w[def_trailing_comma]
+ assert_format <<-CRYSTAL, <<-CRYSTAL
def foo(
a,
@[Ann]
@@ -1071,7 +908,7 @@ describe Crystal::Formatter do
end
CRYSTAL
- assert_format <<-CRYSTAL, <<-CRYSTAL, flags: %w[def_trailing_comma]
+ assert_format <<-CRYSTAL, <<-CRYSTAL
def foo(
a, b
)
@@ -1083,7 +920,7 @@ describe Crystal::Formatter do
end
CRYSTAL
- assert_format <<-CRYSTAL, <<-CRYSTAL, flags: %w[def_trailing_comma]
+ assert_format <<-CRYSTAL, <<-CRYSTAL
def foo(
a, b,
c, d
@@ -1097,7 +934,7 @@ describe Crystal::Formatter do
end
CRYSTAL
- assert_format <<-CRYSTAL, <<-CRYSTAL, flags: %w[def_trailing_comma]
+ assert_format <<-CRYSTAL, <<-CRYSTAL
def foo(
a, # Foo
b # Bar
@@ -1111,7 +948,7 @@ describe Crystal::Formatter do
end
CRYSTAL
- assert_format <<-CRYSTAL, <<-CRYSTAL, flags: %w[def_trailing_comma]
+ assert_format <<-CRYSTAL, <<-CRYSTAL
def foo(
a,
*b
@@ -1125,7 +962,7 @@ describe Crystal::Formatter do
end
CRYSTAL
- assert_format <<-CRYSTAL, <<-CRYSTAL, flags: %w[def_trailing_comma]
+ assert_format <<-CRYSTAL, <<-CRYSTAL
def foo(
a,
**b
@@ -1139,7 +976,7 @@ describe Crystal::Formatter do
end
CRYSTAL
- assert_format <<-CRYSTAL, flags: %w[def_trailing_comma]
+ assert_format <<-CRYSTAL
def foo(
a,
&block
@@ -1147,44 +984,44 @@ describe Crystal::Formatter do
end
CRYSTAL
- assert_format <<-CRYSTAL, flags: %w[def_trailing_comma]
+ assert_format <<-CRYSTAL
def foo(
a,
)
end
CRYSTAL
- assert_format <<-CRYSTAL, flags: %w[def_trailing_comma]
+ assert_format <<-CRYSTAL
def foo(a)
end
CRYSTAL
- assert_format <<-CRYSTAL, flags: %w[def_trailing_comma]
+ assert_format <<-CRYSTAL
def foo(a, b)
end
CRYSTAL
- assert_format <<-CRYSTAL, flags: %w[def_trailing_comma]
+ assert_format <<-CRYSTAL
def foo(a, *args)
end
CRYSTAL
- assert_format <<-CRYSTAL, flags: %w[def_trailing_comma]
+ assert_format <<-CRYSTAL
def foo(a, *args, &block)
end
CRYSTAL
- assert_format <<-CRYSTAL, flags: %w[def_trailing_comma]
+ assert_format <<-CRYSTAL
def foo(a, **kwargs)
end
CRYSTAL
- assert_format <<-CRYSTAL, flags: %w[def_trailing_comma]
+ assert_format <<-CRYSTAL
def foo(a, **kwargs, &block)
end
CRYSTAL
- assert_format <<-CRYSTAL, flags: %w[def_trailing_comma]
+ assert_format <<-CRYSTAL
def foo(a, &block)
end
CRYSTAL
@@ -1709,22 +1546,23 @@ describe Crystal::Formatter do
assert_format "foo = 1\n->foo.[](Int32)"
assert_format "foo = 1\n->foo.[]=(Int32)"
- assert_format "->{ x }"
- assert_format "->{\nx\n}", "->{\n x\n}"
- assert_format "->do\nx\nend", "->do\n x\nend"
- assert_format "->( ){ x }", "->{ x }"
- assert_format "->() do x end", "->do x end"
+ assert_format "->{ x }", "-> { x }"
+ assert_format "->{\nx\n}", "-> {\n x\n}"
+ assert_format "->do\nx\nend", "-> do\n x\nend"
+ assert_format "->( ){ x }", "-> { x }"
+ assert_format "->() do x end", "-> do x end"
assert_format "->( x , y ) { x }", "->(x, y) { x }"
assert_format "->( x : Int32 , y ) { x }", "->(x : Int32, y) { x }"
- assert_format "->{}"
+ assert_format "->{ x }", "-> { x }"
# #13232
- assert_format "->{}", "-> { }", flags: %w[proc_literal_whitespace]
- assert_format "->(){}", "-> { }", flags: %w[proc_literal_whitespace]
- assert_format "->{1}", "-> { 1 }", flags: %w[proc_literal_whitespace]
- assert_format "->(x : Int32) {}", "->(x : Int32) { }", flags: %w[proc_literal_whitespace]
- assert_format "-> : Int32 {}", "-> : Int32 { }", flags: %w[proc_literal_whitespace]
- assert_format "->do\nend", "-> do\nend", flags: %w[proc_literal_whitespace]
+ assert_format "->{}", "-> { }"
+ assert_format "->(){}", "-> { }"
+ assert_format "->{1}", "-> { 1 }"
+ assert_format "->(x : Int32) {}", "->(x : Int32) { }"
+ assert_format "-> : Int32 {}", "-> : Int32 { }"
+ assert_format "->do\nend", "-> do\nend"
+ assert_format "-> : Int32 {}", "-> : Int32 { }"
# Allows whitespace around proc literal, but doesn't enforce them
assert_format "-> { }"
@@ -1733,15 +1571,15 @@ describe Crystal::Formatter do
assert_format "-> : Int32 { }"
assert_format "-> do\nend"
- assert_format "-> : Int32 {}"
+ assert_format "-> : Int32 { }"
assert_format "-> : Int32 | String { 1 }"
- assert_format "-> : Array(Int32) {}"
- assert_format "-> : Int32? {}"
- assert_format "-> : Int32* {}"
- assert_format "-> : Int32[1] {}"
- assert_format "-> : {Int32, String} {}"
+ assert_format "-> : Array(Int32) {}", "-> : Array(Int32) { }"
+ assert_format "-> : Int32? {}", "-> : Int32? { }"
+ assert_format "-> : Int32* {}", "-> : Int32* { }"
+ assert_format "-> : Int32[1] {}", "-> : Int32[1] { }"
+ assert_format "-> : {Int32, String} {}", "-> : {Int32, String} { }"
assert_format "-> : {Int32} { String }"
- assert_format "-> : {x: Int32, y: String} {}"
+ assert_format "-> : {x: Int32, y: String} {}", "-> : {x: Int32, y: String} { }"
assert_format "->\n:\nInt32\n{\n}", "-> : Int32 {\n}"
assert_format "->( x )\n:\nInt32 { }", "->(x) : Int32 { }"
assert_format "->: Int32 do\nx\nend", "-> : Int32 do\n x\nend"
@@ -1929,18 +1767,18 @@ describe Crystal::Formatter do
assert_format "foo((1..3))"
assert_format "foo ()"
assert_format "foo ( )", "foo ()"
- assert_format "def foo(\n\n#foo\nx,\n\n#bar\nz\n)\nend", "def foo(\n # foo\n x,\n\n # bar\n z\n)\nend"
- assert_format "def foo(\nx, #foo\nz #bar\n)\nend", "def foo(\n x, # foo\n z # bar\n)\nend"
+ assert_format "def foo(\n\n#foo\nx,\n\n#bar\nz\n)\nend", "def foo(\n # foo\n x,\n\n # bar\n z,\n)\nend"
+ assert_format "def foo(\nx, #foo\nz #bar\n)\nend", "def foo(\n x, # foo\n z, # bar\n)\nend"
assert_format "a = 1;;; b = 2", "a = 1; b = 2"
assert_format "a = 1\n;\nb = 2", "a = 1\nb = 2"
assert_format "foo do\n # bar\nend"
assert_format "abstract def foo\nabstract def bar"
- assert_format "if 1\n ->{ 1 }\nend"
+ assert_format "if 1\n ->{ 1 }\nend", "if 1\n -> { 1 }\nend"
assert_format "foo.bar do\n baz\n .b\nend"
assert_format "coco.lala\nfoo\n .bar"
assert_format "foo.bar = \n1", "foo.bar =\n 1"
assert_format "foo.bar += \n1", "foo.bar +=\n 1"
- assert_format "->{}"
+ assert_format "->{}", "-> { }"
assert_format "foo &.[a] = 1"
assert_format "[\n # foo\n 1,\n\n # bar\n 2,\n]"
assert_format "[c.x]\n .foo"
@@ -1948,11 +1786,11 @@ describe Crystal::Formatter do
assert_format "bar = foo([\n 1,\n 2,\n 3,\n])"
assert_format "foo({\n 1 => 2,\n 3 => 4,\n 5 => 6,\n})"
assert_format "bar = foo({\n 1 => 2,\n 3 => 4,\n 5 => 6,\n })", "bar = foo({\n 1 => 2,\n 3 => 4,\n 5 => 6,\n})"
- assert_format "foo(->{\n 1 + 2\n})"
- assert_format "bar = foo(->{\n 1 + 2\n})"
- assert_format "foo(->do\n 1 + 2\nend)"
- assert_format "bar = foo(->do\n 1 + 2\nend)"
- assert_format "bar = foo(->{\n 1 + 2\n})"
+ assert_format "foo(->{\n 1 + 2\n})", "foo(-> {\n 1 + 2\n})"
+ assert_format "bar = foo(->{\n 1 + 2\n})", "bar = foo(-> {\n 1 + 2\n})"
+ assert_format "foo(->do\n 1 + 2\nend)", "foo(-> do\n 1 + 2\nend)"
+ assert_format "bar = foo(->do\n 1 + 2\nend)", "bar = foo(-> do\n 1 + 2\nend)"
+ assert_format "bar = foo(->{\n 1 + 2\n})", "bar = foo(-> {\n 1 + 2\n})"
assert_format "case 1\nwhen 2\n 3\n # foo\nelse\n 4\n # bar\nend"
assert_format "1 #=> 2", "1 # => 2"
assert_format "1 #=>2", "1 # => 2"
@@ -2273,11 +2111,11 @@ describe Crystal::Formatter do
assert_format "def foo(a,\n *b)\nend"
assert_format "def foo(a, # comment\n *b)\nend", "def foo(a, # comment\n *b)\nend"
assert_format "def foo(a,\n **b)\nend"
- assert_format "def foo(\n **a\n)\n 1\nend"
+ assert_format "def foo(\n **a\n)\n 1\nend", "def foo(\n **a,\n)\n 1\nend"
assert_format "def foo(**a,)\n 1\nend", "def foo(**a)\n 1\nend"
- assert_format "def foo(\n **a # comment\n)\n 1\nend"
- assert_format "def foo(\n **a\n # comment\n)\n 1\nend"
- assert_format "def foo(\n **a\n\n # comment\n)\n 1\nend"
+ assert_format "def foo(\n **a # comment\n)\n 1\nend", "def foo(\n **a, # comment\n)\n 1\nend"
+ assert_format "def foo(\n **a\n # comment\n)\n 1\nend", "def foo(\n **a,\n # comment\n)\n 1\nend"
+ assert_format "def foo(\n **a\n\n # comment\n)\n 1\nend", "def foo(\n **a,\n\n # comment\n)\n 1\nend"
assert_format "def foo(**b, # comment\n &block)\nend"
assert_format "def foo(a, **b, # comment\n &block)\nend"
@@ -2332,7 +2170,7 @@ describe Crystal::Formatter do
assert_format "alias X = ((Y, Z) ->)"
- assert_format "def x(@y = ->(z) {})\nend"
+ assert_format "def x(@y = ->(z) {})\nend", "def x(@y = ->(z) { })\nend"
assert_format "class X; annotation FooAnnotation ; end ; end", "class X\n annotation FooAnnotation; end\nend"
assert_format "class X\n annotation FooAnnotation \n end \n end", "class X\n annotation FooAnnotation\n end\nend"
@@ -2742,13 +2580,19 @@ describe Crystal::Formatter do
assert_format "a &.a.!"
assert_format "a &.!.!"
- assert_format <<-CRYSTAL
+ assert_format <<-CRYSTAL, <<-CRYSTAL
->{
# first comment
puts "hi"
# second comment
}
CRYSTAL
+ -> {
+ # first comment
+ puts "hi"
+ # second comment
+ }
+ CRYSTAL
# #9014
assert_format <<-CRYSTAL
diff --git a/spec/compiler/interpreter/lib_spec.cr b/spec/compiler/interpreter/lib_spec.cr
index 2c1798645645..bbf6367ee6df 100644
--- a/spec/compiler/interpreter/lib_spec.cr
+++ b/spec/compiler/interpreter/lib_spec.cr
@@ -3,7 +3,7 @@ require "./spec_helper"
require "../loader/spec_helper"
private def ldflags
- {% if flag?(:win32) %}
+ {% if flag?(:msvc) %}
"/LIBPATH:#{SPEC_CRYSTAL_LOADER_LIB_PATH} sum.lib"
{% else %}
"-L#{SPEC_CRYSTAL_LOADER_LIB_PATH} -lsum"
@@ -11,7 +11,7 @@ private def ldflags
end
private def ldflags_with_backtick
- {% if flag?(:win32) %}
+ {% if flag?(:msvc) %}
"/LIBPATH:#{SPEC_CRYSTAL_LOADER_LIB_PATH} `powershell.exe -C Write-Host -NoNewline sum.lib`"
{% else %}
"-L#{SPEC_CRYSTAL_LOADER_LIB_PATH} -l`echo sum`"
@@ -19,12 +19,24 @@ private def ldflags_with_backtick
end
describe Crystal::Repl::Interpreter do
- context "variadic calls" do
- before_all do
- FileUtils.mkdir_p(SPEC_CRYSTAL_LOADER_LIB_PATH)
- build_c_dynlib(compiler_datapath("interpreter", "sum.c"))
- end
+ before_all do
+ FileUtils.mkdir_p(SPEC_CRYSTAL_LOADER_LIB_PATH)
+ build_c_dynlib(compiler_datapath("interpreter", "sum.c"))
+
+ {% if flag?(:win32) %}
+ ENV["PATH"] = "#{SPEC_CRYSTAL_LOADER_LIB_PATH}#{Process::PATH_DELIMITER}#{ENV["PATH"]}"
+ {% end %}
+ end
+
+ after_all do
+ {% if flag?(:win32) %}
+ ENV["PATH"] = ENV["PATH"].delete_at(0, ENV["PATH"].index!(Process::PATH_DELIMITER) + 1)
+ {% end %}
+
+ FileUtils.rm_rf(SPEC_CRYSTAL_LOADER_LIB_PATH)
+ end
+ context "variadic calls" do
it "promotes float" do
interpret(<<-CRYSTAL).should eq 3.5
@[Link(ldflags: #{ldflags.inspect})]
@@ -65,18 +77,9 @@ describe Crystal::Repl::Interpreter do
LibSum.sum_int(2, E::ONE, F::FOUR)
CRYSTAL
end
-
- after_all do
- FileUtils.rm_rf(SPEC_CRYSTAL_LOADER_LIB_PATH)
- end
end
context "command expansion" do
- before_all do
- FileUtils.mkdir_p(SPEC_CRYSTAL_LOADER_LIB_PATH)
- build_c_dynlib(compiler_datapath("interpreter", "sum.c"))
- end
-
it "expands ldflags" do
interpret(<<-CRYSTAL).should eq 4
@[Link(ldflags: #{ldflags_with_backtick.inspect})]
@@ -87,9 +90,5 @@ describe Crystal::Repl::Interpreter do
LibSum.simple_sum_int(2, 2)
CRYSTAL
end
-
- after_all do
- FileUtils.rm_rf(SPEC_CRYSTAL_LOADER_LIB_PATH)
- end
end
end
diff --git a/spec/compiler/interpreter/unions_spec.cr b/spec/compiler/interpreter/unions_spec.cr
index 11bde229b44d..0fa82e8cbddb 100644
--- a/spec/compiler/interpreter/unions_spec.cr
+++ b/spec/compiler/interpreter/unions_spec.cr
@@ -36,6 +36,13 @@ describe Crystal::Repl::Interpreter do
CRYSTAL
end
+ it "returns large union type (#15041)" do
+ interpret(<<-CRYSTAL).should eq(4_i64)
+ a = {3_i64, 4_i64} || nil
+ a.is_a?(Tuple) ? a[1] : 5_i64
+ CRYSTAL
+ end
+
it "put and remove from union in local var" do
interpret(<<-CRYSTAL).should eq(3)
a = 1 == 1 ? 2 : true
diff --git a/spec/compiler/loader/spec_helper.cr b/spec/compiler/loader/spec_helper.cr
index 0db69dc19752..5b2a6454bfa1 100644
--- a/spec/compiler/loader/spec_helper.cr
+++ b/spec/compiler/loader/spec_helper.cr
@@ -8,6 +8,9 @@ def build_c_dynlib(c_filename, *, lib_name = nil, target_dir = SPEC_CRYSTAL_LOAD
{% if flag?(:msvc) %}
o_basename = o_filename.rchop(".lib")
`#{ENV["CC"]? || "cl.exe"} /nologo /LD #{Process.quote(c_filename)} #{Process.quote("/Fo#{o_basename}")} #{Process.quote("/Fe#{o_basename}")}`
+ {% elsif flag?(:win32) && flag?(:gnu) %}
+ o_basename = o_filename.rchop(".a")
+ `#{ENV["CC"]? || "cc"} -shared -fvisibility=hidden #{Process.quote(c_filename)} -o #{Process.quote(o_basename + ".dll")} #{Process.quote("-Wl,--out-implib,#{o_basename}.a")}`
{% else %}
`#{ENV["CC"]? || "cc"} -shared -fvisibility=hidden #{Process.quote(c_filename)} -o #{Process.quote(o_filename)}`
{% end %}
diff --git a/spec/compiler/loader/unix_spec.cr b/spec/compiler/loader/unix_spec.cr
index 42a63b88e860..e3309346803c 100644
--- a/spec/compiler/loader/unix_spec.cr
+++ b/spec/compiler/loader/unix_spec.cr
@@ -40,7 +40,11 @@ describe Crystal::Loader do
exc = expect_raises(Crystal::Loader::LoadError, /no such file|not found|cannot open/i) do
Crystal::Loader.parse(["-l", "foo/bar.o"], search_paths: [] of String)
end
- exc.message.should contain File.join(Dir.current, "foo", "bar.o")
+ {% if flag?(:openbsd) %}
+ exc.message.should contain "foo/bar.o"
+ {% else %}
+ exc.message.should contain File.join(Dir.current, "foo", "bar.o")
+ {% end %}
end
end
@@ -49,7 +53,7 @@ describe Crystal::Loader do
with_env "LD_LIBRARY_PATH": "ld1::ld2", "DYLD_LIBRARY_PATH": nil do
search_paths = Crystal::Loader.default_search_paths
{% if flag?(:darwin) %}
- search_paths.should eq ["/usr/lib", "/usr/local/lib"]
+ search_paths[-2..].should eq ["/usr/lib", "/usr/local/lib"]
{% else %}
search_paths[0, 2].should eq ["ld1", "ld2"]
{% if flag?(:android) %}
diff --git a/spec/compiler/macro/macro_methods_spec.cr b/spec/compiler/macro/macro_methods_spec.cr
index 29de1a51c2be..10ba78d5bdc6 100644
--- a/spec/compiler/macro/macro_methods_spec.cr
+++ b/spec/compiler/macro/macro_methods_spec.cr
@@ -928,6 +928,16 @@ module Crystal
assert_macro %({{["c".id, "b", "a".id].sort}}), %([a, "b", c])
end
+ it "executes sort_by" do
+ assert_macro %({{["abc", "a", "ab"].sort_by { |x| x.size }}}), %(["a", "ab", "abc"])
+ end
+
+ it "calls block exactly once for each element in #sort_by" do
+ assert_macro <<-CRYSTAL, %(5)
+ {{ (i = 0; ["abc", "a", "ab", "abcde", "abcd"].sort_by { i += 1 }; i) }}
+ CRYSTAL
+ end
+
it "executes uniq" do
assert_macro %({{[1, 1, 1, 2, 3, 1, 2, 3, 4].uniq}}), %([1, 2, 3, 4])
end
@@ -1020,10 +1030,6 @@ module Crystal
assert_macro %({{{:a => 1, :b => 3}.size}}), "2"
end
- it "executes sort_by" do
- assert_macro %({{["abc", "a", "ab"].sort_by { |x| x.size }}}), %(["a", "ab", "abc"])
- end
-
it "executes empty?" do
assert_macro %({{{:a => 1}.empty?}}), "false"
end
@@ -1084,6 +1090,12 @@ module Crystal
assert_macro %({{ {'z' => 6, 'a' => 9}.of_value }}), %()
end
+ it "executes has_key?" do
+ assert_macro %({{ {'z' => 6, 'a' => 9}.has_key?('z') }}), %(true)
+ assert_macro %({{ {'z' => 6, 'a' => 9}.has_key?('x') }}), %(false)
+ assert_macro %({{ {'z' => nil, 'a' => 9}.has_key?('z') }}), %(true)
+ end
+
it "executes type" do
assert_macro %({{ x.type }}), %(Headers), {x: HashLiteral.new([] of HashLiteral::Entry, name: Path.new("Headers"))}
end
@@ -1189,6 +1201,14 @@ module Crystal
assert_macro %({% a = {a: 1}; a["a"] = 2 %}{{a["a"]}}), "2"
end
+ it "executes has_key?" do
+ assert_macro %({{{a: 1}.has_key?("a")}}), "true"
+ assert_macro %({{{a: 1}.has_key?(:a)}}), "true"
+ assert_macro %({{{a: nil}.has_key?("a")}}), "true"
+ assert_macro %({{{a: nil}.has_key?("b")}}), "false"
+ assert_macro_error %({{{a: 1}.has_key?(true)}}), "expected 'NamedTupleLiteral#has_key?' first argument to be a SymbolLiteral, StringLiteral or MacroId, not BoolLiteral"
+ end
+
it "creates a named tuple literal with a var" do
assert_macro %({% a = {a: x} %}{{a[:a]}}), "1", {x: 1.int32}
end
@@ -2407,6 +2427,65 @@ module Crystal
end
end
end
+
+ describe "#has_inner_pointers?" do
+ it "works on structs" do
+ assert_macro("{{x.has_inner_pointers?}}", %(false)) do |program|
+ klass = NonGenericClassType.new(program, program, "SomeType", program.struct)
+ klass.struct = true
+ klass.declare_instance_var("@var", program.int32)
+ {x: TypeNode.new(klass)}
+ end
+
+ assert_macro("{{x.has_inner_pointers?}}", %(true)) do |program|
+ klass = NonGenericClassType.new(program, program, "SomeType", program.struct)
+ klass.struct = true
+ klass.declare_instance_var("@var", program.string)
+ {x: TypeNode.new(klass)}
+ end
+ end
+
+ it "works on references" do
+ assert_macro("{{x.has_inner_pointers?}}", %(true)) do |program|
+ klass = NonGenericClassType.new(program, program, "SomeType", program.reference)
+ {x: TypeNode.new(klass)}
+ end
+ end
+
+ it "works on ReferenceStorage" do
+ assert_macro("{{x.has_inner_pointers?}}", %(false)) do |program|
+ reference_storage = GenericReferenceStorageType.new program, program, "ReferenceStorage", program.struct, ["T"]
+ klass = NonGenericClassType.new(program, program, "SomeType", program.reference)
+ klass.declare_instance_var("@var", program.int32)
+ {x: TypeNode.new(reference_storage.instantiate([klass] of TypeVar))}
+ end
+
+ assert_macro("{{x.has_inner_pointers?}}", %(true)) do |program|
+ reference_storage = GenericReferenceStorageType.new program, program, "ReferenceStorage", program.struct, ["T"]
+ klass = NonGenericClassType.new(program, program, "SomeType", program.reference)
+ klass.declare_instance_var("@var", program.string)
+ {x: TypeNode.new(reference_storage.instantiate([klass] of TypeVar))}
+ end
+ end
+
+ it "works on primitive values" do
+ assert_macro("{{x.has_inner_pointers?}}", %(false)) do |program|
+ {x: TypeNode.new(program.int32)}
+ end
+
+ assert_macro("{{x.has_inner_pointers?}}", %(true)) do |program|
+ {x: TypeNode.new(program.void)}
+ end
+
+ assert_macro("{{x.has_inner_pointers?}}", %(true)) do |program|
+ {x: TypeNode.new(program.pointer_of(program.int32))}
+ end
+
+ assert_macro("{{x.has_inner_pointers?}}", %(true)) do |program|
+ {x: TypeNode.new(program.proc_of(program.void))}
+ end
+ end
+ end
end
describe "type declaration methods" do
@@ -2575,6 +2654,14 @@ module Crystal
end
end
+ describe External do
+ it "executes is_a?" do
+ assert_macro %({{x.is_a?(External)}}), "true", {x: External.new("foo", [] of Arg, Nop.new, "foo")}
+ assert_macro %({{x.is_a?(Def)}}), "true", {x: External.new("foo", [] of Arg, Nop.new, "foo")}
+ assert_macro %({{x.is_a?(ASTNode)}}), "true", {x: External.new("foo", [] of Arg, Nop.new, "foo")}
+ end
+ end
+
describe Primitive do
it "executes name" do
assert_macro %({{x.name}}), %(:abc), {x: Primitive.new("abc")}
diff --git a/spec/compiler/parser/parser_spec.cr b/spec/compiler/parser/parser_spec.cr
index 22e9c5feb385..09569b88f003 100644
--- a/spec/compiler/parser/parser_spec.cr
+++ b/spec/compiler/parser/parser_spec.cr
@@ -2606,6 +2606,206 @@ module Crystal
node.end_location.not_nil!.line_number.should eq(5)
end
+ it "sets correct locations of macro if / else" do
+ parser = Parser.new(<<-CR)
+ {% if 1 == val %}
+ "one!"
+ "bar"
+ {% else %}
+ "not one"
+ "bar"
+ {% end %}
+ CR
+
+ node = parser.parse.as MacroIf
+
+ location = node.cond.location.should_not be_nil
+ location.line_number.should eq 1
+ location = node.cond.end_location.should_not be_nil
+ location.line_number.should eq 1
+
+ location = node.then.location.should_not be_nil
+ location.line_number.should eq 1
+ location = node.then.end_location.should_not be_nil
+ location.line_number.should eq 4
+
+ location = node.else.location.should_not be_nil
+ location.line_number.should eq 4
+ location = node.else.end_location.should_not be_nil
+ location.line_number.should eq 7
+ end
+
+ it "sets correct locations of macro if / elsif" do
+ parser = Parser.new(<<-CR)
+ {% if 1 == val %}
+ "one!"
+ "bar"
+ {% elsif 2 == val %}
+ "not one"
+ "bar"
+ {% end %}
+ CR
+
+ node = parser.parse.as MacroIf
+
+ location = node.cond.location.should_not be_nil
+ location.line_number.should eq 1
+ location = node.cond.end_location.should_not be_nil
+ location.line_number.should eq 1
+
+ location = node.then.location.should_not be_nil
+ location.line_number.should eq 1
+ location = node.then.end_location.should_not be_nil
+ location.line_number.should eq 4
+
+ location = node.else.location.should_not be_nil
+ location.line_number.should eq 4
+ location = node.else.end_location.should_not be_nil
+ location.line_number.should eq 7
+ end
+
+ it "sets correct locations of macro if / else / elsif" do
+ parser = Parser.new(<<-CR)
+ {% if 1 == val %}
+ "one!"
+ "bar"
+ {% elsif 2 == val %}
+ "not one"
+ "bar"
+ {% else %}
+ "biz"
+ "blah"
+ {% end %}
+ CR
+
+ node = parser.parse.as MacroIf
+
+ location = node.cond.location.should_not be_nil
+ location.line_number.should eq 1
+ location = node.cond.end_location.should_not be_nil
+ location.line_number.should eq 1
+
+ location = node.then.location.should_not be_nil
+ location.line_number.should eq 1
+ location = node.then.end_location.should_not be_nil
+ location.line_number.should eq 4
+
+ location = node.else.location.should_not be_nil
+ location.line_number.should eq 4
+ location = node.else.end_location.should_not be_nil
+ location.line_number.should eq 10
+ end
+
+ it "sets the correct location for MacroExpressions in a MacroIf" do
+ parser = Parser.new(<<-CR)
+ {% if 1 == 2 %}
+ {{2 * 2}}
+ {% else %}
+ {%
+ 1 + 1
+ 2 + 2
+ %}
+ {% end %}
+ CR
+
+ node = parser.parse.should be_a MacroIf
+ location = node.location.should_not be_nil
+ location.line_number.should eq 1
+ location.column_number.should eq 3
+
+ then_node = node.then.should be_a Expressions
+ then_node_location = then_node.location.should_not be_nil
+ then_node_location.line_number.should eq 1
+ then_node_location = then_node.end_location.should_not be_nil
+ then_node_location.line_number.should eq 3
+
+ then_node_location = then_node.expressions[1].location.should_not be_nil
+ then_node_location.line_number.should eq 2
+ then_node_location = then_node.expressions[1].end_location.should_not be_nil
+ then_node_location.line_number.should eq 2
+
+ else_node = node.else.should be_a Expressions
+ else_node_location = else_node.location.should_not be_nil
+ else_node_location.line_number.should eq 3
+ else_node_location = else_node.end_location.should_not be_nil
+ else_node_location.line_number.should eq 8
+
+ else_node = node.else.should be_a Expressions
+ else_node_location = else_node.expressions[1].location.should_not be_nil
+ else_node_location.line_number.should eq 4
+ else_node_location = else_node.expressions[1].end_location.should_not be_nil
+ else_node_location.line_number.should eq 7
+ end
+
+ it "sets correct location of Begin within another node" do
+ parser = Parser.new(<<-CR)
+ macro finished
+ {% begin %}
+ {{2 * 2}}
+ {%
+ 1 + 1
+ 2 + 2
+ %}
+ {% end %}
+ end
+ CR
+
+ node = parser.parse.should be_a Macro
+ node = node.body.should be_a Expressions
+ node = node.expressions[1].should be_a MacroIf
+
+ location = node.location.should_not be_nil
+ location.line_number.should eq 2
+ location = node.end_location.should_not be_nil
+ location.line_number.should eq 8
+ end
+
+ it "sets correct location of MacroIf within another node" do
+ parser = Parser.new(<<-CR)
+ macro finished
+ {% if false %}
+ {{2 * 2}}
+ {%
+ 1 + 1
+ 2 + 2
+ %}
+ {% end %}
+ end
+ CR
+
+ node = parser.parse.should be_a Macro
+ node = node.body.should be_a Expressions
+ node = node.expressions[1].should be_a MacroIf
+
+ location = node.location.should_not be_nil
+ location.line_number.should eq 2
+ location = node.end_location.should_not be_nil
+ location.line_number.should eq 8
+ end
+
+ it "sets correct location of MacroIf (unless) within another node" do
+ parser = Parser.new(<<-CR)
+ macro finished
+ {% unless false %}
+ {{2 * 2}}
+ {%
+ 1 + 1
+ 2 + 2
+ %}
+ {% end %}
+ end
+ CR
+
+ node = parser.parse.should be_a Macro
+ node = node.body.should be_a Expressions
+ node = node.expressions[1].should be_a MacroIf
+
+ location = node.location.should_not be_nil
+ location.line_number.should eq 2
+ location = node.end_location.should_not be_nil
+ location.line_number.should eq 8
+ end
+
it "sets correct location of trailing ensure" do
parser = Parser.new("foo ensure bar")
node = parser.parse.as(ExceptionHandler)
diff --git a/spec/compiler/semantic/alias_spec.cr b/spec/compiler/semantic/alias_spec.cr
index faf3b81b8e92..3af2f24e5e84 100644
--- a/spec/compiler/semantic/alias_spec.cr
+++ b/spec/compiler/semantic/alias_spec.cr
@@ -178,6 +178,22 @@ describe "Semantic: alias" do
Bar.bar
)) { int32 }
end
+
+ it "reopens #{type} through alias within itself" do
+ assert_type <<-CRYSTAL { int32 }
+ #{type} Foo
+ alias Bar = Foo
+
+ #{type} Bar
+ def self.bar
+ 1
+ end
+ end
+ end
+
+ Foo.bar
+ CRYSTAL
+ end
end
%w(class struct).each do |type|
diff --git a/spec/compiler/semantic/did_you_mean_spec.cr b/spec/compiler/semantic/did_you_mean_spec.cr
index cd3f0856ebcb..1c74ebf74c2f 100644
--- a/spec/compiler/semantic/did_you_mean_spec.cr
+++ b/spec/compiler/semantic/did_you_mean_spec.cr
@@ -75,6 +75,19 @@ describe "Semantic: did you mean" do
"Did you mean 'Foo::Bar'?"
end
+ it "says did you mean for nested class via alias" do
+ assert_error <<-CRYSTAL, "Did you mean 'Boo::Bar'?"
+ class Foo
+ class Bar
+ end
+ end
+
+ alias Boo = Foo
+
+ Boo::Baz.new
+ CRYSTAL
+ end
+
it "says did you mean finds most similar in def" do
assert_error "
def barbaza
diff --git a/spec/compiler/semantic/warnings_spec.cr b/spec/compiler/semantic/warnings_spec.cr
index 6c6914c60fe5..e8bbad7b7c29 100644
--- a/spec/compiler/semantic/warnings_spec.cr
+++ b/spec/compiler/semantic/warnings_spec.cr
@@ -234,7 +234,7 @@ describe "Semantic: warnings" do
# NOTE tempfile might be created in symlinked folder
# which affects how to match current dir /var/folders/...
# with the real path /private/var/folders/...
- path = File.real_path(path)
+ path = File.realpath(path)
main_filename = File.join(path, "main.cr")
output_filename = File.join(path, "main")
@@ -416,7 +416,7 @@ describe "Semantic: warnings" do
# NOTE tempfile might be created in symlinked folder
# which affects how to match current dir /var/folders/...
# with the real path /private/var/folders/...
- path = File.real_path(path)
+ path = File.realpath(path)
main_filename = File.join(path, "main.cr")
output_filename = File.join(path, "main")
diff --git a/spec/llvm-ir/pass-closure-to-c-debug-loc.cr b/spec/llvm-ir/pass-closure-to-c-debug-loc.cr
index a6031798b607..6891ae6ae92f 100644
--- a/spec/llvm-ir/pass-closure-to-c-debug-loc.cr
+++ b/spec/llvm-ir/pass-closure-to-c-debug-loc.cr
@@ -8,7 +8,7 @@ def raise(msg)
end
x = 1
-f = ->{ x }
+f = -> { x }
Foo.foo(f)
# CHECK: define internal i8* @"~check_proc_is_not_closure"(%"->" %0)
diff --git a/spec/llvm-ir/proc-call-debug-loc.cr b/spec/llvm-ir/proc-call-debug-loc.cr
index e83c814f723b..61f02249a9a9 100644
--- a/spec/llvm-ir/proc-call-debug-loc.cr
+++ b/spec/llvm-ir/proc-call-debug-loc.cr
@@ -1,4 +1,4 @@
-x = ->{}
+x = -> { }
x.call
# CHECK: extractvalue %"->" %{{[0-9]+}}, 0
# CHECK-SAME: !dbg [[LOC:![0-9]+]]
diff --git a/spec/primitives/external_command_spec.cr b/spec/primitives/external_command_spec.cr
new file mode 100644
index 000000000000..91687f7c2d21
--- /dev/null
+++ b/spec/primitives/external_command_spec.cr
@@ -0,0 +1,34 @@
+{% skip_file if flag?(:interpreted) %}
+
+require "../spec_helper"
+
+describe Crystal::Command do
+ it "exec external commands", tags: %w[slow] do
+ with_temp_executable "crystal-external" do |path|
+ with_tempfile "crystal-external.cr" do |source_file|
+ File.write source_file, <<-CRYSTAL
+ puts ENV["CRYSTAL"]?
+ puts PROGRAM_NAME
+ puts ARGV
+ CRYSTAL
+
+ Process.run(ENV["CRYSTAL_SPEC_COMPILER_BIN"]? || "bin/crystal", ["build", source_file, "-o", path])
+ end
+
+ File.exists?(path).should be_true
+
+ process = Process.new(ENV["CRYSTAL_SPEC_COMPILER_BIN"]? || "bin/crystal",
+ ["external", "foo", "bar"],
+ output: :pipe,
+ env: {"PATH" => {ENV["PATH"], File.dirname(path)}.join(Process::PATH_DELIMITER)}
+ )
+ output = process.output.gets_to_end
+ status = process.wait
+ status.success?.should be_true
+ lines = output.lines
+ lines[0].should match /crystal/
+ lines[1].should match /crystal-external/
+ lines[2].should eq %(["foo", "bar"])
+ end
+ end
+end
diff --git a/spec/primitives/reference_spec.cr b/spec/primitives/reference_spec.cr
index 13bb024f1ba9..497b49155b5a 100644
--- a/spec/primitives/reference_spec.cr
+++ b/spec/primitives/reference_spec.cr
@@ -37,8 +37,7 @@ describe "Primitives: reference" do
end
end
- # TODO: implement in the interpreter
- pending_interpreted describe: ".pre_initialize" do
+ describe ".pre_initialize" do
it "doesn't fail on complex ivar initializer if value is discarded (#14325)" do
bar_buffer = GC.malloc(instance_sizeof(Outer))
Outer.pre_initialize(bar_buffer)
@@ -55,7 +54,12 @@ describe "Primitives: reference" do
it "sets type ID" do
foo_buffer = GC.malloc(instance_sizeof(Foo))
base = Foo.pre_initialize(foo_buffer).as(Base)
- base.crystal_type_id.should eq(Foo.crystal_instance_type_id)
+ base.should be_a(Foo)
+ base.as(typeof(Foo.crystal_instance_type_id)*).value.should eq(Foo.crystal_instance_type_id)
+ {% unless flag?(:interpreted) %}
+ # FIXME: `Object#crystal_type_id` is incorrect for virtual types in the interpreter (#14967)
+ base.crystal_type_id.should eq(Foo.crystal_instance_type_id)
+ {% end %}
end
it "runs inline instance initializers" do
@@ -89,7 +93,7 @@ describe "Primitives: reference" do
end
end
- pending_interpreted describe: ".unsafe_construct" do
+ describe ".unsafe_construct" do
it "constructs an object in-place" do
foo_buffer = GC.malloc(instance_sizeof(Foo))
foo = Foo.unsafe_construct(foo_buffer, 123_i64)
diff --git a/spec/primitives/slice_spec.cr b/spec/primitives/slice_spec.cr
index 546ae0de5ce1..98bea774df8b 100644
--- a/spec/primitives/slice_spec.cr
+++ b/spec/primitives/slice_spec.cr
@@ -12,6 +12,13 @@ describe "Primitives: Slice" do
slice.to_a.should eq([0, 1, 4, 9, 16, 25] of {{ num }})
slice.read_only?.should be_true
end
+
+ # TODO: these should probably return the same pointers
+ pending_interpreted "creates multiple literals" do
+ slice1 = Slice({{ num }}).literal(1, 2, 3)
+ slice2 = Slice({{ num }}).literal(1, 2, 3)
+ slice1.should eq(slice2)
+ end
{% end %}
end
end
diff --git a/spec/spec_helper.cr b/spec/spec_helper.cr
index ca5bc61ad3c4..d3ccdf13fc87 100644
--- a/spec/spec_helper.cr
+++ b/spec/spec_helper.cr
@@ -284,7 +284,7 @@ def create_spec_compiler
compiler
end
-def run(code, filename = nil, inject_primitives = true, debug = Crystal::Debug::None, flags = nil, *, file = __FILE__)
+def run(code, filename : String? = nil, inject_primitives = true, debug = Crystal::Debug::None, flags = nil, *, file = __FILE__) : LLVM::GenericValue | SpecRunOutput
if inject_primitives
code = %(require "primitives"\n#{code})
end
@@ -294,7 +294,7 @@ def run(code, filename = nil, inject_primitives = true, debug = Crystal::Debug::
# in the current executable!), so instead we compile
# the program and run it, printing the last
# expression and using that to compare the result.
- if code.includes?(%(require "prelude")) || flags
+ if code.includes?(%(require "prelude"))
ast = Parser.parse(code).as(Expressions)
last = ast.expressions.last
assign = Assign.new(Var.new("__tempvar"), last)
@@ -315,7 +315,23 @@ def run(code, filename = nil, inject_primitives = true, debug = Crystal::Debug::
return SpecRunOutput.new(output)
end
else
- new_program.run(code, filename: filename, debug: debug)
+ program = new_program
+ program.flags.concat(flags) if flags
+ program.run(code, filename: filename, debug: debug)
+ end
+end
+
+def run(code, return_type : T.class, filename : String? = nil, inject_primitives = true, debug = Crystal::Debug::None, flags = nil, *, file = __FILE__) forall T
+ if inject_primitives
+ code = %(require "primitives"\n#{code})
+ end
+
+ if code.includes?(%(require "prelude"))
+ fail "TODO: support the prelude in typed codegen specs", file: file
+ else
+ program = new_program
+ program.flags.concat(flags) if flags
+ program.run(code, return_type: T, filename: filename, debug: debug)
end
end
diff --git a/spec/std/benchmark_spec.cr b/spec/std/benchmark_spec.cr
index 2f3c1fb06fd5..63124881c262 100644
--- a/spec/std/benchmark_spec.cr
+++ b/spec/std/benchmark_spec.cr
@@ -12,9 +12,9 @@ describe Benchmark::IPS::Job do
it "works in general / integration test" do
# test several things to avoid running a benchmark over and over again in
# the specs
- j = Benchmark::IPS::Job.new(0.001, 0.001, interactive: false)
- a = j.report("a") { sleep 0.001 }
- b = j.report("b") { sleep 0.002 }
+ j = Benchmark::IPS::Job.new(1.millisecond, 1.millisecond, interactive: false)
+ a = j.report("a") { sleep 1.milliseconds }
+ b = j.report("b") { sleep 2.milliseconds }
j.execute
@@ -31,7 +31,7 @@ describe Benchmark::IPS::Job do
end
private def create_entry
- Benchmark::IPS::Entry.new("label", ->{ 1 + 1 })
+ Benchmark::IPS::Entry.new("label", -> { 1 + 1 })
end
private def h_mean(mean)
diff --git a/spec/std/big/big_float_spec.cr b/spec/std/big/big_float_spec.cr
index 08d7e93bfb0b..4aee9eee51e8 100644
--- a/spec/std/big/big_float_spec.cr
+++ b/spec/std/big/big_float_spec.cr
@@ -345,6 +345,16 @@ describe "BigFloat" do
it { assert_prints (0.1).to_big_f.to_s, "0.100000000000000005551" }
it { assert_prints Float64::MAX.to_big_f.to_s, "1.79769313486231570815e+308" }
it { assert_prints Float64::MIN_POSITIVE.to_big_f.to_s, "2.22507385850720138309e-308" }
+
+ it { (2.to_big_f ** 7133786264).to_s.should end_with("e+2147483648") } # least power of two with a base-10 exponent greater than Int32::MAX
+ it { (2.to_big_f ** -7133786264).to_s.should end_with("e-2147483649") } # least power of two with a base-10 exponent less than Int32::MIN
+ it { (10.to_big_f ** 3000000000 * 1.5).to_s.should end_with("e+3000000000") }
+ it { (10.to_big_f ** -3000000000 * 1.5).to_s.should end_with("e-3000000000") }
+
+ {% unless flag?(:win32) && flag?(:gnu) %}
+ it { (10.to_big_f ** 10000000000 * 1.5).to_s.should end_with("e+10000000000") }
+ it { (10.to_big_f ** -10000000000 * 1.5).to_s.should end_with("e-10000000000") }
+ {% end %}
end
describe "#inspect" do
@@ -547,8 +557,95 @@ describe "BigFloat" do
end
describe "BigFloat Math" do
+ it ".ilogb" do
+ Math.ilogb(0.2.to_big_f).should eq(-3)
+ Math.ilogb(123.45.to_big_f).should eq(6)
+ Math.ilogb(2.to_big_f ** 1_000_000_000).should eq(1_000_000_000)
+
+ {% unless flag?(:win32) && flag?(:gnu) %}
+ Math.ilogb(2.to_big_f ** 100_000_000_000).should eq(100_000_000_000)
+ Math.ilogb(2.to_big_f ** -100_000_000_000).should eq(-100_000_000_000)
+ {% end %}
+
+ expect_raises(ArgumentError) { Math.ilogb(0.to_big_f) }
+ end
+
+ it ".logb" do
+ Math.logb(0.2.to_big_f).should eq(-3.to_big_f)
+ Math.logb(123.45.to_big_f).should eq(6.to_big_f)
+ Math.logb(2.to_big_f ** 1_000_000_000).should eq(1_000_000_000.to_big_f)
+
+ {% unless flag?(:win32) && flag?(:gnu) %}
+ Math.logb(2.to_big_f ** 100_000_000_000).should eq(100_000_000_000.to_big_f)
+ Math.logb(2.to_big_f ** -100_000_000_000).should eq(-100_000_000_000.to_big_f)
+ {% end %}
+
+ expect_raises(ArgumentError) { Math.logb(0.to_big_f) }
+ end
+
+ it ".ldexp" do
+ Math.ldexp(0.2.to_big_f, 2).should eq(0.8.to_big_f)
+ Math.ldexp(0.2.to_big_f, -2).should eq(0.05.to_big_f)
+ Math.ldexp(1.to_big_f, 1_000_000_000).should eq(2.to_big_f ** 1_000_000_000)
+
+ {% unless flag?(:win32) && flag?(:gnu) %}
+ Math.ldexp(1.to_big_f, 100_000_000_000).should eq(2.to_big_f ** 100_000_000_000)
+ Math.ldexp(1.to_big_f, -100_000_000_000).should eq(0.5.to_big_f ** 100_000_000_000)
+ {% end %}
+ end
+
+ it ".scalbn" do
+ Math.scalbn(0.2.to_big_f, 2).should eq(0.8.to_big_f)
+ Math.scalbn(0.2.to_big_f, -2).should eq(0.05.to_big_f)
+ Math.scalbn(1.to_big_f, 1_000_000_000).should eq(2.to_big_f ** 1_000_000_000)
+
+ {% unless flag?(:win32) && flag?(:gnu) %}
+ Math.scalbn(1.to_big_f, 100_000_000_000).should eq(2.to_big_f ** 100_000_000_000)
+ Math.scalbn(1.to_big_f, -100_000_000_000).should eq(0.5.to_big_f ** 100_000_000_000)
+ {% end %}
+ end
+
+ it ".scalbln" do
+ Math.scalbln(0.2.to_big_f, 2).should eq(0.8.to_big_f)
+ Math.scalbln(0.2.to_big_f, -2).should eq(0.05.to_big_f)
+ Math.scalbln(1.to_big_f, 1_000_000_000).should eq(2.to_big_f ** 1_000_000_000)
+
+ {% unless flag?(:win32) && flag?(:gnu) %}
+ Math.scalbln(1.to_big_f, 100_000_000_000).should eq(2.to_big_f ** 100_000_000_000)
+ Math.scalbln(1.to_big_f, -100_000_000_000).should eq(0.5.to_big_f ** 100_000_000_000)
+ {% end %}
+ end
+
it ".frexp" do
+ Math.frexp(0.to_big_f).should eq({0.0, 0})
+ Math.frexp(1.to_big_f).should eq({0.5, 1})
Math.frexp(0.2.to_big_f).should eq({0.8, -2})
+ Math.frexp(2.to_big_f ** 63).should eq({0.5, 64})
+ Math.frexp(2.to_big_f ** 64).should eq({0.5, 65})
+ Math.frexp(2.to_big_f ** 200).should eq({0.5, 201})
+ Math.frexp(2.to_big_f ** -200).should eq({0.5, -199})
+ Math.frexp(2.to_big_f ** 0x7FFFFFFF).should eq({0.5, 0x80000000})
+ Math.frexp(2.to_big_f ** 0x80000000).should eq({0.5, 0x80000001})
+ Math.frexp(2.to_big_f ** 0xFFFFFFFF).should eq({0.5, 0x100000000})
+ Math.frexp(1.75 * 2.to_big_f ** 0x123456789).should eq({0.875, 0x12345678A})
+ Math.frexp(2.to_big_f ** -0x80000000).should eq({0.5, -0x7FFFFFFF})
+ Math.frexp(2.to_big_f ** -0x80000001).should eq({0.5, -0x80000000})
+ Math.frexp(2.to_big_f ** -0x100000000).should eq({0.5, -0xFFFFFFFF})
+ Math.frexp(1.75 * 2.to_big_f ** -0x123456789).should eq({0.875, -0x123456788})
+ Math.frexp(-(2.to_big_f ** 0x7FFFFFFF)).should eq({-0.5, 0x80000000})
+ Math.frexp(-(2.to_big_f ** -0x100000000)).should eq({-0.5, -0xFFFFFFFF})
+ end
+
+ it ".copysign" do
+ Math.copysign(3.to_big_f, 2.to_big_f).should eq(3.to_big_f)
+ Math.copysign(3.to_big_f, 0.to_big_f).should eq(3.to_big_f)
+ Math.copysign(3.to_big_f, -2.to_big_f).should eq(-3.to_big_f)
+ Math.copysign(0.to_big_f, 2.to_big_f).should eq(0.to_big_f)
+ Math.copysign(0.to_big_f, 0.to_big_f).should eq(0.to_big_f)
+ Math.copysign(0.to_big_f, -2.to_big_f).should eq(0.to_big_f)
+ Math.copysign(-3.to_big_f, 2.to_big_f).should eq(3.to_big_f)
+ Math.copysign(-3.to_big_f, 0.to_big_f).should eq(3.to_big_f)
+ Math.copysign(-3.to_big_f, -2.to_big_f).should eq(-3.to_big_f)
end
it ".sqrt" do
diff --git a/spec/std/channel_spec.cr b/spec/std/channel_spec.cr
index 9d121f9d9827..a24790dd8dea 100644
--- a/spec/std/channel_spec.cr
+++ b/spec/std/channel_spec.cr
@@ -82,7 +82,7 @@ describe Channel do
context "receive raise-on-close single-channel" do
it "types" do
ch = Channel(String).new
- spawn_and_wait(->{ ch.send "foo" }) do
+ spawn_and_wait(-> { ch.send "foo" }) do
i, m = Channel.select(ch.receive_select_action)
typeof(i).should eq(Int32)
typeof(m).should eq(String)
@@ -92,7 +92,7 @@ describe Channel do
it "types nilable channel" do
# Yes, although it is discouraged
ch = Channel(Nil).new
- spawn_and_wait(->{ ch.send nil }) do
+ spawn_and_wait(-> { ch.send nil }) do
i, m = Channel.select(ch.receive_select_action)
typeof(i).should eq(Int32)
typeof(m).should eq(Nil)
@@ -101,7 +101,7 @@ describe Channel do
it "raises if channel was closed" do
ch = Channel(String).new
- spawn_and_wait(->{ ch.close }) do
+ spawn_and_wait(-> { ch.close }) do
expect_raises Channel::ClosedError do
Channel.select(ch.receive_select_action)
end
@@ -110,7 +110,7 @@ describe Channel do
it "raises if channel is closed while waiting" do
ch = Channel(String).new
- spawn_and_wait(->{ sleep 0.2; ch.close }) do
+ spawn_and_wait(-> { sleep 0.2.seconds; ch.close }) do
expect_raises Channel::ClosedError do
Channel.select(ch.receive_select_action)
end
@@ -120,7 +120,7 @@ describe Channel do
it "awakes all waiting selects" do
ch = Channel(String).new
- p = ->{
+ p = -> {
begin
Channel.select(ch.receive_select_action)
0
@@ -129,7 +129,7 @@ describe Channel do
end
}
- spawn_and_wait(->{ sleep 0.2; ch.close }) do
+ spawn_and_wait(-> { sleep 0.2.seconds; ch.close }) do
r = parallel p.call, p.call, p.call, p.call
r.should eq({1, 1, 1, 1})
end
@@ -140,7 +140,7 @@ describe Channel do
it "types" do
ch = Channel(String).new
ch2 = Channel(Bool).new
- spawn_and_wait(->{ ch.send "foo" }) do
+ spawn_and_wait(-> { ch.send "foo" }) do
i, m = Channel.select(ch.receive_select_action, ch2.receive_select_action)
typeof(i).should eq(Int32)
typeof(m).should eq(String | Bool)
@@ -151,7 +151,7 @@ describe Channel do
context "receive nil-on-close single-channel" do
it "types" do
ch = Channel(String).new
- spawn_and_wait(->{ ch.send "foo" }) do
+ spawn_and_wait(-> { ch.send "foo" }) do
i, m = Channel.select(ch.receive_select_action?)
typeof(i).should eq(Int32)
typeof(m).should eq(String | Nil)
@@ -161,7 +161,7 @@ describe Channel do
it "types nilable channel" do
# Yes, although it is discouraged
ch = Channel(Nil).new
- spawn_and_wait(->{ ch.send nil }) do
+ spawn_and_wait(-> { ch.send nil }) do
i, m = Channel.select(ch.receive_select_action?)
typeof(i).should eq(Int32)
typeof(m).should eq(Nil)
@@ -170,7 +170,7 @@ describe Channel do
it "returns nil if channel was closed" do
ch = Channel(String).new
- spawn_and_wait(->{ ch.close }) do
+ spawn_and_wait(-> { ch.close }) do
i, m = Channel.select(ch.receive_select_action?)
m.should be_nil
end
@@ -178,7 +178,7 @@ describe Channel do
it "returns nil channel is closed while waiting" do
ch = Channel(String).new
- spawn_and_wait(->{ sleep 0.2; ch.close }) do
+ spawn_and_wait(-> { sleep 0.2.seconds; ch.close }) do
i, m = Channel.select(ch.receive_select_action?)
m.should be_nil
end
@@ -187,11 +187,11 @@ describe Channel do
it "awakes all waiting selects" do
ch = Channel(String).new
- p = ->{
+ p = -> {
Channel.select(ch.receive_select_action?)
}
- spawn_and_wait(->{ sleep 0.2; ch.close }) do
+ spawn_and_wait(-> { sleep 0.2.seconds; ch.close }) do
r = parallel p.call, p.call, p.call, p.call
r.should eq({ {0, nil}, {0, nil}, {0, nil}, {0, nil} })
end
@@ -202,7 +202,7 @@ describe Channel do
it "types" do
ch = Channel(String).new
ch2 = Channel(Bool).new
- spawn_and_wait(->{ ch.send "foo" }) do
+ spawn_and_wait(-> { ch.send "foo" }) do
i, m = Channel.select(ch.receive_select_action?, ch2.receive_select_action?)
typeof(i).should eq(Int32)
typeof(m).should eq(String | Bool | Nil)
@@ -212,7 +212,7 @@ describe Channel do
it "returns index of closed channel" do
ch = Channel(String).new
ch2 = Channel(Bool).new
- spawn_and_wait(->{ ch2.close }) do
+ spawn_and_wait(-> { ch2.close }) do
i, m = Channel.select(ch.receive_select_action?, ch2.receive_select_action?)
i.should eq(1)
m.should eq(nil)
@@ -224,7 +224,7 @@ describe Channel do
it "raises if receive channel was closed and receive? channel was not ready" do
ch = Channel(String).new
ch2 = Channel(String).new
- spawn_and_wait(->{ ch.close }) do
+ spawn_and_wait(-> { ch.close }) do
expect_raises Channel::ClosedError do
Channel.select(ch.receive_select_action, ch2.receive_select_action?)
end
@@ -234,7 +234,7 @@ describe Channel do
it "returns nil if receive channel was not ready and receive? channel was closed" do
ch = Channel(String).new
ch2 = Channel(String).new
- spawn_and_wait(->{ ch2.close }) do
+ spawn_and_wait(-> { ch2.close }) do
i, m = Channel.select(ch.receive_select_action, ch2.receive_select_action?)
i.should eq(1)
m.should eq(nil)
@@ -245,7 +245,7 @@ describe Channel do
context "send raise-on-close single-channel" do
it "types" do
ch = Channel(String).new
- spawn_and_wait(->{ ch.receive }) do
+ spawn_and_wait(-> { ch.receive }) do
i, m = Channel.select(ch.send_select_action("foo"))
typeof(i).should eq(Int32)
typeof(m).should eq(Nil)
@@ -255,7 +255,7 @@ describe Channel do
it "types nilable channel" do
# Yes, although it is discouraged
ch = Channel(Nil).new
- spawn_and_wait(->{ ch.receive }) do
+ spawn_and_wait(-> { ch.receive }) do
i, m = Channel.select(ch.send_select_action(nil))
typeof(i).should eq(Int32)
typeof(m).should eq(Nil)
@@ -264,7 +264,7 @@ describe Channel do
it "raises if channel was closed" do
ch = Channel(String).new
- spawn_and_wait(->{ ch.close }) do
+ spawn_and_wait(-> { ch.close }) do
expect_raises Channel::ClosedError do
Channel.select(ch.send_select_action("foo"))
end
@@ -273,7 +273,7 @@ describe Channel do
it "raises if channel is closed while waiting" do
ch = Channel(String).new
- spawn_and_wait(->{ sleep 0.2; ch.close }) do
+ spawn_and_wait(-> { sleep 0.2.seconds; ch.close }) do
expect_raises Channel::ClosedError do
Channel.select(ch.send_select_action("foo"))
end
@@ -283,7 +283,7 @@ describe Channel do
it "awakes all waiting selects" do
ch = Channel(String).new
- p = ->{
+ p = -> {
begin
Channel.select(ch.send_select_action("foo"))
0
@@ -292,7 +292,7 @@ describe Channel do
end
}
- spawn_and_wait(->{ sleep 0.2; ch.close }) do
+ spawn_and_wait(-> { sleep 0.2.seconds; ch.close }) do
r = parallel p.call, p.call, p.call, p.call
r.should eq({1, 1, 1, 1})
end
@@ -303,7 +303,7 @@ describe Channel do
it "types" do
ch = Channel(String).new
ch2 = Channel(Bool).new
- spawn_and_wait(->{ ch.receive }) do
+ spawn_and_wait(-> { ch.receive }) do
i, m = Channel.select(ch.send_select_action("foo"), ch2.send_select_action(true))
typeof(i).should eq(Int32)
typeof(m).should eq(Nil)
@@ -314,7 +314,7 @@ describe Channel do
context "timeout" do
it "types" do
ch = Channel(String).new
- spawn_and_wait(->{ ch.send "foo" }) do
+ spawn_and_wait(-> { ch.send "foo" }) do
i, m = Channel.select(ch.receive_select_action, timeout_select_action(0.1.seconds))
typeof(i).should eq(Int32)
typeof(m).should eq(String?)
@@ -323,7 +323,7 @@ describe Channel do
it "triggers timeout" do
ch = Channel(String).new
- spawn_and_wait(->{}) do
+ spawn_and_wait(-> { }) do
i, m = Channel.select(ch.receive_select_action, timeout_select_action(0.1.seconds))
i.should eq(1)
@@ -333,7 +333,7 @@ describe Channel do
it "triggers timeout (reverse order)" do
ch = Channel(String).new
- spawn_and_wait(->{}) do
+ spawn_and_wait(-> { }) do
i, m = Channel.select(timeout_select_action(0.1.seconds), ch.receive_select_action)
i.should eq(0)
@@ -343,7 +343,7 @@ describe Channel do
it "triggers timeout (same fiber multiple times)" do
ch = Channel(String).new
- spawn_and_wait(->{}) do
+ spawn_and_wait(-> { }) do
3.times do
i, m = Channel.select(ch.receive_select_action, timeout_select_action(0.1.seconds))
@@ -355,7 +355,7 @@ describe Channel do
it "allows receiving while waiting" do
ch = Channel(String).new
- spawn_and_wait(->{ ch.send "foo" }) do
+ spawn_and_wait(-> { ch.send "foo" }) do
i, m = Channel.select(ch.receive_select_action, timeout_select_action(1.seconds))
i.should eq(0)
m.should eq("foo")
@@ -364,7 +364,7 @@ describe Channel do
it "allows receiving while waiting (reverse order)" do
ch = Channel(String).new
- spawn_and_wait(->{ ch.send "foo" }) do
+ spawn_and_wait(-> { ch.send "foo" }) do
i, m = Channel.select(timeout_select_action(1.seconds), ch.receive_select_action)
i.should eq(1)
m.should eq("foo")
@@ -373,7 +373,7 @@ describe Channel do
it "allows receiving while waiting (same fiber multiple times)" do
ch = Channel(String).new
- spawn_and_wait(->{ 3.times { ch.send "foo" } }) do
+ spawn_and_wait(-> { 3.times { ch.send "foo" } }) do
3.times do
i, m = Channel.select(ch.receive_select_action, timeout_select_action(1.seconds))
i.should eq(0)
@@ -384,7 +384,7 @@ describe Channel do
it "negative amounts should not trigger timeout" do
ch = Channel(String).new
- spawn_and_wait(->{ ch.send "foo" }) do
+ spawn_and_wait(-> { ch.send "foo" }) do
i, m = Channel.select(ch.receive_select_action, timeout_select_action(-1.seconds))
i.should eq(0)
@@ -394,7 +394,7 @@ describe Channel do
it "send raise-on-close raises if channel was closed while waiting" do
ch = Channel(String).new
- spawn_and_wait(->{ ch.close }) do
+ spawn_and_wait(-> { ch.close }) do
expect_raises Channel::ClosedError do
Channel.select(ch.send_select_action("foo"), timeout_select_action(0.1.seconds))
end
@@ -403,7 +403,7 @@ describe Channel do
it "receive raise-on-close raises if channel was closed while waiting" do
ch = Channel(String).new
- spawn_and_wait(->{ ch.close }) do
+ spawn_and_wait(-> { ch.close }) do
expect_raises Channel::ClosedError do
Channel.select(ch.receive_select_action, timeout_select_action(0.1.seconds))
end
@@ -412,7 +412,7 @@ describe Channel do
it "receive nil-on-close returns index of closed while waiting" do
ch = Channel(String).new
- spawn_and_wait(->{ ch.close }) do
+ spawn_and_wait(-> { ch.close }) do
i, m = Channel.select(ch.receive_select_action?, timeout_select_action(0.1.seconds))
i.should eq(0)
@@ -426,7 +426,7 @@ describe Channel do
context "receive raise-on-close single-channel" do
it "types" do
ch = Channel(String).new
- spawn_and_wait(->{ ch.send "foo" }) do
+ spawn_and_wait(-> { ch.send "foo" }) do
i, m = Channel.non_blocking_select(ch.receive_select_action)
typeof(i).should eq(Int32)
typeof(m).should eq(String | Channel::NotReady)
@@ -438,7 +438,7 @@ describe Channel do
it "types" do
ch = Channel(String).new
ch2 = Channel(Bool).new
- spawn_and_wait(->{ ch.send "foo" }) do
+ spawn_and_wait(-> { ch.send "foo" }) do
i, m = Channel.non_blocking_select(ch.receive_select_action, ch2.receive_select_action)
typeof(i).should eq(Int32)
typeof(m).should eq(String | Bool | Channel::NotReady)
@@ -449,7 +449,7 @@ describe Channel do
context "receive nil-on-close single-channel" do
it "types" do
ch = Channel(String).new
- spawn_and_wait(->{ ch.send "foo" }) do
+ spawn_and_wait(-> { ch.send "foo" }) do
i, m = Channel.non_blocking_select(ch.receive_select_action?)
typeof(i).should eq(Int32)
typeof(m).should eq(String | Nil | Channel::NotReady)
@@ -458,7 +458,7 @@ describe Channel do
it "returns nil if channel was closed" do
ch = Channel(String).new
- spawn_and_wait(->{ ch.close }) do
+ spawn_and_wait(-> { ch.close }) do
i, m = Channel.non_blocking_select(ch.receive_select_action?)
m.should be_nil
end
@@ -470,7 +470,7 @@ describe Channel do
ch = Channel(String).new
ch2 = Channel(String).new
- spawn_and_wait(->{ ch.close }) do
+ spawn_and_wait(-> { ch.close }) do
expect_raises Channel::ClosedError do
Channel.non_blocking_select(ch.receive_select_action, ch2.receive_select_action?)
end
@@ -480,7 +480,7 @@ describe Channel do
it "returns nil if receive channel was not ready and receive? channel was closed" do
ch = Channel(String).new
ch2 = Channel(String).new
- spawn_and_wait(->{ ch2.close }) do
+ spawn_and_wait(-> { ch2.close }) do
i, m = Channel.non_blocking_select(ch.receive_select_action, ch2.receive_select_action?)
i.should eq(1)
m.should eq(nil)
@@ -491,7 +491,7 @@ describe Channel do
context "send raise-on-close single-channel" do
it "types" do
ch = Channel(String).new
- spawn_and_wait(->{ ch.receive }) do
+ spawn_and_wait(-> { ch.receive }) do
i, m = Channel.non_blocking_select(ch.send_select_action("foo"))
typeof(i).should eq(Int32)
typeof(m).should eq(Nil | Channel::NotReady)
@@ -503,7 +503,7 @@ describe Channel do
it "types" do
ch = Channel(String).new
ch2 = Channel(Bool).new
- spawn_and_wait(->{ ch.receive }) do
+ spawn_and_wait(-> { ch.receive }) do
i, m = Channel.non_blocking_select(ch.send_select_action("foo"), ch2.send_select_action(true))
typeof(i).should eq(Int32)
typeof(m).should eq(Nil | Channel::NotReady)
@@ -514,7 +514,7 @@ describe Channel do
context "timeout" do
it "types" do
ch = Channel(String).new
- spawn_and_wait(->{ ch.send "foo" }) do
+ spawn_and_wait(-> { ch.send "foo" }) do
i, m = Channel.non_blocking_select(ch.receive_select_action, timeout_select_action(0.1.seconds))
typeof(i).should eq(Int32)
typeof(m).should eq(String | Nil | Channel::NotReady)
@@ -523,7 +523,7 @@ describe Channel do
it "should not trigger timeout" do
ch = Channel(String).new
- spawn_and_wait(->{}) do
+ spawn_and_wait(-> { }) do
i, m = Channel.non_blocking_select(ch.receive_select_action, timeout_select_action(0.1.seconds))
i.should eq(2)
@@ -533,7 +533,7 @@ describe Channel do
it "negative amounts should not trigger timeout" do
ch = Channel(String).new
- spawn_and_wait(->{}) do
+ spawn_and_wait(-> { }) do
i, m = Channel.non_blocking_select(ch.receive_select_action, timeout_select_action(-1.seconds))
i.should eq(2)
@@ -543,7 +543,7 @@ describe Channel do
it "send raise-on-close raises if channel was closed while waiting" do
ch = Channel(String).new
- spawn_and_wait(->{ ch.close }) do
+ spawn_and_wait(-> { ch.close }) do
expect_raises Channel::ClosedError do
Channel.non_blocking_select(ch.send_select_action("foo"), timeout_select_action(0.1.seconds))
end
@@ -552,7 +552,7 @@ describe Channel do
it "receive raise-on-close raises if channel was closed while waiting" do
ch = Channel(String).new
- spawn_and_wait(->{ ch.close }) do
+ spawn_and_wait(-> { ch.close }) do
expect_raises Channel::ClosedError do
Channel.non_blocking_select(ch.receive_select_action, timeout_select_action(0.1.seconds))
end
@@ -561,7 +561,7 @@ describe Channel do
it "receive nil-on-close returns index of closed while waiting" do
ch = Channel(String).new
- spawn_and_wait(->{ ch.close }) do
+ spawn_and_wait(-> { ch.close }) do
i, m = Channel.non_blocking_select(ch.receive_select_action?, timeout_select_action(0.1.seconds))
i.should eq(0)
@@ -573,7 +573,7 @@ describe Channel do
it "returns correct index for array argument" do
ch = [Channel(String).new, Channel(String).new, Channel(String).new]
channels = [ch[0], ch[2], ch[1]] # shuffle around to get non-sequential lock_object_ids
- spawn_and_wait(->{ channels[0].send "foo" }) do
+ spawn_and_wait(-> { channels[0].send "foo" }) do
i, m = Channel.non_blocking_select(channels.map(&.receive_select_action))
i.should eq(0)
diff --git a/spec/std/complex_spec.cr b/spec/std/complex_spec.cr
index 65add18f8533..2b90239d0796 100644
--- a/spec/std/complex_spec.cr
+++ b/spec/std/complex_spec.cr
@@ -265,6 +265,12 @@ describe "Complex" do
it "complex / complex" do
((Complex.new(4, 6.2))/(Complex.new(0.5, 2.7))).should eq(Complex.new(2.485411140583554, -1.0212201591511936))
((Complex.new(4.1, 6.0))/(Complex.new(10, 2.2))).should eq(Complex.new(0.5169782525753529, 0.48626478443342236))
+
+ (1.to_c / -1.to_c).should eq(-1.to_c)
+ assert_complex_nan 1.to_c / Float64::NAN
+
+ (1.to_c / 0.to_c).real.abs.should eq(Float64::INFINITY)
+ (1.to_c / 0.to_c).imag.nan?.should be_true
end
it "complex / number" do
diff --git a/spec/std/concurrent/select_spec.cr b/spec/std/concurrent/select_spec.cr
index f3f439ddd0b3..4f84734a20ad 100644
--- a/spec/std/concurrent/select_spec.cr
+++ b/spec/std/concurrent/select_spec.cr
@@ -243,7 +243,7 @@ describe "select" do
it "types and exec when" do
ch = Channel(String).new
- spawn_and_check(->{ ch.send "foo" }) do |w|
+ spawn_and_check(-> { ch.send "foo" }) do |w|
select
when m = ch.receive
w.check
@@ -253,26 +253,30 @@ describe "select" do
end
end
- it "raises if channel was closed" do
- ch = Channel(String).new
+ {% if flag?(:win32) && flag?(:aarch64) %}
+ pending "raises if channel was closed"
+ {% else %}
+ it "raises if channel was closed" do
+ ch = Channel(String).new
- spawn_and_check(->{ ch.close }) do |w|
- begin
- select
- when m = ch.receive
+ spawn_and_check(-> { ch.close }) do |w|
+ begin
+ select
+ when m = ch.receive
+ end
+ rescue Channel::ClosedError
+ w.check
end
- rescue Channel::ClosedError
- w.check
end
end
- end
+ {% end %}
end
context "non-blocking raise-on-close single-channel" do
it "types and exec when if message was ready" do
ch = Channel(String).new
- spawn_and_check(->{ ch.send "foo" }) do |w|
+ spawn_and_check(-> { ch.send "foo" }) do |w|
select
when m = ch.receive
w.check
@@ -286,7 +290,7 @@ describe "select" do
it "exec else if no message was ready" do
ch = Channel(String).new
- spawn_and_check(->{ nil }) do |w|
+ spawn_and_check(-> { nil }) do |w|
select
when m = ch.receive
else
@@ -295,20 +299,24 @@ describe "select" do
end
end
- it "raises if channel was closed" do
- ch = Channel(String).new
+ {% if flag?(:win32) && flag?(:aarch64) %}
+ pending "raises if channel was closed"
+ {% else %}
+ it "raises if channel was closed" do
+ ch = Channel(String).new
- spawn_and_check(->{ ch.close }) do |w|
- begin
- select
- when m = ch.receive
- else
+ spawn_and_check(-> { ch.close }) do |w|
+ begin
+ select
+ when m = ch.receive
+ else
+ end
+ rescue Channel::ClosedError
+ w.check
end
- rescue Channel::ClosedError
- w.check
end
end
- end
+ {% end %}
end
context "blocking raise-on-close multi-channel" do
@@ -316,7 +324,7 @@ describe "select" do
ch = Channel(String).new
ch2 = Channel(Bool).new
- spawn_and_check(->{ ch.send "foo" }) do |w|
+ spawn_and_check(-> { ch.send "foo" }) do |w|
select
when m = ch.receive
w.check
@@ -331,7 +339,7 @@ describe "select" do
ch = Channel(String).new
ch2 = Channel(Bool).new
- spawn_and_check(->{ ch2.send true }) do |w|
+ spawn_and_check(-> { ch2.send true }) do |w|
select
when m = ch.receive
when m = ch2.receive
@@ -342,37 +350,41 @@ describe "select" do
end
end
- it "raises if channel was closed (1)" do
- ch = Channel(String).new
- ch2 = Channel(Bool).new
+ {% if flag?(:win32) && flag?(:aarch64) %}
+ pending "raises if channel was closed"
+ {% else %}
+ it "raises if channel was closed (1)" do
+ ch = Channel(String).new
+ ch2 = Channel(Bool).new
- spawn_and_check(->{ ch.close }) do |w|
- begin
- select
- when m = ch.receive
- when m = ch2.receive
+ spawn_and_check(-> { ch.close }) do |w|
+ begin
+ select
+ when m = ch.receive
+ when m = ch2.receive
+ end
+ rescue Channel::ClosedError
+ w.check
end
- rescue Channel::ClosedError
- w.check
end
end
- end
- it "raises if channel was closed (2)" do
- ch = Channel(String).new
- ch2 = Channel(Bool).new
+ it "raises if channel was closed (2)" do
+ ch = Channel(String).new
+ ch2 = Channel(Bool).new
- spawn_and_check(->{ ch2.close }) do |w|
- begin
- select
- when m = ch.receive
- when m = ch2.receive
+ spawn_and_check(-> { ch2.close }) do |w|
+ begin
+ select
+ when m = ch.receive
+ when m = ch2.receive
+ end
+ rescue Channel::ClosedError
+ w.check
end
- rescue Channel::ClosedError
- w.check
end
end
- end
+ {% end %}
end
context "non-blocking raise-on-close multi-channel" do
@@ -380,7 +392,7 @@ describe "select" do
ch = Channel(String).new
ch2 = Channel(Bool).new
- spawn_and_check(->{ ch.send "foo" }) do |w|
+ spawn_and_check(-> { ch.send "foo" }) do |w|
select
when m = ch.receive
w.check
@@ -396,7 +408,7 @@ describe "select" do
ch = Channel(String).new
ch2 = Channel(Bool).new
- spawn_and_check(->{ ch2.send true }) do |w|
+ spawn_and_check(-> { ch2.send true }) do |w|
select
when m = ch.receive
when m = ch2.receive
@@ -412,7 +424,7 @@ describe "select" do
ch = Channel(String).new
ch2 = Channel(Bool).new
- spawn_and_check(->{ nil }) do |w|
+ spawn_and_check(-> { nil }) do |w|
select
when m = ch.receive
when m = ch2.receive
@@ -422,46 +434,50 @@ describe "select" do
end
end
- it "raises if channel was closed (1)" do
- ch = Channel(String).new
- ch2 = Channel(Bool).new
+ {% if flag?(:win32) && flag?(:aarch64) %}
+ pending "raises if channel was closed"
+ {% else %}
+ it "raises if channel was closed (1)" do
+ ch = Channel(String).new
+ ch2 = Channel(Bool).new
- spawn_and_check(->{ ch.close }) do |w|
- begin
- select
- when m = ch.receive
- when m = ch2.receive
- else
+ spawn_and_check(-> { ch.close }) do |w|
+ begin
+ select
+ when m = ch.receive
+ when m = ch2.receive
+ else
+ end
+ rescue Channel::ClosedError
+ w.check
end
- rescue Channel::ClosedError
- w.check
end
end
- end
- it "raises if channel was closed (2)" do
- ch = Channel(String).new
- ch2 = Channel(Bool).new
+ it "raises if channel was closed (2)" do
+ ch = Channel(String).new
+ ch2 = Channel(Bool).new
- spawn_and_check(->{ ch2.close }) do |w|
- begin
- select
- when m = ch.receive
- when m = ch2.receive
- else
+ spawn_and_check(-> { ch2.close }) do |w|
+ begin
+ select
+ when m = ch.receive
+ when m = ch2.receive
+ else
+ end
+ rescue Channel::ClosedError
+ w.check
end
- rescue Channel::ClosedError
- w.check
end
end
- end
+ {% end %}
end
context "blocking nil-on-close single-channel" do
it "types and exec when" do
ch = Channel(String).new
- spawn_and_check(->{ ch.send "foo" }) do |w|
+ spawn_and_check(-> { ch.send "foo" }) do |w|
select
when m = ch.receive?
w.check
@@ -474,7 +490,7 @@ describe "select" do
it "types and exec when with nil if channel was closed" do
ch = Channel(String).new
- spawn_and_check(->{ ch.close }) do |w|
+ spawn_and_check(-> { ch.close }) do |w|
select
when m = ch.receive?
w.check
@@ -490,7 +506,7 @@ describe "select" do
ch = Channel(String).new
ch2 = Channel(Bool).new
- spawn_and_check(->{ ch.send "foo" }) do |w|
+ spawn_and_check(-> { ch.send "foo" }) do |w|
select
when m = ch.receive?
w.check
@@ -505,7 +521,7 @@ describe "select" do
ch = Channel(String).new
ch2 = Channel(Bool).new
- spawn_and_check(->{ ch2.send true }) do |w|
+ spawn_and_check(-> { ch2.send true }) do |w|
select
when m = ch.receive?
when m = ch2.receive?
@@ -520,7 +536,7 @@ describe "select" do
ch = Channel(String).new
ch2 = Channel(Bool).new
- spawn_and_check(->{ ch.close }) do |w|
+ spawn_and_check(-> { ch.close }) do |w|
select
when m = ch.receive?
w.check
@@ -535,7 +551,7 @@ describe "select" do
ch = Channel(String).new
ch2 = Channel(Bool).new
- spawn_and_check(->{ ch2.close }) do |w|
+ spawn_and_check(-> { ch2.close }) do |w|
select
when m = ch.receive?
when m = ch2.receive?
@@ -550,7 +566,7 @@ describe "select" do
ch = Channel(String).new
ch2 = Channel(Bool).new
- spawn_and_check(->{ ch.close }) do |w|
+ spawn_and_check(-> { ch.close }) do |w|
select
when m = ch.receive?
w.check
@@ -565,7 +581,7 @@ describe "select" do
ch = Channel(String).new
ch2 = Channel(Bool).new
- spawn_and_check(->{ ch2.close }) do |w|
+ spawn_and_check(-> { ch2.close }) do |w|
select
when m = ch.receive?
when m = ch2.receive?
@@ -581,7 +597,7 @@ describe "select" do
it "types and exec when" do
ch = Channel(String).new
- spawn_and_check(->{ ch.send "foo" }) do |w|
+ spawn_and_check(-> { ch.send "foo" }) do |w|
select
when m = ch.receive?
w.check
@@ -595,7 +611,7 @@ describe "select" do
it "exec else if no message was ready" do
ch = Channel(String).new
- spawn_and_check(->{ nil }) do |w|
+ spawn_and_check(-> { nil }) do |w|
select
when m = ch.receive?
else
@@ -607,7 +623,7 @@ describe "select" do
it "types and exec when with nil if channel was closed" do
ch = Channel(String).new
- spawn_and_check(->{ ch.close }) do |w|
+ spawn_and_check(-> { ch.close }) do |w|
select
when m = ch.receive?
w.check
@@ -624,7 +640,7 @@ describe "select" do
ch = Channel(String).new
ch2 = Channel(Bool).new
- spawn_and_check(->{ ch.send "foo" }) do |w|
+ spawn_and_check(-> { ch.send "foo" }) do |w|
select
when m = ch.receive?
w.check
@@ -640,7 +656,7 @@ describe "select" do
ch = Channel(String).new
ch2 = Channel(Bool).new
- spawn_and_check(->{ ch2.send true }) do |w|
+ spawn_and_check(-> { ch2.send true }) do |w|
select
when m = ch.receive?
when m = ch2.receive?
@@ -656,7 +672,7 @@ describe "select" do
ch = Channel(String).new
ch2 = Channel(Bool).new
- spawn_and_check(->{ ch.close }) do |w|
+ spawn_and_check(-> { ch.close }) do |w|
select
when m = ch.receive?
w.check
@@ -672,7 +688,7 @@ describe "select" do
ch = Channel(String).new
ch2 = Channel(Bool).new
- spawn_and_check(->{ ch2.close }) do |w|
+ spawn_and_check(-> { ch2.close }) do |w|
select
when m = ch.receive?
when m = ch2.receive?
@@ -688,7 +704,7 @@ describe "select" do
ch = Channel(String).new
ch2 = Channel(Bool).new
- spawn_and_check(->{ ch.close }) do |w|
+ spawn_and_check(-> { ch.close }) do |w|
select
when m = ch.receive?
w.check
@@ -704,7 +720,7 @@ describe "select" do
ch = Channel(String).new
ch2 = Channel(Bool).new
- spawn_and_check(->{ ch2.close }) do |w|
+ spawn_and_check(-> { ch2.close }) do |w|
select
when m = ch.receive?
when m = ch2.receive?
@@ -720,7 +736,7 @@ describe "select" do
ch = Channel(String).new
ch2 = Channel(Bool).new
- spawn_and_check(->{ nil }) do |w|
+ spawn_and_check(-> { nil }) do |w|
select
when m = ch.receive?
when m = ch2.receive?
diff --git a/spec/std/dir_spec.cr b/spec/std/dir_spec.cr
index 439da15becd9..d37483eba947 100644
--- a/spec/std/dir_spec.cr
+++ b/spec/std/dir_spec.cr
@@ -643,7 +643,7 @@ describe "Dir" do
Dir.mkdir_p path
# Resolve any symbolic links in path caused by tmpdir being a link.
# For example on macOS, /tmp is a symlink to /private/tmp.
- path = File.real_path(path)
+ path = File.realpath(path)
target_path = File.join(path, "target")
link_path = File.join(path, "link")
diff --git a/spec/std/env_spec.cr b/spec/std/env_spec.cr
index 038bdc74b9b1..c48afb0ff6f9 100644
--- a/spec/std/env_spec.cr
+++ b/spec/std/env_spec.cr
@@ -137,6 +137,10 @@ describe "ENV" do
ENV.fetch("2")
end
end
+
+ it "fetches arbitrary default value" do
+ ENV.fetch("nonexistent", true).should be_true
+ end
end
it "handles unicode" do
diff --git a/spec/std/exception/call_stack_spec.cr b/spec/std/exception/call_stack_spec.cr
index c01fb0ff6b8a..6df0741d2a7b 100644
--- a/spec/std/exception/call_stack_spec.cr
+++ b/spec/std/exception/call_stack_spec.cr
@@ -12,9 +12,9 @@ describe "Backtrace" do
_, output, _ = compile_and_run_file(source_file)
- # resolved file:line:column (no column for windows PDB because of poor
- # support in general)
- {% if flag?(:win32) %}
+ # resolved file:line:column (no column for MSVC PDB because of poor support
+ # by external tooling in general)
+ {% if flag?(:msvc) %}
output.should match(/^#{Regex.escape(source_file)}:3 in 'callee1'/m)
output.should match(/^#{Regex.escape(source_file)}:13 in 'callee3'/m)
{% else %}
@@ -55,14 +55,19 @@ describe "Backtrace" do
error.to_s.should contain("IndexError")
end
- it "prints crash backtrace to stderr", tags: %w[slow] do
- sample = datapath("crash_backtrace_sample")
+ {% if flag?(:openbsd) %}
+ # FIXME: the segfault handler doesn't work on OpenBSD
+ pending "prints crash backtrace to stderr"
+ {% else %}
+ it "prints crash backtrace to stderr", tags: %w[slow] do
+ sample = datapath("crash_backtrace_sample")
- _, output, error = compile_and_run_file(sample)
+ _, output, error = compile_and_run_file(sample)
- output.to_s.should be_empty
- error.to_s.should contain("Invalid memory access")
- end
+ output.to_s.should be_empty
+ error.to_s.should contain("Invalid memory access")
+ end
+ {% end %}
# Do not test this on platforms that cannot remove the current working
# directory of the process:
diff --git a/spec/std/file/tempfile_spec.cr b/spec/std/file/tempfile_spec.cr
index 3ede9e52e44d..84d9cd553398 100644
--- a/spec/std/file/tempfile_spec.cr
+++ b/spec/std/file/tempfile_spec.cr
@@ -200,7 +200,7 @@ describe Crystal::System::File do
fd, path = Crystal::System::File.mktemp("A", "Z", dir: tempdir, random: TestRNG.new([7, 8, 9, 10, 11, 12, 13, 14]))
path.should eq Path[tempdir, "A789abcdeZ"].to_s
ensure
- File.from_fd(path, fd).close if fd && path
+ IO::FileDescriptor.new(fd).close if fd
end
end
@@ -212,7 +212,7 @@ describe Crystal::System::File do
fd, path = Crystal::System::File.mktemp("A", "Z", dir: tempdir, random: TestRNG.new([7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22]))
path.should eq File.join(tempdir, "AfghijklmZ")
ensure
- File.from_fd(path, fd).close if fd && path
+ IO::FileDescriptor.new(fd).close if fd
end
end
@@ -223,7 +223,7 @@ describe Crystal::System::File do
expect_raises(File::AlreadyExistsError, "Error creating temporary file") do
fd, path = Crystal::System::File.mktemp("A", "Z", dir: tempdir, random: TestRNG.new([7, 8, 9, 10, 11, 12, 13, 14]))
ensure
- File.from_fd(path, fd).close if fd && path
+ IO::FileDescriptor.new(fd).close if fd
end
end
end
diff --git a/spec/std/file_spec.cr b/spec/std/file_spec.cr
index 44f947997b34..0f88b2028c2f 100644
--- a/spec/std/file_spec.cr
+++ b/spec/std/file_spec.cr
@@ -71,6 +71,14 @@ describe "File" do
end
end
+ it "opens regular file as non-blocking" do
+ with_tempfile("regular") do |path|
+ File.open(path, "w", blocking: false) do |file|
+ file.blocking.should be_false
+ end
+ end
+ end
+
{% if flag?(:unix) %}
if File.exists?("/dev/tty")
it "opens character device" do
@@ -114,6 +122,22 @@ describe "File" do
end
{% end %}
{% end %}
+
+ it "reads non-blocking file" do
+ File.open(datapath("test_file.txt"), "r", blocking: false) do |f|
+ f.gets_to_end.should eq("Hello World\n" * 20)
+ end
+ end
+
+ it "writes and reads large non-blocking file" do
+ with_tempfile("non-blocking-io.txt") do |path|
+ File.open(path, "w+", blocking: false) do |f|
+ f.puts "Hello World\n" * 40000
+ f.pos = 0
+ f.gets_to_end.should eq("Hello World\n" * 40000)
+ end
+ end
+ end
end
it "reads entire file" do
@@ -212,136 +236,6 @@ describe "File" do
end
end
- describe "executable?" do
- it "gives true" do
- crystal = Process.executable_path || pending! "Unable to locate compiler executable"
- File.executable?(crystal).should be_true
- end
-
- it "gives false" do
- File.executable?(datapath("test_file.txt")).should be_false
- end
-
- it "gives false when the file doesn't exist" do
- File.executable?(datapath("non_existing_file.txt")).should be_false
- end
-
- it "gives false when a component of the path is a file" do
- File.executable?(datapath("dir", "test_file.txt", "")).should be_false
- end
-
- it "follows symlinks" do
- with_tempfile("good_symlink_x.txt", "bad_symlink_x.txt") do |good_path, bad_path|
- crystal = Process.executable_path || pending! "Unable to locate compiler executable"
- File.symlink(File.expand_path(crystal), good_path)
- File.symlink(File.expand_path(datapath("non_existing_file.txt")), bad_path)
-
- File.executable?(good_path).should be_true
- File.executable?(bad_path).should be_false
- end
- end
- end
-
- describe "readable?" do
- it "gives true" do
- File.readable?(datapath("test_file.txt")).should be_true
- end
-
- it "gives false when the file doesn't exist" do
- File.readable?(datapath("non_existing_file.txt")).should be_false
- end
-
- it "gives false when a component of the path is a file" do
- File.readable?(datapath("dir", "test_file.txt", "")).should be_false
- end
-
- # win32 doesn't have a way to make files unreadable via chmod
- {% unless flag?(:win32) %}
- it "gives false when the file has no read permissions" do
- with_tempfile("unreadable.txt") do |path|
- File.write(path, "")
- File.chmod(path, 0o222)
- pending_if_superuser!
- File.readable?(path).should be_false
- end
- end
-
- it "gives false when the file has no permissions" do
- with_tempfile("unaccessible.txt") do |path|
- File.write(path, "")
- File.chmod(path, 0o000)
- pending_if_superuser!
- File.readable?(path).should be_false
- end
- end
-
- it "follows symlinks" do
- with_tempfile("good_symlink_r.txt", "bad_symlink_r.txt", "unreadable.txt") do |good_path, bad_path, unreadable|
- File.write(unreadable, "")
- File.chmod(unreadable, 0o222)
- pending_if_superuser!
-
- File.symlink(File.expand_path(datapath("test_file.txt")), good_path)
- File.symlink(File.expand_path(unreadable), bad_path)
-
- File.readable?(good_path).should be_true
- File.readable?(bad_path).should be_false
- end
- end
- {% end %}
-
- it "gives false when the symbolic link destination doesn't exist" do
- with_tempfile("missing_symlink_r.txt") do |missing_path|
- File.symlink(File.expand_path(datapath("non_existing_file.txt")), missing_path)
- File.readable?(missing_path).should be_false
- end
- end
- end
-
- describe "writable?" do
- it "gives true" do
- File.writable?(datapath("test_file.txt")).should be_true
- end
-
- it "gives false when the file doesn't exist" do
- File.writable?(datapath("non_existing_file.txt")).should be_false
- end
-
- it "gives false when a component of the path is a file" do
- File.writable?(datapath("dir", "test_file.txt", "")).should be_false
- end
-
- it "gives false when the file has no write permissions" do
- with_tempfile("readonly.txt") do |path|
- File.write(path, "")
- File.chmod(path, 0o444)
- pending_if_superuser!
- File.writable?(path).should be_false
- end
- end
-
- it "follows symlinks" do
- with_tempfile("good_symlink_w.txt", "bad_symlink_w.txt", "readonly.txt") do |good_path, bad_path, readonly|
- File.write(readonly, "")
- File.chmod(readonly, 0o444)
- pending_if_superuser!
-
- File.symlink(File.expand_path(datapath("test_file.txt")), good_path)
- File.symlink(File.expand_path(readonly), bad_path)
-
- File.writable?(good_path).should be_true
- File.writable?(bad_path).should be_false
- end
- end
-
- it "gives false when the symbolic link destination doesn't exist" do
- with_tempfile("missing_symlink_w.txt") do |missing_path|
- File.symlink(File.expand_path(datapath("non_existing_file.txt")), missing_path)
- File.writable?(missing_path).should be_false
- end
- end
- end
-
describe "file?" do
it "gives true" do
File.file?(datapath("test_file.txt")).should be_true
@@ -677,6 +571,139 @@ describe "File" do
it "tests unequal for file and directory" do
File.info(datapath("dir")).should_not eq(File.info(datapath("test_file.txt")))
end
+
+ describe ".executable?" do
+ it "gives true" do
+ crystal = Process.executable_path || pending! "Unable to locate compiler executable"
+ File::Info.executable?(crystal).should be_true
+ File.executable?(crystal).should be_true # deprecated
+ end
+
+ it "gives false" do
+ File::Info.executable?(datapath("test_file.txt")).should be_false
+ end
+
+ it "gives false when the file doesn't exist" do
+ File::Info.executable?(datapath("non_existing_file.txt")).should be_false
+ end
+
+ it "gives false when a component of the path is a file" do
+ File::Info.executable?(datapath("dir", "test_file.txt", "")).should be_false
+ end
+
+ it "follows symlinks" do
+ with_tempfile("good_symlink_x.txt", "bad_symlink_x.txt") do |good_path, bad_path|
+ crystal = Process.executable_path || pending! "Unable to locate compiler executable"
+ File.symlink(File.expand_path(crystal), good_path)
+ File.symlink(File.expand_path(datapath("non_existing_file.txt")), bad_path)
+
+ File::Info.executable?(good_path).should be_true
+ File::Info.executable?(bad_path).should be_false
+ end
+ end
+ end
+
+ describe ".readable?" do
+ it "gives true" do
+ File::Info.readable?(datapath("test_file.txt")).should be_true
+ File.readable?(datapath("test_file.txt")).should be_true # deprecated
+ end
+
+ it "gives false when the file doesn't exist" do
+ File::Info.readable?(datapath("non_existing_file.txt")).should be_false
+ end
+
+ it "gives false when a component of the path is a file" do
+ File::Info.readable?(datapath("dir", "test_file.txt", "")).should be_false
+ end
+
+ # win32 doesn't have a way to make files unreadable via chmod
+ {% unless flag?(:win32) %}
+ it "gives false when the file has no read permissions" do
+ with_tempfile("unreadable.txt") do |path|
+ File.write(path, "")
+ File.chmod(path, 0o222)
+ pending_if_superuser!
+ File::Info.readable?(path).should be_false
+ end
+ end
+
+ it "gives false when the file has no permissions" do
+ with_tempfile("unaccessible.txt") do |path|
+ File.write(path, "")
+ File.chmod(path, 0o000)
+ pending_if_superuser!
+ File::Info.readable?(path).should be_false
+ end
+ end
+
+ it "follows symlinks" do
+ with_tempfile("good_symlink_r.txt", "bad_symlink_r.txt", "unreadable.txt") do |good_path, bad_path, unreadable|
+ File.write(unreadable, "")
+ File.chmod(unreadable, 0o222)
+ pending_if_superuser!
+
+ File.symlink(File.expand_path(datapath("test_file.txt")), good_path)
+ File.symlink(File.expand_path(unreadable), bad_path)
+
+ File::Info.readable?(good_path).should be_true
+ File::Info.readable?(bad_path).should be_false
+ end
+ end
+ {% end %}
+
+ it "gives false when the symbolic link destination doesn't exist" do
+ with_tempfile("missing_symlink_r.txt") do |missing_path|
+ File.symlink(File.expand_path(datapath("non_existing_file.txt")), missing_path)
+ File::Info.readable?(missing_path).should be_false
+ end
+ end
+ end
+
+ describe ".writable?" do
+ it "gives true" do
+ File::Info.writable?(datapath("test_file.txt")).should be_true
+ File.writable?(datapath("test_file.txt")).should be_true # deprecated
+ end
+
+ it "gives false when the file doesn't exist" do
+ File::Info.writable?(datapath("non_existing_file.txt")).should be_false
+ end
+
+ it "gives false when a component of the path is a file" do
+ File::Info.writable?(datapath("dir", "test_file.txt", "")).should be_false
+ end
+
+ it "gives false when the file has no write permissions" do
+ with_tempfile("readonly.txt") do |path|
+ File.write(path, "")
+ File.chmod(path, 0o444)
+ pending_if_superuser!
+ File::Info.writable?(path).should be_false
+ end
+ end
+
+ it "follows symlinks" do
+ with_tempfile("good_symlink_w.txt", "bad_symlink_w.txt", "readonly.txt") do |good_path, bad_path, readonly|
+ File.write(readonly, "")
+ File.chmod(readonly, 0o444)
+ pending_if_superuser!
+
+ File.symlink(File.expand_path(datapath("test_file.txt")), good_path)
+ File.symlink(File.expand_path(readonly), bad_path)
+
+ File::Info.writable?(good_path).should be_true
+ File::Info.writable?(bad_path).should be_false
+ end
+ end
+
+ it "gives false when the symbolic link destination doesn't exist" do
+ with_tempfile("missing_symlink_w.txt") do |missing_path|
+ File.symlink(File.expand_path(datapath("non_existing_file.txt")), missing_path)
+ File::Info.writable?(missing_path).should be_false
+ end
+ end
+ end
end
describe "size" do
@@ -1049,6 +1076,41 @@ describe "File" do
end
end
+ it "does not overwrite existing content in append mode" do
+ with_tempfile("append-override.txt") do |filename|
+ File.write(filename, "0123456789")
+
+ File.open(filename, "a") do |file|
+ file.seek(5)
+ file.write "abcd".to_slice
+ end
+
+ File.read(filename).should eq "0123456789abcd"
+ end
+ end
+
+ it "truncates file opened in append mode (#14702)" do
+ with_tempfile("truncate-append.txt") do |path|
+ File.write(path, "0123456789")
+
+ File.open(path, "a") do |file|
+ file.truncate(4)
+ end
+
+ File.read(path).should eq "0123"
+ end
+ end
+
+ it "locks file opened in append mode (#14702)" do
+ with_tempfile("truncate-append.txt") do |path|
+ File.write(path, "0123456789")
+
+ File.open(path, "a") do |file|
+ file.flock_exclusive { }
+ end
+ end
+ end
+
it "can navigate with pos" do
File.open(datapath("test_file.txt")) do |file|
file.pos = 3
@@ -1189,46 +1251,50 @@ describe "File" do
end
end
- it "#flock_shared" do
- File.open(datapath("test_file.txt")) do |file1|
- File.open(datapath("test_file.txt")) do |file2|
- file1.flock_shared do
- file2.flock_shared(blocking: false) { }
+ {true, false}.each do |blocking|
+ context "blocking: #{blocking}" do
+ it "#flock_shared" do
+ File.open(datapath("test_file.txt"), blocking: blocking) do |file1|
+ File.open(datapath("test_file.txt"), blocking: blocking) do |file2|
+ file1.flock_shared do
+ file2.flock_shared(blocking: false) { }
+ end
+ end
end
end
- end
- end
- it "#flock_shared soft blocking fiber" do
- File.open(datapath("test_file.txt")) do |file1|
- File.open(datapath("test_file.txt")) do |file2|
- done = Channel(Nil).new
- file1.flock_exclusive
+ it "#flock_shared soft blocking fiber" do
+ File.open(datapath("test_file.txt"), blocking: blocking) do |file1|
+ File.open(datapath("test_file.txt"), blocking: blocking) do |file2|
+ done = Channel(Nil).new
+ file1.flock_exclusive
- spawn do
- file1.flock_unlock
- done.send nil
- end
+ spawn do
+ file1.flock_unlock
+ done.send nil
+ end
- file2.flock_shared
- done.receive
+ file2.flock_shared
+ done.receive
+ end
+ end
end
- end
- end
- it "#flock_exclusive soft blocking fiber" do
- File.open(datapath("test_file.txt")) do |file1|
- File.open(datapath("test_file.txt")) do |file2|
- done = Channel(Nil).new
- file1.flock_exclusive
+ it "#flock_exclusive soft blocking fiber" do
+ File.open(datapath("test_file.txt"), blocking: blocking) do |file1|
+ File.open(datapath("test_file.txt"), blocking: blocking) do |file2|
+ done = Channel(Nil).new
+ file1.flock_exclusive
- spawn do
- file1.flock_unlock
- done.send nil
- end
+ spawn do
+ file1.flock_unlock
+ done.send nil
+ end
- file2.flock_exclusive
- done.receive
+ file2.flock_exclusive
+ done.receive
+ end
+ end
end
end
end
@@ -1236,17 +1302,19 @@ describe "File" do
it "reads at offset" do
filename = datapath("test_file.txt")
- File.open(filename) do |file|
- file.read_at(6, 100) do |io|
- io.gets_to_end.should eq("World\nHello World\nHello World\nHello World\nHello World\nHello World\nHello World\nHello World\nHello Worl")
- end
+ {true, false}.each do |blocking|
+ File.open(filename, blocking: blocking) do |file|
+ file.read_at(6, 100) do |io|
+ io.gets_to_end.should eq("World\nHello World\nHello World\nHello World\nHello World\nHello World\nHello World\nHello World\nHello Worl")
+ end
- file.read_at(0, 240) do |io|
- io.gets_to_end.should eq(File.read(filename))
- end
+ file.read_at(0, 240) do |io|
+ io.gets_to_end.should eq(File.read(filename))
+ end
- file.read_at(6_i64, 5_i64) do |io|
- io.gets_to_end.should eq("World")
+ file.read_at(6_i64, 5_i64) do |io|
+ io.gets_to_end.should eq("World")
+ end
end
end
end
@@ -1307,15 +1375,15 @@ describe "File" do
end
it_raises_on_null_byte "readable?" do
- File.readable?("foo\0bar")
+ File::Info.readable?("foo\0bar")
end
it_raises_on_null_byte "writable?" do
- File.writable?("foo\0bar")
+ File::Info.writable?("foo\0bar")
end
it_raises_on_null_byte "executable?" do
- File.executable?("foo\0bar")
+ File::Info.executable?("foo\0bar")
end
it_raises_on_null_byte "file?" do
diff --git a/spec/std/http/client/client_spec.cr b/spec/std/http/client/client_spec.cr
index 4c9da8db7ad7..4cd51bf83075 100644
--- a/spec/std/http/client/client_spec.cr
+++ b/spec/std/http/client/client_spec.cr
@@ -6,7 +6,13 @@ require "http/server"
require "http/log"
require "log/spec"
-private def test_server(host, port, read_time = 0, content_type = "text/plain", write_response = true, &)
+# TODO: Windows networking in the interpreter requires #12495
+{% if flag?(:interpreted) && flag?(:win32) %}
+ pending HTTP::Client
+ {% skip_file %}
+{% end %}
+
+private def test_server(host, port, read_time = 0.seconds, content_type = "text/plain", write_response = true, &)
server = TCPServer.new(host, port)
begin
spawn do
@@ -312,12 +318,12 @@ module HTTP
end
it "doesn't read the body if request was HEAD" do
- resp_get = test_server("localhost", 0, 0) do |server|
+ resp_get = test_server("localhost", 0, 0.seconds) do |server|
client = Client.new("localhost", server.local_address.port)
break client.get("/")
end
- test_server("localhost", 0, 0) do |server|
+ test_server("localhost", 0, 0.seconds) do |server|
client = Client.new("localhost", server.local_address.port)
resp_head = client.head("/")
resp_head.headers.should eq(resp_get.headers)
@@ -338,7 +344,7 @@ module HTTP
end
it "tests read_timeout" do
- test_server("localhost", 0, 0) do |server|
+ test_server("localhost", 0, 0.seconds) do |server|
client = Client.new("localhost", server.local_address.port)
client.read_timeout = 1.second
client.get("/")
@@ -348,10 +354,10 @@ module HTTP
# it doesn't make sense to try to write because the client will already
# timeout on read. Writing a response could lead on an exception in
# the server if the socket is closed.
- test_server("localhost", 0, 0.5, write_response: false) do |server|
+ test_server("localhost", 0, 0.5.seconds, write_response: false) do |server|
client = Client.new("localhost", server.local_address.port)
expect_raises(IO::TimeoutError, {% if flag?(:win32) %} "WSARecv timed out" {% else %} "Read timed out" {% end %}) do
- client.read_timeout = 0.001
+ client.read_timeout = 1.millisecond
client.get("/?sleep=1")
end
end
@@ -362,19 +368,19 @@ module HTTP
# it doesn't make sense to try to write because the client will already
# timeout on read. Writing a response could lead on an exception in
# the server if the socket is closed.
- test_server("localhost", 0, 0, write_response: false) do |server|
+ test_server("localhost", 0, 0.seconds, write_response: false) do |server|
client = Client.new("localhost", server.local_address.port)
expect_raises(IO::TimeoutError, {% if flag?(:win32) %} "WSASend timed out" {% else %} "Write timed out" {% end %}) do
- client.write_timeout = 0.001
+ client.write_timeout = 1.millisecond
client.post("/", body: "a" * 5_000_000)
end
end
end
it "tests connect_timeout" do
- test_server("localhost", 0, 0) do |server|
+ test_server("localhost", 0, 0.seconds) do |server|
client = Client.new("localhost", server.local_address.port)
- client.connect_timeout = 0.5
+ client.connect_timeout = 0.5.seconds
client.get("/")
end
end
diff --git a/spec/std/http/request_spec.cr b/spec/std/http/request_spec.cr
index f997ca8998bc..1a378a39d20a 100644
--- a/spec/std/http/request_spec.cr
+++ b/spec/std/http/request_spec.cr
@@ -454,7 +454,7 @@ module HTTP
request.form_params["test"].should eq("foobar")
end
- it "returns ignors invalid content-type" do
+ it "ignores invalid content-type" do
request = Request.new("POST", "/form", nil, HTTP::Params.encode({"test" => "foobar"}))
request.form_params?.should eq(nil)
request.form_params.size.should eq(0)
diff --git a/spec/std/http/server/handlers/log_handler_spec.cr b/spec/std/http/server/handlers/log_handler_spec.cr
index 1f94649f09a8..3f33120e03d6 100644
--- a/spec/std/http/server/handlers/log_handler_spec.cr
+++ b/spec/std/http/server/handlers/log_handler_spec.cr
@@ -28,7 +28,7 @@ describe HTTP::LogHandler do
backend = Log::MemoryBackend.new
log = Log.new("custom", backend, :info)
handler = HTTP::LogHandler.new(log)
- handler.next = ->(ctx : HTTP::Server::Context) {}
+ handler.next = ->(ctx : HTTP::Server::Context) { }
handler.call(context)
logs = Log::EntriesChecker.new(backend.entries)
diff --git a/spec/std/http/server/response_spec.cr b/spec/std/http/server/response_spec.cr
index 99e462151f6b..c5d775e48b8d 100644
--- a/spec/std/http/server/response_spec.cr
+++ b/spec/std/http/server/response_spec.cr
@@ -76,6 +76,15 @@ describe HTTP::Server::Response do
io.to_s.should eq("HTTP/1.1 304 Not Modified\r\nContent-Length: 5\r\n\r\n")
end
+ it "allow explicitly configuring a `Transfer-Encoding` response" do
+ io = IO::Memory.new
+ response = Response.new(io)
+ response.headers["Transfer-Encoding"] = "chunked"
+ response.print "Hello"
+ response.close
+ io.to_s.should eq("HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n5\r\nHello\r\n0\r\n\r\n")
+ end
+
it "prints less then buffer's size" do
io = IO::Memory.new
response = Response.new(io)
diff --git a/spec/std/http/server/server_spec.cr b/spec/std/http/server/server_spec.cr
index c8b39c9e7e42..3c634d755abf 100644
--- a/spec/std/http/server/server_spec.cr
+++ b/spec/std/http/server/server_spec.cr
@@ -4,6 +4,12 @@ require "http/client"
require "../../../support/ssl"
require "../../../support/channel"
+# TODO: Windows networking in the interpreter requires #12495
+{% if flag?(:interpreted) && flag?(:win32) %}
+ pending HTTP::Server
+ {% skip_file %}
+{% end %}
+
# TODO: replace with `HTTP::Client.get` once it supports connecting to Unix socket (#2735)
private def unix_request(path)
UNIXSocket.open(path) do |io|
@@ -65,14 +71,14 @@ describe HTTP::Server do
while !server.listening?
Fiber.yield
end
- sleep 0.1
+ sleep 0.1.seconds
schedule_timeout ch
TCPSocket.open(address.address, address.port) { }
# wait before closing the server
- sleep 0.1
+ sleep 0.1.seconds
server.close
ch.receive.should eq SpecChannelStatus::End
@@ -427,7 +433,7 @@ describe HTTP::Server do
begin
ch.receive
client = HTTP::Client.new(address.address, address.port, client_context)
- client.read_timeout = client.connect_timeout = 3
+ client.read_timeout = client.connect_timeout = 3.seconds
client.get("/").body.should eq "ok"
ensure
ch.send nil
diff --git a/spec/std/http/spec_helper.cr b/spec/std/http/spec_helper.cr
index 18ec9e0bab46..82b4f12d6774 100644
--- a/spec/std/http/spec_helper.cr
+++ b/spec/std/http/spec_helper.cr
@@ -49,7 +49,7 @@ def run_server(server, &)
{% if flag?(:preview_mt) %}
# avoids fiber synchronization issues in specs, like closing the server
# before we properly listen, ...
- sleep 0.001
+ sleep 1.millisecond
{% end %}
yield server_done
ensure
diff --git a/spec/std/http/web_socket_spec.cr b/spec/std/http/web_socket_spec.cr
index 75a54e91fb2e..164a1d067df5 100644
--- a/spec/std/http/web_socket_spec.cr
+++ b/spec/std/http/web_socket_spec.cr
@@ -7,6 +7,12 @@ require "../../support/fibers"
require "../../support/ssl"
require "../socket/spec_helper.cr"
+# TODO: Windows networking in the interpreter requires #12495
+{% if flag?(:interpreted) && flag?(:win32) %}
+ pending HTTP::WebSocket
+ {% skip_file %}
+{% end %}
+
private def assert_text_packet(packet, size, final = false)
assert_packet packet, HTTP::WebSocket::Protocol::Opcode::TEXT, size, final: final
end
diff --git a/spec/std/humanize_spec.cr b/spec/std/humanize_spec.cr
index c909417aca36..d24d2017cb28 100644
--- a/spec/std/humanize_spec.cr
+++ b/spec/std/humanize_spec.cr
@@ -207,6 +207,14 @@ describe Number do
it { assert_prints 1.0e+34.humanize, "10,000Q" }
it { assert_prints 1.0e+35.humanize, "100,000Q" }
+ it { assert_prints Float32::INFINITY.humanize, "Infinity" }
+ it { assert_prints (-Float32::INFINITY).humanize, "-Infinity" }
+ it { assert_prints Float32::NAN.humanize, "NaN" }
+
+ it { assert_prints Float64::INFINITY.humanize, "Infinity" }
+ it { assert_prints (-Float64::INFINITY).humanize, "-Infinity" }
+ it { assert_prints Float64::NAN.humanize, "NaN" }
+
it { assert_prints 1_234.567_890_123.humanize(precision: 2, significant: false), "1.23k" }
it { assert_prints 123.456_789_012_3.humanize(precision: 2, significant: false), "123.46" }
it { assert_prints 12.345_678_901_23.humanize(precision: 2, significant: false), "12.35" }
diff --git a/spec/std/io/buffered_spec.cr b/spec/std/io/buffered_spec.cr
index fbf6ac638ab8..faf684da0e25 100644
--- a/spec/std/io/buffered_spec.cr
+++ b/spec/std/io/buffered_spec.cr
@@ -72,6 +72,15 @@ describe "IO::Buffered" do
end
end
+ it "can set buffer_size to the same value after first use" do
+ io = BufferedWrapper.new(IO::Memory.new("hello\r\nworld\n"))
+ io.buffer_size = 16_384
+ io.gets
+
+ io.buffer_size = 16_384
+ io.buffer_size.should eq(16_384)
+ end
+
it "does gets" do
io = BufferedWrapper.new(IO::Memory.new("hello\r\nworld\n"))
io.gets.should eq("hello")
diff --git a/spec/std/io/delimited_spec.cr b/spec/std/io/delimited_spec.cr
index b41af9ee5fdb..c1e06bf40dc0 100644
--- a/spec/std/io/delimited_spec.cr
+++ b/spec/std/io/delimited_spec.cr
@@ -259,7 +259,7 @@ describe "IO::Delimited" do
io.gets_to_end.should eq("hello")
end
- it "handles the case of peek matching first byte, not having enough room, but later not matching (limted slice)" do
+ it "handles the case of peek matching first byte, not having enough room, but later not matching (limited slice)" do
# not a delimiter
# ---
io = MemoryIOWithFixedPeek.new("abcdefgwijkfghhello")
diff --git a/spec/std/io/file_descriptor_spec.cr b/spec/std/io/file_descriptor_spec.cr
index e497ac1061a3..2e10ea99c030 100644
--- a/spec/std/io/file_descriptor_spec.cr
+++ b/spec/std/io/file_descriptor_spec.cr
@@ -48,17 +48,33 @@ describe IO::FileDescriptor do
end
end
- it "closes on finalize" do
- pipes = [] of IO::FileDescriptor
- assert_finalizes("fd") do
- a, b = IO.pipe
- pipes << b
- a
+ describe "#finalize" do
+ it "closes" do
+ pipes = [] of IO::FileDescriptor
+ assert_finalizes("fd") do
+ a, b = IO.pipe
+ pipes << b
+ a
+ end
+
+ expect_raises(IO::Error) do
+ pipes.each do |p|
+ p.puts "123"
+ end
+ end
end
- expect_raises(IO::Error) do
- pipes.each do |p|
- p.puts "123"
+ it "does not flush" do
+ with_tempfile "fd-finalize-flush" do |path|
+ file = File.new(path, "w")
+ file << "foo"
+ file.flush
+ file << "bar"
+ file.finalize
+
+ File.read(path).should eq "foo"
+ ensure
+ file.try(&.close) rescue nil
end
end
end
diff --git a/spec/std/io/io_spec.cr b/spec/std/io/io_spec.cr
index 6974a9fe3466..1904940f4883 100644
--- a/spec/std/io/io_spec.cr
+++ b/spec/std/io/io_spec.cr
@@ -105,11 +105,11 @@ describe IO do
write.puts "hello"
slice = Bytes.new 1024
- read.read_timeout = 1
+ read.read_timeout = 1.second
read.read(slice).should eq(6)
expect_raises(IO::TimeoutError) do
- read.read_timeout = 0.0000001
+ read.read_timeout = 0.1.microseconds
read.read(slice)
end
end
@@ -425,9 +425,9 @@ describe IO do
str.read_fully?(slice).should be_nil
end
- # pipe(2) returns bidirectional file descriptors on FreeBSD and Solaris,
+ # pipe(2) returns bidirectional file descriptors on some platforms,
# gate this test behind the platform flag.
- {% unless flag?(:freebsd) || flag?(:solaris) %}
+ {% unless flag?(:freebsd) || flag?(:solaris) || flag?(:openbsd) %}
it "raises if trying to read to an IO not opened for reading" do
IO.pipe do |r, w|
expect_raises(IO::Error, "File not open for reading") do
@@ -574,9 +574,9 @@ describe IO do
io.read_byte.should be_nil
end
- # pipe(2) returns bidirectional file descriptors on FreeBSD and Solaris,
+ # pipe(2) returns bidirectional file descriptors on some platforms,
# gate this test behind the platform flag.
- {% unless flag?(:freebsd) || flag?(:solaris) %}
+ {% unless flag?(:freebsd) || flag?(:solaris) || flag?(:openbsd) %}
it "raises if trying to write to an IO not opened for writing" do
IO.pipe do |r, w|
# unless sync is used the flush on close triggers the exception again
@@ -736,7 +736,7 @@ describe IO do
it "says invalid byte sequence" do
io = SimpleIOMemory.new(Slice.new(1, 255_u8))
io.set_encoding("EUC-JP")
- expect_raises ArgumentError, {% if flag?(:musl) || flag?(:freebsd) %}"Incomplete multibyte sequence"{% else %}"Invalid multibyte sequence"{% end %} do
+ expect_raises ArgumentError, {% if flag?(:musl) || flag?(:freebsd) || flag?(:netbsd) %}"Incomplete multibyte sequence"{% else %}"Invalid multibyte sequence"{% end %} do
io.read_char
end
end
@@ -816,23 +816,26 @@ describe IO do
io.gets_to_end.should eq("\r\nFoo\nBar")
end
- it "gets ascii from socket (#9056)" do
- server = TCPServer.new "localhost", 0
- sock = TCPSocket.new "localhost", server.local_address.port
- begin
- sock.set_encoding("ascii")
- spawn do
- client = server.accept
- message = client.gets
- client << "#{message}\n"
+ # TODO: Windows networking in the interpreter requires #12495
+ {% unless flag?(:interpreted) || flag?(:win32) %}
+ it "gets ascii from socket (#9056)" do
+ server = TCPServer.new "localhost", 0
+ sock = TCPSocket.new "localhost", server.local_address.port
+ begin
+ sock.set_encoding("ascii")
+ spawn do
+ client = server.accept
+ message = client.gets
+ client << "#{message}\n"
+ end
+ sock << "K\n"
+ sock.gets.should eq("K")
+ ensure
+ server.close
+ sock.close
end
- sock << "K\n"
- sock.gets.should eq("K")
- ensure
- server.close
- sock.close
end
- end
+ {% end %}
end
describe "encode" do
diff --git a/spec/std/iterator_spec.cr b/spec/std/iterator_spec.cr
index a07b8bedb191..b7f000a871cb 100644
--- a/spec/std/iterator_spec.cr
+++ b/spec/std/iterator_spec.cr
@@ -33,6 +33,13 @@ private class MockIterator
end
describe Iterator do
+ describe "Iterator.empty" do
+ it "creates empty iterator" do
+ iter = Iterator(String).empty
+ iter.next.should be_a(Iterator::Stop)
+ end
+ end
+
describe "Iterator.of" do
it "creates singleton" do
iter = Iterator.of(42)
diff --git a/spec/std/json/parser_spec.cr b/spec/std/json/parser_spec.cr
index 96cfd52277a2..0147cfa92964 100644
--- a/spec/std/json/parser_spec.cr
+++ b/spec/std/json/parser_spec.cr
@@ -22,6 +22,7 @@ describe JSON::Parser do
it_parses "true", true
it_parses "false", false
it_parses "null", nil
+ it_parses %("\\nПривет, мир!"), "\nПривет, мир!"
it_parses "[]", [] of Int32
it_parses "[1]", [1]
diff --git a/spec/std/kernel_spec.cr b/spec/std/kernel_spec.cr
index 149e6385ac97..0a682af8381b 100644
--- a/spec/std/kernel_spec.cr
+++ b/spec/std/kernel_spec.cr
@@ -8,6 +8,14 @@ describe "PROGRAM_NAME" do
pending! "Example is broken in Nix shell (#12332)"
end
+ # MSYS2: gcc/ld doesn't support unicode paths
+ # https://github.com/msys2/MINGW-packages/issues/17812
+ {% if flag?(:windows) %}
+ if ENV["MSYSTEM"]?
+ pending! "Example is broken in MSYS2 shell"
+ end
+ {% end %}
+
File.write(source_file, "File.basename(PROGRAM_NAME).inspect(STDOUT)")
compile_file(source_file, bin_name: "×‽😂") do |executable_file|
@@ -243,54 +251,60 @@ describe "at_exit" do
end
end
-describe "hardware exception" do
- it "reports invalid memory access", tags: %w[slow] do
- status, _, error = compile_and_run_source <<-'CRYSTAL'
- puts Pointer(Int64).null.value
- CRYSTAL
-
- status.success?.should be_false
- error.should contain("Invalid memory access")
- error.should_not contain("Stack overflow")
- end
-
- {% if flag?(:musl) %}
- # FIXME: Pending as mitigation for https://github.com/crystal-lang/crystal/issues/7482
- pending "detects stack overflow on the main stack"
- {% else %}
- it "detects stack overflow on the main stack", tags: %w[slow] do
- # This spec can take some time under FreeBSD where
- # the default stack size is 0.5G. Setting a
- # smaller stack size with `ulimit -s 8192`
- # will address this.
+{% if flag?(:openbsd) %}
+ # FIXME: the segfault handler doesn't work on OpenBSD
+ pending "hardware exception"
+{% else %}
+ describe "hardware exception" do
+ it "reports invalid memory access", tags: %w[slow] do
status, _, error = compile_and_run_source <<-'CRYSTAL'
- def foo
- y = StaticArray(Int8, 512).new(0)
- foo
- end
- foo
- CRYSTAL
+ puts Pointer(Int64).null.value
+ CRYSTAL
status.success?.should be_false
- error.should contain("Stack overflow")
+ error.should contain("Invalid memory access")
+ error.should_not contain("Stack overflow")
end
- {% end %}
- it "detects stack overflow on a fiber stack", tags: %w[slow] do
- status, _, error = compile_and_run_source <<-'CRYSTAL'
- def foo
- y = StaticArray(Int8, 512).new(0)
- foo
+ {% if flag?(:netbsd) %}
+ # FIXME: on netbsd the process crashes with SIGILL after receiving SIGSEGV
+ pending "detects stack overflow on the main stack"
+ pending "detects stack overflow on a fiber stack"
+ {% else %}
+ it "detects stack overflow on the main stack", tags: %w[slow] do
+ # This spec can take some time under FreeBSD where
+ # the default stack size is 0.5G. Setting a
+ # smaller stack size with `ulimit -s 8192`
+ # will address this.
+ status, _, error = compile_and_run_source <<-'CRYSTAL'
+ def foo
+ y = StaticArray(Int8, 512).new(0)
+ foo
+ end
+ foo
+ CRYSTAL
+
+ status.success?.should be_false
+ error.should contain("Stack overflow")
end
- spawn do
- foo
- end
+ it "detects stack overflow on a fiber stack", tags: %w[slow] do
+ status, _, error = compile_and_run_source <<-'CRYSTAL'
+ def foo
+ y = StaticArray(Int8, 512).new(0)
+ foo
+ end
- sleep 60.seconds
- CRYSTAL
+ spawn do
+ foo
+ end
- status.success?.should be_false
- error.should contain("Stack overflow")
+ sleep 60.seconds
+ CRYSTAL
+
+ status.success?.should be_false
+ error.should contain("Stack overflow")
+ end
+ {% end %}
end
-end
+{% end %}
diff --git a/spec/std/llvm/aarch64_spec.cr b/spec/std/llvm/aarch64_spec.cr
index 6e2bac04dc47..41a308b480ec 100644
--- a/spec/std/llvm/aarch64_spec.cr
+++ b/spec/std/llvm/aarch64_spec.cr
@@ -1,11 +1,4 @@
require "spec"
-
-{% if flag?(:interpreted) && !flag?(:win32) %}
- # TODO: figure out how to link against libstdc++ in interpreted code (#14398)
- pending LLVM::ABI::AArch64
- {% skip_file %}
-{% end %}
-
require "llvm"
{% if LibLLVM::BUILT_TARGETS.includes?(:aarch64) %}
diff --git a/spec/std/llvm/arm_abi_spec.cr b/spec/std/llvm/arm_abi_spec.cr
index 8132ca0a38ce..98ae9b588a41 100644
--- a/spec/std/llvm/arm_abi_spec.cr
+++ b/spec/std/llvm/arm_abi_spec.cr
@@ -1,11 +1,4 @@
require "spec"
-
-{% if flag?(:interpreted) && !flag?(:win32) %}
- # TODO: figure out how to link against libstdc++ in interpreted code (#14398)
- pending LLVM::ABI::ARM
- {% skip_file %}
-{% end %}
-
require "llvm"
{% if LibLLVM::BUILT_TARGETS.includes?(:arm) %}
diff --git a/spec/std/llvm/avr_spec.cr b/spec/std/llvm/avr_spec.cr
index 3c23c9bbed6e..a6e95d8937be 100644
--- a/spec/std/llvm/avr_spec.cr
+++ b/spec/std/llvm/avr_spec.cr
@@ -1,11 +1,4 @@
require "spec"
-
-{% if flag?(:interpreted) && !flag?(:win32) %}
- # TODO: figure out how to link against libstdc++ in interpreted code (#14398)
- pending LLVM::ABI::AVR
- {% skip_file %}
-{% end %}
-
require "llvm"
{% if LibLLVM::BUILT_TARGETS.includes?(:avr) %}
diff --git a/spec/std/llvm/llvm_spec.cr b/spec/std/llvm/llvm_spec.cr
index 17ea96d5e261..e39398879e5d 100644
--- a/spec/std/llvm/llvm_spec.cr
+++ b/spec/std/llvm/llvm_spec.cr
@@ -1,11 +1,4 @@
require "spec"
-
-{% if flag?(:interpreted) && !flag?(:win32) %}
- # TODO: figure out how to link against libstdc++ in interpreted code (#14398)
- pending LLVM
- {% skip_file %}
-{% end %}
-
require "llvm"
describe LLVM do
diff --git a/spec/std/llvm/type_spec.cr b/spec/std/llvm/type_spec.cr
index 8c6b99662ca2..94e34f226250 100644
--- a/spec/std/llvm/type_spec.cr
+++ b/spec/std/llvm/type_spec.cr
@@ -1,11 +1,4 @@
require "spec"
-
-{% if flag?(:interpreted) && !flag?(:win32) %}
- # TODO: figure out how to link against libstdc++ in interpreted code (#14398)
- pending LLVM::Type
- {% skip_file %}
-{% end %}
-
require "llvm"
describe LLVM::Type do
diff --git a/spec/std/llvm/x86_64_abi_spec.cr b/spec/std/llvm/x86_64_abi_spec.cr
index 8b971a679c2a..0ba644cefa01 100644
--- a/spec/std/llvm/x86_64_abi_spec.cr
+++ b/spec/std/llvm/x86_64_abi_spec.cr
@@ -1,11 +1,4 @@
require "spec"
-
-{% if flag?(:interpreted) && !flag?(:win32) %}
- # TODO: figure out how to link against libstdc++ in interpreted code (#14398)
- pending LLVM::ABI::X86_64
- {% skip_file %}
-{% end %}
-
require "llvm"
{% if LibLLVM::BUILT_TARGETS.includes?(:x86) %}
diff --git a/spec/std/llvm/x86_abi_spec.cr b/spec/std/llvm/x86_abi_spec.cr
index b79ebc4d4d5c..27d387820298 100644
--- a/spec/std/llvm/x86_abi_spec.cr
+++ b/spec/std/llvm/x86_abi_spec.cr
@@ -1,13 +1,6 @@
{% skip_file if flag?(:win32) %} # 32-bit windows is not supported
require "spec"
-
-{% if flag?(:interpreted) %}
- # TODO: figure out how to link against libstdc++ in interpreted code (#14398)
- pending LLVM::ABI::X86
- {% skip_file %}
-{% end %}
-
require "llvm"
{% if LibLLVM::BUILT_TARGETS.includes?(:x86) %}
diff --git a/spec/std/oauth2/client_spec.cr b/spec/std/oauth2/client_spec.cr
index 3ee66e29ab49..ee445f3426e7 100644
--- a/spec/std/oauth2/client_spec.cr
+++ b/spec/std/oauth2/client_spec.cr
@@ -3,6 +3,12 @@ require "oauth2"
require "http/server"
require "../http/spec_helper"
+# TODO: Windows networking in the interpreter requires #12495
+{% if flag?(:interpreted) && flag?(:win32) %}
+ pending OAuth2::Client
+ {% skip_file %}
+{% end %}
+
describe OAuth2::Client do
describe "authorization uri" do
it "gets with default endpoint" do
diff --git a/spec/std/openssl/ssl/server_spec.cr b/spec/std/openssl/ssl/server_spec.cr
index ff5e578a8ed0..8618ed780a50 100644
--- a/spec/std/openssl/ssl/server_spec.cr
+++ b/spec/std/openssl/ssl/server_spec.cr
@@ -3,6 +3,12 @@ require "socket"
require "../../spec_helper"
require "../../../support/ssl"
+# TODO: Windows networking in the interpreter requires #12495
+{% if flag?(:interpreted) && flag?(:win32) %}
+ pending OpenSSL::SSL::Server
+ {% skip_file %}
+{% end %}
+
describe OpenSSL::SSL::Server do
it "sync_close" do
TCPServer.open(0) do |tcp_server|
@@ -130,7 +136,7 @@ describe OpenSSL::SSL::Server do
OpenSSL::SSL::Server.open tcp_server, server_context do |server|
spawn do
- sleep 1
+ sleep 1.second
OpenSSL::SSL::Socket::Client.open(TCPSocket.new(tcp_server.local_address.address, tcp_server.local_address.port), client_context, hostname: "example.com") do |socket|
end
end
diff --git a/spec/std/openssl/ssl/socket_spec.cr b/spec/std/openssl/ssl/socket_spec.cr
index bbc5b11e4b9b..ed1150407122 100644
--- a/spec/std/openssl/ssl/socket_spec.cr
+++ b/spec/std/openssl/ssl/socket_spec.cr
@@ -4,6 +4,12 @@ require "../../spec_helper"
require "../../socket/spec_helper"
require "../../../support/ssl"
+# TODO: Windows networking in the interpreter requires #12495
+{% if flag?(:interpreted) && flag?(:win32) %}
+ pending OpenSSL::SSL::Socket
+ {% skip_file %}
+{% end %}
+
describe OpenSSL::SSL::Socket do
describe OpenSSL::SSL::Socket::Server do
it "auto accept client by default" do
@@ -69,7 +75,7 @@ describe OpenSSL::SSL::Socket do
server_tests: ->(client : Server) {
client.cipher.should_not be_empty
},
- client_tests: ->(client : Client) {}
+ client_tests: ->(client : Client) { }
)
end
@@ -78,7 +84,7 @@ describe OpenSSL::SSL::Socket do
server_tests: ->(client : Server) {
client.tls_version.should contain "TLS"
},
- client_tests: ->(client : Client) {}
+ client_tests: ->(client : Client) { }
)
end
diff --git a/spec/std/pointer/appender_spec.cr b/spec/std/pointer/appender_spec.cr
index 02ca18e0188e..54aff72c9349 100644
--- a/spec/std/pointer/appender_spec.cr
+++ b/spec/std/pointer/appender_spec.cr
@@ -25,4 +25,18 @@ describe Pointer::Appender do
end
appender.size.should eq 4
end
+
+ it "#to_slice" do
+ data = Slice(Int32).new(5)
+ appender = data.to_unsafe.appender
+ appender.to_slice.should eq Slice(Int32).new(0)
+ appender.to_slice.to_unsafe.should eq data.to_unsafe
+
+ 4.times do |i|
+ appender << (i + 1) * 2
+ appender.to_slice.should eq data[0, i + 1]
+ end
+ appender.to_slice.should eq Slice[2, 4, 6, 8]
+ appender.to_slice.to_unsafe.should eq data.to_unsafe
+ end
end
diff --git a/spec/std/proc_spec.cr b/spec/std/proc_spec.cr
index 87bea44c0422..f378d768fbef 100644
--- a/spec/std/proc_spec.cr
+++ b/spec/std/proc_spec.cr
@@ -28,19 +28,19 @@ describe "Proc" do
end
it "gets pointer" do
- f = ->{ 1 }
+ f = -> { 1 }
f.pointer.address.should be > 0
end
it "gets closure data for non-closure" do
- f = ->{ 1 }
+ f = -> { 1 }
f.closure_data.address.should eq(0)
f.closure?.should be_false
end
it "gets closure data for closure" do
a = 1
- f = ->{ a }
+ f = -> { a }
f.closure_data.address.should be > 0
f.closure?.should be_true
end
@@ -53,19 +53,19 @@ describe "Proc" do
end
it "does ==" do
- func = ->{ 1 }
+ func = -> { 1 }
func.should eq(func)
- func2 = ->{ 1 }
+ func2 = -> { 1 }
func2.should_not eq(func)
end
it "clones" do
- func = ->{ 1 }
+ func = -> { 1 }
func.clone.should eq(func)
end
it "#arity" do
- f = ->(x : Int32, y : Int32) {}
+ f = ->(x : Int32, y : Int32) { }
f.arity.should eq(2)
end
@@ -89,5 +89,5 @@ describe "Proc" do
f2.call('r').should eq(2)
end
- typeof(->{ 1 }.hash)
+ typeof(-> { 1 }.hash)
end
diff --git a/spec/std/process_spec.cr b/spec/std/process_spec.cr
index f067d2f5c775..8347804cadc5 100644
--- a/spec/std/process_spec.cr
+++ b/spec/std/process_spec.cr
@@ -55,7 +55,12 @@ private def newline
end
# interpreted code doesn't receive SIGCHLD for `#wait` to work (#12241)
-pending_interpreted describe: Process do
+{% if flag?(:interpreted) && !flag?(:win32) %}
+ pending Process
+ {% skip_file %}
+{% end %}
+
+describe Process do
describe ".new" do
it "raises if command doesn't exist" do
expect_raises(File::NotFoundError, "Error executing process: 'foobarbaz'") do
@@ -167,6 +172,14 @@ pending_interpreted describe: Process do
error.to_s.should eq("hello#{newline}")
end
+ it "sends long output and error to IO" do
+ output = IO::Memory.new
+ error = IO::Memory.new
+ Process.run(*shell_command("echo #{"." * 8000}"), output: output, error: error)
+ output.to_s.should eq("." * 8000 + newline)
+ error.to_s.should be_empty
+ end
+
it "controls process in block" do
value = Process.run(*stdin_to_stdout_command, error: :inherit) do |proc|
proc.input.puts "hello"
@@ -189,6 +202,20 @@ pending_interpreted describe: Process do
Process.run(*stdin_to_stdout_command, error: closed_io)
end
+ it "forwards non-blocking file" do
+ with_tempfile("non-blocking-process-input.txt", "non-blocking-process-output.txt") do |in_path, out_path|
+ File.open(in_path, "w+", blocking: false) do |input|
+ File.open(out_path, "w+", blocking: false) do |output|
+ input.puts "hello"
+ input.rewind
+ Process.run(*stdin_to_stdout_command, input: input, output: output)
+ output.rewind
+ output.gets_to_end.chomp.should eq("hello")
+ end
+ end
+ end
+ end
+
it "sets working directory with string" do
parent = File.dirname(Dir.current)
command = {% if flag?(:win32) %}
@@ -465,6 +492,27 @@ pending_interpreted describe: Process do
{% end %}
describe ".exec" do
+ it "redirects STDIN and STDOUT to files", tags: %w[slow] do
+ with_tempfile("crystal-exec-stdin", "crystal-exec-stdout") do |stdin_path, stdout_path|
+ File.write(stdin_path, "foobar")
+
+ status, _, _ = compile_and_run_source <<-CRYSTAL
+ command = #{stdin_to_stdout_command[0].inspect}
+ args = #{stdin_to_stdout_command[1].to_a} of String
+ stdin_path = #{stdin_path.inspect}
+ stdout_path = #{stdout_path.inspect}
+ File.open(stdin_path) do |input|
+ File.open(stdout_path, "w") do |output|
+ Process.exec(command, args, input: input, output: output)
+ end
+ end
+ CRYSTAL
+
+ status.success?.should be_true
+ File.read(stdout_path).chomp.should eq("foobar")
+ end
+ end
+
it "gets error from exec" do
expect_raises(File::NotFoundError, "Error executing process: 'foobarbaz'") do
Process.exec("foobarbaz")
diff --git a/spec/std/regex_spec.cr b/spec/std/regex_spec.cr
index 13d301987c56..230976d6ad3e 100644
--- a/spec/std/regex_spec.cr
+++ b/spec/std/regex_spec.cr
@@ -250,6 +250,13 @@ describe "Regex" do
end
end
+ describe "multiline_only" do
+ it "anchor" do
+ ((/^foo.*$/m).match("foo\nbar")).try(&.[](0)).should eq "foo\nbar"
+ ((Regex.new("^foo.*?", Regex::Options::MULTILINE_ONLY)).match("foo\nbar")).try(&.[](0)).should eq "foo"
+ end
+ end
+
describe "extended" do
it "ignores white space" do
/foo bar/.matches?("foobar").should be_false
@@ -426,7 +433,7 @@ describe "Regex" do
})
end
- it "alpanumeric" do
+ it "alphanumeric" do
/(?)/.name_table.should eq({1 => "f1"})
end
diff --git a/spec/std/signal_spec.cr b/spec/std/signal_spec.cr
index cae1c5e83834..e27373e3be21 100644
--- a/spec/std/signal_spec.cr
+++ b/spec/std/signal_spec.cr
@@ -19,44 +19,48 @@ pending_interpreted describe: "Signal" do
end
{% unless flag?(:win32) %}
+ # can't use SIGUSR1/SIGUSR2 on FreeBSD because Boehm uses them to suspend/resume threads
+ signal1 = {% if flag?(:freebsd) %} Signal.new(LibC::SIGRTMAX - 1) {% else %} Signal::USR1 {% end %}
+ signal2 = {% if flag?(:freebsd) %} Signal.new(LibC::SIGRTMAX - 2) {% else %} Signal::USR2 {% end %}
+
it "runs a signal handler" do
ran = false
- Signal::USR1.trap do
+ signal1.trap do
ran = true
end
- Process.signal Signal::USR1, Process.pid
+ Process.signal signal1, Process.pid
10.times do |i|
break if ran
- sleep 0.1
+ sleep 0.1.seconds
end
ran.should be_true
ensure
- Signal::USR1.reset
+ signal1.reset
end
it "ignores a signal" do
- Signal::USR2.ignore
- Process.signal Signal::USR2, Process.pid
+ signal2.ignore
+ Process.signal signal2, Process.pid
end
it "allows chaining of signals" do
ran_first = false
ran_second = false
- Signal::USR1.trap { ran_first = true }
- existing = Signal::USR1.trap_handler?
+ signal1.trap { ran_first = true }
+ existing = signal1.trap_handler?
- Signal::USR1.trap do |signal|
+ signal1.trap do |signal|
existing.try &.call(signal)
ran_second = true
end
- Process.signal Signal::USR1, Process.pid
- sleep 0.1
+ Process.signal signal1, Process.pid
+ sleep 0.1.seconds
ran_first.should be_true
ran_second.should be_true
ensure
- Signal::USR1.reset
+ signal1.reset
end
it "CHLD.reset sets default Crystal child handler" do
diff --git a/spec/std/slice_spec.cr b/spec/std/slice_spec.cr
index 505db8f09109..7624b34c852c 100644
--- a/spec/std/slice_spec.cr
+++ b/spec/std/slice_spec.cr
@@ -104,25 +104,34 @@ describe "Slice" do
it "does []? with start and count" do
slice = Slice.new(4) { |i| i + 1 }
+
slice1 = slice[1, 2]?
slice1.should_not be_nil
slice1 = slice1.not_nil!
slice1.size.should eq(2)
+ slice1.to_unsafe.should eq(slice.to_unsafe + 1)
slice1[0].should eq(2)
slice1[1].should eq(3)
- slice[-1, 1]?.should be_nil
+ slice2 = slice[-1, 1]?
+ slice2.should_not be_nil
+ slice2 = slice2.not_nil!
+ slice2.size.should eq(1)
+ slice2.to_unsafe.should eq(slice.to_unsafe + 3)
+
slice[3, 2]?.should be_nil
slice[0, 5]?.should be_nil
- slice[3, -1]?.should be_nil
+ expect_raises(ArgumentError, "Negative count: -1") { slice[3, -1]? }
end
it "does []? with range" do
slice = Slice.new(4) { |i| i + 1 }
+
slice1 = slice[1..2]?
slice1.should_not be_nil
slice1 = slice1.not_nil!
slice1.size.should eq(2)
+ slice1.to_unsafe.should eq(slice.to_unsafe + 1)
slice1[0].should eq(2)
slice1[1].should eq(3)
@@ -134,15 +143,20 @@ describe "Slice" do
it "does [] with start and count" do
slice = Slice.new(4) { |i| i + 1 }
+
slice1 = slice[1, 2]
slice1.size.should eq(2)
+ slice1.to_unsafe.should eq(slice.to_unsafe + 1)
slice1[0].should eq(2)
slice1[1].should eq(3)
- expect_raises(IndexError) { slice[-1, 1] }
+ slice2 = slice[-1, 1]
+ slice2.size.should eq(1)
+ slice2.to_unsafe.should eq(slice.to_unsafe + 3)
+
expect_raises(IndexError) { slice[3, 2] }
expect_raises(IndexError) { slice[0, 5] }
- expect_raises(IndexError) { slice[3, -1] }
+ expect_raises(ArgumentError, "Negative count: -1") { slice[3, -1] }
end
it "does empty?" do
@@ -489,6 +503,20 @@ describe "Slice" do
end
end
+ it "#same?" do
+ slice = Slice[1, 2, 3]
+
+ slice.should be slice
+ slice.should_not be slice.dup
+ slice.should_not be Slice[1, 2, 3]
+
+ (slice + 1).should be slice + 1
+ slice.should_not be slice + 1
+
+ (slice[0, 2]).should be slice[0, 2]
+ slice.should_not be slice[0, 2]
+ end
+
it "does macro []" do
slice = Slice[1, 'a', "foo"]
slice.should be_a(Slice(Int32 | Char | String))
@@ -659,6 +687,7 @@ describe "Slice" do
subslice = slice[2..4]
subslice.read_only?.should be_false
subslice.size.should eq(3)
+ subslice.to_unsafe.should eq(slice.to_unsafe + 2)
subslice.should eq(Slice.new(3) { |i| i + 3 })
end
diff --git a/spec/std/socket/addrinfo_spec.cr b/spec/std/socket/addrinfo_spec.cr
index 615058472525..b1d6b459623d 100644
--- a/spec/std/socket/addrinfo_spec.cr
+++ b/spec/std/socket/addrinfo_spec.cr
@@ -22,6 +22,20 @@ describe Socket::Addrinfo, tags: "network" do
end
end
end
+
+ it "raises helpful message on getaddrinfo failure" do
+ expect_raises(Socket::Addrinfo::Error, "Hostname lookup for badhostname.unknown failed: ") do
+ Socket::Addrinfo.resolve("badhostname.unknown", 80, type: Socket::Type::DGRAM)
+ end
+ end
+
+ {% if flag?(:win32) %}
+ it "raises timeout error" do
+ expect_raises(IO::TimeoutError) do
+ Socket::Addrinfo.resolve("badhostname", 80, type: Socket::Type::STREAM, timeout: 0.milliseconds)
+ end
+ end
+ {% end %}
end
describe ".tcp" do
@@ -37,11 +51,13 @@ describe Socket::Addrinfo, tags: "network" do
end
end
- it "raises helpful message on getaddrinfo failure" do
- expect_raises(Socket::Addrinfo::Error, "Hostname lookup for badhostname failed: ") do
- Socket::Addrinfo.resolve("badhostname", 80, type: Socket::Type::DGRAM)
+ {% if flag?(:win32) %}
+ it "raises timeout error" do
+ expect_raises(IO::TimeoutError) do
+ Socket::Addrinfo.tcp("badhostname", 80, timeout: 0.milliseconds)
+ end
end
- end
+ {% end %}
end
describe ".udp" do
@@ -56,6 +72,14 @@ describe Socket::Addrinfo, tags: "network" do
typeof(addrinfo).should eq(Socket::Addrinfo)
end
end
+
+ {% if flag?(:win32) %}
+ it "raises timeout error" do
+ expect_raises(IO::TimeoutError) do
+ Socket::Addrinfo.udp("badhostname", 80, timeout: 0.milliseconds)
+ end
+ end
+ {% end %}
end
describe "#ip_address" do
diff --git a/spec/std/socket/socket_spec.cr b/spec/std/socket/socket_spec.cr
index d4e7051d12bd..65f7ed72a453 100644
--- a/spec/std/socket/socket_spec.cr
+++ b/spec/std/socket/socket_spec.cr
@@ -2,6 +2,12 @@ require "./spec_helper"
require "../../support/tempfile"
require "../../support/win32"
+# TODO: Windows networking in the interpreter requires #12495
+{% if flag?(:interpreted) && flag?(:win32) %}
+ pending Socket
+ {% skip_file %}
+{% end %}
+
describe Socket, tags: "network" do
describe ".unix" do
it "creates a unix socket" do
@@ -18,16 +24,19 @@ describe Socket, tags: "network" do
sock.type.should eq(Socket::Type::DGRAM)
{% end %}
- error = expect_raises(Socket::Error) do
- TCPSocket.new(family: :unix)
- end
- error.os_error.should eq({% if flag?(:win32) %}
- WinError::WSAEPROTONOSUPPORT
- {% elsif flag?(:wasi) %}
- WasiError::PROTONOSUPPORT
- {% else %}
- Errno.new(LibC::EPROTONOSUPPORT)
- {% end %})
+ {% unless flag?(:freebsd) %}
+ # for some reason this doesn't fail on freebsd
+ error = expect_raises(Socket::Error) do
+ TCPSocket.new(family: :unix)
+ end
+ error.os_error.should eq({% if flag?(:win32) %}
+ WinError::WSAEPROTONOSUPPORT
+ {% elsif flag?(:wasi) %}
+ WasiError::PROTONOSUPPORT
+ {% else %}
+ Errno.new(LibC::EPROTONOSUPPORT)
+ {% end %})
+ {% end %}
end
end
@@ -73,7 +82,7 @@ describe Socket, tags: "network" do
server = Socket.new(Socket::Family::INET, Socket::Type::STREAM, Socket::Protocol::TCP)
port = unused_local_port
server.bind("0.0.0.0", port)
- server.read_timeout = 0.1
+ server.read_timeout = 0.1.seconds
server.listen
expect_raises(IO::TimeoutError) { server.accept }
@@ -169,4 +178,32 @@ describe Socket, tags: "network" do
socket.close_on_exec?.should be_true
end
{% end %}
+
+ describe "#finalize" do
+ it "does not flush" do
+ port = unused_local_port
+ server = Socket.tcp(Socket::Family::INET)
+ server.bind("127.0.0.1", port)
+ server.listen
+
+ spawn do
+ client = server.not_nil!.accept
+ client.sync = false
+ client << "foo"
+ client.flush
+ client << "bar"
+ client.finalize
+ ensure
+ client.try(&.close) rescue nil
+ end
+
+ socket = Socket.tcp(Socket::Family::INET)
+ socket.connect(Socket::IPAddress.new("127.0.0.1", port))
+
+ socket.gets.should eq "foo"
+ ensure
+ socket.try &.close
+ server.try &.close
+ end
+ end
end
diff --git a/spec/std/socket/tcp_server_spec.cr b/spec/std/socket/tcp_server_spec.cr
index 0c6113a4a7ff..a7d85b8edeff 100644
--- a/spec/std/socket/tcp_server_spec.cr
+++ b/spec/std/socket/tcp_server_spec.cr
@@ -43,7 +43,7 @@ describe TCPServer, tags: "network" do
end
error.os_error.should eq({% if flag?(:win32) %}
WinError::WSATYPE_NOT_FOUND
- {% elsif flag?(:linux) && !flag?(:android) %}
+ {% elsif (flag?(:linux) && !flag?(:android)) || flag?(:openbsd) %}
Errno.new(LibC::EAI_SERVICE)
{% else %}
Errno.new(LibC::EAI_NONAME)
@@ -96,7 +96,7 @@ describe TCPServer, tags: "network" do
# FIXME: Resolve special handling for win32. The error code handling should be identical.
{% if flag?(:win32) %}
[WinError::WSAHOST_NOT_FOUND, WinError::WSATRY_AGAIN].should contain err.os_error
- {% elsif flag?(:android) %}
+ {% elsif flag?(:android) || flag?(:netbsd) || flag?(:openbsd) %}
err.os_error.should eq(Errno.new(LibC::EAI_NODATA))
{% else %}
[Errno.new(LibC::EAI_NONAME), Errno.new(LibC::EAI_AGAIN)].should contain err.os_error
@@ -110,7 +110,7 @@ describe TCPServer, tags: "network" do
# FIXME: Resolve special handling for win32. The error code handling should be identical.
{% if flag?(:win32) %}
[WinError::WSAHOST_NOT_FOUND, WinError::WSATRY_AGAIN].should contain err.os_error
- {% elsif flag?(:android) %}
+ {% elsif flag?(:android) || flag?(:netbsd) || flag?(:openbsd) %}
err.os_error.should eq(Errno.new(LibC::EAI_NODATA))
{% else %}
[Errno.new(LibC::EAI_NONAME), Errno.new(LibC::EAI_AGAIN)].should contain err.os_error
diff --git a/spec/std/socket/tcp_socket_spec.cr b/spec/std/socket/tcp_socket_spec.cr
index 68c00ccd2e79..0b3a381372bf 100644
--- a/spec/std/socket/tcp_socket_spec.cr
+++ b/spec/std/socket/tcp_socket_spec.cr
@@ -3,6 +3,12 @@
require "./spec_helper"
require "../../support/win32"
+# TODO: Windows networking in the interpreter requires #12495
+{% if flag?(:interpreted) && flag?(:win32) %}
+ pending TCPSocket
+ {% skip_file %}
+{% end %}
+
describe TCPSocket, tags: "network" do
describe "#connect" do
each_ip_family do |family, address|
@@ -41,7 +47,7 @@ describe TCPSocket, tags: "network" do
end
error.os_error.should eq({% if flag?(:win32) %}
WinError::WSATYPE_NOT_FOUND
- {% elsif flag?(:linux) && !flag?(:android) %}
+ {% elsif (flag?(:linux) && !flag?(:android)) || flag?(:openbsd) %}
Errno.new(LibC::EAI_SERVICE)
{% else %}
Errno.new(LibC::EAI_NONAME)
@@ -73,7 +79,7 @@ describe TCPSocket, tags: "network" do
# FIXME: Resolve special handling for win32. The error code handling should be identical.
{% if flag?(:win32) %}
[WinError::WSAHOST_NOT_FOUND, WinError::WSATRY_AGAIN].should contain err.os_error
- {% elsif flag?(:android) %}
+ {% elsif flag?(:android) || flag?(:netbsd) || flag?(:openbsd) %}
err.os_error.should eq(Errno.new(LibC::EAI_NODATA))
{% else %}
[Errno.new(LibC::EAI_NONAME), Errno.new(LibC::EAI_AGAIN)].should contain err.os_error
@@ -87,7 +93,7 @@ describe TCPSocket, tags: "network" do
# FIXME: Resolve special handling for win32. The error code handling should be identical.
{% if flag?(:win32) %}
[WinError::WSAHOST_NOT_FOUND, WinError::WSATRY_AGAIN].should contain err.os_error
- {% elsif flag?(:android) %}
+ {% elsif flag?(:android) || flag?(:netbsd) || flag?(:openbsd) %}
err.os_error.should eq(Errno.new(LibC::EAI_NODATA))
{% else %}
[Errno.new(LibC::EAI_NONAME), Errno.new(LibC::EAI_AGAIN)].should contain err.os_error
@@ -136,7 +142,7 @@ describe TCPSocket, tags: "network" do
(client.tcp_nodelay = false).should be_false
client.tcp_nodelay?.should be_false
- {% unless flag?(:openbsd) %}
+ {% unless flag?(:openbsd) || flag?(:netbsd) %}
(client.tcp_keepalive_idle = 42).should eq 42
client.tcp_keepalive_idle.should eq 42
(client.tcp_keepalive_interval = 42).should eq 42
diff --git a/spec/std/socket/udp_socket_spec.cr b/spec/std/socket/udp_socket_spec.cr
index 113a4ea3cf61..dc66d8038036 100644
--- a/spec/std/socket/udp_socket_spec.cr
+++ b/spec/std/socket/udp_socket_spec.cr
@@ -82,6 +82,15 @@ describe UDPSocket, tags: "network" do
# TODO: figure out why updating `multicast_loopback` produces a
# `setsockopt 18: Invalid argument` error
pending "joins and transmits to multicast groups"
+ elsif {{ flag?(:freebsd) }} && family == Socket::Family::INET6
+ # FIXME: fails with "Error sending datagram to [ipv6]:port: Network is unreachable"
+ pending "joins and transmits to multicast groups"
+ elsif {{ flag?(:netbsd) }} && family == Socket::Family::INET6
+ # FIXME: fails with "setsockopt: EADDRNOTAVAIL"
+ pending "joins and transmits to multicast groups"
+ elsif {{ flag?(:openbsd) }}
+ # FIXME: fails with "setsockopt: EINVAL (ipv4) or EADDRNOTAVAIL (ipv6)"
+ pending "joins and transmits to multicast groups"
else
it "joins and transmits to multicast groups" do
udp = UDPSocket.new(family)
diff --git a/spec/std/socket/unix_server_spec.cr b/spec/std/socket/unix_server_spec.cr
index ca364f08667c..60f0279b4091 100644
--- a/spec/std/socket/unix_server_spec.cr
+++ b/spec/std/socket/unix_server_spec.cr
@@ -4,6 +4,12 @@ require "../../support/fibers"
require "../../support/channel"
require "../../support/tempfile"
+# TODO: Windows networking in the interpreter requires #12495
+{% if flag?(:interpreted) && flag?(:win32) %}
+ pending UNIXServer
+ {% skip_file %}
+{% end %}
+
describe UNIXServer do
describe ".new" do
it "raises when path is too long" do
diff --git a/spec/std/socket/unix_socket_spec.cr b/spec/std/socket/unix_socket_spec.cr
index 24777bada67f..7e5eda4e2b65 100644
--- a/spec/std/socket/unix_socket_spec.cr
+++ b/spec/std/socket/unix_socket_spec.cr
@@ -2,6 +2,12 @@ require "spec"
require "socket"
require "../../support/tempfile"
+# TODO: Windows networking in the interpreter requires #12495
+{% if flag?(:interpreted) && flag?(:win32) %}
+ pending UNIXSocket
+ {% skip_file %}
+{% end %}
+
describe UNIXSocket do
it "raises when path is too long" do
with_tempfile("unix_socket-too_long-#{("a" * 2048)}.sock") do |path|
@@ -57,6 +63,30 @@ describe UNIXSocket do
end
end
+ it "#send, #receive" do
+ with_tempfile("unix_socket-receive.sock") do |path|
+ UNIXServer.open(path) do |server|
+ UNIXSocket.open(path) do |client|
+ server.accept do |sock|
+ client.send "ping"
+ message, address = sock.receive
+ message.should eq("ping")
+ typeof(address).should eq(Socket::UNIXAddress)
+ address.path.should eq ""
+
+ sock.send "pong"
+ message, address = client.receive
+ message.should eq("pong")
+ typeof(address).should eq(Socket::UNIXAddress)
+ # The value of path seems to be system-specific. Some implementations
+ # return the socket path, others an empty path.
+ ["", path].should contain address.path
+ end
+ end
+ end
+ end
+ end
+
# `LibC.socketpair` is not supported in Winsock 2.0 yet:
# https://devblogs.microsoft.com/commandline/af_unix-comes-to-windows/#unsupportedunavailable
{% unless flag?(:win32) %}
@@ -76,8 +106,8 @@ describe UNIXSocket do
it "tests read and write timeouts" do
UNIXSocket.pair do |left, right|
# BUG: shrink the socket buffers first
- left.write_timeout = 0.0001
- right.read_timeout = 0.0001
+ left.write_timeout = 0.1.milliseconds
+ right.read_timeout = 0.1.milliseconds
buf = ("a" * IO::DEFAULT_BUFFER_SIZE).to_slice
expect_raises(IO::TimeoutError, "Write timed out") do
diff --git a/spec/std/spec/expectations_spec.cr b/spec/std/spec/expectations_spec.cr
index 4acce2bfbad9..0831bca226ca 100644
--- a/spec/std/spec/expectations_spec.cr
+++ b/spec/std/spec/expectations_spec.cr
@@ -1,5 +1,17 @@
require "spec"
+private module MyModule; end
+
+private class Foo
+ include MyModule
+end
+
+private record NoObjectId, to_unsafe : Int32 do
+ def same?(other : self) : Bool
+ to_unsafe == other.to_unsafe
+ end
+end
+
describe "expectations" do
describe "accept a custom failure message" do
it { 1.should be < 3, "custom message!" }
@@ -25,6 +37,17 @@ describe "expectations" do
array = [1]
array.should_not be [1]
end
+
+ it "works with type that does not implement `#object_id`" do
+ a = NoObjectId.new(1)
+ a.should be a
+ a.should_not be NoObjectId.new(2)
+ end
+
+ it "works with module type (#14920)" do
+ a = Foo.new
+ a.as(MyModule).should be a.as(MyModule)
+ end
end
describe "be_a" do
diff --git a/spec/std/string/grapheme_break_spec.cr b/spec/std/string/grapheme_break_spec.cr
index f1a86656ef12..2ea30c104016 100644
--- a/spec/std/string/grapheme_break_spec.cr
+++ b/spec/std/string/grapheme_break_spec.cr
@@ -16,8 +16,8 @@ describe "String#each_grapheme" do
it_iterates_graphemes " \u0308\n", [" \u0308", '\n'] # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3]
it_iterates_graphemes " \u0001", [' ', '\u0001'] # ÷ [0.2] SPACE (Other) ÷ [5.0] (Control) ÷ [0.3]
it_iterates_graphemes " \u0308\u0001", [" \u0308", '\u0001'] # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3]
- it_iterates_graphemes " \u034F", [" \u034F"] # ÷ [0.2] SPACE (Other) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3]
- it_iterates_graphemes " \u0308\u034F", [" \u0308\u034F"] # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3]
+ it_iterates_graphemes " \u200C", [" \u200C"] # ÷ [0.2] SPACE (Other) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
+ it_iterates_graphemes " \u0308\u200C", [" \u0308\u200C"] # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
it_iterates_graphemes " \u{1F1E6}", [' ', '\u{1F1E6}'] # ÷ [0.2] SPACE (Other) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
it_iterates_graphemes " \u0308\u{1F1E6}", [" \u0308", '\u{1F1E6}'] # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
it_iterates_graphemes " \u0600", [' ', '\u0600'] # ÷ [0.2] SPACE (Other) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
@@ -34,8 +34,6 @@ describe "String#each_grapheme" do
it_iterates_graphemes " \u0308\uAC00", [" \u0308", '\uAC00'] # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
it_iterates_graphemes " \uAC01", [' ', '\uAC01'] # ÷ [0.2] SPACE (Other) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
it_iterates_graphemes " \u0308\uAC01", [" \u0308", '\uAC01'] # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
- it_iterates_graphemes " \u0900", [" \u0900"] # ÷ [0.2] SPACE (Other) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3]
- it_iterates_graphemes " \u0308\u0900", [" \u0308\u0900"] # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3]
it_iterates_graphemes " \u0903", [" \u0903"] # ÷ [0.2] SPACE (Other) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
it_iterates_graphemes " \u0308\u0903", [" \u0308\u0903"] # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
it_iterates_graphemes " \u0904", [' ', '\u0904'] # ÷ [0.2] SPACE (Other) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3]
@@ -48,8 +46,8 @@ describe "String#each_grapheme" do
it_iterates_graphemes " \u0308\u231A", [" \u0308", '\u231A'] # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
it_iterates_graphemes " \u0300", [" \u0300"] # ÷ [0.2] SPACE (Other) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
it_iterates_graphemes " \u0308\u0300", [" \u0308\u0300"] # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
- it_iterates_graphemes " \u093C", [" \u093C"] # ÷ [0.2] SPACE (Other) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
- it_iterates_graphemes " \u0308\u093C", [" \u0308\u093C"] # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+ it_iterates_graphemes " \u0900", [" \u0900"] # ÷ [0.2] SPACE (Other) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+ it_iterates_graphemes " \u0308\u0900", [" \u0308\u0900"] # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
it_iterates_graphemes " \u094D", [" \u094D"] # ÷ [0.2] SPACE (Other) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
it_iterates_graphemes " \u0308\u094D", [" \u0308\u094D"] # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
it_iterates_graphemes " \u200D", [" \u200D"] # ÷ [0.2] SPACE (Other) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
@@ -64,8 +62,8 @@ describe "String#each_grapheme" do
it_iterates_graphemes "\r\u0308\n", ['\r', '\u0308', '\n'] # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3]
it_iterates_graphemes "\r\u0001", ['\r', '\u0001'] # ÷ [0.2] (CR) ÷ [4.0] (Control) ÷ [0.3]
it_iterates_graphemes "\r\u0308\u0001", ['\r', '\u0308', '\u0001'] # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3]
- it_iterates_graphemes "\r\u034F", ['\r', '\u034F'] # ÷ [0.2] (CR) ÷ [4.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3]
- it_iterates_graphemes "\r\u0308\u034F", ['\r', "\u0308\u034F"] # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3]
+ it_iterates_graphemes "\r\u200C", ['\r', '\u200C'] # ÷ [0.2] (CR) ÷ [4.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
+ it_iterates_graphemes "\r\u0308\u200C", ['\r', "\u0308\u200C"] # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
it_iterates_graphemes "\r\u{1F1E6}", ['\r', '\u{1F1E6}'] # ÷ [0.2] (CR) ÷ [4.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
it_iterates_graphemes "\r\u0308\u{1F1E6}", ['\r', '\u0308', '\u{1F1E6}'] # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
it_iterates_graphemes "\r\u0600", ['\r', '\u0600'] # ÷ [0.2] (CR) ÷ [4.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
@@ -82,8 +80,6 @@ describe "String#each_grapheme" do
it_iterates_graphemes "\r\u0308\uAC00", ['\r', '\u0308', '\uAC00'] # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
it_iterates_graphemes "\r\uAC01", ['\r', '\uAC01'] # ÷ [0.2] (CR) ÷ [4.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
it_iterates_graphemes "\r\u0308\uAC01", ['\r', '\u0308', '\uAC01'] # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
- it_iterates_graphemes "\r\u0900", ['\r', '\u0900'] # ÷ [0.2] (CR) ÷ [4.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3]
- it_iterates_graphemes "\r\u0308\u0900", ['\r', "\u0308\u0900"] # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3]
it_iterates_graphemes "\r\u0903", ['\r', '\u0903'] # ÷ [0.2] (CR) ÷ [4.0] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
it_iterates_graphemes "\r\u0308\u0903", ['\r', "\u0308\u0903"] # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
it_iterates_graphemes "\r\u0904", ['\r', '\u0904'] # ÷ [0.2] (CR) ÷ [4.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3]
@@ -96,8 +92,8 @@ describe "String#each_grapheme" do
it_iterates_graphemes "\r\u0308\u231A", ['\r', '\u0308', '\u231A'] # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
it_iterates_graphemes "\r\u0300", ['\r', '\u0300'] # ÷ [0.2] (CR) ÷ [4.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
it_iterates_graphemes "\r\u0308\u0300", ['\r', "\u0308\u0300"] # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
- it_iterates_graphemes "\r\u093C", ['\r', '\u093C'] # ÷ [0.2] (CR) ÷ [4.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
- it_iterates_graphemes "\r\u0308\u093C", ['\r', "\u0308\u093C"] # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+ it_iterates_graphemes "\r\u0900", ['\r', '\u0900'] # ÷ [0.2] (CR) ÷ [4.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+ it_iterates_graphemes "\r\u0308\u0900", ['\r', "\u0308\u0900"] # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
it_iterates_graphemes "\r\u094D", ['\r', '\u094D'] # ÷ [0.2] (CR) ÷ [4.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
it_iterates_graphemes "\r\u0308\u094D", ['\r', "\u0308\u094D"] # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
it_iterates_graphemes "\r\u200D", ['\r', '\u200D'] # ÷ [0.2] (CR) ÷ [4.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
@@ -112,8 +108,8 @@ describe "String#each_grapheme" do
it_iterates_graphemes "\n\u0308\n", ['\n', '\u0308', '\n'] # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3]
it_iterates_graphemes "\n\u0001", ['\n', '\u0001'] # ÷ [0.2] (LF) ÷ [4.0] (Control) ÷ [0.3]
it_iterates_graphemes "\n\u0308\u0001", ['\n', '\u0308', '\u0001'] # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3]
- it_iterates_graphemes "\n\u034F", ['\n', '\u034F'] # ÷ [0.2] (LF) ÷ [4.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3]
- it_iterates_graphemes "\n\u0308\u034F", ['\n', "\u0308\u034F"] # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3]
+ it_iterates_graphemes "\n\u200C", ['\n', '\u200C'] # ÷ [0.2] (LF) ÷ [4.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
+ it_iterates_graphemes "\n\u0308\u200C", ['\n', "\u0308\u200C"] # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
it_iterates_graphemes "\n\u{1F1E6}", ['\n', '\u{1F1E6}'] # ÷ [0.2] (LF) ÷ [4.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
it_iterates_graphemes "\n\u0308\u{1F1E6}", ['\n', '\u0308', '\u{1F1E6}'] # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
it_iterates_graphemes "\n\u0600", ['\n', '\u0600'] # ÷ [0.2] (LF) ÷ [4.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
@@ -130,8 +126,6 @@ describe "String#each_grapheme" do
it_iterates_graphemes "\n\u0308\uAC00", ['\n', '\u0308', '\uAC00'] # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
it_iterates_graphemes "\n\uAC01", ['\n', '\uAC01'] # ÷ [0.2] (LF) ÷ [4.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
it_iterates_graphemes "\n\u0308\uAC01", ['\n', '\u0308', '\uAC01'] # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
- it_iterates_graphemes "\n\u0900", ['\n', '\u0900'] # ÷ [0.2] (LF) ÷ [4.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3]
- it_iterates_graphemes "\n\u0308\u0900", ['\n', "\u0308\u0900"] # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3]
it_iterates_graphemes "\n\u0903", ['\n', '\u0903'] # ÷ [0.2] (LF) ÷ [4.0] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
it_iterates_graphemes "\n\u0308\u0903", ['\n', "\u0308\u0903"] # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
it_iterates_graphemes "\n\u0904", ['\n', '\u0904'] # ÷ [0.2] (LF) ÷ [4.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3]
@@ -144,8 +138,8 @@ describe "String#each_grapheme" do
it_iterates_graphemes "\n\u0308\u231A", ['\n', '\u0308', '\u231A'] # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
it_iterates_graphemes "\n\u0300", ['\n', '\u0300'] # ÷ [0.2] (LF) ÷ [4.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
it_iterates_graphemes "\n\u0308\u0300", ['\n', "\u0308\u0300"] # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
- it_iterates_graphemes "\n\u093C", ['\n', '\u093C'] # ÷ [0.2] (LF) ÷ [4.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
- it_iterates_graphemes "\n\u0308\u093C", ['\n', "\u0308\u093C"] # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+ it_iterates_graphemes "\n\u0900", ['\n', '\u0900'] # ÷ [0.2] (LF) ÷ [4.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+ it_iterates_graphemes "\n\u0308\u0900", ['\n', "\u0308\u0900"] # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
it_iterates_graphemes "\n\u094D", ['\n', '\u094D'] # ÷ [0.2] (LF) ÷ [4.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
it_iterates_graphemes "\n\u0308\u094D", ['\n', "\u0308\u094D"] # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
it_iterates_graphemes "\n\u200D", ['\n', '\u200D'] # ÷ [0.2] (LF) ÷ [4.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
@@ -160,8 +154,8 @@ describe "String#each_grapheme" do
it_iterates_graphemes "\u0001\u0308\n", ['\u0001', '\u0308', '\n'] # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3]
it_iterates_graphemes "\u0001\u0001", ['\u0001', '\u0001'] # ÷ [0.2] (Control) ÷ [4.0] (Control) ÷ [0.3]
it_iterates_graphemes "\u0001\u0308\u0001", ['\u0001', '\u0308', '\u0001'] # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3]
- it_iterates_graphemes "\u0001\u034F", ['\u0001', '\u034F'] # ÷ [0.2] (Control) ÷ [4.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3]
- it_iterates_graphemes "\u0001\u0308\u034F", ['\u0001', "\u0308\u034F"] # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3]
+ it_iterates_graphemes "\u0001\u200C", ['\u0001', '\u200C'] # ÷ [0.2] (Control) ÷ [4.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
+ it_iterates_graphemes "\u0001\u0308\u200C", ['\u0001', "\u0308\u200C"] # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
it_iterates_graphemes "\u0001\u{1F1E6}", ['\u0001', '\u{1F1E6}'] # ÷ [0.2] (Control) ÷ [4.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
it_iterates_graphemes "\u0001\u0308\u{1F1E6}", ['\u0001', '\u0308', '\u{1F1E6}'] # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
it_iterates_graphemes "\u0001\u0600", ['\u0001', '\u0600'] # ÷ [0.2] (Control) ÷ [4.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
@@ -178,8 +172,6 @@ describe "String#each_grapheme" do
it_iterates_graphemes "\u0001\u0308\uAC00", ['\u0001', '\u0308', '\uAC00'] # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
it_iterates_graphemes "\u0001\uAC01", ['\u0001', '\uAC01'] # ÷ [0.2] (Control) ÷ [4.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
it_iterates_graphemes "\u0001\u0308\uAC01", ['\u0001', '\u0308', '\uAC01'] # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
- it_iterates_graphemes "\u0001\u0900", ['\u0001', '\u0900'] # ÷ [0.2] (Control) ÷ [4.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3]
- it_iterates_graphemes "\u0001\u0308\u0900", ['\u0001', "\u0308\u0900"] # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3]
it_iterates_graphemes "\u0001\u0903", ['\u0001', '\u0903'] # ÷ [0.2] (Control) ÷ [4.0] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
it_iterates_graphemes "\u0001\u0308\u0903", ['\u0001', "\u0308\u0903"] # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
it_iterates_graphemes "\u0001\u0904", ['\u0001', '\u0904'] # ÷ [0.2] (Control) ÷ [4.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3]
@@ -192,62 +184,60 @@ describe "String#each_grapheme" do
it_iterates_graphemes "\u0001\u0308\u231A", ['\u0001', '\u0308', '\u231A'] # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
it_iterates_graphemes "\u0001\u0300", ['\u0001', '\u0300'] # ÷ [0.2] (Control) ÷ [4.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
it_iterates_graphemes "\u0001\u0308\u0300", ['\u0001', "\u0308\u0300"] # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
- it_iterates_graphemes "\u0001\u093C", ['\u0001', '\u093C'] # ÷ [0.2] (Control) ÷ [4.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
- it_iterates_graphemes "\u0001\u0308\u093C", ['\u0001', "\u0308\u093C"] # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+ it_iterates_graphemes "\u0001\u0900", ['\u0001', '\u0900'] # ÷ [0.2] (Control) ÷ [4.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+ it_iterates_graphemes "\u0001\u0308\u0900", ['\u0001', "\u0308\u0900"] # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
it_iterates_graphemes "\u0001\u094D", ['\u0001', '\u094D'] # ÷ [0.2] (Control) ÷ [4.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
it_iterates_graphemes "\u0001\u0308\u094D", ['\u0001', "\u0308\u094D"] # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
it_iterates_graphemes "\u0001\u200D", ['\u0001', '\u200D'] # ÷ [0.2] (Control) ÷ [4.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
it_iterates_graphemes "\u0001\u0308\u200D", ['\u0001', "\u0308\u200D"] # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
it_iterates_graphemes "\u0001\u0378", ['\u0001', '\u0378'] # ÷ [0.2] (Control) ÷ [4.0] (Other) ÷ [0.3]
it_iterates_graphemes "\u0001\u0308\u0378", ['\u0001', '\u0308', '\u0378'] # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] (Other) ÷ [0.3]
- it_iterates_graphemes "\u034F ", ['\u034F', ' '] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) ÷ [999.0] SPACE (Other) ÷ [0.3]
- it_iterates_graphemes "\u034F\u0308 ", ["\u034F\u0308", ' '] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3]
- it_iterates_graphemes "\u034F\r", ['\u034F', '\r'] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) ÷ [5.0] (CR) ÷ [0.3]
- it_iterates_graphemes "\u034F\u0308\r", ["\u034F\u0308", '\r'] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CR) ÷ [0.3]
- it_iterates_graphemes "\u034F\n", ['\u034F', '\n'] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) ÷ [5.0] (LF) ÷ [0.3]
- it_iterates_graphemes "\u034F\u0308\n", ["\u034F\u0308", '\n'] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3]
- it_iterates_graphemes "\u034F\u0001", ['\u034F', '\u0001'] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) ÷ [5.0] (Control) ÷ [0.3]
- it_iterates_graphemes "\u034F\u0308\u0001", ["\u034F\u0308", '\u0001'] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3]
- it_iterates_graphemes "\u034F\u034F", ["\u034F\u034F"] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3]
- it_iterates_graphemes "\u034F\u0308\u034F", ["\u034F\u0308\u034F"] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3]
- it_iterates_graphemes "\u034F\u{1F1E6}", ['\u034F', '\u{1F1E6}'] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
- it_iterates_graphemes "\u034F\u0308\u{1F1E6}", ["\u034F\u0308", '\u{1F1E6}'] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
- it_iterates_graphemes "\u034F\u0600", ['\u034F', '\u0600'] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
- it_iterates_graphemes "\u034F\u0308\u0600", ["\u034F\u0308", '\u0600'] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
- it_iterates_graphemes "\u034F\u0A03", ["\u034F\u0A03"] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3]
- it_iterates_graphemes "\u034F\u0308\u0A03", ["\u034F\u0308\u0A03"] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3]
- it_iterates_graphemes "\u034F\u1100", ['\u034F', '\u1100'] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
- it_iterates_graphemes "\u034F\u0308\u1100", ["\u034F\u0308", '\u1100'] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
- it_iterates_graphemes "\u034F\u1160", ['\u034F', '\u1160'] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
- it_iterates_graphemes "\u034F\u0308\u1160", ["\u034F\u0308", '\u1160'] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
- it_iterates_graphemes "\u034F\u11A8", ['\u034F', '\u11A8'] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
- it_iterates_graphemes "\u034F\u0308\u11A8", ["\u034F\u0308", '\u11A8'] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
- it_iterates_graphemes "\u034F\uAC00", ['\u034F', '\uAC00'] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
- it_iterates_graphemes "\u034F\u0308\uAC00", ["\u034F\u0308", '\uAC00'] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
- it_iterates_graphemes "\u034F\uAC01", ['\u034F', '\uAC01'] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
- it_iterates_graphemes "\u034F\u0308\uAC01", ["\u034F\u0308", '\uAC01'] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
- it_iterates_graphemes "\u034F\u0900", ["\u034F\u0900"] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3]
- it_iterates_graphemes "\u034F\u0308\u0900", ["\u034F\u0308\u0900"] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3]
- it_iterates_graphemes "\u034F\u0903", ["\u034F\u0903"] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
- it_iterates_graphemes "\u034F\u0308\u0903", ["\u034F\u0308\u0903"] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
- it_iterates_graphemes "\u034F\u0904", ['\u034F', '\u0904'] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3]
- it_iterates_graphemes "\u034F\u0308\u0904", ["\u034F\u0308", '\u0904'] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3]
- it_iterates_graphemes "\u034F\u0D4E", ['\u034F', '\u0D4E'] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3]
- it_iterates_graphemes "\u034F\u0308\u0D4E", ["\u034F\u0308", '\u0D4E'] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3]
- it_iterates_graphemes "\u034F\u0915", ['\u034F', '\u0915'] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3]
- it_iterates_graphemes "\u034F\u0308\u0915", ["\u034F\u0308", '\u0915'] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3]
- it_iterates_graphemes "\u034F\u231A", ['\u034F', '\u231A'] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
- it_iterates_graphemes "\u034F\u0308\u231A", ["\u034F\u0308", '\u231A'] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
- it_iterates_graphemes "\u034F\u0300", ["\u034F\u0300"] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
- it_iterates_graphemes "\u034F\u0308\u0300", ["\u034F\u0308\u0300"] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
- it_iterates_graphemes "\u034F\u093C", ["\u034F\u093C"] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
- it_iterates_graphemes "\u034F\u0308\u093C", ["\u034F\u0308\u093C"] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
- it_iterates_graphemes "\u034F\u094D", ["\u034F\u094D"] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
- it_iterates_graphemes "\u034F\u0308\u094D", ["\u034F\u0308\u094D"] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
- it_iterates_graphemes "\u034F\u200D", ["\u034F\u200D"] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
- it_iterates_graphemes "\u034F\u0308\u200D", ["\u034F\u0308\u200D"] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
- it_iterates_graphemes "\u034F\u0378", ['\u034F', '\u0378'] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) ÷ [999.0] (Other) ÷ [0.3]
- it_iterates_graphemes "\u034F\u0308\u0378", ["\u034F\u0308", '\u0378'] # ÷ [0.2] COMBINING GRAPHEME JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] (Other) ÷ [0.3]
+ it_iterates_graphemes "\u200C ", ['\u200C', ' '] # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) ÷ [999.0] SPACE (Other) ÷ [0.3]
+ it_iterates_graphemes "\u200C\u0308 ", ["\u200C\u0308", ' '] # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3]
+ it_iterates_graphemes "\u200C\r", ['\u200C', '\r'] # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) ÷ [5.0] (CR) ÷ [0.3]
+ it_iterates_graphemes "\u200C\u0308\r", ["\u200C\u0308", '\r'] # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CR) ÷ [0.3]
+ it_iterates_graphemes "\u200C\n", ['\u200C', '\n'] # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) ÷ [5.0] (LF) ÷ [0.3]
+ it_iterates_graphemes "\u200C\u0308\n", ["\u200C\u0308", '\n'] # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3]
+ it_iterates_graphemes "\u200C\u0001", ['\u200C', '\u0001'] # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) ÷ [5.0] (Control) ÷ [0.3]
+ it_iterates_graphemes "\u200C\u0308\u0001", ["\u200C\u0308", '\u0001'] # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3]
+ it_iterates_graphemes "\u200C\u200C", ["\u200C\u200C"] # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
+ it_iterates_graphemes "\u200C\u0308\u200C", ["\u200C\u0308\u200C"] # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
+ it_iterates_graphemes "\u200C\u{1F1E6}", ['\u200C', '\u{1F1E6}'] # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
+ it_iterates_graphemes "\u200C\u0308\u{1F1E6}", ["\u200C\u0308", '\u{1F1E6}'] # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
+ it_iterates_graphemes "\u200C\u0600", ['\u200C', '\u0600'] # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
+ it_iterates_graphemes "\u200C\u0308\u0600", ["\u200C\u0308", '\u0600'] # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
+ it_iterates_graphemes "\u200C\u0A03", ["\u200C\u0A03"] # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3]
+ it_iterates_graphemes "\u200C\u0308\u0A03", ["\u200C\u0308\u0A03"] # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3]
+ it_iterates_graphemes "\u200C\u1100", ['\u200C', '\u1100'] # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
+ it_iterates_graphemes "\u200C\u0308\u1100", ["\u200C\u0308", '\u1100'] # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
+ it_iterates_graphemes "\u200C\u1160", ['\u200C', '\u1160'] # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
+ it_iterates_graphemes "\u200C\u0308\u1160", ["\u200C\u0308", '\u1160'] # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
+ it_iterates_graphemes "\u200C\u11A8", ['\u200C', '\u11A8'] # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
+ it_iterates_graphemes "\u200C\u0308\u11A8", ["\u200C\u0308", '\u11A8'] # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
+ it_iterates_graphemes "\u200C\uAC00", ['\u200C', '\uAC00'] # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
+ it_iterates_graphemes "\u200C\u0308\uAC00", ["\u200C\u0308", '\uAC00'] # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
+ it_iterates_graphemes "\u200C\uAC01", ['\u200C', '\uAC01'] # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
+ it_iterates_graphemes "\u200C\u0308\uAC01", ["\u200C\u0308", '\uAC01'] # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
+ it_iterates_graphemes "\u200C\u0903", ["\u200C\u0903"] # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
+ it_iterates_graphemes "\u200C\u0308\u0903", ["\u200C\u0308\u0903"] # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
+ it_iterates_graphemes "\u200C\u0904", ['\u200C', '\u0904'] # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3]
+ it_iterates_graphemes "\u200C\u0308\u0904", ["\u200C\u0308", '\u0904'] # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3]
+ it_iterates_graphemes "\u200C\u0D4E", ['\u200C', '\u0D4E'] # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3]
+ it_iterates_graphemes "\u200C\u0308\u0D4E", ["\u200C\u0308", '\u0D4E'] # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3]
+ it_iterates_graphemes "\u200C\u0915", ['\u200C', '\u0915'] # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3]
+ it_iterates_graphemes "\u200C\u0308\u0915", ["\u200C\u0308", '\u0915'] # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3]
+ it_iterates_graphemes "\u200C\u231A", ['\u200C', '\u231A'] # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
+ it_iterates_graphemes "\u200C\u0308\u231A", ["\u200C\u0308", '\u231A'] # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
+ it_iterates_graphemes "\u200C\u0300", ["\u200C\u0300"] # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+ it_iterates_graphemes "\u200C\u0308\u0300", ["\u200C\u0308\u0300"] # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+ it_iterates_graphemes "\u200C\u0900", ["\u200C\u0900"] # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+ it_iterates_graphemes "\u200C\u0308\u0900", ["\u200C\u0308\u0900"] # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+ it_iterates_graphemes "\u200C\u094D", ["\u200C\u094D"] # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
+ it_iterates_graphemes "\u200C\u0308\u094D", ["\u200C\u0308\u094D"] # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
+ it_iterates_graphemes "\u200C\u200D", ["\u200C\u200D"] # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
+ it_iterates_graphemes "\u200C\u0308\u200D", ["\u200C\u0308\u200D"] # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
+ it_iterates_graphemes "\u200C\u0378", ['\u200C', '\u0378'] # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) ÷ [999.0] (Other) ÷ [0.3]
+ it_iterates_graphemes "\u200C\u0308\u0378", ["\u200C\u0308", '\u0378'] # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] (Other) ÷ [0.3]
it_iterates_graphemes "\u{1F1E6} ", ['\u{1F1E6}', ' '] # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [999.0] SPACE (Other) ÷ [0.3]
it_iterates_graphemes "\u{1F1E6}\u0308 ", ["\u{1F1E6}\u0308", ' '] # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3]
it_iterates_graphemes "\u{1F1E6}\r", ['\u{1F1E6}', '\r'] # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [5.0] (CR) ÷ [0.3]
@@ -256,8 +246,8 @@ describe "String#each_grapheme" do
it_iterates_graphemes "\u{1F1E6}\u0308\n", ["\u{1F1E6}\u0308", '\n'] # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3]
it_iterates_graphemes "\u{1F1E6}\u0001", ['\u{1F1E6}', '\u0001'] # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [5.0] (Control) ÷ [0.3]
it_iterates_graphemes "\u{1F1E6}\u0308\u0001", ["\u{1F1E6}\u0308", '\u0001'] # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3]
- it_iterates_graphemes "\u{1F1E6}\u034F", ["\u{1F1E6}\u034F"] # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3]
- it_iterates_graphemes "\u{1F1E6}\u0308\u034F", ["\u{1F1E6}\u0308\u034F"] # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3]
+ it_iterates_graphemes "\u{1F1E6}\u200C", ["\u{1F1E6}\u200C"] # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
+ it_iterates_graphemes "\u{1F1E6}\u0308\u200C", ["\u{1F1E6}\u0308\u200C"] # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
it_iterates_graphemes "\u{1F1E6}\u{1F1E6}", ["\u{1F1E6}\u{1F1E6}"] # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [12.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
it_iterates_graphemes "\u{1F1E6}\u0308\u{1F1E6}", ["\u{1F1E6}\u0308", '\u{1F1E6}'] # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
it_iterates_graphemes "\u{1F1E6}\u0600", ['\u{1F1E6}', '\u0600'] # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
@@ -274,8 +264,6 @@ describe "String#each_grapheme" do
it_iterates_graphemes "\u{1F1E6}\u0308\uAC00", ["\u{1F1E6}\u0308", '\uAC00'] # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
it_iterates_graphemes "\u{1F1E6}\uAC01", ['\u{1F1E6}', '\uAC01'] # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
it_iterates_graphemes "\u{1F1E6}\u0308\uAC01", ["\u{1F1E6}\u0308", '\uAC01'] # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
- it_iterates_graphemes "\u{1F1E6}\u0900", ["\u{1F1E6}\u0900"] # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3]
- it_iterates_graphemes "\u{1F1E6}\u0308\u0900", ["\u{1F1E6}\u0308\u0900"] # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3]
it_iterates_graphemes "\u{1F1E6}\u0903", ["\u{1F1E6}\u0903"] # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
it_iterates_graphemes "\u{1F1E6}\u0308\u0903", ["\u{1F1E6}\u0308\u0903"] # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
it_iterates_graphemes "\u{1F1E6}\u0904", ['\u{1F1E6}', '\u0904'] # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3]
@@ -288,8 +276,8 @@ describe "String#each_grapheme" do
it_iterates_graphemes "\u{1F1E6}\u0308\u231A", ["\u{1F1E6}\u0308", '\u231A'] # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
it_iterates_graphemes "\u{1F1E6}\u0300", ["\u{1F1E6}\u0300"] # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
it_iterates_graphemes "\u{1F1E6}\u0308\u0300", ["\u{1F1E6}\u0308\u0300"] # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
- it_iterates_graphemes "\u{1F1E6}\u093C", ["\u{1F1E6}\u093C"] # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
- it_iterates_graphemes "\u{1F1E6}\u0308\u093C", ["\u{1F1E6}\u0308\u093C"] # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+ it_iterates_graphemes "\u{1F1E6}\u0900", ["\u{1F1E6}\u0900"] # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+ it_iterates_graphemes "\u{1F1E6}\u0308\u0900", ["\u{1F1E6}\u0308\u0900"] # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
it_iterates_graphemes "\u{1F1E6}\u094D", ["\u{1F1E6}\u094D"] # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
it_iterates_graphemes "\u{1F1E6}\u0308\u094D", ["\u{1F1E6}\u0308\u094D"] # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
it_iterates_graphemes "\u{1F1E6}\u200D", ["\u{1F1E6}\u200D"] # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
@@ -304,8 +292,8 @@ describe "String#each_grapheme" do
it_iterates_graphemes "\u0600\u0308\n", ["\u0600\u0308", '\n'] # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3]
it_iterates_graphemes "\u0600\u0001", ['\u0600', '\u0001'] # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) ÷ [5.0] (Control) ÷ [0.3]
it_iterates_graphemes "\u0600\u0308\u0001", ["\u0600\u0308", '\u0001'] # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3]
- it_iterates_graphemes "\u0600\u034F", ["\u0600\u034F"] # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3]
- it_iterates_graphemes "\u0600\u0308\u034F", ["\u0600\u0308\u034F"] # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3]
+ it_iterates_graphemes "\u0600\u200C", ["\u0600\u200C"] # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
+ it_iterates_graphemes "\u0600\u0308\u200C", ["\u0600\u0308\u200C"] # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
it_iterates_graphemes "\u0600\u{1F1E6}", ["\u0600\u{1F1E6}"] # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
it_iterates_graphemes "\u0600\u0308\u{1F1E6}", ["\u0600\u0308", '\u{1F1E6}'] # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
it_iterates_graphemes "\u0600\u0600", ["\u0600\u0600"] # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.2] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
@@ -322,8 +310,6 @@ describe "String#each_grapheme" do
it_iterates_graphemes "\u0600\u0308\uAC00", ["\u0600\u0308", '\uAC00'] # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
it_iterates_graphemes "\u0600\uAC01", ["\u0600\uAC01"] # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.2] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
it_iterates_graphemes "\u0600\u0308\uAC01", ["\u0600\u0308", '\uAC01'] # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
- it_iterates_graphemes "\u0600\u0900", ["\u0600\u0900"] # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3]
- it_iterates_graphemes "\u0600\u0308\u0900", ["\u0600\u0308\u0900"] # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3]
it_iterates_graphemes "\u0600\u0903", ["\u0600\u0903"] # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
it_iterates_graphemes "\u0600\u0308\u0903", ["\u0600\u0308\u0903"] # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
it_iterates_graphemes "\u0600\u0904", ["\u0600\u0904"] # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3]
@@ -336,8 +322,8 @@ describe "String#each_grapheme" do
it_iterates_graphemes "\u0600\u0308\u231A", ["\u0600\u0308", '\u231A'] # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
it_iterates_graphemes "\u0600\u0300", ["\u0600\u0300"] # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
it_iterates_graphemes "\u0600\u0308\u0300", ["\u0600\u0308\u0300"] # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
- it_iterates_graphemes "\u0600\u093C", ["\u0600\u093C"] # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
- it_iterates_graphemes "\u0600\u0308\u093C", ["\u0600\u0308\u093C"] # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+ it_iterates_graphemes "\u0600\u0900", ["\u0600\u0900"] # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+ it_iterates_graphemes "\u0600\u0308\u0900", ["\u0600\u0308\u0900"] # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
it_iterates_graphemes "\u0600\u094D", ["\u0600\u094D"] # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
it_iterates_graphemes "\u0600\u0308\u094D", ["\u0600\u0308\u094D"] # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
it_iterates_graphemes "\u0600\u200D", ["\u0600\u200D"] # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
@@ -352,8 +338,8 @@ describe "String#each_grapheme" do
it_iterates_graphemes "\u0A03\u0308\n", ["\u0A03\u0308", '\n'] # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3]
it_iterates_graphemes "\u0A03\u0001", ['\u0A03', '\u0001'] # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [5.0] (Control) ÷ [0.3]
it_iterates_graphemes "\u0A03\u0308\u0001", ["\u0A03\u0308", '\u0001'] # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3]
- it_iterates_graphemes "\u0A03\u034F", ["\u0A03\u034F"] # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3]
- it_iterates_graphemes "\u0A03\u0308\u034F", ["\u0A03\u0308\u034F"] # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3]
+ it_iterates_graphemes "\u0A03\u200C", ["\u0A03\u200C"] # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
+ it_iterates_graphemes "\u0A03\u0308\u200C", ["\u0A03\u0308\u200C"] # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
it_iterates_graphemes "\u0A03\u{1F1E6}", ['\u0A03', '\u{1F1E6}'] # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
it_iterates_graphemes "\u0A03\u0308\u{1F1E6}", ["\u0A03\u0308", '\u{1F1E6}'] # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
it_iterates_graphemes "\u0A03\u0600", ['\u0A03', '\u0600'] # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
@@ -370,8 +356,6 @@ describe "String#each_grapheme" do
it_iterates_graphemes "\u0A03\u0308\uAC00", ["\u0A03\u0308", '\uAC00'] # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
it_iterates_graphemes "\u0A03\uAC01", ['\u0A03', '\uAC01'] # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
it_iterates_graphemes "\u0A03\u0308\uAC01", ["\u0A03\u0308", '\uAC01'] # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
- it_iterates_graphemes "\u0A03\u0900", ["\u0A03\u0900"] # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3]
- it_iterates_graphemes "\u0A03\u0308\u0900", ["\u0A03\u0308\u0900"] # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3]
it_iterates_graphemes "\u0A03\u0903", ["\u0A03\u0903"] # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
it_iterates_graphemes "\u0A03\u0308\u0903", ["\u0A03\u0308\u0903"] # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
it_iterates_graphemes "\u0A03\u0904", ['\u0A03', '\u0904'] # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3]
@@ -384,8 +368,8 @@ describe "String#each_grapheme" do
it_iterates_graphemes "\u0A03\u0308\u231A", ["\u0A03\u0308", '\u231A'] # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
it_iterates_graphemes "\u0A03\u0300", ["\u0A03\u0300"] # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
it_iterates_graphemes "\u0A03\u0308\u0300", ["\u0A03\u0308\u0300"] # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
- it_iterates_graphemes "\u0A03\u093C", ["\u0A03\u093C"] # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
- it_iterates_graphemes "\u0A03\u0308\u093C", ["\u0A03\u0308\u093C"] # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+ it_iterates_graphemes "\u0A03\u0900", ["\u0A03\u0900"] # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+ it_iterates_graphemes "\u0A03\u0308\u0900", ["\u0A03\u0308\u0900"] # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
it_iterates_graphemes "\u0A03\u094D", ["\u0A03\u094D"] # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
it_iterates_graphemes "\u0A03\u0308\u094D", ["\u0A03\u0308\u094D"] # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
it_iterates_graphemes "\u0A03\u200D", ["\u0A03\u200D"] # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
@@ -400,8 +384,8 @@ describe "String#each_grapheme" do
it_iterates_graphemes "\u1100\u0308\n", ["\u1100\u0308", '\n'] # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3]
it_iterates_graphemes "\u1100\u0001", ['\u1100', '\u0001'] # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) ÷ [5.0] (Control) ÷ [0.3]
it_iterates_graphemes "\u1100\u0308\u0001", ["\u1100\u0308", '\u0001'] # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3]
- it_iterates_graphemes "\u1100\u034F", ["\u1100\u034F"] # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3]
- it_iterates_graphemes "\u1100\u0308\u034F", ["\u1100\u0308\u034F"] # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3]
+ it_iterates_graphemes "\u1100\u200C", ["\u1100\u200C"] # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
+ it_iterates_graphemes "\u1100\u0308\u200C", ["\u1100\u0308\u200C"] # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
it_iterates_graphemes "\u1100\u{1F1E6}", ['\u1100', '\u{1F1E6}'] # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
it_iterates_graphemes "\u1100\u0308\u{1F1E6}", ["\u1100\u0308", '\u{1F1E6}'] # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
it_iterates_graphemes "\u1100\u0600", ['\u1100', '\u0600'] # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
@@ -418,8 +402,6 @@ describe "String#each_grapheme" do
it_iterates_graphemes "\u1100\u0308\uAC00", ["\u1100\u0308", '\uAC00'] # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
it_iterates_graphemes "\u1100\uAC01", ["\u1100\uAC01"] # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [6.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
it_iterates_graphemes "\u1100\u0308\uAC01", ["\u1100\u0308", '\uAC01'] # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
- it_iterates_graphemes "\u1100\u0900", ["\u1100\u0900"] # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3]
- it_iterates_graphemes "\u1100\u0308\u0900", ["\u1100\u0308\u0900"] # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3]
it_iterates_graphemes "\u1100\u0903", ["\u1100\u0903"] # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
it_iterates_graphemes "\u1100\u0308\u0903", ["\u1100\u0308\u0903"] # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
it_iterates_graphemes "\u1100\u0904", ['\u1100', '\u0904'] # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3]
@@ -432,8 +414,8 @@ describe "String#each_grapheme" do
it_iterates_graphemes "\u1100\u0308\u231A", ["\u1100\u0308", '\u231A'] # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
it_iterates_graphemes "\u1100\u0300", ["\u1100\u0300"] # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
it_iterates_graphemes "\u1100\u0308\u0300", ["\u1100\u0308\u0300"] # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
- it_iterates_graphemes "\u1100\u093C", ["\u1100\u093C"] # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
- it_iterates_graphemes "\u1100\u0308\u093C", ["\u1100\u0308\u093C"] # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+ it_iterates_graphemes "\u1100\u0900", ["\u1100\u0900"] # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+ it_iterates_graphemes "\u1100\u0308\u0900", ["\u1100\u0308\u0900"] # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
it_iterates_graphemes "\u1100\u094D", ["\u1100\u094D"] # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
it_iterates_graphemes "\u1100\u0308\u094D", ["\u1100\u0308\u094D"] # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
it_iterates_graphemes "\u1100\u200D", ["\u1100\u200D"] # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
@@ -448,8 +430,8 @@ describe "String#each_grapheme" do
it_iterates_graphemes "\u1160\u0308\n", ["\u1160\u0308", '\n'] # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3]
it_iterates_graphemes "\u1160\u0001", ['\u1160', '\u0001'] # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [5.0] (Control) ÷ [0.3]
it_iterates_graphemes "\u1160\u0308\u0001", ["\u1160\u0308", '\u0001'] # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3]
- it_iterates_graphemes "\u1160\u034F", ["\u1160\u034F"] # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3]
- it_iterates_graphemes "\u1160\u0308\u034F", ["\u1160\u0308\u034F"] # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3]
+ it_iterates_graphemes "\u1160\u200C", ["\u1160\u200C"] # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
+ it_iterates_graphemes "\u1160\u0308\u200C", ["\u1160\u0308\u200C"] # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
it_iterates_graphemes "\u1160\u{1F1E6}", ['\u1160', '\u{1F1E6}'] # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
it_iterates_graphemes "\u1160\u0308\u{1F1E6}", ["\u1160\u0308", '\u{1F1E6}'] # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
it_iterates_graphemes "\u1160\u0600", ['\u1160', '\u0600'] # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
@@ -466,8 +448,6 @@ describe "String#each_grapheme" do
it_iterates_graphemes "\u1160\u0308\uAC00", ["\u1160\u0308", '\uAC00'] # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
it_iterates_graphemes "\u1160\uAC01", ['\u1160', '\uAC01'] # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
it_iterates_graphemes "\u1160\u0308\uAC01", ["\u1160\u0308", '\uAC01'] # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
- it_iterates_graphemes "\u1160\u0900", ["\u1160\u0900"] # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3]
- it_iterates_graphemes "\u1160\u0308\u0900", ["\u1160\u0308\u0900"] # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3]
it_iterates_graphemes "\u1160\u0903", ["\u1160\u0903"] # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
it_iterates_graphemes "\u1160\u0308\u0903", ["\u1160\u0308\u0903"] # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
it_iterates_graphemes "\u1160\u0904", ['\u1160', '\u0904'] # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3]
@@ -480,8 +460,8 @@ describe "String#each_grapheme" do
it_iterates_graphemes "\u1160\u0308\u231A", ["\u1160\u0308", '\u231A'] # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
it_iterates_graphemes "\u1160\u0300", ["\u1160\u0300"] # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
it_iterates_graphemes "\u1160\u0308\u0300", ["\u1160\u0308\u0300"] # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
- it_iterates_graphemes "\u1160\u093C", ["\u1160\u093C"] # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
- it_iterates_graphemes "\u1160\u0308\u093C", ["\u1160\u0308\u093C"] # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+ it_iterates_graphemes "\u1160\u0900", ["\u1160\u0900"] # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+ it_iterates_graphemes "\u1160\u0308\u0900", ["\u1160\u0308\u0900"] # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
it_iterates_graphemes "\u1160\u094D", ["\u1160\u094D"] # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
it_iterates_graphemes "\u1160\u0308\u094D", ["\u1160\u0308\u094D"] # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
it_iterates_graphemes "\u1160\u200D", ["\u1160\u200D"] # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
@@ -496,8 +476,8 @@ describe "String#each_grapheme" do
it_iterates_graphemes "\u11A8\u0308\n", ["\u11A8\u0308", '\n'] # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3]
it_iterates_graphemes "\u11A8\u0001", ['\u11A8', '\u0001'] # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [5.0] (Control) ÷ [0.3]
it_iterates_graphemes "\u11A8\u0308\u0001", ["\u11A8\u0308", '\u0001'] # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3]
- it_iterates_graphemes "\u11A8\u034F", ["\u11A8\u034F"] # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3]
- it_iterates_graphemes "\u11A8\u0308\u034F", ["\u11A8\u0308\u034F"] # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3]
+ it_iterates_graphemes "\u11A8\u200C", ["\u11A8\u200C"] # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
+ it_iterates_graphemes "\u11A8\u0308\u200C", ["\u11A8\u0308\u200C"] # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
it_iterates_graphemes "\u11A8\u{1F1E6}", ['\u11A8', '\u{1F1E6}'] # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
it_iterates_graphemes "\u11A8\u0308\u{1F1E6}", ["\u11A8\u0308", '\u{1F1E6}'] # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
it_iterates_graphemes "\u11A8\u0600", ['\u11A8', '\u0600'] # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
@@ -514,8 +494,6 @@ describe "String#each_grapheme" do
it_iterates_graphemes "\u11A8\u0308\uAC00", ["\u11A8\u0308", '\uAC00'] # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
it_iterates_graphemes "\u11A8\uAC01", ['\u11A8', '\uAC01'] # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
it_iterates_graphemes "\u11A8\u0308\uAC01", ["\u11A8\u0308", '\uAC01'] # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
- it_iterates_graphemes "\u11A8\u0900", ["\u11A8\u0900"] # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3]
- it_iterates_graphemes "\u11A8\u0308\u0900", ["\u11A8\u0308\u0900"] # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3]
it_iterates_graphemes "\u11A8\u0903", ["\u11A8\u0903"] # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
it_iterates_graphemes "\u11A8\u0308\u0903", ["\u11A8\u0308\u0903"] # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
it_iterates_graphemes "\u11A8\u0904", ['\u11A8', '\u0904'] # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3]
@@ -528,8 +506,8 @@ describe "String#each_grapheme" do
it_iterates_graphemes "\u11A8\u0308\u231A", ["\u11A8\u0308", '\u231A'] # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
it_iterates_graphemes "\u11A8\u0300", ["\u11A8\u0300"] # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
it_iterates_graphemes "\u11A8\u0308\u0300", ["\u11A8\u0308\u0300"] # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
- it_iterates_graphemes "\u11A8\u093C", ["\u11A8\u093C"] # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
- it_iterates_graphemes "\u11A8\u0308\u093C", ["\u11A8\u0308\u093C"] # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+ it_iterates_graphemes "\u11A8\u0900", ["\u11A8\u0900"] # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+ it_iterates_graphemes "\u11A8\u0308\u0900", ["\u11A8\u0308\u0900"] # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
it_iterates_graphemes "\u11A8\u094D", ["\u11A8\u094D"] # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
it_iterates_graphemes "\u11A8\u0308\u094D", ["\u11A8\u0308\u094D"] # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
it_iterates_graphemes "\u11A8\u200D", ["\u11A8\u200D"] # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
@@ -544,8 +522,8 @@ describe "String#each_grapheme" do
it_iterates_graphemes "\uAC00\u0308\n", ["\uAC00\u0308", '\n'] # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3]
it_iterates_graphemes "\uAC00\u0001", ['\uAC00', '\u0001'] # ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [5.0] (Control) ÷ [0.3]
it_iterates_graphemes "\uAC00\u0308\u0001", ["\uAC00\u0308", '\u0001'] # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3]
- it_iterates_graphemes "\uAC00\u034F", ["\uAC00\u034F"] # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3]
- it_iterates_graphemes "\uAC00\u0308\u034F", ["\uAC00\u0308\u034F"] # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3]
+ it_iterates_graphemes "\uAC00\u200C", ["\uAC00\u200C"] # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
+ it_iterates_graphemes "\uAC00\u0308\u200C", ["\uAC00\u0308\u200C"] # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
it_iterates_graphemes "\uAC00\u{1F1E6}", ['\uAC00', '\u{1F1E6}'] # ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
it_iterates_graphemes "\uAC00\u0308\u{1F1E6}", ["\uAC00\u0308", '\u{1F1E6}'] # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
it_iterates_graphemes "\uAC00\u0600", ['\uAC00', '\u0600'] # ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
@@ -562,8 +540,6 @@ describe "String#each_grapheme" do
it_iterates_graphemes "\uAC00\u0308\uAC00", ["\uAC00\u0308", '\uAC00'] # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
it_iterates_graphemes "\uAC00\uAC01", ['\uAC00', '\uAC01'] # ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
it_iterates_graphemes "\uAC00\u0308\uAC01", ["\uAC00\u0308", '\uAC01'] # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
- it_iterates_graphemes "\uAC00\u0900", ["\uAC00\u0900"] # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3]
- it_iterates_graphemes "\uAC00\u0308\u0900", ["\uAC00\u0308\u0900"] # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3]
it_iterates_graphemes "\uAC00\u0903", ["\uAC00\u0903"] # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
it_iterates_graphemes "\uAC00\u0308\u0903", ["\uAC00\u0308\u0903"] # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
it_iterates_graphemes "\uAC00\u0904", ['\uAC00', '\u0904'] # ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3]
@@ -576,8 +552,8 @@ describe "String#each_grapheme" do
it_iterates_graphemes "\uAC00\u0308\u231A", ["\uAC00\u0308", '\u231A'] # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
it_iterates_graphemes "\uAC00\u0300", ["\uAC00\u0300"] # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
it_iterates_graphemes "\uAC00\u0308\u0300", ["\uAC00\u0308\u0300"] # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
- it_iterates_graphemes "\uAC00\u093C", ["\uAC00\u093C"] # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
- it_iterates_graphemes "\uAC00\u0308\u093C", ["\uAC00\u0308\u093C"] # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+ it_iterates_graphemes "\uAC00\u0900", ["\uAC00\u0900"] # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+ it_iterates_graphemes "\uAC00\u0308\u0900", ["\uAC00\u0308\u0900"] # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
it_iterates_graphemes "\uAC00\u094D", ["\uAC00\u094D"] # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
it_iterates_graphemes "\uAC00\u0308\u094D", ["\uAC00\u0308\u094D"] # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
it_iterates_graphemes "\uAC00\u200D", ["\uAC00\u200D"] # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
@@ -592,8 +568,8 @@ describe "String#each_grapheme" do
it_iterates_graphemes "\uAC01\u0308\n", ["\uAC01\u0308", '\n'] # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3]
it_iterates_graphemes "\uAC01\u0001", ['\uAC01', '\u0001'] # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [5.0] (Control) ÷ [0.3]
it_iterates_graphemes "\uAC01\u0308\u0001", ["\uAC01\u0308", '\u0001'] # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3]
- it_iterates_graphemes "\uAC01\u034F", ["\uAC01\u034F"] # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3]
- it_iterates_graphemes "\uAC01\u0308\u034F", ["\uAC01\u0308\u034F"] # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3]
+ it_iterates_graphemes "\uAC01\u200C", ["\uAC01\u200C"] # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
+ it_iterates_graphemes "\uAC01\u0308\u200C", ["\uAC01\u0308\u200C"] # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
it_iterates_graphemes "\uAC01\u{1F1E6}", ['\uAC01', '\u{1F1E6}'] # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
it_iterates_graphemes "\uAC01\u0308\u{1F1E6}", ["\uAC01\u0308", '\u{1F1E6}'] # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
it_iterates_graphemes "\uAC01\u0600", ['\uAC01', '\u0600'] # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
@@ -610,8 +586,6 @@ describe "String#each_grapheme" do
it_iterates_graphemes "\uAC01\u0308\uAC00", ["\uAC01\u0308", '\uAC00'] # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
it_iterates_graphemes "\uAC01\uAC01", ['\uAC01', '\uAC01'] # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
it_iterates_graphemes "\uAC01\u0308\uAC01", ["\uAC01\u0308", '\uAC01'] # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
- it_iterates_graphemes "\uAC01\u0900", ["\uAC01\u0900"] # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3]
- it_iterates_graphemes "\uAC01\u0308\u0900", ["\uAC01\u0308\u0900"] # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3]
it_iterates_graphemes "\uAC01\u0903", ["\uAC01\u0903"] # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
it_iterates_graphemes "\uAC01\u0308\u0903", ["\uAC01\u0308\u0903"] # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
it_iterates_graphemes "\uAC01\u0904", ['\uAC01', '\u0904'] # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3]
@@ -624,62 +598,14 @@ describe "String#each_grapheme" do
it_iterates_graphemes "\uAC01\u0308\u231A", ["\uAC01\u0308", '\u231A'] # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
it_iterates_graphemes "\uAC01\u0300", ["\uAC01\u0300"] # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
it_iterates_graphemes "\uAC01\u0308\u0300", ["\uAC01\u0308\u0300"] # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
- it_iterates_graphemes "\uAC01\u093C", ["\uAC01\u093C"] # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
- it_iterates_graphemes "\uAC01\u0308\u093C", ["\uAC01\u0308\u093C"] # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+ it_iterates_graphemes "\uAC01\u0900", ["\uAC01\u0900"] # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+ it_iterates_graphemes "\uAC01\u0308\u0900", ["\uAC01\u0308\u0900"] # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
it_iterates_graphemes "\uAC01\u094D", ["\uAC01\u094D"] # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
it_iterates_graphemes "\uAC01\u0308\u094D", ["\uAC01\u0308\u094D"] # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
it_iterates_graphemes "\uAC01\u200D", ["\uAC01\u200D"] # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
it_iterates_graphemes "\uAC01\u0308\u200D", ["\uAC01\u0308\u200D"] # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
it_iterates_graphemes "\uAC01\u0378", ['\uAC01', '\u0378'] # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [999.0] (Other) ÷ [0.3]
it_iterates_graphemes "\uAC01\u0308\u0378", ["\uAC01\u0308", '\u0378'] # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] (Other) ÷ [0.3]
- it_iterates_graphemes "\u0900 ", ['\u0900', ' '] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [999.0] SPACE (Other) ÷ [0.3]
- it_iterates_graphemes "\u0900\u0308 ", ["\u0900\u0308", ' '] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3]
- it_iterates_graphemes "\u0900\r", ['\u0900', '\r'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [5.0] (CR) ÷ [0.3]
- it_iterates_graphemes "\u0900\u0308\r", ["\u0900\u0308", '\r'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CR) ÷ [0.3]
- it_iterates_graphemes "\u0900\n", ['\u0900', '\n'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [5.0] (LF) ÷ [0.3]
- it_iterates_graphemes "\u0900\u0308\n", ["\u0900\u0308", '\n'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3]
- it_iterates_graphemes "\u0900\u0001", ['\u0900', '\u0001'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [5.0] (Control) ÷ [0.3]
- it_iterates_graphemes "\u0900\u0308\u0001", ["\u0900\u0308", '\u0001'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3]
- it_iterates_graphemes "\u0900\u034F", ["\u0900\u034F"] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3]
- it_iterates_graphemes "\u0900\u0308\u034F", ["\u0900\u0308\u034F"] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3]
- it_iterates_graphemes "\u0900\u{1F1E6}", ['\u0900', '\u{1F1E6}'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
- it_iterates_graphemes "\u0900\u0308\u{1F1E6}", ["\u0900\u0308", '\u{1F1E6}'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
- it_iterates_graphemes "\u0900\u0600", ['\u0900', '\u0600'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
- it_iterates_graphemes "\u0900\u0308\u0600", ["\u0900\u0308", '\u0600'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
- it_iterates_graphemes "\u0900\u0A03", ["\u0900\u0A03"] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3]
- it_iterates_graphemes "\u0900\u0308\u0A03", ["\u0900\u0308\u0A03"] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3]
- it_iterates_graphemes "\u0900\u1100", ['\u0900', '\u1100'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
- it_iterates_graphemes "\u0900\u0308\u1100", ["\u0900\u0308", '\u1100'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
- it_iterates_graphemes "\u0900\u1160", ['\u0900', '\u1160'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
- it_iterates_graphemes "\u0900\u0308\u1160", ["\u0900\u0308", '\u1160'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
- it_iterates_graphemes "\u0900\u11A8", ['\u0900', '\u11A8'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
- it_iterates_graphemes "\u0900\u0308\u11A8", ["\u0900\u0308", '\u11A8'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
- it_iterates_graphemes "\u0900\uAC00", ['\u0900', '\uAC00'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
- it_iterates_graphemes "\u0900\u0308\uAC00", ["\u0900\u0308", '\uAC00'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
- it_iterates_graphemes "\u0900\uAC01", ['\u0900', '\uAC01'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
- it_iterates_graphemes "\u0900\u0308\uAC01", ["\u0900\u0308", '\uAC01'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
- it_iterates_graphemes "\u0900\u0900", ["\u0900\u0900"] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3]
- it_iterates_graphemes "\u0900\u0308\u0900", ["\u0900\u0308\u0900"] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3]
- it_iterates_graphemes "\u0900\u0903", ["\u0900\u0903"] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
- it_iterates_graphemes "\u0900\u0308\u0903", ["\u0900\u0308\u0903"] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
- it_iterates_graphemes "\u0900\u0904", ['\u0900', '\u0904'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3]
- it_iterates_graphemes "\u0900\u0308\u0904", ["\u0900\u0308", '\u0904'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3]
- it_iterates_graphemes "\u0900\u0D4E", ['\u0900', '\u0D4E'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3]
- it_iterates_graphemes "\u0900\u0308\u0D4E", ["\u0900\u0308", '\u0D4E'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3]
- it_iterates_graphemes "\u0900\u0915", ['\u0900', '\u0915'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3]
- it_iterates_graphemes "\u0900\u0308\u0915", ["\u0900\u0308", '\u0915'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3]
- it_iterates_graphemes "\u0900\u231A", ['\u0900', '\u231A'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
- it_iterates_graphemes "\u0900\u0308\u231A", ["\u0900\u0308", '\u231A'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
- it_iterates_graphemes "\u0900\u0300", ["\u0900\u0300"] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
- it_iterates_graphemes "\u0900\u0308\u0300", ["\u0900\u0308\u0300"] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
- it_iterates_graphemes "\u0900\u093C", ["\u0900\u093C"] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
- it_iterates_graphemes "\u0900\u0308\u093C", ["\u0900\u0308\u093C"] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
- it_iterates_graphemes "\u0900\u094D", ["\u0900\u094D"] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
- it_iterates_graphemes "\u0900\u0308\u094D", ["\u0900\u0308\u094D"] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
- it_iterates_graphemes "\u0900\u200D", ["\u0900\u200D"] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
- it_iterates_graphemes "\u0900\u0308\u200D", ["\u0900\u0308\u200D"] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
- it_iterates_graphemes "\u0900\u0378", ['\u0900', '\u0378'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [999.0] (Other) ÷ [0.3]
- it_iterates_graphemes "\u0900\u0308\u0378", ["\u0900\u0308", '\u0378'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] (Other) ÷ [0.3]
it_iterates_graphemes "\u0903 ", ['\u0903', ' '] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [999.0] SPACE (Other) ÷ [0.3]
it_iterates_graphemes "\u0903\u0308 ", ["\u0903\u0308", ' '] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3]
it_iterates_graphemes "\u0903\r", ['\u0903', '\r'] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [5.0] (CR) ÷ [0.3]
@@ -688,8 +614,8 @@ describe "String#each_grapheme" do
it_iterates_graphemes "\u0903\u0308\n", ["\u0903\u0308", '\n'] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3]
it_iterates_graphemes "\u0903\u0001", ['\u0903', '\u0001'] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [5.0] (Control) ÷ [0.3]
it_iterates_graphemes "\u0903\u0308\u0001", ["\u0903\u0308", '\u0001'] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3]
- it_iterates_graphemes "\u0903\u034F", ["\u0903\u034F"] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3]
- it_iterates_graphemes "\u0903\u0308\u034F", ["\u0903\u0308\u034F"] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3]
+ it_iterates_graphemes "\u0903\u200C", ["\u0903\u200C"] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
+ it_iterates_graphemes "\u0903\u0308\u200C", ["\u0903\u0308\u200C"] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
it_iterates_graphemes "\u0903\u{1F1E6}", ['\u0903', '\u{1F1E6}'] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
it_iterates_graphemes "\u0903\u0308\u{1F1E6}", ["\u0903\u0308", '\u{1F1E6}'] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
it_iterates_graphemes "\u0903\u0600", ['\u0903', '\u0600'] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
@@ -706,8 +632,6 @@ describe "String#each_grapheme" do
it_iterates_graphemes "\u0903\u0308\uAC00", ["\u0903\u0308", '\uAC00'] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
it_iterates_graphemes "\u0903\uAC01", ['\u0903', '\uAC01'] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
it_iterates_graphemes "\u0903\u0308\uAC01", ["\u0903\u0308", '\uAC01'] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
- it_iterates_graphemes "\u0903\u0900", ["\u0903\u0900"] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3]
- it_iterates_graphemes "\u0903\u0308\u0900", ["\u0903\u0308\u0900"] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3]
it_iterates_graphemes "\u0903\u0903", ["\u0903\u0903"] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
it_iterates_graphemes "\u0903\u0308\u0903", ["\u0903\u0308\u0903"] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
it_iterates_graphemes "\u0903\u0904", ['\u0903', '\u0904'] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3]
@@ -720,8 +644,8 @@ describe "String#each_grapheme" do
it_iterates_graphemes "\u0903\u0308\u231A", ["\u0903\u0308", '\u231A'] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
it_iterates_graphemes "\u0903\u0300", ["\u0903\u0300"] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
it_iterates_graphemes "\u0903\u0308\u0300", ["\u0903\u0308\u0300"] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
- it_iterates_graphemes "\u0903\u093C", ["\u0903\u093C"] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
- it_iterates_graphemes "\u0903\u0308\u093C", ["\u0903\u0308\u093C"] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+ it_iterates_graphemes "\u0903\u0900", ["\u0903\u0900"] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+ it_iterates_graphemes "\u0903\u0308\u0900", ["\u0903\u0308\u0900"] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
it_iterates_graphemes "\u0903\u094D", ["\u0903\u094D"] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
it_iterates_graphemes "\u0903\u0308\u094D", ["\u0903\u0308\u094D"] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
it_iterates_graphemes "\u0903\u200D", ["\u0903\u200D"] # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
@@ -736,8 +660,8 @@ describe "String#each_grapheme" do
it_iterates_graphemes "\u0904\u0308\n", ["\u0904\u0308", '\n'] # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3]
it_iterates_graphemes "\u0904\u0001", ['\u0904', '\u0001'] # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [5.0] (Control) ÷ [0.3]
it_iterates_graphemes "\u0904\u0308\u0001", ["\u0904\u0308", '\u0001'] # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3]
- it_iterates_graphemes "\u0904\u034F", ["\u0904\u034F"] # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3]
- it_iterates_graphemes "\u0904\u0308\u034F", ["\u0904\u0308\u034F"] # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3]
+ it_iterates_graphemes "\u0904\u200C", ["\u0904\u200C"] # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
+ it_iterates_graphemes "\u0904\u0308\u200C", ["\u0904\u0308\u200C"] # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
it_iterates_graphemes "\u0904\u{1F1E6}", ['\u0904', '\u{1F1E6}'] # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
it_iterates_graphemes "\u0904\u0308\u{1F1E6}", ["\u0904\u0308", '\u{1F1E6}'] # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
it_iterates_graphemes "\u0904\u0600", ['\u0904', '\u0600'] # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
@@ -754,8 +678,6 @@ describe "String#each_grapheme" do
it_iterates_graphemes "\u0904\u0308\uAC00", ["\u0904\u0308", '\uAC00'] # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
it_iterates_graphemes "\u0904\uAC01", ['\u0904', '\uAC01'] # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
it_iterates_graphemes "\u0904\u0308\uAC01", ["\u0904\u0308", '\uAC01'] # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
- it_iterates_graphemes "\u0904\u0900", ["\u0904\u0900"] # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3]
- it_iterates_graphemes "\u0904\u0308\u0900", ["\u0904\u0308\u0900"] # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3]
it_iterates_graphemes "\u0904\u0903", ["\u0904\u0903"] # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
it_iterates_graphemes "\u0904\u0308\u0903", ["\u0904\u0308\u0903"] # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
it_iterates_graphemes "\u0904\u0904", ['\u0904', '\u0904'] # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3]
@@ -768,8 +690,8 @@ describe "String#each_grapheme" do
it_iterates_graphemes "\u0904\u0308\u231A", ["\u0904\u0308", '\u231A'] # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
it_iterates_graphemes "\u0904\u0300", ["\u0904\u0300"] # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
it_iterates_graphemes "\u0904\u0308\u0300", ["\u0904\u0308\u0300"] # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
- it_iterates_graphemes "\u0904\u093C", ["\u0904\u093C"] # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
- it_iterates_graphemes "\u0904\u0308\u093C", ["\u0904\u0308\u093C"] # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+ it_iterates_graphemes "\u0904\u0900", ["\u0904\u0900"] # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+ it_iterates_graphemes "\u0904\u0308\u0900", ["\u0904\u0308\u0900"] # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
it_iterates_graphemes "\u0904\u094D", ["\u0904\u094D"] # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
it_iterates_graphemes "\u0904\u0308\u094D", ["\u0904\u0308\u094D"] # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
it_iterates_graphemes "\u0904\u200D", ["\u0904\u200D"] # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
@@ -784,8 +706,8 @@ describe "String#each_grapheme" do
it_iterates_graphemes "\u0D4E\u0308\n", ["\u0D4E\u0308", '\n'] # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3]
it_iterates_graphemes "\u0D4E\u0001", ['\u0D4E', '\u0001'] # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [5.0] (Control) ÷ [0.3]
it_iterates_graphemes "\u0D4E\u0308\u0001", ["\u0D4E\u0308", '\u0001'] # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3]
- it_iterates_graphemes "\u0D4E\u034F", ["\u0D4E\u034F"] # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3]
- it_iterates_graphemes "\u0D4E\u0308\u034F", ["\u0D4E\u0308\u034F"] # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3]
+ it_iterates_graphemes "\u0D4E\u200C", ["\u0D4E\u200C"] # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
+ it_iterates_graphemes "\u0D4E\u0308\u200C", ["\u0D4E\u0308\u200C"] # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
it_iterates_graphemes "\u0D4E\u{1F1E6}", ["\u0D4E\u{1F1E6}"] # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
it_iterates_graphemes "\u0D4E\u0308\u{1F1E6}", ["\u0D4E\u0308", '\u{1F1E6}'] # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
it_iterates_graphemes "\u0D4E\u0600", ["\u0D4E\u0600"] # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.2] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
@@ -802,8 +724,6 @@ describe "String#each_grapheme" do
it_iterates_graphemes "\u0D4E\u0308\uAC00", ["\u0D4E\u0308", '\uAC00'] # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
it_iterates_graphemes "\u0D4E\uAC01", ["\u0D4E\uAC01"] # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.2] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
it_iterates_graphemes "\u0D4E\u0308\uAC01", ["\u0D4E\u0308", '\uAC01'] # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
- it_iterates_graphemes "\u0D4E\u0900", ["\u0D4E\u0900"] # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3]
- it_iterates_graphemes "\u0D4E\u0308\u0900", ["\u0D4E\u0308\u0900"] # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3]
it_iterates_graphemes "\u0D4E\u0903", ["\u0D4E\u0903"] # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
it_iterates_graphemes "\u0D4E\u0308\u0903", ["\u0D4E\u0308\u0903"] # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
it_iterates_graphemes "\u0D4E\u0904", ["\u0D4E\u0904"] # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3]
@@ -816,8 +736,8 @@ describe "String#each_grapheme" do
it_iterates_graphemes "\u0D4E\u0308\u231A", ["\u0D4E\u0308", '\u231A'] # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
it_iterates_graphemes "\u0D4E\u0300", ["\u0D4E\u0300"] # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
it_iterates_graphemes "\u0D4E\u0308\u0300", ["\u0D4E\u0308\u0300"] # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
- it_iterates_graphemes "\u0D4E\u093C", ["\u0D4E\u093C"] # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
- it_iterates_graphemes "\u0D4E\u0308\u093C", ["\u0D4E\u0308\u093C"] # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+ it_iterates_graphemes "\u0D4E\u0900", ["\u0D4E\u0900"] # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+ it_iterates_graphemes "\u0D4E\u0308\u0900", ["\u0D4E\u0308\u0900"] # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
it_iterates_graphemes "\u0D4E\u094D", ["\u0D4E\u094D"] # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
it_iterates_graphemes "\u0D4E\u0308\u094D", ["\u0D4E\u0308\u094D"] # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
it_iterates_graphemes "\u0D4E\u200D", ["\u0D4E\u200D"] # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
@@ -832,8 +752,8 @@ describe "String#each_grapheme" do
it_iterates_graphemes "\u0915\u0308\n", ["\u0915\u0308", '\n'] # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3]
it_iterates_graphemes "\u0915\u0001", ['\u0915', '\u0001'] # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [5.0] (Control) ÷ [0.3]
it_iterates_graphemes "\u0915\u0308\u0001", ["\u0915\u0308", '\u0001'] # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3]
- it_iterates_graphemes "\u0915\u034F", ["\u0915\u034F"] # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3]
- it_iterates_graphemes "\u0915\u0308\u034F", ["\u0915\u0308\u034F"] # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3]
+ it_iterates_graphemes "\u0915\u200C", ["\u0915\u200C"] # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
+ it_iterates_graphemes "\u0915\u0308\u200C", ["\u0915\u0308\u200C"] # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
it_iterates_graphemes "\u0915\u{1F1E6}", ['\u0915', '\u{1F1E6}'] # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
it_iterates_graphemes "\u0915\u0308\u{1F1E6}", ["\u0915\u0308", '\u{1F1E6}'] # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
it_iterates_graphemes "\u0915\u0600", ['\u0915', '\u0600'] # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
@@ -850,8 +770,6 @@ describe "String#each_grapheme" do
it_iterates_graphemes "\u0915\u0308\uAC00", ["\u0915\u0308", '\uAC00'] # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
it_iterates_graphemes "\u0915\uAC01", ['\u0915', '\uAC01'] # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
it_iterates_graphemes "\u0915\u0308\uAC01", ["\u0915\u0308", '\uAC01'] # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
- it_iterates_graphemes "\u0915\u0900", ["\u0915\u0900"] # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3]
- it_iterates_graphemes "\u0915\u0308\u0900", ["\u0915\u0308\u0900"] # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3]
it_iterates_graphemes "\u0915\u0903", ["\u0915\u0903"] # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
it_iterates_graphemes "\u0915\u0308\u0903", ["\u0915\u0308\u0903"] # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
it_iterates_graphemes "\u0915\u0904", ['\u0915', '\u0904'] # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3]
@@ -864,8 +782,8 @@ describe "String#each_grapheme" do
it_iterates_graphemes "\u0915\u0308\u231A", ["\u0915\u0308", '\u231A'] # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
it_iterates_graphemes "\u0915\u0300", ["\u0915\u0300"] # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
it_iterates_graphemes "\u0915\u0308\u0300", ["\u0915\u0308\u0300"] # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
- it_iterates_graphemes "\u0915\u093C", ["\u0915\u093C"] # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
- it_iterates_graphemes "\u0915\u0308\u093C", ["\u0915\u0308\u093C"] # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+ it_iterates_graphemes "\u0915\u0900", ["\u0915\u0900"] # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+ it_iterates_graphemes "\u0915\u0308\u0900", ["\u0915\u0308\u0900"] # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
it_iterates_graphemes "\u0915\u094D", ["\u0915\u094D"] # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
it_iterates_graphemes "\u0915\u0308\u094D", ["\u0915\u0308\u094D"] # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
it_iterates_graphemes "\u0915\u200D", ["\u0915\u200D"] # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
@@ -880,8 +798,8 @@ describe "String#each_grapheme" do
it_iterates_graphemes "\u231A\u0308\n", ["\u231A\u0308", '\n'] # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3]
it_iterates_graphemes "\u231A\u0001", ['\u231A', '\u0001'] # ÷ [0.2] WATCH (ExtPict) ÷ [5.0] (Control) ÷ [0.3]
it_iterates_graphemes "\u231A\u0308\u0001", ["\u231A\u0308", '\u0001'] # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3]
- it_iterates_graphemes "\u231A\u034F", ["\u231A\u034F"] # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3]
- it_iterates_graphemes "\u231A\u0308\u034F", ["\u231A\u0308\u034F"] # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3]
+ it_iterates_graphemes "\u231A\u200C", ["\u231A\u200C"] # ÷ [0.2] WATCH (ExtPict) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
+ it_iterates_graphemes "\u231A\u0308\u200C", ["\u231A\u0308\u200C"] # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
it_iterates_graphemes "\u231A\u{1F1E6}", ['\u231A', '\u{1F1E6}'] # ÷ [0.2] WATCH (ExtPict) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
it_iterates_graphemes "\u231A\u0308\u{1F1E6}", ["\u231A\u0308", '\u{1F1E6}'] # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
it_iterates_graphemes "\u231A\u0600", ['\u231A', '\u0600'] # ÷ [0.2] WATCH (ExtPict) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
@@ -898,8 +816,6 @@ describe "String#each_grapheme" do
it_iterates_graphemes "\u231A\u0308\uAC00", ["\u231A\u0308", '\uAC00'] # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
it_iterates_graphemes "\u231A\uAC01", ['\u231A', '\uAC01'] # ÷ [0.2] WATCH (ExtPict) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
it_iterates_graphemes "\u231A\u0308\uAC01", ["\u231A\u0308", '\uAC01'] # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
- it_iterates_graphemes "\u231A\u0900", ["\u231A\u0900"] # ÷ [0.2] WATCH (ExtPict) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3]
- it_iterates_graphemes "\u231A\u0308\u0900", ["\u231A\u0308\u0900"] # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3]
it_iterates_graphemes "\u231A\u0903", ["\u231A\u0903"] # ÷ [0.2] WATCH (ExtPict) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
it_iterates_graphemes "\u231A\u0308\u0903", ["\u231A\u0308\u0903"] # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
it_iterates_graphemes "\u231A\u0904", ['\u231A', '\u0904'] # ÷ [0.2] WATCH (ExtPict) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3]
@@ -912,8 +828,8 @@ describe "String#each_grapheme" do
it_iterates_graphemes "\u231A\u0308\u231A", ["\u231A\u0308", '\u231A'] # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
it_iterates_graphemes "\u231A\u0300", ["\u231A\u0300"] # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
it_iterates_graphemes "\u231A\u0308\u0300", ["\u231A\u0308\u0300"] # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
- it_iterates_graphemes "\u231A\u093C", ["\u231A\u093C"] # ÷ [0.2] WATCH (ExtPict) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
- it_iterates_graphemes "\u231A\u0308\u093C", ["\u231A\u0308\u093C"] # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+ it_iterates_graphemes "\u231A\u0900", ["\u231A\u0900"] # ÷ [0.2] WATCH (ExtPict) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+ it_iterates_graphemes "\u231A\u0308\u0900", ["\u231A\u0308\u0900"] # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
it_iterates_graphemes "\u231A\u094D", ["\u231A\u094D"] # ÷ [0.2] WATCH (ExtPict) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
it_iterates_graphemes "\u231A\u0308\u094D", ["\u231A\u0308\u094D"] # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
it_iterates_graphemes "\u231A\u200D", ["\u231A\u200D"] # ÷ [0.2] WATCH (ExtPict) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
@@ -928,8 +844,8 @@ describe "String#each_grapheme" do
it_iterates_graphemes "\u0300\u0308\n", ["\u0300\u0308", '\n'] # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3]
it_iterates_graphemes "\u0300\u0001", ['\u0300', '\u0001'] # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3]
it_iterates_graphemes "\u0300\u0308\u0001", ["\u0300\u0308", '\u0001'] # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3]
- it_iterates_graphemes "\u0300\u034F", ["\u0300\u034F"] # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3]
- it_iterates_graphemes "\u0300\u0308\u034F", ["\u0300\u0308\u034F"] # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3]
+ it_iterates_graphemes "\u0300\u200C", ["\u0300\u200C"] # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
+ it_iterates_graphemes "\u0300\u0308\u200C", ["\u0300\u0308\u200C"] # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
it_iterates_graphemes "\u0300\u{1F1E6}", ['\u0300', '\u{1F1E6}'] # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
it_iterates_graphemes "\u0300\u0308\u{1F1E6}", ["\u0300\u0308", '\u{1F1E6}'] # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
it_iterates_graphemes "\u0300\u0600", ['\u0300', '\u0600'] # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
@@ -946,8 +862,6 @@ describe "String#each_grapheme" do
it_iterates_graphemes "\u0300\u0308\uAC00", ["\u0300\u0308", '\uAC00'] # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
it_iterates_graphemes "\u0300\uAC01", ['\u0300', '\uAC01'] # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
it_iterates_graphemes "\u0300\u0308\uAC01", ["\u0300\u0308", '\uAC01'] # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
- it_iterates_graphemes "\u0300\u0900", ["\u0300\u0900"] # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3]
- it_iterates_graphemes "\u0300\u0308\u0900", ["\u0300\u0308\u0900"] # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3]
it_iterates_graphemes "\u0300\u0903", ["\u0300\u0903"] # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
it_iterates_graphemes "\u0300\u0308\u0903", ["\u0300\u0308\u0903"] # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
it_iterates_graphemes "\u0300\u0904", ['\u0300', '\u0904'] # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3]
@@ -960,62 +874,60 @@ describe "String#each_grapheme" do
it_iterates_graphemes "\u0300\u0308\u231A", ["\u0300\u0308", '\u231A'] # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
it_iterates_graphemes "\u0300\u0300", ["\u0300\u0300"] # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
it_iterates_graphemes "\u0300\u0308\u0300", ["\u0300\u0308\u0300"] # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
- it_iterates_graphemes "\u0300\u093C", ["\u0300\u093C"] # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
- it_iterates_graphemes "\u0300\u0308\u093C", ["\u0300\u0308\u093C"] # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+ it_iterates_graphemes "\u0300\u0900", ["\u0300\u0900"] # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+ it_iterates_graphemes "\u0300\u0308\u0900", ["\u0300\u0308\u0900"] # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
it_iterates_graphemes "\u0300\u094D", ["\u0300\u094D"] # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
it_iterates_graphemes "\u0300\u0308\u094D", ["\u0300\u0308\u094D"] # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
it_iterates_graphemes "\u0300\u200D", ["\u0300\u200D"] # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
it_iterates_graphemes "\u0300\u0308\u200D", ["\u0300\u0308\u200D"] # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
it_iterates_graphemes "\u0300\u0378", ['\u0300', '\u0378'] # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [999.0] (Other) ÷ [0.3]
it_iterates_graphemes "\u0300\u0308\u0378", ["\u0300\u0308", '\u0378'] # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] (Other) ÷ [0.3]
- it_iterates_graphemes "\u093C ", ['\u093C', ' '] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3]
- it_iterates_graphemes "\u093C\u0308 ", ["\u093C\u0308", ' '] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3]
- it_iterates_graphemes "\u093C\r", ['\u093C', '\r'] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [5.0] (CR) ÷ [0.3]
- it_iterates_graphemes "\u093C\u0308\r", ["\u093C\u0308", '\r'] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CR) ÷ [0.3]
- it_iterates_graphemes "\u093C\n", ['\u093C', '\n'] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3]
- it_iterates_graphemes "\u093C\u0308\n", ["\u093C\u0308", '\n'] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3]
- it_iterates_graphemes "\u093C\u0001", ['\u093C', '\u0001'] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3]
- it_iterates_graphemes "\u093C\u0308\u0001", ["\u093C\u0308", '\u0001'] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3]
- it_iterates_graphemes "\u093C\u034F", ["\u093C\u034F"] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3]
- it_iterates_graphemes "\u093C\u0308\u034F", ["\u093C\u0308\u034F"] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3]
- it_iterates_graphemes "\u093C\u{1F1E6}", ['\u093C', '\u{1F1E6}'] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
- it_iterates_graphemes "\u093C\u0308\u{1F1E6}", ["\u093C\u0308", '\u{1F1E6}'] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
- it_iterates_graphemes "\u093C\u0600", ['\u093C', '\u0600'] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
- it_iterates_graphemes "\u093C\u0308\u0600", ["\u093C\u0308", '\u0600'] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
- it_iterates_graphemes "\u093C\u0A03", ["\u093C\u0A03"] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3]
- it_iterates_graphemes "\u093C\u0308\u0A03", ["\u093C\u0308\u0A03"] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3]
- it_iterates_graphemes "\u093C\u1100", ['\u093C', '\u1100'] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
- it_iterates_graphemes "\u093C\u0308\u1100", ["\u093C\u0308", '\u1100'] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
- it_iterates_graphemes "\u093C\u1160", ['\u093C', '\u1160'] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
- it_iterates_graphemes "\u093C\u0308\u1160", ["\u093C\u0308", '\u1160'] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
- it_iterates_graphemes "\u093C\u11A8", ['\u093C', '\u11A8'] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
- it_iterates_graphemes "\u093C\u0308\u11A8", ["\u093C\u0308", '\u11A8'] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
- it_iterates_graphemes "\u093C\uAC00", ['\u093C', '\uAC00'] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
- it_iterates_graphemes "\u093C\u0308\uAC00", ["\u093C\u0308", '\uAC00'] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
- it_iterates_graphemes "\u093C\uAC01", ['\u093C', '\uAC01'] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
- it_iterates_graphemes "\u093C\u0308\uAC01", ["\u093C\u0308", '\uAC01'] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
- it_iterates_graphemes "\u093C\u0900", ["\u093C\u0900"] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3]
- it_iterates_graphemes "\u093C\u0308\u0900", ["\u093C\u0308\u0900"] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3]
- it_iterates_graphemes "\u093C\u0903", ["\u093C\u0903"] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
- it_iterates_graphemes "\u093C\u0308\u0903", ["\u093C\u0308\u0903"] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
- it_iterates_graphemes "\u093C\u0904", ['\u093C', '\u0904'] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3]
- it_iterates_graphemes "\u093C\u0308\u0904", ["\u093C\u0308", '\u0904'] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3]
- it_iterates_graphemes "\u093C\u0D4E", ['\u093C', '\u0D4E'] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3]
- it_iterates_graphemes "\u093C\u0308\u0D4E", ["\u093C\u0308", '\u0D4E'] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3]
- it_iterates_graphemes "\u093C\u0915", ['\u093C', '\u0915'] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3]
- it_iterates_graphemes "\u093C\u0308\u0915", ["\u093C\u0308", '\u0915'] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3]
- it_iterates_graphemes "\u093C\u231A", ['\u093C', '\u231A'] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
- it_iterates_graphemes "\u093C\u0308\u231A", ["\u093C\u0308", '\u231A'] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
- it_iterates_graphemes "\u093C\u0300", ["\u093C\u0300"] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
- it_iterates_graphemes "\u093C\u0308\u0300", ["\u093C\u0308\u0300"] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
- it_iterates_graphemes "\u093C\u093C", ["\u093C\u093C"] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
- it_iterates_graphemes "\u093C\u0308\u093C", ["\u093C\u0308\u093C"] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
- it_iterates_graphemes "\u093C\u094D", ["\u093C\u094D"] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
- it_iterates_graphemes "\u093C\u0308\u094D", ["\u093C\u0308\u094D"] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
- it_iterates_graphemes "\u093C\u200D", ["\u093C\u200D"] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
- it_iterates_graphemes "\u093C\u0308\u200D", ["\u093C\u0308\u200D"] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
- it_iterates_graphemes "\u093C\u0378", ['\u093C', '\u0378'] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [999.0] (Other) ÷ [0.3]
- it_iterates_graphemes "\u093C\u0308\u0378", ["\u093C\u0308", '\u0378'] # ÷ [0.2] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] (Other) ÷ [0.3]
+ it_iterates_graphemes "\u0900 ", ['\u0900', ' '] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3]
+ it_iterates_graphemes "\u0900\u0308 ", ["\u0900\u0308", ' '] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3]
+ it_iterates_graphemes "\u0900\r", ['\u0900', '\r'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [5.0] (CR) ÷ [0.3]
+ it_iterates_graphemes "\u0900\u0308\r", ["\u0900\u0308", '\r'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CR) ÷ [0.3]
+ it_iterates_graphemes "\u0900\n", ['\u0900', '\n'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3]
+ it_iterates_graphemes "\u0900\u0308\n", ["\u0900\u0308", '\n'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3]
+ it_iterates_graphemes "\u0900\u0001", ['\u0900', '\u0001'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3]
+ it_iterates_graphemes "\u0900\u0308\u0001", ["\u0900\u0308", '\u0001'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3]
+ it_iterates_graphemes "\u0900\u200C", ["\u0900\u200C"] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
+ it_iterates_graphemes "\u0900\u0308\u200C", ["\u0900\u0308\u200C"] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
+ it_iterates_graphemes "\u0900\u{1F1E6}", ['\u0900', '\u{1F1E6}'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
+ it_iterates_graphemes "\u0900\u0308\u{1F1E6}", ["\u0900\u0308", '\u{1F1E6}'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
+ it_iterates_graphemes "\u0900\u0600", ['\u0900', '\u0600'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
+ it_iterates_graphemes "\u0900\u0308\u0600", ["\u0900\u0308", '\u0600'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
+ it_iterates_graphemes "\u0900\u0A03", ["\u0900\u0A03"] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3]
+ it_iterates_graphemes "\u0900\u0308\u0A03", ["\u0900\u0308\u0A03"] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3]
+ it_iterates_graphemes "\u0900\u1100", ['\u0900', '\u1100'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
+ it_iterates_graphemes "\u0900\u0308\u1100", ["\u0900\u0308", '\u1100'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3]
+ it_iterates_graphemes "\u0900\u1160", ['\u0900', '\u1160'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
+ it_iterates_graphemes "\u0900\u0308\u1160", ["\u0900\u0308", '\u1160'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3]
+ it_iterates_graphemes "\u0900\u11A8", ['\u0900', '\u11A8'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
+ it_iterates_graphemes "\u0900\u0308\u11A8", ["\u0900\u0308", '\u11A8'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3]
+ it_iterates_graphemes "\u0900\uAC00", ['\u0900', '\uAC00'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
+ it_iterates_graphemes "\u0900\u0308\uAC00", ["\u0900\u0308", '\uAC00'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
+ it_iterates_graphemes "\u0900\uAC01", ['\u0900', '\uAC01'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
+ it_iterates_graphemes "\u0900\u0308\uAC01", ["\u0900\u0308", '\uAC01'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
+ it_iterates_graphemes "\u0900\u0903", ["\u0900\u0903"] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
+ it_iterates_graphemes "\u0900\u0308\u0903", ["\u0900\u0308\u0903"] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
+ it_iterates_graphemes "\u0900\u0904", ['\u0900', '\u0904'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3]
+ it_iterates_graphemes "\u0900\u0308\u0904", ["\u0900\u0308", '\u0904'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3]
+ it_iterates_graphemes "\u0900\u0D4E", ['\u0900', '\u0D4E'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3]
+ it_iterates_graphemes "\u0900\u0308\u0D4E", ["\u0900\u0308", '\u0D4E'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3]
+ it_iterates_graphemes "\u0900\u0915", ['\u0900', '\u0915'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3]
+ it_iterates_graphemes "\u0900\u0308\u0915", ["\u0900\u0308", '\u0915'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3]
+ it_iterates_graphemes "\u0900\u231A", ['\u0900', '\u231A'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
+ it_iterates_graphemes "\u0900\u0308\u231A", ["\u0900\u0308", '\u231A'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
+ it_iterates_graphemes "\u0900\u0300", ["\u0900\u0300"] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+ it_iterates_graphemes "\u0900\u0308\u0300", ["\u0900\u0308\u0300"] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
+ it_iterates_graphemes "\u0900\u0900", ["\u0900\u0900"] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+ it_iterates_graphemes "\u0900\u0308\u0900", ["\u0900\u0308\u0900"] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+ it_iterates_graphemes "\u0900\u094D", ["\u0900\u094D"] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
+ it_iterates_graphemes "\u0900\u0308\u094D", ["\u0900\u0308\u094D"] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
+ it_iterates_graphemes "\u0900\u200D", ["\u0900\u200D"] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
+ it_iterates_graphemes "\u0900\u0308\u200D", ["\u0900\u0308\u200D"] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
+ it_iterates_graphemes "\u0900\u0378", ['\u0900', '\u0378'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [999.0] (Other) ÷ [0.3]
+ it_iterates_graphemes "\u0900\u0308\u0378", ["\u0900\u0308", '\u0378'] # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] (Other) ÷ [0.3]
it_iterates_graphemes "\u094D ", ['\u094D', ' '] # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3]
it_iterates_graphemes "\u094D\u0308 ", ["\u094D\u0308", ' '] # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3]
it_iterates_graphemes "\u094D\r", ['\u094D', '\r'] # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [5.0] (CR) ÷ [0.3]
@@ -1024,8 +936,8 @@ describe "String#each_grapheme" do
it_iterates_graphemes "\u094D\u0308\n", ["\u094D\u0308", '\n'] # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3]
it_iterates_graphemes "\u094D\u0001", ['\u094D', '\u0001'] # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3]
it_iterates_graphemes "\u094D\u0308\u0001", ["\u094D\u0308", '\u0001'] # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3]
- it_iterates_graphemes "\u094D\u034F", ["\u094D\u034F"] # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3]
- it_iterates_graphemes "\u094D\u0308\u034F", ["\u094D\u0308\u034F"] # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3]
+ it_iterates_graphemes "\u094D\u200C", ["\u094D\u200C"] # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
+ it_iterates_graphemes "\u094D\u0308\u200C", ["\u094D\u0308\u200C"] # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
it_iterates_graphemes "\u094D\u{1F1E6}", ['\u094D', '\u{1F1E6}'] # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
it_iterates_graphemes "\u094D\u0308\u{1F1E6}", ["\u094D\u0308", '\u{1F1E6}'] # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
it_iterates_graphemes "\u094D\u0600", ['\u094D', '\u0600'] # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
@@ -1042,8 +954,6 @@ describe "String#each_grapheme" do
it_iterates_graphemes "\u094D\u0308\uAC00", ["\u094D\u0308", '\uAC00'] # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
it_iterates_graphemes "\u094D\uAC01", ['\u094D', '\uAC01'] # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
it_iterates_graphemes "\u094D\u0308\uAC01", ["\u094D\u0308", '\uAC01'] # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
- it_iterates_graphemes "\u094D\u0900", ["\u094D\u0900"] # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3]
- it_iterates_graphemes "\u094D\u0308\u0900", ["\u094D\u0308\u0900"] # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3]
it_iterates_graphemes "\u094D\u0903", ["\u094D\u0903"] # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
it_iterates_graphemes "\u094D\u0308\u0903", ["\u094D\u0308\u0903"] # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
it_iterates_graphemes "\u094D\u0904", ['\u094D', '\u0904'] # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3]
@@ -1056,8 +966,8 @@ describe "String#each_grapheme" do
it_iterates_graphemes "\u094D\u0308\u231A", ["\u094D\u0308", '\u231A'] # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
it_iterates_graphemes "\u094D\u0300", ["\u094D\u0300"] # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
it_iterates_graphemes "\u094D\u0308\u0300", ["\u094D\u0308\u0300"] # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
- it_iterates_graphemes "\u094D\u093C", ["\u094D\u093C"] # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
- it_iterates_graphemes "\u094D\u0308\u093C", ["\u094D\u0308\u093C"] # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+ it_iterates_graphemes "\u094D\u0900", ["\u094D\u0900"] # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+ it_iterates_graphemes "\u094D\u0308\u0900", ["\u094D\u0308\u0900"] # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
it_iterates_graphemes "\u094D\u094D", ["\u094D\u094D"] # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
it_iterates_graphemes "\u094D\u0308\u094D", ["\u094D\u0308\u094D"] # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
it_iterates_graphemes "\u094D\u200D", ["\u094D\u200D"] # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
@@ -1072,8 +982,8 @@ describe "String#each_grapheme" do
it_iterates_graphemes "\u200D\u0308\n", ["\u200D\u0308", '\n'] # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3]
it_iterates_graphemes "\u200D\u0001", ['\u200D', '\u0001'] # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3]
it_iterates_graphemes "\u200D\u0308\u0001", ["\u200D\u0308", '\u0001'] # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3]
- it_iterates_graphemes "\u200D\u034F", ["\u200D\u034F"] # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3]
- it_iterates_graphemes "\u200D\u0308\u034F", ["\u200D\u0308\u034F"] # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3]
+ it_iterates_graphemes "\u200D\u200C", ["\u200D\u200C"] # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
+ it_iterates_graphemes "\u200D\u0308\u200C", ["\u200D\u0308\u200C"] # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
it_iterates_graphemes "\u200D\u{1F1E6}", ['\u200D', '\u{1F1E6}'] # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
it_iterates_graphemes "\u200D\u0308\u{1F1E6}", ["\u200D\u0308", '\u{1F1E6}'] # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
it_iterates_graphemes "\u200D\u0600", ['\u200D', '\u0600'] # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
@@ -1090,8 +1000,6 @@ describe "String#each_grapheme" do
it_iterates_graphemes "\u200D\u0308\uAC00", ["\u200D\u0308", '\uAC00'] # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
it_iterates_graphemes "\u200D\uAC01", ['\u200D', '\uAC01'] # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
it_iterates_graphemes "\u200D\u0308\uAC01", ["\u200D\u0308", '\uAC01'] # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
- it_iterates_graphemes "\u200D\u0900", ["\u200D\u0900"] # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3]
- it_iterates_graphemes "\u200D\u0308\u0900", ["\u200D\u0308\u0900"] # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3]
it_iterates_graphemes "\u200D\u0903", ["\u200D\u0903"] # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
it_iterates_graphemes "\u200D\u0308\u0903", ["\u200D\u0308\u0903"] # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
it_iterates_graphemes "\u200D\u0904", ['\u200D', '\u0904'] # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3]
@@ -1104,8 +1012,8 @@ describe "String#each_grapheme" do
it_iterates_graphemes "\u200D\u0308\u231A", ["\u200D\u0308", '\u231A'] # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
it_iterates_graphemes "\u200D\u0300", ["\u200D\u0300"] # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
it_iterates_graphemes "\u200D\u0308\u0300", ["\u200D\u0308\u0300"] # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
- it_iterates_graphemes "\u200D\u093C", ["\u200D\u093C"] # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
- it_iterates_graphemes "\u200D\u0308\u093C", ["\u200D\u0308\u093C"] # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+ it_iterates_graphemes "\u200D\u0900", ["\u200D\u0900"] # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+ it_iterates_graphemes "\u200D\u0308\u0900", ["\u200D\u0308\u0900"] # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
it_iterates_graphemes "\u200D\u094D", ["\u200D\u094D"] # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
it_iterates_graphemes "\u200D\u0308\u094D", ["\u200D\u0308\u094D"] # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
it_iterates_graphemes "\u200D\u200D", ["\u200D\u200D"] # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
@@ -1120,8 +1028,8 @@ describe "String#each_grapheme" do
it_iterates_graphemes "\u0378\u0308\n", ["\u0378\u0308", '\n'] # ÷ [0.2] (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3]
it_iterates_graphemes "\u0378\u0001", ['\u0378', '\u0001'] # ÷ [0.2] (Other) ÷ [5.0] (Control) ÷ [0.3]
it_iterates_graphemes "\u0378\u0308\u0001", ["\u0378\u0308", '\u0001'] # ÷ [0.2] (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3]
- it_iterates_graphemes "\u0378\u034F", ["\u0378\u034F"] # ÷ [0.2] (Other) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3]
- it_iterates_graphemes "\u0378\u0308\u034F", ["\u0378\u0308\u034F"] # ÷ [0.2] (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAPHEME JOINER (Extend) ÷ [0.3]
+ it_iterates_graphemes "\u0378\u200C", ["\u0378\u200C"] # ÷ [0.2] (Other) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
+ it_iterates_graphemes "\u0378\u0308\u200C", ["\u0378\u0308\u200C"] # ÷ [0.2] (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3]
it_iterates_graphemes "\u0378\u{1F1E6}", ['\u0378', '\u{1F1E6}'] # ÷ [0.2] (Other) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
it_iterates_graphemes "\u0378\u0308\u{1F1E6}", ["\u0378\u0308", '\u{1F1E6}'] # ÷ [0.2] (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3]
it_iterates_graphemes "\u0378\u0600", ['\u0378', '\u0600'] # ÷ [0.2] (Other) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3]
@@ -1138,8 +1046,6 @@ describe "String#each_grapheme" do
it_iterates_graphemes "\u0378\u0308\uAC00", ["\u0378\u0308", '\uAC00'] # ÷ [0.2] (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3]
it_iterates_graphemes "\u0378\uAC01", ['\u0378', '\uAC01'] # ÷ [0.2] (Other) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
it_iterates_graphemes "\u0378\u0308\uAC01", ["\u0378\u0308", '\uAC01'] # ÷ [0.2] (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3]
- it_iterates_graphemes "\u0378\u0900", ["\u0378\u0900"] # ÷ [0.2] (Other) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3]
- it_iterates_graphemes "\u0378\u0308\u0900", ["\u0378\u0308\u0900"] # ÷ [0.2] (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts) ÷ [0.3]
it_iterates_graphemes "\u0378\u0903", ["\u0378\u0903"] # ÷ [0.2] (Other) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
it_iterates_graphemes "\u0378\u0308\u0903", ["\u0378\u0308\u0903"] # ÷ [0.2] (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3]
it_iterates_graphemes "\u0378\u0904", ['\u0378', '\u0904'] # ÷ [0.2] (Other) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3]
@@ -1152,8 +1058,8 @@ describe "String#each_grapheme" do
it_iterates_graphemes "\u0378\u0308\u231A", ["\u0378\u0308", '\u231A'] # ÷ [0.2] (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3]
it_iterates_graphemes "\u0378\u0300", ["\u0378\u0300"] # ÷ [0.2] (Other) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
it_iterates_graphemes "\u0378\u0308\u0300", ["\u0378\u0308\u0300"] # ÷ [0.2] (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3]
- it_iterates_graphemes "\u0378\u093C", ["\u0378\u093C"] # ÷ [0.2] (Other) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
- it_iterates_graphemes "\u0378\u0308\u093C", ["\u0378\u0308\u093C"] # ÷ [0.2] (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+ it_iterates_graphemes "\u0378\u0900", ["\u0378\u0900"] # ÷ [0.2] (Other) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
+ it_iterates_graphemes "\u0378\u0308\u0900", ["\u0378\u0308\u0900"] # ÷ [0.2] (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3]
it_iterates_graphemes "\u0378\u094D", ["\u0378\u094D"] # ÷ [0.2] (Other) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
it_iterates_graphemes "\u0378\u0308\u094D", ["\u0378\u0308\u094D"] # ÷ [0.2] (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3]
it_iterates_graphemes "\u0378\u200D", ["\u0378\u200D"] # ÷ [0.2] (Other) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3]
@@ -1176,10 +1082,10 @@ describe "String#each_grapheme" do
it_iterates_graphemes "a\u0308b", ["a\u0308", 'b'] # ÷ [0.2] LATIN SMALL LETTER A (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] LATIN SMALL LETTER B (Other) ÷ [0.3]
it_iterates_graphemes "a\u0903b", ["a\u0903", 'b'] # ÷ [0.2] LATIN SMALL LETTER A (Other) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [999.0] LATIN SMALL LETTER B (Other) ÷ [0.3]
it_iterates_graphemes "a\u0600b", ['a', "\u0600b"] # ÷ [0.2] LATIN SMALL LETTER A (Other) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) × [9.2] LATIN SMALL LETTER B (Other) ÷ [0.3]
- it_iterates_graphemes "\u{1F476}\u{1F3FF}\u{1F476}", ["\u{1F476}\u{1F3FF}", '\u{1F476}'] # ÷ [0.2] BABY (ExtPict) × [9.0] EMOJI MODIFIER FITZPATRICK TYPE-6 (Extend) ÷ [999.0] BABY (ExtPict) ÷ [0.3]
- it_iterates_graphemes "a\u{1F3FF}\u{1F476}", ["a\u{1F3FF}", '\u{1F476}'] # ÷ [0.2] LATIN SMALL LETTER A (Other) × [9.0] EMOJI MODIFIER FITZPATRICK TYPE-6 (Extend) ÷ [999.0] BABY (ExtPict) ÷ [0.3]
- it_iterates_graphemes "a\u{1F3FF}\u{1F476}\u200D\u{1F6D1}", ["a\u{1F3FF}", "\u{1F476}\u200D\u{1F6D1}"] # ÷ [0.2] LATIN SMALL LETTER A (Other) × [9.0] EMOJI MODIFIER FITZPATRICK TYPE-6 (Extend) ÷ [999.0] BABY (ExtPict) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [11.0] OCTAGONAL SIGN (ExtPict) ÷ [0.3]
- it_iterates_graphemes "\u{1F476}\u{1F3FF}\u0308\u200D\u{1F476}\u{1F3FF}", ["\u{1F476}\u{1F3FF}\u0308\u200D\u{1F476}\u{1F3FF}"] # ÷ [0.2] BABY (ExtPict) × [9.0] EMOJI MODIFIER FITZPATRICK TYPE-6 (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [11.0] BABY (ExtPict) × [9.0] EMOJI MODIFIER FITZPATRICK TYPE-6 (Extend) ÷ [0.3]
+ it_iterates_graphemes "\u{1F476}\u{1F3FF}\u{1F476}", ["\u{1F476}\u{1F3FF}", '\u{1F476}'] # ÷ [0.2] BABY (ExtPict) × [9.0] EMOJI MODIFIER FITZPATRICK TYPE-6 (Extend_ExtCccZwj) ÷ [999.0] BABY (ExtPict) ÷ [0.3]
+ it_iterates_graphemes "a\u{1F3FF}\u{1F476}", ["a\u{1F3FF}", '\u{1F476}'] # ÷ [0.2] LATIN SMALL LETTER A (Other) × [9.0] EMOJI MODIFIER FITZPATRICK TYPE-6 (Extend_ExtCccZwj) ÷ [999.0] BABY (ExtPict) ÷ [0.3]
+ it_iterates_graphemes "a\u{1F3FF}\u{1F476}\u200D\u{1F6D1}", ["a\u{1F3FF}", "\u{1F476}\u200D\u{1F6D1}"] # ÷ [0.2] LATIN SMALL LETTER A (Other) × [9.0] EMOJI MODIFIER FITZPATRICK TYPE-6 (Extend_ExtCccZwj) ÷ [999.0] BABY (ExtPict) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [11.0] OCTAGONAL SIGN (ExtPict) ÷ [0.3]
+ it_iterates_graphemes "\u{1F476}\u{1F3FF}\u0308\u200D\u{1F476}\u{1F3FF}", ["\u{1F476}\u{1F3FF}\u0308\u200D\u{1F476}\u{1F3FF}"] # ÷ [0.2] BABY (ExtPict) × [9.0] EMOJI MODIFIER FITZPATRICK TYPE-6 (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [11.0] BABY (ExtPict) × [9.0] EMOJI MODIFIER FITZPATRICK TYPE-6 (Extend_ExtCccZwj) ÷ [0.3]
it_iterates_graphemes "\u{1F6D1}\u200D\u{1F6D1}", ["\u{1F6D1}\u200D\u{1F6D1}"] # ÷ [0.2] OCTAGONAL SIGN (ExtPict) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [11.0] OCTAGONAL SIGN (ExtPict) ÷ [0.3]
it_iterates_graphemes "a\u200D\u{1F6D1}", ["a\u200D", '\u{1F6D1}'] # ÷ [0.2] LATIN SMALL LETTER A (Other) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [999.0] OCTAGONAL SIGN (ExtPict) ÷ [0.3]
it_iterates_graphemes "\u2701\u200D\u2701", ["\u2701\u200D\u2701"] # ÷ [0.2] UPPER BLADE SCISSORS (Other) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [11.0] UPPER BLADE SCISSORS (Other) ÷ [0.3]
diff --git a/spec/std/string_spec.cr b/spec/std/string_spec.cr
index 00310bfcbc47..6d7487ded0e2 100644
--- a/spec/std/string_spec.cr
+++ b/spec/std/string_spec.cr
@@ -321,6 +321,7 @@ describe "String" do
it { expect_raises(ArgumentError) { "1__234".to_i } }
it { expect_raises(ArgumentError) { "1_234".to_i } }
it { expect_raises(ArgumentError) { " 1234 ".to_i(whitespace: false) } }
+ it { expect_raises(ArgumentError) { "".to_i(whitespace: false) } }
it { expect_raises(ArgumentError) { "0x123".to_i } }
it { expect_raises(ArgumentError) { "0b123".to_i } }
it { expect_raises(ArgumentError) { "000b123".to_i(prefix: true) } }
@@ -515,6 +516,7 @@ describe "String" do
"nan".to_f?(whitespace: false).try(&.nan?).should be_true
" nan".to_f?(whitespace: false).should be_nil
"nan ".to_f?(whitespace: false).should be_nil
+ expect_raises(ArgumentError) { "".to_f(whitespace: false) }
"nani".to_f?(strict: true).should be_nil
" INF".to_f?.should eq Float64::INFINITY
"INF".to_f?.should eq Float64::INFINITY
@@ -724,6 +726,10 @@ describe "String" do
it { assert_prints " spáçes before".titleize, " Spáçes Before" }
it { assert_prints "testá-se múitô".titleize, "Testá-se Múitô" }
it { assert_prints "iO iO".titleize(Unicode::CaseOptions::Turkic), "İo İo" }
+ it { assert_prints "foo_Bar".titleize, "Foo_bar" }
+ it { assert_prints "foo_bar".titleize, "Foo_bar" }
+ it { assert_prints "testá_se múitô".titleize(underscore_to_space: true), "Testá Se Múitô" }
+ it { assert_prints "foo_bar".titleize(underscore_to_space: true), "Foo Bar" }
it "handles multi-character mappings correctly (#13533)" do
assert_prints "fflİ İffl dz DZ".titleize, "Ffli̇ İffl Dz Dz"
@@ -735,6 +741,12 @@ describe "String" do
String.build { |io| "\xB5!\xE0\xC1\xB5?".titleize(io) }.should eq("\xB5!\xE0\xC1\xB5?".scrub)
String.build { |io| "a\xA0b".titleize(io) }.should eq("A\xA0b".scrub)
end
+
+ describe "with IO" do
+ it { String.build { |io| "foo_Bar".titleize io }.should eq "Foo_bar" }
+ it { String.build { |io| "foo_bar".titleize io }.should eq "Foo_bar" }
+ it { String.build { |io| "foo_bar".titleize(io, underscore_to_space: true) }.should eq "Foo Bar" }
+ end
end
describe "chomp" do
@@ -945,6 +957,8 @@ describe "String" do
it { "日本語".index('本').should eq(1) }
it { "bar".index('あ').should be_nil }
it { "あいう_えお".index('_').should eq(3) }
+ it { "xyz\xFFxyz".index('\u{FFFD}').should eq(3) }
+ it { "日\xFF語".index('\u{FFFD}').should eq(1) }
describe "with offset" do
it { "foobarbaz".index('a', 5).should eq(7) }
@@ -952,6 +966,10 @@ describe "String" do
it { "foo".index('g', 1).should be_nil }
it { "foo".index('g', -20).should be_nil }
it { "日本語日本語".index('本', 2).should eq(4) }
+ it { "xyz\xFFxyz".index('\u{FFFD}', 2).should eq(3) }
+ it { "xyz\xFFxyz".index('\u{FFFD}', 4).should be_nil }
+ it { "日本\xFF語".index('\u{FFFD}', 2).should eq(2) }
+ it { "日本\xFF語".index('\u{FFFD}', 3).should be_nil }
# Check offset type
it { "foobarbaz".index('a', 5_i64).should eq(7) }
@@ -1094,6 +1112,8 @@ describe "String" do
it { "foobar".rindex('g').should be_nil }
it { "日本語日本語".rindex('本').should eq(4) }
it { "あいう_えお".rindex('_').should eq(3) }
+ it { "xyz\xFFxyz".rindex('\u{FFFD}').should eq(3) }
+ it { "日\xFF語".rindex('\u{FFFD}').should eq(1) }
describe "with offset" do
it { "bbbb".rindex('b', 2).should eq(2) }
@@ -1106,6 +1126,10 @@ describe "String" do
it { "faobar".rindex('a', 3).should eq(1) }
it { "faobarbaz".rindex('a', -3).should eq(4) }
it { "日本語日本語".rindex('本', 3).should eq(1) }
+ it { "xyz\xFFxyz".rindex('\u{FFFD}', 4).should eq(3) }
+ it { "xyz\xFFxyz".rindex('\u{FFFD}', 2).should be_nil }
+ it { "日本\xFF語".rindex('\u{FFFD}', 2).should eq(2) }
+ it { "日本\xFF語".rindex('\u{FFFD}', 1).should be_nil }
# Check offset type
it { "bbbb".rindex('b', 2_i64).should eq(2) }
@@ -2806,7 +2830,7 @@ describe "String" do
bytes.to_a.should eq([72, 0, 101, 0, 108, 0, 108, 0, 111, 0])
end
- {% unless flag?(:musl) || flag?(:solaris) || flag?(:freebsd) || flag?(:dragonfly) %}
+ {% unless flag?(:musl) || flag?(:solaris) || flag?(:freebsd) || flag?(:dragonfly) || flag?(:netbsd) %}
it "flushes the shift state (#11992)" do
"\u{00CA}".encode("BIG5-HKSCS").should eq(Bytes[0x88, 0x66])
"\u{00CA}\u{0304}".encode("BIG5-HKSCS").should eq(Bytes[0x88, 0x62])
@@ -2815,7 +2839,7 @@ describe "String" do
# FreeBSD iconv encoder expects ISO/IEC 10646 compatibility code points,
# see https://www.ccli.gov.hk/doc/e_hkscs_2008.pdf for details.
- {% if flag?(:freebsd) || flag?(:dragonfly) %}
+ {% if flag?(:freebsd) || flag?(:dragonfly) || flag?(:netbsd) %}
it "flushes the shift state (#11992)" do
"\u{F329}".encode("BIG5-HKSCS").should eq(Bytes[0x88, 0x66])
"\u{F325}".encode("BIG5-HKSCS").should eq(Bytes[0x88, 0x62])
@@ -2859,7 +2883,7 @@ describe "String" do
String.new(bytes, "UTF-16LE").should eq("Hello")
end
- {% unless flag?(:solaris) || flag?(:freebsd) || flag?(:dragonfly) %}
+ {% unless flag?(:solaris) || flag?(:freebsd) || flag?(:dragonfly) || flag?(:netbsd) %}
it "decodes with shift state" do
String.new(Bytes[0x88, 0x66], "BIG5-HKSCS").should eq("\u{00CA}")
String.new(Bytes[0x88, 0x62], "BIG5-HKSCS").should eq("\u{00CA}\u{0304}")
@@ -2868,7 +2892,7 @@ describe "String" do
# FreeBSD iconv decoder returns ISO/IEC 10646-1:2000 code points,
# see https://www.ccli.gov.hk/doc/e_hkscs_2008.pdf for details.
- {% if flag?(:freebsd) || flag?(:dragonfly) %}
+ {% if flag?(:freebsd) || flag?(:dragonfly) || flag?(:netbsd) %}
it "decodes with shift state" do
String.new(Bytes[0x88, 0x66], "BIG5-HKSCS").should eq("\u{00CA}")
String.new(Bytes[0x88, 0x62], "BIG5-HKSCS").should eq("\u{F325}")
diff --git a/spec/std/system/group_spec.cr b/spec/std/system/group_spec.cr
index 5c55611e4d28..ba511d03a05c 100644
--- a/spec/std/system/group_spec.cr
+++ b/spec/std/system/group_spec.cr
@@ -1,10 +1,14 @@
-{% skip_file if flag?(:win32) %}
-
require "spec"
require "system/group"
-GROUP_NAME = {{ `id -gn`.stringify.chomp }}
-GROUP_ID = {{ `id -g`.stringify.chomp }}
+{% if flag?(:win32) %}
+ GROUP_NAME = "BUILTIN\\Administrators"
+ GROUP_ID = "S-1-5-32-544"
+{% else %}
+ GROUP_NAME = {{ `id -gn`.stringify.chomp }}
+ GROUP_ID = {{ `id -g`.stringify.chomp }}
+{% end %}
+
INVALID_GROUP_NAME = "this_group_does_not_exist"
INVALID_GROUP_ID = {% if flag?(:android) %}"8888"{% else %}"1234567"{% end %}
diff --git a/spec/std/system/user_spec.cr b/spec/std/system/user_spec.cr
index 9fea934bc227..f0cb977d014d 100644
--- a/spec/std/system/user_spec.cr
+++ b/spec/std/system/user_spec.cr
@@ -1,20 +1,36 @@
-{% skip_file if flag?(:win32) %}
-
require "spec"
require "system/user"
-USER_NAME = {{ `id -un`.stringify.chomp }}
-USER_ID = {{ `id -u`.stringify.chomp }}
+{% if flag?(:win32) %}
+ {% name, id = `whoami /USER /FO TABLE /NH`.stringify.chomp.split(" ") %}
+ USER_NAME = {{ name }}
+ USER_ID = {{ id }}
+{% else %}
+ USER_NAME = {{ `id -un`.stringify.chomp }}
+ USER_ID = {{ `id -u`.stringify.chomp }}
+{% end %}
+
INVALID_USER_NAME = "this_user_does_not_exist"
INVALID_USER_ID = {% if flag?(:android) %}"8888"{% else %}"1234567"{% end %}
+def normalized_username(username)
+ # on Windows, domain names are case-insensitive, so we unify the letter case
+ # from sources like `whoami`, `hostname`, or Win32 APIs
+ {% if flag?(:win32) %}
+ domain, _, user = username.partition('\\')
+ "#{domain.upcase}\\#{user}"
+ {% else %}
+ username
+ {% end %}
+end
+
describe System::User do
describe ".find_by(*, name)" do
it "returns a user by name" do
user = System::User.find_by(name: USER_NAME)
user.should be_a(System::User)
- user.username.should eq(USER_NAME)
+ normalized_username(user.username).should eq(normalized_username(USER_NAME))
user.id.should eq(USER_ID)
end
@@ -31,7 +47,7 @@ describe System::User do
user.should be_a(System::User)
user.id.should eq(USER_ID)
- user.username.should eq(USER_NAME)
+ normalized_username(user.username).should eq(normalized_username(USER_NAME))
end
it "raises on nonexistent user id" do
@@ -46,7 +62,7 @@ describe System::User do
user = System::User.find_by?(name: USER_NAME).not_nil!
user.should be_a(System::User)
- user.username.should eq(USER_NAME)
+ normalized_username(user.username).should eq(normalized_username(USER_NAME))
user.id.should eq(USER_ID)
end
@@ -62,7 +78,7 @@ describe System::User do
user.should be_a(System::User)
user.id.should eq(USER_ID)
- user.username.should eq(USER_NAME)
+ normalized_username(user.username).should eq(normalized_username(USER_NAME))
end
it "returns nil on nonexistent user id" do
@@ -73,7 +89,8 @@ describe System::User do
describe "#username" do
it "is the same as the source name" do
- System::User.find_by(name: USER_NAME).username.should eq(USER_NAME)
+ user = System::User.find_by(name: USER_NAME)
+ normalized_username(user.username).should eq(normalized_username(USER_NAME))
end
end
@@ -109,7 +126,8 @@ describe System::User do
describe "#to_s" do
it "returns a string representation" do
- System::User.find_by(name: USER_NAME).to_s.should eq("#{USER_NAME} (#{USER_ID})")
+ user = System::User.find_by(name: USER_NAME)
+ user.to_s.should eq("#{user.username} (#{user.id})")
end
end
end
diff --git a/spec/std/thread/condition_variable_spec.cr b/spec/std/thread/condition_variable_spec.cr
index ff9c44204bb6..1bf78f797357 100644
--- a/spec/std/thread/condition_variable_spec.cr
+++ b/spec/std/thread/condition_variable_spec.cr
@@ -1,11 +1,3 @@
-{% if flag?(:musl) %}
- # FIXME: These thread specs occasionally fail on musl/alpine based ci, so
- # they're disabled for now to reduce noise.
- # See https://github.com/crystal-lang/crystal/issues/8738
- pending Thread::ConditionVariable
- {% skip_file %}
-{% end %}
-
require "../spec_helper"
# interpreter doesn't support threads yet (#14287)
diff --git a/spec/std/thread/mutex_spec.cr b/spec/std/thread/mutex_spec.cr
index ff298f318329..99f3c5d385c3 100644
--- a/spec/std/thread/mutex_spec.cr
+++ b/spec/std/thread/mutex_spec.cr
@@ -1,11 +1,3 @@
-{% if flag?(:musl) %}
- # FIXME: These thread specs occasionally fail on musl/alpine based ci, so
- # they're disabled for now to reduce noise.
- # See https://github.com/crystal-lang/crystal/issues/8738
- pending Thread::Mutex
- {% skip_file %}
-{% end %}
-
require "../spec_helper"
# interpreter doesn't support threads yet (#14287)
diff --git a/spec/std/thread_spec.cr b/spec/std/thread_spec.cr
index feb55454b621..5a43c7e429d1 100644
--- a/spec/std/thread_spec.cr
+++ b/spec/std/thread_spec.cr
@@ -1,13 +1,5 @@
require "./spec_helper"
-{% if flag?(:musl) %}
- # FIXME: These thread specs occasionally fail on musl/alpine based ci, so
- # they're disabled for now to reduce noise.
- # See https://github.com/crystal-lang/crystal/issues/8738
- pending Thread
- {% skip_file %}
-{% end %}
-
# interpreter doesn't support threads yet (#14287)
pending_interpreted describe: Thread do
it "allows passing an argumentless fun to execute" do
diff --git a/spec/std/time/span_spec.cr b/spec/std/time/span_spec.cr
index f9c1dd83f04f..ec49e38651cc 100644
--- a/spec/std/time/span_spec.cr
+++ b/spec/std/time/span_spec.cr
@@ -360,7 +360,7 @@ describe Time::Span do
1.1.weeks.should eq(7.7.days)
end
- it "can substract big amount using microseconds" do
+ it "can subtract big amount using microseconds" do
jan_1_2k = Time.utc(2000, 1, 1)
past = Time.utc(5, 2, 3, 0, 0, 0)
delta = (past - jan_1_2k).total_microseconds.to_i64
@@ -368,7 +368,7 @@ describe Time::Span do
past2.should eq(past)
end
- it "can substract big amount using milliseconds" do
+ it "can subtract big amount using milliseconds" do
jan_1_2k = Time.utc(2000, 1, 1)
past = Time.utc(5, 2, 3, 0, 0, 0)
delta = (past - jan_1_2k).total_milliseconds.to_i64
diff --git a/spec/std/uri/json_spec.cr b/spec/std/uri/json_spec.cr
new file mode 100644
index 000000000000..a21f503958a5
--- /dev/null
+++ b/spec/std/uri/json_spec.cr
@@ -0,0 +1,14 @@
+require "spec"
+require "uri/json"
+
+describe "URI" do
+ describe "serializes" do
+ it "#to_json" do
+ URI.parse("https://example.com").to_json.should eq %q("https://example.com")
+ end
+
+ it "from_json_object_key?" do
+ URI.from_json_object_key?("https://example.com").should eq(URI.parse("https://example.com"))
+ end
+ end
+end
diff --git a/spec/std/uri/params/from_www_form_spec.cr b/spec/std/uri/params/from_www_form_spec.cr
new file mode 100644
index 000000000000..e0ab818c2e86
--- /dev/null
+++ b/spec/std/uri/params/from_www_form_spec.cr
@@ -0,0 +1,151 @@
+require "spec"
+require "uri/params/serializable"
+
+private enum Color
+ Red
+ Green
+ Blue
+end
+
+describe ".from_www_form" do
+ it Array do
+ Array(Int32).from_www_form(URI::Params.new({"values" => ["1", "2"]}), "values").should eq [1, 2]
+ Array(Int32).from_www_form(URI::Params.new({"values[]" => ["1", "2"]}), "values").should eq [1, 2]
+ Array(String).from_www_form(URI::Params.new({"values" => ["", ""]}), "values").should eq ["", ""]
+ end
+
+ describe Bool do
+ it "a truthy value" do
+ Bool.from_www_form("true").should be_true
+ Bool.from_www_form("on").should be_true
+ Bool.from_www_form("yes").should be_true
+ Bool.from_www_form("1").should be_true
+ end
+
+ it "a falsey value" do
+ Bool.from_www_form("false").should be_false
+ Bool.from_www_form("off").should be_false
+ Bool.from_www_form("no").should be_false
+ Bool.from_www_form("0").should be_false
+ end
+
+ it "any other value" do
+ Bool.from_www_form("foo").should be_nil
+ Bool.from_www_form("").should be_nil
+ end
+ end
+
+ describe String do
+ it "scalar string" do
+ String.from_www_form("John Doe").should eq "John Doe"
+ end
+
+ it "with key" do
+ String.from_www_form(URI::Params.new({"name" => ["John Doe"]}), "name").should eq "John Doe"
+ end
+
+ it "with missing key" do
+ String.from_www_form(URI::Params.new({"" => ["John Doe"]}), "name").should be_nil
+ end
+
+ it "with alternate casing" do
+ String.from_www_form(URI::Params.new({"Name" => ["John Doe"]}), "name").should be_nil
+ end
+
+ it "empty value" do
+ String.from_www_form(URI::Params.new({"name" => [""]}), "name").should eq ""
+ end
+ end
+
+ describe Enum do
+ it "valid value" do
+ Color.from_www_form("green").should eq Color::Green
+ end
+
+ it "invalid value" do
+ expect_raises ArgumentError do
+ Color.from_www_form ""
+ end
+ end
+ end
+
+ describe Time do
+ it "valid value" do
+ Time.from_www_form("2016-11-16T09:55:48-03:00").to_utc.should eq(Time.utc(2016, 11, 16, 12, 55, 48))
+ Time.from_www_form("2016-11-16T09:55:48-0300").to_utc.should eq(Time.utc(2016, 11, 16, 12, 55, 48))
+ Time.from_www_form("20161116T095548-03:00").to_utc.should eq(Time.utc(2016, 11, 16, 12, 55, 48))
+ end
+
+ it "invalid value" do
+ expect_raises Time::Format::Error do
+ Time.from_www_form ""
+ end
+ end
+ end
+
+ describe Nil do
+ it "valid values" do
+ Nil.from_www_form("").should be_nil
+ end
+
+ it "invalid value" do
+ expect_raises ArgumentError do
+ Nil.from_www_form "null"
+ end
+ end
+ end
+
+ describe Number do
+ describe Int do
+ it "valid numbers" do
+ Int64.from_www_form("123").should eq 123_i64
+ UInt8.from_www_form("7").should eq 7_u8
+ Int64.from_www_form("-12").should eq -12_i64
+ end
+
+ it "with whitespace" do
+ expect_raises ArgumentError do
+ Int32.from_www_form(" 123")
+ end
+ end
+
+ it "empty value" do
+ expect_raises ArgumentError do
+ Int16.from_www_form ""
+ end
+ end
+ end
+
+ describe Float do
+ it "valid numbers" do
+ Float32.from_www_form("123.0").should eq 123_f32
+ Float64.from_www_form("123.0").should eq 123_f64
+ end
+
+ it "with whitespace" do
+ expect_raises ArgumentError do
+ Float64.from_www_form(" 123.0")
+ end
+ end
+
+ it "empty value" do
+ expect_raises Exception do
+ Float64.from_www_form ""
+ end
+ end
+ end
+ end
+
+ describe Union do
+ it "valid" do
+ String?.from_www_form(URI::Params.parse("name=John Doe"), "name").should eq "John Doe"
+ String?.from_www_form(URI::Params.parse("name="), "name").should eq ""
+ end
+
+ it "invalid" do
+ expect_raises ArgumentError do
+ (Int32 | Float64).from_www_form(URI::Params.parse("name=John Doe"), "name")
+ end
+ end
+ end
+end
diff --git a/spec/std/uri/params/serializable_spec.cr b/spec/std/uri/params/serializable_spec.cr
new file mode 100644
index 000000000000..bb1fdc7240e9
--- /dev/null
+++ b/spec/std/uri/params/serializable_spec.cr
@@ -0,0 +1,133 @@
+require "spec"
+require "uri/params/serializable"
+
+private record SimpleType, page : Int32, strict : Bool, per_page : UInt8 do
+ include URI::Params::Serializable
+end
+
+private record SimpleTypeDefaults, page : Int32, strict : Bool, per_page : Int32 = 10 do
+ include URI::Params::Serializable
+end
+
+private record SimpleTypeNilable, page : Int32, strict : Bool, per_page : Int32? = nil do
+ include URI::Params::Serializable
+end
+
+private record SimpleTypeNilableDefault, page : Int32, strict : Bool, per_page : Int32? = 20 do
+ include URI::Params::Serializable
+end
+
+record Filter, status : String?, total : Float64? do
+ include URI::Params::Serializable
+end
+
+record Search, filter : Filter?, limit : Int32 = 25, offset : Int32 = 0 do
+ include URI::Params::Serializable
+end
+
+record GrandChild, name : String do
+ include URI::Params::Serializable
+end
+
+record Child, status : String?, grand_child : GrandChild do
+ include URI::Params::Serializable
+end
+
+record Parent, child : Child do
+ include URI::Params::Serializable
+end
+
+module MyConverter
+ def self.from_www_form(params : URI::Params, name : String)
+ params[name].to_i * 10
+ end
+end
+
+private record ConverterType, value : Int32 do
+ include URI::Params::Serializable
+
+ @[URI::Params::Field(converter: MyConverter)]
+ @value : Int32
+end
+
+class ParentType
+ include URI::Params::Serializable
+
+ getter name : String
+end
+
+class ChildType < ParentType
+end
+
+describe URI::Params::Serializable do
+ describe ".from_www_form" do
+ it "simple type" do
+ SimpleType.from_www_form("page=10&strict=true&per_page=5").should eq SimpleType.new(10, true, 5)
+ end
+
+ it "missing required property" do
+ expect_raises URI::SerializableError, "Missing required property: 'page'." do
+ SimpleType.from_www_form("strict=true&per_page=5")
+ end
+ end
+
+ it "with default values" do
+ SimpleTypeDefaults.from_www_form("page=10&strict=off").should eq SimpleTypeDefaults.new(10, false, 10)
+ end
+
+ it "with nilable values" do
+ SimpleTypeNilable.from_www_form("page=10&strict=true").should eq SimpleTypeNilable.new(10, true, nil)
+ end
+
+ it "with nilable default" do
+ SimpleTypeNilableDefault.from_www_form("page=10&strict=true").should eq SimpleTypeNilableDefault.new(10, true, 20)
+ end
+
+ it "with custom converter" do
+ ConverterType.from_www_form("value=10").should eq ConverterType.new(100)
+ end
+
+ it "child type" do
+ ChildType.from_www_form("name=Fred").name.should eq "Fred"
+ end
+
+ describe "nested type" do
+ it "happy path" do
+ Search.from_www_form("offset=10&filter[status]=active&filter[total]=3.14")
+ .should eq Search.new Filter.new("active", 3.14), offset: 10
+ end
+
+ it "missing nilable nested data" do
+ Search.from_www_form("offset=10")
+ .should eq Search.new Filter.new(nil, nil), offset: 10
+ end
+
+ it "missing required nested property" do
+ expect_raises URI::SerializableError, "Missing required property: 'child[grand_child][name]'." do
+ Parent.from_www_form("child[status]=active")
+ end
+ end
+
+ it "doubly nested" do
+ Parent.from_www_form("child[status]=active&child[grand_child][name]=Fred")
+ .should eq Parent.new Child.new("active", GrandChild.new("Fred"))
+ end
+ end
+ end
+
+ describe "#to_www_form" do
+ it "simple type" do
+ SimpleType.new(10, true, 5).to_www_form.should eq "page=10&strict=true&per_page=5"
+ end
+
+ it "nested type path" do
+ Search.new(Filter.new("active", 3.14), offset: 10).to_www_form
+ .should eq "filter%5Bstatus%5D=active&filter%5Btotal%5D=3.14&limit=25&offset=10"
+ end
+
+ it "doubly nested" do
+ Parent.new(Child.new("active", GrandChild.new("Fred"))).to_www_form
+ .should eq "child%5Bstatus%5D=active&child%5Bgrand_child%5D%5Bname%5D=Fred"
+ end
+ end
+end
diff --git a/spec/std/uri/params/to_www_form_spec.cr b/spec/std/uri/params/to_www_form_spec.cr
new file mode 100644
index 000000000000..c10d44334de5
--- /dev/null
+++ b/spec/std/uri/params/to_www_form_spec.cr
@@ -0,0 +1,60 @@
+require "spec"
+require "uri/params/serializable"
+
+private enum Color
+ Red
+ Green
+ BlueGreen
+end
+
+describe "#to_www_form" do
+ it Number do
+ URI::Params.build do |builder|
+ 12.to_www_form builder, "value"
+ end.should eq "value=12"
+ end
+
+ it Enum do
+ URI::Params.build do |builder|
+ Color::BlueGreen.to_www_form builder, "value"
+ end.should eq "value=blue_green"
+ end
+
+ it String do
+ URI::Params.build do |builder|
+ "12".to_www_form builder, "value"
+ end.should eq "value=12"
+ end
+
+ it Bool do
+ URI::Params.build do |builder|
+ false.to_www_form builder, "value"
+ end.should eq "value=false"
+ end
+
+ it Nil do
+ URI::Params.build do |builder|
+ nil.to_www_form builder, "value"
+ end.should eq "value="
+ end
+
+ it Time do
+ URI::Params.build do |builder|
+ Time.utc(2024, 8, 6, 9, 48, 10).to_www_form builder, "value"
+ end.should eq "value=2024-08-06T09%3A48%3A10Z"
+ end
+
+ describe Array do
+ it "of a single type" do
+ URI::Params.build do |builder|
+ [1, 2, 3].to_www_form builder, "value"
+ end.should eq "value=1&value=2&value=3"
+ end
+
+ it "of a union of types" do
+ URI::Params.build do |builder|
+ [1, false, "foo"].to_www_form builder, "value"
+ end.should eq "value=1&value=false&value=foo"
+ end
+ end
+end
diff --git a/spec/std/uuid_spec.cr b/spec/std/uuid_spec.cr
index 48cc3351a3c6..5d7e627031f0 100644
--- a/spec/std/uuid_spec.cr
+++ b/spec/std/uuid_spec.cr
@@ -1,6 +1,7 @@
require "spec"
require "uuid"
require "spec/helpers/string"
+require "../support/wasm32"
describe "UUID" do
describe "#==" do
diff --git a/spec/std/wait_group_spec.cr b/spec/std/wait_group_spec.cr
index 459af8d5c898..6c2f46daa562 100644
--- a/spec/std/wait_group_spec.cr
+++ b/spec/std/wait_group_spec.cr
@@ -160,6 +160,19 @@ describe WaitGroup do
extra.get.should eq(32)
end
+ it "takes a block to WaitGroup.wait" do
+ fiber_count = 10
+ completed = Array.new(fiber_count) { false }
+
+ WaitGroup.wait do |wg|
+ fiber_count.times do |i|
+ wg.spawn { completed[i] = true }
+ end
+ end
+
+ completed.should eq [true] * 10
+ end
+
# the test takes far too much time for the interpreter to complete
{% unless flag?(:interpreted) %}
it "stress add/done/wait" do
diff --git a/spec/std/xml/reader_spec.cr b/spec/std/xml/reader_spec.cr
index d89593620970..4ec3d8cddc5c 100644
--- a/spec/std/xml/reader_spec.cr
+++ b/spec/std/xml/reader_spec.cr
@@ -577,15 +577,5 @@ module XML
reader.errors.map(&.to_s).should eq ["Opening and ending tag mismatch: people line 1 and foo"]
end
-
- it "adds errors to `XML::Error.errors` (deprecated)" do
- XML::Error.errors # clear class error list
-
- reader = XML::Reader.new(%())
- reader.read
- reader.expand?
-
- XML::Error.errors.try(&.map(&.to_s)).should eq ["Opening and ending tag mismatch: people line 1 and foo"]
- end
end
end
diff --git a/spec/std/yaml/serializable_spec.cr b/spec/std/yaml/serializable_spec.cr
index 7d13f4318350..a48f0c754425 100644
--- a/spec/std/yaml/serializable_spec.cr
+++ b/spec/std/yaml/serializable_spec.cr
@@ -1001,7 +1001,7 @@ describe "YAML::Serializable" do
yaml = YAMLAttrWithPresenceAndIgnoreSerialize.from_yaml(%({"last_name": null}))
yaml.last_name_present?.should be_true
- # libyaml 0.2.5 removes traling space for empty scalar nodes
+ # libyaml 0.2.5 removes trailing space for empty scalar nodes
if YAML.libyaml_version >= SemanticVersion.new(0, 2, 5)
yaml.to_yaml.should eq("---\nlast_name:\n")
else
diff --git a/spec/support/channel.cr b/spec/support/channel.cr
index 7ca8d0668797..5ec3511c89c8 100644
--- a/spec/support/channel.cr
+++ b/spec/support/channel.cr
@@ -10,9 +10,9 @@ def schedule_timeout(c : Channel(SpecChannelStatus))
# TODO: it's not clear why some interpreter specs
# take more than 1 second in some cases.
# See #12429.
- sleep 5
+ sleep 5.seconds
{% else %}
- sleep 1
+ sleep 1.second
{% end %}
c.send(SpecChannelStatus::Timeout)
end
diff --git a/spec/support/retry.cr b/spec/support/retry.cr
index 638804c4be81..76fca476db95 100644
--- a/spec/support/retry.cr
+++ b/spec/support/retry.cr
@@ -7,7 +7,7 @@ def retry(n = 5, &)
if i == 0
Fiber.yield
else
- sleep 0.01 * (2**i)
+ sleep 10.milliseconds * (2**i)
end
else
return
diff --git a/spec/support/tempfile.cr b/spec/support/tempfile.cr
index a77070d90e40..ef4468040955 100644
--- a/spec/support/tempfile.cr
+++ b/spec/support/tempfile.cr
@@ -67,7 +67,7 @@ def with_temp_c_object_file(c_code, *, filename = "temp_c", file = __FILE__, &)
end
end
- `#{cl} /nologo /c #{Process.quote(c_filename)} #{Process.quote("/Fo#{o_filename}")}`.should be_truthy
+ `#{cl} /nologo /c /MD #{Process.quote(c_filename)} #{Process.quote("/Fo#{o_filename}")}`.should be_truthy
{% else %}
`#{ENV["CC"]? || "cc"} #{Process.quote(c_filename)} -c -o #{Process.quote(o_filename)}`.should be_truthy
{% end %}
diff --git a/src/VERSION b/src/VERSION
index 2f2e08cfa3bf..9a4866bbcede 100644
--- a/src/VERSION
+++ b/src/VERSION
@@ -1 +1 @@
-1.14.0-dev
+1.15.0-dev
diff --git a/src/base64.cr b/src/base64.cr
index 241d00c57bda..951684afc7ef 100644
--- a/src/base64.cr
+++ b/src/base64.cr
@@ -163,7 +163,7 @@ module Base64
buf = Pointer(UInt8).malloc(decode_size(slice.size))
appender = buf.appender
from_base64(slice) { |byte| appender << byte }
- Slice.new(buf, appender.size.to_i32)
+ appender.to_slice
end
# Writes the base64-decoded version of *data* to *io*.
diff --git a/src/benchmark.cr b/src/benchmark.cr
index bd77a93ae026..14bc12ae069a 100644
--- a/src/benchmark.cr
+++ b/src/benchmark.cr
@@ -11,8 +11,8 @@ require "./benchmark/**"
# require "benchmark"
#
# Benchmark.ips do |x|
-# x.report("short sleep") { sleep 0.01 }
-# x.report("shorter sleep") { sleep 0.001 }
+# x.report("short sleep") { sleep 10.milliseconds }
+# x.report("shorter sleep") { sleep 1.millisecond }
# end
# ```
#
@@ -31,7 +31,7 @@ require "./benchmark/**"
# require "benchmark"
#
# Benchmark.ips(warmup: 4, calculation: 10) do |x|
-# x.report("sleep") { sleep 0.01 }
+# x.report("sleep") { sleep 10.milliseconds }
# end
# ```
#
@@ -102,10 +102,10 @@ module Benchmark
# to which one can report the benchmarks. See the module's description.
#
# The optional parameters *calculation* and *warmup* set the duration of
- # those stages in seconds. For more detail on these stages see
+ # those stages. For more detail on these stages see
# `Benchmark::IPS`. When the *interactive* parameter is `true`, results are
# displayed and updated as they are calculated, otherwise all at once after they finished.
- def ips(calculation = 5, warmup = 2, interactive = STDOUT.tty?, &)
+ def ips(calculation : Time::Span = 5.seconds, warmup : Time::Span = 2.seconds, interactive : Bool = STDOUT.tty?, &)
{% if !flag?(:release) %}
puts "Warning: benchmarking without the `--release` flag won't yield useful results"
{% end %}
@@ -117,6 +117,18 @@ module Benchmark
job
end
+ # Instruction per second interface of the `Benchmark` module. Yields a `Job`
+ # to which one can report the benchmarks. See the module's description.
+ #
+ # The optional parameters *calculation* and *warmup* set the duration of
+ # those stages in seconds. For more detail on these stages see
+ # `Benchmark::IPS`. When the *interactive* parameter is `true`, results are
+ # displayed and updated as they are calculated, otherwise all at once after they finished.
+ @[Deprecated("Use `#ips(Time::Span, Time::Span, Bool, &)` instead.")]
+ def ips(calculation = 5, warmup = 2, interactive = STDOUT.tty?, &)
+ ips(calculation.seconds, warmup.seconds, !!interactive) { |job| yield job }
+ end
+
# Returns the time used to execute the given block.
def measure(label = "", &) : BM::Tms
t0, r0 = Process.times, Time.monotonic
diff --git a/src/benchmark/ips.cr b/src/benchmark/ips.cr
index cb952325eca0..def5b09c7c66 100644
--- a/src/benchmark/ips.cr
+++ b/src/benchmark/ips.cr
@@ -20,13 +20,16 @@ module Benchmark
@warmup_time : Time::Span
@calculation_time : Time::Span
- def initialize(calculation = 5, warmup = 2, interactive = STDOUT.tty?)
+ def initialize(calculation @calculation_time : Time::Span = 5.seconds, warmup @warmup_time : Time::Span = 2.seconds, interactive : Bool = STDOUT.tty?)
@interactive = !!interactive
- @warmup_time = warmup.seconds
- @calculation_time = calculation.seconds
@items = [] of Entry
end
+ @[Deprecated("Use `.new(Time::Span, Time::Span, Bool)` instead.")]
+ def self.new(calculation = 5, warmup = 2, interactive = STDOUT.tty?)
+ new(calculation.seconds, warmup.seconds, !!interactive)
+ end
+
# Adds code to be benchmarked
def report(label = "", &action) : Benchmark::IPS::Entry
item = Entry.new(label, action)
diff --git a/src/big/big_float.cr b/src/big/big_float.cr
index cadc91282fc1..5a57500fbdd7 100644
--- a/src/big/big_float.cr
+++ b/src/big/big_float.cr
@@ -115,18 +115,60 @@ struct BigFloat < Float
BigFloat.new { |mpf| LibGMP.mpf_neg(mpf, self) }
end
+ def +(other : Int::Primitive) : BigFloat
+ Int.primitive_ui_check(other) do |ui, neg_ui, big_i|
+ {
+ ui: BigFloat.new { |mpf| LibGMP.mpf_add_ui(mpf, self, {{ ui }}) },
+ neg_ui: BigFloat.new { |mpf| LibGMP.mpf_sub_ui(mpf, self, {{ neg_ui }}) },
+ big_i: self + {{ big_i }},
+ }
+ end
+ end
+
def +(other : Number) : BigFloat
BigFloat.new { |mpf| LibGMP.mpf_add(mpf, self, other.to_big_f) }
end
+ def -(other : Int::Primitive) : BigFloat
+ Int.primitive_ui_check(other) do |ui, neg_ui, big_i|
+ {
+ ui: BigFloat.new { |mpf| LibGMP.mpf_sub_ui(mpf, self, {{ ui }}) },
+ neg_ui: BigFloat.new { |mpf| LibGMP.mpf_add_ui(mpf, self, {{ neg_ui }}) },
+ big_i: self - {{ big_i }},
+ }
+ end
+ end
+
def -(other : Number) : BigFloat
BigFloat.new { |mpf| LibGMP.mpf_sub(mpf, self, other.to_big_f) }
end
+ def *(other : Int::Primitive) : BigFloat
+ Int.primitive_ui_check(other) do |ui, neg_ui, big_i|
+ {
+ ui: BigFloat.new { |mpf| LibGMP.mpf_mul_ui(mpf, self, {{ ui }}) },
+ neg_ui: BigFloat.new { |mpf| LibGMP.mpf_mul_ui(mpf, self, {{ neg_ui }}); LibGMP.mpf_neg(mpf, mpf) },
+ big_i: self + {{ big_i }},
+ }
+ end
+ end
+
def *(other : Number) : BigFloat
BigFloat.new { |mpf| LibGMP.mpf_mul(mpf, self, other.to_big_f) }
end
+ def /(other : Int::Primitive) : BigFloat
+ # Division by 0 in BigFloat is not allowed, there is no BigFloat::Infinity
+ raise DivisionByZeroError.new if other == 0
+ Int.primitive_ui_check(other) do |ui, neg_ui, _|
+ {
+ ui: BigFloat.new { |mpf| LibGMP.mpf_div_ui(mpf, self, {{ ui }}) },
+ neg_ui: BigFloat.new { |mpf| LibGMP.mpf_div_ui(mpf, self, {{ neg_ui }}); LibGMP.mpf_neg(mpf, mpf) },
+ big_i: BigFloat.new { |mpf| LibGMP.mpf_div(mpf, self, BigFloat.new(other)) },
+ }
+ end
+ end
+
def /(other : BigFloat) : BigFloat
# Division by 0 in BigFloat is not allowed, there is no BigFloat::Infinity
raise DivisionByZeroError.new if other == 0
@@ -320,10 +362,12 @@ struct BigFloat < Float
end
def to_s(io : IO) : Nil
- cstr = LibGMP.mpf_get_str(nil, out decimal_exponent, 10, 0, self)
+ cstr = LibGMP.mpf_get_str(nil, out orig_decimal_exponent, 10, 0, self)
length = LibC.strlen(cstr)
buffer = Slice.new(cstr, length)
+ decimal_exponent = fix_exponent_overflow(orig_decimal_exponent)
+
# add negative sign
if buffer[0]? == 45 # '-'
io << '-'
@@ -373,6 +417,55 @@ struct BigFloat < Float
end
end
+ # The same `LibGMP::MpExp` is used in `LibGMP::MPF` to represent a
+ # `BigFloat`'s exponent in base `256 ** sizeof(LibGMP::MpLimb)`, and to return
+ # a base-10 exponent in `LibGMP.mpf_get_str`. The latter is around 9.6x the
+ # former when `MpLimb` is 32-bit, or around 19.3x when `MpLimb` is 64-bit.
+ # This means the base-10 exponent will overflow for the majority of `MpExp`'s
+ # domain, even though `BigFloat`s will work correctly in this exponent range
+ # otherwise. This method exists to recover the original exponent for `#to_s`.
+ #
+ # Note that if `MpExp` is 64-bit, which is the case for non-Windows 64-bit
+ # targets, then `mpf_get_str` will simply crash for values above
+ # `2 ** 0x1_0000_0000_0000_0080`; here `exponent10` is around 5.553e+18, and
+ # never overflows. Thus there is no need to check for overflow in that case.
+ private def fix_exponent_overflow(exponent10)
+ {% if LibGMP::MpExp == Int64 %}
+ exponent10
+ {% else %}
+ # When `self` is non-zero,
+ #
+ # @mpf.@_mp_exp == Math.log(abs, 256.0 ** sizeof(LibGMP::MpLimb)).floor + 1
+ # @mpf.@_mp_exp - 1 <= Math.log(abs, 256.0 ** sizeof(LibGMP::MpLimb)) < @mpf.@_mp_exp
+ # @mpf.@_mp_exp - 1 <= Math.log10(abs) / Math.log10(256.0 ** sizeof(LibGMP::MpLimb)) < @mpf.@_mp_exp
+ # Math.log10(abs) >= (@mpf.@_mp_exp - 1) * Math.log10(256.0 ** sizeof(LibGMP::MpLimb))
+ # Math.log10(abs) < @mpf.@_mp_exp * Math.log10(256.0 ** sizeof(LibGMP::MpLimb))
+ #
+ # And also,
+ #
+ # exponent10 == Math.log10(abs).floor + 1
+ # exponent10 - 1 <= Math.log10(abs) < exponent10
+ #
+ # When `exponent10` overflows, it differs from its real value by an
+ # integer multiple of `256.0 ** sizeof(LibGMP::MpExp)`. We have to recover
+ # the integer `overflow_n` such that:
+ #
+ # LibGMP::MpExp::MIN <= exponent10 <= LibGMP::MpExp::MAX
+ # Math.log10(abs) ~= exponent10 + overflow_n * 256.0 ** sizeof(LibGMP::MpExp)
+ # ~= @mpf.@_mp_exp * Math.log10(256.0 ** sizeof(LibGMP::MpLimb))
+ #
+ # Because the possible intervals for the real `exponent10` are so far apart,
+ # it suffices to approximate `overflow_n` as follows:
+ #
+ # overflow_n ~= (@mpf.@_mp_exp * Math.log10(256.0 ** sizeof(LibGMP::MpLimb)) - exponent10) / 256.0 ** sizeof(LibGMP::MpExp)
+ #
+ # This value will be very close to an integer, which we then obtain with
+ # `#round`.
+ overflow_n = ((@mpf.@_mp_exp * Math.log10(256.0 ** sizeof(LibGMP::MpLimb)) - exponent10) / 256.0 ** sizeof(LibGMP::MpExp))
+ exponent10.to_i64 + overflow_n.round.to_i64 * (256_i64 ** sizeof(LibGMP::MpExp))
+ {% end %}
+ end
+
def clone
self
end
@@ -448,6 +541,29 @@ struct Int
def <=>(other : BigFloat)
-(other <=> self)
end
+
+ def -(other : BigFloat) : BigFloat
+ Int.primitive_ui_check(self) do |ui, neg_ui, _|
+ {
+ ui: BigFloat.new { |mpf| LibGMP.mpf_neg(mpf, other); LibGMP.mpf_add_ui(mpf, mpf, {{ ui }}) },
+ neg_ui: BigFloat.new { |mpf| LibGMP.mpf_neg(mpf, other); LibGMP.mpf_sub_ui(mpf, mpf, {{ neg_ui }}) },
+ big_i: BigFloat.new { |mpf| LibGMP.mpf_sub(mpf, BigFloat.new(self), other) },
+ }
+ end
+ end
+
+ def /(other : BigFloat) : BigFloat
+ # Division by 0 in BigFloat is not allowed, there is no BigFloat::Infinity
+ raise DivisionByZeroError.new if other == 0
+
+ Int.primitive_ui_check(self) do |ui, neg_ui, _|
+ {
+ ui: BigFloat.new { |mpf| LibGMP.mpf_ui_div(mpf, {{ ui }}, other) },
+ neg_ui: BigFloat.new { |mpf| LibGMP.mpf_ui_div(mpf, {{ neg_ui }}, other); LibGMP.mpf_neg(mpf, mpf) },
+ big_i: BigFloat.new { |mpf| LibGMP.mpf_div(mpf, BigFloat.new(self), other) },
+ }
+ end
+ end
end
struct Float
@@ -470,17 +586,78 @@ class String
end
module Math
- # Decomposes the given floating-point *value* into a normalized fraction and an integral power of two.
- def frexp(value : BigFloat) : {BigFloat, Int64}
- LibGMP.mpf_get_d_2exp(out exp, value) # we need BigFloat frac, so will skip Float64 one.
- frac = BigFloat.new do |mpf|
+ # Returns the unbiased base 2 exponent of the given floating-point *value*.
+ #
+ # Raises `ArgumentError` if *value* is zero.
+ def ilogb(value : BigFloat) : Int64
+ raise ArgumentError.new "Cannot get exponent of zero" if value.zero?
+ leading_zeros = value.@mpf._mp_d[value.@mpf._mp_size.abs - 1].leading_zeros_count
+ 8_i64 * sizeof(LibGMP::MpLimb) * value.@mpf._mp_exp - leading_zeros - 1
+ end
+
+ # Returns the unbiased radix-independent exponent of the given floating-point *value*.
+ #
+ # For `BigFloat` this is equivalent to `ilogb`.
+ #
+ # Raises `ArgumentError` is *value* is zero.
+ def logb(value : BigFloat) : BigFloat
+ ilogb(value).to_big_f
+ end
+
+ # Multiplies the given floating-point *value* by 2 raised to the power *exp*.
+ def ldexp(value : BigFloat, exp : Int) : BigFloat
+ BigFloat.new do |mpf|
if exp >= 0
- LibGMP.mpf_div_2exp(mpf, value, exp)
+ LibGMP.mpf_mul_2exp(mpf, value, exp.to_u64)
else
- LibGMP.mpf_mul_2exp(mpf, value, -exp)
+ LibGMP.mpf_div_2exp(mpf, value, exp.abs.to_u64)
end
end
- {frac, exp.to_i64}
+ end
+
+ # Returns the floating-point *value* with its exponent raised by *exp*.
+ #
+ # For `BigFloat` this is equivalent to `ldexp`.
+ def scalbn(value : BigFloat, exp : Int) : BigFloat
+ ldexp(value, exp)
+ end
+
+ # :ditto:
+ def scalbln(value : BigFloat, exp : Int) : BigFloat
+ ldexp(value, exp)
+ end
+
+ # Decomposes the given floating-point *value* into a normalized fraction and an integral power of two.
+ def frexp(value : BigFloat) : {BigFloat, Int64}
+ return {BigFloat.zero, 0_i64} if value.zero?
+
+ # We compute this ourselves since `LibGMP.mpf_get_d_2exp` only returns a
+ # `LibC::Long` exponent, which is not sufficient for 32-bit `LibC::Long` and
+ # 32-bit `LibGMP::MpExp`, e.g. on 64-bit Windows.
+ # Since `0.5 <= frac.abs < 1.0`, the radix point should be just above the
+ # most significant limb, and there should be no leading zeros in that limb.
+ leading_zeros = value.@mpf._mp_d[value.@mpf._mp_size.abs - 1].leading_zeros_count
+ exp = 8_i64 * sizeof(LibGMP::MpLimb) * value.@mpf._mp_exp - leading_zeros
+
+ frac = BigFloat.new do |mpf|
+ # remove leading zeros in the most significant limb
+ LibGMP.mpf_mul_2exp(mpf, value, leading_zeros)
+ # reset the exponent manually
+ mpf.value._mp_exp = 0
+ end
+
+ {frac, exp}
+ end
+
+ # Returns the floating-point value with the magnitude of *value1* and the sign of *value2*.
+ #
+ # `BigFloat` does not support signed zeros; if `value2 == 0`, the returned value is non-negative.
+ def copysign(value1 : BigFloat, value2 : BigFloat) : BigFloat
+ if value1.negative? != value2.negative? # opposite signs
+ -value1
+ else
+ value1
+ end
end
# Calculates the square root of *value*.
@@ -494,21 +671,3 @@ module Math
BigFloat.new { |mpf| LibGMP.mpf_sqrt(mpf, value) }
end
end
-
-# :nodoc:
-struct Crystal::Hasher
- def self.reduce_num(value : BigFloat)
- float_normalize_wrap(value) do |value|
- # more exact version of `Math.frexp`
- LibGMP.mpf_get_d_2exp(out exp, value)
- frac = BigFloat.new do |mpf|
- if exp >= 0
- LibGMP.mpf_div_2exp(mpf, value, exp)
- else
- LibGMP.mpf_mul_2exp(mpf, value, -exp)
- end
- end
- float_normalize_reference(value, frac, exp)
- end
- end
-end
diff --git a/src/big/big_int.cr b/src/big/big_int.cr
index 49738cb8bfbc..c306a490a412 100644
--- a/src/big/big_int.cr
+++ b/src/big/big_int.cr
@@ -659,7 +659,7 @@ struct BigInt < Int
{% for n in [8, 16, 32, 64, 128] %}
def to_i{{n}} : Int{{n}}
\{% if Int{{n}} == LibGMP::SI %}
- LibGMP.{{ flag?(:win32) ? "fits_si_p".id : "fits_slong_p".id }}(self) != 0 ? LibGMP.get_si(self) : raise OverflowError.new
+ LibGMP.{{ flag?(:win32) && !flag?(:gnu) ? "fits_si_p".id : "fits_slong_p".id }}(self) != 0 ? LibGMP.get_si(self) : raise OverflowError.new
\{% elsif Int{{n}}::MAX.is_a?(NumberLiteral) && Int{{n}}::MAX < LibGMP::SI::MAX %}
LibGMP::SI.new(self).to_i{{n}}
\{% else %}
@@ -669,7 +669,7 @@ struct BigInt < Int
def to_u{{n}} : UInt{{n}}
\{% if UInt{{n}} == LibGMP::UI %}
- LibGMP.{{ flag?(:win32) ? "fits_ui_p".id : "fits_ulong_p".id }}(self) != 0 ? LibGMP.get_ui(self) : raise OverflowError.new
+ LibGMP.{{ flag?(:win32) && !flag?(:gnu) ? "fits_ui_p".id : "fits_ulong_p".id }}(self) != 0 ? LibGMP.get_ui(self) : raise OverflowError.new
\{% elsif UInt{{n}}::MAX.is_a?(NumberLiteral) && UInt{{n}}::MAX < LibGMP::UI::MAX %}
LibGMP::UI.new(self).to_u{{n}}
\{% else %}
diff --git a/src/big/lib_gmp.cr b/src/big/lib_gmp.cr
index 00834598d9d2..7368cb0e9fb6 100644
--- a/src/big/lib_gmp.cr
+++ b/src/big/lib_gmp.cr
@@ -1,5 +1,8 @@
-{% if flag?(:win32) %}
+{% if flag?(:win32) && !flag?(:gnu) %}
@[Link("mpir")]
+ {% if compare_versions(Crystal::VERSION, "1.11.0-dev") >= 0 %}
+ @[Link(dll: "mpir.dll")]
+ {% end %}
{% else %}
@[Link("gmp")]
{% end %}
@@ -11,7 +14,7 @@ lib LibGMP
# MPIR uses its own `mpir_si` and `mpir_ui` typedefs in places where GMP uses
# the LibC types, when the function name has `si` or `ui`; we follow this
# distinction
- {% if flag?(:win32) && flag?(:bits64) %}
+ {% if flag?(:win32) && !flag?(:gnu) && flag?(:bits64) %}
alias SI = LibC::LongLong
alias UI = LibC::ULongLong
{% else %}
@@ -23,17 +26,19 @@ lib LibGMP
alias Double = LibC::Double
alias BitcntT = UI
- {% if flag?(:win32) && flag?(:bits64) %}
- alias MpExp = LibC::Long
+ alias MpExp = LibC::Long
+
+ {% if flag?(:win32) && !flag?(:gnu) %}
alias MpSize = LibC::LongLong
- alias MpLimb = LibC::ULongLong
- {% elsif flag?(:bits64) %}
- alias MpExp = Int64
- alias MpSize = LibC::Long
- alias MpLimb = LibC::ULong
{% else %}
- alias MpExp = Int32
alias MpSize = LibC::Long
+ {% end %}
+
+ # NOTE: this assumes GMP is configured by build time to define
+ # `_LONG_LONG_LIMB=1` on Windows
+ {% if flag?(:win32) %}
+ alias MpLimb = LibC::ULongLong
+ {% else %}
alias MpLimb = LibC::ULong
{% end %}
@@ -146,11 +151,12 @@ lib LibGMP
# # Miscellaneous Functions
- fun fits_ulong_p = __gmpz_fits_ulong_p(op : MPZ*) : Int
- fun fits_slong_p = __gmpz_fits_slong_p(op : MPZ*) : Int
- {% if flag?(:win32) %}
+ {% if flag?(:win32) && !flag?(:gnu) %}
fun fits_ui_p = __gmpz_fits_ui_p(op : MPZ*) : Int
fun fits_si_p = __gmpz_fits_si_p(op : MPZ*) : Int
+ {% else %}
+ fun fits_ulong_p = __gmpz_fits_ulong_p(op : MPZ*) : Int
+ fun fits_slong_p = __gmpz_fits_slong_p(op : MPZ*) : Int
{% end %}
# # Special Functions
@@ -233,8 +239,11 @@ lib LibGMP
# # Arithmetic
fun mpf_add = __gmpf_add(rop : MPF*, op1 : MPF*, op2 : MPF*)
+ fun mpf_add_ui = __gmpf_add_ui(rop : MPF*, op1 : MPF*, op2 : UI)
fun mpf_sub = __gmpf_sub(rop : MPF*, op1 : MPF*, op2 : MPF*)
+ fun mpf_sub_ui = __gmpf_sub_ui(rop : MPF*, op1 : MPF*, op2 : UI)
fun mpf_mul = __gmpf_mul(rop : MPF*, op1 : MPF*, op2 : MPF*)
+ fun mpf_mul_ui = __gmpf_mul_ui(rop : MPF*, op1 : MPF*, op2 : UI)
fun mpf_div = __gmpf_div(rop : MPF*, op1 : MPF*, op2 : MPF*)
fun mpf_div_ui = __gmpf_div_ui(rop : MPF*, op1 : MPF*, op2 : UI)
fun mpf_ui_div = __gmpf_ui_div(rop : MPF*, op1 : UI, op2 : MPF*)
diff --git a/src/big/number.cr b/src/big/number.cr
index 1251e8113db3..8761a2aa8b6c 100644
--- a/src/big/number.cr
+++ b/src/big/number.cr
@@ -8,18 +8,6 @@ struct BigFloat
self.class.new(self / other)
end
- def /(other : Int::Primitive) : BigFloat
- # Division by 0 in BigFloat is not allowed, there is no BigFloat::Infinity
- raise DivisionByZeroError.new if other == 0
- Int.primitive_ui_check(other) do |ui, neg_ui, _|
- {
- ui: BigFloat.new { |mpf| LibGMP.mpf_div_ui(mpf, self, {{ ui }}) },
- neg_ui: BigFloat.new { |mpf| LibGMP.mpf_div_ui(mpf, self, {{ neg_ui }}); LibGMP.mpf_neg(mpf, mpf) },
- big_i: BigFloat.new { |mpf| LibGMP.mpf_div(mpf, self, BigFloat.new(other)) },
- }
- end
- end
-
Number.expand_div [Float32, Float64], BigFloat
end
@@ -91,70 +79,60 @@ end
struct Int8
Number.expand_div [BigInt], BigFloat
- Number.expand_div [BigFloat], BigFloat
Number.expand_div [BigDecimal], BigDecimal
Number.expand_div [BigRational], BigRational
end
struct Int16
Number.expand_div [BigInt], BigFloat
- Number.expand_div [BigFloat], BigFloat
Number.expand_div [BigDecimal], BigDecimal
Number.expand_div [BigRational], BigRational
end
struct Int32
Number.expand_div [BigInt], BigFloat
- Number.expand_div [BigFloat], BigFloat
Number.expand_div [BigDecimal], BigDecimal
Number.expand_div [BigRational], BigRational
end
struct Int64
Number.expand_div [BigInt], BigFloat
- Number.expand_div [BigFloat], BigFloat
Number.expand_div [BigDecimal], BigDecimal
Number.expand_div [BigRational], BigRational
end
struct Int128
Number.expand_div [BigInt], BigFloat
- Number.expand_div [BigFloat], BigFloat
Number.expand_div [BigDecimal], BigDecimal
Number.expand_div [BigRational], BigRational
end
struct UInt8
Number.expand_div [BigInt], BigFloat
- Number.expand_div [BigFloat], BigFloat
Number.expand_div [BigDecimal], BigDecimal
Number.expand_div [BigRational], BigRational
end
struct UInt16
Number.expand_div [BigInt], BigFloat
- Number.expand_div [BigFloat], BigFloat
Number.expand_div [BigDecimal], BigDecimal
Number.expand_div [BigRational], BigRational
end
struct UInt32
Number.expand_div [BigInt], BigFloat
- Number.expand_div [BigFloat], BigFloat
Number.expand_div [BigDecimal], BigDecimal
Number.expand_div [BigRational], BigRational
end
struct UInt64
Number.expand_div [BigInt], BigFloat
- Number.expand_div [BigFloat], BigFloat
Number.expand_div [BigDecimal], BigDecimal
Number.expand_div [BigRational], BigRational
end
struct UInt128
Number.expand_div [BigInt], BigFloat
- Number.expand_div [BigFloat], BigFloat
Number.expand_div [BigDecimal], BigDecimal
Number.expand_div [BigRational], BigRational
end
diff --git a/src/box.cr b/src/box.cr
index 78799838e688..a5a6900b2ea1 100644
--- a/src/box.cr
+++ b/src/box.cr
@@ -5,9 +5,13 @@
#
# For an example usage, see `Proc`'s explanation about sending Procs to C.
class Box(T)
+ # :nodoc:
+ #
# Returns the original object
getter object : T
+ # :nodoc:
+ #
# Creates a `Box` with the given object.
#
# This method isn't usually used directly. Instead, `Box.box` is used.
diff --git a/src/channel.cr b/src/channel.cr
index dfd61ff51cc4..4e23f8bb9b09 100644
--- a/src/channel.cr
+++ b/src/channel.cr
@@ -1,6 +1,7 @@
require "fiber"
require "crystal/spin_lock"
require "crystal/pointer_linked_list"
+require "channel/select"
# A `Channel` enables concurrent communication between fibers.
#
@@ -26,106 +27,15 @@ class Channel(T)
@lock = Crystal::SpinLock.new
@queue : Deque(T)?
- # :nodoc:
- record NotReady
# :nodoc:
record UseDefault
- # :nodoc:
- module SelectAction(S)
- abstract def execute : DeliveryState
- abstract def wait(context : SelectContext(S))
- abstract def wait_result_impl(context : SelectContext(S))
- abstract def unwait_impl(context : SelectContext(S))
- abstract def result : S
- abstract def lock_object_id
- abstract def lock
- abstract def unlock
-
- def create_context_and_wait(shared_state)
- context = SelectContext.new(shared_state, self)
- self.wait(context)
- context
- end
-
- # wait_result overload allow implementors to define
- # wait_result_impl with the right type and Channel.select_impl
- # to allow dispatching over unions that will not happen
- def wait_result(context : SelectContext)
- raise "BUG: Unexpected call to #{typeof(self)}#wait_result(context : #{typeof(context)})"
- end
-
- def wait_result(context : SelectContext(S))
- wait_result_impl(context)
- end
-
- # idem wait_result/wait_result_impl
- def unwait(context : SelectContext)
- raise "BUG: Unexpected call to #{typeof(self)}#unwait(context : #{typeof(context)})"
- end
-
- def unwait(context : SelectContext(S))
- unwait_impl(context)
- end
-
- # Implementor that returns `Channel::UseDefault` in `#execute`
- # must redefine `#default_result`
- def default_result
- raise "Unreachable"
- end
- end
-
- private enum SelectState
- None = 0
- Active = 1
- Done = 2
- end
-
- private class SelectContextSharedState
- @state : Atomic(SelectState)
-
- def initialize(value : SelectState)
- @state = Atomic(SelectState).new(value)
- end
-
- def compare_and_set(cmp : SelectState, new : SelectState) : {SelectState, Bool}
- @state.compare_and_set(cmp, new)
- end
- end
-
- private class SelectContext(S)
- @state : SelectContextSharedState
- property action : SelectAction(S)
- @activated = false
-
- def initialize(@state, @action : SelectAction(S))
- end
-
- def activated? : Bool
- @activated
- end
-
- def try_trigger : Bool
- _, succeed = @state.compare_and_set(:active, :done)
- if succeed
- @activated = true
- end
- succeed
- end
- end
-
class ClosedError < Exception
def initialize(msg = "Channel is closed")
super(msg)
end
end
- private enum DeliveryState
- None
- Delivered
- Closed
- end
-
private module SenderReceiverCloseAction
def close
self.state = DeliveryState::Closed
@@ -398,112 +308,6 @@ class Channel(T)
nil
end
- # :nodoc:
- def self.select(*ops : SelectAction)
- self.select ops
- end
-
- # :nodoc:
- def self.select(ops : Indexable(SelectAction))
- i, m = select_impl(ops, false)
- raise "BUG: Blocking select returned not ready status" if m.is_a?(NotReady)
- return i, m
- end
-
- # :nodoc:
- def self.non_blocking_select(*ops : SelectAction)
- self.non_blocking_select ops
- end
-
- # :nodoc:
- def self.non_blocking_select(ops : Indexable(SelectAction))
- select_impl(ops, true)
- end
-
- private def self.select_impl(ops : Indexable(SelectAction), non_blocking)
- # ops_locks is a duplicate of ops that can be sorted without disturbing the
- # index positions of ops
- if ops.responds_to?(:unstable_sort_by!)
- # If the collection type implements `unstable_sort_by!` we can dup it.
- # This applies to two types:
- # * `Array`: `Array#to_a` does not dup and would return the same instance,
- # thus we'd be sorting ops and messing up the index positions.
- # * `StaticArray`: This avoids a heap allocation because we can dup a
- # static array on the stack.
- ops_locks = ops.dup
- elsif ops.responds_to?(:to_static_array)
- # If the collection type implements `to_static_array` we can create a
- # copy without allocating an array. This applies to `Tuple` types, which
- # the compiler generates for `select` expressions.
- ops_locks = ops.to_static_array
- else
- ops_locks = ops.to_a
- end
-
- # Sort the operations by the channel they contain
- # This is to avoid deadlocks between concurrent `select` calls
- ops_locks.unstable_sort_by!(&.lock_object_id)
-
- each_skip_duplicates(ops_locks, &.lock)
-
- ops.each_with_index do |op, index|
- state = op.execute
-
- case state
- in .delivered?
- each_skip_duplicates(ops_locks, &.unlock)
- return index, op.result
- in .closed?
- each_skip_duplicates(ops_locks, &.unlock)
- return index, op.default_result
- in .none?
- # do nothing
- end
- end
-
- if non_blocking
- each_skip_duplicates(ops_locks, &.unlock)
- return ops.size, NotReady.new
- end
-
- # Because `channel#close` may clean up a long list, `select_context.try_trigger` may
- # be called after the select return. In order to prevent invalid address access,
- # the state is allocated in the heap.
- shared_state = SelectContextSharedState.new(SelectState::Active)
- contexts = ops.map &.create_context_and_wait(shared_state)
-
- each_skip_duplicates(ops_locks, &.unlock)
- Fiber.suspend
-
- contexts.each_with_index do |context, index|
- op = ops[index]
- op.lock
- op.unwait(context)
- op.unlock
- end
-
- contexts.each_with_index do |context, index|
- if context.activated?
- return index, ops[index].wait_result(context)
- end
- end
-
- raise "BUG: Fiber was awaken from select but no action was activated"
- end
-
- private def self.each_skip_duplicates(ops_locks, &)
- # Avoid deadlocks from trying to lock the same lock twice.
- # `ops_lock` is sorted by `lock_object_id`, so identical onces will be in
- # a row and we skip repeats while iterating.
- last_lock_id = nil
- ops_locks.each do |op|
- if op.lock_object_id != last_lock_id
- last_lock_id = op.lock_object_id
- yield op
- end
- end
- end
-
# :nodoc:
def send_select_action(value : T)
SendAction.new(self, value)
@@ -699,69 +503,4 @@ class Channel(T)
raise ClosedError.new
end
end
-
- # :nodoc:
- class TimeoutAction
- include SelectAction(Nil)
-
- # Total amount of time to wait
- @timeout : Time::Span
- @select_context : SelectContext(Nil)?
-
- def initialize(@timeout : Time::Span)
- end
-
- def execute : DeliveryState
- DeliveryState::None
- end
-
- def result : Nil
- nil
- end
-
- def wait(context : SelectContext(Nil)) : Nil
- @select_context = context
- Fiber.timeout(@timeout, self)
- end
-
- def wait_result_impl(context : SelectContext(Nil))
- nil
- end
-
- def unwait_impl(context : SelectContext(Nil))
- Fiber.cancel_timeout
- end
-
- def lock_object_id : UInt64
- self.object_id
- end
-
- def lock
- end
-
- def unlock
- end
-
- def time_expired(fiber : Fiber) : Nil
- if @select_context.try &.try_trigger
- fiber.enqueue
- end
- end
- end
-end
-
-# Timeout keyword for use in `select`.
-#
-# ```
-# select
-# when x = ch.receive
-# puts "got #{x}"
-# when timeout(1.seconds)
-# puts "timeout"
-# end
-# ```
-#
-# NOTE: It won't trigger if the `select` has an `else` case (i.e.: a non-blocking select).
-def timeout_select_action(timeout : Time::Span) : Channel::TimeoutAction
- Channel::TimeoutAction.new(timeout)
end
diff --git a/src/channel/select.cr b/src/channel/select.cr
new file mode 100644
index 000000000000..05db47a79a4c
--- /dev/null
+++ b/src/channel/select.cr
@@ -0,0 +1,158 @@
+class Channel(T)
+ # :nodoc:
+ record NotReady
+
+ private enum SelectState
+ None = 0
+ Active = 1
+ Done = 2
+ end
+
+ private class SelectContextSharedState
+ @state : Atomic(SelectState)
+
+ def initialize(value : SelectState)
+ @state = Atomic(SelectState).new(value)
+ end
+
+ def compare_and_set(cmp : SelectState, new : SelectState) : {SelectState, Bool}
+ @state.compare_and_set(cmp, new)
+ end
+ end
+
+ private class SelectContext(S)
+ @state : SelectContextSharedState
+ property action : SelectAction(S)
+ @activated = false
+
+ def initialize(@state, @action : SelectAction(S))
+ end
+
+ def activated? : Bool
+ @activated
+ end
+
+ def try_trigger : Bool
+ _, succeed = @state.compare_and_set(:active, :done)
+ if succeed
+ @activated = true
+ end
+ succeed
+ end
+ end
+
+ private enum DeliveryState
+ None
+ Delivered
+ Closed
+ end
+
+ # :nodoc:
+ def self.select(*ops : SelectAction)
+ self.select ops
+ end
+
+ # :nodoc:
+ def self.select(ops : Indexable(SelectAction))
+ i, m = select_impl(ops, false)
+ raise "BUG: Blocking select returned not ready status" if m.is_a?(NotReady)
+ return i, m
+ end
+
+ # :nodoc:
+ def self.non_blocking_select(*ops : SelectAction)
+ self.non_blocking_select ops
+ end
+
+ # :nodoc:
+ def self.non_blocking_select(ops : Indexable(SelectAction))
+ select_impl(ops, true)
+ end
+
+ private def self.select_impl(ops : Indexable(SelectAction), non_blocking)
+ # ops_locks is a duplicate of ops that can be sorted without disturbing the
+ # index positions of ops
+ if ops.responds_to?(:unstable_sort_by!)
+ # If the collection type implements `unstable_sort_by!` we can dup it.
+ # This applies to two types:
+ # * `Array`: `Array#to_a` does not dup and would return the same instance,
+ # thus we'd be sorting ops and messing up the index positions.
+ # * `StaticArray`: This avoids a heap allocation because we can dup a
+ # static array on the stack.
+ ops_locks = ops.dup
+ elsif ops.responds_to?(:to_static_array)
+ # If the collection type implements `to_static_array` we can create a
+ # copy without allocating an array. This applies to `Tuple` types, which
+ # the compiler generates for `select` expressions.
+ ops_locks = ops.to_static_array
+ else
+ ops_locks = ops.to_a
+ end
+
+ # Sort the operations by the channel they contain
+ # This is to avoid deadlocks between concurrent `select` calls
+ ops_locks.unstable_sort_by!(&.lock_object_id)
+
+ each_skip_duplicates(ops_locks, &.lock)
+
+ ops.each_with_index do |op, index|
+ state = op.execute
+
+ case state
+ in .delivered?
+ each_skip_duplicates(ops_locks, &.unlock)
+ return index, op.result
+ in .closed?
+ each_skip_duplicates(ops_locks, &.unlock)
+ return index, op.default_result
+ in .none?
+ # do nothing
+ end
+ end
+
+ if non_blocking
+ each_skip_duplicates(ops_locks, &.unlock)
+ return ops.size, NotReady.new
+ end
+
+ # Because `channel#close` may clean up a long list, `select_context.try_trigger` may
+ # be called after the select return. In order to prevent invalid address access,
+ # the state is allocated in the heap.
+ shared_state = SelectContextSharedState.new(SelectState::Active)
+ contexts = ops.map &.create_context_and_wait(shared_state)
+
+ each_skip_duplicates(ops_locks, &.unlock)
+ Fiber.suspend
+
+ contexts.each_with_index do |context, index|
+ op = ops[index]
+ op.lock
+ op.unwait(context)
+ op.unlock
+ end
+
+ contexts.each_with_index do |context, index|
+ if context.activated?
+ return index, ops[index].wait_result(context)
+ end
+ end
+
+ raise "BUG: Fiber was awaken from select but no action was activated"
+ end
+
+ private def self.each_skip_duplicates(ops_locks, &)
+ # Avoid deadlocks from trying to lock the same lock twice.
+ # `ops_lock` is sorted by `lock_object_id`, so identical ones will be in
+ # a row and we skip repeats while iterating.
+ last_lock_id = nil
+ ops_locks.each do |op|
+ if op.lock_object_id != last_lock_id
+ last_lock_id = op.lock_object_id
+ yield op
+ end
+ end
+ end
+end
+
+require "./select/select_action"
+require "./select/timeout_action"
diff --git a/src/channel/select/select_action.cr b/src/channel/select/select_action.cr
new file mode 100644
index 000000000000..d5439fde5587
--- /dev/null
+++ b/src/channel/select/select_action.cr
@@ -0,0 +1,45 @@
+class Channel(T)
+ # :nodoc:
+ module SelectAction(S)
+ abstract def execute : DeliveryState
+ abstract def wait(context : SelectContext(S))
+ abstract def wait_result_impl(context : SelectContext(S))
+ abstract def unwait_impl(context : SelectContext(S))
+ abstract def result : S
+ abstract def lock_object_id
+ abstract def lock
+ abstract def unlock
+
+ def create_context_and_wait(shared_state)
+ context = SelectContext.new(shared_state, self)
+ self.wait(context)
+ context
+ end
+
+ # wait_result overload allow implementors to define
+ # wait_result_impl with the right type and Channel.select_impl
+ # to allow dispatching over unions that will not happen
+ def wait_result(context : SelectContext)
+ raise "BUG: Unexpected call to #{typeof(self)}#wait_result(context : #{typeof(context)})"
+ end
+
+ def wait_result(context : SelectContext(S))
+ wait_result_impl(context)
+ end
+
+ # idem wait_result/wait_result_impl
+ def unwait(context : SelectContext)
+ raise "BUG: Unexpected call to #{typeof(self)}#unwait(context : #{typeof(context)})"
+ end
+
+ def unwait(context : SelectContext(S))
+ unwait_impl(context)
+ end
+
+ # Implementor that returns `Channel::UseDefault` in `#execute`
+ # must redefine `#default_result`
+ def default_result
+ raise "Unreachable"
+ end
+ end
+end
diff --git a/src/channel/select/timeout_action.cr b/src/channel/select/timeout_action.cr
new file mode 100644
index 000000000000..39986197bbdc
--- /dev/null
+++ b/src/channel/select/timeout_action.cr
@@ -0,0 +1,68 @@
+# Timeout keyword for use in `select`.
+#
+# ```
+# select
+# when x = ch.receive
+# puts "got #{x}"
+# when timeout(1.seconds)
+# puts "timeout"
+# end
+# ```
+#
+# NOTE: It won't trigger if the `select` has an `else` case (i.e.: a non-blocking select).
+def timeout_select_action(timeout : Time::Span) : Channel::TimeoutAction
+ Channel::TimeoutAction.new(timeout)
+end
+
+class Channel(T)
+ # :nodoc:
+ class TimeoutAction
+ include SelectAction(Nil)
+
+ # Total amount of time to wait
+ @timeout : Time::Span
+ @select_context : SelectContext(Nil)?
+
+ def initialize(@timeout : Time::Span)
+ end
+
+ def execute : DeliveryState
+ DeliveryState::None
+ end
+
+ def result : Nil
+ nil
+ end
+
+ def wait(context : SelectContext(Nil)) : Nil
+ @select_context = context
+ Fiber.timeout(@timeout, self)
+ end
+
+ def wait_result_impl(context : SelectContext(Nil))
+ nil
+ end
+
+ def unwait_impl(context : SelectContext(Nil))
+ Fiber.cancel_timeout
+ end
+
+ def lock_object_id : UInt64
+ self.object_id
+ end
+
+ def lock
+ end
+
+ def unlock
+ end
+
+ def time_expired(fiber : Fiber) : Nil
+ fiber.enqueue if time_expired?
+ end
+
+ def time_expired? : Bool
+ @select_context.try &.try_trigger || false
+ end
+ end
+end
diff --git a/src/compiler/crystal/codegen/call.cr b/src/compiler/crystal/codegen/call.cr
index 1b678232c054..5934ffeb0c14 100644
--- a/src/compiler/crystal/codegen/call.cr
+++ b/src/compiler/crystal/codegen/call.cr
@@ -340,7 +340,10 @@ class Crystal::CodeGenVisitor
# Create self var if available
if node_obj
- new_vars["%self"] = LLVMVar.new(@last, node_obj.type, true)
+ # call `#remove_indirection` here so that the downcast call in
+ # `#visit(Var)` doesn't spend time expanding module types again and again
+ # (it should be the only use site of `node_obj.type`)
+ new_vars["%self"] = LLVMVar.new(@last, node_obj.type.remove_indirection, true)
end
# Get type if of args and create arg vars
@@ -359,6 +362,10 @@ class Crystal::CodeGenVisitor
is_super = node.super?
+ # call `#remove_indirection` here so that the `match_type_id` below doesn't
+ # spend time expanding module types again and again
+ owner = owner.remove_indirection unless is_super
+
with_cloned_context do
context.vars = new_vars
diff --git a/src/compiler/crystal/codegen/codegen.cr b/src/compiler/crystal/codegen/codegen.cr
index a46d255901e5..7e15b1bdc385 100644
--- a/src/compiler/crystal/codegen/codegen.cr
+++ b/src/compiler/crystal/codegen/codegen.cr
@@ -17,7 +17,7 @@ module Crystal
ONCE = "__crystal_once"
class Program
- def run(code, filename = nil, debug = Debug::Default)
+ def run(code, filename : String? = nil, debug = Debug::Default)
parser = new_parser(code)
parser.filename = filename
node = parser.parse
@@ -69,6 +69,81 @@ module Crystal
end
end
+ def run(code, return_type : T.class, filename : String? = nil, debug = Debug::Default) forall T
+ parser = new_parser(code)
+ parser.filename = filename
+ node = parser.parse
+ node = normalize node
+ node = semantic node
+ evaluate node, T, debug: debug
+ end
+
+ def evaluate(node, return_type : T.class, debug = Debug::Default) : T forall T
+ llvm_context =
+ {% if LibLLVM::IS_LT_110 %}
+ LLVM::Context.new
+ {% else %}
+ begin
+ ts_ctx = LLVM::Orc::ThreadSafeContext.new
+ ts_ctx.context
+ end
+ {% end %}
+
+ visitor = CodeGenVisitor.new self, node, single_module: true, debug: debug, llvm_context: llvm_context
+ visitor.accept node
+ visitor.process_finished_hooks
+ visitor.finish
+
+ llvm_mod = visitor.modules[""].mod
+ llvm_mod.target = target_machine.triple
+
+ main = visitor.typed_fun?(llvm_mod, MAIN_NAME).not_nil!
+
+ # void (*__evaluate_wrapper)(void*)
+ wrapper_type = LLVM::Type.function([llvm_context.void_pointer], llvm_context.void)
+ wrapper = llvm_mod.functions.add("__evaluate_wrapper", wrapper_type) do |func|
+ func.basic_blocks.append "entry" do |builder|
+ argc = llvm_context.int32.const_int(0)
+ argv = llvm_context.void_pointer.pointer.null
+ ret = builder.call(main.type, main.func, [argc, argv])
+ unless node.type.void? || node.type.nil_type?
+ out_ptr = func.params[0]
+ {% if LibLLVM::IS_LT_150 %}
+ out_ptr = builder.bit_cast out_ptr, main.type.return_type.pointer
+ {% end %}
+ builder.store(ret, out_ptr)
+ end
+ builder.ret
+ end
+ end
+
+ llvm_mod.verify
+
+ result = uninitialized T
+
+ {% if LibLLVM::IS_LT_110 %}
+ LLVM::JITCompiler.new(llvm_mod) do |jit|
+ func_ptr = jit.function_address("__evaluate_wrapper")
+ func = Proc(T*, Nil).new(func_ptr, Pointer(Void).null)
+ func.call(pointerof(result))
+ end
+ {% else %}
+ lljit_builder = LLVM::Orc::LLJITBuilder.new
+ lljit = LLVM::Orc::LLJIT.new(lljit_builder)
+
+ dylib = lljit.main_jit_dylib
+ dylib.link_symbols_from_current_process(lljit.global_prefix)
+ tsm = LLVM::Orc::ThreadSafeModule.new(llvm_mod, ts_ctx)
+ lljit.add_llvm_ir_module(dylib, tsm)
+
+ func_ptr = lljit.lookup("__evaluate_wrapper")
+ func = Proc(T*, Nil).new(func_ptr, Pointer(Void).null)
+ func.call(pointerof(result))
+ {% end %}
+
+ result
+ end
+
def codegen(node, single_module = false, debug = Debug::Default,
frame_pointers = FramePointers::Auto)
visitor = CodeGenVisitor.new self, node, single_module: single_module,
@@ -195,11 +270,11 @@ module Crystal
def initialize(@program : Program, @node : ASTNode,
@single_module : Bool = false,
@debug = Debug::Default,
- @frame_pointers : FramePointers = :auto)
+ @frame_pointers : FramePointers = :auto,
+ @llvm_context : LLVM::Context = LLVM::Context.new)
@abi = @program.target_machine.abi
- @llvm_context = LLVM::Context.new
# LLVM::Context.register(@llvm_context, "main")
- @llvm_mod = @llvm_context.new_module("main_module")
+ @llvm_mod = configure_module(@llvm_context.new_module("main_module"))
@main_mod = @llvm_mod
@main_llvm_context = @main_mod.context
@llvm_typer = LLVMTyper.new(@program, @llvm_context)
@@ -210,7 +285,7 @@ module Crystal
@main = @llvm_mod.functions.add(MAIN_NAME, main_type)
@fun_types = { {@llvm_mod, MAIN_NAME} => main_type }
- if @program.has_flag? "windows"
+ if @program.has_flag?("msvc")
@personality_name = "__CxxFrameHandler3"
@main.personality_function = windows_personality_fun.func
else
@@ -270,8 +345,6 @@ module Crystal
@unused_fun_defs = [] of FunDef
@proc_counts = Hash(String, Int32).new(0)
- @llvm_mod.data_layout = self.data_layout
-
# We need to define __crystal_malloc and __crystal_realloc as soon as possible,
# to avoid some memory being allocated with plain malloc.
codegen_well_known_functions @node
@@ -292,6 +365,30 @@ module Crystal
getter llvm_context
+ def configure_module(llvm_mod)
+ llvm_mod.data_layout = @program.target_machine.data_layout
+
+ # enable branch authentication instructions (BTI)
+ if @program.has_flag?("aarch64")
+ if @program.has_flag?("branch-protection=bti")
+ llvm_mod.add_flag(:override, "branch-target-enforcement", 1)
+ end
+ end
+
+ # enable control flow enforcement protection (CET): IBT and/or SHSTK
+ if @program.has_flag?("x86_64") || @program.has_flag?("i386")
+ if @program.has_flag?("cf-protection=branch") || @program.has_flag?("cf-protection=full")
+ llvm_mod.add_flag(:override, "cf-protection-branch", 1)
+ end
+
+ if @program.has_flag?("cf-protection=return") || @program.has_flag?("cf-protection=full")
+ llvm_mod.add_flag(:override, "cf-protection-return", 1)
+ end
+ end
+
+ llvm_mod
+ end
+
def new_builder(llvm_context)
wrap_builder(llvm_context.new_builder)
end
@@ -344,10 +441,6 @@ module Crystal
global.initializer = llvm_element_type.const_array(llvm_elements)
end
- def data_layout
- @program.target_machine.data_layout
- end
-
class CodegenWellKnownFunctions < Visitor
@codegen : CodeGenVisitor
@@ -2413,7 +2506,7 @@ module Crystal
end
def self.safe_mangling(program, name)
- if program.has_flag?("windows")
+ if program.has_flag?("msvc")
String.build do |str|
name.each_char do |char|
if char.ascii_alphanumeric? || char == '_'
diff --git a/src/compiler/crystal/codegen/debug.cr b/src/compiler/crystal/codegen/debug.cr
index 72555d074bb0..870506377f7a 100644
--- a/src/compiler/crystal/codegen/debug.cr
+++ b/src/compiler/crystal/codegen/debug.cr
@@ -40,19 +40,15 @@ module Crystal
def push_debug_info_metadata(mod)
di_builder(mod).end
- if @program.has_flag?("windows")
+ if @program.has_flag?("msvc")
# Windows uses CodeView instead of DWARF
- mod.add_flag(
- LibLLVM::ModuleFlagBehavior::Warning,
- "CodeView",
- mod.context.int32.const_int(1)
- )
+ mod.add_flag(LibLLVM::ModuleFlagBehavior::Warning, "CodeView", 1)
end
mod.add_flag(
LibLLVM::ModuleFlagBehavior::Warning,
"Debug Info Version",
- mod.context.int32.const_int(LLVM::DEBUG_METADATA_VERSION)
+ LLVM::DEBUG_METADATA_VERSION
)
end
@@ -367,6 +363,16 @@ module Crystal
old_debug_location = @current_debug_location
set_current_debug_location location
if builder.current_debug_location != llvm_nil && (ptr = alloca)
+ # FIXME: When debug records are used instead of debug intrinsics, it
+ # seems inserting them into an empty BasicBlock will instead place them
+ # in a totally different (next?) function where the variable doesn't
+ # exist, leading to a "function-local metadata used in wrong function"
+ # validation error. This might happen when e.g. all variables inside a
+ # block are closured. Ideally every debug record should immediately
+ # follow the variable it declares.
+ {% unless LibLLVM::IS_LT_190 %}
+ call(do_nothing_fun) if block.instructions.empty?
+ {% end %}
di_builder.insert_declare_at_end(ptr, var, expr, builder.current_debug_location_metadata, block)
set_current_debug_location old_debug_location
true
@@ -376,6 +382,12 @@ module Crystal
end
end
+ private def do_nothing_fun
+ fetch_typed_fun(@llvm_mod, "llvm.donothing") do
+ LLVM::Type.function([] of LLVM::Type, @llvm_context.void)
+ end
+ end
+
# Emit debug info for toplevel variables. Used for the main module and all
# required files.
def emit_vars_debug_info(vars)
diff --git a/src/compiler/crystal/codegen/exception.cr b/src/compiler/crystal/codegen/exception.cr
index 9a33e1337550..944ac99fce7d 100644
--- a/src/compiler/crystal/codegen/exception.cr
+++ b/src/compiler/crystal/codegen/exception.cr
@@ -60,9 +60,9 @@ class Crystal::CodeGenVisitor
#
# Note we codegen the ensure body three times! In practice this isn't a big deal, since ensure bodies are typically small.
- windows = @program.has_flag? "windows"
+ msvc = @program.has_flag?("msvc")
- context.fun.personality_function = windows_personality_fun.func if windows
+ context.fun.personality_function = windows_personality_fun.func if msvc
# This is the block which is entered when the body raises an exception
rescue_block = new_block "rescue"
@@ -109,7 +109,7 @@ class Crystal::CodeGenVisitor
old_catch_pad = @catch_pad
- if windows
+ if msvc
# Windows structured exception handling must enter a catch_switch instruction
# which decides which catch body block to enter. Crystal only ever generates one catch body
# which is used for all exceptions. For more information on how structured exception handling works in LLVM,
@@ -138,7 +138,8 @@ class Crystal::CodeGenVisitor
caught_exception = load exception_llvm_type, caught_exception_ptr
exception_type_id = type_id(caught_exception, exception_type)
else
- # Unwind exception handling code - used on non-windows platforms - is a lot simpler.
+ # Unwind exception handling code - used on non-MSVC platforms (essentially the Itanium
+ # C++ ABI) - is a lot simpler.
# First we generate the landing pad instruction, this returns a tuple of the libunwind
# exception object and the type ID of the exception. This tuple is set up in the crystal
# personality function in raise.cr
@@ -188,7 +189,7 @@ class Crystal::CodeGenVisitor
# If the rescue restriction matches, codegen the rescue block.
position_at_end this_rescue_block
- # On windows, we are "inside" the catchpad block. It's difficult to track when to catch_ret when
+ # On MSVC, we are "inside" the catchpad block. It's difficult to track when to catch_ret when
# codegenning the entire rescue body, so we catch_ret early and execute the rescue bodies "outside" the
# rescue block.
if catch_pad = @catch_pad
@@ -248,7 +249,7 @@ class Crystal::CodeGenVisitor
# Codegen catchswitch+pad or landing pad as described above.
# This code is simpler because we never need to extract the exception type
- if windows
+ if msvc
rescue_ensure_body = new_block "rescue_ensure_body"
catch_switch = builder.catch_switch(old_catch_pad || LLVM::Value.null, @rescue_block || LLVM::BasicBlock.null, 1)
builder.add_handler catch_switch, rescue_ensure_body
@@ -283,8 +284,8 @@ class Crystal::CodeGenVisitor
end
def codegen_re_raise(node, unwind_ex_obj)
- if @program.has_flag? "windows"
- # On windows we can re-raise by calling _CxxThrowException with two null arguments
+ if @program.has_flag?("msvc")
+ # On the MSVC C++ ABI we can re-raise by calling _CxxThrowException with two null arguments
call windows_throw_fun, [llvm_context.void_pointer.null, llvm_context.void_pointer.null]
unreachable
else
diff --git a/src/compiler/crystal/codegen/fun.cr b/src/compiler/crystal/codegen/fun.cr
index 5b7c9b224c83..c56bde6e5c2a 100644
--- a/src/compiler/crystal/codegen/fun.cr
+++ b/src/compiler/crystal/codegen/fun.cr
@@ -236,17 +236,22 @@ class Crystal::CodeGenVisitor
# Check if this def must use the C calling convention and the return
# value must be either casted or passed by sret
if target_def.c_calling_convention? && target_def.abi_info?
+ return_type = target_def.body.type
+ if return_type.proc?
+ @last = check_proc_is_not_closure(@last, return_type)
+ end
+
abi_info = abi_info(target_def)
- ret_type = abi_info.return_type
- if cast = ret_type.cast
+ abi_ret_type = abi_info.return_type
+ if cast = abi_ret_type.cast
casted_last = pointer_cast @last, cast.pointer
last = load cast, casted_last
ret last
return
end
- if (attr = ret_type.attr) && attr == LLVM::Attribute::StructRet
- store load(llvm_type(target_def.body.type), @last), context.fun.params[0]
+ if (attr = abi_ret_type.attr) && attr == LLVM::Attribute::StructRet
+ store load(llvm_type(return_type), @last), context.fun.params[0]
ret
return
end
@@ -621,8 +626,7 @@ class Crystal::CodeGenVisitor
# LLVM::Context.register(llvm_context, type_name)
llvm_typer = LLVMTyper.new(@program, llvm_context)
- llvm_mod = llvm_context.new_module(type_name)
- llvm_mod.data_layout = self.data_layout
+ llvm_mod = configure_module(llvm_context.new_module(type_name))
llvm_builder = new_builder(llvm_context)
define_symbol_table llvm_mod, llvm_typer
diff --git a/src/compiler/crystal/codegen/link.cr b/src/compiler/crystal/codegen/link.cr
index 3601aa0fd870..b2b827916cbf 100644
--- a/src/compiler/crystal/codegen/link.cr
+++ b/src/compiler/crystal/codegen/link.cr
@@ -120,18 +120,18 @@ module Crystal
end
class Program
- def lib_flags
- has_flag?("windows") ? lib_flags_windows : lib_flags_posix
+ def lib_flags(cross_compiling : Bool = false)
+ has_flag?("msvc") ? lib_flags_windows(cross_compiling) : lib_flags_posix(cross_compiling)
end
- private def lib_flags_windows
+ private def lib_flags_windows(cross_compiling)
flags = [] of String
# Add CRYSTAL_LIBRARY_PATH locations, so the linker preferentially
# searches user-given library paths.
if has_flag?("msvc")
CrystalLibraryPath.paths.each do |path|
- flags << Process.quote_windows("/LIBPATH:#{path}")
+ flags << quote_flag("/LIBPATH:#{path}", cross_compiling)
end
end
@@ -141,14 +141,14 @@ module Crystal
end
if libname = ann.lib
- flags << Process.quote_windows("#{libname}.lib")
+ flags << quote_flag("#{libname}.lib", cross_compiling)
end
end
flags.join(" ")
end
- private def lib_flags_posix
+ private def lib_flags_posix(cross_compiling)
flags = [] of String
static_build = has_flag?("static")
@@ -158,7 +158,7 @@ module Crystal
# Add CRYSTAL_LIBRARY_PATH locations, so the linker preferentially
# searches user-given library paths.
CrystalLibraryPath.paths.each do |path|
- flags << Process.quote_posix("-L#{path}")
+ flags << quote_flag("-L#{path}", cross_compiling)
end
link_annotations.reverse_each do |ann|
@@ -173,17 +173,25 @@ module Crystal
elsif (lib_name = ann.lib) && (flag = pkg_config(lib_name, static_build))
flags << flag
elsif (lib_name = ann.lib)
- flags << Process.quote_posix("-l#{lib_name}")
+ flags << quote_flag("-l#{lib_name}", cross_compiling)
end
if framework = ann.framework
- flags << "-framework" << Process.quote_posix(framework)
+ flags << "-framework" << quote_flag(framework, cross_compiling)
end
end
flags.join(" ")
end
+ private def quote_flag(flag, cross_compiling)
+ if cross_compiling
+ has_flag?("windows") ? Process.quote_windows(flag) : Process.quote_posix(flag)
+ else
+ Process.quote(flag)
+ end
+ end
+
# Searches among CRYSTAL_LIBRARY_PATH, the compiler's directory, and PATH
# for every DLL specified in the used `@[Link]` annotations. Yields the
# absolute path and `true` if found, the base name and `false` if not found.
@@ -292,8 +300,6 @@ module Crystal
private def add_link_annotations(types, annotations)
types.try &.each_value do |type|
- next if type.is_a?(AliasType) || type.is_a?(TypeDefType)
-
if type.is_a?(LibType) && type.used? && (link_annotations = type.link_annotations)
annotations.concat link_annotations
end
diff --git a/src/compiler/crystal/codegen/unions.cr b/src/compiler/crystal/codegen/unions.cr
index b2b63a17c5ab..fdf1d81a4c95 100644
--- a/src/compiler/crystal/codegen/unions.cr
+++ b/src/compiler/crystal/codegen/unions.cr
@@ -81,16 +81,19 @@ module Crystal
def store_bool_in_union(target_type, union_pointer, value)
struct_type = llvm_type(target_type)
+ union_value_type = struct_type.struct_element_types[1]
store type_id(value, @program.bool), union_type_id(struct_type, union_pointer)
# To store a boolean in a union
- # we sign-extend it to the size in bits of the union
- union_size = @llvm_typer.size_of(struct_type.struct_element_types[1])
+ # we zero-extend it to the size in bits of the union
+ union_size = @llvm_typer.size_of(union_value_type)
int_type = llvm_context.int((union_size * 8).to_i32)
bool_as_extended_int = builder.zext(value, int_type)
casted_value_ptr = pointer_cast(union_value(struct_type, union_pointer), int_type.pointer)
- store bool_as_extended_int, casted_value_ptr
+ inst = store bool_as_extended_int, casted_value_ptr
+ set_alignment(inst, @llvm_typer.align_of(union_value_type))
+ inst
end
def store_nil_in_union(target_type, union_pointer)
diff --git a/src/compiler/crystal/command.cr b/src/compiler/crystal/command.cr
index f8ece87e3d4b..571c965352e0 100644
--- a/src/compiler/crystal/command.cr
+++ b/src/compiler/crystal/command.cr
@@ -40,14 +40,14 @@ class Crystal::Command
Tool:
context show context for given location
+ dependencies show file dependency tree
expand show macro expansion for given location
flags print all macro `flag?` values
format format project, directories and/or files
hierarchy show type hierarchy
- dependencies show file dependency tree
implementations show implementations for given call in location
- unreachable show methods that are never called
types show type of main variables
+ unreachable show methods that are never called
--help, -h show this help
USAGE
@@ -130,6 +130,9 @@ class Crystal::Command
else
if command.ends_with?(".cr")
error "file '#{command}' does not exist"
+ elsif external_command = Process.find_executable("crystal-#{command}")
+ options.shift
+ Process.exec(external_command, options, env: {"CRYSTAL" => Process.executable_path})
else
error "unknown command: #{command}"
end
diff --git a/src/compiler/crystal/command/format.cr b/src/compiler/crystal/command/format.cr
index ed63a26796f9..9d0431b3e3bb 100644
--- a/src/compiler/crystal/command/format.cr
+++ b/src/compiler/crystal/command/format.cr
@@ -78,7 +78,7 @@ class Crystal::Command
@show_backtrace : Bool = false,
@color : Bool = true,
# stdio is injectable for testing
- @stdin : IO = STDIN, @stdout : IO = STDOUT, @stderr : IO = STDERR
+ @stdin : IO = STDIN, @stdout : IO = STDOUT, @stderr : IO = STDERR,
)
@format_stdin = files.size == 1 && files[0] == "-"
diff --git a/src/compiler/crystal/compiler.cr b/src/compiler/crystal/compiler.cr
index 38880ee9ed64..878a1ae4896a 100644
--- a/src/compiler/crystal/compiler.cr
+++ b/src/compiler/crystal/compiler.cr
@@ -5,6 +5,9 @@ require "crystal/digest/md5"
{% if flag?(:msvc) %}
require "./loader"
{% end %}
+{% if flag?(:preview_mt) %}
+ require "wait_group"
+{% end %}
module Crystal
@[Flags]
@@ -80,7 +83,13 @@ module Crystal
property? no_codegen = false
# Maximum number of LLVM modules that are compiled in parallel
- property n_threads : Int32 = {% if flag?(:preview_mt) || flag?(:win32) %} 1 {% else %} 8 {% end %}
+ property n_threads : Int32 = {% if flag?(:preview_mt) %}
+ ENV["CRYSTAL_WORKERS"]?.try(&.to_i?) || 4
+ {% elsif flag?(:win32) %}
+ 1
+ {% else %}
+ 8
+ {% end %}
# Default prelude file to use. This ends up adding a
# `require "prelude"` (or whatever name is set here) to
@@ -328,6 +337,12 @@ module Crystal
CompilationUnit.new(self, program, type_name, llvm_mod, output_dir, bc_flags_changed)
end
+ {% if LibLLVM::IS_LT_170 %}
+ # initialize the legacy pass manager once in the main thread/process
+ # before we start codegen in threads (MT) or processes (fork)
+ init_llvm_legacy_pass_manager unless optimization_mode.o0?
+ {% end %}
+
if @cross_compile
cross_compile program, units, output_filename
else
@@ -339,7 +354,7 @@ module Crystal
run_dsymutil(output_filename) unless debug.none?
{% end %}
- {% if flag?(:windows) %}
+ {% if flag?(:msvc) %}
copy_dlls(program, output_filename) unless static?
{% end %}
end
@@ -391,7 +406,7 @@ module Crystal
llvm_mod = unit.llvm_mod
@progress_tracker.stage("Codegen (bc+obj)") do
- optimize llvm_mod unless @optimization_mode.o0?
+ optimize llvm_mod, target_machine unless @optimization_mode.o0?
unit.emit(@emit_targets, emit_base_filename || output_filename)
@@ -409,9 +424,8 @@ module Crystal
private def linker_command(program : Program, object_names, output_filename, output_dir, expand = false)
if program.has_flag? "msvc"
- lib_flags = program.lib_flags
- # Execute and expand `subcommands`.
- lib_flags = lib_flags.gsub(/`(.*?)`/) { `#{$1}` } if expand
+ lib_flags = program.lib_flags(@cross_compile)
+ lib_flags = expand_lib_flags(lib_flags) if expand
object_arg = Process.quote_windows(object_names)
output_arg = Process.quote_windows("/Fe#{output_filename}")
@@ -455,15 +469,71 @@ module Crystal
{linker, cmd, nil}
elsif program.has_flag? "wasm32"
link_flags = @link_flags || ""
- {"wasm-ld", %(wasm-ld "${@}" -o #{Process.quote_posix(output_filename)} #{link_flags} -lc #{program.lib_flags}), object_names}
+ {"wasm-ld", %(wasm-ld "${@}" -o #{Process.quote_posix(output_filename)} #{link_flags} -lc #{program.lib_flags(@cross_compile)}), object_names}
elsif program.has_flag? "avr"
link_flags = @link_flags || ""
link_flags += " --target=avr-unknown-unknown -mmcu=#{@mcpu} -Wl,--gc-sections"
- {DEFAULT_LINKER, %(#{DEFAULT_LINKER} "${@}" -o #{Process.quote_posix(output_filename)} #{link_flags} #{program.lib_flags}), object_names}
+ {DEFAULT_LINKER, %(#{DEFAULT_LINKER} "${@}" -o #{Process.quote_posix(output_filename)} #{link_flags} #{program.lib_flags(@cross_compile)}), object_names}
+ elsif program.has_flag?("win32") && program.has_flag?("gnu")
+ link_flags = @link_flags || ""
+ link_flags += " -Wl,--stack,0x800000"
+ lib_flags = program.lib_flags(@cross_compile)
+ lib_flags = expand_lib_flags(lib_flags) if expand
+ cmd = %(#{DEFAULT_LINKER} #{Process.quote_windows(object_names)} -o #{Process.quote_windows(output_filename)} #{link_flags} #{lib_flags}).gsub('\n', ' ')
+
+ if cmd.size > 32000
+ # The command line would be too big, pass the args through a file instead.
+ # GCC response file does not interpret those args as shell-escaped
+ # arguments, we must rebuild the whole command line
+ args_filename = "#{output_dir}/linker_args.txt"
+ File.open(args_filename, "w") do |f|
+ object_names.each do |object_name|
+ f << object_name.gsub(GCC_RESPONSE_FILE_TR) << ' '
+ end
+ f << "-o " << output_filename.gsub(GCC_RESPONSE_FILE_TR) << ' '
+ f << link_flags << ' ' << lib_flags
+ end
+ cmd = "#{DEFAULT_LINKER} #{Process.quote_windows("@" + args_filename)}"
+ end
+
+ {DEFAULT_LINKER, cmd, nil}
else
link_flags = @link_flags || ""
link_flags += " -rdynamic"
- {DEFAULT_LINKER, %(#{DEFAULT_LINKER} "${@}" -o #{Process.quote_posix(output_filename)} #{link_flags} #{program.lib_flags}), object_names}
+
+ if program.has_flag?("freebsd") || program.has_flag?("openbsd")
+ # pkgs are installed to usr/local/lib but it's not in LIBRARY_PATH by
+ # default; we declare it to ease linking on these platforms:
+ link_flags += " -L/usr/local/lib"
+ end
+
+ {DEFAULT_LINKER, %(#{DEFAULT_LINKER} "${@}" -o #{Process.quote_posix(output_filename)} #{link_flags} #{program.lib_flags(@cross_compile)}), object_names}
+ end
+ end
+
+ private GCC_RESPONSE_FILE_TR = {
+ " ": %q(\ ),
+ "'": %q(\'),
+ "\"": %q(\"),
+ "\\": "\\\\",
+ }
+
+ private def expand_lib_flags(lib_flags)
+ lib_flags.gsub(/`(.*?)`/) do
+ command = $1
+ begin
+ error_io = IO::Memory.new
+ output = Process.run(command, shell: true, output: :pipe, error: error_io) do |process|
+ process.output.gets_to_end
+ end
+ unless $?.success?
+ error_io.rewind
+ error "Error executing subcommand for linker flags: #{command.inspect}: #{error_io}"
+ end
+ output.chomp
+ rescue exc
+ error "Error executing subcommand for linker flags: #{command.inspect}: #{exc}"
+ end
end
end
@@ -512,7 +582,8 @@ module Crystal
private def parallel_codegen(units, n_threads)
{% if flag?(:preview_mt) %}
- raise "Cannot fork compiler in multithread mode."
+ raise "LLVM isn't multithreaded and cannot fork compiler in multithread mode." unless LLVM.multithreaded?
+ mt_codegen(units, n_threads)
{% elsif LibC.has_method?("fork") %}
fork_codegen(units, n_threads)
{% else %}
@@ -520,6 +591,39 @@ module Crystal
{% end %}
end
+ private def mt_codegen(units, n_threads)
+ channel = Channel(CompilationUnit).new(n_threads * 2)
+ wg = WaitGroup.new
+ mutex = Mutex.new
+
+ n_threads.times do
+ wg.spawn do
+ while unit = channel.receive?
+ unit.compile(isolate_context: true)
+ mutex.synchronize { @progress_tracker.stage_progress += 1 }
+ end
+ end
+ end
+
+ units.each do |unit|
+ # We generate the bitcode in the main thread because LLVM contexts
+ # must be unique per compilation unit, but we share different contexts
+ # across many modules (or rely on the global context); trying to
+ # codegen in parallel would segfault!
+ #
+ # Luckily generating the bitcode is quick and once the bitcode is
+ # generated we don't need the global LLVM contexts anymore but can
+ # parse the bitcode in an isolated context and we can parallelize the
+ # slowest part: the optimization pass & compiling the object file.
+ unit.generate_bitcode
+
+ channel.send(unit)
+ end
+ channel.close
+
+ wg.wait
+ end
+
private def fork_codegen(units, n_threads)
workers = fork_workers(n_threads) do |input, output|
while i = input.gets(chomp: true).presence
@@ -603,7 +707,7 @@ module Crystal
end
end
- private def fork_workers(n_threads)
+ private def fork_workers(n_threads, &)
workers = [] of {Int32, IO::FileDescriptor, IO::FileDescriptor}
n_threads.times do
@@ -660,9 +764,10 @@ module Crystal
puts
puts "Codegen (bc+obj):"
- if units.size == reused
+ case reused
+ when units.size
puts " - all previous .o files were reused"
- elsif reused == 0
+ when .zero?
puts " - no previous .o files were reused"
else
puts " - #{reused}/#{units.size} .o files were reused"
@@ -689,61 +794,52 @@ module Crystal
end
{% if LibLLVM::IS_LT_170 %}
+ property! pass_manager_builder : LLVM::PassManagerBuilder
+
+ private def init_llvm_legacy_pass_manager
+ registry = LLVM::PassRegistry.instance
+ registry.initialize_all
+
+ builder = LLVM::PassManagerBuilder.new
+ builder.size_level = 0
+
+ case optimization_mode
+ in .o3?
+ builder.opt_level = 3
+ builder.use_inliner_with_threshold = 275
+ in .o2?
+ builder.opt_level = 2
+ builder.use_inliner_with_threshold = 275
+ in .o1?
+ builder.opt_level = 1
+ builder.use_inliner_with_threshold = 150
+ in .o0?
+ # default behaviour, no optimizations
+ in .os?
+ builder.opt_level = 2
+ builder.size_level = 1
+ builder.use_inliner_with_threshold = 50
+ in .oz?
+ builder.opt_level = 2
+ builder.size_level = 2
+ builder.use_inliner_with_threshold = 5
+ end
+
+ @pass_manager_builder = builder
+ end
+
private def optimize_with_pass_manager(llvm_mod)
fun_pass_manager = llvm_mod.new_function_pass_manager
pass_manager_builder.populate fun_pass_manager
fun_pass_manager.run llvm_mod
- module_pass_manager.run llvm_mod
- end
-
- @module_pass_manager : LLVM::ModulePassManager?
-
- private def module_pass_manager
- @module_pass_manager ||= begin
- mod_pass_manager = LLVM::ModulePassManager.new
- pass_manager_builder.populate mod_pass_manager
- mod_pass_manager
- end
- end
-
- @pass_manager_builder : LLVM::PassManagerBuilder?
-
- private def pass_manager_builder
- @pass_manager_builder ||= begin
- registry = LLVM::PassRegistry.instance
- registry.initialize_all
-
- builder = LLVM::PassManagerBuilder.new
- builder.size_level = 0
-
- case optimization_mode
- in .o3?
- builder.opt_level = 3
- builder.use_inliner_with_threshold = 275
- in .o2?
- builder.opt_level = 2
- builder.use_inliner_with_threshold = 275
- in .o1?
- builder.opt_level = 1
- builder.use_inliner_with_threshold = 150
- in .o0?
- # default behaviour, no optimizations
- in .os?
- builder.opt_level = 2
- builder.size_level = 1
- builder.use_inliner_with_threshold = 50
- in .oz?
- builder.opt_level = 2
- builder.size_level = 2
- builder.use_inliner_with_threshold = 5
- end
- builder
- end
+ module_pass_manager = LLVM::ModulePassManager.new
+ pass_manager_builder.populate module_pass_manager
+ module_pass_manager.run llvm_mod
end
{% end %}
- protected def optimize(llvm_mod)
+ protected def optimize(llvm_mod, target_machine)
{% if LibLLVM::IS_LT_130 %}
optimize_with_pass_manager(llvm_mod)
{% else %}
@@ -819,6 +915,9 @@ module Crystal
getter llvm_mod
property? reused_previous_compilation = false
getter object_extension : String
+ @memory_buffer : LLVM::MemoryBuffer?
+ @object_name : String?
+ @bc_name : String?
def initialize(@compiler : Compiler, program : Program, @name : String,
@llvm_mod : LLVM::Module, @output_dir : String, @bc_flags_changed : Bool)
@@ -848,40 +947,44 @@ module Crystal
@object_extension = compiler.codegen_target.object_extension
end
- def compile
- compile_to_object
+ def generate_bitcode
+ @memory_buffer ||= llvm_mod.write_bitcode_to_memory_buffer
end
- private def compile_to_object
- bc_name = self.bc_name
- object_name = self.object_name
- temporary_object_name = self.temporary_object_name
+ # To compile a file we first generate a `.bc` file and then create an
+ # object file from it. These `.bc` files are stored in the cache
+ # directory.
+ #
+ # On a next compilation of the same project, and if the compile flags
+ # didn't change (a combination of the target triple, mcpu and link flags,
+ # amongst others), we check if the new `.bc` file is exactly the same as
+ # the old one. In that case the `.o` file will also be the same, so we
+ # simply reuse the old one. Generating an `.o` file is what takes most
+ # time.
+ #
+ # However, instead of directly generating the final `.o` file from the
+ # `.bc` file, we generate it to a temporary name (`.o.tmp`) and then we
+ # rename that file to `.o`. We do this because the compiler could be
+ # interrupted while the `.o` file is being generated, leading to a
+ # corrupted file that later would cause compilation issues. Moving a file
+ # is an atomic operation so no corrupted `.o` file should be generated.
+ def compile(isolate_context = false)
+ if must_compile?
+ isolate_module_context if isolate_context
+ update_bitcode_cache
+ compile_to_object
+ else
+ @reused_previous_compilation = true
+ end
+ dump_llvm_ir
+ end
+
+ private def must_compile?
+ memory_buffer = generate_bitcode
- # To compile a file we first generate a `.bc` file and then
- # create an object file from it. These `.bc` files are stored
- # in the cache directory.
- #
- # On a next compilation of the same project, and if the compile
- # flags didn't change (a combination of the target triple, mcpu
- # and link flags, amongst others), we check if the new
- # `.bc` file is exactly the same as the old one. In that case
- # the `.o` file will also be the same, so we simply reuse the
- # old one. Generating an `.o` file is what takes most time.
- #
- # However, instead of directly generating the final `.o` file
- # from the `.bc` file, we generate it to a temporary name (`.o.tmp`)
- # and then we rename that file to `.o`. We do this because the compiler
- # could be interrupted while the `.o` file is being generated, leading
- # to a corrupted file that later would cause compilation issues.
- # Moving a file is an atomic operation so no corrupted `.o` file should
- # be generated.
-
- must_compile = true
can_reuse_previous_compilation =
compiler.emit_targets.none? && !@bc_flags_changed && File.exists?(bc_name) && File.exists?(object_name)
- memory_buffer = llvm_mod.write_bitcode_to_memory_buffer
-
if can_reuse_previous_compilation
memory_io = IO::Memory.new(memory_buffer.to_slice)
changed = File.open(bc_name) { |bc_file| !IO.same_content?(bc_file, memory_io) }
@@ -889,32 +992,39 @@ module Crystal
# If the user cancelled a previous compilation
# it might be that the .o file is empty
if !changed && File.size(object_name) > 0
- must_compile = false
memory_buffer.dispose
- memory_buffer = nil
+ return false
else
# We need to compile, so we'll write the memory buffer to file
end
end
- # If there's a memory buffer, it means we must create a .o from it
- if memory_buffer
- # Delete existing .o file. It cannot be used anymore.
- File.delete?(object_name)
- # Create the .bc file (for next compilations)
- File.write(bc_name, memory_buffer.to_slice)
- memory_buffer.dispose
- end
+ true
+ end
- if must_compile
- compiler.optimize llvm_mod unless compiler.optimization_mode.o0?
- compiler.target_machine.emit_obj_to_file llvm_mod, temporary_object_name
- File.rename(temporary_object_name, object_name)
- else
- @reused_previous_compilation = true
- end
+ # Parse the previously generated bitcode into the LLVM module using a
+ # dedicated context, so we can safely optimize & compile the module in
+ # multiple threads (llvm contexts can't be shared across threads).
+ private def isolate_module_context
+ @llvm_mod = LLVM::Module.parse(@memory_buffer.not_nil!, LLVM::Context.new)
+ end
- dump_llvm_ir
+ private def update_bitcode_cache
+ return unless memory_buffer = @memory_buffer
+
+ # Delete existing .o file. It cannot be used anymore.
+ File.delete?(object_name)
+ # Create the .bc file (for next compilations)
+ File.write(bc_name, memory_buffer.to_slice)
+ memory_buffer.dispose
+ end
+
+ private def compile_to_object
+ temporary_object_name = self.temporary_object_name
+ target_machine = compiler.create_target_machine
+ compiler.optimize llvm_mod, target_machine unless compiler.optimization_mode.o0?
+ target_machine.emit_obj_to_file llvm_mod, temporary_object_name
+ File.rename(temporary_object_name, object_name)
end
private def dump_llvm_ir
diff --git a/src/compiler/crystal/ffi/lib_ffi.cr b/src/compiler/crystal/ffi/lib_ffi.cr
index 97163c989ee5..2d08cf4e18dd 100644
--- a/src/compiler/crystal/ffi/lib_ffi.cr
+++ b/src/compiler/crystal/ffi/lib_ffi.cr
@@ -147,7 +147,7 @@ module Crystal
abi : ABI,
nargs : LibC::UInt,
rtype : Type*,
- atypes : Type**
+ atypes : Type**,
) : Status
fun prep_cif_var = ffi_prep_cif_var(
@@ -156,7 +156,7 @@ module Crystal
nfixedargs : LibC::UInt,
varntotalargs : LibC::UInt,
rtype : Type*,
- atypes : Type**
+ atypes : Type**,
) : Status
@[Raises]
@@ -164,7 +164,7 @@ module Crystal
cif : Cif*,
fn : Void*,
rvalue : Void*,
- avalue : Void**
+ avalue : Void**,
) : Void
fun closure_alloc = ffi_closure_alloc(size : LibC::SizeT, code : Void**) : Closure*
@@ -174,7 +174,7 @@ module Crystal
cif : Cif*,
fun : ClosureFun,
user_data : Void*,
- code_loc : Void*
+ code_loc : Void*,
) : Status
end
end
diff --git a/src/compiler/crystal/interpreter/closure_context.cr b/src/compiler/crystal/interpreter/closure_context.cr
index 5df87d884363..4e633ae104b4 100644
--- a/src/compiler/crystal/interpreter/closure_context.cr
+++ b/src/compiler/crystal/interpreter/closure_context.cr
@@ -20,7 +20,7 @@ class Crystal::Repl
@vars : Hash(String, {Int32, Type}),
@self_type : Type?,
@parent : ClosureContext?,
- @bytesize : Int32
+ @bytesize : Int32,
)
end
end
diff --git a/src/compiler/crystal/interpreter/compiled_def.cr b/src/compiler/crystal/interpreter/compiled_def.cr
index 8bfc3252fcb9..f9d3d48088bd 100644
--- a/src/compiler/crystal/interpreter/compiled_def.cr
+++ b/src/compiler/crystal/interpreter/compiled_def.cr
@@ -26,7 +26,7 @@ class Crystal::Repl
@owner : Type,
@args_bytesize : Int32,
@instructions : CompiledInstructions = CompiledInstructions.new,
- @local_vars = LocalVars.new(context)
+ @local_vars = LocalVars.new(context),
)
end
end
diff --git a/src/compiler/crystal/interpreter/compiler.cr b/src/compiler/crystal/interpreter/compiler.cr
index 50024d8b65e3..ea278876c44f 100644
--- a/src/compiler/crystal/interpreter/compiler.cr
+++ b/src/compiler/crystal/interpreter/compiler.cr
@@ -103,7 +103,7 @@ class Crystal::Repl::Compiler < Crystal::Visitor
@instructions : CompiledInstructions = CompiledInstructions.new,
scope : Type? = nil,
@def = nil,
- @top_level = true
+ @top_level = true,
)
@scope = scope || @context.program
@@ -138,7 +138,7 @@ class Crystal::Repl::Compiler < Crystal::Visitor
context : Context,
compiled_def : CompiledDef,
top_level : Bool,
- scope : Type = compiled_def.owner
+ scope : Type = compiled_def.owner,
)
new(
context: context,
diff --git a/src/compiler/crystal/interpreter/context.cr b/src/compiler/crystal/interpreter/context.cr
index 50e36a3ff8b7..c2c1537e002d 100644
--- a/src/compiler/crystal/interpreter/context.cr
+++ b/src/compiler/crystal/interpreter/context.cr
@@ -393,14 +393,16 @@ class Crystal::Repl::Context
getter(loader : Loader) {
lib_flags = program.lib_flags
# Execute and expand `subcommands`.
- lib_flags = lib_flags.gsub(/`(.*?)`/) { `#{$1}` }
+ lib_flags = lib_flags.gsub(/`(.*?)`/) { `#{$1}`.chomp }
args = Process.parse_arguments(lib_flags)
# FIXME: Part 1: This is a workaround for initial integration of the interpreter:
# The loader can't handle the static libgc.a usually shipped with crystal and loading as a shared library conflicts
# with the compiler's own GC.
- # (MSVC doesn't seem to have this issue)
- args.delete("-lgc")
+ # (Windows doesn't seem to have this issue)
+ unless program.has_flag?("win32") && program.has_flag?("gnu")
+ args.delete("-lgc")
+ end
# recreate the MSVC developer prompt environment, similar to how compiled
# code does it in `Compiler#linker_command`
diff --git a/src/compiler/crystal/interpreter/instructions.cr b/src/compiler/crystal/interpreter/instructions.cr
index 8fae94f5ee62..23428df03b90 100644
--- a/src/compiler/crystal/interpreter/instructions.cr
+++ b/src/compiler/crystal/interpreter/instructions.cr
@@ -1276,6 +1276,16 @@ require "./repl"
ptr
end,
},
+ reset_class: {
+ operands: [size : Int32, type_id : Int32],
+ pop_values: [pointer : Pointer(UInt8)],
+ push: true,
+ code: begin
+ pointer.clear(size)
+ pointer.as(Int32*).value = type_id
+ pointer
+ end,
+ },
put_metaclass: {
operands: [size : Int32, union_type : Bool],
push: true,
@@ -1299,7 +1309,7 @@ require "./repl"
code: begin
tmp_stack = stack
stack_grow_by(union_size - from_size)
- (tmp_stack - from_size).copy_to(tmp_stack - from_size + type_id_bytesize, from_size)
+ (tmp_stack - from_size).move_to(tmp_stack - from_size + type_id_bytesize, from_size)
(tmp_stack - from_size).as(Int64*).value = type_id.to_i64!
end,
disassemble: {
@@ -1309,6 +1319,8 @@ require "./repl"
put_reference_type_in_union: {
operands: [union_size : Int32],
code: begin
+ # `copy_to` here is valid only when `from_size <= type_id_bytesize`,
+ # which is always true
from_size = sizeof(Pointer(UInt8))
reference = (stack - from_size).as(UInt8**).value
type_id =
@@ -1452,7 +1464,7 @@ require "./repl"
tuple_indexer_known_index: {
operands: [tuple_size : Int32, offset : Int32, value_size : Int32],
code: begin
- (stack - tuple_size).copy_from(stack - tuple_size + offset, value_size)
+ (stack - tuple_size).move_from(stack - tuple_size + offset, value_size)
aligned_value_size = align(value_size)
stack_shrink_by(tuple_size - value_size)
stack_grow_by(aligned_value_size - value_size)
@@ -1464,7 +1476,7 @@ require "./repl"
},
tuple_copy_element: {
operands: [tuple_size : Int32, old_offset : Int32, new_offset : Int32, element_size : Int32],
- code: (stack - tuple_size + new_offset).copy_from(stack - tuple_size + old_offset, element_size),
+ code: (stack - tuple_size + new_offset).move_from(stack - tuple_size + old_offset, element_size),
},
# >>> Tuples (3)
diff --git a/src/compiler/crystal/interpreter/interpreter.cr b/src/compiler/crystal/interpreter/interpreter.cr
index aa90d83f413f..e26a6751c176 100644
--- a/src/compiler/crystal/interpreter/interpreter.cr
+++ b/src/compiler/crystal/interpreter/interpreter.cr
@@ -113,7 +113,7 @@ class Crystal::Repl::Interpreter
def initialize(
@context : Context,
# TODO: what if the stack is exhausted?
- @stack : UInt8* = Pointer(Void).malloc(8 * 1024 * 1024).as(UInt8*)
+ @stack : UInt8* = Pointer(Void).malloc(8 * 1024 * 1024).as(UInt8*),
)
@local_vars = LocalVars.new(@context)
@argv = [] of String
@@ -1000,16 +1000,16 @@ class Crystal::Repl::Interpreter
private macro stack_pop(t)
%aligned_size = align(sizeof({{t}}))
%value = uninitialized {{t}}
- (stack - %aligned_size).copy_to(pointerof(%value).as(UInt8*), sizeof({{t}}))
+ (stack - %aligned_size).copy_to(pointerof(%value).as(UInt8*), sizeof(typeof(%value)))
stack_shrink_by(%aligned_size)
%value
end
private macro stack_push(value)
%temp = {{value}}
- stack.copy_from(pointerof(%temp).as(UInt8*), sizeof(typeof({{value}})))
+ %size = sizeof(typeof(%temp))
- %size = sizeof(typeof({{value}}))
+ stack.copy_from(pointerof(%temp).as(UInt8*), %size)
%aligned_size = align(%size)
stack += %size
stack_grow_by(%aligned_size - %size)
diff --git a/src/compiler/crystal/interpreter/lib_function.cr b/src/compiler/crystal/interpreter/lib_function.cr
index 54ac2ac297cf..e1898869227e 100644
--- a/src/compiler/crystal/interpreter/lib_function.cr
+++ b/src/compiler/crystal/interpreter/lib_function.cr
@@ -19,7 +19,7 @@ class Crystal::Repl::LibFunction
@def : External,
@symbol : Void*,
@call_interface : FFI::CallInterface,
- @args_bytesizes : Array(Int32)
+ @args_bytesizes : Array(Int32),
)
end
end
diff --git a/src/compiler/crystal/interpreter/primitives.cr b/src/compiler/crystal/interpreter/primitives.cr
index e411229600f9..ca436947370e 100644
--- a/src/compiler/crystal/interpreter/primitives.cr
+++ b/src/compiler/crystal/interpreter/primitives.cr
@@ -87,6 +87,8 @@ class Crystal::Repl::Compiler
pointer_add(inner_sizeof_type(element_type), node: node)
when "class"
+ # Should match Crystal::Repl::Value#runtime_type
+ # in src/compiler/crystal/interpreter/value.cr
obj = obj.not_nil!
type = obj.type.remove_indirection
@@ -176,6 +178,30 @@ class Crystal::Repl::Compiler
pop(sizeof(Pointer(Void)), node: nil)
end
end
+ when "pre_initialize"
+ type =
+ if obj
+ discard_value(obj)
+ obj.type.instance_type
+ else
+ scope.instance_type
+ end
+
+ accept_call_members(node)
+
+ dup sizeof(Pointer(Void)), node: nil
+ reset_class(aligned_instance_sizeof_type(type), type_id(type), node: node)
+
+ initializer_compiled_defs = @context.type_instance_var_initializers(type)
+ unless initializer_compiled_defs.empty?
+ initializer_compiled_defs.size.times do
+ dup sizeof(Pointer(Void)), node: nil
+ end
+
+ initializer_compiled_defs.each do |compiled_def|
+ call compiled_def, node: nil
+ end
+ end
when "tuple_indexer_known_index"
unless @wants_value
accept_call_members(node)
diff --git a/src/compiler/crystal/interpreter/value.cr b/src/compiler/crystal/interpreter/value.cr
index 349dff00f78b..681798bf7a32 100644
--- a/src/compiler/crystal/interpreter/value.cr
+++ b/src/compiler/crystal/interpreter/value.cr
@@ -67,6 +67,21 @@ struct Crystal::Repl::Value
end
end
+ def runtime_type : Crystal::Type
+ # Should match Crystal::Repl::Compiler#visit_primitive "class" case
+ # in src/compiler/crystal/interpreter/primitives.cr
+ case type
+ when Crystal::UnionType
+ type_id = @pointer.as(Int32*).value
+ context.type_from_id(type_id)
+ when Crystal::VirtualType
+ type_id = @pointer.as(Void**).value.as(Int32*).value
+ context.type_from_id(type_id)
+ else
+ type
+ end
+ end
+
# Copies the contents of this value to another pointer.
def copy_to(pointer : Pointer(UInt8))
@pointer.copy_to(pointer, context.inner_sizeof_type(@type))
diff --git a/src/compiler/crystal/loader.cr b/src/compiler/crystal/loader.cr
index 5a147dad590f..84ff43d03d8e 100644
--- a/src/compiler/crystal/loader.cr
+++ b/src/compiler/crystal/loader.cr
@@ -1,4 +1,4 @@
-{% skip_file unless flag?(:unix) || flag?(:msvc) %}
+{% skip_file unless flag?(:unix) || flag?(:win32) %}
require "option_parser"
# This loader component imitates the behaviour of `ld.so` for linking and loading
@@ -105,4 +105,6 @@ end
require "./loader/unix"
{% elsif flag?(:msvc) %}
require "./loader/msvc"
+{% elsif flag?(:win32) && flag?(:gnu) %}
+ require "./loader/mingw"
{% end %}
diff --git a/src/compiler/crystal/loader/mingw.cr b/src/compiler/crystal/loader/mingw.cr
new file mode 100644
index 000000000000..677f564cec16
--- /dev/null
+++ b/src/compiler/crystal/loader/mingw.cr
@@ -0,0 +1,195 @@
+{% skip_file unless flag?(:win32) && flag?(:gnu) %}
+
+require "crystal/system/win32/library_archive"
+
+# MinGW-based loader used on Windows. Assumes an MSYS2 shell.
+#
+# The core implementation is derived from the MSVC loader. Main deviations are:
+#
+# - `.parse` follows GNU `ld`'s style, rather than MSVC `link`'s;
+# - `#library_filename` follows the usual naming of the MinGW linker: `.dll.a`
+# for DLL import libraries, `.a` for other libraries;
+# - `.default_search_paths` relies solely on `.cc_each_library_path`.
+#
+# TODO: The actual MinGW linker supports linking to DLLs directly, figure out
+# how this is done.
+
+class Crystal::Loader
+ alias Handle = Void*
+
+ def initialize(@search_paths : Array(String))
+ end
+
+ # Parses linker arguments in the style of `ld`.
+ #
+ # This is identical to the Unix loader. *dll_search_paths* has no effect.
+ def self.parse(args : Array(String), *, search_paths : Array(String) = default_search_paths, dll_search_paths : Array(String)? = nil) : self
+ libnames = [] of String
+ file_paths = [] of String
+ extra_search_paths = [] of String
+
+ OptionParser.parse(args.dup) do |parser|
+ parser.on("-L DIRECTORY", "--library-path DIRECTORY", "Add DIRECTORY to library search path") do |directory|
+ extra_search_paths << directory
+ end
+ parser.on("-l LIBNAME", "--library LIBNAME", "Search for library LIBNAME") do |libname|
+ libnames << libname
+ end
+ parser.on("-static", "Do not link against shared libraries") do
+ raise LoadError.new "static libraries are not supported by Crystal's runtime loader"
+ end
+ parser.unknown_args do |args, after_dash|
+ file_paths.concat args
+ end
+
+ parser.invalid_option do |arg|
+ unless arg.starts_with?("-Wl,")
+ raise LoadError.new "Not a recognized linker flag: #{arg}"
+ end
+ end
+ end
+
+ search_paths = extra_search_paths + search_paths
+
+ begin
+ loader = new(search_paths)
+ loader.load_all(libnames, file_paths)
+ loader
+ rescue exc : LoadError
+ exc.args = args
+ exc.search_paths = search_paths
+ raise exc
+ end
+ end
+
+ def self.library_filename(libname : String) : String
+ "lib#{libname}.a"
+ end
+
+ def find_symbol?(name : String) : Handle?
+ @handles.each do |handle|
+ address = LibC.GetProcAddress(handle, name.check_no_null_byte)
+ return address if address
+ end
+ end
+
+ def load_file(path : String | ::Path) : Nil
+ load_file?(path) || raise LoadError.new "cannot load #{path}"
+ end
+
+ def load_file?(path : String | ::Path) : Bool
+ if api_set?(path)
+ return load_dll?(path.to_s)
+ end
+
+ return false unless File.file?(path)
+
+ System::LibraryArchive.imported_dlls(path).all? do |dll|
+ load_dll?(dll)
+ end
+ end
+
+ private def load_dll?(dll)
+ handle = open_library(dll)
+ return false unless handle
+
+ @handles << handle
+ @loaded_libraries << (module_filename(handle) || dll)
+ true
+ end
+
+ def load_library(libname : String) : Nil
+ load_library?(libname) || raise LoadError.new "cannot find #{Loader.library_filename(libname)}"
+ end
+
+ def load_library?(libname : String) : Bool
+ if ::Path::SEPARATORS.any? { |separator| libname.includes?(separator) }
+ return load_file?(::Path[libname].expand)
+ end
+
+ # attempt .dll.a before .a
+ # TODO: verify search order
+ @search_paths.each do |directory|
+ library_path = File.join(directory, Loader.library_filename(libname + ".dll"))
+ return true if load_file?(library_path)
+
+ library_path = File.join(directory, Loader.library_filename(libname))
+ return true if load_file?(library_path)
+ end
+
+ false
+ end
+
+ private def open_library(path : String)
+ LibC.LoadLibraryExW(System.to_wstr(path), nil, 0)
+ end
+
+ def load_current_program_handle
+ if LibC.GetModuleHandleExW(0, nil, out hmodule) != 0
+ @handles << hmodule
+ @loaded_libraries << (Process.executable_path || "current program handle")
+ end
+ end
+
+ def close_all : Nil
+ @handles.each do |handle|
+ LibC.FreeLibrary(handle)
+ end
+ @handles.clear
+ end
+
+ private def api_set?(dll)
+ dll.to_s.matches?(/^(?:api-|ext-)[a-zA-Z0-9-]*l\d+-\d+-\d+\.dll$/)
+ end
+
+ private def module_filename(handle)
+ Crystal::System.retry_wstr_buffer do |buffer, small_buf|
+ len = LibC.GetModuleFileNameW(handle, buffer, buffer.size)
+ if 0 < len < buffer.size
+ break String.from_utf16(buffer[0, len])
+ elsif small_buf && len == buffer.size
+ next 32767 # big enough. 32767 is the maximum total path length of UNC path.
+ else
+ break nil
+ end
+ end
+ end
+
+ # Returns a list of directories used as the default search paths.
+ #
+ # Right now this depends on `cc` exclusively.
+ def self.default_search_paths : Array(String)
+ default_search_paths = [] of String
+
+ cc_each_library_path do |path|
+ default_search_paths << path
+ end
+
+ default_search_paths.uniq!
+ end
+
+ # identical to the Unix loader
+ def self.cc_each_library_path(& : String ->) : Nil
+ search_dirs = begin
+ cc =
+ {% if Crystal.has_constant?("Compiler") %}
+ Crystal::Compiler::DEFAULT_LINKER
+ {% else %}
+ # this allows the loader to be required alone without the compiler
+ ENV["CC"]? || "cc"
+ {% end %}
+
+ `#{cc} -print-search-dirs`
+ rescue IO::Error
+ return
+ end
+
+ search_dirs.each_line do |line|
+ if libraries = line.lchop?("libraries: =")
+ libraries.split(Process::PATH_DELIMITER) do |path|
+ yield File.expand_path(path)
+ end
+ end
+ end
+ end
+end
diff --git a/src/compiler/crystal/loader/msvc.cr b/src/compiler/crystal/loader/msvc.cr
index 05bf988c9218..966f6ec5d246 100644
--- a/src/compiler/crystal/loader/msvc.cr
+++ b/src/compiler/crystal/loader/msvc.cr
@@ -133,15 +133,25 @@ class Crystal::Loader
end
def load_file?(path : String | ::Path) : Bool
+ # API sets shouldn't be linked directly from linker flags, but just in case
+ if api_set?(path)
+ return load_dll?(path.to_s)
+ end
+
return false unless File.file?(path)
# On Windows, each `.lib` import library may reference any number of `.dll`
# files, whose base names may not match the library's. Thus it is necessary
# to extract this information from the library archive itself.
- System::LibraryArchive.imported_dlls(path).each do |dll|
- dll_full_path = @dll_search_paths.try &.each do |search_path|
- full_path = File.join(search_path, dll)
- break full_path if File.file?(full_path)
+ System::LibraryArchive.imported_dlls(path).all? do |dll|
+ # API set names do not refer to physical filenames despite ending with
+ # `.dll`, and therefore should not use a path search:
+ # https://learn.microsoft.com/en-us/cpp/windows/universal-crt-deployment?view=msvc-170#local-deployment
+ unless api_set?(dll)
+ dll_full_path = @dll_search_paths.try &.each do |search_path|
+ full_path = File.join(search_path, dll)
+ break full_path if File.file?(full_path)
+ end
end
dll = dll_full_path || dll
@@ -152,13 +162,16 @@ class Crystal::Loader
#
# Note that the compiler's directory and PATH are effectively searched
# twice when coming from the interpreter
- handle = open_library(dll)
- return false unless handle
-
- @handles << handle
- @loaded_libraries << (module_filename(handle) || dll)
+ load_dll?(dll)
end
+ end
+
+ private def load_dll?(dll)
+ handle = open_library(dll)
+ return false unless handle
+ @handles << handle
+ @loaded_libraries << (module_filename(handle) || dll)
true
end
@@ -172,7 +185,6 @@ class Crystal::Loader
end
private def open_library(path : String)
- # TODO: respect `@[Link(dll:)]`'s search order
LibC.LoadLibraryExW(System.to_wstr(path), nil, 0)
end
@@ -190,6 +202,12 @@ class Crystal::Loader
@handles.clear
end
+ # Returns whether *dll* names an API set according to:
+ # https://learn.microsoft.com/en-us/windows/win32/apiindex/windows-apisets#api-set-contract-names
+ private def api_set?(dll)
+ dll.to_s.matches?(/^(?:api-|ext-)[a-zA-Z0-9-]*l\d+-\d+-\d+\.dll$/)
+ end
+
private def module_filename(handle)
Crystal::System.retry_wstr_buffer do |buffer, small_buf|
len = LibC.GetModuleFileNameW(handle, buffer, buffer.size)
diff --git a/src/compiler/crystal/loader/unix.cr b/src/compiler/crystal/loader/unix.cr
index dfab9736b038..962a3a47f22a 100644
--- a/src/compiler/crystal/loader/unix.cr
+++ b/src/compiler/crystal/loader/unix.cr
@@ -76,6 +76,15 @@ class Crystal::Loader
parser.unknown_args do |args, after_dash|
file_paths.concat args
end
+
+ # although flags starting with `-Wl,` appear in `args` above, this is
+ # still called by `OptionParser`, so we assume it is fine to ignore these
+ # flags
+ parser.invalid_option do |arg|
+ unless arg.starts_with?("-Wl,")
+ raise LoadError.new "Not a recognized linker flag: #{arg}"
+ end
+ end
end
search_paths = extra_search_paths + search_paths
@@ -162,6 +171,10 @@ class Crystal::Loader
read_ld_conf(default_search_paths)
{% end %}
+ cc_each_library_path do |path|
+ default_search_paths << path
+ end
+
{% if flag?(:darwin) %}
default_search_paths << "/usr/lib"
default_search_paths << "/usr/local/lib"
@@ -179,7 +192,7 @@ class Crystal::Loader
default_search_paths << "/usr/lib"
{% end %}
- default_search_paths
+ default_search_paths.uniq!
end
def self.read_ld_conf(array = [] of String, path = "/etc/ld.so.conf") : Nil
@@ -201,4 +214,20 @@ class Crystal::Loader
end
end
end
+
+ def self.cc_each_library_path(& : String ->) : Nil
+ search_dirs = begin
+ `#{Crystal::Compiler::DEFAULT_LINKER} -print-search-dirs`
+ rescue IO::Error
+ return
+ end
+
+ search_dirs.each_line do |line|
+ if libraries = line.lchop?("libraries: =")
+ libraries.split(Process::PATH_DELIMITER) do |path|
+ yield File.expand_path(path)
+ end
+ end
+ end
+ end
end
diff --git a/src/compiler/crystal/macros.cr b/src/compiler/crystal/macros.cr
index c0d4f6e0a071..a2ea0aeb85fe 100644
--- a/src/compiler/crystal/macros.cr
+++ b/src/compiler/crystal/macros.cr
@@ -800,6 +800,10 @@ module Crystal::Macros
def []=(key : ASTNode, value : ASTNode) : ASTNode
end
+ # Similar to `Hash#has_hey?`
+ def has_key?(key : ASTNode) : BoolLiteral
+ end
+
# Returns the type specified at the end of the Hash literal, if any.
#
# This refers to the key type after brackets in `{} of String => Int32`.
@@ -874,6 +878,10 @@ module Crystal::Macros
# Adds or replaces a key.
def []=(key : SymbolLiteral | StringLiteral | MacroId, value : ASTNode) : ASTNode
end
+
+ # Similar to `NamedTuple#has_key?`
+ def has_key?(key : SymbolLiteral | StringLiteral | MacroId) : ASTNode
+ end
end
# A range literal.
@@ -2853,5 +2861,24 @@ module Crystal::Macros
# `self` is an ancestor of *other*.
def >=(other : TypeNode) : BoolLiteral
end
+
+ # Returns whether `self` contains any inner pointers.
+ #
+ # Primitive types, except `Void`, are expected to not contain inner pointers.
+ # `Proc` and `Pointer` contain inner pointers.
+ # Unions, structs and collection types (tuples, static arrays)
+ # have inner pointers if any of their contained types has inner pointers.
+ # All other types, including classes, are expected to contain inner pointers.
+ #
+ # Types that do not have inner pointers may opt to use atomic allocations,
+ # i.e. `GC.malloc_atomic` rather than `GC.malloc`. The compiler ensures
+ # that, for any type `T`:
+ #
+ # * `Pointer(T).malloc` is atomic if and only if `T` has no inner pointers;
+ # * `T.allocate` is atomic if and only if `T` is a reference type and
+ # `ReferenceStorage(T)` has no inner pointers.
+ # NOTE: Like `#instance_vars` this method must be called from within a method. The result may be incorrect when used in top-level code.
+ def has_inner_pointers? : BoolLiteral
+ end
end
end
diff --git a/src/compiler/crystal/macros/methods.cr b/src/compiler/crystal/macros/methods.cr
index a44bba1b76f9..3a81015f0ffd 100644
--- a/src/compiler/crystal/macros/methods.cr
+++ b/src/compiler/crystal/macros/methods.cr
@@ -965,6 +965,10 @@ module Crystal
interpret_check_args { @of.try(&.key) || Nop.new }
when "of_value"
interpret_check_args { @of.try(&.value) || Nop.new }
+ when "has_key?"
+ interpret_check_args do |key|
+ BoolLiteral.new(entries.any? &.key.==(key))
+ end
when "type"
interpret_check_args { @name || Nop.new }
when "clear"
@@ -1042,11 +1046,7 @@ module Crystal
when "[]"
interpret_check_args do |key|
case key
- when SymbolLiteral
- key = key.value
- when MacroId
- key = key.value
- when StringLiteral
+ when SymbolLiteral, MacroId, StringLiteral
key = key.value
else
raise "argument to [] must be a symbol or string, not #{key.class_desc}:\n\n#{key}"
@@ -1058,11 +1058,7 @@ module Crystal
when "[]="
interpret_check_args do |key, value|
case key
- when SymbolLiteral
- key = key.value
- when MacroId
- key = key.value
- when StringLiteral
+ when SymbolLiteral, MacroId, StringLiteral
key = key.value
else
raise "expected 'NamedTupleLiteral#[]=' first argument to be a SymbolLiteral or MacroId, not #{key.class_desc}"
@@ -1077,6 +1073,17 @@ module Crystal
value
end
+ when "has_key?"
+ interpret_check_args do |key|
+ case key
+ when SymbolLiteral, MacroId, StringLiteral
+ key = key.value
+ else
+ raise "expected 'NamedTupleLiteral#has_key?' first argument to be a SymbolLiteral, StringLiteral or MacroId, not #{key.class_desc}"
+ end
+
+ BoolLiteral.new(entries.any? &.key.==(key))
+ end
else
super
end
@@ -2013,6 +2020,8 @@ module Crystal
SymbolLiteral.new("public")
end
end
+ when "has_inner_pointers?"
+ interpret_check_args { BoolLiteral.new(type.has_inner_pointers?) }
else
super
end
@@ -3230,12 +3239,17 @@ end
private def sort_by(object, klass, block, interpreter)
block_arg = block.args.first?
- klass.new(object.elements.sort { |x, y|
- block_arg.try { |arg| interpreter.define_var(arg.name, x) }
- x_result = interpreter.accept(block.body)
- block_arg.try { |arg| interpreter.define_var(arg.name, y) }
- y_result = interpreter.accept(block.body)
+ klass.new(object.elements.sort_by do |elem|
+ block_arg.try { |arg| interpreter.define_var(arg.name, elem) }
+ result = interpreter.accept(block.body)
+ InterpretCompareWrapper.new(result)
+ end)
+end
- x_result.interpret_compare(y_result)
- })
+private record InterpretCompareWrapper, node : Crystal::ASTNode do
+ include Comparable(self)
+
+ def <=>(other : self)
+ node.interpret_compare(other.node)
+ end
end
diff --git a/src/compiler/crystal/macros/types.cr b/src/compiler/crystal/macros/types.cr
index 7a7777e8aef3..3a40a9bc90aa 100644
--- a/src/compiler/crystal/macros/types.cr
+++ b/src/compiler/crystal/macros/types.cr
@@ -46,7 +46,8 @@ module Crystal
@macro_types["Arg"] = NonGenericMacroType.new self, "Arg", ast_node
@macro_types["ProcNotation"] = NonGenericMacroType.new self, "ProcNotation", ast_node
- @macro_types["Def"] = NonGenericMacroType.new self, "Def", ast_node
+ @macro_types["Def"] = def_type = NonGenericMacroType.new self, "Def", ast_node
+ @macro_types["External"] = NonGenericMacroType.new self, "External", def_type
@macro_types["Macro"] = NonGenericMacroType.new self, "Macro", ast_node
@macro_types["UnaryExpression"] = unary_expression = NonGenericMacroType.new self, "UnaryExpression", ast_node
@@ -102,7 +103,6 @@ module Crystal
# bottom type
@macro_types["NoReturn"] = @macro_no_return = NoReturnMacroType.new self
- # unimplemented types (see https://github.com/crystal-lang/crystal/issues/3274#issuecomment-860092436)
@macro_types["Self"] = NonGenericMacroType.new self, "Self", ast_node
@macro_types["Underscore"] = NonGenericMacroType.new self, "Underscore", ast_node
@macro_types["Select"] = NonGenericMacroType.new self, "Select", ast_node
diff --git a/src/compiler/crystal/program.cr b/src/compiler/crystal/program.cr
index b1cc99f0dfc6..bab4e22b9fba 100644
--- a/src/compiler/crystal/program.cr
+++ b/src/compiler/crystal/program.cr
@@ -205,6 +205,8 @@ module Crystal
types["Regex"] = @regex = NonGenericClassType.new self, self, "Regex", reference
types["Range"] = range = @range = GenericClassType.new self, self, "Range", struct_t, ["B", "E"]
range.struct = true
+ types["Slice"] = slice = @slice = GenericClassType.new self, self, "Slice", struct_t, ["T"]
+ slice.struct = true
types["Exception"] = @exception = NonGenericClassType.new self, self, "Exception", reference
@@ -504,7 +506,7 @@ module Crystal
recorded_requires << RecordedRequire.new(filename, relative_to)
end
- def run_requires(node : Require, filenames) : Nil
+ def run_requires(node : Require, filenames, &) : Nil
dependency_printer = compiler.try(&.dependency_printer)
filenames.each do |filename|
@@ -528,7 +530,7 @@ module Crystal
{% for name in %w(object no_return value number reference void nil bool char int int8 int16 int32 int64 int128
uint8 uint16 uint32 uint64 uint128 float float32 float64 string symbol pointer enumerable indexable
- array static_array exception tuple named_tuple proc union enum range regex crystal
+ array static_array exception tuple named_tuple proc union enum range slice regex crystal
packed_annotation thread_local_annotation no_inline_annotation
always_inline_annotation naked_annotation returns_twice_annotation
raises_annotation primitive_annotation call_convention_annotation
diff --git a/src/compiler/crystal/semantic/abstract_def_checker.cr b/src/compiler/crystal/semantic/abstract_def_checker.cr
index 2a7ccdc05d2a..6d1aa58447a1 100644
--- a/src/compiler/crystal/semantic/abstract_def_checker.cr
+++ b/src/compiler/crystal/semantic/abstract_def_checker.cr
@@ -24,7 +24,6 @@
# ```
class Crystal::AbstractDefChecker
def initialize(@program : Program)
- @all_checked = Set(Type).new
end
def run
@@ -41,9 +40,6 @@ class Crystal::AbstractDefChecker
end
def check_single(type)
- return if @all_checked.includes?(type)
- @all_checked << type
-
if type.abstract? || type.module?
type.defs.try &.each_value do |defs_with_metadata|
defs_with_metadata.each do |def_with_metadata|
diff --git a/src/compiler/crystal/semantic/bindings.cr b/src/compiler/crystal/semantic/bindings.cr
index c5fe9f164742..a7dacb8668c9 100644
--- a/src/compiler/crystal/semantic/bindings.cr
+++ b/src/compiler/crystal/semantic/bindings.cr
@@ -1,7 +1,77 @@
module Crystal
+ # Specialized container for ASTNodes to use for bindings tracking.
+ #
+ # The average number of elements in both dependencies and observers is below 2
+ # for ASTNodes. This struct inlines the first two elements saving up 4
+ # allocations per node (two arrays, with a header and buffer for each) but we
+ # need to pay a slight extra cost in memory upfront: a total of 6 pointers (48
+ # bytes) vs 2 pointers (16 bytes). The other downside is that since this is a
+ # struct, we need to be careful with mutation.
+ struct SmallNodeList
+ include Enumerable(ASTNode)
+
+ @first : ASTNode?
+ @second : ASTNode?
+ @tail : Array(ASTNode)?
+
+ def each(& : ASTNode ->)
+ yield @first || return
+ yield @second || return
+ @tail.try(&.each { |node| yield node })
+ end
+
+ def size
+ if @first.nil?
+ 0
+ elsif @second.nil?
+ 1
+ elsif (tail = @tail).nil?
+ 2
+ else
+ 2 + tail.size
+ end
+ end
+
+ def push(node : ASTNode) : self
+ if @first.nil?
+ @first = node
+ elsif @second.nil?
+ @second = node
+ elsif (tail = @tail).nil?
+ @tail = [node] of ASTNode
+ else
+ tail.push(node)
+ end
+ self
+ end
+
+ def reject!(& : ASTNode ->) : self
+ if first = @first
+ if second = @second
+ if tail = @tail
+ tail.reject! { |node| yield node }
+ end
+ if yield second
+ @second = tail.try &.shift?
+ end
+ end
+ if yield first
+ @first = @second
+ @second = tail.try &.shift?
+ end
+ end
+ self
+ end
+
+ def concat(nodes : Enumerable(ASTNode)) : self
+ nodes.each { |node| self.push(node) }
+ self
+ end
+ end
+
class ASTNode
- property! dependencies : Array(ASTNode)
- property observers : Array(ASTNode)?
+ getter dependencies : SmallNodeList = SmallNodeList.new
+ @observers : SmallNodeList = SmallNodeList.new
property enclosing_call : Call?
@dirty = false
@@ -107,8 +177,8 @@ module Crystal
end
def bind_to(node : ASTNode) : Nil
- bind(node) do |dependencies|
- dependencies.push node
+ bind(node) do
+ @dependencies.push node
node.add_observer self
end
end
@@ -116,8 +186,8 @@ module Crystal
def bind_to(nodes : Indexable) : Nil
return if nodes.empty?
- bind do |dependencies|
- dependencies.concat nodes
+ bind do
+ @dependencies.concat nodes
nodes.each &.add_observer self
end
end
@@ -130,9 +200,7 @@ module Crystal
raise_frozen_type freeze_type, from_type, from
end
- dependencies = @dependencies ||= [] of ASTNode
-
- yield dependencies
+ yield
new_type = type_from_dependencies
new_type = map_type(new_type) if new_type
@@ -150,7 +218,7 @@ module Crystal
end
def type_from_dependencies : Type?
- Type.merge dependencies
+ Type.merge @dependencies
end
def unbind_from(nodes : Nil)
@@ -158,18 +226,17 @@ module Crystal
end
def unbind_from(node : ASTNode)
- @dependencies.try &.reject! &.same?(node)
+ @dependencies.reject! &.same?(node)
node.remove_observer self
end
- def unbind_from(nodes : Array(ASTNode))
- @dependencies.try &.reject! { |dep| nodes.any? &.same?(dep) }
+ def unbind_from(nodes : Enumerable(ASTNode))
+ @dependencies.reject! { |dep| nodes.any? &.same?(dep) }
nodes.each &.remove_observer self
end
def add_observer(observer)
- observers = @observers ||= [] of ASTNode
- observers.push observer
+ @observers.push observer
end
def remove_observer(observer)
@@ -269,16 +336,10 @@ module Crystal
visited = Set(ASTNode).new.compare_by_identity
owner_trace << node if node.type?.try &.includes_type?(owner)
visited.add node
- while deps = node.dependencies?
- dependencies = deps.select { |dep| dep.type? && dep.type.includes_type?(owner) && !visited.includes?(dep) }
- if dependencies.size > 0
- node = dependencies.first
- nil_reason = node.nil_reason if node.is_a?(MetaTypeVar)
- owner_trace << node if node
- visited.add node
- else
- break
- end
+ while node = node.dependencies.find { |dep| dep.type? && dep.type.includes_type?(owner) && !visited.includes?(dep) }
+ nil_reason = node.nil_reason if node.is_a?(MetaTypeVar)
+ owner_trace << node if node
+ visited.add node
end
MethodTraceException.new(owner, owner_trace, nil_reason, program.show_error_trace?)
diff --git a/src/compiler/crystal/semantic/call.cr b/src/compiler/crystal/semantic/call.cr
index f581ea79d577..1fa4379d543e 100644
--- a/src/compiler/crystal/semantic/call.cr
+++ b/src/compiler/crystal/semantic/call.cr
@@ -13,6 +13,9 @@ class Crystal::Call
property? uses_with_scope = false
class RetryLookupWithLiterals < ::Exception
+ def initialize
+ self.callstack = Exception::CallStack.empty
+ end
end
def program
diff --git a/src/compiler/crystal/semantic/call_error.cr b/src/compiler/crystal/semantic/call_error.cr
index aee5b9e2019b..d19be20afbad 100644
--- a/src/compiler/crystal/semantic/call_error.cr
+++ b/src/compiler/crystal/semantic/call_error.cr
@@ -643,8 +643,7 @@ class Crystal::Call
if obj.is_a?(InstanceVar)
scope = self.scope
ivar = scope.lookup_instance_var(obj.name)
- deps = ivar.dependencies?
- if deps && deps.size == 1 && deps.first.same?(program.nil_var)
+ if ivar.dependencies.size == 1 && ivar.dependencies.first.same?(program.nil_var)
similar_name = scope.lookup_similar_instance_var_name(ivar.name)
if similar_name
msg << colorize(" (#{ivar.name} was never assigned a value, did you mean #{similar_name}?)").yellow.bold
diff --git a/src/compiler/crystal/semantic/cleanup_transformer.cr b/src/compiler/crystal/semantic/cleanup_transformer.cr
index 541e0f51d662..054c7871bd8e 100644
--- a/src/compiler/crystal/semantic/cleanup_transformer.cr
+++ b/src/compiler/crystal/semantic/cleanup_transformer.cr
@@ -1090,10 +1090,7 @@ module Crystal
node = super
unless node.type?
- if dependencies = node.dependencies?
- node.unbind_from node.dependencies
- end
-
+ node.unbind_from node.dependencies
node.bind_to node.expressions
end
diff --git a/src/compiler/crystal/semantic/filters.cr b/src/compiler/crystal/semantic/filters.cr
index 66d1a728804b..7dd253fc2292 100644
--- a/src/compiler/crystal/semantic/filters.cr
+++ b/src/compiler/crystal/semantic/filters.cr
@@ -1,7 +1,7 @@
module Crystal
class TypeFilteredNode < ASTNode
def initialize(@filter : TypeFilter, @node : ASTNode)
- @dependencies = [@node] of ASTNode
+ @dependencies.push @node
node.add_observer self
update(@node)
end
diff --git a/src/compiler/crystal/semantic/flags.cr b/src/compiler/crystal/semantic/flags.cr
index d455f1fdb0c7..d4b0f265a3d1 100644
--- a/src/compiler/crystal/semantic/flags.cr
+++ b/src/compiler/crystal/semantic/flags.cr
@@ -49,7 +49,18 @@ class Crystal::Program
flags.add "freebsd#{target.freebsd_version}"
end
flags.add "netbsd" if target.netbsd?
- flags.add "openbsd" if target.openbsd?
+
+ if target.openbsd?
+ flags.add "openbsd"
+
+ case target.architecture
+ when "aarch64"
+ flags.add "branch-protection=bti" unless flags.any?(&.starts_with?("branch-protection="))
+ when "x86_64", "i386"
+ flags.add "cf-protection=branch" unless flags.any?(&.starts_with?("cf-protection="))
+ end
+ end
+
flags.add "dragonfly" if target.dragonfly?
flags.add "solaris" if target.solaris?
flags.add "android" if target.android?
diff --git a/src/compiler/crystal/semantic/main_visitor.cr b/src/compiler/crystal/semantic/main_visitor.cr
index c33c64e893ff..efd76f76f056 100644
--- a/src/compiler/crystal/semantic/main_visitor.cr
+++ b/src/compiler/crystal/semantic/main_visitor.cr
@@ -373,7 +373,7 @@ module Crystal
var.bind_to(@program.nil_var)
var.nil_if_read = false
- meta_var.bind_to(@program.nil_var) unless meta_var.dependencies.try &.any? &.same?(@program.nil_var)
+ meta_var.bind_to(@program.nil_var) unless meta_var.dependencies.any? &.same?(@program.nil_var)
node.bind_to(@program.nil_var)
end
@@ -1283,7 +1283,7 @@ module Crystal
# It can happen that this call is inside an ArrayLiteral or HashLiteral,
# was expanded but isn't bound to the expansion because the call (together
# with its expansion) was cloned.
- if (expanded = node.expanded) && (!node.dependencies? || !node.type?)
+ if (expanded = node.expanded) && (node.dependencies.empty? || !node.type?)
node.bind_to(expanded)
end
@@ -1313,6 +1313,10 @@ module Crystal
if check_special_new_call(node, obj.type?)
return false
end
+
+ if check_slice_literal_call(node, obj.type?)
+ return false
+ end
end
args.each &.accept(self)
@@ -1567,6 +1571,60 @@ module Crystal
false
end
+ def check_slice_literal_call(node, obj_type)
+ return false unless obj_type
+ return false unless obj_type.metaclass?
+
+ instance_type = obj_type.instance_type.remove_typedef
+
+ if node.name == "literal"
+ case instance_type
+ when GenericClassType # Slice
+ return false unless instance_type == @program.slice
+ node.raise "TODO: implement slice_literal primitive for Slice without generic arguments"
+ when GenericClassInstanceType # Slice(T)
+ return false unless instance_type.generic_type == @program.slice
+
+ element_type = instance_type.type_vars["T"].type
+ kind = case element_type
+ when IntegerType
+ element_type.kind
+ when FloatType
+ element_type.kind
+ else
+ node.raise "Only slice literals of primitive integer or float types can be created"
+ end
+
+ node.args.each do |arg|
+ arg.raise "Expected NumberLiteral, got #{arg.class_desc}" unless arg.is_a?(NumberLiteral)
+ arg.accept self
+ arg.raise "Argument out of range for a Slice(#{element_type})" unless arg.representable_in?(element_type)
+ end
+
+ # create the internal constant `$Slice:n` to hold the slice contents
+ const_name = "$Slice:#{@program.const_slices.size}"
+ const_value = Nop.new
+ const_value.type = @program.static_array_of(element_type, node.args.size)
+ const = Const.new(@program, @program, const_name, const_value)
+ @program.types[const_name] = const
+ @program.const_slices << Program::ConstSliceInfo.new(const_name, kind, node.args)
+
+ # ::Slice.new(pointerof($Slice:n.@buffer), {{ args.size }}, read_only: true)
+ pointer_node = PointerOf.new(ReadInstanceVar.new(Path.new(const_name).at(node), "@buffer").at(node)).at(node)
+ size_node = NumberLiteral.new(node.args.size.to_s, :i32).at(node)
+ read_only_node = NamedArgument.new("read_only", BoolLiteral.new(true).at(node)).at(node)
+ expanded = Call.new(Path.global("Slice").at(node), "new", [pointer_node, size_node], named_args: [read_only_node]).at(node)
+
+ expanded.accept self
+ node.bind_to expanded
+ node.expanded = expanded
+ return true
+ end
+ end
+
+ false
+ end
+
# Rewrite:
#
# LibFoo::Struct.new arg0: value0, argN: value0
@@ -2308,7 +2366,7 @@ module Crystal
when "pointer_new"
visit_pointer_new node
when "slice_literal"
- visit_slice_literal node
+ node.raise "BUG: Slice literal should have been expanded"
when "argc"
# Already typed
when "argv"
@@ -2466,51 +2524,6 @@ module Crystal
node.type = scope.instance_type
end
- def visit_slice_literal(node)
- call = self.call.not_nil!
-
- case slice_type = scope.instance_type
- when GenericClassType # Slice
- call.raise "TODO: implement slice_literal primitive for Slice without generic arguments"
- when GenericClassInstanceType # Slice(T)
- element_type = slice_type.type_vars["T"].type
- kind = case element_type
- when IntegerType
- element_type.kind
- when FloatType
- element_type.kind
- else
- call.raise "Only slice literals of primitive integer or float types can be created"
- end
-
- call.args.each do |arg|
- arg.raise "Expected NumberLiteral, got #{arg.class_desc}" unless arg.is_a?(NumberLiteral)
- arg.raise "Argument out of range for a Slice(#{element_type})" unless arg.representable_in?(element_type)
- end
-
- # create the internal constant `$Slice:n` to hold the slice contents
- const_name = "$Slice:#{@program.const_slices.size}"
- const_value = Nop.new
- const_value.type = @program.static_array_of(element_type, call.args.size)
- const = Const.new(@program, @program, const_name, const_value)
- @program.types[const_name] = const
- @program.const_slices << Program::ConstSliceInfo.new(const_name, kind, call.args)
-
- # ::Slice.new(pointerof($Slice:n.@buffer), {{ args.size }}, read_only: true)
- pointer_node = PointerOf.new(ReadInstanceVar.new(Path.new(const_name).at(node), "@buffer").at(node)).at(node)
- size_node = NumberLiteral.new(call.args.size.to_s, :i32).at(node)
- read_only_node = NamedArgument.new("read_only", BoolLiteral.new(true).at(node)).at(node)
- extra = Call.new(Path.global("Slice").at(node), "new", [pointer_node, size_node], named_args: [read_only_node]).at(node)
-
- extra.accept self
- node.extra = extra
- node.type = slice_type
- call.expanded = extra
- else
- node.raise "BUG: Unknown scope for slice_literal primitive"
- end
- end
-
def visit_struct_or_union_set(node)
scope = @scope.as(NonGenericClassType)
@@ -2659,7 +2672,7 @@ module Crystal
end
end
- private def visit_size_or_align_of(node)
+ private def visit_size_or_align_of(node, &)
@in_type_args += 1
node.exp.accept self
@in_type_args -= 1
@@ -2685,7 +2698,7 @@ module Crystal
false
end
- private def visit_instance_size_or_align_of(node)
+ private def visit_instance_size_or_align_of(node, &)
@in_type_args += 1
node.exp.accept self
@in_type_args -= 1
diff --git a/src/compiler/crystal/semantic/math_interpreter.cr b/src/compiler/crystal/semantic/math_interpreter.cr
index c39d290aa1e9..d6846e420a7b 100644
--- a/src/compiler/crystal/semantic/math_interpreter.cr
+++ b/src/compiler/crystal/semantic/math_interpreter.cr
@@ -73,6 +73,7 @@ struct Crystal::MathInterpreter
when "//" then left // right
when "&" then left & right
when "|" then left | right
+ when "^" then left ^ right
when "<<" then left << right
when ">>" then left >> right
when "%" then left % right
diff --git a/src/compiler/crystal/semantic/new.cr b/src/compiler/crystal/semantic/new.cr
index de8ae55312a0..43a0a631e2c6 100644
--- a/src/compiler/crystal/semantic/new.cr
+++ b/src/compiler/crystal/semantic/new.cr
@@ -22,8 +22,6 @@ module Crystal
end
def define_default_new(type)
- return if type.is_a?(AliasType) || type.is_a?(TypeDefType)
-
type.types?.try &.each_value do |type|
define_default_new_single(type)
end
diff --git a/src/compiler/crystal/semantic/path_lookup.cr b/src/compiler/crystal/semantic/path_lookup.cr
index b2d66879d253..72cab053984b 100644
--- a/src/compiler/crystal/semantic/path_lookup.cr
+++ b/src/compiler/crystal/semantic/path_lookup.cr
@@ -71,7 +71,7 @@ module Crystal
# precedence than ancestors and the enclosing namespace.
def lookup_path_item(name : String, lookup_self, lookup_in_namespace, include_private, location) : Type | ASTNode | Nil
# First search in our types
- type = types?.try &.[name]?
+ type = lookup_name(name)
if type
if type.private? && !include_private
return nil
diff --git a/src/compiler/crystal/semantic/recursive_struct_checker.cr b/src/compiler/crystal/semantic/recursive_struct_checker.cr
index e7f64913789f..888730e342bb 100644
--- a/src/compiler/crystal/semantic/recursive_struct_checker.cr
+++ b/src/compiler/crystal/semantic/recursive_struct_checker.cr
@@ -14,10 +14,8 @@
# Because the type of `Test.@test` would be: `Test | Nil`.
class Crystal::RecursiveStructChecker
@program : Program
- @all_checked : Set(Type)
def initialize(@program)
- @all_checked = Set(Type).new
end
def run
@@ -34,9 +32,6 @@ class Crystal::RecursiveStructChecker
end
def check_single(type)
- has_not_been_checked = @all_checked.add?(type)
- return unless has_not_been_checked
-
if struct?(type)
target = type
checked = Set(Type).new
diff --git a/src/compiler/crystal/semantic/suggestions.cr b/src/compiler/crystal/semantic/suggestions.cr
index 8f4a69d963bc..e9e05612007f 100644
--- a/src/compiler/crystal/semantic/suggestions.cr
+++ b/src/compiler/crystal/semantic/suggestions.cr
@@ -13,10 +13,10 @@ module Crystal
type = self
names.each_with_index do |name, idx|
previous_type = type
- type = previous_type.types?.try &.[name]?
+ type = previous_type.lookup_name(name)
unless type
best_match = Levenshtein.find(name.downcase) do |finder|
- previous_type.types?.try &.each_key do |type_name|
+ previous_type.remove_alias.types?.try &.each_key do |type_name|
finder.test(type_name.downcase, type_name)
end
end
diff --git a/src/compiler/crystal/semantic/top_level_visitor.cr b/src/compiler/crystal/semantic/top_level_visitor.cr
index 1fc7119b9ffd..3654e24ff7a5 100644
--- a/src/compiler/crystal/semantic/top_level_visitor.cr
+++ b/src/compiler/crystal/semantic/top_level_visitor.cr
@@ -193,9 +193,9 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor
if superclass.is_a?(GenericClassInstanceType)
superclass.generic_type.add_subclass(type)
end
+ scope.types[name] = type
end
- scope.types[name] = type
node.resolved_type = type
process_annotations(annotations) do |annotation_type, ann|
diff --git a/src/compiler/crystal/semantic/type_declaration_processor.cr b/src/compiler/crystal/semantic/type_declaration_processor.cr
index 65451741fac3..0e6008b2fa78 100644
--- a/src/compiler/crystal/semantic/type_declaration_processor.cr
+++ b/src/compiler/crystal/semantic/type_declaration_processor.cr
@@ -621,14 +621,10 @@ struct Crystal::TypeDeclarationProcessor
end
private def remove_duplicate_instance_vars_declarations
- # All the types that we checked for duplicate variables
- duplicates_checked = Set(Type).new
- remove_duplicate_instance_vars_declarations(@program, duplicates_checked)
+ remove_duplicate_instance_vars_declarations(@program)
end
- private def remove_duplicate_instance_vars_declarations(type : Type, duplicates_checked : Set(Type))
- return unless duplicates_checked.add?(type)
-
+ private def remove_duplicate_instance_vars_declarations(type : Type)
# If a class has an instance variable that already exists in a superclass, remove it.
# Ideally we should process instance variables in a top-down fashion, but it's tricky
# with modules and multiple-inheritance. Removing duplicates at the end is maybe
@@ -650,7 +646,7 @@ struct Crystal::TypeDeclarationProcessor
end
type.types?.try &.each_value do |nested_type|
- remove_duplicate_instance_vars_declarations(nested_type, duplicates_checked)
+ remove_duplicate_instance_vars_declarations(nested_type)
end
end
diff --git a/src/compiler/crystal/semantic/type_merge.cr b/src/compiler/crystal/semantic/type_merge.cr
index d68cdeb38a99..67e9f1b61911 100644
--- a/src/compiler/crystal/semantic/type_merge.cr
+++ b/src/compiler/crystal/semantic/type_merge.cr
@@ -17,7 +17,7 @@ module Crystal
end
end
- def type_merge(nodes : Array(ASTNode)) : Type?
+ def type_merge(nodes : Enumerable(ASTNode)) : Type?
case nodes.size
when 0
nil
@@ -25,8 +25,10 @@ module Crystal
nodes.first.type?
when 2
# Merging two types is the most common case, so we optimize it
- first, second = nodes
- type_merge(first.type?, second.type?)
+ # We use `#each_cons_pair` to avoid any intermediate allocation
+ nodes.each_cons_pair do |first, second|
+ return type_merge(first.type?, second.type?)
+ end
else
combined_union_of compact_types(nodes, &.type?)
end
@@ -161,7 +163,7 @@ module Crystal
end
class Type
- def self.merge(nodes : Array(ASTNode)) : Type?
+ def self.merge(nodes : Enumerable(ASTNode)) : Type?
nodes.find(&.type?).try &.type.program.type_merge(nodes)
end
@@ -207,7 +209,7 @@ module Crystal
def self.least_common_ancestor(
type1 : MetaclassType | GenericClassInstanceMetaclassType,
- type2 : MetaclassType | GenericClassInstanceMetaclassType
+ type2 : MetaclassType | GenericClassInstanceMetaclassType,
)
return nil unless unifiable_metaclass?(type1) && unifiable_metaclass?(type2)
@@ -225,7 +227,7 @@ module Crystal
def self.least_common_ancestor(
type1 : NonGenericModuleType | GenericModuleInstanceType | GenericClassType,
- type2 : NonGenericModuleType | GenericModuleInstanceType | GenericClassType
+ type2 : NonGenericModuleType | GenericModuleInstanceType | GenericClassType,
)
return type2 if type1.implements?(type2)
return type1 if type2.implements?(type1)
diff --git a/src/compiler/crystal/syntax/parser.cr b/src/compiler/crystal/syntax/parser.cr
index bd7c67a975b8..1f0b6160a363 100644
--- a/src/compiler/crystal/syntax/parser.cr
+++ b/src/compiler/crystal/syntax/parser.cr
@@ -3211,9 +3211,9 @@ module Crystal
case @token.type
when .macro_literal?
- pieces << MacroLiteral.new(@token.value.to_s)
+ pieces << MacroLiteral.new(@token.value.to_s).at(@token.location).at_end(token_end_location)
when .macro_expression_start?
- pieces << MacroExpression.new(parse_macro_expression)
+ pieces << MacroExpression.new(parse_macro_expression).at(@token.location).at_end(token_end_location)
check_macro_expression_end
skip_whitespace = check_macro_skip_whitespace
when .macro_control_start?
@@ -3341,6 +3341,7 @@ module Crystal
end
def parse_macro_control(start_location, macro_state = Token::MacroState.default)
+ location = @token.location
next_token_skip_space_or_newline
case @token.value
@@ -3385,9 +3386,9 @@ module Crystal
return MacroFor.new(vars, exp, body).at_end(token_end_location)
when Keyword::IF
- return parse_macro_if(start_location, macro_state)
+ return parse_macro_if(start_location, macro_state).at(location)
when Keyword::UNLESS
- return parse_macro_if(start_location, macro_state, is_unless: true)
+ return parse_macro_if(start_location, macro_state, is_unless: true).at(location)
when Keyword::BEGIN
next_token_skip_space
check :OP_PERCENT_RCURLY
@@ -3400,7 +3401,7 @@ module Crystal
next_token_skip_space
check :OP_PERCENT_RCURLY
- return MacroIf.new(BoolLiteral.new(true), body).at_end(token_end_location)
+ return MacroIf.new(BoolLiteral.new(true), body).at(location).at_end(token_end_location)
when Keyword::ELSE, Keyword::ELSIF, Keyword::END
return nil
when Keyword::VERBATIM
@@ -3428,7 +3429,7 @@ module Crystal
exps = parse_expressions
@in_macro_expression = false
- MacroExpression.new(exps, output: false).at_end(token_end_location)
+ MacroExpression.new(exps, output: false).at(location).at_end(token_end_location)
end
def parse_macro_if(start_location, macro_state, check_end = true, is_unless = false)
@@ -3475,7 +3476,8 @@ module Crystal
end
when Keyword::ELSIF
unexpected_token if is_unless
- a_else = parse_macro_if(start_location, macro_state, false)
+ start_loc = @token.location
+ a_else = parse_macro_if(start_location, macro_state, false).at(start_loc)
if check_end
check_ident :end
diff --git a/src/compiler/crystal/tools/dependencies.cr b/src/compiler/crystal/tools/dependencies.cr
index cfb26fbccc43..91701285639b 100644
--- a/src/compiler/crystal/tools/dependencies.cr
+++ b/src/compiler/crystal/tools/dependencies.cr
@@ -8,8 +8,8 @@ class Crystal::Command
dependency_printer = DependencyPrinter.create(STDOUT, format: DependencyPrinter::Format.parse(config.output_format), verbose: config.verbose)
- dependency_printer.includes.concat config.includes.map { |path| ::Path[path].expand.to_s }
- dependency_printer.excludes.concat config.excludes.map { |path| ::Path[path].expand.to_s }
+ dependency_printer.includes.concat config.includes.map { |path| ::Path[path].expand.to_posix.to_s }
+ dependency_printer.excludes.concat config.excludes.map { |path| ::Path[path].expand.to_posix.to_s }
config.compiler.dependency_printer = dependency_printer
dependency_printer.start_format
@@ -124,7 +124,7 @@ module Crystal
end
private def print_indent
- @io.print " " * @stack.size unless @stack.empty?
+ @io.print " " * @stack.size unless @stack.empty? || @format.flat?
end
end
diff --git a/src/compiler/crystal/tools/doc/generator.cr b/src/compiler/crystal/tools/doc/generator.cr
index 635a6be65731..4c5988cccae5 100644
--- a/src/compiler/crystal/tools/doc/generator.cr
+++ b/src/compiler/crystal/tools/doc/generator.cr
@@ -217,6 +217,8 @@ class Crystal::Doc::Generator
def crystal_builtin?(type)
return false unless project_info.crystal_stdlib?
+ # TODO: Enabling this allows links to `NoReturn` to work, but has two `NoReturn`s show up in the sidebar
+ # return true if type.is_a?(NamedType) && {"NoReturn", "Void"}.includes?(type.name)
return false unless type.is_a?(Const) || type.is_a?(NonGenericModuleType)
crystal_type = @program.types["Crystal"]
@@ -249,13 +251,6 @@ class Crystal::Doc::Generator
def collect_subtypes(parent)
types = [] of Type
- # AliasType has defined `types?` to be the types
- # of the aliased type, but for docs we don't want
- # to list the nested types for aliases.
- if parent.is_a?(AliasType)
- return types
- end
-
parent.types?.try &.each_value do |type|
case type
when Const, LibType
diff --git a/src/compiler/crystal/tools/doc/type.cr b/src/compiler/crystal/tools/doc/type.cr
index 9a40bd23e189..624c8f017fe7 100644
--- a/src/compiler/crystal/tools/doc/type.cr
+++ b/src/compiler/crystal/tools/doc/type.cr
@@ -3,6 +3,13 @@ require "./item"
class Crystal::Doc::Type
include Item
+ PSEUDO_CLASS_PREFIX = "CRYSTAL_PSEUDO__"
+ PSEUDO_CLASS_NOTE = <<-DOC
+
+ NOTE: This is a pseudo-class provided directly by the Crystal compiler.
+ It cannot be reopened nor overridden.
+ DOC
+
getter type : Crystal::Type
def initialize(@generator : Generator, type : Crystal::Type)
@@ -39,7 +46,11 @@ class Crystal::Doc::Type
when Program
"Top Level Namespace"
when NamedType
- type.name
+ if @generator.project_info.crystal_stdlib?
+ type.name.lchop(PSEUDO_CLASS_PREFIX)
+ else
+ type.name
+ end
when NoReturnType
"NoReturn"
when VoidType
@@ -403,7 +414,11 @@ class Crystal::Doc::Type
end
def doc
- @type.doc
+ if (t = type).is_a?(NamedType) && t.name.starts_with?(PSEUDO_CLASS_PREFIX)
+ "#{@type.doc}#{PSEUDO_CLASS_NOTE}"
+ else
+ @type.doc
+ end
end
def lookup_path(path_or_names : Path | Array(String))
diff --git a/src/compiler/crystal/tools/formatter.cr b/src/compiler/crystal/tools/formatter.cr
index 796afe0730de..7ea32627078e 100644
--- a/src/compiler/crystal/tools/formatter.cr
+++ b/src/compiler/crystal/tools/formatter.cr
@@ -1476,7 +1476,7 @@ module Crystal
# this formats `def foo # ...` to `def foo(&) # ...` for yielding
# methods before consuming the comment line
if node.block_arity && node.args.empty? && !node.block_arg && !node.double_splat
- write "(&)" if flag?("method_signature_yield")
+ write "(&)"
end
skip_space consume_newline: false
@@ -1523,7 +1523,7 @@ module Crystal
end
def format_def_args(node : Def | Macro)
- yields = node.is_a?(Def) && !node.block_arity.nil? && flag?("method_signature_yield")
+ yields = node.is_a?(Def) && !node.block_arity.nil?
format_def_args node.args, node.block_arg, node.splat_index, false, node.double_splat, yields
end
@@ -1651,7 +1651,7 @@ module Crystal
yield
# Write "," before skipping spaces to prevent inserting comment between argument and comma.
- write "," if has_more || (wrote_newline && @token.type.op_comma?) || (write_trailing_comma && flag?("def_trailing_comma"))
+ write "," if has_more || (wrote_newline && @token.type.op_comma?) || write_trailing_comma
just_wrote_newline = skip_space
if @token.type.newline?
@@ -1681,7 +1681,7 @@ module Crystal
elsif @token.type.op_rparen? && has_more && !just_wrote_newline
# if we found a `)` and there are still more parameters to write, it
# must have been a missing `&` for a def that yields
- write " " if flag?("method_signature_yield")
+ write " "
end
just_wrote_newline
@@ -4273,7 +4273,7 @@ module Crystal
skip_space_or_newline
end
- write " " if a_def.args.present? || return_type || flag?("proc_literal_whitespace") || whitespace_after_op_minus_gt
+ write " "
is_do = false
if @token.keyword?(:do)
@@ -4281,7 +4281,7 @@ module Crystal
is_do = true
else
write_token :OP_LCURLY
- write " " if a_def.body.is_a?(Nop) && (flag?("proc_literal_whitespace") || @token.type.space?)
+ write " " if a_def.body.is_a?(Nop)
end
skip_space
diff --git a/src/compiler/crystal/tools/init.cr b/src/compiler/crystal/tools/init.cr
index 96b004eec2fd..01b2e137c578 100644
--- a/src/compiler/crystal/tools/init.cr
+++ b/src/compiler/crystal/tools/init.cr
@@ -157,7 +157,7 @@ module Crystal
@github_name = "none",
@silent = false,
@force = false,
- @skip_existing = false
+ @skip_existing = false,
)
end
diff --git a/src/compiler/crystal/tools/unreachable.cr b/src/compiler/crystal/tools/unreachable.cr
index a8886fecf596..4ba681240385 100644
--- a/src/compiler/crystal/tools/unreachable.cr
+++ b/src/compiler/crystal/tools/unreachable.cr
@@ -6,7 +6,7 @@ require "csv"
module Crystal
class Command
private def unreachable
- config, result = compile_no_codegen "tool unreachable", path_filter: true, unreachable_command: true, allowed_formats: %w[text json csv]
+ config, result = compile_no_codegen "tool unreachable", path_filter: true, unreachable_command: true, allowed_formats: %w[text json csv codecov]
unreachable = UnreachableVisitor.new
@@ -42,6 +42,8 @@ module Crystal
to_json(STDOUT)
when "csv"
to_csv(STDOUT)
+ when "codecov"
+ to_codecov(STDOUT)
else
to_text(STDOUT)
end
@@ -111,6 +113,31 @@ module Crystal
end
end
end
+
+ # https://docs.codecov.com/docs/codecov-custom-coverage-format
+ def to_codecov(io)
+ hits = Hash(String, Hash(Int32, Int32)).new { |hash, key| hash[key] = Hash(Int32, Int32).new(0) }
+
+ each do |a_def, location, count|
+ hits[location.filename][location.line_number] = count
+ end
+
+ JSON.build io do |builder|
+ builder.object do
+ builder.string "coverage"
+ builder.object do
+ hits.each do |filename, line_coverage|
+ builder.string filename
+ builder.object do
+ line_coverage.each do |line, count|
+ builder.field line, count
+ end
+ end
+ end
+ end
+ end
+ end
+ end
end
# This visitor walks the entire reachable code tree and collect locations
diff --git a/src/compiler/crystal/types.cr b/src/compiler/crystal/types.cr
index 5d903b763050..3a2a759b3158 100644
--- a/src/compiler/crystal/types.cr
+++ b/src/compiler/crystal/types.cr
@@ -373,6 +373,10 @@ module Crystal
nil
end
+ def lookup_name(name)
+ types?.try(&.[name]?)
+ end
+
def parents
nil
end
@@ -1389,10 +1393,10 @@ module Crystal
# Float64 mantissa has 52 bits
case kind
when .i8?, .u8?, .i16?, .u16?
- # Less than 23 bits, so convertable to Float32 and Float64 without precision loss
+ # Less than 23 bits, so convertible to Float32 and Float64 without precision loss
true
when .i32?, .u32?
- # Less than 52 bits, so convertable to Float64 without precision loss
+ # Less than 52 bits, so convertible to Float64 without precision loss
other_type.kind.f64?
else
false
@@ -2756,17 +2760,9 @@ module Crystal
delegate lookup_defs, lookup_defs_with_modules, lookup_first_def,
lookup_macro, lookup_macros, to: aliased_type
- def types?
+ def lookup_name(name)
process_value
- if aliased_type = @aliased_type
- aliased_type.types?
- else
- nil
- end
- end
-
- def types
- types?.not_nil!
+ @aliased_type.try(&.lookup_name(name))
end
def remove_alias
diff --git a/src/compiler/crystal/util.cr b/src/compiler/crystal/util.cr
index c33bfa5d0d42..d0de6f226f36 100644
--- a/src/compiler/crystal/util.cr
+++ b/src/compiler/crystal/util.cr
@@ -41,7 +41,7 @@ module Crystal
source : String | Array(String),
highlight_line_number = nil,
color = false,
- line_number_start = 1
+ line_number_start = 1,
)
source = source.lines if source.is_a? String
line_number_padding = (source.size + line_number_start).to_s.chars.size
diff --git a/src/complex.cr b/src/complex.cr
index 65fbc9204b59..e2a5830b395a 100644
--- a/src/complex.cr
+++ b/src/complex.cr
@@ -237,14 +237,28 @@ struct Complex
# Divides `self` by *other*.
def /(other : Complex) : Complex
- if other.real <= other.imag
- r = other.real / other.imag
- d = other.imag + r * other.real
- Complex.new((@real * r + @imag) / d, (@imag * r - @real) / d)
- else
+ if other.real.nan? || other.imag.nan?
+ Complex.new(Float64::NAN, Float64::NAN)
+ elsif other.imag.abs < other.real.abs
r = other.imag / other.real
d = other.real + r * other.imag
- Complex.new((@real + @imag * r) / d, (@imag - @real * r) / d)
+
+ if d.nan? || d == 0
+ Complex.new(Float64::NAN, Float64::NAN)
+ else
+ Complex.new((@real + @imag * r) / d, (@imag - @real * r) / d)
+ end
+ elsif other.imag == 0 # other.real == 0
+ Complex.new(@real / other.real, @imag / other.real)
+ else # 0 < other.real.abs <= other.imag.abs
+ r = other.real / other.imag
+ d = other.imag + r * other.real
+
+ if d.nan? || d == 0
+ Complex.new(Float64::NAN, Float64::NAN)
+ else
+ Complex.new((@real * r + @imag) / d, (@imag * r - @real) / d)
+ end
end
end
diff --git a/src/concurrent.cr b/src/concurrent.cr
index 6f3a58291a22..0f8805857720 100644
--- a/src/concurrent.cr
+++ b/src/concurrent.cr
@@ -7,6 +7,7 @@ require "crystal/tracing"
#
# While this fiber is waiting this time, other ready-to-execute
# fibers might start their execution.
+@[Deprecated("Use `::sleep(Time::Span)` instead")]
def sleep(seconds : Number) : Nil
if seconds < 0
raise ArgumentError.new "Sleep seconds must be positive"
@@ -42,7 +43,7 @@ end
#
# spawn do
# 6.times do
-# sleep 1
+# sleep 1.second
# puts 1
# end
# ch.send(nil)
@@ -50,7 +51,7 @@ end
#
# spawn do
# 3.times do
-# sleep 2
+# sleep 2.seconds
# puts 2
# end
# ch.send(nil)
diff --git a/src/crystal/lib_iconv.cr b/src/crystal/lib_iconv.cr
index 5f1506758454..07100ff9c1dc 100644
--- a/src/crystal/lib_iconv.cr
+++ b/src/crystal/lib_iconv.cr
@@ -6,7 +6,7 @@ require "c/stddef"
@[Link("iconv")]
{% if compare_versions(Crystal::VERSION, "1.11.0-dev") >= 0 %}
- @[Link(dll: "libiconv.dll")]
+ @[Link(dll: "iconv-2.dll")]
{% end %}
lib LibIconv
type IconvT = Void*
diff --git a/src/crystal/once.cr b/src/crystal/once.cr
index 1e6243669809..56eea2be693a 100644
--- a/src/crystal/once.cr
+++ b/src/crystal/once.cr
@@ -11,9 +11,6 @@
# :nodoc:
class Crystal::OnceState
@rec = [] of Bool*
- {% if flag?(:preview_mt) %}
- @mutex = Mutex.new(:reentrant)
- {% end %}
def once(flag : Bool*, initializer : Void*)
unless flag.value
@@ -29,7 +26,13 @@ class Crystal::OnceState
end
end
- {% if flag?(:preview_mt) %}
+ # on Win32, `Crystal::System::FileDescriptor#@@reader_thread` spawns a new
+ # thread even without the `preview_mt` flag, and the thread can also reference
+ # Crystal constants, leading to race conditions, so we always enable the mutex
+ # TODO: can this be improved?
+ {% if flag?(:preview_mt) || flag?(:win32) %}
+ @mutex = Mutex.new(:reentrant)
+
def once(flag : Bool*, initializer : Void*)
unless flag.value
@mutex.synchronize do
diff --git a/src/crystal/pe.cr b/src/crystal/pe.cr
new file mode 100644
index 000000000000..d1b19401ad19
--- /dev/null
+++ b/src/crystal/pe.cr
@@ -0,0 +1,110 @@
+module Crystal
+ # :nodoc:
+ #
+ # Portable Executable reader.
+ #
+ # Documentation:
+ # -
+ struct PE
+ class Error < Exception
+ end
+
+ record SectionHeader, name : String, virtual_offset : UInt32, offset : UInt32, size : UInt32
+
+ record COFFSymbol, offset : UInt32, name : String
+
+ # addresses in COFF debug info are relative to this image base; used by
+ # `Exception::CallStack.read_dwarf_sections` to calculate the real relocated
+ # addresses
+ getter original_image_base : UInt64
+
+ @section_headers : Slice(SectionHeader)
+ @string_table_base : UInt32
+
+ # mapping from zero-based section index to list of symbols sorted by
+ # offsets within that section
+ getter coff_symbols = Hash(Int32, Array(COFFSymbol)).new
+
+ def self.open(path : String | ::Path, &)
+ File.open(path, "r") do |file|
+ yield new(file)
+ end
+ end
+
+ def initialize(@io : IO::FileDescriptor)
+ dos_header = uninitialized LibC::IMAGE_DOS_HEADER
+ io.read_fully(pointerof(dos_header).to_slice(1).to_unsafe_bytes)
+ raise Error.new("Invalid DOS header") unless dos_header.e_magic == 0x5A4D # MZ
+
+ io.seek(dos_header.e_lfanew)
+ nt_header = uninitialized LibC::IMAGE_NT_HEADERS
+ io.read_fully(pointerof(nt_header).to_slice(1).to_unsafe_bytes)
+ raise Error.new("Invalid PE header") unless nt_header.signature == 0x00004550 # PE\0\0
+
+ @original_image_base = nt_header.optionalHeader.imageBase
+ @string_table_base = nt_header.fileHeader.pointerToSymbolTable + nt_header.fileHeader.numberOfSymbols * sizeof(LibC::IMAGE_SYMBOL)
+
+ section_count = nt_header.fileHeader.numberOfSections
+ nt_section_headers = Pointer(LibC::IMAGE_SECTION_HEADER).malloc(section_count).to_slice(section_count)
+ io.read_fully(nt_section_headers.to_unsafe_bytes)
+
+ @section_headers = nt_section_headers.map do |nt_header|
+ if nt_header.name[0] === '/'
+ # section name is longer than 8 bytes; look up the COFF string table
+ name_buf = nt_header.name.to_slice + 1
+ string_offset = String.new(name_buf.to_unsafe, name_buf.index(0) || name_buf.size).to_i
+ io.seek(@string_table_base + string_offset)
+ name = io.gets('\0', chomp: true).not_nil!
+ else
+ name = String.new(nt_header.name.to_unsafe, nt_header.name.index(0) || nt_header.name.size)
+ end
+
+ SectionHeader.new(name: name, virtual_offset: nt_header.virtualAddress, offset: nt_header.pointerToRawData, size: nt_header.virtualSize)
+ end
+
+ io.seek(nt_header.fileHeader.pointerToSymbolTable)
+ image_symbol_count = nt_header.fileHeader.numberOfSymbols
+ image_symbols = Pointer(LibC::IMAGE_SYMBOL).malloc(image_symbol_count).to_slice(image_symbol_count)
+ io.read_fully(image_symbols.to_unsafe_bytes)
+
+ aux_count = 0
+ image_symbols.each_with_index do |sym, i|
+ if aux_count == 0
+ aux_count = sym.numberOfAuxSymbols.to_i
+ else
+ aux_count &-= 1
+ end
+
+ next unless aux_count == 0
+ next unless sym.type.bits_set?(0x20) # COFF function
+ next unless sym.sectionNumber > 0 # one-based section index
+ next unless sym.storageClass.in?(LibC::IMAGE_SYM_CLASS_EXTERNAL, LibC::IMAGE_SYM_CLASS_STATIC)
+
+ if sym.n.name.short == 0
+ io.seek(@string_table_base + sym.n.name.long)
+ name = io.gets('\0', chomp: true).not_nil!
+ else
+ name = String.new(sym.n.shortName.to_slice).rstrip('\0')
+ end
+
+ # `@coff_symbols` uses zero-based indices
+ section_coff_symbols = @coff_symbols.put_if_absent(sym.sectionNumber.to_i &- 1) { [] of COFFSymbol }
+ section_coff_symbols << COFFSymbol.new(sym.value, name)
+ end
+
+ # add one sentinel symbol to ensure binary search on the offsets works
+ @coff_symbols.each_with_index do |(_, symbols), i|
+ symbols.sort_by!(&.offset)
+ symbols << COFFSymbol.new(@section_headers[i].size, "??")
+ end
+ end
+
+ def read_section?(name : String, &)
+ if sh = @section_headers.find(&.name.== name)
+ @io.seek(sh.offset) do
+ yield sh, @io
+ end
+ end
+ end
+ end
+end
diff --git a/src/crystal/pointer_linked_list.cr b/src/crystal/pointer_linked_list.cr
index 03109979d662..cde9b0b79ddc 100644
--- a/src/crystal/pointer_linked_list.cr
+++ b/src/crystal/pointer_linked_list.cr
@@ -7,8 +7,8 @@ struct Crystal::PointerLinkedList(T)
module Node
macro included
- property previous : Pointer(self) = Pointer(self).null
- property next : Pointer(self) = Pointer(self).null
+ property previous : ::Pointer(self) = ::Pointer(self).null
+ property next : ::Pointer(self) = ::Pointer(self).null
end
end
diff --git a/src/crystal/scheduler.cr b/src/crystal/scheduler.cr
index d3634e9aea6a..bed98ef4d05b 100644
--- a/src/crystal/scheduler.cr
+++ b/src/crystal/scheduler.cr
@@ -24,6 +24,12 @@ class Crystal::Scheduler
Thread.current.scheduler.@event_loop
end
+ def self.event_loop?
+ if scheduler = Thread.current?.try(&.scheduler?)
+ scheduler.@event_loop
+ end
+ end
+
def self.enqueue(fiber : Fiber) : Nil
Crystal.trace :sched, "enqueue", fiber: fiber do
thread = Thread.current
diff --git a/src/crystal/spin_lock.cr b/src/crystal/spin_lock.cr
index 4255fcae7bbd..105c235e0c66 100644
--- a/src/crystal/spin_lock.cr
+++ b/src/crystal/spin_lock.cr
@@ -1,5 +1,5 @@
# :nodoc:
-class Crystal::SpinLock
+struct Crystal::SpinLock
private UNLOCKED = 0
private LOCKED = 1
diff --git a/src/crystal/system/addrinfo.cr b/src/crystal/system/addrinfo.cr
new file mode 100644
index 000000000000..ff9166f3aca1
--- /dev/null
+++ b/src/crystal/system/addrinfo.cr
@@ -0,0 +1,40 @@
+module Crystal::System::Addrinfo
+ # alias Handle
+
+ # protected def initialize(addrinfo : Handle)
+
+ # def system_ip_address : ::Socket::IPAddress
+
+ # def self.getaddrinfo(domain, service, family, type, protocol, timeout) : Handle
+
+ # def self.next_addrinfo(addrinfo : Handle) : Handle
+
+ # def self.free_addrinfo(addrinfo : Handle)
+
+ def self.getaddrinfo(domain, service, family, type, protocol, timeout, & : ::Socket::Addrinfo ->)
+ addrinfo = root = getaddrinfo(domain, service, family, type, protocol, timeout)
+
+ begin
+ while addrinfo
+ yield ::Socket::Addrinfo.new(addrinfo)
+ addrinfo = next_addrinfo(addrinfo)
+ end
+ ensure
+ free_addrinfo(root)
+ end
+ end
+end
+
+{% if flag?(:wasi) %}
+ require "./wasi/addrinfo"
+{% elsif flag?(:unix) %}
+ require "./unix/addrinfo"
+{% elsif flag?(:win32) %}
+ {% if flag?(:win7) %}
+ require "./win32/addrinfo_win7"
+ {% else %}
+ require "./win32/addrinfo"
+ {% end %}
+{% else %}
+ {% raise "No Crystal::System::Addrinfo implementation available" %}
+{% end %}
diff --git a/src/crystal/system/event_loop.cr b/src/crystal/system/event_loop.cr
index 46954e6034ff..fe973ec8c99e 100644
--- a/src/crystal/system/event_loop.cr
+++ b/src/crystal/system/event_loop.cr
@@ -17,6 +17,11 @@ abstract class Crystal::EventLoop
Crystal::Scheduler.event_loop
end
+ @[AlwaysInline]
+ def self.current? : self?
+ Crystal::Scheduler.event_loop?
+ end
+
# Runs the loop.
#
# Returns immediately if events are activable. Set `blocking` to false to
@@ -51,7 +56,7 @@ abstract class Crystal::EventLoop
abstract def free : Nil
# Adds a new timeout to this event.
- abstract def add(timeout : Time::Span?) : Nil
+ abstract def add(timeout : Time::Span) : Nil
end
end
diff --git a/src/crystal/system/event_loop/file_descriptor.cr b/src/crystal/system/event_loop/file_descriptor.cr
index a041263609d9..5fb6cbb95cb0 100644
--- a/src/crystal/system/event_loop/file_descriptor.cr
+++ b/src/crystal/system/event_loop/file_descriptor.cr
@@ -19,5 +19,13 @@ abstract class Crystal::EventLoop
# Closes the file descriptor resource.
abstract def close(file_descriptor : Crystal::System::FileDescriptor) : Nil
+
+ # Removes the file descriptor from the event loop. Can be used to free up
+ # memory resources associated with the file descriptor, as well as removing
+ # the file descriptor from kernel data structures.
+ #
+ # Called by `::IO::FileDescriptor#finalize` before closing the file
+ # descriptor. Errors shall be silently ignored.
+ abstract def remove(file_descriptor : Crystal::System::FileDescriptor) : Nil
end
end
diff --git a/src/crystal/system/event_loop/socket.cr b/src/crystal/system/event_loop/socket.cr
index e6f35478b487..6309aed391e0 100644
--- a/src/crystal/system/event_loop/socket.cr
+++ b/src/crystal/system/event_loop/socket.cr
@@ -62,5 +62,13 @@ abstract class Crystal::EventLoop
# Closes the socket.
abstract def close(socket : ::Socket) : Nil
+
+ # Removes the socket from the event loop. Can be used to free up memory
+ # resources associated with the socket, as well as removing the socket from
+ # kernel data structures.
+ #
+ # Called by `::Socket#finalize` before closing the socket. Errors shall be
+ # silently ignored.
+ abstract def remove(socket : ::Socket) : Nil
end
end
diff --git a/src/crystal/system/fiber.cr b/src/crystal/system/fiber.cr
index 1cc47e2917e1..1f15d2fe5535 100644
--- a/src/crystal/system/fiber.cr
+++ b/src/crystal/system/fiber.cr
@@ -1,12 +1,12 @@
module Crystal::System::Fiber
# Allocates memory for a stack.
- # def self.allocate_stack(stack_size : Int) : Void*
+ # def self.allocate_stack(stack_size : Int, protect : Bool) : Void*
+
+ # Prepares an existing, unused stack for use again.
+ # def self.reset_stack(stack : Void*, stack_size : Int, protect : Bool) : Nil
# Frees memory of a stack.
# def self.free_stack(stack : Void*, stack_size : Int) : Nil
-
- # Determines location of the top of the main process fiber's stack.
- # def self.main_fiber_stack(stack_bottom : Void*) : Void*
end
{% if flag?(:wasi) %}
diff --git a/src/crystal/system/file.cr b/src/crystal/system/file.cr
index 75985c107fd5..84dbd0fa5c98 100644
--- a/src/crystal/system/file.cr
+++ b/src/crystal/system/file.cr
@@ -65,7 +65,7 @@ module Crystal::System::File
io << suffix
end
- handle, errno = open(path, mode, perm)
+ handle, errno = open(path, mode, perm, blocking: true)
if error_is_none?(errno)
return {handle, path}
@@ -87,13 +87,6 @@ module Crystal::System::File
private def self.error_is_file_exists?(errno)
errno.in?(Errno::EEXIST, WinError::ERROR_FILE_EXISTS)
end
-
- # Closes the internal file descriptor without notifying libevent.
- # This is directly used after the fork of a process to close the
- # parent's Crystal::System::Signal.@@pipe reference before re initializing
- # the event loop. In the case of a fork that will exec there is even
- # no need to initialize the event loop at all.
- # def file_descriptor_close
end
{% if flag?(:wasi) %}
diff --git a/src/crystal/system/file_descriptor.cr b/src/crystal/system/file_descriptor.cr
index 0180627d59ce..03868bc07034 100644
--- a/src/crystal/system/file_descriptor.cr
+++ b/src/crystal/system/file_descriptor.cr
@@ -14,6 +14,23 @@ module Crystal::System::FileDescriptor
# cooked mode otherwise.
# private def system_raw(enable : Bool, & : ->)
+ # Closes the internal file descriptor without notifying the event loop.
+ # This is directly used after the fork of a process to close the
+ # parent's Crystal::System::Signal.@@pipe reference before re initializing
+ # the event loop. In the case of a fork that will exec there is even
+ # no need to initialize the event loop at all.
+ # Also used in `IO::FileDescriptor#finalize`.
+ # def file_descriptor_close
+
+ # Returns `true` or `false` if this file descriptor pretends to block or not
+ # to block the caller thread regardless of the underlying internal file
+ # descriptor's implementation. Returns `nil` if nothing needs to be done, i.e.
+ # `#blocking` is identical to `#system_blocking?`.
+ #
+ # Currently used by console STDIN on Windows.
+ private def emulated_blocking? : Bool?
+ end
+
private def system_read(slice : Bytes) : Int32
event_loop.read(self, slice)
end
@@ -22,6 +39,10 @@ module Crystal::System::FileDescriptor
event_loop.write(self, slice)
end
+ private def event_loop? : Crystal::EventLoop::FileDescriptor?
+ Crystal::EventLoop.current?
+ end
+
private def event_loop : Crystal::EventLoop::FileDescriptor
Crystal::EventLoop.current
end
diff --git a/src/crystal/system/group.cr b/src/crystal/system/group.cr
index dce631e8c1ab..6cb93739a900 100644
--- a/src/crystal/system/group.cr
+++ b/src/crystal/system/group.cr
@@ -1,7 +1,19 @@
+module Crystal::System::Group
+ # def system_name : String
+
+ # def system_id : String
+
+ # def self.from_name?(groupname : String) : ::System::Group?
+
+ # def self.from_id?(groupid : String) : ::System::Group?
+end
+
{% if flag?(:wasi) %}
require "./wasi/group"
{% elsif flag?(:unix) %}
require "./unix/group"
+{% elsif flag?(:win32) %}
+ require "./win32/group"
{% else %}
{% raise "No Crystal::System::Group implementation available" %}
{% end %}
diff --git a/src/crystal/system/print_error.cr b/src/crystal/system/print_error.cr
index 796579bf256a..b55e05e51ec6 100644
--- a/src/crystal/system/print_error.cr
+++ b/src/crystal/system/print_error.cr
@@ -23,7 +23,7 @@ module Crystal::System
String.each_utf16_char(bytes) do |char|
if appender.size > utf8.size - char.bytesize
# buffer is full (char won't fit)
- print_error utf8.to_slice[0...appender.size]
+ print_error appender.to_slice
appender = utf8.to_unsafe.appender
end
@@ -33,7 +33,7 @@ module Crystal::System
end
if appender.size > 0
- print_error utf8.to_slice[0...appender.size]
+ print_error appender.to_slice
end
end
diff --git a/src/crystal/system/random.cr b/src/crystal/system/random.cr
index 1a5b3c8f4677..ccf9d6dfa344 100644
--- a/src/crystal/system/random.cr
+++ b/src/crystal/system/random.cr
@@ -13,7 +13,12 @@ end
{% if flag?(:wasi) %}
require "./wasi/random"
{% elsif flag?(:linux) %}
- require "./unix/getrandom"
+ require "c/sys/random"
+ \{% if LibC.has_method?(:getrandom) %}
+ require "./unix/getrandom"
+ \{% else %}
+ require "./unix/urandom"
+ \{% end %}
{% elsif flag?(:bsd) || flag?(:darwin) %}
require "./unix/arc4random"
{% elsif flag?(:unix) %}
diff --git a/src/crystal/system/socket.cr b/src/crystal/system/socket.cr
index 2669b4c57bca..8d5e8c9afaf0 100644
--- a/src/crystal/system/socket.cr
+++ b/src/crystal/system/socket.cr
@@ -91,6 +91,18 @@ module Crystal::System::Socket
# private def system_close
+ # Closes the internal handle without notifying the event loop.
+ # This is directly used after the fork of a process to close the
+ # parent's Crystal::System::Signal.@@pipe reference before re initializing
+ # the event loop. In the case of a fork that will exec there is even
+ # no need to initialize the event loop at all.
+ # Also used in `Socket#finalize`
+ # def socket_close
+
+ private def event_loop? : Crystal::EventLoop::Socket?
+ Crystal::EventLoop.current?
+ end
+
private def event_loop : Crystal::EventLoop::Socket
Crystal::EventLoop.current
end
diff --git a/src/crystal/system/thread.cr b/src/crystal/system/thread.cr
index d9dc6acf17dc..0d6f5077633a 100644
--- a/src/crystal/system/thread.cr
+++ b/src/crystal/system/thread.cr
@@ -23,6 +23,14 @@ module Crystal::System::Thread
# private def stack_address : Void*
# private def system_name=(String) : String
+
+ # def self.init_suspend_resume : Nil
+
+ # private def system_suspend : Nil
+
+ # private def system_wait_suspended : Nil
+
+ # private def system_resume : Nil
end
{% if flag?(:wasi) %}
@@ -66,6 +74,14 @@ class Thread
@@threads.try(&.unsafe_each { |thread| yield thread })
end
+ def self.lock : Nil
+ threads.@mutex.lock
+ end
+
+ def self.unlock : Nil
+ threads.@mutex.unlock
+ end
+
# Creates and starts a new system thread.
def initialize(@name : String? = nil, &@func : Thread ->)
@system_handle = uninitialized Crystal::System::Thread::Handle
@@ -75,7 +91,7 @@ class Thread
# Used once to initialize the thread object representing the main thread of
# the process (that already exists).
def initialize
- @func = ->(t : Thread) {}
+ @func = ->(t : Thread) { }
@system_handle = Crystal::System::Thread.current_handle
@current_fiber = @main_fiber = Fiber.new(stack_address, self)
@@ -168,6 +184,26 @@ class Thread
# Holds the GC thread handler
property gc_thread_handler : Void* = Pointer(Void).null
+
+ def suspend : Nil
+ system_suspend
+ end
+
+ def wait_suspended : Nil
+ system_wait_suspended
+ end
+
+ def resume : Nil
+ system_resume
+ end
+
+ def self.stop_world : Nil
+ GC.stop_world
+ end
+
+ def self.start_world : Nil
+ GC.start_world
+ end
end
require "./thread_linked_list"
diff --git a/src/crystal/system/unix/addrinfo.cr b/src/crystal/system/unix/addrinfo.cr
new file mode 100644
index 000000000000..7f1e51558397
--- /dev/null
+++ b/src/crystal/system/unix/addrinfo.cr
@@ -0,0 +1,71 @@
+module Crystal::System::Addrinfo
+ alias Handle = LibC::Addrinfo*
+
+ @addr : LibC::SockaddrIn6
+
+ protected def initialize(addrinfo : Handle)
+ @family = ::Socket::Family.from_value(addrinfo.value.ai_family)
+ @type = ::Socket::Type.from_value(addrinfo.value.ai_socktype)
+ @protocol = ::Socket::Protocol.from_value(addrinfo.value.ai_protocol)
+ @size = addrinfo.value.ai_addrlen.to_i
+
+ @addr = uninitialized LibC::SockaddrIn6
+
+ case @family
+ when ::Socket::Family::INET6
+ addrinfo.value.ai_addr.as(LibC::SockaddrIn6*).copy_to(pointerof(@addr).as(LibC::SockaddrIn6*), 1)
+ when ::Socket::Family::INET
+ addrinfo.value.ai_addr.as(LibC::SockaddrIn*).copy_to(pointerof(@addr).as(LibC::SockaddrIn*), 1)
+ else
+ # TODO: (asterite) UNSPEC and UNIX unsupported?
+ end
+ end
+
+ def system_ip_address : ::Socket::IPAddress
+ ::Socket::IPAddress.from(to_unsafe, size)
+ end
+
+ def to_unsafe
+ pointerof(@addr).as(LibC::Sockaddr*)
+ end
+
+ def self.getaddrinfo(domain, service, family, type, protocol, timeout) : Handle
+ hints = LibC::Addrinfo.new
+ hints.ai_family = (family || ::Socket::Family::UNSPEC).to_i32
+ hints.ai_socktype = type
+ hints.ai_protocol = protocol
+ hints.ai_flags = 0
+
+ if service.is_a?(Int)
+ hints.ai_flags |= LibC::AI_NUMERICSERV
+ end
+
+ # On OS X < 10.12, the libsystem implementation of getaddrinfo segfaults
+ # if AI_NUMERICSERV is set, and servname is NULL or 0.
+ {% if flag?(:darwin) %}
+ if service.in?(0, nil) && (hints.ai_flags & LibC::AI_NUMERICSERV)
+ hints.ai_flags |= LibC::AI_NUMERICSERV
+ service = "00"
+ end
+ {% end %}
+
+ ret = LibC.getaddrinfo(domain, service.to_s, pointerof(hints), out ptr)
+ unless ret.zero?
+ if ret == LibC::EAI_SYSTEM
+ raise ::Socket::Addrinfo::Error.from_os_error nil, Errno.value, domain: domain
+ end
+
+ error = Errno.new(ret)
+ raise ::Socket::Addrinfo::Error.from_os_error(nil, error, domain: domain, type: type, protocol: protocol, service: service)
+ end
+ ptr
+ end
+
+ def self.next_addrinfo(addrinfo : Handle) : Handle
+ addrinfo.value.ai_next
+ end
+
+ def self.free_addrinfo(addrinfo : Handle)
+ LibC.freeaddrinfo(addrinfo)
+ end
+end
diff --git a/src/crystal/system/unix/dir.cr b/src/crystal/system/unix/dir.cr
index 5e66b33b65e7..72d1183dcc72 100644
--- a/src/crystal/system/unix/dir.cr
+++ b/src/crystal/system/unix/dir.cr
@@ -42,7 +42,12 @@ module Crystal::System::Dir
end
def self.info(dir, path) : ::File::Info
- Crystal::System::FileDescriptor.system_info LibC.dirfd(dir)
+ fd = {% if flag?(:netbsd) %}
+ dir.value.dd_fd
+ {% else %}
+ LibC.dirfd(dir)
+ {% end %}
+ Crystal::System::FileDescriptor.system_info(fd)
end
def self.close(dir, path) : Nil
diff --git a/src/crystal/system/unix/event_libevent.cr b/src/crystal/system/unix/event_libevent.cr
index 21d6765646d1..32578e5aba9a 100644
--- a/src/crystal/system/unix/event_libevent.cr
+++ b/src/crystal/system/unix/event_libevent.cr
@@ -19,16 +19,16 @@ module Crystal::LibEvent
@freed = false
end
- def add(timeout : Time::Span?) : Nil
- if timeout
- timeval = LibC::Timeval.new(
- tv_sec: LibC::TimeT.new(timeout.total_seconds),
- tv_usec: timeout.nanoseconds // 1_000
- )
- LibEvent2.event_add(@event, pointerof(timeval))
- else
- LibEvent2.event_add(@event, nil)
- end
+ def add(timeout : Time::Span) : Nil
+ timeval = LibC::Timeval.new(
+ tv_sec: LibC::TimeT.new(timeout.total_seconds),
+ tv_usec: timeout.nanoseconds // 1_000
+ )
+ LibEvent2.event_add(@event, pointerof(timeval))
+ end
+
+ def add(timeout : Nil) : Nil
+ LibEvent2.event_add(@event, nil)
end
def free : Nil
diff --git a/src/crystal/system/unix/event_loop_libevent.cr b/src/crystal/system/unix/event_loop_libevent.cr
index 32c9c8409b17..4594f07ffe66 100644
--- a/src/crystal/system/unix/event_loop_libevent.cr
+++ b/src/crystal/system/unix/event_loop_libevent.cr
@@ -4,6 +4,9 @@ require "./event_libevent"
class Crystal::LibEvent::EventLoop < Crystal::EventLoop
private getter(event_base) { Crystal::LibEvent::Event::Base.new }
+ def after_fork_before_exec : Nil
+ end
+
{% unless flag?(:preview_mt) %}
# Reinitializes the event loop after a fork.
def after_fork : Nil
@@ -70,7 +73,7 @@ class Crystal::LibEvent::EventLoop < Crystal::EventLoop
end
def read(file_descriptor : Crystal::System::FileDescriptor, slice : Bytes) : Int32
- file_descriptor.evented_read("Error reading file_descriptor") do
+ evented_read(file_descriptor, "Error reading file_descriptor") do
LibC.read(file_descriptor.fd, slice, slice.size).tap do |return_code|
if return_code == -1 && Errno.value == Errno::EBADF
raise IO::Error.new "File not open for reading", target: file_descriptor
@@ -80,7 +83,7 @@ class Crystal::LibEvent::EventLoop < Crystal::EventLoop
end
def write(file_descriptor : Crystal::System::FileDescriptor, slice : Bytes) : Int32
- file_descriptor.evented_write("Error writing file_descriptor") do
+ evented_write(file_descriptor, "Error writing file_descriptor") do
LibC.write(file_descriptor.fd, slice, slice.size).tap do |return_code|
if return_code == -1 && Errno.value == Errno::EBADF
raise IO::Error.new "File not open for writing", target: file_descriptor
@@ -93,14 +96,17 @@ class Crystal::LibEvent::EventLoop < Crystal::EventLoop
file_descriptor.evented_close
end
+ def remove(file_descriptor : Crystal::System::FileDescriptor) : Nil
+ end
+
def read(socket : ::Socket, slice : Bytes) : Int32
- socket.evented_read("Error reading socket") do
+ evented_read(socket, "Error reading socket") do
LibC.recv(socket.fd, slice, slice.size, 0).to_i32
end
end
def write(socket : ::Socket, slice : Bytes) : Int32
- socket.evented_write("Error writing to socket") do
+ evented_write(socket, "Error writing to socket") do
LibC.send(socket.fd, slice, slice.size, 0).to_i32
end
end
@@ -114,7 +120,7 @@ class Crystal::LibEvent::EventLoop < Crystal::EventLoop
addrlen = LibC::SocklenT.new(sizeof(LibC::SockaddrStorage))
- bytes_read = socket.evented_read("Error receiving datagram") do
+ bytes_read = evented_read(socket, "Error receiving datagram") do
LibC.recvfrom(socket.fd, slice, slice.size, 0, sockaddr, pointerof(addrlen))
end
@@ -185,4 +191,44 @@ class Crystal::LibEvent::EventLoop < Crystal::EventLoop
def close(socket : ::Socket) : Nil
socket.evented_close
end
+
+ def remove(socket : ::Socket) : Nil
+ end
+
+ def evented_read(target, errno_msg : String, &) : Int32
+ loop do
+ bytes_read = yield
+ if bytes_read != -1
+ # `to_i32` is acceptable because `Slice#size` is an Int32
+ return bytes_read.to_i32
+ end
+
+ if Errno.value == Errno::EAGAIN
+ target.wait_readable
+ else
+ raise IO::Error.from_errno(errno_msg, target: target)
+ end
+ end
+ ensure
+ target.evented_resume_pending_readers
+ end
+
+ def evented_write(target, errno_msg : String, &) : Int32
+ begin
+ loop do
+ bytes_written = yield
+ if bytes_written != -1
+ return bytes_written.to_i32
+ end
+
+ if Errno.value == Errno::EAGAIN
+ target.wait_writable
+ else
+ raise IO::Error.from_errno(errno_msg, target: target)
+ end
+ end
+ ensure
+ target.evented_resume_pending_writers
+ end
+ end
end
diff --git a/src/crystal/system/unix/fiber.cr b/src/crystal/system/unix/fiber.cr
index 317a3f7fbd41..42153b28bed2 100644
--- a/src/crystal/system/unix/fiber.cr
+++ b/src/crystal/system/unix/fiber.cr
@@ -21,6 +21,9 @@ module Crystal::System::Fiber
pointer
end
+ def self.reset_stack(stack : Void*, stack_size : Int, protect : Bool) : Nil
+ end
+
def self.free_stack(stack : Void*, stack_size) : Nil
LibC.munmap(stack, stack_size)
end
diff --git a/src/crystal/system/unix/file.cr b/src/crystal/system/unix/file.cr
index a353cf29cd3c..a049659e684f 100644
--- a/src/crystal/system/unix/file.cr
+++ b/src/crystal/system/unix/file.cr
@@ -3,10 +3,10 @@ require "file/error"
# :nodoc:
module Crystal::System::File
- def self.open(filename : String, mode : String, perm : Int32 | ::File::Permissions)
+ def self.open(filename : String, mode : String, perm : Int32 | ::File::Permissions, blocking)
perm = ::File::Permissions.new(perm) if perm.is_a? Int32
- fd, errno = open(filename, open_flag(mode), perm)
+ fd, errno = open(filename, open_flag(mode), perm, blocking)
unless errno.none?
raise ::File::Error.from_os_error("Error opening file with mode '#{mode}'", errno, file: filename)
@@ -15,7 +15,7 @@ module Crystal::System::File
fd
end
- def self.open(filename : String, flags : Int32, perm : ::File::Permissions) : {LibC::Int, Errno}
+ def self.open(filename : String, flags : Int32, perm : ::File::Permissions, blocking _blocking) : {LibC::Int, Errno}
filename.check_no_null_byte
flags |= LibC::O_CLOEXEC
@@ -24,6 +24,9 @@ module Crystal::System::File
{fd, fd < 0 ? Errno.value : Errno::NONE}
end
+ protected def system_set_mode(mode : String)
+ end
+
def self.info?(path : String, follow_symlinks : Bool) : ::File::Info?
stat = uninitialized LibC::Stat
if follow_symlinks
diff --git a/src/crystal/system/unix/file_descriptor.cr b/src/crystal/system/unix/file_descriptor.cr
index 0c3ece9cfff8..60515b701136 100644
--- a/src/crystal/system/unix/file_descriptor.cr
+++ b/src/crystal/system/unix/file_descriptor.cr
@@ -120,7 +120,14 @@ module Crystal::System::FileDescriptor
file_descriptor_close
end
- def file_descriptor_close : Nil
+ def file_descriptor_close(&) : Nil
+ # It would usually be set by IO::Buffered#unbuffered_close but we sometimes
+ # close file descriptors directly (i.e. signal/process pipes) and the IO
+ # object wouldn't be marked as closed, leading IO::FileDescriptor#finalize
+ # to try to close the fd again (pointless) and lead to other issues if we
+ # try to do more cleanup in the finalizer (error)
+ @closed = true
+
# Clear the @volatile_fd before actually closing it in order to
# reduce the chance of reading an outdated fd value
_fd = @volatile_fd.swap(-1)
@@ -130,11 +137,17 @@ module Crystal::System::FileDescriptor
when Errno::EINTR, Errno::EINPROGRESS
# ignore
else
- raise IO::Error.from_errno("Error closing file", target: self)
+ yield
end
end
end
+ def file_descriptor_close
+ file_descriptor_close do
+ raise IO::Error.from_errno("Error closing file", target: self)
+ end
+ end
+
private def system_flock_shared(blocking)
flock LibC::FlockOp::SH, blocking
end
@@ -152,7 +165,7 @@ module Crystal::System::FileDescriptor
if retry
until flock(op)
- sleep 0.1
+ sleep 0.1.seconds
end
else
flock(op) || raise IO::Error.from_errno("Error applying file lock: file is already locked", target: self)
@@ -190,6 +203,14 @@ module Crystal::System::FileDescriptor
end
def self.pipe(read_blocking, write_blocking)
+ pipe_fds = system_pipe
+ r = IO::FileDescriptor.new(pipe_fds[0], read_blocking)
+ w = IO::FileDescriptor.new(pipe_fds[1], write_blocking)
+ w.sync = true
+ {r, w}
+ end
+
+ def self.system_pipe : StaticArray(LibC::Int, 2)
pipe_fds = uninitialized StaticArray(LibC::Int, 2)
{% if LibC.has_method?(:pipe2) %}
@@ -206,18 +227,14 @@ module Crystal::System::FileDescriptor
end
{% end %}
- r = IO::FileDescriptor.new(pipe_fds[0], read_blocking)
- w = IO::FileDescriptor.new(pipe_fds[1], write_blocking)
- w.sync = true
-
- {r, w}
+ pipe_fds
end
- def self.pread(fd, buffer, offset)
- bytes_read = LibC.pread(fd, buffer, buffer.size, offset).to_i64
+ def self.pread(file, buffer, offset)
+ bytes_read = LibC.pread(file.fd, buffer, buffer.size, offset).to_i64
if bytes_read == -1
- raise IO::Error.from_errno "Error reading file"
+ raise IO::Error.from_errno("Error reading file", target: file)
end
bytes_read
@@ -242,6 +259,20 @@ module Crystal::System::FileDescriptor
io
end
+ # Helper to write *size* values at *pointer* to a given *fd*.
+ def self.write_fully(fd : LibC::Int, pointer : Pointer, size : Int32 = 1) : Nil
+ write_fully(fd, Slice.new(pointer, size).unsafe_slice_of(UInt8))
+ end
+
+ # Helper to fully write a slice to a given *fd*.
+ def self.write_fully(fd : LibC::Int, slice : Slice(UInt8)) : Nil
+ until slice.size == 0
+ size = LibC.write(fd, slice, slice.size)
+ break if size == -1
+ slice += size
+ end
+ end
+
private def system_echo(enable : Bool, mode = nil)
new_mode = mode || FileDescriptor.tcgetattr(fd)
flags = LibC::ECHO | LibC::ECHOE | LibC::ECHOK | LibC::ECHONL
diff --git a/src/crystal/system/unix/getrandom.cr b/src/crystal/system/unix/getrandom.cr
index 229716a3d846..6ad217c7cbf2 100644
--- a/src/crystal/system/unix/getrandom.cr
+++ b/src/crystal/system/unix/getrandom.cr
@@ -1,116 +1,39 @@
-{% skip_file unless flag?(:linux) %}
-
-require "c/unistd"
-require "./syscall"
-
-{% if flag?(:interpreted) %}
- lib LibC
- fun getrandom(buf : Void*, buflen : SizeT, flags : UInt32) : LibC::SSizeT
- end
-
- module Crystal::System::Syscall
- GRND_NONBLOCK = 1u32
-
- # TODO: Implement syscall for interpreter
- def self.getrandom(buf : UInt8*, buflen : LibC::SizeT, flags : UInt32) : LibC::SSizeT
- LibC.getrandom(buf, buflen, flags)
- end
- end
-{% end %}
+require "c/sys/random"
module Crystal::System::Random
- @@initialized = false
- @@getrandom_available = false
- @@urandom : ::File?
-
- private def self.init
- @@initialized = true
-
- if has_sys_getrandom
- @@getrandom_available = true
- else
- urandom = ::File.open("/dev/urandom", "r")
- return unless urandom.info.type.character_device?
-
- urandom.close_on_exec = true
- urandom.read_buffering = false # don't buffer bytes
- @@urandom = urandom
- end
- end
-
- private def self.has_sys_getrandom
- sys_getrandom(Bytes.new(16))
- true
- rescue
- false
- end
-
# Reads n random bytes using the Linux `getrandom(2)` syscall.
- def self.random_bytes(buf : Bytes) : Nil
- init unless @@initialized
-
- if @@getrandom_available
- getrandom(buf)
- elsif urandom = @@urandom
- urandom.read_fully(buf)
- else
- raise "Failed to access secure source to generate random bytes!"
- end
+ def self.random_bytes(buffer : Bytes) : Nil
+ getrandom(buffer)
end
def self.next_u : UInt8
- init unless @@initialized
-
- if @@getrandom_available
- buf = uninitialized UInt8
- getrandom(pointerof(buf).to_slice(1))
- buf
- elsif urandom = @@urandom
- urandom.read_byte.not_nil!
- else
- raise "Failed to access secure source to generate random bytes!"
- end
+ buffer = uninitialized UInt8
+ getrandom(pointerof(buffer).to_slice(1))
+ buffer
end
# Reads n random bytes using the Linux `getrandom(2)` syscall.
- private def self.getrandom(buf)
+ private def self.getrandom(buffer)
# getrandom(2) may only read up to 256 bytes at once without being
# interrupted or returning early
chunk_size = 256
- while buf.size > 0
- if buf.size < chunk_size
- chunk_size = buf.size
- end
+ while buffer.size > 0
+ read_bytes = 0
- read_bytes = sys_getrandom(buf[0, chunk_size])
+ loop do
+ # pass GRND_NONBLOCK flag so that it fails with EAGAIN if the requested
+ # entropy was not available
+ read_bytes = LibC.getrandom(buffer, buffer.size.clamp(..chunk_size), LibC::GRND_NONBLOCK)
+ break unless read_bytes == -1
- buf += read_bytes
- end
- end
+ err = Errno.value
+ raise RuntimeError.from_os_error("getrandom", err) unless err.in?(Errno::EINTR, Errno::EAGAIN)
- # Low-level wrapper for the `getrandom(2)` syscall, returns the number of
- # bytes read or the errno as a negative number if an error occurred (or the
- # syscall isn't available). The GRND_NONBLOCK=1 flag is passed as last argument,
- # so that it returns -EAGAIN if the requested entropy was not available.
- #
- # We use the kernel syscall instead of the `getrandom` C function so any
- # binary compiled for Linux will always use getrandom if the kernel is 3.17+
- # and silently fallback to read from /dev/urandom if not (so it's more
- # portable).
- private def self.sys_getrandom(buf : Bytes)
- loop do
- read_bytes = Syscall.getrandom(buf.to_unsafe, LibC::SizeT.new(buf.size), Syscall::GRND_NONBLOCK)
- if read_bytes < 0
- err = Errno.new(-read_bytes.to_i)
- if err.in?(Errno::EINTR, Errno::EAGAIN)
- ::Fiber.yield
- else
- raise RuntimeError.from_os_error("getrandom", err)
- end
- else
- return read_bytes
+ ::Fiber.yield
end
+
+ buffer += read_bytes
end
end
end
diff --git a/src/crystal/system/unix/group.cr b/src/crystal/system/unix/group.cr
index d7d408f77608..d4562cc7d286 100644
--- a/src/crystal/system/unix/group.cr
+++ b/src/crystal/system/unix/group.cr
@@ -4,11 +4,22 @@ require "../unix"
module Crystal::System::Group
private GETGR_R_SIZE_MAX = 1024 * 16
- private def from_struct(grp)
- new(String.new(grp.gr_name), grp.gr_gid.to_s)
+ def initialize(@name : String, @id : String)
end
- private def from_name?(groupname : String)
+ def system_name
+ @name
+ end
+
+ def system_id
+ @id
+ end
+
+ private def self.from_struct(grp)
+ ::System::Group.new(String.new(grp.gr_name), grp.gr_gid.to_s)
+ end
+
+ def self.from_name?(groupname : String)
groupname.check_no_null_byte
grp = uninitialized LibC::Group
@@ -21,7 +32,7 @@ module Crystal::System::Group
end
end
- private def from_id?(groupid : String)
+ def self.from_id?(groupid : String)
groupid = groupid.to_u32?
return unless groupid
diff --git a/src/crystal/system/unix/process.cr b/src/crystal/system/unix/process.cr
index 83f95cc8648c..0eb58231900e 100644
--- a/src/crystal/system/unix/process.cr
+++ b/src/crystal/system/unix/process.cr
@@ -176,7 +176,14 @@ struct Crystal::System::Process
newmask = uninitialized LibC::SigsetT
oldmask = uninitialized LibC::SigsetT
+ # block signals while we fork, so the child process won't forward signals it
+ # may receive to the parent through the signal pipe, but make sure to not
+ # block stop-the-world signals as it appears to create deadlocks in glibc
+ # for example; this is safe because these signal handlers musn't be
+ # registered through `Signal.trap` but directly through `sigaction`.
LibC.sigfillset(pointerof(newmask))
+ LibC.sigdelset(pointerof(newmask), System::Thread.sig_suspend)
+ LibC.sigdelset(pointerof(newmask), System::Thread.sig_resume)
ret = LibC.pthread_sigmask(LibC::SIG_SETMASK, pointerof(newmask), pointerof(oldmask))
raise RuntimeError.from_errno("Failed to disable signals") unless ret == 0
@@ -185,6 +192,9 @@ struct Crystal::System::Process
# child:
pid = nil
if will_exec
+ # notify event loop
+ Crystal::EventLoop.current.after_fork_before_exec
+
# reset signal handlers, then sigmask (inherited on exec):
Crystal::System::Signal.after_fork_before_exec
LibC.sigemptyset(pointerof(newmask))
@@ -231,43 +241,47 @@ struct Crystal::System::Process
end
def self.spawn(command_args, env, clear_env, input, output, error, chdir)
- reader_pipe, writer_pipe = IO.pipe
+ r, w = FileDescriptor.system_pipe
pid = self.fork(will_exec: true)
if !pid
+ LibC.close(r)
begin
- reader_pipe.close
- writer_pipe.close_on_exec = true
self.try_replace(command_args, env, clear_env, input, output, error, chdir)
- writer_pipe.write_byte(1)
- writer_pipe.write_bytes(Errno.value.to_i)
+ byte = 1_u8
+ errno = Errno.value.to_i32
+ FileDescriptor.write_fully(w, pointerof(byte))
+ FileDescriptor.write_fully(w, pointerof(errno))
rescue ex
- writer_pipe.write_byte(0)
- writer_pipe.write_bytes(ex.message.try(&.bytesize) || 0)
- writer_pipe << ex.message
- writer_pipe.close
+ byte = 0_u8
+ message = ex.inspect_with_backtrace
+ FileDescriptor.write_fully(w, pointerof(byte))
+ FileDescriptor.write_fully(w, message.to_slice)
ensure
+ LibC.close(w)
LibC._exit 127
end
end
- writer_pipe.close
+ LibC.close(w)
+ reader_pipe = IO::FileDescriptor.new(r, blocking: false)
+
begin
case reader_pipe.read_byte
when nil
# Pipe was closed, no error
when 0
# Error message coming
- message_size = reader_pipe.read_bytes(Int32)
- if message_size > 0
- message = String.build(message_size) { |io| IO.copy(reader_pipe, io, message_size) }
- end
- reader_pipe.close
+ message = reader_pipe.gets_to_end
raise RuntimeError.new("Error executing process: '#{command_args[0]}': #{message}")
when 1
# Errno coming
- errno = Errno.new(reader_pipe.read_bytes(Int32))
- self.raise_exception_from_errno(command_args[0], errno)
+ # can't use IO#read_bytes(Int32) because we skipped system/network
+ # endianness check when writing the integer while read_bytes would;
+ # we thus read it in the same as order as written
+ buf = uninitialized StaticArray(UInt8, 4)
+ reader_pipe.read_fully(buf.to_slice)
+ raise_exception_from_errno(command_args[0], Errno.new(buf.unsafe_as(Int32)))
else
raise RuntimeError.new("BUG: Invalid error response received from subprocess")
end
@@ -338,15 +352,17 @@ struct Crystal::System::Process
private def self.reopen_io(src_io : IO::FileDescriptor, dst_io : IO::FileDescriptor)
if src_io.closed?
- dst_io.close
- return
- end
+ dst_io.file_descriptor_close
+ else
+ src_io = to_real_fd(src_io)
- src_io = to_real_fd(src_io)
+ # dst_io.reopen(src_io)
+ ret = LibC.dup2(src_io.fd, dst_io.fd)
+ raise IO::Error.from_errno("dup2") if ret == -1
- dst_io.reopen(src_io)
- dst_io.blocking = true
- dst_io.close_on_exec = false
+ dst_io.blocking = true
+ dst_io.close_on_exec = false
+ end
end
private def self.to_real_fd(fd : IO::FileDescriptor)
diff --git a/src/crystal/system/unix/pthread.cr b/src/crystal/system/unix/pthread.cr
index d38e52ee012a..73aa2a652ca1 100644
--- a/src/crystal/system/unix/pthread.cr
+++ b/src/crystal/system/unix/pthread.cr
@@ -1,5 +1,6 @@
require "c/pthread"
require "c/sched"
+require "../panic"
module Crystal::System::Thread
alias Handle = LibC::PthreadT
@@ -8,20 +9,35 @@ module Crystal::System::Thread
@system_handle
end
+ protected setter system_handle
+
private def init_handle
- # NOTE: the thread may start before `pthread_create` returns, so
- # `@system_handle` must be set as soon as possible; we cannot use a separate
- # handle and assign it to `@system_handle`, which would have been too late
+ # NOTE: `@system_handle` needs to be set here too, not just in
+ # `.thread_proc`, since the current thread might progress first; the value
+ # of `LibC.pthread_self` inside the new thread must be equal to this
+ # `@system_handle` after `pthread_create` returns
ret = GC.pthread_create(
thread: pointerof(@system_handle),
attr: Pointer(LibC::PthreadAttrT).null,
- start: ->(data : Void*) { data.as(::Thread).start; Pointer(Void).null },
+ start: ->Thread.thread_proc(Void*),
arg: self.as(Void*),
)
raise RuntimeError.from_os_error("pthread_create", Errno.new(ret)) unless ret == 0
end
+ def self.thread_proc(data : Void*) : Void*
+ th = data.as(::Thread)
+
+ # `#start` calls `#stack_address`, which might read `@system_handle` before
+ # `GC.pthread_create` updates it in the original thread that spawned the
+ # current one, so we also assign to it here
+ th.system_handle = current_handle
+
+ th.start
+ Pointer(Void).null
+ end
+
def self.current_handle : Handle
LibC.pthread_self
end
@@ -115,11 +131,26 @@ module Crystal::System::Thread
ret = LibC.pthread_attr_destroy(pointerof(attr))
raise RuntimeError.from_os_error("pthread_attr_destroy", Errno.new(ret)) unless ret == 0
{% elsif flag?(:linux) %}
- if LibC.pthread_getattr_np(@system_handle, out attr) == 0
- LibC.pthread_attr_getstack(pointerof(attr), pointerof(address), out _)
- end
+ ret = LibC.pthread_getattr_np(@system_handle, out attr)
+ raise RuntimeError.from_os_error("pthread_getattr_np", Errno.new(ret)) unless ret == 0
+
+ LibC.pthread_attr_getstack(pointerof(attr), pointerof(address), out stack_size)
+
ret = LibC.pthread_attr_destroy(pointerof(attr))
raise RuntimeError.from_os_error("pthread_attr_destroy", Errno.new(ret)) unless ret == 0
+
+ # with musl-libc, the main thread does not respect `rlimit -Ss` and
+ # instead returns the same default stack size as non-default threads, so
+ # we obtain the rlimit to correct the stack address manually
+ {% if flag?(:musl) %}
+ if Thread.current_is_main?
+ if LibC.getrlimit(LibC::RLIMIT_STACK, out rlim) == 0
+ address = address + stack_size - rlim.rlim_cur
+ else
+ raise RuntimeError.from_errno("getrlimit")
+ end
+ end
+ {% end %}
{% elsif flag?(:openbsd) %}
ret = LibC.pthread_stackseg_np(@system_handle, out stack)
raise RuntimeError.from_os_error("pthread_stackseg_np", Errno.new(ret)) unless ret == 0
@@ -137,6 +168,14 @@ module Crystal::System::Thread
address
end
+ {% if flag?(:musl) %}
+ @@main_handle : Handle = current_handle
+
+ def self.current_is_main?
+ current_handle == @@main_handle
+ end
+ {% end %}
+
# Warning: must be called from the current thread itself, because Darwin
# doesn't allow to set the name of any thread but the current one!
private def system_name=(name : String) : String
@@ -153,6 +192,97 @@ module Crystal::System::Thread
{% end %}
name
end
+
+ @suspended = Atomic(Bool).new(false)
+
+ def self.init_suspend_resume : Nil
+ install_sig_suspend_signal_handler
+ install_sig_resume_signal_handler
+ end
+
+ private def self.install_sig_suspend_signal_handler
+ action = LibC::Sigaction.new
+ action.sa_flags = LibC::SA_SIGINFO
+ action.sa_sigaction = LibC::SigactionHandlerT.new do |_, _, _|
+ # notify that the thread has been interrupted
+ Thread.current_thread.@suspended.set(true)
+
+ # block all signals but SIG_RESUME
+ mask = uninitialized LibC::SigsetT
+ LibC.sigfillset(pointerof(mask))
+ LibC.sigdelset(pointerof(mask), SIG_RESUME)
+
+ # suspend the thread until it receives the SIG_RESUME signal
+ LibC.sigsuspend(pointerof(mask))
+ end
+ LibC.sigemptyset(pointerof(action.@sa_mask))
+ LibC.sigaction(SIG_SUSPEND, pointerof(action), nil)
+ end
+
+ private def self.install_sig_resume_signal_handler
+ action = LibC::Sigaction.new
+ action.sa_flags = 0
+ action.sa_sigaction = LibC::SigactionHandlerT.new do |_, _, _|
+ # do nothing (a handler is still required to receive the signal)
+ end
+ LibC.sigemptyset(pointerof(action.@sa_mask))
+ LibC.sigaction(SIG_RESUME, pointerof(action), nil)
+ end
+
+ private def system_suspend : Nil
+ @suspended.set(false)
+
+ if LibC.pthread_kill(@system_handle, SIG_SUSPEND) == -1
+ System.panic("pthread_kill()", Errno.value)
+ end
+ end
+
+ private def system_wait_suspended : Nil
+ until @suspended.get
+ Thread.yield_current
+ end
+ end
+
+ private def system_resume : Nil
+ if LibC.pthread_kill(@system_handle, SIG_RESUME) == -1
+ System.panic("pthread_kill()", Errno.value)
+ end
+ end
+
+ # the suspend/resume signals try to follow BDWGC but aren't exact (e.g. it may
+ # use SIGUSR1 and SIGUSR2 on FreeBSD instead of SIGRT).
+
+ private SIG_SUSPEND =
+ {% if flag?(:linux) %}
+ LibC::SIGPWR
+ {% elsif LibC.has_constant?(:SIGRTMIN) %}
+ LibC::SIGRTMIN + 6
+ {% else %}
+ LibC::SIGXFSZ
+ {% end %}
+
+ private SIG_RESUME =
+ {% if LibC.has_constant?(:SIGRTMIN) %}
+ LibC::SIGRTMIN + 5
+ {% else %}
+ LibC::SIGXCPU
+ {% end %}
+
+ def self.sig_suspend : ::Signal
+ if GC.responds_to?(:sig_suspend)
+ GC.sig_suspend
+ else
+ ::Signal.new(SIG_SUSPEND)
+ end
+ end
+
+ def self.sig_resume : ::Signal
+ if GC.responds_to?(:sig_resume)
+ GC.sig_resume
+ else
+ ::Signal.new(SIG_RESUME)
+ end
+ end
end
# In musl (alpine) the calls to unwind API segfaults
diff --git a/src/crystal/system/unix/socket.cr b/src/crystal/system/unix/socket.cr
index 33ac70659b9f..7c39e140849c 100644
--- a/src/crystal/system/unix/socket.cr
+++ b/src/crystal/system/unix/socket.cr
@@ -208,6 +208,10 @@ module Crystal::System::Socket
# always lead to undefined results. This is not specific to libevent.
event_loop.close(self)
+ socket_close
+ end
+
+ private def socket_close(&)
# Clear the @volatile_fd before actually closing it in order to
# reduce the chance of reading an outdated fd value
fd = @volatile_fd.swap(-1)
@@ -219,11 +223,17 @@ module Crystal::System::Socket
when Errno::EINTR, Errno::EINPROGRESS
# ignore
else
- raise ::Socket::Error.from_errno("Error closing socket")
+ yield
end
end
end
+ private def socket_close
+ socket_close do
+ raise ::Socket::Error.from_errno("Error closing socket")
+ end
+ end
+
private def system_local_address
sockaddr6 = uninitialized LibC::SockaddrIn6
sockaddr = pointerof(sockaddr6).as(LibC::Sockaddr*)
diff --git a/src/crystal/system/unix/urandom.cr b/src/crystal/system/unix/urandom.cr
index 7ac025f43e6b..fe81129a8ade 100644
--- a/src/crystal/system/unix/urandom.cr
+++ b/src/crystal/system/unix/urandom.cr
@@ -1,5 +1,3 @@
-{% skip_file unless flag?(:unix) && !flag?(:netbsd) && !flag?(:openbsd) && !flag?(:linux) %}
-
module Crystal::System::Random
@@initialized = false
@@urandom : ::File?
diff --git a/src/crystal/system/unix/user.cr b/src/crystal/system/unix/user.cr
index 8e4f16e8c1c4..c1f91d0f118c 100644
--- a/src/crystal/system/unix/user.cr
+++ b/src/crystal/system/unix/user.cr
@@ -4,14 +4,41 @@ require "../unix"
module Crystal::System::User
GETPW_R_SIZE_MAX = 1024 * 16
- private def from_struct(pwd)
+ def initialize(@username : String, @id : String, @group_id : String, @name : String, @home_directory : String, @shell : String)
+ end
+
+ def system_username
+ @username
+ end
+
+ def system_id
+ @id
+ end
+
+ def system_group_id
+ @group_id
+ end
+
+ def system_name
+ @name
+ end
+
+ def system_home_directory
+ @home_directory
+ end
+
+ def system_shell
+ @shell
+ end
+
+ private def self.from_struct(pwd)
username = String.new(pwd.pw_name)
# `pw_gecos` is not part of POSIX and bionic for example always leaves it null
user = pwd.pw_gecos ? String.new(pwd.pw_gecos).partition(',')[0] : username
- new(username, pwd.pw_uid.to_s, pwd.pw_gid.to_s, user, String.new(pwd.pw_dir), String.new(pwd.pw_shell))
+ ::System::User.new(username, pwd.pw_uid.to_s, pwd.pw_gid.to_s, user, String.new(pwd.pw_dir), String.new(pwd.pw_shell))
end
- private def from_username?(username : String)
+ def self.from_username?(username : String)
username.check_no_null_byte
pwd = uninitialized LibC::Passwd
@@ -24,7 +51,7 @@ module Crystal::System::User
end
end
- private def from_id?(id : String)
+ def self.from_id?(id : String)
id = id.to_u32?
return unless id
diff --git a/src/crystal/system/user.cr b/src/crystal/system/user.cr
index ecee92c8dcb5..88766496a9d8 100644
--- a/src/crystal/system/user.cr
+++ b/src/crystal/system/user.cr
@@ -1,7 +1,27 @@
+module Crystal::System::User
+ # def system_username : String
+
+ # def system_id : String
+
+ # def system_group_id : String
+
+ # def system_name : String
+
+ # def system_home_directory : String
+
+ # def system_shell : String
+
+ # def self.from_username?(username : String) : ::System::User?
+
+ # def self.from_id?(id : String) : ::System::User?
+end
+
{% if flag?(:wasi) %}
require "./wasi/user"
{% elsif flag?(:unix) %}
require "./unix/user"
+{% elsif flag?(:win32) %}
+ require "./win32/user"
{% else %}
{% raise "No Crystal::System::User implementation available" %}
{% end %}
diff --git a/src/crystal/system/wasi/addrinfo.cr b/src/crystal/system/wasi/addrinfo.cr
new file mode 100644
index 000000000000..29ba8e0b3cfc
--- /dev/null
+++ b/src/crystal/system/wasi/addrinfo.cr
@@ -0,0 +1,27 @@
+module Crystal::System::Addrinfo
+ alias Handle = NoReturn
+
+ protected def initialize(addrinfo : Handle)
+ raise NotImplementedError.new("Crystal::System::Addrinfo#initialize")
+ end
+
+ def system_ip_address : ::Socket::IPAddress
+ raise NotImplementedError.new("Crystal::System::Addrinfo#system_ip_address")
+ end
+
+ def to_unsafe
+ raise NotImplementedError.new("Crystal::System::Addrinfo#to_unsafe")
+ end
+
+ def self.getaddrinfo(domain, service, family, type, protocol, timeout) : Handle
+ raise NotImplementedError.new("Crystal::System::Addrinfo.getaddrinfo")
+ end
+
+ def self.next_addrinfo(addrinfo : Handle) : Handle
+ raise NotImplementedError.new("Crystal::System::Addrinfo.next_addrinfo")
+ end
+
+ def self.free_addrinfo(addrinfo : Handle)
+ raise NotImplementedError.new("Crystal::System::Addrinfo.free_addrinfo")
+ end
+end
diff --git a/src/crystal/system/wasi/event_loop.cr b/src/crystal/system/wasi/event_loop.cr
index 5aaf54452571..3cce9ba8361c 100644
--- a/src/crystal/system/wasi/event_loop.cr
+++ b/src/crystal/system/wasi/event_loop.cr
@@ -30,7 +30,7 @@ class Crystal::Wasi::EventLoop < Crystal::EventLoop
end
def read(file_descriptor : Crystal::System::FileDescriptor, slice : Bytes) : Int32
- file_descriptor.evented_read("Error reading file_descriptor") do
+ evented_read(file_descriptor, "Error reading file_descriptor") do
LibC.read(file_descriptor.fd, slice, slice.size).tap do |return_code|
if return_code == -1 && Errno.value == Errno::EBADF
raise IO::Error.new "File not open for reading", target: file_descriptor
@@ -40,7 +40,7 @@ class Crystal::Wasi::EventLoop < Crystal::EventLoop
end
def write(file_descriptor : Crystal::System::FileDescriptor, slice : Bytes) : Int32
- file_descriptor.evented_write("Error writing file_descriptor") do
+ evented_write(file_descriptor, "Error writing file_descriptor") do
LibC.write(file_descriptor.fd, slice, slice.size).tap do |return_code|
if return_code == -1 && Errno.value == Errno::EBADF
raise IO::Error.new "File not open for writing", target: file_descriptor
@@ -53,14 +53,17 @@ class Crystal::Wasi::EventLoop < Crystal::EventLoop
file_descriptor.evented_close
end
+ def remove(file_descriptor : Crystal::System::FileDescriptor) : Nil
+ end
+
def read(socket : ::Socket, slice : Bytes) : Int32
- socket.evented_read("Error reading socket") do
+ evented_read(socket, "Error reading socket") do
LibC.recv(socket.fd, slice, slice.size, 0).to_i32
end
end
def write(socket : ::Socket, slice : Bytes) : Int32
- socket.evented_write("Error writing to socket") do
+ evented_write(socket, "Error writing to socket") do
LibC.send(socket.fd, slice, slice.size, 0)
end
end
@@ -84,12 +87,55 @@ class Crystal::Wasi::EventLoop < Crystal::EventLoop
def close(socket : ::Socket) : Nil
socket.evented_close
end
+
+ def remove(socket : ::Socket) : Nil
+ end
+
+ def evented_read(target, errno_msg : String, &) : Int32
+ loop do
+ bytes_read = yield
+ if bytes_read != -1
+ # `to_i32` is acceptable because `Slice#size` is an Int32
+ return bytes_read.to_i32
+ end
+
+ if Errno.value == Errno::EAGAIN
+ target.wait_readable
+ else
+ raise IO::Error.from_errno(errno_msg, target: target)
+ end
+ end
+ ensure
+ target.evented_resume_pending_readers
+ end
+
+ def evented_write(target, errno_msg : String, &) : Int32
+ begin
+ loop do
+ bytes_written = yield
+ if bytes_written != -1
+ return bytes_written.to_i32
+ end
+
+ if Errno.value == Errno::EAGAIN
+ target.wait_writable
+ else
+ raise IO::Error.from_errno(errno_msg, target: target)
+ end
+ end
+ ensure
+ target.evented_resume_pending_writers
+ end
+ end
end
struct Crystal::Wasi::Event
include Crystal::EventLoop::Event
- def add(timeout : Time::Span?) : Nil
+ def add(timeout : Time::Span) : Nil
+ end
+
+ def add(timeout : Nil) : Nil
end
def free : Nil
diff --git a/src/crystal/system/wasi/fiber.cr b/src/crystal/system/wasi/fiber.cr
index 516fcc10a29a..8461bb15d00c 100644
--- a/src/crystal/system/wasi/fiber.cr
+++ b/src/crystal/system/wasi/fiber.cr
@@ -3,6 +3,9 @@ module Crystal::System::Fiber
LibC.malloc(stack_size)
end
+ def self.reset_stack(stack : Void*, stack_size : Int, protect : Bool) : Nil
+ end
+
def self.free_stack(stack : Void*, stack_size) : Nil
LibC.free(stack)
end
diff --git a/src/crystal/system/wasi/file.cr b/src/crystal/system/wasi/file.cr
index 0d197550e3db..a48463eded4e 100644
--- a/src/crystal/system/wasi/file.cr
+++ b/src/crystal/system/wasi/file.cr
@@ -2,6 +2,9 @@ require "../unix/file"
# :nodoc:
module Crystal::System::File
+ protected def system_set_mode(mode : String)
+ end
+
def self.chmod(path, mode)
raise NotImplementedError.new "Crystal::System::File.chmod"
end
diff --git a/src/crystal/system/wasi/group.cr b/src/crystal/system/wasi/group.cr
index 0aa09bd40aa8..c94fffa4fe6e 100644
--- a/src/crystal/system/wasi/group.cr
+++ b/src/crystal/system/wasi/group.cr
@@ -1,9 +1,17 @@
module Crystal::System::Group
- private def from_name?(groupname : String)
- raise NotImplementedError.new("Crystal::System::Group#from_name?")
+ def system_name
+ raise NotImplementedError.new("Crystal::System::Group#system_name")
end
- private def from_id?(groupid : String)
- raise NotImplementedError.new("Crystal::System::Group#from_id?")
+ def system_id
+ raise NotImplementedError.new("Crystal::System::Group#system_id")
+ end
+
+ def self.from_name?(groupname : String)
+ raise NotImplementedError.new("Crystal::System::Group.from_name?")
+ end
+
+ def self.from_id?(groupid : String)
+ raise NotImplementedError.new("Crystal::System::Group.from_id?")
end
end
diff --git a/src/crystal/system/wasi/thread.cr b/src/crystal/system/wasi/thread.cr
index 6f0c0cbe8260..1e8f6957d526 100644
--- a/src/crystal/system/wasi/thread.cr
+++ b/src/crystal/system/wasi/thread.cr
@@ -38,4 +38,19 @@ module Crystal::System::Thread
# TODO: Implement
Pointer(Void).null
end
+
+ def self.init_suspend_resume : Nil
+ end
+
+ private def system_suspend : Nil
+ raise NotImplementedError.new("Crystal::System::Thread.system_suspend")
+ end
+
+ private def system_wait_suspended : Nil
+ raise NotImplementedError.new("Crystal::System::Thread.system_wait_suspended")
+ end
+
+ private def system_resume : Nil
+ raise NotImplementedError.new("Crystal::System::Thread.system_resume")
+ end
end
diff --git a/src/crystal/system/wasi/user.cr b/src/crystal/system/wasi/user.cr
index 06415897000e..2d1c6e91b770 100644
--- a/src/crystal/system/wasi/user.cr
+++ b/src/crystal/system/wasi/user.cr
@@ -1,9 +1,33 @@
module Crystal::System::User
- private def from_username?(username : String)
- raise NotImplementedError.new("Crystal::System::User#from_username?")
+ def system_username
+ raise NotImplementedError.new("Crystal::System::User#system_username")
end
- private def from_id?(id : String)
- raise NotImplementedError.new("Crystal::System::User#from_id?")
+ def system_id
+ raise NotImplementedError.new("Crystal::System::User#system_id")
+ end
+
+ def system_group_id
+ raise NotImplementedError.new("Crystal::System::User#system_group_id")
+ end
+
+ def system_name
+ raise NotImplementedError.new("Crystal::System::User#system_name")
+ end
+
+ def system_home_directory
+ raise NotImplementedError.new("Crystal::System::User#system_home_directory")
+ end
+
+ def system_shell
+ raise NotImplementedError.new("Crystal::System::User#system_shell")
+ end
+
+ def self.from_username?(username : String)
+ raise NotImplementedError.new("Crystal::System::User.from_username?")
+ end
+
+ def self.from_id?(id : String)
+ raise NotImplementedError.new("Crystal::System::User.from_id?")
end
end
diff --git a/src/crystal/system/win32/addrinfo.cr b/src/crystal/system/win32/addrinfo.cr
new file mode 100644
index 000000000000..91ebb1620a43
--- /dev/null
+++ b/src/crystal/system/win32/addrinfo.cr
@@ -0,0 +1,88 @@
+module Crystal::System::Addrinfo
+ alias Handle = LibC::ADDRINFOEXW*
+
+ @addr : LibC::SockaddrIn6
+
+ protected def initialize(addrinfo : Handle)
+ @family = ::Socket::Family.from_value(addrinfo.value.ai_family)
+ @type = ::Socket::Type.from_value(addrinfo.value.ai_socktype)
+ @protocol = ::Socket::Protocol.from_value(addrinfo.value.ai_protocol)
+ @size = addrinfo.value.ai_addrlen.to_i
+
+ @addr = uninitialized LibC::SockaddrIn6
+
+ case @family
+ when ::Socket::Family::INET6
+ addrinfo.value.ai_addr.as(LibC::SockaddrIn6*).copy_to(pointerof(@addr).as(LibC::SockaddrIn6*), 1)
+ when ::Socket::Family::INET
+ addrinfo.value.ai_addr.as(LibC::SockaddrIn*).copy_to(pointerof(@addr).as(LibC::SockaddrIn*), 1)
+ else
+ # TODO: (asterite) UNSPEC and UNIX unsupported?
+ end
+ end
+
+ def system_ip_address : ::Socket::IPAddress
+ ::Socket::IPAddress.from(to_unsafe, size)
+ end
+
+ def to_unsafe
+ pointerof(@addr).as(LibC::Sockaddr*)
+ end
+
+ def self.getaddrinfo(domain, service, family, type, protocol, timeout) : Handle
+ hints = LibC::ADDRINFOEXW.new
+ hints.ai_family = (family || ::Socket::Family::UNSPEC).to_i32
+ hints.ai_socktype = type
+ hints.ai_protocol = protocol
+ hints.ai_flags = 0
+
+ if service.is_a?(Int)
+ hints.ai_flags |= LibC::AI_NUMERICSERV
+ if service < 0
+ raise ::Socket::Addrinfo::Error.from_os_error(nil, WinError::WSATYPE_NOT_FOUND, domain: domain, type: type, protocol: protocol, service: service)
+ end
+ end
+
+ Crystal::IOCP::GetAddrInfoOverlappedOperation.run(Crystal::EventLoop.current.iocp) do |operation|
+ completion_routine = LibC::LPLOOKUPSERVICE_COMPLETION_ROUTINE.new do |dwError, dwBytes, lpOverlapped|
+ orig_operation = Crystal::IOCP::GetAddrInfoOverlappedOperation.unbox(lpOverlapped)
+ LibC.PostQueuedCompletionStatus(orig_operation.iocp, 0, 0, lpOverlapped)
+ end
+
+ # NOTE: we handle the timeout ourselves so we don't pass a `LibC::Timeval`
+ # to Win32 here
+ result = LibC.GetAddrInfoExW(
+ Crystal::System.to_wstr(domain), Crystal::System.to_wstr(service.to_s), LibC::NS_DNS, nil, pointerof(hints),
+ out addrinfos, nil, operation, completion_routine, out cancel_handle)
+
+ if result == 0
+ return addrinfos
+ else
+ case error = WinError.new(result.to_u32!)
+ when .wsa_io_pending?
+ # used in `Crystal::IOCP::OverlappedOperation#try_cancel_getaddrinfo`
+ operation.cancel_handle = cancel_handle
+ else
+ raise ::Socket::Addrinfo::Error.from_os_error("GetAddrInfoExW", error, domain: domain, type: type, protocol: protocol, service: service)
+ end
+ end
+
+ operation.wait_for_result(timeout) do |error|
+ case error
+ when .wsa_e_cancelled?
+ raise IO::TimeoutError.new("GetAddrInfoExW timed out")
+ else
+ raise ::Socket::Addrinfo::Error.from_os_error("GetAddrInfoExW", error, domain: domain, type: type, protocol: protocol, service: service)
+ end
+ end
+ end
+ end
+
+ def self.next_addrinfo(addrinfo : Handle) : Handle
+ addrinfo.value.ai_next
+ end
+
+ def self.free_addrinfo(addrinfo : Handle)
+ LibC.FreeAddrInfoExW(addrinfo)
+ end
+end
diff --git a/src/crystal/system/win32/addrinfo_win7.cr b/src/crystal/system/win32/addrinfo_win7.cr
new file mode 100644
index 000000000000..b033d61f16e7
--- /dev/null
+++ b/src/crystal/system/win32/addrinfo_win7.cr
@@ -0,0 +1,61 @@
+module Crystal::System::Addrinfo
+ alias Handle = LibC::Addrinfo*
+
+ @addr : LibC::SockaddrIn6
+
+ protected def initialize(addrinfo : Handle)
+ @family = ::Socket::Family.from_value(addrinfo.value.ai_family)
+ @type = ::Socket::Type.from_value(addrinfo.value.ai_socktype)
+ @protocol = ::Socket::Protocol.from_value(addrinfo.value.ai_protocol)
+ @size = addrinfo.value.ai_addrlen.to_i
+
+ @addr = uninitialized LibC::SockaddrIn6
+
+ case @family
+ when ::Socket::Family::INET6
+ addrinfo.value.ai_addr.as(LibC::SockaddrIn6*).copy_to(pointerof(@addr).as(LibC::SockaddrIn6*), 1)
+ when ::Socket::Family::INET
+ addrinfo.value.ai_addr.as(LibC::SockaddrIn*).copy_to(pointerof(@addr).as(LibC::SockaddrIn*), 1)
+ else
+ # TODO: (asterite) UNSPEC and UNIX unsupported?
+ end
+ end
+
+ def system_ip_address : ::Socket::IPAddress
+ ::Socket::IPAddress.from(to_unsafe, size)
+ end
+
+ def to_unsafe
+ pointerof(@addr).as(LibC::Sockaddr*)
+ end
+
+ def self.getaddrinfo(domain, service, family, type, protocol, timeout) : Handle
+ hints = LibC::Addrinfo.new
+ hints.ai_family = (family || ::Socket::Family::UNSPEC).to_i32
+ hints.ai_socktype = type
+ hints.ai_protocol = protocol
+ hints.ai_flags = 0
+
+ if service.is_a?(Int)
+ hints.ai_flags |= LibC::AI_NUMERICSERV
+ if service < 0
+ raise ::Socket::Addrinfo::Error.from_os_error(nil, WinError::WSATYPE_NOT_FOUND, domain: domain, type: type, protocol: protocol, service: service)
+ end
+ end
+
+ ret = LibC.getaddrinfo(domain, service.to_s, pointerof(hints), out ptr)
+ unless ret.zero?
+ error = WinError.new(ret.to_u32!)
+ raise ::Socket::Addrinfo::Error.from_os_error(nil, error, domain: domain, type: type, protocol: protocol, service: service)
+ end
+ ptr
+ end
+
+ def self.next_addrinfo(addrinfo : Handle) : Handle
+ addrinfo.value.ai_next
+ end
+
+ def self.free_addrinfo(addrinfo : Handle)
+ LibC.freeaddrinfo(addrinfo)
+ end
+end
diff --git a/src/crystal/system/win32/event_loop_iocp.cr b/src/crystal/system/win32/event_loop_iocp.cr
index 25c8db41d9ff..3089e36edfeb 100644
--- a/src/crystal/system/win32/event_loop_iocp.cr
+++ b/src/crystal/system/win32/event_loop_iocp.cr
@@ -76,7 +76,7 @@ class Crystal::IOCP::EventLoop < Crystal::EventLoop
# Wait for completion timed out but it may have been interrupted or we ask
# for immediate timeout (nonblocking), so we check for the next event
- # readyness again:
+ # readiness again:
return false if next_event.wake_at > Time.monotonic
end
@@ -121,7 +121,7 @@ class Crystal::IOCP::EventLoop < Crystal::EventLoop
return unless thread
# alert the thread to interrupt GetQueuedCompletionStatusEx
- LibC.QueueUserAPC(->(ptr : LibC::ULONG_PTR) {}, thread, LibC::ULONG_PTR.new(0))
+ LibC.QueueUserAPC(->(ptr : LibC::ULONG_PTR) { }, thread, LibC::ULONG_PTR.new(0))
end
def enqueue(event : Crystal::IOCP::Event)
@@ -144,18 +144,15 @@ class Crystal::IOCP::EventLoop < Crystal::EventLoop
end
def read(file_descriptor : Crystal::System::FileDescriptor, slice : Bytes) : Int32
- handle = file_descriptor.windows_handle
- IOCP.overlapped_operation(file_descriptor, handle, "ReadFile", file_descriptor.read_timeout) do |overlapped|
- ret = LibC.ReadFile(handle, slice, slice.size, out byte_count, overlapped)
+ IOCP.overlapped_operation(file_descriptor, "ReadFile", file_descriptor.read_timeout) do |overlapped|
+ ret = LibC.ReadFile(file_descriptor.windows_handle, slice, slice.size, out byte_count, overlapped)
{ret, byte_count}
end.to_i32
end
def write(file_descriptor : Crystal::System::FileDescriptor, slice : Bytes) : Int32
- handle = file_descriptor.windows_handle
-
- IOCP.overlapped_operation(file_descriptor, handle, "WriteFile", file_descriptor.write_timeout, writing: true) do |overlapped|
- ret = LibC.WriteFile(handle, slice, slice.size, out byte_count, overlapped)
+ IOCP.overlapped_operation(file_descriptor, "WriteFile", file_descriptor.write_timeout, writing: true) do |overlapped|
+ ret = LibC.WriteFile(file_descriptor.windows_handle, slice, slice.size, out byte_count, overlapped)
{ret, byte_count}
end.to_i32
end
@@ -164,6 +161,9 @@ class Crystal::IOCP::EventLoop < Crystal::EventLoop
LibC.CancelIoEx(file_descriptor.windows_handle, nil) unless file_descriptor.system_blocking?
end
+ def remove(file_descriptor : Crystal::System::FileDescriptor) : Nil
+ end
+
private def wsa_buffer(bytes)
wsabuf = LibC::WSABUF.new
wsabuf.len = bytes.size
@@ -231,7 +231,7 @@ class Crystal::IOCP::EventLoop < Crystal::EventLoop
end
def connect(socket : ::Socket, address : ::Socket::Addrinfo | ::Socket::Address, timeout : ::Time::Span?) : IO::Error?
- socket.overlapped_connect(socket.fd, "ConnectEx") do |overlapped|
+ socket.overlapped_connect(socket.fd, "ConnectEx", timeout) do |overlapped|
# This is: LibC.ConnectEx(fd, address, address.size, nil, 0, nil, overlapped)
Crystal::System::Socket.connect_ex.call(socket.fd, address.to_unsafe, address.size, Pointer(Void).null, 0_u32, Pointer(UInt32).null, overlapped.to_unsafe)
end
@@ -274,6 +274,9 @@ class Crystal::IOCP::EventLoop < Crystal::EventLoop
def close(socket : ::Socket) : Nil
end
+
+ def remove(socket : ::Socket) : Nil
+ end
end
class Crystal::IOCP::Event
@@ -295,8 +298,8 @@ class Crystal::IOCP::Event
free
end
- def add(timeout : Time::Span?) : Nil
- @wake_at = timeout ? Time.monotonic + timeout : Time.monotonic
+ def add(timeout : Time::Span) : Nil
+ @wake_at = Time.monotonic + timeout
Crystal::EventLoop.current.enqueue(self)
end
end
diff --git a/src/crystal/system/win32/fiber.cr b/src/crystal/system/win32/fiber.cr
index 9e6495ee594e..05fd230a9cac 100644
--- a/src/crystal/system/win32/fiber.cr
+++ b/src/crystal/system/win32/fiber.cr
@@ -7,28 +7,63 @@ module Crystal::System::Fiber
# overflow
RESERVED_STACK_SIZE = LibC::DWORD.new(0x10000)
- # the reserved stack size, plus the size of a single page
- @@total_reserved_size : LibC::DWORD = begin
- LibC.GetNativeSystemInfo(out system_info)
- system_info.dwPageSize + RESERVED_STACK_SIZE
- end
-
def self.allocate_stack(stack_size, protect) : Void*
- unless memory_pointer = LibC.VirtualAlloc(nil, stack_size, LibC::MEM_COMMIT | LibC::MEM_RESERVE, LibC::PAGE_READWRITE)
- raise RuntimeError.from_winerror("VirtualAlloc")
+ if stack_top = LibC.VirtualAlloc(nil, stack_size, LibC::MEM_RESERVE, LibC::PAGE_READWRITE)
+ if protect
+ if commit_and_guard(stack_top, stack_size)
+ return stack_top
+ end
+ else
+ # for the interpreter, the stack is just ordinary memory so the entire
+ # range is committed
+ if LibC.VirtualAlloc(stack_top, stack_size, LibC::MEM_COMMIT, LibC::PAGE_READWRITE)
+ return stack_top
+ end
+ end
+
+ # failure
+ LibC.VirtualFree(stack_top, 0, LibC::MEM_RELEASE)
end
- # Detects stack overflows by guarding the top of the stack, similar to
- # `LibC.mprotect`. Windows will fail to allocate a new guard page for these
- # fiber stacks and trigger a stack overflow exception
+ raise RuntimeError.from_winerror("VirtualAlloc")
+ end
+
+ def self.reset_stack(stack : Void*, stack_size : Int, protect : Bool) : Nil
if protect
- if LibC.VirtualProtect(memory_pointer, @@total_reserved_size, LibC::PAGE_READWRITE | LibC::PAGE_GUARD, out _) == 0
- LibC.VirtualFree(memory_pointer, 0, LibC::MEM_RELEASE)
- raise RuntimeError.from_winerror("VirtualProtect")
+ if LibC.VirtualFree(stack, 0, LibC::MEM_DECOMMIT) == 0
+ raise RuntimeError.from_winerror("VirtualFree")
+ end
+ unless commit_and_guard(stack, stack_size)
+ raise RuntimeError.from_winerror("VirtualAlloc")
end
end
+ end
+
+ # Commits the bottommost page and sets up the guard pages above it, in the
+ # same manner as each thread's main stack. When the stack hits a guard page
+ # for the first time, a page fault is generated, the page's guard status is
+ # reset, and Windows checks if a reserved page is available above. On success,
+ # a new guard page is committed, and on failure, a stack overflow exception is
+ # triggered after the `RESERVED_STACK_SIZE` portion is made available.
+ private def self.commit_and_guard(stack_top, stack_size)
+ stack_bottom = stack_top + stack_size
+
+ LibC.GetNativeSystemInfo(out system_info)
+ stack_commit_size = system_info.dwPageSize
+ stack_commit_top = stack_bottom - stack_commit_size
+ unless LibC.VirtualAlloc(stack_commit_top, stack_commit_size, LibC::MEM_COMMIT, LibC::PAGE_READWRITE)
+ return false
+ end
+
+ # the reserved stack size, plus a final guard page for when the stack
+ # overflow handler itself overflows the stack
+ stack_guard_size = system_info.dwPageSize + RESERVED_STACK_SIZE
+ stack_guard_top = stack_commit_top - stack_guard_size
+ unless LibC.VirtualAlloc(stack_guard_top, stack_guard_size, LibC::MEM_COMMIT, LibC::PAGE_READWRITE | LibC::PAGE_GUARD)
+ return false
+ end
- memory_pointer
+ true
end
def self.free_stack(stack : Void*, stack_size) : Nil
diff --git a/src/crystal/system/win32/file.cr b/src/crystal/system/win32/file.cr
index 83d6afcf18ca..b6f9cf2b7ccd 100644
--- a/src/crystal/system/win32/file.cr
+++ b/src/crystal/system/win32/file.cr
@@ -9,7 +9,12 @@ require "c/ntifs"
require "c/winioctl"
module Crystal::System::File
- def self.open(filename : String, mode : String, perm : Int32 | ::File::Permissions) : FileDescriptor::Handle
+ # On Windows we cannot rely on the system mode `FILE_APPEND_DATA` and
+ # keep track of append mode explicitly. When writing data, this ensures to only
+ # write at the end of the file.
+ @system_append = false
+
+ def self.open(filename : String, mode : String, perm : Int32 | ::File::Permissions, blocking : Bool?) : FileDescriptor::Handle
perm = ::File::Permissions.new(perm) if perm.is_a? Int32
# Only the owner writable bit is used, since windows only supports
# the read only attribute.
@@ -19,7 +24,7 @@ module Crystal::System::File
perm = LibC::S_IREAD
end
- handle, error = open(filename, open_flag(mode), ::File::Permissions.new(perm))
+ handle, error = open(filename, open_flag(mode), ::File::Permissions.new(perm), blocking != false)
unless error.error_success?
raise ::File::Error.from_os_error("Error opening file with mode '#{mode}'", error, file: filename)
end
@@ -27,8 +32,8 @@ module Crystal::System::File
handle
end
- def self.open(filename : String, flags : Int32, perm : ::File::Permissions) : {FileDescriptor::Handle, WinError}
- access, disposition, attributes = self.posix_to_open_opts flags, perm
+ def self.open(filename : String, flags : Int32, perm : ::File::Permissions, blocking : Bool) : {FileDescriptor::Handle, WinError}
+ access, disposition, attributes = self.posix_to_open_opts flags, perm, blocking
handle = LibC.CreateFileW(
System.to_wstr(filename),
@@ -43,7 +48,7 @@ module Crystal::System::File
{handle.address, handle == LibC::INVALID_HANDLE_VALUE ? WinError.value : WinError::ERROR_SUCCESS}
end
- private def self.posix_to_open_opts(flags : Int32, perm : ::File::Permissions)
+ private def self.posix_to_open_opts(flags : Int32, perm : ::File::Permissions, blocking : Bool)
access = if flags.bits_set? LibC::O_WRONLY
LibC::FILE_GENERIC_WRITE
elsif flags.bits_set? LibC::O_RDWR
@@ -52,10 +57,9 @@ module Crystal::System::File
LibC::FILE_GENERIC_READ
end
- if flags.bits_set? LibC::O_APPEND
- access |= LibC::FILE_APPEND_DATA
- access &= ~LibC::FILE_WRITE_DATA
- end
+ # do not handle `O_APPEND`, because Win32 append mode relies on removing
+ # `FILE_WRITE_DATA` which breaks file truncation and locking; instead,
+ # simply set the end of the file as the write offset in `#write_blocking`
if flags.bits_set? LibC::O_TRUNC
if flags.bits_set? LibC::O_CREAT
@@ -73,7 +77,7 @@ module Crystal::System::File
disposition = LibC::OPEN_EXISTING
end
- attributes = LibC::FILE_ATTRIBUTE_NORMAL
+ attributes = 0
unless perm.owner_write?
attributes |= LibC::FILE_ATTRIBUTE_READONLY
end
@@ -93,13 +97,26 @@ module Crystal::System::File
attributes |= LibC::FILE_FLAG_RANDOM_ACCESS
end
+ unless blocking
+ attributes |= LibC::FILE_FLAG_OVERLAPPED
+ end
+
{access, disposition, attributes}
end
+ protected def system_set_mode(mode : String)
+ @system_append = true if mode.starts_with?('a')
+ end
+
+ private def write_blocking(handle, slice)
+ write_blocking(handle, slice, pos: @system_append ? UInt64::MAX : nil)
+ end
+
NOT_FOUND_ERRORS = {
WinError::ERROR_FILE_NOT_FOUND,
WinError::ERROR_PATH_NOT_FOUND,
WinError::ERROR_INVALID_NAME,
+ WinError::ERROR_DIRECTORY,
}
def self.check_not_found_error(message, path)
diff --git a/src/crystal/system/win32/file_descriptor.cr b/src/crystal/system/win32/file_descriptor.cr
index dc8d479532be..1f277505302a 100644
--- a/src/crystal/system/win32/file_descriptor.cr
+++ b/src/crystal/system/win32/file_descriptor.cr
@@ -3,6 +3,7 @@ require "c/consoleapi"
require "c/consoleapi2"
require "c/winnls"
require "crystal/system/win32/iocp"
+require "crystal/system/thread"
module Crystal::System::FileDescriptor
# Platform-specific type to represent a file descriptor handle to the operating
@@ -52,8 +53,17 @@ module Crystal::System::FileDescriptor
end
end
- private def write_blocking(handle, slice)
- ret = LibC.WriteFile(handle, slice, slice.size, out bytes_written, nil)
+ private def write_blocking(handle, slice, pos = nil)
+ overlapped = LibC::OVERLAPPED.new
+ if pos
+ overlapped.union.offset.offset = LibC::DWORD.new!(pos)
+ overlapped.union.offset.offsetHigh = LibC::DWORD.new!(pos >> 32)
+ overlapped_ptr = pointerof(overlapped)
+ else
+ overlapped_ptr = Pointer(LibC::OVERLAPPED).null
+ end
+
+ ret = LibC.WriteFile(handle, slice, slice.size, out bytes_written, overlapped_ptr)
if ret.zero?
case error = WinError.value
when .error_access_denied?
@@ -67,19 +77,31 @@ module Crystal::System::FileDescriptor
bytes_written
end
+ def emulated_blocking? : Bool?
+ # reading from STDIN is done via a separate thread (see
+ # `ConsoleUtils.read_console` below)
+ handle = windows_handle
+ if LibC.GetConsoleMode(handle, out _) != 0
+ if handle == LibC.GetStdHandle(LibC::STD_INPUT_HANDLE)
+ return false
+ end
+ end
+ end
+
# :nodoc:
def system_blocking?
@system_blocking
end
private def system_blocking=(blocking)
- unless blocking == @system_blocking
+ unless blocking == self.blocking
raise IO::Error.new("Cannot reconfigure `IO::FileDescriptor#blocking` after creation")
end
end
private def system_blocking_init(value)
@system_blocking = value
+ Crystal::EventLoop.current.create_completion_port(windows_handle) unless value
end
private def system_close_on_exec?
@@ -110,10 +132,6 @@ module Crystal::System::FileDescriptor
end
protected def windows_handle
- FileDescriptor.windows_handle(fd)
- end
-
- def self.windows_handle(fd)
LibC::HANDLE.new(fd)
end
@@ -176,8 +194,14 @@ module Crystal::System::FileDescriptor
file_descriptor_close
end
- def file_descriptor_close
+ def file_descriptor_close(&)
if LibC.CloseHandle(windows_handle) == 0
+ yield
+ end
+ end
+
+ def file_descriptor_close
+ file_descriptor_close do
raise IO::Error.from_winerror("Error closing file", target: self)
end
end
@@ -195,41 +219,62 @@ module Crystal::System::FileDescriptor
end
private def flock(exclusive, retry)
- flags = LibC::LOCKFILE_FAIL_IMMEDIATELY
+ flags = 0_u32
+ flags |= LibC::LOCKFILE_FAIL_IMMEDIATELY if !retry || system_blocking?
flags |= LibC::LOCKFILE_EXCLUSIVE_LOCK if exclusive
handle = windows_handle
- if retry
+ if retry && system_blocking?
until lock_file(handle, flags)
- sleep 0.1
+ sleep 0.1.seconds
end
else
- lock_file(handle, flags) || raise IO::Error.from_winerror("Error applying file lock: file is already locked")
+ lock_file(handle, flags) || raise IO::Error.from_winerror("Error applying file lock: file is already locked", target: self)
end
end
private def lock_file(handle, flags)
- # lpOverlapped must be provided despite the synchronous use of this method.
- overlapped = LibC::OVERLAPPED.new
- # lock the entire file with offset 0 in overlapped and number of bytes set to max value
- if 0 != LibC.LockFileEx(handle, flags, 0, 0xFFFF_FFFF, 0xFFFF_FFFF, pointerof(overlapped))
- true
- else
- winerror = WinError.value
- if winerror == WinError::ERROR_LOCK_VIOLATION
- false
+ IOCP::IOOverlappedOperation.run(handle) do |operation|
+ result = LibC.LockFileEx(handle, flags, 0, 0xFFFF_FFFF, 0xFFFF_FFFF, operation)
+
+ if result == 0
+ case error = WinError.value
+ when .error_io_pending?
+ # the operation is running asynchronously; do nothing
+ when .error_lock_violation?
+ # synchronous failure
+ return false
+ else
+ raise IO::Error.from_os_error("LockFileEx", error, target: self)
+ end
else
- raise IO::Error.from_os_error("LockFileEx", winerror, target: self)
+ return true
end
+
+ operation.wait_for_result(nil) do |error|
+ raise IO::Error.from_os_error("LockFileEx", error, target: self)
+ end
+
+ true
end
end
private def unlock_file(handle)
- # lpOverlapped must be provided despite the synchronous use of this method.
- overlapped = LibC::OVERLAPPED.new
- # unlock the entire file with offset 0 in overlapped and number of bytes set to max value
- if 0 == LibC.UnlockFileEx(handle, 0, 0xFFFF_FFFF, 0xFFFF_FFFF, pointerof(overlapped))
- raise IO::Error.from_winerror("UnLockFileEx")
+ IOCP::IOOverlappedOperation.run(handle) do |operation|
+ result = LibC.UnlockFileEx(handle, 0, 0xFFFF_FFFF, 0xFFFF_FFFF, operation)
+
+ if result == 0
+ error = WinError.value
+ unless error.error_io_pending?
+ raise IO::Error.from_os_error("UnlockFileEx", error, target: self)
+ end
+ else
+ return
+ end
+
+ operation.wait_for_result(nil) do |error|
+ raise IO::Error.from_os_error("UnlockFileEx", error, target: self)
+ end
end
end
@@ -249,13 +294,11 @@ module Crystal::System::FileDescriptor
w_pipe_flags |= LibC::FILE_FLAG_OVERLAPPED unless write_blocking
w_pipe = LibC.CreateNamedPipeA(pipe_name, w_pipe_flags, pipe_mode, 1, PIPE_BUFFER_SIZE, PIPE_BUFFER_SIZE, 0, nil)
raise IO::Error.from_winerror("CreateNamedPipeA") if w_pipe == LibC::INVALID_HANDLE_VALUE
- Crystal::EventLoop.current.create_completion_port(w_pipe) unless write_blocking
r_pipe_flags = LibC::FILE_FLAG_NO_BUFFERING
r_pipe_flags |= LibC::FILE_FLAG_OVERLAPPED unless read_blocking
r_pipe = LibC.CreateFileW(System.to_wstr(pipe_name), LibC::GENERIC_READ | LibC::FILE_WRITE_ATTRIBUTES, 0, nil, LibC::OPEN_EXISTING, r_pipe_flags, nil)
raise IO::Error.from_winerror("CreateFileW") if r_pipe == LibC::INVALID_HANDLE_VALUE
- Crystal::EventLoop.current.create_completion_port(r_pipe) unless read_blocking
r = IO::FileDescriptor.new(r_pipe.address, read_blocking)
w = IO::FileDescriptor.new(w_pipe.address, write_blocking)
@@ -264,19 +307,26 @@ module Crystal::System::FileDescriptor
{r, w}
end
- def self.pread(fd, buffer, offset)
- handle = windows_handle(fd)
+ def self.pread(file, buffer, offset)
+ handle = file.windows_handle
- overlapped = LibC::OVERLAPPED.new
- overlapped.union.offset.offset = LibC::DWORD.new!(offset)
- overlapped.union.offset.offsetHigh = LibC::DWORD.new!(offset >> 32)
- if LibC.ReadFile(handle, buffer, buffer.size, out bytes_read, pointerof(overlapped)) == 0
- error = WinError.value
- return 0_i64 if error == WinError::ERROR_HANDLE_EOF
- raise IO::Error.from_os_error "Error reading file", error, target: self
- end
+ if file.system_blocking?
+ overlapped = LibC::OVERLAPPED.new
+ overlapped.union.offset.offset = LibC::DWORD.new!(offset)
+ overlapped.union.offset.offsetHigh = LibC::DWORD.new!(offset >> 32)
+ if LibC.ReadFile(handle, buffer, buffer.size, out bytes_read, pointerof(overlapped)) == 0
+ error = WinError.value
+ return 0_i64 if error == WinError::ERROR_HANDLE_EOF
+ raise IO::Error.from_os_error "Error reading file", error, target: file
+ end
- bytes_read.to_i64
+ bytes_read.to_i64
+ else
+ IOCP.overlapped_operation(file, "ReadFile", file.read_timeout, offset: offset) do |overlapped|
+ ret = LibC.ReadFile(handle, buffer, buffer.size, out byte_count, overlapped)
+ {ret, byte_count}
+ end.to_i64
+ end
end
def self.from_stdio(fd)
@@ -301,7 +351,11 @@ module Crystal::System::FileDescriptor
end
end
+ # `blocking` must be set to `true` because the underlying handles never
+ # support overlapped I/O; instead, `#emulated_blocking?` should return
+ # `false` for `STDIN` as it uses a separate thread
io = IO::FileDescriptor.new(handle.address, blocking: true)
+
# Set sync or flush_on_newline as described in STDOUT and STDERR docs.
# See https://crystal-lang.org/api/toplevel.html#STDERR
if console_handle
@@ -423,15 +477,61 @@ private module ConsoleUtils
appender << byte
end
end
- @@buffer = @@utf8_buffer[0, appender.size]
+ @@buffer = appender.to_slice
end
private def self.read_console(handle : LibC::HANDLE, slice : Slice(UInt16)) : Int32
+ @@mtx.synchronize do
+ @@read_requests << ReadRequest.new(
+ handle: handle,
+ slice: slice,
+ iocp: Crystal::EventLoop.current.iocp,
+ completion_key: Crystal::IOCP::CompletionKey.new(:stdin_read, ::Fiber.current),
+ )
+ @@read_cv.signal
+ end
+
+ ::Fiber.suspend
+
+ @@mtx.synchronize do
+ @@bytes_read.shift
+ end
+ end
+
+ private def self.read_console_blocking(handle : LibC::HANDLE, slice : Slice(UInt16)) : Int32
if 0 == LibC.ReadConsoleW(handle, slice, slice.size, out units_read, nil)
raise IO::Error.from_winerror("ReadConsoleW")
end
units_read.to_i32
end
+
+ record ReadRequest, handle : LibC::HANDLE, slice : Slice(UInt16), iocp : LibC::HANDLE, completion_key : Crystal::IOCP::CompletionKey
+
+ @@read_cv = ::Thread::ConditionVariable.new
+ @@read_requests = Deque(ReadRequest).new
+ @@bytes_read = Deque(Int32).new
+ @@mtx = ::Thread::Mutex.new
+ @@reader_thread = ::Thread.new { reader_loop }
+
+ private def self.reader_loop
+ while true
+ request = @@mtx.synchronize do
+ loop do
+ if entry = @@read_requests.shift?
+ break entry
+ end
+ @@read_cv.wait(@@mtx)
+ end
+ end
+
+ bytes = read_console_blocking(request.handle, request.slice)
+
+ @@mtx.synchronize do
+ @@bytes_read << bytes
+ LibC.PostQueuedCompletionStatus(request.iocp, LibC::JOB_OBJECT_MSG_EXIT_PROCESS, request.completion_key.object_id, nil)
+ end
+ end
+ end
end
# Enable UTF-8 console I/O for the duration of program execution
diff --git a/src/crystal/system/win32/group.cr b/src/crystal/system/win32/group.cr
new file mode 100644
index 000000000000..3b40774ac2d8
--- /dev/null
+++ b/src/crystal/system/win32/group.cr
@@ -0,0 +1,82 @@
+require "crystal/system/windows"
+
+# This file contains source code derived from the following:
+#
+# * https://cs.opensource.google/go/go/+/refs/tags/go1.23.0:src/os/user/lookup_windows.go
+# * https://cs.opensource.google/go/go/+/refs/tags/go1.23.0:src/syscall/security_windows.go
+#
+# The following is their license:
+#
+# Copyright 2009 The Go Authors.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google LLC nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+module Crystal::System::Group
+ def initialize(@name : String, @id : String)
+ end
+
+ def system_name : String
+ @name
+ end
+
+ def system_id : String
+ @id
+ end
+
+ def self.from_name?(groupname : String) : ::System::Group?
+ if found = Crystal::System.name_to_sid(groupname)
+ from_sid(found.sid)
+ end
+ end
+
+ def self.from_id?(groupid : String) : ::System::Group?
+ if sid = Crystal::System.sid_from_s(groupid)
+ begin
+ from_sid(sid)
+ ensure
+ LibC.LocalFree(sid)
+ end
+ end
+ end
+
+ private def self.from_sid(sid : LibC::SID*) : ::System::Group?
+ canonical = Crystal::System.sid_to_name(sid) || return
+
+ # https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-samr/7b2aeb27-92fc-41f6-8437-deb65d950921#gt_0387e636-5654-4910-9519-1f8326cf5ec0
+ # SidTypeAlias should also be treated as a group type next to SidTypeGroup
+ # and SidTypeWellKnownGroup:
+ # "alias object -> resource group: A group object..."
+ #
+ # Tests show that "Administrators" can be considered of type SidTypeAlias.
+ case canonical.type
+ when .sid_type_group?, .sid_type_well_known_group?, .sid_type_alias?
+ domain_and_group = canonical.domain.empty? ? canonical.name : "#{canonical.domain}\\#{canonical.name}"
+ gid = Crystal::System.sid_to_s(sid)
+ ::System::Group.new(domain_and_group, gid)
+ end
+ end
+end
diff --git a/src/crystal/system/win32/iocp.cr b/src/crystal/system/win32/iocp.cr
index ba0f11eb2af5..19c92c8f8725 100644
--- a/src/crystal/system/win32/iocp.cr
+++ b/src/crystal/system/win32/iocp.cr
@@ -6,7 +6,16 @@ require "crystal/system/thread_linked_list"
module Crystal::IOCP
# :nodoc:
class CompletionKey
+ enum Tag
+ ProcessRun
+ StdinRead
+ end
+
property fiber : Fiber?
+ getter tag : Tag
+
+ def initialize(@tag : Tag, @fiber : Fiber? = nil)
+ end
end
def self.wait_queued_completions(timeout, alertable = false, &)
@@ -39,20 +48,19 @@ module Crystal::IOCP
# at the moment only `::Process#wait` uses a non-nil completion key; all
# I/O operations, including socket ones, do not set this field
case completion_key = Pointer(Void).new(entry.lpCompletionKey).as(CompletionKey?)
- when Nil
+ in Nil
operation = OverlappedOperation.unbox(entry.lpOverlapped)
operation.schedule { |fiber| yield fiber }
- else
- case entry.dwNumberOfBytesTransferred
- when LibC::JOB_OBJECT_MSG_EXIT_PROCESS, LibC::JOB_OBJECT_MSG_ABNORMAL_EXIT_PROCESS
+ in CompletionKey
+ if completion_key_valid?(completion_key, entry.dwNumberOfBytesTransferred)
+ # if `Process` exits before a call to `#wait`, this fiber will be
+ # reset already
if fiber = completion_key.fiber
- # this ensures the `::Process` doesn't keep an indirect reference to
- # `::Thread.current`, as that leads to a finalization cycle
+ # this ensures existing references to `completion_key` do not keep
+ # an indirect reference to `::Thread.current`, as that leads to a
+ # finalization cycle
completion_key.fiber = nil
-
yield fiber
- else
- # the `Process` exits before a call to `#wait`; do nothing
end
end
end
@@ -61,49 +69,76 @@ module Crystal::IOCP
false
end
- class OverlappedOperation
+ private def self.completion_key_valid?(completion_key, number_of_bytes_transferred)
+ case completion_key.tag
+ in .process_run?
+ number_of_bytes_transferred.in?(LibC::JOB_OBJECT_MSG_EXIT_PROCESS, LibC::JOB_OBJECT_MSG_ABNORMAL_EXIT_PROCESS)
+ in .stdin_read?
+ true
+ end
+ end
+
+ abstract class OverlappedOperation
enum State
STARTED
DONE
- CANCELLED
end
+ abstract def wait_for_result(timeout, & : WinError ->)
+ private abstract def try_cancel : Bool
+
@overlapped = LibC::OVERLAPPED.new
@fiber = Fiber.current
@state : State = :started
- property next : OverlappedOperation?
- property previous : OverlappedOperation?
- @@canceled = Thread::LinkedList(OverlappedOperation).new
- def initialize(@handle : LibC::HANDLE)
+ def self.run(*args, **opts, &)
+ operation_storage = uninitialized ReferenceStorage(self)
+ operation = unsafe_construct(pointerof(operation_storage), *args, **opts)
+ yield operation
end
- def initialize(handle : LibC::SOCKET)
- @handle = LibC::HANDLE.new(handle)
+ def self.unbox(overlapped : LibC::OVERLAPPED*) : self
+ start = overlapped.as(Pointer(UInt8)) - offsetof(self, @overlapped)
+ Box(self).unbox(start.as(Pointer(Void)))
end
- def self.run(handle, &)
- operation = OverlappedOperation.new(handle)
- begin
- yield operation
- ensure
- operation.done
- end
+ def to_unsafe
+ pointerof(@overlapped)
end
- def self.unbox(overlapped : LibC::OVERLAPPED*)
- start = overlapped.as(Pointer(UInt8)) - offsetof(OverlappedOperation, @overlapped)
- Box(OverlappedOperation).unbox(start.as(Pointer(Void)))
+ protected def schedule(&)
+ done!
+ yield @fiber
end
- def to_unsafe
- pointerof(@overlapped)
+ private def done!
+ @fiber.cancel_timeout
+ @state = :done
end
- def wait_for_result(timeout, &)
- wait_for_completion(timeout)
+ private def wait_for_completion(timeout)
+ if timeout
+ sleep timeout
+ else
+ Fiber.suspend
+ end
- raise Exception.new("Invalid state #{@state}") unless @state.done? || @state.started?
+ unless @state.done?
+ if try_cancel
+ # Wait for cancellation to complete. We must not free the operation
+ # until it's completed.
+ Fiber.suspend
+ end
+ end
+ end
+ end
+
+ class IOOverlappedOperation < OverlappedOperation
+ def initialize(@handle : LibC::HANDLE)
+ end
+
+ def wait_for_result(timeout, & : WinError ->)
+ wait_for_completion(timeout)
result = LibC.GetOverlappedResult(@handle, self, out bytes, 0)
if result.zero?
@@ -116,15 +151,35 @@ module Crystal::IOCP
bytes
end
- def wait_for_wsa_result(timeout, &)
- wait_for_completion(timeout)
- wsa_result { |error| yield error }
+ private def try_cancel : Bool
+ # Microsoft documentation:
+ # The application must not free or reuse the OVERLAPPED structure
+ # associated with the canceled I/O operations until they have completed
+ # (this does not apply to asynchronous operations that finished
+ # synchronously, as nothing would be queued to the IOCP)
+ ret = LibC.CancelIoEx(@handle, self)
+ if ret.zero?
+ case error = WinError.value
+ when .error_not_found?
+ # Operation has already completed, do nothing
+ return false
+ else
+ raise RuntimeError.from_os_error("CancelIoEx", os_error: error)
+ end
+ end
+ true
+ end
+ end
+
+ class WSAOverlappedOperation < OverlappedOperation
+ def initialize(@handle : LibC::SOCKET)
end
- def wsa_result(&)
- raise Exception.new("Invalid state #{@state}") unless @state.done? || @state.started?
+ def wait_for_result(timeout, & : WinError ->)
+ wait_for_completion(timeout)
+
flags = 0_u32
- result = LibC.WSAGetOverlappedResult(LibC::SOCKET.new(@handle.address), self, out bytes, false, pointerof(flags))
+ result = LibC.WSAGetOverlappedResult(@handle, self, out bytes, false, pointerof(flags))
if result.zero?
error = WinError.wsa_value
yield error
@@ -135,55 +190,73 @@ module Crystal::IOCP
bytes
end
- protected def schedule(&)
- case @state
- when .started?
- yield @fiber
- done!
- when .cancelled?
- @@canceled.delete(self)
- else
- raise Exception.new("Invalid state #{@state}")
- end
- end
-
- protected def done
- case @state
- when .started?
- # https://learn.microsoft.com/en-us/windows/win32/api/ioapiset/nf-ioapiset-cancelioex
- # > The application must not free or reuse the OVERLAPPED structure
- # associated with the canceled I/O operations until they have completed
- if LibC.CancelIoEx(@handle, self) != 0
- @state = :cancelled
- @@canceled.push(self) # to increase lifetime
+ private def try_cancel : Bool
+ # Microsoft documentation:
+ # The application must not free or reuse the OVERLAPPED structure
+ # associated with the canceled I/O operations until they have completed
+ # (this does not apply to asynchronous operations that finished
+ # synchronously, as nothing would be queued to the IOCP)
+ ret = LibC.CancelIoEx(Pointer(Void).new(@handle), self)
+ if ret.zero?
+ case error = WinError.value
+ when .error_not_found?
+ # Operation has already completed, do nothing
+ return false
+ else
+ raise RuntimeError.from_os_error("CancelIoEx", os_error: error)
end
end
+ true
end
+ end
- def done!
- @state = :done
+ class GetAddrInfoOverlappedOperation < OverlappedOperation
+ getter iocp
+ setter cancel_handle : LibC::HANDLE = LibC::INVALID_HANDLE_VALUE
+
+ def initialize(@iocp : LibC::HANDLE)
end
- def wait_for_completion(timeout)
- if timeout
- timeout_event = Crystal::IOCP::Event.new(Fiber.current)
- timeout_event.add(timeout)
- else
- timeout_event = Crystal::IOCP::Event.new(Fiber.current, Time::Span::MAX)
+ def wait_for_result(timeout, & : WinError ->)
+ wait_for_completion(timeout)
+
+ result = LibC.GetAddrInfoExOverlappedResult(self)
+ unless result.zero?
+ error = WinError.new(result.to_u32!)
+ yield error
+
+ raise Socket::Addrinfo::Error.from_os_error("GetAddrInfoExOverlappedResult", error)
end
- # memoize event loop to make sure that we still target the same instance
- # after wakeup (guaranteed by current MT model but let's be future proof)
- event_loop = Crystal::EventLoop.current
- event_loop.enqueue(timeout_event)
- Fiber.suspend
+ @overlapped.union.pointer.as(LibC::ADDRINFOEXW**).value
+ end
- event_loop.dequeue(timeout_event)
+ private def try_cancel : Bool
+ ret = LibC.GetAddrInfoExCancel(pointerof(@cancel_handle))
+ unless ret.zero?
+ case error = WinError.new(ret.to_u32!)
+ when .wsa_invalid_handle?
+ # Operation has already completed, do nothing
+ return false
+ else
+ raise Socket::Addrinfo::Error.from_os_error("GetAddrInfoExCancel", error)
+ end
+ end
+ true
end
end
- def self.overlapped_operation(target, handle, method, timeout, *, writing = false, &)
- OverlappedOperation.run(handle) do |operation|
+ def self.overlapped_operation(file_descriptor, method, timeout, *, offset = nil, writing = false, &)
+ handle = file_descriptor.windows_handle
+ seekable = LibC.SetFilePointerEx(handle, 0, out original_offset, IO::Seek::Current) != 0
+
+ IOOverlappedOperation.run(handle) do |operation|
+ overlapped = operation.to_unsafe
+ if seekable
+ start_offset = offset || original_offset
+ overlapped.value.union.offset.offset = LibC::DWORD.new!(start_offset)
+ overlapped.value.union.offset.offsetHigh = LibC::DWORD.new!(start_offset >> 32)
+ end
result, value = yield operation
if result == 0
@@ -195,18 +268,21 @@ module Crystal::IOCP
when .error_io_pending?
# the operation is running asynchronously; do nothing
when .error_access_denied?
- raise IO::Error.new "File not open for #{writing ? "writing" : "reading"}", target: target
+ raise IO::Error.new "File not open for #{writing ? "writing" : "reading"}", target: file_descriptor
else
- raise IO::Error.from_os_error(method, error, target: target)
+ raise IO::Error.from_os_error(method, error, target: file_descriptor)
end
else
- operation.done!
+ # operation completed synchronously; seek forward by number of bytes
+ # read or written if handle is seekable, since overlapped I/O doesn't do
+ # it automatically
+ LibC.SetFilePointerEx(handle, value, nil, IO::Seek::Current) if seekable
return value
end
- operation.wait_for_result(timeout) do |error|
+ byte_count = operation.wait_for_result(timeout) do |error|
case error
- when .error_io_incomplete?
+ when .error_io_incomplete?, .error_operation_aborted?
raise IO::TimeoutError.new("#{method} timed out")
when .error_handle_eof?
return 0_u32
@@ -215,11 +291,20 @@ module Crystal::IOCP
return 0_u32
end
end
+
+ # operation completed asynchronously; seek to the original file position
+ # plus the number of bytes read or written (other operations might have
+ # moved the file pointer so we don't use `IO::Seek::Current` here), unless
+ # we are calling `Crystal::System::FileDescriptor.pread`
+ if seekable && !offset
+ LibC.SetFilePointerEx(handle, original_offset + byte_count, nil, IO::Seek::Set)
+ end
+ byte_count
end
end
def self.wsa_overlapped_operation(target, socket, method, timeout, connreset_is_error = true, &)
- OverlappedOperation.run(socket) do |operation|
+ WSAOverlappedOperation.run(socket) do |operation|
result, value = yield operation
if result == LibC::SOCKET_ERROR
@@ -230,13 +315,12 @@ module Crystal::IOCP
raise IO::Error.from_os_error(method, error, target: target)
end
else
- operation.done!
return value
end
- operation.wait_for_wsa_result(timeout) do |error|
+ operation.wait_for_result(timeout) do |error|
case error
- when .wsa_io_incomplete?
+ when .wsa_io_incomplete?, .error_operation_aborted?
raise IO::TimeoutError.new("#{method} timed out")
when .wsaeconnreset?
return 0_u32 unless connreset_is_error
diff --git a/src/crystal/system/win32/library_archive.cr b/src/crystal/system/win32/library_archive.cr
index 775677938bac..24c50f3405fa 100644
--- a/src/crystal/system/win32/library_archive.cr
+++ b/src/crystal/system/win32/library_archive.cr
@@ -17,6 +17,10 @@ module Crystal::System::LibraryArchive
private struct COFFReader
getter dlls = Set(String).new
+ # MSVC-style import libraries include the `__NULL_IMPORT_DESCRIPTOR` symbol,
+ # MinGW-style ones do not
+ getter? msvc = false
+
def initialize(@ar : ::File)
end
@@ -39,6 +43,7 @@ module Crystal::System::LibraryArchive
if first
first = false
return unless filename == "/"
+ handle_first_member(io)
elsif !filename.in?("/", "//")
handle_standard_member(io)
end
@@ -62,26 +67,69 @@ module Crystal::System::LibraryArchive
@ar.seek(new_pos)
end
+ private def handle_first_member(io)
+ symbol_count = io.read_bytes(UInt32, IO::ByteFormat::BigEndian)
+
+ # 4-byte offset per symbol
+ io.skip(symbol_count * 4)
+
+ symbol_count.times do
+ symbol = io.gets('\0', chomp: true)
+ if symbol == "__NULL_IMPORT_DESCRIPTOR"
+ @msvc = true
+ break
+ end
+ end
+ end
+
private def handle_standard_member(io)
- sig1 = io.read_bytes(UInt16, IO::ByteFormat::LittleEndian)
- return unless sig1 == 0x0000 # IMAGE_FILE_MACHINE_UNKNOWN
+ machine = io.read_bytes(UInt16, IO::ByteFormat::LittleEndian)
+ section_count = io.read_bytes(UInt16, IO::ByteFormat::LittleEndian)
- sig2 = io.read_bytes(UInt16, IO::ByteFormat::LittleEndian)
- return unless sig2 == 0xFFFF
+ if machine == 0x0000 && section_count == 0xFFFF
+ # short import library
+ version = io.read_bytes(UInt16, IO::ByteFormat::LittleEndian)
+ return unless version == 0 # 1 and 2 are used by object files (ANON_OBJECT_HEADER)
- version = io.read_bytes(UInt16, IO::ByteFormat::LittleEndian)
- return unless version == 0 # 1 and 2 are used by object files (ANON_OBJECT_HEADER)
+ # machine(2) + time(4) + size(4) + ordinal/hint(2) + flags(2)
+ io.skip(14)
- # machine(2) + time(4) + size(4) + ordinal/hint(2) + flags(2)
- io.skip(14)
+ # TODO: is there a way to do this without constructing a temporary string,
+ # but with the optimizations present in `IO#gets`?
+ return unless io.gets('\0') # symbol name
- # TODO: is there a way to do this without constructing a temporary string,
- # but with the optimizations present in `IO#gets`?
- return unless io.gets('\0') # symbol name
+ if dll_name = io.gets('\0', chomp: true)
+ @dlls << dll_name if valid_dll?(dll_name)
+ end
+ else
+ # long import library, code based on GNU binutils `dlltool -I`:
+ # https://sourceware.org/git/?p=binutils-gdb.git;a=blob;f=binutils/dlltool.c;hb=967dc35c78adb85ee1e2e596047d9dc69107a9db#l3231
+
+ # timeDateStamp(4) + pointerToSymbolTable(4) + numberOfSymbols(4) + sizeOfOptionalHeader(2) + characteristics(2)
+ io.skip(16)
+
+ section_count.times do |i|
+ section_header = uninitialized LibC::IMAGE_SECTION_HEADER
+ return unless io.read_fully?(pointerof(section_header).to_slice(1).to_unsafe_bytes)
+
+ name = String.new(section_header.name.to_unsafe, section_header.name.index(0) || section_header.name.size)
+ next unless name == (msvc? ? ".idata$6" : ".idata$7")
+
+ if msvc? ? section_header.characteristics.bits_set?(LibC::IMAGE_SCN_CNT_INITIALIZED_DATA) : section_header.pointerToRelocations == 0
+ bytes_read = sizeof(LibC::IMAGE_FILE_HEADER) + sizeof(LibC::IMAGE_SECTION_HEADER) * (i + 1)
+ io.skip(section_header.pointerToRawData - bytes_read)
+ if dll_name = io.gets('\0', chomp: true, limit: section_header.sizeOfRawData)
+ @dlls << dll_name if valid_dll?(dll_name)
+ end
+ end
- if dll_name = io.gets('\0', chomp: true)
- @dlls << dll_name
+ return
+ end
end
end
+
+ private def valid_dll?(name)
+ name.size >= 5 && name[-4..].compare(".dll", case_insensitive: true) == 0
+ end
end
end
diff --git a/src/crystal/system/win32/path.cr b/src/crystal/system/win32/path.cr
index 06f9346a2bae..f7bb1d23191b 100644
--- a/src/crystal/system/win32/path.cr
+++ b/src/crystal/system/win32/path.cr
@@ -4,18 +4,16 @@ require "c/shlobj_core"
module Crystal::System::Path
def self.home : String
- if home_path = ENV["USERPROFILE"]?.presence
- home_path
+ ENV["USERPROFILE"]?.presence || known_folder_path(LibC::FOLDERID_Profile)
+ end
+
+ def self.known_folder_path(guid : LibC::GUID) : String
+ if LibC.SHGetKnownFolderPath(pointerof(guid), 0, nil, out path_ptr) == 0
+ path, _ = String.from_utf16(path_ptr)
+ LibC.CoTaskMemFree(path_ptr)
+ path
else
- # TODO: interpreter doesn't implement pointerof(Path)` yet
- folderid = LibC::FOLDERID_Profile
- if LibC.SHGetKnownFolderPath(pointerof(folderid), 0, nil, out path_ptr) == 0
- home_path, _ = String.from_utf16(path_ptr)
- LibC.CoTaskMemFree(path_ptr)
- home_path
- else
- raise RuntimeError.from_winerror("SHGetKnownFolderPath")
- end
+ raise RuntimeError.from_winerror("SHGetKnownFolderPath")
end
end
end
diff --git a/src/crystal/system/win32/process.cr b/src/crystal/system/win32/process.cr
index 05b2ea36584e..7031654d2299 100644
--- a/src/crystal/system/win32/process.cr
+++ b/src/crystal/system/win32/process.cr
@@ -17,7 +17,7 @@ struct Crystal::System::Process
@thread_id : LibC::DWORD
@process_handle : LibC::HANDLE
@job_object : LibC::HANDLE
- @completion_key = IOCP::CompletionKey.new
+ @completion_key = IOCP::CompletionKey.new(:process_run)
@@interrupt_handler : Proc(::Process::ExitReason, Nil)?
@@interrupt_count = Crystal::AtomicSemaphore.new
@@ -326,9 +326,9 @@ struct Crystal::System::Process
end
private def self.try_replace(command_args, env, clear_env, input, output, error, chdir)
- reopen_io(input, ORIGINAL_STDIN)
- reopen_io(output, ORIGINAL_STDOUT)
- reopen_io(error, ORIGINAL_STDERR)
+ old_input_fd = reopen_io(input, ORIGINAL_STDIN)
+ old_output_fd = reopen_io(output, ORIGINAL_STDOUT)
+ old_error_fd = reopen_io(error, ORIGINAL_STDERR)
ENV.clear if clear_env
env.try &.each do |key, val|
@@ -351,11 +351,18 @@ struct Crystal::System::Process
argv << Pointer(LibC::WCHAR).null
LibC._wexecvp(command, argv)
+
+ # exec failed; restore the original C runtime file descriptors
+ errno = Errno.value
+ LibC._dup2(old_input_fd, 0)
+ LibC._dup2(old_output_fd, 1)
+ LibC._dup2(old_error_fd, 2)
+ errno
end
def self.replace(command_args, env, clear_env, input, output, error, chdir) : NoReturn
- try_replace(command_args, env, clear_env, input, output, error, chdir)
- raise_exception_from_errno(command_args.is_a?(String) ? command_args : command_args[0])
+ errno = try_replace(command_args, env, clear_env, input, output, error, chdir)
+ raise_exception_from_errno(command_args.is_a?(String) ? command_args : command_args[0], errno)
end
private def self.raise_exception_from_errno(command, errno = Errno.value)
@@ -367,21 +374,41 @@ struct Crystal::System::Process
end
end
+ # Replaces the C standard streams' file descriptors, not Win32's, since
+ # `try_replace` uses the C `LibC._wexecvp` and only cares about the former.
+ # Returns a duplicate of the original file descriptor
private def self.reopen_io(src_io : IO::FileDescriptor, dst_io : IO::FileDescriptor)
- src_io = to_real_fd(src_io)
+ unless src_io.system_blocking?
+ raise IO::Error.new("Non-blocking streams are not supported in `Process.exec`", target: src_io)
+ end
- dst_io.reopen(src_io)
- dst_io.blocking = true
- dst_io.close_on_exec = false
- end
+ src_fd =
+ case src_io
+ when STDIN then 0
+ when STDOUT then 1
+ when STDERR then 2
+ else
+ LibC._open_osfhandle(src_io.windows_handle, 0)
+ end
- private def self.to_real_fd(fd : IO::FileDescriptor)
- case fd
- when STDIN then ORIGINAL_STDIN
- when STDOUT then ORIGINAL_STDOUT
- when STDERR then ORIGINAL_STDERR
- else fd
+ dst_fd =
+ case dst_io
+ when ORIGINAL_STDIN then 0
+ when ORIGINAL_STDOUT then 1
+ when ORIGINAL_STDERR then 2
+ else
+ raise "BUG: Invalid destination IO"
+ end
+
+ return src_fd if dst_fd == src_fd
+
+ orig_src_fd = LibC._dup(src_fd)
+
+ if LibC._dup2(src_fd, dst_fd) == -1
+ raise IO::Error.from_errno("Failed to replace C file descriptor", target: dst_io)
end
+
+ orig_src_fd
end
def self.chroot(path)
diff --git a/src/crystal/system/win32/signal.cr b/src/crystal/system/win32/signal.cr
index d805ea4fd1ab..4cebe7cf9c6a 100644
--- a/src/crystal/system/win32/signal.cr
+++ b/src/crystal/system/win32/signal.cr
@@ -1,4 +1,5 @@
require "c/signal"
+require "c/malloc"
module Crystal::System::Signal
def self.trap(signal, handler) : Nil
@@ -16,4 +17,47 @@ module Crystal::System::Signal
def self.ignore(signal) : Nil
raise NotImplementedError.new("Crystal::System::Signal.ignore")
end
+
+ def self.setup_seh_handler
+ LibC.AddVectoredExceptionHandler(1, ->(exception_info) do
+ case exception_info.value.exceptionRecord.value.exceptionCode
+ when LibC::EXCEPTION_ACCESS_VIOLATION
+ addr = exception_info.value.exceptionRecord.value.exceptionInformation[1]
+ Crystal::System.print_error "Invalid memory access (C0000005) at address %p\n", Pointer(Void).new(addr)
+ {% if flag?(:gnu) %}
+ Exception::CallStack.print_backtrace
+ {% else %}
+ Exception::CallStack.print_backtrace(exception_info)
+ {% end %}
+ LibC._exit(1)
+ when LibC::EXCEPTION_STACK_OVERFLOW
+ LibC._resetstkoflw
+ Crystal::System.print_error "Stack overflow (e.g., infinite or very deep recursion)\n"
+ {% if flag?(:gnu) %}
+ Exception::CallStack.print_backtrace
+ {% else %}
+ Exception::CallStack.print_backtrace(exception_info)
+ {% end %}
+ LibC._exit(1)
+ else
+ LibC::EXCEPTION_CONTINUE_SEARCH
+ end
+ end)
+
+ # ensure that even in the case of stack overflow there is enough reserved
+ # stack space for recovery (for other threads this is done in
+ # `Crystal::System::Thread.thread_proc`)
+ stack_size = Crystal::System::Fiber::RESERVED_STACK_SIZE
+ LibC.SetThreadStackGuarantee(pointerof(stack_size))
+
+ # this catches invalid argument checks inside the C runtime library
+ LibC._set_invalid_parameter_handler(->(expression, _function, _file, _line, _pReserved) do
+ message = expression ? String.from_utf16(expression)[0] : "(no message)"
+ Crystal::System.print_error "CRT invalid parameter handler invoked: %s\n", message
+ caller.each do |frame|
+ Crystal::System.print_error " from %s\n", frame
+ end
+ LibC._exit(1)
+ end)
+ end
end
diff --git a/src/crystal/system/win32/socket.cr b/src/crystal/system/win32/socket.cr
index 6a5d44ab5133..bfb82581204b 100644
--- a/src/crystal/system/win32/socket.cr
+++ b/src/crystal/system/win32/socket.cr
@@ -128,8 +128,8 @@ module Crystal::System::Socket
end
# :nodoc:
- def overlapped_connect(socket, method, &)
- IOCP::OverlappedOperation.run(socket) do |operation|
+ def overlapped_connect(socket, method, timeout, &)
+ IOCP::WSAOverlappedOperation.run(socket) do |operation|
result = yield operation
if result == 0
@@ -142,11 +142,10 @@ module Crystal::System::Socket
return ::Socket::Error.from_os_error("ConnectEx", error)
end
else
- operation.done!
return nil
end
- operation.wait_for_wsa_result(read_timeout) do |error|
+ operation.wait_for_result(timeout) do |error|
case error
when .wsa_io_incomplete?, .wsaeconnrefused?
return ::Socket::ConnectError.from_os_error(method, error)
@@ -193,7 +192,7 @@ module Crystal::System::Socket
end
def overlapped_accept(socket, method, &)
- IOCP::OverlappedOperation.run(socket) do |operation|
+ IOCP::WSAOverlappedOperation.run(socket) do |operation|
result = yield operation
if result == 0
@@ -204,18 +203,15 @@ module Crystal::System::Socket
return false
end
else
- operation.done!
return true
end
- unless operation.wait_for_completion(read_timeout)
- raise IO::TimeoutError.new("#{method} timed out")
- end
-
- operation.wsa_result do |error|
+ operation.wait_for_result(read_timeout) do |error|
case error
when .wsa_io_incomplete?, .wsaenotsock?
return false
+ when .error_operation_aborted?
+ raise IO::TimeoutError.new("#{method} timed out")
end
end
@@ -370,6 +366,10 @@ module Crystal::System::Socket
end
def system_close
+ socket_close
+ end
+
+ private def socket_close(&)
handle = @volatile_fd.swap(LibC::INVALID_SOCKET)
ret = LibC.closesocket(handle)
@@ -379,11 +379,17 @@ module Crystal::System::Socket
when WinError::WSAEINTR, WinError::WSAEINPROGRESS
# ignore
else
- raise ::Socket::Error.from_os_error("Error closing socket", err)
+ yield err
end
end
end
+ def socket_close
+ socket_close do |err|
+ raise ::Socket::Error.from_os_error("Error closing socket", err)
+ end
+ end
+
private def system_local_address
sockaddr6 = uninitialized LibC::SockaddrIn6
sockaddr = pointerof(sockaddr6).as(LibC::Sockaddr*)
diff --git a/src/crystal/system/win32/thread.cr b/src/crystal/system/win32/thread.cr
index ddfe3298b20a..9cb60f01ced8 100644
--- a/src/crystal/system/win32/thread.cr
+++ b/src/crystal/system/win32/thread.cr
@@ -1,5 +1,6 @@
require "c/processthreadsapi"
require "c/synchapi"
+require "../panic"
module Crystal::System::Thread
alias Handle = LibC::HANDLE
@@ -44,12 +45,49 @@ module Crystal::System::Thread
LibC.SwitchToThread
end
- @[ThreadLocal]
- class_property current_thread : ::Thread { ::Thread.new }
+ # MinGW does not support TLS correctly
+ {% if flag?(:gnu) %}
+ @@current_key : LibC::DWORD = begin
+ current_key = LibC.TlsAlloc
+ if current_key == LibC::TLS_OUT_OF_INDEXES
+ Crystal::System.panic("TlsAlloc()", WinError.value)
+ end
+ current_key
+ end
- def self.current_thread? : ::Thread?
- @@current_thread
- end
+ def self.current_thread : ::Thread
+ th = current_thread?
+ return th if th
+
+ # Thread#start sets `Thread.current` as soon it starts. Thus we know
+ # that if `Thread.current` is not set then we are in the main thread
+ self.current_thread = ::Thread.new
+ end
+
+ def self.current_thread? : ::Thread?
+ ptr = LibC.TlsGetValue(@@current_key)
+ err = WinError.value
+ unless err == WinError::ERROR_SUCCESS
+ Crystal::System.panic("TlsGetValue()", err)
+ end
+
+ ptr.as(::Thread?)
+ end
+
+ def self.current_thread=(thread : ::Thread)
+ if LibC.TlsSetValue(@@current_key, thread.as(Void*)) == 0
+ Crystal::System.panic("TlsSetValue()", WinError.value)
+ end
+ thread
+ end
+ {% else %}
+ @[ThreadLocal]
+ class_property current_thread : ::Thread { ::Thread.new }
+
+ def self.current_thread? : ::Thread?
+ @@current_thread
+ end
+ {% end %}
def self.sleep(time : ::Time::Span) : Nil
LibC.Sleep(time.total_milliseconds.to_i.clamp(1..))
@@ -75,7 +113,9 @@ module Crystal::System::Thread
{% else %}
tib = LibC.NtCurrentTeb
high_limit = tib.value.stackBase
- LibC.VirtualQuery(tib.value.stackLimit, out mbi, sizeof(LibC::MEMORY_BASIC_INFORMATION))
+ if LibC.VirtualQuery(tib.value.stackLimit, out mbi, sizeof(LibC::MEMORY_BASIC_INFORMATION)) == 0
+ raise RuntimeError.from_winerror("VirtualQuery")
+ end
low_limit = mbi.allocationBase
low_limit
{% end %}
@@ -87,4 +127,31 @@ module Crystal::System::Thread
{% end %}
name
end
+
+ def self.init_suspend_resume : Nil
+ end
+
+ private def system_suspend : Nil
+ if LibC.SuspendThread(@system_handle) == -1
+ Crystal::System.panic("SuspendThread()", WinError.value)
+ end
+ end
+
+ private def system_wait_suspended : Nil
+ # context must be aligned on 16 bytes but we lack a mean to force the
+ # alignment on the struct, so we overallocate then realign the pointer:
+ local = uninitialized UInt8[sizeof(Tuple(LibC::CONTEXT, UInt8[15]))]
+ thread_context = Pointer(LibC::CONTEXT).new(local.to_unsafe.address &+ 15_u64 & ~15_u64)
+ thread_context.value.contextFlags = LibC::CONTEXT_FULL
+
+ if LibC.GetThreadContext(@system_handle, thread_context) == -1
+ Crystal::System.panic("GetThreadContext()", WinError.value)
+ end
+ end
+
+ private def system_resume : Nil
+ if LibC.ResumeThread(@system_handle) == -1
+ Crystal::System.panic("ResumeThread()", WinError.value)
+ end
+ end
end
diff --git a/src/crystal/system/win32/user.cr b/src/crystal/system/win32/user.cr
new file mode 100644
index 000000000000..4a06570c72b8
--- /dev/null
+++ b/src/crystal/system/win32/user.cr
@@ -0,0 +1,222 @@
+require "crystal/system/windows"
+require "c/lm"
+require "c/userenv"
+require "c/security"
+
+# This file contains source code derived from the following:
+#
+# * https://cs.opensource.google/go/go/+/refs/tags/go1.23.0:src/os/user/lookup_windows.go
+# * https://cs.opensource.google/go/go/+/refs/tags/go1.23.0:src/syscall/security_windows.go
+#
+# The following is their license:
+#
+# Copyright 2009 The Go Authors.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google LLC nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+module Crystal::System::User
+ def initialize(@username : String, @id : String, @group_id : String, @name : String, @home_directory : String)
+ end
+
+ def system_username
+ @username
+ end
+
+ def system_id
+ @id
+ end
+
+ def system_group_id
+ @group_id
+ end
+
+ def system_name
+ @name
+ end
+
+ def system_home_directory
+ @home_directory
+ end
+
+ def system_shell
+ Crystal::System::User.cmd_path
+ end
+
+ class_getter(cmd_path : String) do
+ "#{Crystal::System::Path.known_folder_path(LibC::FOLDERID_System)}\\cmd.exe"
+ end
+
+ def self.from_username?(username : String) : ::System::User?
+ if found = Crystal::System.name_to_sid(username)
+ if found.type.sid_type_user?
+ from_sid(found.sid)
+ end
+ end
+ end
+
+ def self.from_id?(id : String) : ::System::User?
+ if sid = Crystal::System.sid_from_s(id)
+ begin
+ from_sid(sid)
+ ensure
+ LibC.LocalFree(sid)
+ end
+ end
+ end
+
+ private def self.from_sid(sid : LibC::SID*) : ::System::User?
+ canonical = Crystal::System.sid_to_name(sid) || return
+ return unless canonical.type.sid_type_user?
+
+ domain_and_user = "#{canonical.domain}\\#{canonical.name}"
+ full_name = lookup_full_name(canonical.name, canonical.domain, domain_and_user) || return
+ pgid = lookup_primary_group_id(canonical.name, canonical.domain) || return
+ uid = Crystal::System.sid_to_s(sid)
+ home_dir = lookup_home_directory(uid, canonical.name) || return
+
+ ::System::User.new(domain_and_user, uid, pgid, full_name, home_dir)
+ end
+
+ private def self.lookup_full_name(name : String, domain : String, domain_and_user : String) : String?
+ if domain_joined?
+ domain_and_user = Crystal::System.to_wstr(domain_and_user)
+ Crystal::System.retry_wstr_buffer do |buffer, small_buf|
+ len = LibC::ULong.new(buffer.size)
+ if LibC.TranslateNameW(domain_and_user, LibC::EXTENDED_NAME_FORMAT::NameSamCompatible, LibC::EXTENDED_NAME_FORMAT::NameDisplay, buffer, pointerof(len)) != 0
+ return String.from_utf16(buffer[0, len - 1])
+ elsif small_buf && len > 0
+ next len
+ else
+ break
+ end
+ end
+ end
+
+ info = uninitialized LibC::USER_INFO_10*
+ if LibC.NetUserGetInfo(Crystal::System.to_wstr(domain), Crystal::System.to_wstr(name), 10, pointerof(info).as(LibC::BYTE**)) == LibC::NERR_Success
+ begin
+ str, _ = String.from_utf16(info.value.usri10_full_name)
+ return str
+ ensure
+ LibC.NetApiBufferFree(info)
+ end
+ end
+
+ # domain worked neither as a domain nor as a server
+ # could be domain server unavailable
+ # pretend username is fullname
+ name
+ end
+
+ # obtains the primary group SID for a user using this method:
+ # https://support.microsoft.com/en-us/help/297951/how-to-use-the-primarygroupid-attribute-to-find-the-primary-group-for
+ # The method follows this formula: domainRID + "-" + primaryGroupRID
+ private def self.lookup_primary_group_id(name : String, domain : String) : String?
+ domain_sid = Crystal::System.name_to_sid(domain) || return
+ return unless domain_sid.type.sid_type_domain?
+
+ domain_sid_str = Crystal::System.sid_to_s(domain_sid.sid)
+
+ # If the user has joined a domain use the RID of the default primary group
+ # called "Domain Users":
+ # https://support.microsoft.com/en-us/help/243330/well-known-security-identifiers-in-windows-operating-systems
+ # SID: S-1-5-21domain-513
+ #
+ # The correct way to obtain the primary group of a domain user is
+ # probing the user primaryGroupID attribute in the server Active Directory:
+ # https://learn.microsoft.com/en-us/windows/win32/adschema/a-primarygroupid
+ #
+ # Note that the primary group of domain users should not be modified
+ # on Windows for performance reasons, even if it's possible to do that.
+ # The .NET Developer's Guide to Directory Services Programming - Page 409
+ # https://books.google.bg/books?id=kGApqjobEfsC&lpg=PA410&ots=p7oo-eOQL7&dq=primary%20group%20RID&hl=bg&pg=PA409#v=onepage&q&f=false
+ return "#{domain_sid_str}-513" if domain_joined?
+
+ # For non-domain users call NetUserGetInfo() with level 4, which
+ # in this case would not have any network overhead.
+ # The primary group should not change from RID 513 here either
+ # but the group will be called "None" instead:
+ # https://www.adampalmer.me/iodigitalsec/2013/08/10/windows-null-session-enumeration/
+ # "Group 'None' (RID: 513)"
+ info = uninitialized LibC::USER_INFO_4*
+ if LibC.NetUserGetInfo(Crystal::System.to_wstr(domain), Crystal::System.to_wstr(name), 4, pointerof(info).as(LibC::BYTE**)) == LibC::NERR_Success
+ begin
+ "#{domain_sid_str}-#{info.value.usri4_primary_group_id}"
+ ensure
+ LibC.NetApiBufferFree(info)
+ end
+ end
+ end
+
+ private REGISTRY_PROFILE_LIST = %q(SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList).to_utf16
+ private ProfileImagePath = "ProfileImagePath".to_utf16
+
+ private def self.lookup_home_directory(uid : String, username : String) : String?
+ # If this user has logged in at least once their home path should be stored
+ # in the registry under the specified SID. References:
+ # https://social.technet.microsoft.com/wiki/contents/articles/13895.how-to-remove-a-corrupted-user-profile-from-the-registry.aspx
+ # https://support.asperasoft.com/hc/en-us/articles/216127438-How-to-delete-Windows-user-profiles
+ #
+ # The registry is the most reliable way to find the home path as the user
+ # might have decided to move it outside of the default location,
+ # (e.g. C:\users). Reference:
+ # https://answers.microsoft.com/en-us/windows/forum/windows_7-security/how-do-i-set-a-home-directory-outside-cusers-for-a/aed68262-1bf4-4a4d-93dc-7495193a440f
+ reg_home_dir = WindowsRegistry.open?(LibC::HKEY_LOCAL_MACHINE, REGISTRY_PROFILE_LIST) do |key_handle|
+ WindowsRegistry.open?(key_handle, uid.to_utf16) do |sub_handle|
+ WindowsRegistry.get_string(sub_handle, ProfileImagePath)
+ end
+ end
+ return reg_home_dir if reg_home_dir
+
+ # If the home path does not exist in the registry, the user might
+ # have not logged in yet; fall back to using getProfilesDirectory().
+ # Find the username based on a SID and append that to the result of
+ # getProfilesDirectory(). The domain is not relevant here.
+ # NOTE: the user has not logged in so this directory might not exist
+ profile_dir = Crystal::System.retry_wstr_buffer do |buffer, small_buf|
+ len = LibC::DWORD.new(buffer.size)
+ if LibC.GetProfilesDirectoryW(buffer, pointerof(len)) != 0
+ break String.from_utf16(buffer[0, len - 1])
+ elsif small_buf && len > 0
+ next len
+ else
+ break nil
+ end
+ end
+ return "#{profile_dir}\\#{username}" if profile_dir
+ end
+
+ private def self.domain_joined? : Bool
+ status = LibC.NetGetJoinInformation(nil, out domain, out type)
+ if status != LibC::NERR_Success
+ raise RuntimeError.from_os_error("NetGetJoinInformation", WinError.new(status))
+ end
+ is_domain = type.net_setup_domain_name?
+ LibC.NetApiBufferFree(domain)
+ is_domain
+ end
+end
diff --git a/src/crystal/system/win32/wmain.cr b/src/crystal/system/win32/wmain.cr
index 71383c66a88a..caad6748229f 100644
--- a/src/crystal/system/win32/wmain.cr
+++ b/src/crystal/system/win32/wmain.cr
@@ -4,7 +4,12 @@ require "c/stdlib"
{% begin %}
# we have both `main` and `wmain`, so we must choose an unambiguous entry point
- @[Link({{ flag?(:static) ? "libcmt" : "msvcrt" }}, ldflags: "/ENTRY:wmainCRTStartup")]
+ @[Link({{ flag?(:static) ? "libcmt" : "msvcrt" }})]
+ {% if flag?(:msvc) %}
+ @[Link(ldflags: "/ENTRY:wmainCRTStartup")]
+ {% elsif flag?(:gnu) && !flag?(:interpreted) %}
+ @[Link(ldflags: "-municode")]
+ {% end %}
{% end %}
lib LibCrystalMain
end
diff --git a/src/crystal/system/windows.cr b/src/crystal/system/windows.cr
index b303d4d61f6d..90b38396cf8f 100644
--- a/src/crystal/system/windows.cr
+++ b/src/crystal/system/windows.cr
@@ -1,3 +1,5 @@
+require "c/sddl"
+
# :nodoc:
module Crystal::System
def self.retry_wstr_buffer(&)
@@ -13,4 +15,55 @@ module Crystal::System
def self.to_wstr(str : String, name : String? = nil) : LibC::LPWSTR
str.check_no_null_byte(name).to_utf16.to_unsafe
end
+
+ def self.sid_to_s(sid : LibC::SID*) : String
+ if LibC.ConvertSidToStringSidW(sid, out ptr) == 0
+ raise RuntimeError.from_winerror("ConvertSidToStringSidW")
+ end
+ str, _ = String.from_utf16(ptr)
+ LibC.LocalFree(ptr)
+ str
+ end
+
+ def self.sid_from_s(str : String) : LibC::SID*
+ status = LibC.ConvertStringSidToSidW(to_wstr(str), out sid)
+ status != 0 ? sid : Pointer(LibC::SID).null
+ end
+
+ record SIDLookupResult, sid : LibC::SID*, domain : String, type : LibC::SID_NAME_USE
+
+ def self.name_to_sid(name : String) : SIDLookupResult?
+ utf16_name = to_wstr(name)
+
+ sid_size = LibC::DWORD.zero
+ domain_buf_size = LibC::DWORD.zero
+ LibC.LookupAccountNameW(nil, utf16_name, nil, pointerof(sid_size), nil, pointerof(domain_buf_size), out _)
+
+ unless WinError.value.error_none_mapped?
+ sid = Pointer(UInt8).malloc(sid_size).as(LibC::SID*)
+ domain_buf = Slice(LibC::WCHAR).new(domain_buf_size)
+ if LibC.LookupAccountNameW(nil, utf16_name, sid, pointerof(sid_size), domain_buf, pointerof(domain_buf_size), out sid_type) != 0
+ domain = String.from_utf16(domain_buf[..-2])
+ SIDLookupResult.new(sid, domain, sid_type)
+ end
+ end
+ end
+
+ record NameLookupResult, name : String, domain : String, type : LibC::SID_NAME_USE
+
+ def self.sid_to_name(sid : LibC::SID*) : NameLookupResult?
+ name_buf_size = LibC::DWORD.zero
+ domain_buf_size = LibC::DWORD.zero
+ LibC.LookupAccountSidW(nil, sid, nil, pointerof(name_buf_size), nil, pointerof(domain_buf_size), out _)
+
+ unless WinError.value.error_none_mapped?
+ name_buf = Slice(LibC::WCHAR).new(name_buf_size)
+ domain_buf = Slice(LibC::WCHAR).new(domain_buf_size)
+ if LibC.LookupAccountSidW(nil, sid, name_buf, pointerof(name_buf_size), domain_buf, pointerof(domain_buf_size), out sid_type) != 0
+ name = String.from_utf16(name_buf[..-2])
+ domain = String.from_utf16(domain_buf[..-2])
+ NameLookupResult.new(name, domain, sid_type)
+ end
+ end
+ end
end
diff --git a/src/docs_main.cr b/src/docs_main.cr
index 5769678ca131..1fec70580a04 100644
--- a/src/docs_main.cr
+++ b/src/docs_main.cr
@@ -52,11 +52,11 @@ require "./string_pool"
require "./string_scanner"
require "./unicode/unicode"
require "./uri"
+require "./uri/json"
+require "./uri/params/serializable"
require "./uuid"
require "./uuid/json"
require "./syscall"
-{% unless flag?(:win32) %}
- require "./system/*"
-{% end %}
+require "./system/*"
require "./wait_group"
require "./docs_pseudo_methods"
diff --git a/src/docs_pseudo_methods.cr b/src/docs_pseudo_methods.cr
index d4f1fb832263..d789f4a9ecc8 100644
--- a/src/docs_pseudo_methods.cr
+++ b/src/docs_pseudo_methods.cr
@@ -200,3 +200,33 @@ class Object
def __crystal_pseudo_responds_to?(name : Symbol) : Bool
end
end
+
+# Some expressions won't return to the current scope and therefore have no return type.
+# This is expressed as the special return type `NoReturn`.
+#
+# Typical examples for non-returning methods and keywords are `return`, `exit`, `raise`, `next`, and `break`.
+#
+# This is for example useful for deconstructing union types:
+#
+# ```
+# string = STDIN.gets
+# typeof(string) # => String?
+# typeof(raise "Empty input") # => NoReturn
+# typeof(string || raise "Empty input") # => String
+# ```
+#
+# The compiler recognizes that in case string is Nil, the right hand side of the expression `string || raise` will be evaluated.
+# Since `typeof(raise "Empty input")` is `NoReturn` the execution would not return to the current scope in that case.
+# That leaves only `String` as resulting type of the expression.
+#
+# Every expression whose code paths all result in `NoReturn` will be `NoReturn` as well.
+# `NoReturn` does not show up in a union type because it would essentially be included in every expression's type.
+# It is only used when an expression will never return to the current scope.
+#
+# `NoReturn` can be explicitly set as return type of a method or function definition but will usually be inferred by the compiler.
+struct CRYSTAL_PSEUDO__NoReturn
+end
+
+# Similar in usage to `Nil`. `Void` is preferred for C lib bindings.
+struct CRYSTAL_PSEUDO__Void
+end
diff --git a/src/ecr/macros.cr b/src/ecr/macros.cr
index 92c02cc4284a..5e051232271b 100644
--- a/src/ecr/macros.cr
+++ b/src/ecr/macros.cr
@@ -34,7 +34,7 @@ module ECR
# ```
macro def_to_s(filename)
def to_s(__io__ : IO) : Nil
- ECR.embed {{filename}}, "__io__"
+ ::ECR.embed {{filename}}, "__io__"
end
end
diff --git a/src/env.cr b/src/env.cr
index b28e4014ea22..13779f3051aa 100644
--- a/src/env.cr
+++ b/src/env.cr
@@ -60,7 +60,7 @@ module ENV
# Retrieves a value corresponding to the given *key*. Return the second argument's value
# if the *key* does not exist.
- def self.fetch(key, default) : String?
+ def self.fetch(key, default : T) : String | T forall T
fetch(key) { default }
end
diff --git a/src/exception/call_stack.cr b/src/exception/call_stack.cr
index c80f73a6ce48..506317d2580e 100644
--- a/src/exception/call_stack.cr
+++ b/src/exception/call_stack.cr
@@ -1,6 +1,6 @@
{% if flag?(:interpreted) %}
require "./call_stack/interpreter"
-{% elsif flag?(:win32) %}
+{% elsif flag?(:win32) && !flag?(:gnu) %}
require "./call_stack/stackwalk"
{% elsif flag?(:wasm32) %}
require "./call_stack/null"
@@ -31,10 +31,11 @@ struct Exception::CallStack
@callstack : Array(Void*)
@backtrace : Array(String)?
- def initialize
- @callstack = CallStack.unwind
+ def initialize(@callstack : Array(Void*) = CallStack.unwind)
end
+ class_getter empty = new([] of Void*)
+
def printable_backtrace : Array(String)
@backtrace ||= decode_backtrace
end
diff --git a/src/exception/call_stack/dwarf.cr b/src/exception/call_stack/dwarf.cr
index 96d99f03205a..253a72a38ebc 100644
--- a/src/exception/call_stack/dwarf.cr
+++ b/src/exception/call_stack/dwarf.cr
@@ -10,6 +10,10 @@ struct Exception::CallStack
@@dwarf_line_numbers : Crystal::DWARF::LineNumbers?
@@dwarf_function_names : Array(Tuple(LibC::SizeT, LibC::SizeT, String))?
+ {% if flag?(:win32) %}
+ @@coff_symbols : Hash(Int32, Array(Crystal::PE::COFFSymbol))?
+ {% end %}
+
# :nodoc:
def self.load_debug_info : Nil
return if ENV["CRYSTAL_LOAD_DEBUG_INFO"]? == "0"
diff --git a/src/exception/call_stack/elf.cr b/src/exception/call_stack/elf.cr
index efa54f41329c..51d565528577 100644
--- a/src/exception/call_stack/elf.cr
+++ b/src/exception/call_stack/elf.cr
@@ -1,65 +1,83 @@
-require "crystal/elf"
-{% unless flag?(:wasm32) %}
- require "c/link"
+{% if flag?(:win32) %}
+ require "crystal/pe"
+{% else %}
+ require "crystal/elf"
+ {% unless flag?(:wasm32) %}
+ require "c/link"
+ {% end %}
{% end %}
struct Exception::CallStack
- private struct DlPhdrData
- getter program : String
- property base_address : LibC::Elf_Addr = 0
+ {% unless flag?(:win32) %}
+ private struct DlPhdrData
+ getter program : String
+ property base_address : LibC::Elf_Addr = 0
- def initialize(@program : String)
+ def initialize(@program : String)
+ end
end
- end
+ {% end %}
protected def self.load_debug_info_impl : Nil
program = Process.executable_path
return unless program && File::Info.readable? program
- data = DlPhdrData.new(program)
-
- phdr_callback = LibC::DlPhdrCallback.new do |info, size, data|
- # `dl_iterate_phdr` does not always visit the current program first; on
- # Android the first object is `/system/bin/linker64`, the second is the
- # full program path (not the empty string), so we check both here
- name_c_str = info.value.name
- if name_c_str && (name_c_str.value == 0 || LibC.strcmp(name_c_str, data.as(DlPhdrData*).value.program) == 0)
- # The first entry is the header for the current program.
- # Note that we avoid allocating here and just store the base address
- # to be passed to self.read_dwarf_sections when dl_iterate_phdr returns.
- # Calling self.read_dwarf_sections from this callback may lead to reallocations
- # and deadlocks due to the internal lock held by dl_iterate_phdr (#10084).
- data.as(DlPhdrData*).value.base_address = info.value.addr
- 1
- else
- 0
+
+ {% if flag?(:win32) %}
+ if LibC.GetModuleHandleExW(LibC::GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, nil, out hmodule) != 0
+ self.read_dwarf_sections(program, hmodule.address)
end
- end
+ {% else %}
+ data = DlPhdrData.new(program)
- LibC.dl_iterate_phdr(phdr_callback, pointerof(data))
- self.read_dwarf_sections(data.program, data.base_address)
+ phdr_callback = LibC::DlPhdrCallback.new do |info, size, data|
+ # `dl_iterate_phdr` does not always visit the current program first; on
+ # Android the first object is `/system/bin/linker64`, the second is the
+ # full program path (not the empty string), so we check both here
+ name_c_str = info.value.name
+ if name_c_str && (name_c_str.value == 0 || LibC.strcmp(name_c_str, data.as(DlPhdrData*).value.program) == 0)
+ # The first entry is the header for the current program.
+ # Note that we avoid allocating here and just store the base address
+ # to be passed to self.read_dwarf_sections when dl_iterate_phdr returns.
+ # Calling self.read_dwarf_sections from this callback may lead to reallocations
+ # and deadlocks due to the internal lock held by dl_iterate_phdr (#10084).
+ data.as(DlPhdrData*).value.base_address = info.value.addr
+ 1
+ else
+ 0
+ end
+ end
+
+ LibC.dl_iterate_phdr(phdr_callback, pointerof(data))
+ self.read_dwarf_sections(data.program, data.base_address)
+ {% end %}
end
protected def self.read_dwarf_sections(program, base_address = 0)
- Crystal::ELF.open(program) do |elf|
- line_strings = elf.read_section?(".debug_line_str") do |sh, io|
+ {{ flag?(:win32) ? Crystal::PE : Crystal::ELF }}.open(program) do |image|
+ {% if flag?(:win32) %}
+ base_address -= image.original_image_base
+ @@coff_symbols = image.coff_symbols
+ {% end %}
+
+ line_strings = image.read_section?(".debug_line_str") do |sh, io|
Crystal::DWARF::Strings.new(io, sh.offset, sh.size)
end
- strings = elf.read_section?(".debug_str") do |sh, io|
+ strings = image.read_section?(".debug_str") do |sh, io|
Crystal::DWARF::Strings.new(io, sh.offset, sh.size)
end
- elf.read_section?(".debug_line") do |sh, io|
+ image.read_section?(".debug_line") do |sh, io|
@@dwarf_line_numbers = Crystal::DWARF::LineNumbers.new(io, sh.size, base_address, strings, line_strings)
end
- elf.read_section?(".debug_info") do |sh, io|
+ image.read_section?(".debug_info") do |sh, io|
names = [] of {LibC::SizeT, LibC::SizeT, String}
while (offset = io.pos - sh.offset) < sh.size
info = Crystal::DWARF::Info.new(io, offset)
- elf.read_section?(".debug_abbrev") do |sh, io|
+ image.read_section?(".debug_abbrev") do |sh, io|
info.read_abbreviations(io)
end
diff --git a/src/exception/call_stack/libunwind.cr b/src/exception/call_stack/libunwind.cr
index 73a851a00339..c0f75867aeba 100644
--- a/src/exception/call_stack/libunwind.cr
+++ b/src/exception/call_stack/libunwind.cr
@@ -1,9 +1,11 @@
-require "c/dlfcn"
+{% unless flag?(:win32) %}
+ require "c/dlfcn"
+{% end %}
require "c/stdio"
require "c/string"
require "../lib_unwind"
-{% if flag?(:darwin) || flag?(:bsd) || flag?(:linux) || flag?(:solaris) %}
+{% if flag?(:darwin) || flag?(:bsd) || flag?(:linux) || flag?(:solaris) || flag?(:win32) %}
require "./dwarf"
{% else %}
require "./null"
@@ -33,7 +35,11 @@ struct Exception::CallStack
{% end %}
def self.setup_crash_handler
- Crystal::System::Signal.setup_segfault_handler
+ {% if flag?(:win32) %}
+ Crystal::System::Signal.setup_seh_handler
+ {% else %}
+ Crystal::System::Signal.setup_segfault_handler
+ {% end %}
end
{% if flag?(:interpreted) %} @[Primitive(:interpreter_call_stack_unwind)] {% end %}
@@ -122,32 +128,18 @@ struct Exception::CallStack
end
{% end %}
- if frame = unsafe_decode_frame(repeated_frame.ip)
- offset, sname, fname = frame
+ unsafe_decode_frame(repeated_frame.ip) do |offset, sname, fname|
Crystal::System.print_error "%s +%lld in %s", sname, offset.to_i64, fname
- else
- Crystal::System.print_error "???"
+ return
end
- end
- protected def self.decode_frame(ip, original_ip = ip)
- if LibC.dladdr(ip, out info) != 0
- offset = original_ip - info.dli_saddr
+ Crystal::System.print_error "???"
+ end
- if offset == 0
- return decode_frame(ip - 1, original_ip)
- end
- return if info.dli_sname.null? && info.dli_fname.null?
- if info.dli_sname.null?
- symbol = "??"
- else
- symbol = String.new(info.dli_sname)
- end
- if info.dli_fname.null?
- file = "??"
- else
- file = String.new(info.dli_fname)
- end
+ protected def self.decode_frame(ip)
+ decode_frame(ip) do |offset, symbol, file|
+ symbol = symbol ? String.new(symbol) : "??"
+ file = file ? String.new(file) : "??"
{offset, symbol, file}
end
end
@@ -155,19 +147,128 @@ struct Exception::CallStack
# variant of `.decode_frame` that returns the C strings directly instead of
# wrapping them in `String.new`, since the SIGSEGV handler cannot allocate
# memory via the GC
- protected def self.unsafe_decode_frame(ip)
+ protected def self.unsafe_decode_frame(ip, &)
+ decode_frame(ip) do |offset, symbol, file|
+ symbol ||= "??".to_unsafe
+ file ||= "??".to_unsafe
+ yield offset, symbol, file
+ end
+ end
+
+ private def self.decode_frame(ip, &)
original_ip = ip
- while LibC.dladdr(ip, out info) != 0
- offset = original_ip - info.dli_saddr
- if offset == 0
- ip -= 1
- next
+ while true
+ retry = dladdr(ip) do |file, symbol, address|
+ offset = original_ip - address
+ if offset == 0
+ ip -= 1
+ true
+ elsif symbol.null? && file.null?
+ false
+ else
+ return yield offset, symbol, file
+ end
end
-
- return if info.dli_sname.null? && info.dli_fname.null?
- symbol = info.dli_sname || "??".to_unsafe
- file = info.dli_fname || "??".to_unsafe
- return {offset, symbol, file}
+ break unless retry
end
end
+
+ {% if flag?(:win32) %}
+ def self.dladdr(ip, &)
+ if LibC.GetModuleHandleExW(LibC::GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT | LibC::GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, ip.as(LibC::LPWSTR), out hmodule) != 0
+ symbol, address = internal_symbol(hmodule, ip) || external_symbol(hmodule, ip) || return
+
+ utf16_file = uninitialized LibC::WCHAR[LibC::MAX_PATH]
+ len = LibC.GetModuleFileNameW(hmodule, utf16_file, utf16_file.size)
+ if 0 < len < utf16_file.size
+ utf8_file = uninitialized UInt8[sizeof(UInt8[LibC::MAX_PATH][3])]
+ file = utf8_file.to_unsafe
+ appender = file.appender
+ String.each_utf16_char(utf16_file.to_slice[0, len + 1]) do |ch|
+ ch.each_byte { |b| appender << b }
+ end
+ else
+ file = Pointer(UInt8).null
+ end
+
+ yield file, symbol, address
+ end
+ end
+
+ private def self.internal_symbol(hmodule, ip)
+ if coff_symbols = @@coff_symbols
+ if LibC.GetModuleHandleExW(LibC::GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, nil, out this_hmodule) != 0 && this_hmodule == hmodule
+ section_base, section_index = lookup_section(hmodule, ip) || return
+ offset = ip - section_base
+ section_coff_symbols = coff_symbols[section_index]? || return
+ next_sym = section_coff_symbols.bsearch_index { |sym| offset < sym.offset } || return
+ sym = section_coff_symbols[next_sym - 1]? || return
+
+ {sym.name.to_unsafe, section_base + sym.offset}
+ end
+ end
+ end
+
+ private def self.external_symbol(hmodule, ip)
+ if dir = data_directory(hmodule, LibC::IMAGE_DIRECTORY_ENTRY_EXPORT)
+ exports = dir.to_unsafe.as(LibC::IMAGE_EXPORT_DIRECTORY*).value
+
+ found_address = Pointer(Void).null
+ found_index = -1
+
+ func_address_offsets = (hmodule + exports.addressOfFunctions).as(LibC::DWORD*).to_slice(exports.numberOfFunctions)
+ func_address_offsets.each_with_index do |offset, i|
+ address = hmodule + offset
+ if found_address < address <= ip
+ found_address, found_index = address, i
+ end
+ end
+
+ return unless found_address
+
+ func_name_ordinals = (hmodule + exports.addressOfNameOrdinals).as(LibC::WORD*).to_slice(exports.numberOfNames)
+ if ordinal_index = func_name_ordinals.index(&.== found_index)
+ symbol = (hmodule + (hmodule + exports.addressOfNames).as(LibC::DWORD*)[ordinal_index]).as(UInt8*)
+ {symbol, found_address}
+ end
+ end
+ end
+
+ private def self.lookup_section(hmodule, ip)
+ dos_header = hmodule.as(LibC::IMAGE_DOS_HEADER*)
+ return unless dos_header.value.e_magic == 0x5A4D # MZ
+
+ nt_header = (hmodule + dos_header.value.e_lfanew).as(LibC::IMAGE_NT_HEADERS*)
+ return unless nt_header.value.signature == 0x00004550 # PE\0\0
+
+ section_headers = (nt_header + 1).as(LibC::IMAGE_SECTION_HEADER*).to_slice(nt_header.value.fileHeader.numberOfSections)
+ section_headers.each_with_index do |header, i|
+ base = hmodule + header.virtualAddress
+ if base <= ip < base + header.virtualSize
+ return base, i
+ end
+ end
+ end
+
+ private def self.data_directory(hmodule, index)
+ dos_header = hmodule.as(LibC::IMAGE_DOS_HEADER*)
+ return unless dos_header.value.e_magic == 0x5A4D # MZ
+
+ nt_header = (hmodule + dos_header.value.e_lfanew).as(LibC::IMAGE_NT_HEADERS*)
+ return unless nt_header.value.signature == 0x00004550 # PE\0\0
+ return unless nt_header.value.optionalHeader.magic == {{ flag?(:bits64) ? 0x20b : 0x10b }}
+ return unless index.in?(0...{16, nt_header.value.optionalHeader.numberOfRvaAndSizes}.min)
+
+ directory = nt_header.value.optionalHeader.dataDirectory.to_unsafe[index]
+ if directory.virtualAddress != 0
+ Bytes.new(hmodule.as(UInt8*) + directory.virtualAddress, directory.size, read_only: true)
+ end
+ end
+ {% else %}
+ private def self.dladdr(ip, &)
+ if LibC.dladdr(ip, out info) != 0
+ yield info.dli_fname, info.dli_sname, info.dli_saddr
+ end
+ end
+ {% end %}
end
diff --git a/src/exception/call_stack/stackwalk.cr b/src/exception/call_stack/stackwalk.cr
index 2b9a03b472c7..d7e3da8e35f1 100644
--- a/src/exception/call_stack/stackwalk.cr
+++ b/src/exception/call_stack/stackwalk.cr
@@ -1,5 +1,4 @@
require "c/dbghelp"
-require "c/malloc"
# :nodoc:
struct Exception::CallStack
@@ -33,38 +32,7 @@ struct Exception::CallStack
end
def self.setup_crash_handler
- LibC.AddVectoredExceptionHandler(1, ->(exception_info) do
- case exception_info.value.exceptionRecord.value.exceptionCode
- when LibC::EXCEPTION_ACCESS_VIOLATION
- addr = exception_info.value.exceptionRecord.value.exceptionInformation[1]
- Crystal::System.print_error "Invalid memory access (C0000005) at address %p\n", Pointer(Void).new(addr)
- print_backtrace(exception_info)
- LibC._exit(1)
- when LibC::EXCEPTION_STACK_OVERFLOW
- LibC._resetstkoflw
- Crystal::System.print_error "Stack overflow (e.g., infinite or very deep recursion)\n"
- print_backtrace(exception_info)
- LibC._exit(1)
- else
- LibC::EXCEPTION_CONTINUE_SEARCH
- end
- end)
-
- # ensure that even in the case of stack overflow there is enough reserved
- # stack space for recovery (for other threads this is done in
- # `Crystal::System::Thread.thread_proc`)
- stack_size = Crystal::System::Fiber::RESERVED_STACK_SIZE
- LibC.SetThreadStackGuarantee(pointerof(stack_size))
-
- # this catches invalid argument checks inside the C runtime library
- LibC._set_invalid_parameter_handler(->(expression, _function, _file, _line, _pReserved) do
- message = expression ? String.from_utf16(expression)[0] : "(no message)"
- Crystal::System.print_error "CRT invalid parameter handler invoked: %s\n", message
- caller.each do |frame|
- Crystal::System.print_error " from %s\n", frame
- end
- LibC._exit(1)
- end)
+ Crystal::System::Signal.setup_seh_handler
end
{% if flag?(:interpreted) %} @[Primitive(:interpreter_call_stack_unwind)] {% end %}
@@ -93,6 +61,8 @@ struct Exception::CallStack
{% elsif flag?(:i386) %}
# TODO: use WOW64_CONTEXT in place of CONTEXT
{% raise "x86 not supported" %}
+ {% elsif flag?(:aarch64) %}
+ LibC::IMAGE_FILE_MACHINE_ARM64
{% else %}
{% raise "Architecture not supported" %}
{% end %}
@@ -102,9 +72,15 @@ struct Exception::CallStack
stack_frame.addrFrame.mode = LibC::ADDRESS_MODE::AddrModeFlat
stack_frame.addrStack.mode = LibC::ADDRESS_MODE::AddrModeFlat
- stack_frame.addrPC.offset = context.value.rip
- stack_frame.addrFrame.offset = context.value.rbp
- stack_frame.addrStack.offset = context.value.rsp
+ {% if flag?(:x86_64) %}
+ stack_frame.addrPC.offset = context.value.rip
+ stack_frame.addrFrame.offset = context.value.rbp
+ stack_frame.addrStack.offset = context.value.rsp
+ {% elsif flag?(:aarch64) %}
+ stack_frame.addrPC.offset = context.value.pc
+ stack_frame.addrFrame.offset = context.value.x[29]
+ stack_frame.addrStack.offset = context.value.sp
+ {% end %}
last_frame = nil
cur_proc = LibC.GetCurrentProcess
diff --git a/src/exception/lib_unwind.cr b/src/exception/lib_unwind.cr
index 7c9c6fd75ec5..83350c12fe3a 100644
--- a/src/exception/lib_unwind.cr
+++ b/src/exception/lib_unwind.cr
@@ -113,8 +113,12 @@ lib LibUnwind
struct Exception
exception_class : LibC::SizeT
exception_cleanup : LibC::SizeT
- private1 : UInt64
- private2 : UInt64
+ {% if flag?(:win32) && flag?(:gnu) %}
+ private_ : UInt64[6]
+ {% else %}
+ private1 : UInt64
+ private2 : UInt64
+ {% end %}
exception_object : Void*
exception_type_id : Int32
end
diff --git a/src/fiber.cr b/src/fiber.cr
index 0d471e5a96e4..1086ebdd3669 100644
--- a/src/fiber.cr
+++ b/src/fiber.cr
@@ -234,20 +234,21 @@ class Fiber
end
# :nodoc:
- def timeout(timeout : Time::Span?, select_action : Channel::TimeoutAction? = nil) : Nil
+ def timeout(timeout : Time::Span, select_action : Channel::TimeoutAction) : Nil
@timeout_select_action = select_action
timeout_event.add(timeout)
end
# :nodoc:
def cancel_timeout : Nil
+ return unless @timeout_select_action
@timeout_select_action = nil
@timeout_event.try &.delete
end
# The current fiber will resume after a period of time.
# The timeout can be cancelled with `cancel_timeout`
- def self.timeout(timeout : Time::Span?, select_action : Channel::TimeoutAction? = nil) : Nil
+ def self.timeout(timeout : Time::Span, select_action : Channel::TimeoutAction) : Nil
Fiber.current.timeout(timeout, select_action)
end
diff --git a/src/fiber/context/x86_64-microsoft.cr b/src/fiber/context/x86_64-microsoft.cr
index 55d893cb8184..08576fc348aa 100644
--- a/src/fiber/context/x86_64-microsoft.cr
+++ b/src/fiber/context/x86_64-microsoft.cr
@@ -4,19 +4,24 @@ class Fiber
# :nodoc:
def makecontext(stack_ptr, fiber_main) : Nil
# A great explanation on stack contexts for win32:
- # https://cfsamson.gitbook.io/green-threads-explained-in-200-lines-of-rust/supporting-windows
+ # https://web.archive.org/web/20220527113808/https://cfsamson.gitbook.io/green-threads-explained-in-200-lines-of-rust/supporting-windows
- # 8 registers + 2 qwords for NT_TIB + 1 parameter + 10 128bit XMM registers
- @context.stack_top = (stack_ptr - (11 + 10*2)).as(Void*)
+ # 8 registers + 3 qwords for NT_TIB + 1 parameter + 10 128bit XMM registers
+ @context.stack_top = (stack_ptr - (12 + 10*2)).as(Void*)
@context.resumable = 1
+ # actual stack top, not including guard pages and reserved pages
+ LibC.GetNativeSystemInfo(out system_info)
+ stack_top = @stack_bottom - system_info.dwPageSize
+
stack_ptr[0] = fiber_main.pointer # %rbx: Initial `resume` will `ret` to this address
stack_ptr[-1] = self.as(Void*) # %rcx: puts `self` as first argument for `fiber_main`
- # The following two values are stored in the Thread Information Block (NT_TIB)
+ # The following three values are stored in the Thread Information Block (NT_TIB)
# and are used by Windows to track the current stack limits
- stack_ptr[-2] = @stack # %gs:0x10: Stack Limit
- stack_ptr[-3] = @stack_bottom # %gs:0x08: Stack Base
+ stack_ptr[-2] = @stack # %gs:0x1478: Win32 DeallocationStack
+ stack_ptr[-3] = stack_top # %gs:0x10: Stack Limit
+ stack_ptr[-4] = @stack_bottom # %gs:0x08: Stack Base
end
# :nodoc:
@@ -27,6 +32,7 @@ class Fiber
# %rcx , %rdx
asm("
pushq %rcx
+ pushq %gs:0x1478 // Thread Information Block: Win32 DeallocationStack
pushq %gs:0x10 // Thread Information Block: Stack Limit
pushq %gs:0x08 // Thread Information Block: Stack Base
pushq %rdi // push 1st argument (because of initial resume)
@@ -73,6 +79,7 @@ class Fiber
popq %rdi // pop 1st argument (for initial resume)
popq %gs:0x08
popq %gs:0x10
+ popq %gs:0x1478
popq %rcx
")
{% else %}
@@ -80,6 +87,7 @@ class Fiber
# instructions that breaks the context switching.
asm("
pushq %rcx
+ pushq %gs:0x1478 // Thread Information Block: Win32 DeallocationStack
pushq %gs:0x10 // Thread Information Block: Stack Limit
pushq %gs:0x08 // Thread Information Block: Stack Base
pushq %rdi // push 1st argument (because of initial resume)
@@ -126,6 +134,7 @@ class Fiber
popq %rdi // pop 1st argument (for initial resume)
popq %gs:0x08
popq %gs:0x10
+ popq %gs:0x1478
popq %rcx
" :: "r"(current_context), "r"(new_context))
{% end %}
diff --git a/src/fiber/stack_pool.cr b/src/fiber/stack_pool.cr
index c9ea3ceb68e0..8f809335f46c 100644
--- a/src/fiber/stack_pool.cr
+++ b/src/fiber/stack_pool.cr
@@ -42,7 +42,11 @@ class Fiber
# Removes a stack from the bottom of the pool, or allocates a new one.
def checkout : {Void*, Void*}
- stack = @deque.pop? || Crystal::System::Fiber.allocate_stack(STACK_SIZE, @protect)
+ if stack = @deque.pop?
+ Crystal::System::Fiber.reset_stack(stack, STACK_SIZE, @protect)
+ else
+ stack = Crystal::System::Fiber.allocate_stack(STACK_SIZE, @protect)
+ end
{stack, stack + STACK_SIZE}
end
diff --git a/src/file.cr b/src/file.cr
index ff6c68ef4d03..1d12a01f4209 100644
--- a/src/file.cr
+++ b/src/file.cr
@@ -165,15 +165,15 @@ class File < IO::FileDescriptor
# *blocking* must be set to `false` on POSIX targets when the file to open
# isn't a regular file but a character device (e.g. `/dev/tty`) or fifo. These
# files depend on another process or thread to also be reading or writing, and
- # system event queues will properly report readyness.
+ # system event queues will properly report readiness.
#
# *blocking* may also be set to `nil` in which case the blocking or
# non-blocking flag will be determined automatically, at the expense of an
# additional syscall.
def self.new(filename : Path | String, mode = "r", perm = DEFAULT_CREATE_PERMISSIONS, encoding = nil, invalid = nil, blocking = true)
filename = filename.to_s
- fd = Crystal::System::File.open(filename, mode, perm: perm)
- new(filename, fd, blocking: blocking, encoding: encoding, invalid: invalid)
+ fd = Crystal::System::File.open(filename, mode, perm: perm, blocking: blocking)
+ new(filename, fd, blocking: blocking, encoding: encoding, invalid: invalid).tap { |f| f.system_set_mode(mode) }
end
getter path : String
diff --git a/src/file/preader.cr b/src/file/preader.cr
index d366457314ce..9f7d09643305 100644
--- a/src/file/preader.cr
+++ b/src/file/preader.cr
@@ -20,7 +20,7 @@ class File::PReader < IO
count = slice.size
count = Math.min(count, @bytesize - @pos)
- bytes_read = Crystal::System::FileDescriptor.pread(@file.fd, slice[0, count], @offset + @pos)
+ bytes_read = Crystal::System::FileDescriptor.pread(@file, slice[0, count], @offset + @pos)
@pos += bytes_read
diff --git a/src/gc/boehm.cr b/src/gc/boehm.cr
index 8ccc1bb7b6e8..33d6466d792b 100644
--- a/src/gc/boehm.cr
+++ b/src/gc/boehm.cr
@@ -161,6 +161,11 @@ lib LibGC
alias WarnProc = LibC::Char*, Word ->
fun set_warn_proc = GC_set_warn_proc(WarnProc)
$warn_proc = GC_current_warn_proc : WarnProc
+
+ fun stop_world_external = GC_stop_world_external
+ fun start_world_external = GC_start_world_external
+ fun get_suspend_signal = GC_get_suspend_signal : Int
+ fun get_thr_restart_signal = GC_get_thr_restart_signal : Int
end
module GC
@@ -195,7 +200,7 @@ module GC
{% end %}
LibGC.init
- LibGC.set_start_callback ->do
+ LibGC.set_start_callback -> do
GC.lock_write
end
@@ -446,7 +451,7 @@ module GC
@@curr_push_other_roots = block
@@prev_push_other_roots = LibGC.get_push_other_roots
- LibGC.set_push_other_roots ->do
+ LibGC.set_push_other_roots -> do
@@curr_push_other_roots.try(&.call)
@@prev_push_other_roots.try(&.call)
end
@@ -470,4 +475,26 @@ module GC
GC.unlock_write
end
{% end %}
+
+ # :nodoc:
+ def self.stop_world : Nil
+ LibGC.stop_world_external
+ end
+
+ # :nodoc:
+ def self.start_world : Nil
+ LibGC.start_world_external
+ end
+
+ {% if flag?(:unix) %}
+ # :nodoc:
+ def self.sig_suspend : Signal
+ Signal.new(LibGC.get_suspend_signal)
+ end
+
+ # :nodoc:
+ def self.sig_resume : Signal
+ Signal.new(LibGC.get_thr_restart_signal)
+ end
+ {% end %}
end
diff --git a/src/gc/none.cr b/src/gc/none.cr
index 640e6e8f927d..ce84027e6e69 100644
--- a/src/gc/none.cr
+++ b/src/gc/none.cr
@@ -5,6 +5,7 @@ require "crystal/tracing"
module GC
def self.init
+ Crystal::System::Thread.init_suspend_resume
end
# :nodoc:
@@ -138,4 +139,57 @@ module GC
# :nodoc:
def self.push_stack(stack_top, stack_bottom)
end
+
+ # Stop and start the world.
+ #
+ # This isn't a GC-safe stop-the-world implementation (it may allocate objects
+ # while stopping the world), but the guarantees are enough for the purpose of
+ # gc_none. It could be GC-safe if Thread::LinkedList(T) became a struct, and
+ # Thread::Mutex either became a struct or provide low level abstraction
+ # methods that directly interact with syscalls (without allocating).
+ #
+ # Thread safety is guaranteed by the mutex in Thread::LinkedList: either a
+ # thread is starting and hasn't added itself to the list (it will block until
+ # it can acquire the lock), or is currently adding itself (the current thread
+ # will block until it can acquire the lock).
+ #
+ # In both cases there can't be a deadlock since we won't suspend another
+ # thread until it has successfully added (or removed) itself to (from) the
+ # linked list and released the lock, and the other thread won't progress until
+ # it can add (or remove) itself from the list.
+ #
+ # Finally, we lock the mutex and keep it locked until we resume the world, so
+ # any thread waiting on the mutex will only be resumed when the world is
+ # resumed.
+
+ # :nodoc:
+ def self.stop_world : Nil
+ current_thread = Thread.current
+
+ # grab the lock (and keep it until the world is restarted)
+ Thread.lock
+
+ # tell all threads to stop (async)
+ Thread.unsafe_each do |thread|
+ thread.suspend unless thread == current_thread
+ end
+
+ # wait for all threads to have stopped
+ Thread.unsafe_each do |thread|
+ thread.wait_suspended unless thread == current_thread
+ end
+ end
+
+ # :nodoc:
+ def self.start_world : Nil
+ current_thread = Thread.current
+
+ # tell all threads to resume
+ Thread.unsafe_each do |thread|
+ thread.resume unless thread == current_thread
+ end
+
+ # finally, we can release the lock
+ Thread.unlock
+ end
end
diff --git a/src/hash.cr b/src/hash.cr
index cfa556f921ed..9b2936ddd618 100644
--- a/src/hash.cr
+++ b/src/hash.cr
@@ -1055,7 +1055,7 @@ class Hash(K, V)
self
end
- # Returns `true` of this Hash is comparing keys by `object_id`.
+ # Returns `true` if this Hash is comparing keys by `object_id`.
#
# See `compare_by_identity`.
getter? compare_by_identity : Bool
@@ -1747,7 +1747,8 @@ class Hash(K, V)
# hash.transform_keys { |key, value| key.to_s * value } # => {"a" => 1, "bb" => 2, "ccc" => 3}
# ```
def transform_keys(& : K, V -> K2) : Hash(K2, V) forall K2
- each_with_object({} of K2 => V) do |(key, value), memo|
+ copy = Hash(K2, V).new(initial_capacity: entries_capacity)
+ each_with_object(copy) do |(key, value), memo|
memo[yield(key, value)] = value
end
end
@@ -1762,7 +1763,8 @@ class Hash(K, V)
# hash.transform_values { |value, key| "#{key}#{value}" } # => {:a => "a1", :b => "b2", :c => "c3"}
# ```
def transform_values(& : V, K -> V2) : Hash(K, V2) forall V2
- each_with_object({} of K => V2) do |(key, value), memo|
+ copy = Hash(K, V2).new(initial_capacity: entries_capacity)
+ each_with_object(copy) do |(key, value), memo|
memo[key] = yield(value, key)
end
end
@@ -2149,6 +2151,7 @@ class Hash(K, V)
hash
end
+ # :nodoc:
struct Entry(K, V)
getter key, value, hash
diff --git a/src/http/client.cr b/src/http/client.cr
index b641065ac930..7324bdf7d639 100644
--- a/src/http/client.cr
+++ b/src/http/client.cr
@@ -343,10 +343,10 @@ class HTTP::Client
# ```
setter connect_timeout : Time::Span?
- # **This method has no effect right now**
- #
# Sets the number of seconds to wait when resolving a name, before raising an `IO::TimeoutError`.
#
+ # NOTE: *dns_timeout* is currently only supported on Windows.
+ #
# ```
# require "http/client"
#
@@ -363,10 +363,10 @@ class HTTP::Client
self.dns_timeout = dns_timeout.seconds
end
- # **This method has no effect right now**
- #
# Sets the number of seconds to wait when resolving a name with a `Time::Span`, before raising an `IO::TimeoutError`.
#
+ # NOTE: *dns_timeout* is currently only supported on Windows.
+ #
# ```
# require "http/client"
#
diff --git a/src/http/server/response.cr b/src/http/server/response.cr
index 5c80b31cce00..4dd6968ac560 100644
--- a/src/http/server/response.cr
+++ b/src/http/server/response.cr
@@ -255,7 +255,9 @@ class HTTP::Server
private def unbuffered_write(slice : Bytes) : Nil
return if slice.empty?
- unless response.wrote_headers?
+ if response.headers["Transfer-Encoding"]? == "chunked"
+ @chunked = true
+ elsif !response.wrote_headers?
if response.version != "HTTP/1.0" && !response.headers.has_key?("Content-Length")
response.headers["Transfer-Encoding"] = "chunked"
@chunked = true
@@ -289,7 +291,7 @@ class HTTP::Server
status = response.status
set_content_length = !(status.not_modified? || status.no_content? || status.informational?)
- if !response.wrote_headers? && !response.headers.has_key?("Content-Length") && set_content_length
+ if !response.wrote_headers? && !response.headers.has_key?("Transfer-Encoding") && !response.headers.has_key?("Content-Length") && set_content_length
response.content_length = @out_count
end
diff --git a/src/humanize.cr b/src/humanize.cr
index bb285fe3a07d..db9d84c64889 100644
--- a/src/humanize.cr
+++ b/src/humanize.cr
@@ -216,7 +216,7 @@ struct Number
#
# See `Int#humanize_bytes` to format a file size.
def humanize(io : IO, precision = 3, separator = '.', delimiter = ',', *, base = 10 ** 3, significant = true, &prefixes : (Int32, Float64) -> {Int32, _} | {Int32, _, Bool}) : Nil
- if zero?
+ if zero? || (responds_to?(:infinite?) && self.infinite?) || (responds_to?(:nan?) && self.nan?)
digits = 0
else
log = Math.log10(abs)
diff --git a/src/intrinsics.cr b/src/intrinsics.cr
index c5ae837d8931..dc83ab91c884 100644
--- a/src/intrinsics.cr
+++ b/src/intrinsics.cr
@@ -163,8 +163,13 @@ lib LibIntrinsics
{% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_fshr128)] {% end %}
fun fshr128 = "llvm.fshr.i128"(a : UInt128, b : UInt128, count : UInt128) : UInt128
- fun va_start = "llvm.va_start"(ap : Void*)
- fun va_end = "llvm.va_end"(ap : Void*)
+ {% if compare_versions(Crystal::LLVM_VERSION, "19.1.0") < 0 %}
+ fun va_start = "llvm.va_start"(ap : Void*)
+ fun va_end = "llvm.va_end"(ap : Void*)
+ {% else %}
+ fun va_start = "llvm.va_start.p0"(ap : Void*)
+ fun va_end = "llvm.va_end.p0"(ap : Void*)
+ {% end %}
{% if flag?(:i386) || flag?(:x86_64) %}
{% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_pause)] {% end %}
@@ -179,7 +184,7 @@ end
module Intrinsics
macro debugtrap
- LibIntrinsics.debugtrap
+ ::LibIntrinsics.debugtrap
end
def self.pause
@@ -191,15 +196,15 @@ module Intrinsics
end
macro memcpy(dest, src, len, is_volatile)
- LibIntrinsics.memcpy({{dest}}, {{src}}, {{len}}, {{is_volatile}})
+ ::LibIntrinsics.memcpy({{dest}}, {{src}}, {{len}}, {{is_volatile}})
end
macro memmove(dest, src, len, is_volatile)
- LibIntrinsics.memmove({{dest}}, {{src}}, {{len}}, {{is_volatile}})
+ ::LibIntrinsics.memmove({{dest}}, {{src}}, {{len}}, {{is_volatile}})
end
macro memset(dest, val, len, is_volatile)
- LibIntrinsics.memset({{dest}}, {{val}}, {{len}}, {{is_volatile}})
+ ::LibIntrinsics.memset({{dest}}, {{val}}, {{len}}, {{is_volatile}})
end
def self.read_cycle_counter
@@ -263,43 +268,43 @@ module Intrinsics
end
macro countleading8(src, zero_is_undef)
- LibIntrinsics.countleading8({{src}}, {{zero_is_undef}})
+ ::LibIntrinsics.countleading8({{src}}, {{zero_is_undef}})
end
macro countleading16(src, zero_is_undef)
- LibIntrinsics.countleading16({{src}}, {{zero_is_undef}})
+ ::LibIntrinsics.countleading16({{src}}, {{zero_is_undef}})
end
macro countleading32(src, zero_is_undef)
- LibIntrinsics.countleading32({{src}}, {{zero_is_undef}})
+ ::LibIntrinsics.countleading32({{src}}, {{zero_is_undef}})
end
macro countleading64(src, zero_is_undef)
- LibIntrinsics.countleading64({{src}}, {{zero_is_undef}})
+ ::LibIntrinsics.countleading64({{src}}, {{zero_is_undef}})
end
macro countleading128(src, zero_is_undef)
- LibIntrinsics.countleading128({{src}}, {{zero_is_undef}})
+ ::LibIntrinsics.countleading128({{src}}, {{zero_is_undef}})
end
macro counttrailing8(src, zero_is_undef)
- LibIntrinsics.counttrailing8({{src}}, {{zero_is_undef}})
+ ::LibIntrinsics.counttrailing8({{src}}, {{zero_is_undef}})
end
macro counttrailing16(src, zero_is_undef)
- LibIntrinsics.counttrailing16({{src}}, {{zero_is_undef}})
+ ::LibIntrinsics.counttrailing16({{src}}, {{zero_is_undef}})
end
macro counttrailing32(src, zero_is_undef)
- LibIntrinsics.counttrailing32({{src}}, {{zero_is_undef}})
+ ::LibIntrinsics.counttrailing32({{src}}, {{zero_is_undef}})
end
macro counttrailing64(src, zero_is_undef)
- LibIntrinsics.counttrailing64({{src}}, {{zero_is_undef}})
+ ::LibIntrinsics.counttrailing64({{src}}, {{zero_is_undef}})
end
macro counttrailing128(src, zero_is_undef)
- LibIntrinsics.counttrailing128({{src}}, {{zero_is_undef}})
+ ::LibIntrinsics.counttrailing128({{src}}, {{zero_is_undef}})
end
def self.fshl8(a, b, count) : UInt8
@@ -343,14 +348,14 @@ module Intrinsics
end
macro va_start(ap)
- LibIntrinsics.va_start({{ap}})
+ ::LibIntrinsics.va_start({{ap}})
end
macro va_end(ap)
- LibIntrinsics.va_end({{ap}})
+ ::LibIntrinsics.va_end({{ap}})
end
end
macro debugger
- Intrinsics.debugtrap
+ ::Intrinsics.debugtrap
end
diff --git a/src/io/buffered.cr b/src/io/buffered.cr
index 0e69872a638f..8bd65210aef2 100644
--- a/src/io/buffered.cr
+++ b/src/io/buffered.cr
@@ -49,7 +49,7 @@ module IO::Buffered
# Set the buffer size of both the read and write buffer
# Cannot be changed after any of the buffers have been allocated
def buffer_size=(value)
- if @in_buffer || @out_buffer
+ if (@in_buffer || @out_buffer) && (buffer_size != value)
raise ArgumentError.new("Cannot change buffer_size after buffers have been allocated")
end
@buffer_size = value
diff --git a/src/io/evented.cr b/src/io/evented.cr
index ccc040932285..d2b3a66c336f 100644
--- a/src/io/evented.cr
+++ b/src/io/evented.cr
@@ -13,43 +13,6 @@ module IO::Evented
@read_event = Crystal::ThreadLocalValue(Crystal::EventLoop::Event).new
@write_event = Crystal::ThreadLocalValue(Crystal::EventLoop::Event).new
- def evented_read(errno_msg : String, &) : Int32
- loop do
- bytes_read = yield
- if bytes_read != -1
- # `to_i32` is acceptable because `Slice#size` is an Int32
- return bytes_read.to_i32
- end
-
- if Errno.value == Errno::EAGAIN
- wait_readable
- else
- raise IO::Error.from_errno(errno_msg, target: self)
- end
- end
- ensure
- resume_pending_readers
- end
-
- def evented_write(errno_msg : String, &) : Int32
- begin
- loop do
- bytes_written = yield
- if bytes_written != -1
- return bytes_written.to_i32
- end
-
- if Errno.value == Errno::EAGAIN
- wait_writable
- else
- raise IO::Error.from_errno(errno_msg, target: self)
- end
- end
- ensure
- resume_pending_writers
- end
- end
-
# :nodoc:
def resume_read(timed_out = false) : Nil
@read_timed_out = timed_out
@@ -132,13 +95,15 @@ module IO::Evented
end
end
- private def resume_pending_readers
+ # :nodoc:
+ def evented_resume_pending_readers
if (readers = @readers.get?) && !readers.empty?
add_read_event
end
end
- private def resume_pending_writers
+ # :nodoc:
+ def evented_resume_pending_writers
if (writers = @writers.get?) && !writers.empty?
add_write_event
end
diff --git a/src/io/file_descriptor.cr b/src/io/file_descriptor.cr
index d4459e9bbe0c..a9b303b4b58c 100644
--- a/src/io/file_descriptor.cr
+++ b/src/io/file_descriptor.cr
@@ -66,7 +66,15 @@ class IO::FileDescriptor < IO
Crystal::System::FileDescriptor.from_stdio(fd)
end
+ # Returns whether I/O operations on this file descriptor block the current
+ # thread. If false, operations might opt to suspend the current fiber instead.
+ #
+ # This might be different from the internal file descriptor. For example, when
+ # `STDIN` is a terminal on Windows, this returns `false` since the underlying
+ # blocking reads are done on a completely separate thread.
def blocking
+ emulated = emulated_blocking?
+ return emulated unless emulated.nil?
system_blocking?
end
@@ -233,10 +241,22 @@ class IO::FileDescriptor < IO
system_flock_unlock
end
+ # Finalizes the file descriptor resource.
+ #
+ # This involves releasing the handle to the operating system, i.e. closing it.
+ # It does *not* implicitly call `#flush`, so data waiting in the buffer may be
+ # lost.
+ # It's recommended to always close the file descriptor explicitly via `#close`
+ # (or implicitly using the `.open` constructor).
+ #
+ # Resource release can be disabled with `close_on_finalize = false`.
+ #
+ # This method is a no-op if the file descriptor has already been closed.
def finalize
return if closed? || !close_on_finalize?
- close rescue nil
+ event_loop?.try(&.remove(self))
+ file_descriptor_close { } # ignore error
end
def closed? : Bool
diff --git a/src/iterator.cr b/src/iterator.cr
index a46c813b36b3..6a1513ef2130 100644
--- a/src/iterator.cr
+++ b/src/iterator.cr
@@ -144,6 +144,19 @@ module Iterator(T)
Stop::INSTANCE
end
+ # Returns an empty iterator.
+ def self.empty
+ EmptyIterator(T).new
+ end
+
+ private struct EmptyIterator(T)
+ include Iterator(T)
+
+ def next
+ stop
+ end
+ end
+
def self.of(element : T)
SingletonIterator(T).new(element)
end
diff --git a/src/json/serialization.cr b/src/json/serialization.cr
index b1eb86d15082..15d948f02f40 100644
--- a/src/json/serialization.cr
+++ b/src/json/serialization.cr
@@ -164,7 +164,7 @@ module JSON
private def self.new_from_json_pull_parser(pull : ::JSON::PullParser)
instance = allocate
instance.initialize(__pull_for_json_serializable: pull)
- GC.add_finalizer(instance) if instance.responds_to?(:finalize)
+ ::GC.add_finalizer(instance) if instance.responds_to?(:finalize)
instance
end
@@ -422,8 +422,8 @@ module JSON
# Try to find the discriminator while also getting the raw
# string value of the parsed JSON, so then we can pass it
# to the final type.
- json = String.build do |io|
- JSON.build(io) do |builder|
+ json = ::String.build do |io|
+ ::JSON.build(io) do |builder|
builder.start_object
pull.read_object do |key|
if key == {{field.id.stringify}}
diff --git a/src/kernel.cr b/src/kernel.cr
index 8c84a197b78f..ac241161c16d 100644
--- a/src/kernel.cr
+++ b/src/kernel.cr
@@ -584,14 +584,14 @@ end
# Hooks are defined here due to load order problems.
def self.after_fork_child_callbacks
@@after_fork_child_callbacks ||= [
- # clean ups (don't depend on event loop):
+ # reinit event loop first:
+ -> { Crystal::EventLoop.current.after_fork },
+
+ # reinit signal handling:
->Crystal::System::Signal.after_fork,
->Crystal::System::SignalChildHandler.after_fork,
- # reinit event loop:
- ->{ Crystal::EventLoop.current.after_fork },
-
- # more clean ups (may depend on event loop):
+ # additional reinitialization
->Random::DEFAULT.new_seed,
] of -> Nil
end
@@ -616,3 +616,16 @@ end
Crystal::System::Signal.setup_default_handlers
{% end %}
{% end %}
+
+# This is a temporary workaround to ensure there is always something in the IOCP
+# event loop being awaited, since both the interrupt loop and the fiber stack
+# pool collector are disabled in interpreted code. Without this, asynchronous
+# code that bypasses `Crystal::IOCP::OverlappedOperation` does not currently
+# work, see https://github.com/crystal-lang/crystal/pull/14949#issuecomment-2328314463
+{% if flag?(:interpreted) && flag?(:win32) %}
+ spawn(name: "Interpreter idle loop") do
+ while true
+ sleep 1.day
+ end
+ end
+{% end %}
diff --git a/src/lib_c/aarch64-android/c/signal.cr b/src/lib_c/aarch64-android/c/signal.cr
index 741c8f0efb65..27676c3f733f 100644
--- a/src/lib_c/aarch64-android/c/signal.cr
+++ b/src/lib_c/aarch64-android/c/signal.cr
@@ -79,6 +79,7 @@ lib LibC
fun kill(__pid : PidT, __signal : Int) : Int
fun pthread_sigmask(__how : Int, __new_set : SigsetT*, __old_set : SigsetT*) : Int
+ fun pthread_kill(__thread : PthreadT, __sig : Int) : Int
fun sigaction(__signal : Int, __new_action : Sigaction*, __old_action : Sigaction*) : Int
fun sigaltstack(__new_signal_stack : StackT*, __old_signal_stack : StackT*) : Int
{% if ANDROID_API >= 21 %}
@@ -89,5 +90,6 @@ lib LibC
fun sigaddset(__set : SigsetT*, __signal : Int) : Int
fun sigdelset(__set : SigsetT*, __signal : Int) : Int
fun sigismember(__set : SigsetT*, __signal : Int) : Int
+ fun sigsuspend(__mask : SigsetT*) : Int
{% end %}
end
diff --git a/src/lib_c/aarch64-android/c/sys/random.cr b/src/lib_c/aarch64-android/c/sys/random.cr
new file mode 100644
index 000000000000..77e193958ff2
--- /dev/null
+++ b/src/lib_c/aarch64-android/c/sys/random.cr
@@ -0,0 +1,7 @@
+lib LibC
+ {% if ANDROID_API >= 28 %}
+ GRND_NONBLOCK = 1_u32
+
+ fun getrandom(buf : Void*, buflen : SizeT, flags : UInt32) : SSizeT
+ {% end %}
+end
diff --git a/src/lib_c/aarch64-darwin/c/signal.cr b/src/lib_c/aarch64-darwin/c/signal.cr
index e58adc30289f..0034eef42834 100644
--- a/src/lib_c/aarch64-darwin/c/signal.cr
+++ b/src/lib_c/aarch64-darwin/c/signal.cr
@@ -77,6 +77,7 @@ lib LibC
fun kill(x0 : PidT, x1 : Int) : Int
fun pthread_sigmask(Int, SigsetT*, SigsetT*) : Int
+ fun pthread_kill(PthreadT, Int) : Int
fun signal(x0 : Int, x1 : Int -> Void) : Int -> Void
fun sigaction(x0 : Int, x1 : Sigaction*, x2 : Sigaction*) : Int
fun sigaltstack(x0 : StackT*, x1 : StackT*) : Int
@@ -85,4 +86,5 @@ lib LibC
fun sigaddset(SigsetT*, Int) : Int
fun sigdelset(SigsetT*, Int) : Int
fun sigismember(SigsetT*, Int) : Int
+ fun sigsuspend(SigsetT*) : Int
end
diff --git a/src/lib_c/aarch64-linux-gnu/c/signal.cr b/src/lib_c/aarch64-linux-gnu/c/signal.cr
index 1f7d82eb2145..7ff9fcda1b07 100644
--- a/src/lib_c/aarch64-linux-gnu/c/signal.cr
+++ b/src/lib_c/aarch64-linux-gnu/c/signal.cr
@@ -78,6 +78,7 @@ lib LibC
fun kill(pid : PidT, sig : Int) : Int
fun pthread_sigmask(Int, SigsetT*, SigsetT*) : Int
+ fun pthread_kill(PthreadT, Int) : Int
fun signal(sig : Int, handler : Int -> Void) : Int -> Void
fun sigaction(x0 : Int, x1 : Sigaction*, x2 : Sigaction*) : Int
fun sigaltstack(x0 : StackT*, x1 : StackT*) : Int
@@ -86,4 +87,5 @@ lib LibC
fun sigaddset(SigsetT*, Int) : Int
fun sigdelset(SigsetT*, Int) : Int
fun sigismember(SigsetT*, Int) : Int
+ fun sigsuspend(SigsetT*) : Int
end
diff --git a/src/lib_c/aarch64-linux-gnu/c/sys/random.cr b/src/lib_c/aarch64-linux-gnu/c/sys/random.cr
new file mode 100644
index 000000000000..2c74de96abfb
--- /dev/null
+++ b/src/lib_c/aarch64-linux-gnu/c/sys/random.cr
@@ -0,0 +1,5 @@
+lib LibC
+ GRND_NONBLOCK = 1_u32
+
+ fun getrandom(buf : Void*, buflen : SizeT, flags : UInt32) : SSizeT
+end
diff --git a/src/lib_c/aarch64-linux-musl/c/signal.cr b/src/lib_c/aarch64-linux-musl/c/signal.cr
index 5bfa187b14ec..c65fbb0ff653 100644
--- a/src/lib_c/aarch64-linux-musl/c/signal.cr
+++ b/src/lib_c/aarch64-linux-musl/c/signal.cr
@@ -77,6 +77,7 @@ lib LibC
fun kill(x0 : PidT, x1 : Int) : Int
fun pthread_sigmask(Int, SigsetT*, SigsetT*) : Int
+ fun pthread_kill(PthreadT, Int) : Int
fun signal(x0 : Int, x1 : Int -> Void) : Int -> Void
fun sigaction(x0 : Int, x1 : Sigaction*, x2 : Sigaction*) : Int
fun sigaltstack(x0 : StackT*, x1 : StackT*) : Int
@@ -85,4 +86,5 @@ lib LibC
fun sigaddset(SigsetT*, Int) : Int
fun sigdelset(SigsetT*, Int) : Int
fun sigismember(SigsetT*, Int) : Int
+ fun sigsuspend(SigsetT*) : Int
end
diff --git a/src/lib_c/aarch64-linux-musl/c/sys/random.cr b/src/lib_c/aarch64-linux-musl/c/sys/random.cr
new file mode 100644
index 000000000000..2c74de96abfb
--- /dev/null
+++ b/src/lib_c/aarch64-linux-musl/c/sys/random.cr
@@ -0,0 +1,5 @@
+lib LibC
+ GRND_NONBLOCK = 1_u32
+
+ fun getrandom(buf : Void*, buflen : SizeT, flags : UInt32) : SSizeT
+end
diff --git a/src/lib_c/aarch64-linux-musl/c/sys/resource.cr b/src/lib_c/aarch64-linux-musl/c/sys/resource.cr
index 7f550c37a622..daa583ac5895 100644
--- a/src/lib_c/aarch64-linux-musl/c/sys/resource.cr
+++ b/src/lib_c/aarch64-linux-musl/c/sys/resource.cr
@@ -1,4 +1,15 @@
lib LibC
+ alias RlimT = ULongLong
+
+ struct Rlimit
+ rlim_cur : RlimT
+ rlim_max : RlimT
+ end
+
+ fun getrlimit(Int, Rlimit*) : Int
+
+ RLIMIT_STACK = 3
+
struct RUsage
ru_utime : Timeval
ru_stime : Timeval
diff --git a/src/lib_c/aarch64-windows-msvc b/src/lib_c/aarch64-windows-msvc
new file mode 120000
index 000000000000..072348f65d09
--- /dev/null
+++ b/src/lib_c/aarch64-windows-msvc
@@ -0,0 +1 @@
+x86_64-windows-msvc
\ No newline at end of file
diff --git a/src/lib_c/arm-linux-gnueabihf/c/signal.cr b/src/lib_c/arm-linux-gnueabihf/c/signal.cr
index d94d657e1ca8..0113c045341c 100644
--- a/src/lib_c/arm-linux-gnueabihf/c/signal.cr
+++ b/src/lib_c/arm-linux-gnueabihf/c/signal.cr
@@ -77,6 +77,7 @@ lib LibC
fun kill(pid : PidT, sig : Int) : Int
fun pthread_sigmask(Int, SigsetT*, SigsetT*) : Int
+ fun pthread_kill(PthreadT, Int) : Int
fun signal(sig : Int, handler : Int -> Void) : Int -> Void
fun sigaction(x0 : Int, x1 : Sigaction*, x2 : Sigaction*) : Int
fun sigaltstack(x0 : StackT*, x1 : StackT*) : Int
@@ -85,4 +86,5 @@ lib LibC
fun sigaddset(SigsetT*, Int) : Int
fun sigdelset(SigsetT*, Int) : Int
fun sigismember(SigsetT*, Int) : Int
+ fun sigsuspend(SigsetT*) : Int
end
diff --git a/src/lib_c/arm-linux-gnueabihf/c/sys/random.cr b/src/lib_c/arm-linux-gnueabihf/c/sys/random.cr
new file mode 100644
index 000000000000..2c74de96abfb
--- /dev/null
+++ b/src/lib_c/arm-linux-gnueabihf/c/sys/random.cr
@@ -0,0 +1,5 @@
+lib LibC
+ GRND_NONBLOCK = 1_u32
+
+ fun getrandom(buf : Void*, buflen : SizeT, flags : UInt32) : SSizeT
+end
diff --git a/src/lib_c/i386-linux-gnu/c/signal.cr b/src/lib_c/i386-linux-gnu/c/signal.cr
index 11aab8bfe6bb..1a5260073c2d 100644
--- a/src/lib_c/i386-linux-gnu/c/signal.cr
+++ b/src/lib_c/i386-linux-gnu/c/signal.cr
@@ -77,6 +77,7 @@ lib LibC
fun kill(pid : PidT, sig : Int) : Int
fun pthread_sigmask(Int, SigsetT*, SigsetT*) : Int
+ fun pthread_kill(PthreadT, Int) : Int
fun signal(sig : Int, handler : Int -> Void) : Int -> Void
fun sigaction(x0 : Int, x1 : Sigaction*, x2 : Sigaction*) : Int
fun sigaltstack(x0 : StackT*, x1 : StackT*) : Int
@@ -85,4 +86,5 @@ lib LibC
fun sigaddset(SigsetT*, Int) : Int
fun sigdelset(SigsetT*, Int) : Int
fun sigismember(SigsetT*, Int) : Int
+ fun sigsuspend(SigsetT*) : Int
end
diff --git a/src/lib_c/i386-linux-gnu/c/sys/random.cr b/src/lib_c/i386-linux-gnu/c/sys/random.cr
new file mode 100644
index 000000000000..2c74de96abfb
--- /dev/null
+++ b/src/lib_c/i386-linux-gnu/c/sys/random.cr
@@ -0,0 +1,5 @@
+lib LibC
+ GRND_NONBLOCK = 1_u32
+
+ fun getrandom(buf : Void*, buflen : SizeT, flags : UInt32) : SSizeT
+end
diff --git a/src/lib_c/i386-linux-musl/c/signal.cr b/src/lib_c/i386-linux-musl/c/signal.cr
index f2e554942b69..ac374b684c76 100644
--- a/src/lib_c/i386-linux-musl/c/signal.cr
+++ b/src/lib_c/i386-linux-musl/c/signal.cr
@@ -76,6 +76,7 @@ lib LibC
fun kill(x0 : PidT, x1 : Int) : Int
fun pthread_sigmask(Int, SigsetT*, SigsetT*) : Int
+ fun pthread_kill(PthreadT, Int) : Int
fun signal(x0 : Int, x1 : Int -> Void) : Int -> Void
fun sigaction(x0 : Int, x1 : Sigaction*, x2 : Sigaction*) : Int
fun sigaltstack(x0 : StackT*, x1 : StackT*) : Int
@@ -84,4 +85,5 @@ lib LibC
fun sigaddset(SigsetT*, Int) : Int
fun sigdelset(SigsetT*, Int) : Int
fun sigismember(SigsetT*, Int) : Int
+ fun sigsuspend(SigsetT*) : Int
end
diff --git a/src/lib_c/i386-linux-musl/c/sys/random.cr b/src/lib_c/i386-linux-musl/c/sys/random.cr
new file mode 100644
index 000000000000..2c74de96abfb
--- /dev/null
+++ b/src/lib_c/i386-linux-musl/c/sys/random.cr
@@ -0,0 +1,5 @@
+lib LibC
+ GRND_NONBLOCK = 1_u32
+
+ fun getrandom(buf : Void*, buflen : SizeT, flags : UInt32) : SSizeT
+end
diff --git a/src/lib_c/i386-linux-musl/c/sys/resource.cr b/src/lib_c/i386-linux-musl/c/sys/resource.cr
index 7f550c37a622..daa583ac5895 100644
--- a/src/lib_c/i386-linux-musl/c/sys/resource.cr
+++ b/src/lib_c/i386-linux-musl/c/sys/resource.cr
@@ -1,4 +1,15 @@
lib LibC
+ alias RlimT = ULongLong
+
+ struct Rlimit
+ rlim_cur : RlimT
+ rlim_max : RlimT
+ end
+
+ fun getrlimit(Int, Rlimit*) : Int
+
+ RLIMIT_STACK = 3
+
struct RUsage
ru_utime : Timeval
ru_stime : Timeval
diff --git a/src/lib_c/x86_64-darwin/c/signal.cr b/src/lib_c/x86_64-darwin/c/signal.cr
index e58adc30289f..0034eef42834 100644
--- a/src/lib_c/x86_64-darwin/c/signal.cr
+++ b/src/lib_c/x86_64-darwin/c/signal.cr
@@ -77,6 +77,7 @@ lib LibC
fun kill(x0 : PidT, x1 : Int) : Int
fun pthread_sigmask(Int, SigsetT*, SigsetT*) : Int
+ fun pthread_kill(PthreadT, Int) : Int
fun signal(x0 : Int, x1 : Int -> Void) : Int -> Void
fun sigaction(x0 : Int, x1 : Sigaction*, x2 : Sigaction*) : Int
fun sigaltstack(x0 : StackT*, x1 : StackT*) : Int
@@ -85,4 +86,5 @@ lib LibC
fun sigaddset(SigsetT*, Int) : Int
fun sigdelset(SigsetT*, Int) : Int
fun sigismember(SigsetT*, Int) : Int
+ fun sigsuspend(SigsetT*) : Int
end
diff --git a/src/lib_c/x86_64-dragonfly/c/signal.cr b/src/lib_c/x86_64-dragonfly/c/signal.cr
index 1751eeed3176..e362ef1fa218 100644
--- a/src/lib_c/x86_64-dragonfly/c/signal.cr
+++ b/src/lib_c/x86_64-dragonfly/c/signal.cr
@@ -90,6 +90,7 @@ lib LibC
fun kill(x0 : PidT, x1 : Int) : Int
fun pthread_sigmask(Int, SigsetT*, SigsetT*) : Int
+ fun pthread_kill(PthreadT, Int) : Int
fun signal(x0 : Int, x1 : Int -> Void) : Int -> Void
fun sigaction(x0 : Int, x1 : Sigaction*, x2 : Sigaction*) : Int
fun sigaltstack(x0 : StackT*, x1 : StackT*) : Int
@@ -98,4 +99,5 @@ lib LibC
fun sigaddset(SigsetT*, Int) : Int
fun sigdelset(SigsetT*, Int) : Int
fun sigismember(SigsetT*, Int) : Int
+ fun sigsuspend(SigsetT*) : Int
end
diff --git a/src/lib_c/x86_64-freebsd/c/signal.cr b/src/lib_c/x86_64-freebsd/c/signal.cr
index fd8d07cfd4cc..c79d0630511b 100644
--- a/src/lib_c/x86_64-freebsd/c/signal.cr
+++ b/src/lib_c/x86_64-freebsd/c/signal.cr
@@ -8,31 +8,33 @@ lib LibC
SIGILL = 4
SIGTRAP = 5
SIGIOT = LibC::SIGABRT
- SIGABRT = 6
- SIGFPE = 8
- SIGKILL = 9
- SIGBUS = 10
- SIGSEGV = 11
- SIGSYS = 12
- SIGPIPE = 13
- SIGALRM = 14
- SIGTERM = 15
- SIGURG = 16
- SIGSTOP = 17
- SIGTSTP = 18
- SIGCONT = 19
- SIGCHLD = 20
- SIGTTIN = 21
- SIGTTOU = 22
- SIGIO = 23
- SIGXCPU = 24
- SIGXFSZ = 25
- SIGVTALRM = 26
- SIGUSR1 = 30
- SIGUSR2 = 31
- SIGEMT = 7
- SIGINFO = 29
- SIGWINCH = 28
+ SIGABRT = 6
+ SIGFPE = 8
+ SIGKILL = 9
+ SIGBUS = 10
+ SIGSEGV = 11
+ SIGSYS = 12
+ SIGPIPE = 13
+ SIGALRM = 14
+ SIGTERM = 15
+ SIGURG = 16
+ SIGSTOP = 17
+ SIGTSTP = 18
+ SIGCONT = 19
+ SIGCHLD = 20
+ SIGTTIN = 21
+ SIGTTOU = 22
+ SIGIO = 23
+ SIGXCPU = 24
+ SIGXFSZ = 25
+ SIGVTALRM = 26
+ SIGUSR1 = 30
+ SIGUSR2 = 31
+ SIGEMT = 7
+ SIGINFO = 29
+ SIGWINCH = 28
+ SIGRTMIN = 65
+ SIGRTMAX = 126
SIGSTKSZ = 2048 + 32768 # MINSIGSTKSZ + 32768
SIG_SETMASK = 3
@@ -85,6 +87,7 @@ lib LibC
fun kill(x0 : PidT, x1 : Int) : Int
fun pthread_sigmask(Int, SigsetT*, SigsetT*) : Int
+ fun pthread_kill(PthreadT, Int) : Int
fun signal(x0 : Int, x1 : Int -> Void) : Int -> Void
fun sigaction(x0 : Int, x1 : Sigaction*, x2 : Sigaction*) : Int
fun sigaltstack(x0 : StackT*, x1 : StackT*) : Int
@@ -93,4 +96,5 @@ lib LibC
fun sigaddset(SigsetT*, Int) : Int
fun sigdelset(SigsetT*, Int) : Int
fun sigismember(SigsetT*, Int) : Int
+ fun sigsuspend(SigsetT*) : Int
end
diff --git a/src/lib_c/x86_64-linux-gnu/c/signal.cr b/src/lib_c/x86_64-linux-gnu/c/signal.cr
index 07d8e0fe1ae6..b5ed2f8c8fb3 100644
--- a/src/lib_c/x86_64-linux-gnu/c/signal.cr
+++ b/src/lib_c/x86_64-linux-gnu/c/signal.cr
@@ -78,6 +78,7 @@ lib LibC
fun kill(pid : PidT, sig : Int) : Int
fun pthread_sigmask(Int, SigsetT*, SigsetT*) : Int
+ fun pthread_kill(PthreadT, Int) : Int
fun signal(sig : Int, handler : Int -> Void) : Int -> Void
fun sigaction(x0 : Int, x1 : Sigaction*, x2 : Sigaction*) : Int
fun sigaltstack(x0 : StackT*, x1 : StackT*) : Int
@@ -86,4 +87,5 @@ lib LibC
fun sigaddset(SigsetT*, Int) : Int
fun sigdelset(SigsetT*, Int) : Int
fun sigismember(SigsetT*, Int) : Int
+ fun sigsuspend(SigsetT*) : Int
end
diff --git a/src/lib_c/x86_64-linux-gnu/c/sys/random.cr b/src/lib_c/x86_64-linux-gnu/c/sys/random.cr
new file mode 100644
index 000000000000..2c74de96abfb
--- /dev/null
+++ b/src/lib_c/x86_64-linux-gnu/c/sys/random.cr
@@ -0,0 +1,5 @@
+lib LibC
+ GRND_NONBLOCK = 1_u32
+
+ fun getrandom(buf : Void*, buflen : SizeT, flags : UInt32) : SSizeT
+end
diff --git a/src/lib_c/x86_64-linux-musl/c/signal.cr b/src/lib_c/x86_64-linux-musl/c/signal.cr
index bba7e0c7c21a..42c2aead3e0f 100644
--- a/src/lib_c/x86_64-linux-musl/c/signal.cr
+++ b/src/lib_c/x86_64-linux-musl/c/signal.cr
@@ -77,6 +77,7 @@ lib LibC
fun kill(x0 : PidT, x1 : Int) : Int
fun pthread_sigmask(Int, SigsetT*, SigsetT*) : Int
+ fun pthread_kill(PthreadT, Int) : Int
fun signal(x0 : Int, x1 : Int -> Void) : Int -> Void
fun sigaction(x0 : Int, x1 : Sigaction*, x2 : Sigaction*) : Int
fun sigaltstack(x0 : StackT*, x1 : StackT*) : Int
@@ -85,4 +86,5 @@ lib LibC
fun sigaddset(SigsetT*, Int) : Int
fun sigdelset(SigsetT*, Int) : Int
fun sigismember(SigsetT*, Int) : Int
+ fun sigsuspend(SigsetT*) : Int
end
diff --git a/src/lib_c/x86_64-linux-musl/c/sys/random.cr b/src/lib_c/x86_64-linux-musl/c/sys/random.cr
new file mode 100644
index 000000000000..2c74de96abfb
--- /dev/null
+++ b/src/lib_c/x86_64-linux-musl/c/sys/random.cr
@@ -0,0 +1,5 @@
+lib LibC
+ GRND_NONBLOCK = 1_u32
+
+ fun getrandom(buf : Void*, buflen : SizeT, flags : UInt32) : SSizeT
+end
diff --git a/src/lib_c/x86_64-linux-musl/c/sys/resource.cr b/src/lib_c/x86_64-linux-musl/c/sys/resource.cr
index 7f550c37a622..daa583ac5895 100644
--- a/src/lib_c/x86_64-linux-musl/c/sys/resource.cr
+++ b/src/lib_c/x86_64-linux-musl/c/sys/resource.cr
@@ -1,4 +1,15 @@
lib LibC
+ alias RlimT = ULongLong
+
+ struct Rlimit
+ rlim_cur : RlimT
+ rlim_max : RlimT
+ end
+
+ fun getrlimit(Int, Rlimit*) : Int
+
+ RLIMIT_STACK = 3
+
struct RUsage
ru_utime : Timeval
ru_stime : Timeval
diff --git a/src/lib_c/x86_64-netbsd/c/dirent.cr b/src/lib_c/x86_64-netbsd/c/dirent.cr
index 71dabe7b08ce..e3b8492083f7 100644
--- a/src/lib_c/x86_64-netbsd/c/dirent.cr
+++ b/src/lib_c/x86_64-netbsd/c/dirent.cr
@@ -29,5 +29,4 @@ lib LibC
fun opendir = __opendir30(x0 : Char*) : DIR*
fun readdir = __readdir30(x0 : DIR*) : Dirent*
fun rewinddir(x0 : DIR*) : Void
- fun dirfd(dirp : DIR*) : Int
end
diff --git a/src/lib_c/x86_64-netbsd/c/netdb.cr b/src/lib_c/x86_64-netbsd/c/netdb.cr
index 4443325cd487..c098ab2f5fc6 100644
--- a/src/lib_c/x86_64-netbsd/c/netdb.cr
+++ b/src/lib_c/x86_64-netbsd/c/netdb.cr
@@ -13,6 +13,7 @@ lib LibC
EAI_FAIL = 4
EAI_FAMILY = 5
EAI_MEMORY = 6
+ EAI_NODATA = 7
EAI_NONAME = 8
EAI_SERVICE = 9
EAI_SOCKTYPE = 10
diff --git a/src/lib_c/x86_64-netbsd/c/signal.cr b/src/lib_c/x86_64-netbsd/c/signal.cr
index 93d42e38b093..0b21c5c3f839 100644
--- a/src/lib_c/x86_64-netbsd/c/signal.cr
+++ b/src/lib_c/x86_64-netbsd/c/signal.cr
@@ -77,6 +77,7 @@ lib LibC
fun kill(x0 : PidT, x1 : Int) : Int
fun pthread_sigmask(Int, SigsetT*, SigsetT*) : Int
+ fun pthread_kill(PthreadT, Int) : Int
fun signal(x0 : Int, x1 : Int -> Void) : Int -> Void
fun sigaction = __sigaction14(x0 : Int, x1 : Sigaction*, x2 : Sigaction*) : Int
fun sigaltstack = __sigaltstack14(x0 : StackT*, x1 : StackT*) : Int
@@ -85,4 +86,5 @@ lib LibC
fun sigaddset = __sigaddset14(SigsetT*, Int) : Int
fun sigdelset = __sigdelset14(SigsetT*, Int) : Int
fun sigismember = __sigismember14(SigsetT*, Int) : Int
+ fun sigsuspend(SigsetT*) : Int
end
diff --git a/src/lib_c/x86_64-netbsd/c/sys/time.cr b/src/lib_c/x86_64-netbsd/c/sys/time.cr
index f276784708c0..3bb54d42c5cd 100644
--- a/src/lib_c/x86_64-netbsd/c/sys/time.cr
+++ b/src/lib_c/x86_64-netbsd/c/sys/time.cr
@@ -13,5 +13,5 @@ lib LibC
fun gettimeofday = __gettimeofday50(x0 : Timeval*, x1 : Timezone*) : Int
fun utimes = __utimes50(path : Char*, times : Timeval[2]) : Int
- fun futimens = __futimens50(fd : Int, times : Timespec[2]) : Int
+ fun futimens(fd : Int, times : Timespec[2]) : Int
end
diff --git a/src/lib_c/x86_64-openbsd/c/netdb.cr b/src/lib_c/x86_64-openbsd/c/netdb.cr
index be3c5f06ab2d..6dd1e6c8513f 100644
--- a/src/lib_c/x86_64-openbsd/c/netdb.cr
+++ b/src/lib_c/x86_64-openbsd/c/netdb.cr
@@ -13,6 +13,7 @@ lib LibC
EAI_FAIL = -4
EAI_FAMILY = -6
EAI_MEMORY = -10
+ EAI_NODATA = -5
EAI_NONAME = -2
EAI_SERVICE = -8
EAI_SOCKTYPE = -7
diff --git a/src/lib_c/x86_64-openbsd/c/signal.cr b/src/lib_c/x86_64-openbsd/c/signal.cr
index 04aa27000219..1c9b86137e4a 100644
--- a/src/lib_c/x86_64-openbsd/c/signal.cr
+++ b/src/lib_c/x86_64-openbsd/c/signal.cr
@@ -76,6 +76,7 @@ lib LibC
fun kill(x0 : PidT, x1 : Int) : Int
fun pthread_sigmask(Int, SigsetT*, SigsetT*) : Int
+ fun pthread_kill(PthreadT, Int) : Int
fun signal(x0 : Int, x1 : Int -> Void) : Int -> Void
fun sigaction(x0 : Int, x1 : Sigaction*, x2 : Sigaction*) : Int
fun sigaltstack(x0 : StackT*, x1 : StackT*) : Int
@@ -84,4 +85,5 @@ lib LibC
fun sigaddset(SigsetT*, Int) : Int
fun sigdelset(SigsetT*, Int) : Int
fun sigismember(SigsetT*, Int) : Int
+ fun sigsuspend(SigsetT*) : Int
end
diff --git a/src/lib_c/x86_64-solaris/c/signal.cr b/src/lib_c/x86_64-solaris/c/signal.cr
index 9bde30946054..ee502aa621e4 100644
--- a/src/lib_c/x86_64-solaris/c/signal.cr
+++ b/src/lib_c/x86_64-solaris/c/signal.cr
@@ -90,6 +90,7 @@ lib LibC
fun kill(x0 : PidT, x1 : Int) : Int
fun pthread_sigmask(Int, SigsetT*, SigsetT*) : Int
+ fun pthread_kill(PthreadT, Int) : Int
fun signal(x0 : Int, x1 : Int -> Void) : Int -> Void
fun sigaction(x0 : Int, x1 : Sigaction*, x2 : Sigaction*) : Int
fun sigaltstack(x0 : StackT*, x1 : StackT*) : Int
@@ -98,4 +99,5 @@ lib LibC
fun sigaddset(SigsetT*, Int) : Int
fun sigdelset(SigsetT*, Int) : Int
fun sigismember(SigsetT*, Int) : Int
+ fun sigsuspend(SigsetT*) : Int
end
diff --git a/src/lib_c/x86_64-windows-gnu b/src/lib_c/x86_64-windows-gnu
new file mode 120000
index 000000000000..072348f65d09
--- /dev/null
+++ b/src/lib_c/x86_64-windows-gnu
@@ -0,0 +1 @@
+x86_64-windows-msvc
\ No newline at end of file
diff --git a/src/lib_c/x86_64-windows-msvc/c/consoleapi.cr b/src/lib_c/x86_64-windows-msvc/c/consoleapi.cr
index fe2fbe381d03..7f7160a6448b 100644
--- a/src/lib_c/x86_64-windows-msvc/c/consoleapi.cr
+++ b/src/lib_c/x86_64-windows-msvc/c/consoleapi.cr
@@ -19,7 +19,7 @@ lib LibC
lpBuffer : Void*,
nNumberOfCharsToRead : DWORD,
lpNumberOfCharsRead : DWORD*,
- pInputControl : Void*
+ pInputControl : Void*,
) : BOOL
CTRL_C_EVENT = 0
diff --git a/src/lib_c/x86_64-windows-msvc/c/dbghelp.cr b/src/lib_c/x86_64-windows-msvc/c/dbghelp.cr
index af37cb0c7f0c..abd9e0b36104 100644
--- a/src/lib_c/x86_64-windows-msvc/c/dbghelp.cr
+++ b/src/lib_c/x86_64-windows-msvc/c/dbghelp.cr
@@ -122,6 +122,7 @@ lib LibC
end
IMAGE_FILE_MACHINE_AMD64 = DWORD.new!(0x8664)
+ IMAGE_FILE_MACHINE_ARM64 = DWORD.new!(0xAA64)
alias PREAD_PROCESS_MEMORY_ROUTINE64 = HANDLE, DWORD64, Void*, DWORD, DWORD* -> BOOL
alias PFUNCTION_TABLE_ACCESS_ROUTINE64 = HANDLE, DWORD64 -> Void*
@@ -131,6 +132,6 @@ lib LibC
fun StackWalk64(
machineType : DWORD, hProcess : HANDLE, hThread : HANDLE, stackFrame : STACKFRAME64*, contextRecord : Void*,
readMemoryRoutine : PREAD_PROCESS_MEMORY_ROUTINE64, functionTableAccessRoutine : PFUNCTION_TABLE_ACCESS_ROUTINE64,
- getModuleBaseRoutine : PGET_MODULE_BASE_ROUTINE64, translateAddress : PTRANSLATE_ADDRESS_ROUTINE64
+ getModuleBaseRoutine : PGET_MODULE_BASE_ROUTINE64, translateAddress : PTRANSLATE_ADDRESS_ROUTINE64,
) : BOOL
end
diff --git a/src/lib_c/x86_64-windows-msvc/c/fileapi.cr b/src/lib_c/x86_64-windows-msvc/c/fileapi.cr
index c17c0fb48a9a..94714b557cbe 100644
--- a/src/lib_c/x86_64-windows-msvc/c/fileapi.cr
+++ b/src/lib_c/x86_64-windows-msvc/c/fileapi.cr
@@ -107,14 +107,14 @@ lib LibC
dwReserved : DWORD,
nNumberOfBytesToLockLow : DWORD,
nNumberOfBytesToLockHigh : DWORD,
- lpOverlapped : OVERLAPPED*
+ lpOverlapped : OVERLAPPED*,
) : BOOL
fun UnlockFileEx(
hFile : HANDLE,
dwReserved : DWORD,
nNumberOfBytesToUnlockLow : DWORD,
nNumberOfBytesToUnlockHigh : DWORD,
- lpOverlapped : OVERLAPPED*
+ lpOverlapped : OVERLAPPED*,
) : BOOL
fun SetFileTime(hFile : HANDLE, lpCreationTime : FILETIME*,
lpLastAccessTime : FILETIME*, lpLastWriteTime : FILETIME*) : BOOL
diff --git a/src/lib_c/x86_64-windows-msvc/c/io.cr b/src/lib_c/x86_64-windows-msvc/c/io.cr
index 75da8c18e5b9..ccbaa15f2d1b 100644
--- a/src/lib_c/x86_64-windows-msvc/c/io.cr
+++ b/src/lib_c/x86_64-windows-msvc/c/io.cr
@@ -2,12 +2,13 @@ require "c/stdint"
lib LibC
fun _wexecvp(cmdname : WCHAR*, argv : WCHAR**) : IntPtrT
+ fun _open_osfhandle(osfhandle : HANDLE, flags : LibC::Int) : LibC::Int
+ fun _dup(fd : Int) : Int
+ fun _dup2(fd1 : Int, fd2 : Int) : Int
# unused
- fun _open_osfhandle(osfhandle : HANDLE, flags : LibC::Int) : LibC::Int
fun _get_osfhandle(fd : Int) : IntPtrT
fun _close(fd : Int) : Int
- fun _dup2(fd1 : Int, fd2 : Int) : Int
fun _isatty(fd : Int) : Int
fun _write(fd : Int, buffer : UInt8*, count : UInt) : Int
fun _read(fd : Int, buffer : UInt8*, count : UInt) : Int
diff --git a/src/lib_c/x86_64-windows-msvc/c/ioapiset.cr b/src/lib_c/x86_64-windows-msvc/c/ioapiset.cr
index 1c94b66db4c8..d6632e329f6b 100644
--- a/src/lib_c/x86_64-windows-msvc/c/ioapiset.cr
+++ b/src/lib_c/x86_64-windows-msvc/c/ioapiset.cr
@@ -3,14 +3,14 @@ lib LibC
hFile : HANDLE,
lpOverlapped : OVERLAPPED*,
lpNumberOfBytesTransferred : DWORD*,
- bWait : BOOL
+ bWait : BOOL,
) : BOOL
fun CreateIoCompletionPort(
fileHandle : HANDLE,
existingCompletionPort : HANDLE,
completionKey : ULong*,
- numberOfConcurrentThreads : DWORD
+ numberOfConcurrentThreads : DWORD,
) : HANDLE
fun GetQueuedCompletionStatusEx(
@@ -19,14 +19,22 @@ lib LibC
ulCount : ULong,
ulNumEntriesRemoved : ULong*,
dwMilliseconds : DWORD,
- fAlertable : BOOL
+ fAlertable : BOOL,
) : BOOL
+
+ fun PostQueuedCompletionStatus(
+ completionPort : HANDLE,
+ dwNumberOfBytesTransferred : DWORD,
+ dwCompletionKey : ULONG_PTR,
+ lpOverlapped : OVERLAPPED*,
+ ) : BOOL
+
fun CancelIoEx(
hFile : HANDLE,
- lpOverlapped : OVERLAPPED*
+ lpOverlapped : OVERLAPPED*,
) : BOOL
fun CancelIo(
- hFile : HANDLE
+ hFile : HANDLE,
) : BOOL
fun DeviceIoControl(
@@ -37,6 +45,6 @@ lib LibC
lpOutBuffer : Void*,
nOutBufferSize : DWORD,
lpBytesReturned : DWORD*,
- lpOverlapped : OVERLAPPED*
+ lpOverlapped : OVERLAPPED*,
) : BOOL
end
diff --git a/src/lib_c/x86_64-windows-msvc/c/knownfolders.cr b/src/lib_c/x86_64-windows-msvc/c/knownfolders.cr
index 04c16573cc76..6ce1831cb1e5 100644
--- a/src/lib_c/x86_64-windows-msvc/c/knownfolders.cr
+++ b/src/lib_c/x86_64-windows-msvc/c/knownfolders.cr
@@ -2,4 +2,5 @@ require "c/guiddef"
lib LibC
FOLDERID_Profile = GUID.new(0x5e6c858f, 0x0e22, 0x4760, UInt8.static_array(0x9a, 0xfe, 0xea, 0x33, 0x17, 0xb6, 0x71, 0x73))
+ FOLDERID_System = GUID.new(0x1ac14e77, 0x02e7, 0x4e5d, UInt8.static_array(0xb7, 0x44, 0x2e, 0xb1, 0xae, 0x51, 0x98, 0xb7))
end
diff --git a/src/lib_c/x86_64-windows-msvc/c/libloaderapi.cr b/src/lib_c/x86_64-windows-msvc/c/libloaderapi.cr
index 37a95f3fa089..5612233553d9 100644
--- a/src/lib_c/x86_64-windows-msvc/c/libloaderapi.cr
+++ b/src/lib_c/x86_64-windows-msvc/c/libloaderapi.cr
@@ -9,6 +9,9 @@ lib LibC
fun LoadLibraryExW(lpLibFileName : LPWSTR, hFile : HANDLE, dwFlags : DWORD) : HMODULE
fun FreeLibrary(hLibModule : HMODULE) : BOOL
+ GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT = 0x00000002
+ GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS = 0x00000004
+
fun GetModuleHandleExW(dwFlags : DWORD, lpModuleName : LPWSTR, phModule : HMODULE*) : BOOL
fun GetProcAddress(hModule : HMODULE, lpProcName : LPSTR) : FARPROC
diff --git a/src/lib_c/x86_64-windows-msvc/c/lm.cr b/src/lib_c/x86_64-windows-msvc/c/lm.cr
new file mode 100644
index 000000000000..72f5affc9b55
--- /dev/null
+++ b/src/lib_c/x86_64-windows-msvc/c/lm.cr
@@ -0,0 +1,59 @@
+require "c/winnt"
+
+@[Link("netapi32")]
+lib LibC
+ alias NET_API_STATUS = DWORD
+
+ NERR_Success = NET_API_STATUS.new!(0)
+
+ enum NETSETUP_JOIN_STATUS
+ NetSetupUnknownStatus = 0
+ NetSetupUnjoined
+ NetSetupWorkgroupName
+ NetSetupDomainName
+ end
+
+ fun NetGetJoinInformation(lpServer : LPWSTR, lpNameBuffer : LPWSTR*, bufferType : NETSETUP_JOIN_STATUS*) : NET_API_STATUS
+
+ struct USER_INFO_4
+ usri4_name : LPWSTR
+ usri4_password : LPWSTR
+ usri4_password_age : DWORD
+ usri4_priv : DWORD
+ usri4_home_dir : LPWSTR
+ usri4_comment : LPWSTR
+ usri4_flags : DWORD
+ usri4_script_path : LPWSTR
+ usri4_auth_flags : DWORD
+ usri4_full_name : LPWSTR
+ usri4_usr_comment : LPWSTR
+ usri4_parms : LPWSTR
+ usri4_workstations : LPWSTR
+ usri4_last_logon : DWORD
+ usri4_last_logoff : DWORD
+ usri4_acct_expires : DWORD
+ usri4_max_storage : DWORD
+ usri4_units_per_week : DWORD
+ usri4_logon_hours : BYTE*
+ usri4_bad_pw_count : DWORD
+ usri4_num_logons : DWORD
+ usri4_logon_server : LPWSTR
+ usri4_country_code : DWORD
+ usri4_code_page : DWORD
+ usri4_user_sid : SID*
+ usri4_primary_group_id : DWORD
+ usri4_profile : LPWSTR
+ usri4_home_dir_drive : LPWSTR
+ usri4_password_expired : DWORD
+ end
+
+ struct USER_INFO_10
+ usri10_name : LPWSTR
+ usri10_comment : LPWSTR
+ usri10_usr_comment : LPWSTR
+ usri10_full_name : LPWSTR
+ end
+
+ fun NetUserGetInfo(servername : LPWSTR, username : LPWSTR, level : DWORD, bufptr : BYTE**) : NET_API_STATUS
+ fun NetApiBufferFree(buffer : Void*) : NET_API_STATUS
+end
diff --git a/src/lib_c/x86_64-windows-msvc/c/memoryapi.cr b/src/lib_c/x86_64-windows-msvc/c/memoryapi.cr
index 7b0103713d8a..0ea28b8262f6 100644
--- a/src/lib_c/x86_64-windows-msvc/c/memoryapi.cr
+++ b/src/lib_c/x86_64-windows-msvc/c/memoryapi.cr
@@ -11,5 +11,5 @@ lib LibC
fun VirtualFree(lpAddress : Void*, dwSize : SizeT, dwFreeType : DWORD) : BOOL
fun VirtualProtect(lpAddress : Void*, dwSize : SizeT, flNewProtect : DWORD, lpfOldProtect : DWORD*) : BOOL
- fun VirtualQuery(lpAddress : Void*, lpBuffer : MEMORY_BASIC_INFORMATION*, dwLength : SizeT)
+ fun VirtualQuery(lpAddress : Void*, lpBuffer : MEMORY_BASIC_INFORMATION*, dwLength : SizeT) : SizeT
end
diff --git a/src/lib_c/x86_64-windows-msvc/c/processthreadsapi.cr b/src/lib_c/x86_64-windows-msvc/c/processthreadsapi.cr
index d1e13eced324..22001cfc1632 100644
--- a/src/lib_c/x86_64-windows-msvc/c/processthreadsapi.cr
+++ b/src/lib_c/x86_64-windows-msvc/c/processthreadsapi.cr
@@ -59,5 +59,15 @@ lib LibC
fun SwitchToThread : BOOL
fun QueueUserAPC(pfnAPC : PAPCFUNC, hThread : HANDLE, dwData : ULONG_PTR) : DWORD
+ fun GetThreadContext(hThread : HANDLE, lpContext : CONTEXT*) : DWORD
+ fun ResumeThread(hThread : HANDLE) : DWORD
+ fun SuspendThread(hThread : HANDLE) : DWORD
+
+ TLS_OUT_OF_INDEXES = 0xFFFFFFFF_u32
+
+ fun TlsAlloc : DWORD
+ fun TlsGetValue(dwTlsIndex : DWORD) : Void*
+ fun TlsSetValue(dwTlsIndex : DWORD, lpTlsValue : Void*) : BOOL
+
PROCESS_QUERY_INFORMATION = 0x0400
end
diff --git a/src/lib_c/x86_64-windows-msvc/c/sddl.cr b/src/lib_c/x86_64-windows-msvc/c/sddl.cr
new file mode 100644
index 000000000000..64e1fa8b25c1
--- /dev/null
+++ b/src/lib_c/x86_64-windows-msvc/c/sddl.cr
@@ -0,0 +1,6 @@
+require "c/winnt"
+
+lib LibC
+ fun ConvertSidToStringSidW(sid : SID*, stringSid : LPWSTR*) : BOOL
+ fun ConvertStringSidToSidW(stringSid : LPWSTR, sid : SID**) : BOOL
+end
diff --git a/src/lib_c/x86_64-windows-msvc/c/security.cr b/src/lib_c/x86_64-windows-msvc/c/security.cr
new file mode 100644
index 000000000000..5a904c51df40
--- /dev/null
+++ b/src/lib_c/x86_64-windows-msvc/c/security.cr
@@ -0,0 +1,21 @@
+require "c/winnt"
+
+@[Link("secur32")]
+lib LibC
+ enum EXTENDED_NAME_FORMAT
+ NameUnknown = 0
+ NameFullyQualifiedDN = 1
+ NameSamCompatible = 2
+ NameDisplay = 3
+ NameUniqueId = 6
+ NameCanonical = 7
+ NameUserPrincipal = 8
+ NameCanonicalEx = 9
+ NameServicePrincipal = 10
+ NameDnsDomain = 12
+ NameGivenName = 13
+ NameSurname = 14
+ end
+
+ fun TranslateNameW(lpAccountName : LPWSTR, accountNameFormat : EXTENDED_NAME_FORMAT, desiredNameFormat : EXTENDED_NAME_FORMAT, lpTranslatedName : LPWSTR, nSize : ULong*) : BOOLEAN
+end
diff --git a/src/lib_c/x86_64-windows-msvc/c/stdio.cr b/src/lib_c/x86_64-windows-msvc/c/stdio.cr
index f23bba8503f6..ddfa97235d87 100644
--- a/src/lib_c/x86_64-windows-msvc/c/stdio.cr
+++ b/src/lib_c/x86_64-windows-msvc/c/stdio.cr
@@ -1,6 +1,8 @@
require "./stddef"
-@[Link("legacy_stdio_definitions")]
+{% if flag?(:msvc) %}
+ @[Link("legacy_stdio_definitions")]
+{% end %}
lib LibC
# unused
fun printf(format : Char*, ...) : Int
diff --git a/src/lib_c/x86_64-windows-msvc/c/stringapiset.cr b/src/lib_c/x86_64-windows-msvc/c/stringapiset.cr
index f60e80a59328..c22bd1dfab31 100644
--- a/src/lib_c/x86_64-windows-msvc/c/stringapiset.cr
+++ b/src/lib_c/x86_64-windows-msvc/c/stringapiset.cr
@@ -8,13 +8,13 @@ lib LibC
fun WideCharToMultiByte(
codePage : UInt, dwFlags : DWORD, lpWideCharStr : LPWSTR,
cchWideChar : Int, lpMultiByteStr : LPSTR, cbMultiByte : Int,
- lpDefaultChar : CHAR*, lpUsedDefaultChar : BOOL*
+ lpDefaultChar : CHAR*, lpUsedDefaultChar : BOOL*,
) : Int
# this was for the now removed delay-load helper, all other code should use
# `String#to_utf16` instead
fun MultiByteToWideChar(
codePage : UInt, dwFlags : DWORD, lpMultiByteStr : LPSTR,
- cbMultiByte : Int, lpWideCharStr : LPWSTR, cchWideChar : Int
+ cbMultiByte : Int, lpWideCharStr : LPWSTR, cchWideChar : Int,
) : Int
end
diff --git a/src/lib_c/x86_64-windows-msvc/c/userenv.cr b/src/lib_c/x86_64-windows-msvc/c/userenv.cr
new file mode 100644
index 000000000000..bb32977d79f7
--- /dev/null
+++ b/src/lib_c/x86_64-windows-msvc/c/userenv.cr
@@ -0,0 +1,6 @@
+require "c/winnt"
+
+@[Link("userenv")]
+lib LibC
+ fun GetProfilesDirectoryW(lpProfileDir : LPWSTR, lpcchSize : DWORD*) : BOOL
+end
diff --git a/src/lib_c/x86_64-windows-msvc/c/winbase.cr b/src/lib_c/x86_64-windows-msvc/c/winbase.cr
index 0a736a4fa89c..7b7a8735ddf2 100644
--- a/src/lib_c/x86_64-windows-msvc/c/winbase.cr
+++ b/src/lib_c/x86_64-windows-msvc/c/winbase.cr
@@ -4,6 +4,10 @@ require "c/int_safe"
require "c/minwinbase"
lib LibC
+ alias HLOCAL = Void*
+
+ fun LocalFree(hMem : HLOCAL)
+
FORMAT_MESSAGE_ALLOCATE_BUFFER = 0x00000100_u32
FORMAT_MESSAGE_IGNORE_INSERTS = 0x00000200_u32
FORMAT_MESSAGE_FROM_STRING = 0x00000400_u32
@@ -69,4 +73,7 @@ lib LibC
end
fun GetFileInformationByHandleEx(hFile : HANDLE, fileInformationClass : FILE_INFO_BY_HANDLE_CLASS, lpFileInformation : Void*, dwBufferSize : DWORD) : BOOL
+
+ fun LookupAccountNameW(lpSystemName : LPWSTR, lpAccountName : LPWSTR, sid : SID*, cbSid : DWORD*, referencedDomainName : LPWSTR, cchReferencedDomainName : DWORD*, peUse : SID_NAME_USE*) : BOOL
+ fun LookupAccountSidW(lpSystemName : LPWSTR, sid : SID*, name : LPWSTR, cchName : DWORD*, referencedDomainName : LPWSTR, cchReferencedDomainName : DWORD*, peUse : SID_NAME_USE*) : BOOL
end
diff --git a/src/lib_c/x86_64-windows-msvc/c/winnt.cr b/src/lib_c/x86_64-windows-msvc/c/winnt.cr
index e1f133dcae48..99c8f24ac9e1 100644
--- a/src/lib_c/x86_64-windows-msvc/c/winnt.cr
+++ b/src/lib_c/x86_64-windows-msvc/c/winnt.cr
@@ -95,6 +95,31 @@ lib LibC
WRITE = 0x20006
end
+ struct SID_IDENTIFIER_AUTHORITY
+ value : BYTE[6]
+ end
+
+ struct SID
+ revision : BYTE
+ subAuthorityCount : BYTE
+ identifierAuthority : SID_IDENTIFIER_AUTHORITY
+ subAuthority : DWORD[1]
+ end
+
+ enum SID_NAME_USE
+ SidTypeUser = 1
+ SidTypeGroup
+ SidTypeDomain
+ SidTypeAlias
+ SidTypeWellKnownGroup
+ SidTypeDeletedAccount
+ SidTypeInvalid
+ SidTypeUnknown
+ SidTypeComputer
+ SidTypeLabel
+ SidTypeLogonSession
+ end
+
enum JOBOBJECTINFOCLASS
AssociateCompletionPortInformation = 7
ExtendedLimitInformation = 9
@@ -140,54 +165,84 @@ lib LibC
JOB_OBJECT_MSG_EXIT_PROCESS = 7
JOB_OBJECT_MSG_ABNORMAL_EXIT_PROCESS = 8
- struct CONTEXT
- p1Home : DWORD64
- p2Home : DWORD64
- p3Home : DWORD64
- p4Home : DWORD64
- p5Home : DWORD64
- p6Home : DWORD64
- contextFlags : DWORD
- mxCsr : DWORD
- segCs : WORD
- segDs : WORD
- segEs : WORD
- segFs : WORD
- segGs : WORD
- segSs : WORD
- eFlags : DWORD
- dr0 : DWORD64
- dr1 : DWORD64
- dr2 : DWORD64
- dr3 : DWORD64
- dr6 : DWORD64
- dr7 : DWORD64
- rax : DWORD64
- rcx : DWORD64
- rdx : DWORD64
- rbx : DWORD64
- rsp : DWORD64
- rbp : DWORD64
- rsi : DWORD64
- rdi : DWORD64
- r8 : DWORD64
- r9 : DWORD64
- r10 : DWORD64
- r11 : DWORD64
- r12 : DWORD64
- r13 : DWORD64
- r14 : DWORD64
- r15 : DWORD64
- rip : DWORD64
- fltSave : UInt8[512] # DUMMYUNIONNAME
- vectorRegister : UInt8[16][26] # M128A[26]
- vectorControl : DWORD64
- debugControl : DWORD64
- lastBranchToRip : DWORD64
- lastBranchFromRip : DWORD64
- lastExceptionToRip : DWORD64
- lastExceptionFromRip : DWORD64
- end
+ {% if flag?(:x86_64) %}
+ struct CONTEXT
+ p1Home : DWORD64
+ p2Home : DWORD64
+ p3Home : DWORD64
+ p4Home : DWORD64
+ p5Home : DWORD64
+ p6Home : DWORD64
+ contextFlags : DWORD
+ mxCsr : DWORD
+ segCs : WORD
+ segDs : WORD
+ segEs : WORD
+ segFs : WORD
+ segGs : WORD
+ segSs : WORD
+ eFlags : DWORD
+ dr0 : DWORD64
+ dr1 : DWORD64
+ dr2 : DWORD64
+ dr3 : DWORD64
+ dr6 : DWORD64
+ dr7 : DWORD64
+ rax : DWORD64
+ rcx : DWORD64
+ rdx : DWORD64
+ rbx : DWORD64
+ rsp : DWORD64
+ rbp : DWORD64
+ rsi : DWORD64
+ rdi : DWORD64
+ r8 : DWORD64
+ r9 : DWORD64
+ r10 : DWORD64
+ r11 : DWORD64
+ r12 : DWORD64
+ r13 : DWORD64
+ r14 : DWORD64
+ r15 : DWORD64
+ rip : DWORD64
+ fltSave : UInt8[512] # DUMMYUNIONNAME
+ vectorRegister : UInt8[16][26] # M128A[26]
+ vectorControl : DWORD64
+ debugControl : DWORD64
+ lastBranchToRip : DWORD64
+ lastBranchFromRip : DWORD64
+ lastExceptionToRip : DWORD64
+ lastExceptionFromRip : DWORD64
+ end
+ {% elsif flag?(:aarch64) %}
+ struct ARM64_NT_NEON128_DUMMYSTRUCTNAME
+ low : ULongLong
+ high : LongLong
+ end
+
+ union ARM64_NT_NEON128
+ dummystructname : ARM64_NT_NEON128_DUMMYSTRUCTNAME
+ d : Double[2]
+ s : Float[4]
+ h : WORD[8]
+ b : BYTE[16]
+ end
+
+ struct CONTEXT
+ contextFlags : DWORD
+ cpsr : DWORD
+ x : DWORD64[31] # x29 = fp, x30 = lr
+ sp : DWORD64
+ pc : DWORD64
+ v : ARM64_NT_NEON128[32]
+ fpcr : DWORD
+ fpsr : DWORD
+ bcr : DWORD[8]
+ bvr : DWORD64[8]
+ wcr : DWORD[8]
+ wvr : DWORD64[8]
+ end
+ {% end %}
{% if flag?(:x86_64) %}
CONTEXT_AMD64 = DWORD.new!(0x00100000)
@@ -211,6 +266,14 @@ lib LibC
CONTEXT_EXTENDED_REGISTERS = CONTEXT_i386 | 0x00000020
CONTEXT_FULL = CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_SEGMENTS
+ {% elsif flag?(:aarch64) %}
+ CONTEXT_ARM64 = DWORD.new!(0x00400000)
+
+ CONTEXT_ARM64_CONTROL = CONTEXT_ARM64 | 0x1
+ CONTEXT_ARM64_INTEGER = CONTEXT_ARM64 | 0x2
+ CONTEXT_ARM64_FLOATING_POINT = CONTEXT_ARM64 | 0x4
+
+ CONTEXT_FULL = CONTEXT_ARM64_CONTROL | CONTEXT_ARM64_INTEGER | CONTEXT_ARM64_FLOATING_POINT
{% end %}
fun RtlCaptureContext(contextRecord : CONTEXT*)
@@ -329,11 +392,67 @@ lib LibC
optionalHeader : IMAGE_OPTIONAL_HEADER64
end
+ IMAGE_DIRECTORY_ENTRY_EXPORT = 0
+ IMAGE_DIRECTORY_ENTRY_IMPORT = 1
+ IMAGE_DIRECTORY_ENTRY_IAT = 12
+
+ IMAGE_SCN_CNT_INITIALIZED_DATA = 0x00000040
+
+ struct IMAGE_SECTION_HEADER
+ name : BYTE[8]
+ virtualSize : DWORD
+ virtualAddress : DWORD
+ sizeOfRawData : DWORD
+ pointerToRawData : DWORD
+ pointerToRelocations : DWORD
+ pointerToLinenumbers : DWORD
+ numberOfRelocations : WORD
+ numberOfLinenumbers : WORD
+ characteristics : DWORD
+ end
+
+ struct IMAGE_EXPORT_DIRECTORY
+ characteristics : DWORD
+ timeDateStamp : DWORD
+ majorVersion : WORD
+ minorVersion : WORD
+ name : DWORD
+ base : DWORD
+ numberOfFunctions : DWORD
+ numberOfNames : DWORD
+ addressOfFunctions : DWORD
+ addressOfNames : DWORD
+ addressOfNameOrdinals : DWORD
+ end
+
struct IMAGE_IMPORT_BY_NAME
hint : WORD
name : CHAR[1]
end
+ struct IMAGE_SYMBOL_n_name
+ short : DWORD
+ long : DWORD
+ end
+
+ union IMAGE_SYMBOL_n
+ shortName : BYTE[8]
+ name : IMAGE_SYMBOL_n_name
+ end
+
+ IMAGE_SYM_CLASS_EXTERNAL = 2
+ IMAGE_SYM_CLASS_STATIC = 3
+
+ @[Packed]
+ struct IMAGE_SYMBOL
+ n : IMAGE_SYMBOL_n
+ value : DWORD
+ sectionNumber : Short
+ type : WORD
+ storageClass : BYTE
+ numberOfAuxSymbols : BYTE
+ end
+
union IMAGE_THUNK_DATA64_u1
forwarderString : ULongLong
function : ULongLong
diff --git a/src/lib_c/x86_64-windows-msvc/c/winsock2.cr b/src/lib_c/x86_64-windows-msvc/c/winsock2.cr
index 223c2366b072..21ae8baba852 100644
--- a/src/lib_c/x86_64-windows-msvc/c/winsock2.cr
+++ b/src/lib_c/x86_64-windows-msvc/c/winsock2.cr
@@ -20,6 +20,8 @@ lib LibC
lpVendorInfo : Char*
end
+ NS_DNS = 12_u32
+
INVALID_SOCKET = ~SOCKET.new(0)
SOCKET_ERROR = -1
@@ -111,6 +113,11 @@ lib LibC
alias WSAOVERLAPPED_COMPLETION_ROUTINE = Proc(DWORD, DWORD, WSAOVERLAPPED*, DWORD, Void)
+ struct Timeval
+ tv_sec : Long
+ tv_usec : Long
+ end
+
struct Linger
l_onoff : UShort
l_linger : UShort
@@ -147,7 +154,7 @@ lib LibC
addr : Sockaddr*,
addrlen : Int*,
lpfnCondition : LPCONDITIONPROC,
- dwCallbackData : DWORD*
+ dwCallbackData : DWORD*,
) : SOCKET
fun WSAConnect(
@@ -157,21 +164,21 @@ lib LibC
lpCallerData : WSABUF*,
lpCalleeData : WSABUF*,
lpSQOS : LPQOS,
- lpGQOS : LPQOS
+ lpGQOS : LPQOS,
)
fun WSACreateEvent : WSAEVENT
fun WSAEventSelect(
s : SOCKET,
hEventObject : WSAEVENT,
- lNetworkEvents : Long
+ lNetworkEvents : Long,
) : Int
fun WSAGetOverlappedResult(
s : SOCKET,
lpOverlapped : WSAOVERLAPPED*,
lpcbTransfer : DWORD*,
fWait : BOOL,
- lpdwFlags : DWORD*
+ lpdwFlags : DWORD*,
) : BOOL
fun WSAIoctl(
s : SOCKET,
@@ -182,7 +189,7 @@ lib LibC
cbOutBuffer : DWORD,
lpcbBytesReturned : DWORD*,
lpOverlapped : WSAOVERLAPPED*,
- lpCompletionRoutine : WSAOVERLAPPED_COMPLETION_ROUTINE*
+ lpCompletionRoutine : WSAOVERLAPPED_COMPLETION_ROUTINE*,
) : Int
fun WSARecv(
s : SOCKET,
@@ -191,7 +198,7 @@ lib LibC
lpNumberOfBytesRecvd : DWORD*,
lpFlags : DWORD*,
lpOverlapped : WSAOVERLAPPED*,
- lpCompletionRoutine : WSAOVERLAPPED_COMPLETION_ROUTINE*
+ lpCompletionRoutine : WSAOVERLAPPED_COMPLETION_ROUTINE*,
) : Int
fun WSARecvFrom(
s : SOCKET,
@@ -202,10 +209,10 @@ lib LibC
lpFrom : Sockaddr*,
lpFromlen : Int*,
lpOverlapped : WSAOVERLAPPED*,
- lpCompletionRoutine : WSAOVERLAPPED_COMPLETION_ROUTINE*
+ lpCompletionRoutine : WSAOVERLAPPED_COMPLETION_ROUTINE*,
) : Int
fun WSAResetEvent(
- hEvent : WSAEVENT
+ hEvent : WSAEVENT,
) : BOOL
fun WSASend(
s : SOCKET,
@@ -214,7 +221,7 @@ lib LibC
lpNumberOfBytesSent : DWORD*,
dwFlags : DWORD,
lpOverlapped : WSAOVERLAPPED*,
- lpCompletionRoutine : WSAOVERLAPPED_COMPLETION_ROUTINE*
+ lpCompletionRoutine : WSAOVERLAPPED_COMPLETION_ROUTINE*,
) : Int
fun WSASendTo(
s : SOCKET,
@@ -225,7 +232,7 @@ lib LibC
lpTo : Sockaddr*,
iTolen : Int,
lpOverlapped : WSAOVERLAPPED*,
- lpCompletionRoutine : WSAOVERLAPPED_COMPLETION_ROUTINE*
+ lpCompletionRoutine : WSAOVERLAPPED_COMPLETION_ROUTINE*,
) : Int
fun WSASocketW(
af : Int,
@@ -233,13 +240,13 @@ lib LibC
protocol : Int,
lpProtocolInfo : WSAPROTOCOL_INFOW*,
g : GROUP,
- dwFlags : DWORD
+ dwFlags : DWORD,
) : SOCKET
fun WSAWaitForMultipleEvents(
cEvents : DWORD,
lphEvents : WSAEVENT*,
fWaitAll : BOOL,
dwTimeout : DWORD,
- fAlertable : BOOL
+ fAlertable : BOOL,
) : DWORD
end
diff --git a/src/lib_c/x86_64-windows-msvc/c/ws2def.cr b/src/lib_c/x86_64-windows-msvc/c/ws2def.cr
index 9fc19857f4a3..41e0a1a408eb 100644
--- a/src/lib_c/x86_64-windows-msvc/c/ws2def.cr
+++ b/src/lib_c/x86_64-windows-msvc/c/ws2def.cr
@@ -208,4 +208,18 @@ lib LibC
ai_addr : Sockaddr*
ai_next : Addrinfo*
end
+
+ struct ADDRINFOEXW
+ ai_flags : Int
+ ai_family : Int
+ ai_socktype : Int
+ ai_protocol : Int
+ ai_addrlen : SizeT
+ ai_canonname : LPWSTR
+ ai_addr : Sockaddr*
+ ai_blob : Void*
+ ai_bloblen : SizeT
+ ai_provider : GUID*
+ ai_next : ADDRINFOEXW*
+ end
end
diff --git a/src/lib_c/x86_64-windows-msvc/c/ws2tcpip.cr b/src/lib_c/x86_64-windows-msvc/c/ws2tcpip.cr
index 338063ccf6f6..3b3f61ba7fdb 100644
--- a/src/lib_c/x86_64-windows-msvc/c/ws2tcpip.cr
+++ b/src/lib_c/x86_64-windows-msvc/c/ws2tcpip.cr
@@ -17,4 +17,24 @@ lib LibC
fun getaddrinfo(pNodeName : Char*, pServiceName : Char*, pHints : Addrinfo*, ppResult : Addrinfo**) : Int
fun inet_ntop(family : Int, pAddr : Void*, pStringBuf : Char*, stringBufSize : SizeT) : Char*
fun inet_pton(family : Int, pszAddrString : Char*, pAddrBuf : Void*) : Int
+
+ fun FreeAddrInfoExW(pAddrInfoEx : ADDRINFOEXW*)
+
+ alias LPLOOKUPSERVICE_COMPLETION_ROUTINE = DWORD, DWORD, WSAOVERLAPPED* ->
+
+ fun GetAddrInfoExW(
+ pName : LPWSTR,
+ pServiceName : LPWSTR,
+ dwNameSpace : DWORD,
+ lpNspId : GUID*,
+ hints : ADDRINFOEXW*,
+ ppResult : ADDRINFOEXW**,
+ timeout : Timeval*,
+ lpOverlapped : OVERLAPPED*,
+ lpCompletionRoutine : LPLOOKUPSERVICE_COMPLETION_ROUTINE,
+ lpHandle : HANDLE*,
+ ) : Int
+
+ fun GetAddrInfoExOverlappedResult(lpOverlapped : OVERLAPPED*) : Int
+ fun GetAddrInfoExCancel(lpHandle : HANDLE*) : Int
end
diff --git a/src/llvm.cr b/src/llvm.cr
index 6fb8767cad54..84c9dc89aa8f 100644
--- a/src/llvm.cr
+++ b/src/llvm.cr
@@ -140,6 +140,13 @@ module LLVM
string
end
+ protected def self.assert(error : LibLLVM::ErrorRef)
+ if error
+ chars = LibLLVM.get_error_message(error)
+ raise String.new(chars).tap { LibLLVM.dispose_error_message(chars) }
+ end
+ end
+
{% unless LibLLVM::IS_LT_130 %}
def self.run_passes(module mod : Module, passes : String, target_machine : TargetMachine, options : PassBuilderOptions)
LibLLVM.run_passes(mod, passes, target_machine, options)
diff --git a/src/llvm/builder.cr b/src/llvm/builder.cr
index 741f9ee8eb5c..b406d84145e5 100644
--- a/src/llvm/builder.cr
+++ b/src/llvm/builder.cr
@@ -239,11 +239,13 @@ class LLVM::Builder
end
{% end %}
- def not(value, name = "")
- # check_value(value)
+ {% for name in %w(not neg fneg) %}
+ def {{name.id}}(value, name = "")
+ # check_value(value)
- Value.new LibLLVM.build_not(self, value, name)
- end
+ Value.new LibLLVM.build_{{name.id}}(self, value, name)
+ end
+ {% end %}
def unreachable
Value.new LibLLVM.build_unreachable(self)
@@ -385,6 +387,10 @@ class LLVM::Builder
LibLLVM.dispose_builder(@unwrap)
end
+ def finalize
+ dispose
+ end
+
# The next lines are for ease debugging when a types/values
# are incorrectly used across contexts.
diff --git a/src/llvm/context.cr b/src/llvm/context.cr
index 987e8f13ba6b..84c96610a96f 100644
--- a/src/llvm/context.cr
+++ b/src/llvm/context.cr
@@ -108,7 +108,11 @@ class LLVM::Context
end
def const_string(string : String) : Value
- Value.new LibLLVM.const_string_in_context(self, string, string.bytesize, 0)
+ {% if LibLLVM::IS_LT_190 %}
+ Value.new LibLLVM.const_string_in_context(self, string, string.bytesize, 0)
+ {% else %}
+ Value.new LibLLVM.const_string_in_context2(self, string, string.bytesize, 0)
+ {% end %}
end
def const_struct(values : Array(LLVM::Value), packed = false) : Value
diff --git a/src/llvm/di_builder.cr b/src/llvm/di_builder.cr
index 37be65ef8cf8..7a06a7041349 100644
--- a/src/llvm/di_builder.cr
+++ b/src/llvm/di_builder.cr
@@ -96,7 +96,11 @@ struct LLVM::DIBuilder
end
def insert_declare_at_end(storage, var_info, expr, dl : LibLLVM::MetadataRef, block)
- LibLLVM.di_builder_insert_declare_at_end(self, storage, var_info, expr, dl, block)
+ {% if LibLLVM::IS_LT_190 %}
+ LibLLVM.di_builder_insert_declare_at_end(self, storage, var_info, expr, dl, block)
+ {% else %}
+ LibLLVM.di_builder_insert_declare_record_at_end(self, storage, var_info, expr, dl, block)
+ {% end %}
end
def get_or_create_array(elements : Array(LibLLVM::MetadataRef))
diff --git a/src/llvm/ext/find-llvm-config b/src/llvm/ext/find-llvm-config
index 40be636e1b23..5aa381aaf13b 100755
--- a/src/llvm/ext/find-llvm-config
+++ b/src/llvm/ext/find-llvm-config
@@ -16,7 +16,14 @@ if ! LLVM_CONFIG=$(command -v "$LLVM_CONFIG"); then
fi
if [ "$LLVM_CONFIG" ]; then
- printf "$LLVM_CONFIG"
+ case "$(uname -s)" in
+ MINGW32_NT*|MINGW64_NT*)
+ printf "%s" "$(cygpath -w "$LLVM_CONFIG")"
+ ;;
+ *)
+ printf "%s" "$LLVM_CONFIG"
+ ;;
+ esac
else
printf "Error: Could not find location of llvm-config. Please specify path in environment variable LLVM_CONFIG.\n" >&2
printf "Supported LLVM versions: $(cat "$(dirname $0)/llvm-versions.txt" | sed 's/\.0//g')\n" >&2
diff --git a/src/llvm/ext/llvm-versions.txt b/src/llvm/ext/llvm-versions.txt
index 92ae5ecbaa5a..6f4d3d4816d0 100644
--- a/src/llvm/ext/llvm-versions.txt
+++ b/src/llvm/ext/llvm-versions.txt
@@ -1 +1 @@
-18.1 17.0 16.0 15.0 14.0 13.0 12.0 11.1 11.0 10.0 9.0 8.0
+19.1 18.1 17.0 16.0 15.0 14.0 13.0 12.0 11.1 11.0 10.0 9.0 8.0
diff --git a/src/llvm/jit_compiler.cr b/src/llvm/jit_compiler.cr
index 33d03e697107..4acae901f381 100644
--- a/src/llvm/jit_compiler.cr
+++ b/src/llvm/jit_compiler.cr
@@ -39,6 +39,10 @@ class LLVM::JITCompiler
LibLLVM.get_pointer_to_global(self, value)
end
+ def function_address(name : String) : Void*
+ Pointer(Void).new(LibLLVM.get_function_address(self, name.check_no_null_byte))
+ end
+
def to_unsafe
@unwrap
end
diff --git a/src/llvm/lib_llvm.cr b/src/llvm/lib_llvm.cr
index 976cedc90df5..8b6856631b55 100644
--- a/src/llvm/lib_llvm.cr
+++ b/src/llvm/lib_llvm.cr
@@ -1,5 +1,5 @@
{% begin %}
- {% if flag?(:win32) && !flag?(:static) %}
+ {% if flag?(:msvc) && !flag?(:static) %}
{% config = nil %}
{% for dir in Crystal::LIBRARY_PATH.split(Crystal::System::Process::HOST_PATH_DELIMITER) %}
{% config ||= read_file?("#{dir.id}/llvm_VERSION") %}
@@ -21,7 +21,7 @@
lib LibLLVM
end
{% else %}
- {% llvm_config = env("LLVM_CONFIG") || `#{__DIR__}/ext/find-llvm-config`.stringify %}
+ {% llvm_config = env("LLVM_CONFIG") || `sh #{__DIR__}/ext/find-llvm-config`.stringify %}
{% llvm_version = `#{llvm_config.id} --version`.stringify %}
{% llvm_targets = env("LLVM_TARGETS") || `#{llvm_config.id} --targets-built`.stringify %}
{% llvm_ldflags = "`#{llvm_config.id} --libs --system-libs --ldflags#{" --link-static".id if flag?(:static)}#{" 2> /dev/null".id unless flag?(:win32)}`" %}
@@ -65,6 +65,7 @@
IS_LT_160 = {{compare_versions(LibLLVM::VERSION, "16.0.0") < 0}}
IS_LT_170 = {{compare_versions(LibLLVM::VERSION, "17.0.0") < 0}}
IS_LT_180 = {{compare_versions(LibLLVM::VERSION, "18.0.0") < 0}}
+ IS_LT_190 = {{compare_versions(LibLLVM::VERSION, "19.0.0") < 0}}
end
{% end %}
diff --git a/src/llvm/lib_llvm/bit_reader.cr b/src/llvm/lib_llvm/bit_reader.cr
new file mode 100644
index 000000000000..9bfd271cbbe2
--- /dev/null
+++ b/src/llvm/lib_llvm/bit_reader.cr
@@ -0,0 +1,5 @@
+require "./types"
+
+lib LibLLVM
+ fun parse_bitcode_in_context2 = LLVMParseBitcodeInContext2(c : ContextRef, mb : MemoryBufferRef, m : ModuleRef*) : Int
+end
diff --git a/src/llvm/lib_llvm/core.cr b/src/llvm/lib_llvm/core.cr
index de6f04010cfa..7137501fdb31 100644
--- a/src/llvm/lib_llvm/core.cr
+++ b/src/llvm/lib_llvm/core.cr
@@ -5,7 +5,12 @@ lib LibLLVM
# counterparts (e.g. `LLVMModuleFlagBehavior` v.s. `LLVM::Module::ModFlagBehavior`)
enum ModuleFlagBehavior
- Warning = 1
+ Error = 0
+ Warning = 1
+ Require = 2
+ Override = 3
+ Append = 4
+ AppendUnique = 5
end
alias AttributeIndex = UInt
@@ -116,7 +121,11 @@ lib LibLLVM
fun const_int_get_zext_value = LLVMConstIntGetZExtValue(constant_val : ValueRef) : ULongLong
fun const_int_get_sext_value = LLVMConstIntGetSExtValue(constant_val : ValueRef) : LongLong
- fun const_string_in_context = LLVMConstStringInContext(c : ContextRef, str : Char*, length : UInt, dont_null_terminate : Bool) : ValueRef
+ {% if LibLLVM::IS_LT_190 %}
+ fun const_string_in_context = LLVMConstStringInContext(c : ContextRef, str : Char*, length : UInt, dont_null_terminate : Bool) : ValueRef
+ {% else %}
+ fun const_string_in_context2 = LLVMConstStringInContext2(c : ContextRef, str : Char*, length : SizeT, dont_null_terminate : Bool) : ValueRef
+ {% end %}
fun const_struct_in_context = LLVMConstStructInContext(c : ContextRef, constant_vals : ValueRef*, count : UInt, packed : Bool) : ValueRef
fun const_array = LLVMConstArray(element_ty : TypeRef, constant_vals : ValueRef*, length : UInt) : ValueRef
@@ -239,6 +248,8 @@ lib LibLLVM
fun build_or = LLVMBuildOr(BuilderRef, lhs : ValueRef, rhs : ValueRef, name : Char*) : ValueRef
fun build_xor = LLVMBuildXor(BuilderRef, lhs : ValueRef, rhs : ValueRef, name : Char*) : ValueRef
fun build_not = LLVMBuildNot(BuilderRef, value : ValueRef, name : Char*) : ValueRef
+ fun build_neg = LLVMBuildNeg(BuilderRef, value : ValueRef, name : Char*) : ValueRef
+ fun build_fneg = LLVMBuildFNeg(BuilderRef, value : ValueRef, name : Char*) : ValueRef
fun build_malloc = LLVMBuildMalloc(BuilderRef, ty : TypeRef, name : Char*) : ValueRef
fun build_array_malloc = LLVMBuildArrayMalloc(BuilderRef, ty : TypeRef, val : ValueRef, name : Char*) : ValueRef
diff --git a/src/llvm/lib_llvm/debug_info.cr b/src/llvm/lib_llvm/debug_info.cr
index e97e8c71a177..15d2eca3ebd6 100644
--- a/src/llvm/lib_llvm/debug_info.cr
+++ b/src/llvm/lib_llvm/debug_info.cr
@@ -14,7 +14,7 @@ lib LibLLVM
builder : DIBuilderRef, lang : LLVM::DwarfSourceLanguage, file_ref : MetadataRef, producer : Char*,
producer_len : SizeT, is_optimized : Bool, flags : Char*, flags_len : SizeT, runtime_ver : UInt,
split_name : Char*, split_name_len : SizeT, kind : DWARFEmissionKind, dwo_id : UInt,
- split_debug_inlining : Bool, debug_info_for_profiling : Bool
+ split_debug_inlining : Bool, debug_info_for_profiling : Bool,
) : MetadataRef
{% else %}
fun di_builder_create_compile_unit = LLVMDIBuilderCreateCompileUnit(
@@ -22,82 +22,82 @@ lib LibLLVM
producer_len : SizeT, is_optimized : Bool, flags : Char*, flags_len : SizeT, runtime_ver : UInt,
split_name : Char*, split_name_len : SizeT, kind : DWARFEmissionKind, dwo_id : UInt,
split_debug_inlining : Bool, debug_info_for_profiling : Bool, sys_root : Char*,
- sys_root_len : SizeT, sdk : Char*, sdk_len : SizeT
+ sys_root_len : SizeT, sdk : Char*, sdk_len : SizeT,
) : MetadataRef
{% end %}
fun di_builder_create_file = LLVMDIBuilderCreateFile(
builder : DIBuilderRef, filename : Char*, filename_len : SizeT,
- directory : Char*, directory_len : SizeT
+ directory : Char*, directory_len : SizeT,
) : MetadataRef
fun di_builder_create_function = LLVMDIBuilderCreateFunction(
builder : DIBuilderRef, scope : MetadataRef, name : Char*, name_len : SizeT,
linkage_name : Char*, linkage_name_len : SizeT, file : MetadataRef, line_no : UInt,
ty : MetadataRef, is_local_to_unit : Bool, is_definition : Bool, scope_line : UInt,
- flags : LLVM::DIFlags, is_optimized : Bool
+ flags : LLVM::DIFlags, is_optimized : Bool,
) : MetadataRef
fun di_builder_create_lexical_block = LLVMDIBuilderCreateLexicalBlock(
- builder : DIBuilderRef, scope : MetadataRef, file : MetadataRef, line : UInt, column : UInt
+ builder : DIBuilderRef, scope : MetadataRef, file : MetadataRef, line : UInt, column : UInt,
) : MetadataRef
fun di_builder_create_lexical_block_file = LLVMDIBuilderCreateLexicalBlockFile(
- builder : DIBuilderRef, scope : MetadataRef, file_scope : MetadataRef, discriminator : UInt
+ builder : DIBuilderRef, scope : MetadataRef, file_scope : MetadataRef, discriminator : UInt,
) : MetadataRef
fun di_builder_create_debug_location = LLVMDIBuilderCreateDebugLocation(
- ctx : ContextRef, line : UInt, column : UInt, scope : MetadataRef, inlined_at : MetadataRef
+ ctx : ContextRef, line : UInt, column : UInt, scope : MetadataRef, inlined_at : MetadataRef,
) : MetadataRef
fun di_builder_get_or_create_type_array = LLVMDIBuilderGetOrCreateTypeArray(builder : DIBuilderRef, types : MetadataRef*, length : SizeT) : MetadataRef
fun di_builder_create_subroutine_type = LLVMDIBuilderCreateSubroutineType(
builder : DIBuilderRef, file : MetadataRef, parameter_types : MetadataRef*,
- num_parameter_types : UInt, flags : LLVM::DIFlags
+ num_parameter_types : UInt, flags : LLVM::DIFlags,
) : MetadataRef
{% unless LibLLVM::IS_LT_90 %}
fun di_builder_create_enumerator = LLVMDIBuilderCreateEnumerator(
- builder : DIBuilderRef, name : Char*, name_len : SizeT, value : Int64, is_unsigned : Bool
+ builder : DIBuilderRef, name : Char*, name_len : SizeT, value : Int64, is_unsigned : Bool,
) : MetadataRef
{% end %}
fun di_builder_create_enumeration_type = LLVMDIBuilderCreateEnumerationType(
builder : DIBuilderRef, scope : MetadataRef, name : Char*, name_len : SizeT, file : MetadataRef,
line_number : UInt, size_in_bits : UInt64, align_in_bits : UInt32,
- elements : MetadataRef*, num_elements : UInt, class_ty : MetadataRef
+ elements : MetadataRef*, num_elements : UInt, class_ty : MetadataRef,
) : MetadataRef
fun di_builder_create_union_type = LLVMDIBuilderCreateUnionType(
builder : DIBuilderRef, scope : MetadataRef, name : Char*, name_len : SizeT, file : MetadataRef,
line_number : UInt, size_in_bits : UInt64, align_in_bits : UInt32, flags : LLVM::DIFlags,
- elements : MetadataRef*, num_elements : UInt, run_time_lang : UInt, unique_id : Char*, unique_id_len : SizeT
+ elements : MetadataRef*, num_elements : UInt, run_time_lang : UInt, unique_id : Char*, unique_id_len : SizeT,
) : MetadataRef
fun di_builder_create_array_type = LLVMDIBuilderCreateArrayType(
builder : DIBuilderRef, size : UInt64, align_in_bits : UInt32,
- ty : MetadataRef, subscripts : MetadataRef*, num_subscripts : UInt
+ ty : MetadataRef, subscripts : MetadataRef*, num_subscripts : UInt,
) : MetadataRef
fun di_builder_create_unspecified_type = LLVMDIBuilderCreateUnspecifiedType(builder : DIBuilderRef, name : Char*, name_len : SizeT) : MetadataRef
fun di_builder_create_basic_type = LLVMDIBuilderCreateBasicType(
builder : DIBuilderRef, name : Char*, name_len : SizeT, size_in_bits : UInt64,
- encoding : UInt, flags : LLVM::DIFlags
+ encoding : UInt, flags : LLVM::DIFlags,
) : MetadataRef
fun di_builder_create_pointer_type = LLVMDIBuilderCreatePointerType(
builder : DIBuilderRef, pointee_ty : MetadataRef, size_in_bits : UInt64, align_in_bits : UInt32,
- address_space : UInt, name : Char*, name_len : SizeT
+ address_space : UInt, name : Char*, name_len : SizeT,
) : MetadataRef
fun di_builder_create_struct_type = LLVMDIBuilderCreateStructType(
builder : DIBuilderRef, scope : MetadataRef, name : Char*, name_len : SizeT, file : MetadataRef,
line_number : UInt, size_in_bits : UInt64, align_in_bits : UInt32, flags : LLVM::DIFlags,
derived_from : MetadataRef, elements : MetadataRef*, num_elements : UInt,
- run_time_lang : UInt, v_table_holder : MetadataRef, unique_id : Char*, unique_id_len : SizeT
+ run_time_lang : UInt, v_table_holder : MetadataRef, unique_id : Char*, unique_id_len : SizeT,
) : MetadataRef
fun di_builder_create_member_type = LLVMDIBuilderCreateMemberType(
builder : DIBuilderRef, scope : MetadataRef, name : Char*, name_len : SizeT, file : MetadataRef,
line_no : UInt, size_in_bits : UInt64, align_in_bits : UInt32, offset_in_bits : UInt64,
- flags : LLVM::DIFlags, ty : MetadataRef
+ flags : LLVM::DIFlags, ty : MetadataRef,
) : MetadataRef
fun di_builder_create_replaceable_composite_type = LLVMDIBuilderCreateReplaceableCompositeType(
builder : DIBuilderRef, tag : UInt, name : Char*, name_len : SizeT, scope : MetadataRef,
file : MetadataRef, line : UInt, runtime_lang : UInt, size_in_bits : UInt64, align_in_bits : UInt32,
- flags : LLVM::DIFlags, unique_identifier : Char*, unique_identifier_len : SizeT
+ flags : LLVM::DIFlags, unique_identifier : Char*, unique_identifier_len : SizeT,
) : MetadataRef
fun di_builder_get_or_create_subrange = LLVMDIBuilderGetOrCreateSubrange(builder : DIBuilderRef, lo : Int64, count : Int64) : MetadataRef
@@ -111,18 +111,25 @@ lib LibLLVM
fun metadata_replace_all_uses_with = LLVMMetadataReplaceAllUsesWith(target_metadata : MetadataRef, replacement : MetadataRef)
- fun di_builder_insert_declare_at_end = LLVMDIBuilderInsertDeclareAtEnd(
- builder : DIBuilderRef, storage : ValueRef, var_info : MetadataRef,
- expr : MetadataRef, debug_loc : MetadataRef, block : BasicBlockRef
- ) : ValueRef
+ {% if LibLLVM::IS_LT_190 %}
+ fun di_builder_insert_declare_at_end = LLVMDIBuilderInsertDeclareAtEnd(
+ builder : DIBuilderRef, storage : ValueRef, var_info : MetadataRef,
+ expr : MetadataRef, debug_loc : MetadataRef, block : BasicBlockRef,
+ ) : ValueRef
+ {% else %}
+ fun di_builder_insert_declare_record_at_end = LLVMDIBuilderInsertDeclareRecordAtEnd(
+ builder : DIBuilderRef, storage : ValueRef, var_info : MetadataRef,
+ expr : MetadataRef, debug_loc : MetadataRef, block : BasicBlockRef,
+ ) : DbgRecordRef
+ {% end %}
fun di_builder_create_auto_variable = LLVMDIBuilderCreateAutoVariable(
builder : DIBuilderRef, scope : MetadataRef, name : Char*, name_len : SizeT, file : MetadataRef,
- line_no : UInt, ty : MetadataRef, always_preserve : Bool, flags : LLVM::DIFlags, align_in_bits : UInt32
+ line_no : UInt, ty : MetadataRef, always_preserve : Bool, flags : LLVM::DIFlags, align_in_bits : UInt32,
) : MetadataRef
fun di_builder_create_parameter_variable = LLVMDIBuilderCreateParameterVariable(
builder : DIBuilderRef, scope : MetadataRef, name : Char*, name_len : SizeT, arg_no : UInt,
- file : MetadataRef, line_no : UInt, ty : MetadataRef, always_preserve : Bool, flags : LLVM::DIFlags
+ file : MetadataRef, line_no : UInt, ty : MetadataRef, always_preserve : Bool, flags : LLVM::DIFlags,
) : MetadataRef
fun set_subprogram = LLVMSetSubprogram(func : ValueRef, sp : MetadataRef)
diff --git a/src/llvm/lib_llvm/error.cr b/src/llvm/lib_llvm/error.cr
index b816a7e2088b..5a035b5f80a5 100644
--- a/src/llvm/lib_llvm/error.cr
+++ b/src/llvm/lib_llvm/error.cr
@@ -1,3 +1,6 @@
lib LibLLVM
type ErrorRef = Void*
+
+ fun get_error_message = LLVMGetErrorMessage(err : ErrorRef) : Char*
+ fun dispose_error_message = LLVMDisposeErrorMessage(err_msg : Char*)
end
diff --git a/src/llvm/lib_llvm/execution_engine.cr b/src/llvm/lib_llvm/execution_engine.cr
index f9de5c10ea39..bfc2e23154db 100644
--- a/src/llvm/lib_llvm/execution_engine.cr
+++ b/src/llvm/lib_llvm/execution_engine.cr
@@ -30,4 +30,5 @@ lib LibLLVM
fun run_function = LLVMRunFunction(ee : ExecutionEngineRef, f : ValueRef, num_args : UInt, args : GenericValueRef*) : GenericValueRef
fun get_execution_engine_target_machine = LLVMGetExecutionEngineTargetMachine(ee : ExecutionEngineRef) : TargetMachineRef
fun get_pointer_to_global = LLVMGetPointerToGlobal(ee : ExecutionEngineRef, global : ValueRef) : Void*
+ fun get_function_address = LLVMGetFunctionAddress(ee : ExecutionEngineRef, name : Char*) : UInt64
end
diff --git a/src/llvm/lib_llvm/lljit.cr b/src/llvm/lib_llvm/lljit.cr
new file mode 100644
index 000000000000..93c2089c9db0
--- /dev/null
+++ b/src/llvm/lib_llvm/lljit.cr
@@ -0,0 +1,17 @@
+{% skip_file if LibLLVM::IS_LT_110 %}
+
+lib LibLLVM
+ alias OrcLLJITBuilderRef = Void*
+ alias OrcLLJITRef = Void*
+
+ fun orc_create_lljit_builder = LLVMOrcCreateLLJITBuilder : OrcLLJITBuilderRef
+ fun orc_dispose_lljit_builder = LLVMOrcDisposeLLJITBuilder(builder : OrcLLJITBuilderRef)
+
+ fun orc_create_lljit = LLVMOrcCreateLLJIT(result : OrcLLJITRef*, builder : OrcLLJITBuilderRef) : ErrorRef
+ fun orc_dispose_lljit = LLVMOrcDisposeLLJIT(j : OrcLLJITRef) : ErrorRef
+
+ fun orc_lljit_get_main_jit_dylib = LLVMOrcLLJITGetMainJITDylib(j : OrcLLJITRef) : OrcJITDylibRef
+ fun orc_lljit_get_global_prefix = LLVMOrcLLJITGetGlobalPrefix(j : OrcLLJITRef) : Char
+ fun orc_lljit_add_llvm_ir_module = LLVMOrcLLJITAddLLVMIRModule(j : OrcLLJITRef, jd : OrcJITDylibRef, tsm : OrcThreadSafeModuleRef) : ErrorRef
+ fun orc_lljit_lookup = LLVMOrcLLJITLookup(j : OrcLLJITRef, result : OrcExecutorAddress*, name : Char*) : ErrorRef
+end
diff --git a/src/llvm/lib_llvm/orc.cr b/src/llvm/lib_llvm/orc.cr
new file mode 100644
index 000000000000..278a9c4aab5d
--- /dev/null
+++ b/src/llvm/lib_llvm/orc.cr
@@ -0,0 +1,26 @@
+{% skip_file if LibLLVM::IS_LT_110 %}
+
+lib LibLLVM
+ # OrcJITTargetAddress before LLVM 13.0 (also an alias of UInt64)
+ alias OrcExecutorAddress = UInt64
+ alias OrcSymbolStringPoolEntryRef = Void*
+ alias OrcJITDylibRef = Void*
+ alias OrcDefinitionGeneratorRef = Void*
+ alias OrcSymbolPredicate = Void*, OrcSymbolStringPoolEntryRef -> Int
+ alias OrcThreadSafeContextRef = Void*
+ alias OrcThreadSafeModuleRef = Void*
+
+ fun orc_create_dynamic_library_search_generator_for_process = LLVMOrcCreateDynamicLibrarySearchGeneratorForProcess(
+ result : OrcDefinitionGeneratorRef*, global_prefx : Char,
+ filter : OrcSymbolPredicate, filter_ctx : Void*,
+ ) : ErrorRef
+
+ fun orc_jit_dylib_add_generator = LLVMOrcJITDylibAddGenerator(jd : OrcJITDylibRef, dg : OrcDefinitionGeneratorRef)
+
+ fun orc_create_new_thread_safe_context = LLVMOrcCreateNewThreadSafeContext : OrcThreadSafeContextRef
+ fun orc_thread_safe_context_get_context = LLVMOrcThreadSafeContextGetContext(ts_ctx : OrcThreadSafeContextRef) : ContextRef
+ fun orc_dispose_thread_safe_context = LLVMOrcDisposeThreadSafeContext(ts_ctx : OrcThreadSafeContextRef)
+
+ fun orc_create_new_thread_safe_module = LLVMOrcCreateNewThreadSafeModule(m : ModuleRef, ts_ctx : OrcThreadSafeContextRef) : OrcThreadSafeModuleRef
+ fun orc_dispose_thread_safe_module = LLVMOrcDisposeThreadSafeModule(tsm : OrcThreadSafeModuleRef)
+end
diff --git a/src/llvm/lib_llvm/types.cr b/src/llvm/lib_llvm/types.cr
index a1b374f30219..532078394794 100644
--- a/src/llvm/lib_llvm/types.cr
+++ b/src/llvm/lib_llvm/types.cr
@@ -17,4 +17,5 @@ lib LibLLVM
{% end %}
type OperandBundleRef = Void*
type AttributeRef = Void*
+ type DbgRecordRef = Void*
end
diff --git a/src/llvm/module.cr b/src/llvm/module.cr
index f216d485055c..0e73e983358a 100644
--- a/src/llvm/module.cr
+++ b/src/llvm/module.cr
@@ -6,6 +6,12 @@ class LLVM::Module
getter context : Context
+ def self.parse(memory_buffer : MemoryBuffer, context : Context) : self
+ LibLLVM.parse_bitcode_in_context2(context, memory_buffer, out module_ref)
+ raise "BUG: failed to parse LLVM bitcode from memory buffer" unless module_ref
+ new(module_ref, context)
+ end
+
def initialize(@unwrap : LibLLVM::ModuleRef, @context : Context)
@owned = false
end
@@ -39,6 +45,10 @@ class LLVM::Module
GlobalCollection.new(self)
end
+ def add_flag(module_flag : LibLLVM::ModuleFlagBehavior, key : String, val : Int32)
+ add_flag(module_flag, key, @context.int32.const_int(val))
+ end
+
def add_flag(module_flag : LibLLVM::ModuleFlagBehavior, key : String, val : Value)
LibLLVM.add_module_flag(
self,
diff --git a/src/llvm/orc/jit_dylib.cr b/src/llvm/orc/jit_dylib.cr
new file mode 100644
index 000000000000..b1050725110b
--- /dev/null
+++ b/src/llvm/orc/jit_dylib.cr
@@ -0,0 +1,16 @@
+{% skip_file if LibLLVM::IS_LT_110 %}
+
+@[Experimental("The C API wrapped by this type is marked as experimental by LLVM.")]
+class LLVM::Orc::JITDylib
+ protected def initialize(@unwrap : LibLLVM::OrcJITDylibRef)
+ end
+
+ def to_unsafe
+ @unwrap
+ end
+
+ def link_symbols_from_current_process(global_prefix : Char) : Nil
+ LLVM.assert LibLLVM.orc_create_dynamic_library_search_generator_for_process(out dg, global_prefix.ord.to_u8, nil, nil)
+ LibLLVM.orc_jit_dylib_add_generator(self, dg)
+ end
+end
diff --git a/src/llvm/orc/lljit.cr b/src/llvm/orc/lljit.cr
new file mode 100644
index 000000000000..62fcc7f0519f
--- /dev/null
+++ b/src/llvm/orc/lljit.cr
@@ -0,0 +1,46 @@
+{% skip_file if LibLLVM::IS_LT_110 %}
+
+@[Experimental("The C API wrapped by this type is marked as experimental by LLVM.")]
+class LLVM::Orc::LLJIT
+ protected def initialize(@unwrap : LibLLVM::OrcLLJITRef)
+ end
+
+ def self.new(builder : LLJITBuilder)
+ builder.take_ownership { raise "Failed to take ownership of LLVM::Orc::LLJITBuilder" }
+ LLVM.assert LibLLVM.orc_create_lljit(out unwrap, builder)
+ new(unwrap)
+ end
+
+ def to_unsafe
+ @unwrap
+ end
+
+ def dispose : Nil
+ LLVM.assert LibLLVM.orc_dispose_lljit(self)
+ @unwrap = LibLLVM::OrcLLJITRef.null
+ end
+
+ def finalize
+ if @unwrap
+ LibLLVM.orc_dispose_lljit(self)
+ end
+ end
+
+ def main_jit_dylib : JITDylib
+ JITDylib.new(LibLLVM.orc_lljit_get_main_jit_dylib(self))
+ end
+
+ def global_prefix : Char
+ LibLLVM.orc_lljit_get_global_prefix(self).unsafe_chr
+ end
+
+ def add_llvm_ir_module(dylib : JITDylib, tsm : ThreadSafeModule) : Nil
+ tsm.take_ownership { raise "Failed to take ownership of LLVM::Orc::ThreadSafeModule" }
+ LLVM.assert LibLLVM.orc_lljit_add_llvm_ir_module(self, dylib, tsm)
+ end
+
+ def lookup(name : String) : Void*
+ LLVM.assert LibLLVM.orc_lljit_lookup(self, out address, name.check_no_null_byte)
+ Pointer(Void).new(address)
+ end
+end
diff --git a/src/llvm/orc/lljit_builder.cr b/src/llvm/orc/lljit_builder.cr
new file mode 100644
index 000000000000..8147e5947376
--- /dev/null
+++ b/src/llvm/orc/lljit_builder.cr
@@ -0,0 +1,35 @@
+{% skip_file if LibLLVM::IS_LT_110 %}
+
+@[Experimental("The C API wrapped by this type is marked as experimental by LLVM.")]
+class LLVM::Orc::LLJITBuilder
+ protected def initialize(@unwrap : LibLLVM::OrcLLJITBuilderRef)
+ @dispose_on_finalize = true
+ end
+
+ def self.new
+ new(LibLLVM.orc_create_lljit_builder)
+ end
+
+ def to_unsafe
+ @unwrap
+ end
+
+ def dispose : Nil
+ LibLLVM.orc_dispose_lljit_builder(self)
+ @unwrap = LibLLVM::OrcLLJITBuilderRef.null
+ end
+
+ def finalize
+ if @dispose_on_finalize && @unwrap
+ dispose
+ end
+ end
+
+ def take_ownership(&) : Nil
+ if @dispose_on_finalize
+ @dispose_on_finalize = false
+ else
+ yield
+ end
+ end
+end
diff --git a/src/llvm/orc/thread_safe_context.cr b/src/llvm/orc/thread_safe_context.cr
new file mode 100644
index 000000000000..38c4ece7a50a
--- /dev/null
+++ b/src/llvm/orc/thread_safe_context.cr
@@ -0,0 +1,30 @@
+{% skip_file if LibLLVM::IS_LT_110 %}
+
+@[Experimental("The C API wrapped by this type is marked as experimental by LLVM.")]
+class LLVM::Orc::ThreadSafeContext
+ protected def initialize(@unwrap : LibLLVM::OrcThreadSafeContextRef)
+ end
+
+ def self.new
+ new(LibLLVM.orc_create_new_thread_safe_context)
+ end
+
+ def to_unsafe
+ @unwrap
+ end
+
+ def dispose : Nil
+ LibLLVM.orc_dispose_thread_safe_context(self)
+ @unwrap = LibLLVM::OrcThreadSafeContextRef.null
+ end
+
+ def finalize
+ if @unwrap
+ dispose
+ end
+ end
+
+ def context : LLVM::Context
+ LLVM::Context.new(LibLLVM.orc_thread_safe_context_get_context(self), false)
+ end
+end
diff --git a/src/llvm/orc/thread_safe_module.cr b/src/llvm/orc/thread_safe_module.cr
new file mode 100644
index 000000000000..5e29667fd9cd
--- /dev/null
+++ b/src/llvm/orc/thread_safe_module.cr
@@ -0,0 +1,36 @@
+{% skip_file if LibLLVM::IS_LT_110 %}
+
+@[Experimental("The C API wrapped by this type is marked as experimental by LLVM.")]
+class LLVM::Orc::ThreadSafeModule
+ protected def initialize(@unwrap : LibLLVM::OrcThreadSafeModuleRef)
+ @dispose_on_finalize = true
+ end
+
+ def self.new(llvm_mod : LLVM::Module, ts_ctx : ThreadSafeContext)
+ llvm_mod.take_ownership { raise "Failed to take ownership of LLVM::Module" }
+ new(LibLLVM.orc_create_new_thread_safe_module(llvm_mod, ts_ctx))
+ end
+
+ def to_unsafe
+ @unwrap
+ end
+
+ def dispose : Nil
+ LibLLVM.orc_dispose_thread_safe_module(self)
+ @unwrap = LibLLVM::OrcThreadSafeModuleRef.null
+ end
+
+ def finalize
+ if @dispose_on_finalize && @unwrap
+ dispose
+ end
+ end
+
+ def take_ownership(&) : Nil
+ if @dispose_on_finalize
+ @dispose_on_finalize = false
+ else
+ yield
+ end
+ end
+end
diff --git a/src/llvm/target_machine.cr b/src/llvm/target_machine.cr
index b9de8296d5c8..6e31836ef7f2 100644
--- a/src/llvm/target_machine.cr
+++ b/src/llvm/target_machine.cr
@@ -48,7 +48,7 @@ class LLVM::TargetMachine
def abi
triple = self.triple
case triple
- when /x86_64.+windows-msvc/
+ when /x86_64.+windows-(?:msvc|gnu)/
ABI::X86_Win64.new(self)
when /x86_64|amd64/
ABI::X86_64.new(self)
diff --git a/src/number.cr b/src/number.cr
index f7c82aa4cded..9d955c065df3 100644
--- a/src/number.cr
+++ b/src/number.cr
@@ -59,7 +59,7 @@ struct Number
# :nodoc:
macro expand_div(rhs_types, result_type)
{% for rhs in rhs_types %}
- @[AlwaysInline]
+ @[::AlwaysInline]
def /(other : {{rhs}}) : {{result_type}}
{{result_type}}.new(self) / {{result_type}}.new(other)
end
@@ -84,7 +84,7 @@ struct Number
# [1, 2, 3, 4] of Int64 # : Array(Int64)
# ```
macro [](*nums)
- Array({{@type}}).build({{nums.size}}) do |%buffer|
+ ::Array({{@type}}).build({{nums.size}}) do |%buffer|
{% for num, i in nums %}
%buffer[{{i}}] = {{@type}}.new({{num}})
{% end %}
@@ -113,7 +113,7 @@ struct Number
# Slice[1_i64, 2_i64, 3_i64, 4_i64] # : Slice(Int64)
# ```
macro slice(*nums, read_only = false)
- %slice = Slice({{@type}}).new({{nums.size}}, read_only: {{read_only}})
+ %slice = ::Slice({{@type}}).new({{nums.size}}, read_only: {{read_only}})
{% for num, i in nums %}
%slice.to_unsafe[{{i}}] = {{@type}}.new!({{num}})
{% end %}
@@ -139,7 +139,7 @@ struct Number
# StaticArray[1_i64, 2_i64, 3_i64, 4_i64] # : StaticArray(Int64)
# ```
macro static_array(*nums)
- %array = uninitialized StaticArray({{@type}}, {{nums.size}})
+ %array = uninitialized ::StaticArray({{@type}}, {{nums.size}})
{% for num, i in nums %}
%array.to_unsafe[{{i}}] = {{@type}}.new!({{num}})
{% end %}
diff --git a/src/object.cr b/src/object.cr
index ba818ac2979e..800736687788 100644
--- a/src/object.cr
+++ b/src/object.cr
@@ -562,7 +562,7 @@ class Object
def {{method_prefix}}\{{name.var.id}} : \{{name.type}}
if (value = {{var_prefix}}\{{name.var.id}}).nil?
- ::raise NilAssertionError.new("\{{@type}}\{{"{{doc_prefix}}".id}}\{{name.var.id}} cannot be nil")
+ ::raise ::NilAssertionError.new("\{{@type}}\{{"{{doc_prefix}}".id}}\{{name.var.id}} cannot be nil")
else
value
end
@@ -574,7 +574,7 @@ class Object
def {{method_prefix}}\{{name.id}}
if (value = {{var_prefix}}\{{name.id}}).nil?
- ::raise NilAssertionError.new("\{{@type}}\{{"{{doc_prefix}}".id}}\{{name.id}} cannot be nil")
+ ::raise ::NilAssertionError.new("\{{@type}}\{{"{{doc_prefix}}".id}}\{{name.id}} cannot be nil")
else
value
end
@@ -1293,7 +1293,7 @@ class Object
# wrapper.capitalize # => "Hello"
# ```
macro delegate(*methods, to object)
- {% if compare_versions(Crystal::VERSION, "1.12.0-dev") >= 0 %}
+ {% if compare_versions(::Crystal::VERSION, "1.12.0-dev") >= 0 %}
{% eq_operators = %w(<= >= == != []= ===) %}
{% for method in methods %}
{% if method.id.ends_with?('=') && !eq_operators.includes?(method.id.stringify) %}
@@ -1427,18 +1427,18 @@ class Object
macro def_clone
# Returns a copy of `self` with all instance variables cloned.
def clone
- \{% if @type < Reference && !@type.instance_vars.map(&.type).all? { |t| t == ::Bool || t == ::Char || t == ::Symbol || t == ::String || t < ::Number::Primitive } %}
+ \{% if @type < ::Reference && !@type.instance_vars.map(&.type).all? { |t| t == ::Bool || t == ::Char || t == ::Symbol || t == ::String || t < ::Number::Primitive } %}
exec_recursive_clone do |hash|
clone = \{{@type}}.allocate
hash[object_id] = clone.object_id
clone.initialize_copy(self)
- GC.add_finalizer(clone) if clone.responds_to?(:finalize)
+ ::GC.add_finalizer(clone) if clone.responds_to?(:finalize)
clone
end
\{% else %}
clone = \{{@type}}.allocate
clone.initialize_copy(self)
- GC.add_finalizer(clone) if clone.responds_to?(:finalize)
+ ::GC.add_finalizer(clone) if clone.responds_to?(:finalize)
clone
\{% end %}
end
diff --git a/src/openssl/lib_crypto.cr b/src/openssl/lib_crypto.cr
index aef6a238f663..fecc69ad44fc 100644
--- a/src/openssl/lib_crypto.cr
+++ b/src/openssl/lib_crypto.cr
@@ -1,6 +1,6 @@
{% begin %}
lib LibCrypto
- {% if flag?(:win32) %}
+ {% if flag?(:msvc) %}
{% from_libressl = false %}
{% ssl_version = nil %}
{% for dir in Crystal::LIBRARY_PATH.split(Crystal::System::Process::HOST_PATH_DELIMITER) %}
@@ -13,10 +13,12 @@
{% end %}
{% ssl_version ||= "0.0.0" %}
{% else %}
- {% from_libressl = (`hash pkg-config 2> /dev/null || printf %s false` != "false") &&
- (`test -f $(pkg-config --silence-errors --variable=includedir libcrypto)/openssl/opensslv.h || printf %s false` != "false") &&
- (`printf "#include \nLIBRESSL_VERSION_NUMBER" | ${CC:-cc} $(pkg-config --cflags --silence-errors libcrypto || true) -E -`.chomp.split('\n').last != "LIBRESSL_VERSION_NUMBER") %}
- {% ssl_version = `hash pkg-config 2> /dev/null && pkg-config --silence-errors --modversion libcrypto || printf %s 0.0.0`.split.last.gsub(/[^0-9.]/, "") %}
+ # these have to be wrapped in `sh -c` since for MinGW-w64 the compiler
+ # passes the command string to `LibC.CreateProcessW`
+ {% from_libressl = (`sh -c 'hash pkg-config 2> /dev/null || printf %s false'` != "false") &&
+ (`sh -c 'test -f $(pkg-config --silence-errors --variable=includedir libcrypto)/openssl/opensslv.h || printf %s false'` != "false") &&
+ (`sh -c 'printf "#include \nLIBRESSL_VERSION_NUMBER" | ${CC:-cc} $(pkg-config --cflags --silence-errors libcrypto || true) -E -'`.chomp.split('\n').last != "LIBRESSL_VERSION_NUMBER") %}
+ {% ssl_version = `sh -c 'hash pkg-config 2> /dev/null && pkg-config --silence-errors --modversion libcrypto || printf %s 0.0.0'`.split.last.gsub(/[^0-9.]/, "") %}
{% end %}
{% if from_libressl %}
@@ -57,7 +59,10 @@ lib LibCrypto
struct Bio
method : Void*
- callback : (Void*, Int, Char*, Int, Long, Long) -> Long
+ callback : BIO_callback_fn
+ {% if compare_versions(LIBRESSL_VERSION, "3.5.0") >= 0 %}
+ callback_ex : BIO_callback_fn_ex
+ {% end %}
cb_arg : Char*
init : Int
shutdown : Int
@@ -72,6 +77,9 @@ lib LibCrypto
num_write : ULong
end
+ alias BIO_callback_fn = (Bio*, Int, Char*, Int, Long, Long) -> Long
+ alias BIO_callback_fn_ex = (Bio*, Int, Char, SizeT, Int, Long, Int, SizeT*) -> Long
+
PKCS5_SALT_LEN = 8
EVP_MAX_KEY_LENGTH = 32
EVP_MAX_IV_LENGTH = 16
diff --git a/src/openssl/lib_ssl.cr b/src/openssl/lib_ssl.cr
index 6adb3f172a3b..4e7e2def549c 100644
--- a/src/openssl/lib_ssl.cr
+++ b/src/openssl/lib_ssl.cr
@@ -6,7 +6,7 @@ require "./lib_crypto"
{% begin %}
lib LibSSL
- {% if flag?(:win32) %}
+ {% if flag?(:msvc) %}
{% from_libressl = false %}
{% ssl_version = nil %}
{% for dir in Crystal::LIBRARY_PATH.split(Crystal::System::Process::HOST_PATH_DELIMITER) %}
@@ -19,10 +19,12 @@ require "./lib_crypto"
{% end %}
{% ssl_version ||= "0.0.0" %}
{% else %}
- {% from_libressl = (`hash pkg-config 2> /dev/null || printf %s false` != "false") &&
- (`test -f $(pkg-config --silence-errors --variable=includedir libssl)/openssl/opensslv.h || printf %s false` != "false") &&
- (`printf "#include \nLIBRESSL_VERSION_NUMBER" | ${CC:-cc} $(pkg-config --cflags --silence-errors libssl || true) -E -`.chomp.split('\n').last != "LIBRESSL_VERSION_NUMBER") %}
- {% ssl_version = `hash pkg-config 2> /dev/null && pkg-config --silence-errors --modversion libssl || printf %s 0.0.0`.split.last.gsub(/[^0-9.]/, "") %}
+ # these have to be wrapped in `sh -c` since for MinGW-w64 the compiler
+ # passes the command string to `LibC.CreateProcessW`
+ {% from_libressl = (`sh -c 'hash pkg-config 2> /dev/null || printf %s false'` != "false") &&
+ (`sh -c 'test -f $(pkg-config --silence-errors --variable=includedir libssl)/openssl/opensslv.h || printf %s false'` != "false") &&
+ (`sh -c 'printf "#include \nLIBRESSL_VERSION_NUMBER" | ${CC:-cc} $(pkg-config --cflags --silence-errors libssl || true) -E -'`.chomp.split('\n').last != "LIBRESSL_VERSION_NUMBER") %}
+ {% ssl_version = `sh -c 'hash pkg-config 2> /dev/null && pkg-config --silence-errors --modversion libssl || printf %s 0.0.0'`.split.last.gsub(/[^0-9.]/, "") %}
{% end %}
{% if from_libressl %}
diff --git a/src/pointer.cr b/src/pointer.cr
index c3ebbf3e56fc..87da18b25fa5 100644
--- a/src/pointer.cr
+++ b/src/pointer.cr
@@ -52,6 +52,20 @@ struct Pointer(T)
def pointer
@pointer
end
+
+ # Creates a slice pointing at the values appended by this instance.
+ #
+ # ```
+ # slice = Slice(Int32).new(5)
+ # appender = slice.to_unsafe.appender
+ # appender << 1
+ # appender << 2
+ # appender << 3
+ # appender.to_slice # => Slice[1, 2, 3]
+ # ```
+ def to_slice : Slice(T)
+ @start.to_slice(size)
+ end
end
include Comparable(self)
@@ -420,6 +434,7 @@ struct Pointer(T)
# ptr = Pointer(Int32).new(5678)
# ptr.address # => 5678
# ```
+ @[Deprecated("Call `.new(UInt64)` directly instead")]
def self.new(address : Int)
new address.to_u64!
end
diff --git a/src/primitives.cr b/src/primitives.cr
index 9383ba642165..e033becdfbd2 100644
--- a/src/primitives.cr
+++ b/src/primitives.cr
@@ -206,12 +206,8 @@ struct Pointer(T)
# ```
#
# The implementation uses `GC.malloc` if the compiler is aware that the
- # allocated type contains inner address pointers. Otherwise it uses
- # `GC.malloc_atomic`. Primitive types are expected to not contain pointers,
- # except `Void`. `Proc` and `Pointer` are expected to contain pointers.
- # For unions, structs and collection types (tuples, static array)
- # it depends on the contained types. All other types, including classes are
- # expected to contain inner address pointers.
+ # allocated type contains inner address pointers. See
+ # `Crystal::Macros::TypeNode#has_inner_pointers?` for details.
#
# To override this implicit behaviour, `GC.malloc` and `GC.malloc_atomic`
# can be used directly instead.
diff --git a/src/proc.cr b/src/proc.cr
index fca714517dbf..69c0ebf5cd0e 100644
--- a/src/proc.cr
+++ b/src/proc.cr
@@ -3,7 +3,7 @@
#
# ```
# # A proc without arguments
-# ->{ 1 } # Proc(Int32)
+# -> { 1 } # Proc(Int32)
#
# # A proc with one argument
# ->(x : Int32) { x.to_s } # Proc(Int32, String)
diff --git a/src/process.cr b/src/process.cr
index c8364196373f..63b78bf0f716 100644
--- a/src/process.cr
+++ b/src/process.cr
@@ -291,33 +291,20 @@ class Process
private def stdio_to_fd(stdio : Stdio, for dst_io : IO::FileDescriptor) : IO::FileDescriptor
case stdio
- when IO::FileDescriptor
- stdio
- when IO
- if stdio.closed?
- if dst_io == STDIN
- return File.open(File::NULL, "r").tap(&.close)
- else
- return File.open(File::NULL, "w").tap(&.close)
+ in IO::FileDescriptor
+ # on Windows, only async pipes can be passed to child processes, async
+ # regular files will report an error and those require a separate pipe
+ # (https://github.com/crystal-lang/crystal/pull/13362#issuecomment-1519082712)
+ {% if flag?(:win32) %}
+ unless stdio.blocking || stdio.info.type.pipe?
+ return io_to_fd(stdio, for: dst_io)
end
- end
-
- if dst_io == STDIN
- fork_io, process_io = IO.pipe(read_blocking: true)
-
- @wait_count += 1
- ensure_channel
- spawn { copy_io(stdio, process_io, channel, close_dst: true) }
- else
- process_io, fork_io = IO.pipe(write_blocking: true)
+ {% end %}
- @wait_count += 1
- ensure_channel
- spawn { copy_io(process_io, stdio, channel, close_src: true) }
- end
-
- fork_io
- when Redirect::Pipe
+ stdio
+ in IO
+ io_to_fd(stdio, for: dst_io)
+ in Redirect::Pipe
case dst_io
when STDIN
fork_io, @input = IO.pipe(read_blocking: true)
@@ -330,17 +317,41 @@ class Process
end
fork_io
- when Redirect::Inherit
+ in Redirect::Inherit
dst_io
- when Redirect::Close
+ in Redirect::Close
if dst_io == STDIN
File.open(File::NULL, "r")
else
File.open(File::NULL, "w")
end
+ end
+ end
+
+ private def io_to_fd(stdio : Stdio, for dst_io : IO::FileDescriptor) : IO::FileDescriptor
+ if stdio.closed?
+ if dst_io == STDIN
+ return File.open(File::NULL, "r").tap(&.close)
+ else
+ return File.open(File::NULL, "w").tap(&.close)
+ end
+ end
+
+ if dst_io == STDIN
+ fork_io, process_io = IO.pipe(read_blocking: true)
+
+ @wait_count += 1
+ ensure_channel
+ spawn { copy_io(stdio, process_io, channel, close_dst: true) }
else
- raise "BUG: Impossible type in stdio #{stdio.class}"
+ process_io, fork_io = IO.pipe(write_blocking: true)
+
+ @wait_count += 1
+ ensure_channel
+ spawn { copy_io(process_io, stdio, channel, close_src: true) }
end
+
+ fork_io
end
# :nodoc:
diff --git a/src/raise.cr b/src/raise.cr
index ff8684795e77..0c9563495a94 100644
--- a/src/raise.cr
+++ b/src/raise.cr
@@ -91,7 +91,7 @@ end
{% if flag?(:interpreted) %}
# interpreter does not need `__crystal_personality`
-{% elsif flag?(:win32) %}
+{% elsif flag?(:win32) && !flag?(:gnu) %}
require "exception/lib_unwind"
{% begin %}
@@ -181,8 +181,10 @@ end
0u64
end
{% else %}
- # :nodoc:
- fun __crystal_personality(version : Int32, actions : LibUnwind::Action, exception_class : UInt64, exception_object : LibUnwind::Exception*, context : Void*) : LibUnwind::ReasonCode
+ {% mingw = flag?(:win32) && flag?(:gnu) %}
+ fun {{ mingw ? "__crystal_personality_imp".id : "__crystal_personality".id }}(
+ version : Int32, actions : LibUnwind::Action, exception_class : UInt64, exception_object : LibUnwind::Exception*, context : Void*,
+ ) : LibUnwind::ReasonCode
start = LibUnwind.get_region_start(context)
ip = LibUnwind.get_ip(context)
lsd = LibUnwind.get_language_specific_data(context)
@@ -197,9 +199,26 @@ end
return LibUnwind::ReasonCode::CONTINUE_UNWIND
end
+
+ {% if mingw %}
+ lib LibC
+ alias EXCEPTION_DISPOSITION = Int
+ alias DISPATCHER_CONTEXT = Void
+ end
+
+ lib LibUnwind
+ alias PersonalityFn = Int32, Action, UInt64, Exception*, Void* -> ReasonCode
+
+ fun _GCC_specific_handler(ms_exc : LibC::EXCEPTION_RECORD64*, this_frame : Void*, ms_orig_context : LibC::CONTEXT*, ms_disp : LibC::DISPATCHER_CONTEXT*, gcc_per : PersonalityFn) : LibC::EXCEPTION_DISPOSITION
+ end
+
+ fun __crystal_personality(ms_exc : LibC::EXCEPTION_RECORD64*, this_frame : Void*, ms_orig_context : LibC::CONTEXT*, ms_disp : LibC::DISPATCHER_CONTEXT*) : LibC::EXCEPTION_DISPOSITION
+ LibUnwind._GCC_specific_handler(ms_exc, this_frame, ms_orig_context, ms_disp, ->__crystal_personality_imp)
+ end
+ {% end %}
{% end %}
-{% unless flag?(:interpreted) || flag?(:win32) || flag?(:wasm32) %}
+{% unless flag?(:interpreted) || (flag?(:win32) && !flag?(:gnu)) || flag?(:wasm32) %}
# :nodoc:
@[Raises]
fun __crystal_raise(unwind_ex : LibUnwind::Exception*) : NoReturn
@@ -244,7 +263,7 @@ def raise(message : String) : NoReturn
raise Exception.new(message)
end
-{% if flag?(:win32) %}
+{% if flag?(:win32) && !flag?(:gnu) %}
# :nodoc:
{% if flag?(:interpreted) %} @[Primitive(:interpreter_raise_without_backtrace)] {% end %}
def raise_without_backtrace(exception : Exception) : NoReturn
diff --git a/src/random/isaac.cr b/src/random/isaac.cr
index c877cb9dbae9..294d439fb82d 100644
--- a/src/random/isaac.cr
+++ b/src/random/isaac.cr
@@ -61,7 +61,7 @@ class Random::ISAAC
a = b = c = d = e = f = g = h = 0x9e3779b9_u32
- mix = ->{
+ mix = -> {
a ^= b << 11; d &+= a; b &+= c
b ^= c >> 2; e &+= b; c &+= d
c ^= d << 8; f &+= c; d &+= e
diff --git a/src/random/secure.cr b/src/random/secure.cr
index 1722b5e6e884..a6b9df03063f 100644
--- a/src/random/secure.cr
+++ b/src/random/secure.cr
@@ -12,7 +12,7 @@ require "crystal/system/random"
# ```
#
# On BSD-based systems and macOS/Darwin, it uses [`arc4random`](https://man.openbsd.org/arc4random),
-# on Linux [`getrandom`](http://man7.org/linux/man-pages/man2/getrandom.2.html) (if the kernel supports it),
+# on Linux [`getrandom`](http://man7.org/linux/man-pages/man2/getrandom.2.html),
# on Windows [`RtlGenRandom`](https://docs.microsoft.com/en-us/windows/win32/api/ntsecapi/nf-ntsecapi-rtlgenrandom),
# and falls back to reading from `/dev/urandom` on UNIX systems.
module Random::Secure
diff --git a/src/range.cr b/src/range.cr
index 39d8119dff6e..e8ee24b190cb 100644
--- a/src/range.cr
+++ b/src/range.cr
@@ -480,7 +480,10 @@ struct Range(B, E)
# (3..8).size # => 6
# (3...8).size # => 5
# ```
- def size
+ #
+ # Raises `OverflowError` if the difference is bigger than `Int32`.
+ # Raises `ArgumentError` if either `begin` or `end` are `nil`.
+ def size : Int32
b = self.begin
e = self.end
@@ -488,7 +491,7 @@ struct Range(B, E)
if b.is_a?(Int) && e.is_a?(Int)
e -= 1 if @exclusive
n = e - b + 1
- n < 0 ? 0 : n
+ n < 0 ? 0 : n.to_i32
else
if b.nil? || e.nil?
raise ArgumentError.new("Can't calculate size of an open range")
diff --git a/src/regex.cr b/src/regex.cr
index 69dd500226a9..c71ac9cd673a 100644
--- a/src/regex.cr
+++ b/src/regex.cr
@@ -240,12 +240,17 @@ class Regex
# flag that activates both behaviours, so here we do the same by
# mapping `MULTILINE` to `PCRE_MULTILINE | PCRE_DOTALL`.
# The same applies for PCRE2 except that the native values are 0x200 and 0x400.
+ #
+ # For the behaviour of `PCRE_MULTILINE` use `MULTILINE_ONLY`.
# Multiline matching.
#
# Equivalent to `MULTILINE | DOTALL` in PCRE and PCRE2.
MULTILINE = 0x0000_0006
+ # Equivalent to `MULTILINE` in PCRE and PCRE2.
+ MULTILINE_ONLY = 0x0000_0004
+
DOTALL = 0x0000_0002
# Ignore white space and `#` comments.
diff --git a/src/regex/pcre.cr b/src/regex/pcre.cr
index e6cf6eaca7b0..19decbb66712 100644
--- a/src/regex/pcre.cr
+++ b/src/regex/pcre.cr
@@ -6,7 +6,7 @@ module Regex::PCRE
String.new(LibPCRE.version)
end
- class_getter version_number : {Int32, Int32} = begin
+ class_getter version_number : {Int32, Int32} do
version = self.version
dot = version.index('.') || raise RuntimeError.new("Invalid libpcre2 version")
space = version.index(' ', dot) || raise RuntimeError.new("Invalid libpcre2 version")
@@ -36,7 +36,8 @@ module Regex::PCRE
if options.includes?(option)
flag |= case option
when .ignore_case? then LibPCRE::CASELESS
- when .multiline? then LibPCRE::DOTALL | LibPCRE::MULTILINE
+ when .multiline? then LibPCRE::MULTILINE | LibPCRE::DOTALL
+ when .multiline_only? then LibPCRE::MULTILINE
when .dotall? then LibPCRE::DOTALL
when .extended? then LibPCRE::EXTENDED
when .anchored? then LibPCRE::ANCHORED
diff --git a/src/regex/pcre2.cr b/src/regex/pcre2.cr
index da811225842f..b56a4ea68839 100644
--- a/src/regex/pcre2.cr
+++ b/src/regex/pcre2.cr
@@ -13,7 +13,7 @@ module Regex::PCRE2
end
end
- class_getter version_number : {Int32, Int32} = begin
+ class_getter version_number : {Int32, Int32} do
version = self.version
dot = version.index('.') || raise RuntimeError.new("Invalid libpcre2 version")
space = version.index(' ', dot) || raise RuntimeError.new("Invalid libpcre2 version")
@@ -67,7 +67,8 @@ module Regex::PCRE2
if options.includes?(option)
flag |= case option
when .ignore_case? then LibPCRE2::CASELESS
- when .multiline? then LibPCRE2::DOTALL | LibPCRE2::MULTILINE
+ when .multiline? then LibPCRE2::MULTILINE | LibPCRE2::DOTALL
+ when .multiline_only? then LibPCRE2::MULTILINE
when .dotall? then LibPCRE2::DOTALL
when .extended? then LibPCRE2::EXTENDED
when .anchored? then LibPCRE2::ANCHORED
diff --git a/src/set.cr b/src/set.cr
index c998fab949a1..1bcc5178fbb0 100644
--- a/src/set.cr
+++ b/src/set.cr
@@ -73,7 +73,7 @@ struct Set(T)
self
end
- # Returns `true` of this Set is comparing objects by `object_id`.
+ # Returns `true` if this Set is comparing objects by `object_id`.
#
# See `compare_by_identity`.
def compare_by_identity? : Bool
diff --git a/src/signal.cr b/src/signal.cr
index e0f59a9f57d3..37999c76b9e1 100644
--- a/src/signal.cr
+++ b/src/signal.cr
@@ -8,17 +8,17 @@ require "crystal/system/signal"
#
# ```
# puts "Ctrl+C still has the OS default action (stops the program)"
-# sleep 3
+# sleep 3.seconds
#
# Signal::INT.trap do
# puts "Gotcha!"
# end
# puts "Ctrl+C will be caught from now on"
-# sleep 3
+# sleep 3.seconds
#
# Signal::INT.reset
# puts "Ctrl+C is back to the OS default action"
-# sleep 3
+# sleep 3.seconds
# ```
#
# WARNING: An uncaught exception in a signal handler is a fatal error.
diff --git a/src/slice.cr b/src/slice.cr
index 196a29a768dd..ace008e53e05 100644
--- a/src/slice.cr
+++ b/src/slice.cr
@@ -34,14 +34,14 @@ struct Slice(T)
macro [](*args, read_only = false)
# TODO: there should be a better way to check this, probably
# asking if @type was instantiated or if T is defined
- {% if @type.name != "Slice(T)" && T < Number %}
+ {% if @type.name != "Slice(T)" && T < ::Number %}
{{T}}.slice({{args.splat(", ")}}read_only: {{read_only}})
{% else %}
- %ptr = Pointer(typeof({{args.splat}})).malloc({{args.size}})
+ %ptr = ::Pointer(typeof({{args.splat}})).malloc({{args.size}})
{% for arg, i in args %}
%ptr[{{i}}] = {{arg}}
{% end %}
- Slice.new(%ptr, {{args.size}}, read_only: {{read_only}})
+ ::Slice.new(%ptr, {{args.size}}, read_only: {{read_only}})
{% end %}
end
@@ -222,35 +222,49 @@ struct Slice(T)
end
# Returns a new slice that starts at *start* elements from this slice's start,
- # and of *count* size.
+ # and of exactly *count* size.
#
+ # Negative *start* is added to `#size`, thus it's treated as index counting
+ # from the end of the array, `-1` designating the last element.
+ #
+ # Raises `ArgumentError` if *count* is negative.
# Returns `nil` if the new slice falls outside this slice.
#
# ```
# slice = Slice.new(5) { |i| i + 10 }
# slice # => Slice[10, 11, 12, 13, 14]
#
- # slice[1, 3]? # => Slice[11, 12, 13]
- # slice[1, 33]? # => nil
+ # slice[1, 3]? # => Slice[11, 12, 13]
+ # slice[1, 33]? # => nil
+ # slice[-3, 2]? # => Slice[12, 13]
+ # slice[-3, 10]? # => nil
# ```
def []?(start : Int, count : Int) : Slice(T)?
- return unless 0 <= start <= @size
- return unless 0 <= count <= @size - start
+ # we skip the calculated count because the subslice must contain exactly
+ # *count* elements
+ start, _ = Indexable.normalize_start_and_count(start, count, size) { return }
+ return unless count <= @size - start
Slice.new(@pointer + start, count, read_only: @read_only)
end
# Returns a new slice that starts at *start* elements from this slice's start,
- # and of *count* size.
+ # and of exactly *count* size.
+ #
+ # Negative *start* is added to `#size`, thus it's treated as index counting
+ # from the end of the array, `-1` designating the last element.
#
+ # Raises `ArgumentError` if *count* is negative.
# Raises `IndexError` if the new slice falls outside this slice.
#
# ```
# slice = Slice.new(5) { |i| i + 10 }
# slice # => Slice[10, 11, 12, 13, 14]
#
- # slice[1, 3] # => Slice[11, 12, 13]
- # slice[1, 33] # raises IndexError
+ # slice[1, 3] # => Slice[11, 12, 13]
+ # slice[1, 33] # raises IndexError
+ # slice[-3, 2] # => Slice[12, 13]
+ # slice[-3, 10] # raises IndexError
# ```
def [](start : Int, count : Int) : Slice(T)
self[start, count]? || raise IndexError.new
@@ -845,6 +859,21 @@ struct Slice(T)
{% end %}
end
+ # Returns `true` if `self` and *other* point to the same memory, i.e. pointer
+ # and size are identical.
+ #
+ # ```
+ # slice = Slice[1, 2, 3]
+ # slice.same?(slice) # => true
+ # slice == Slice[1, 2, 3] # => false
+ # slice.same?(slice + 1) # => false
+ # (slice + 1).same?(slice + 1) # => true
+ # slice.same?(slice[0, 2]) # => false
+ # ```
+ def same?(other : self) : Bool
+ to_unsafe == other.to_unsafe && size == other.size
+ end
+
def to_slice : self
self
end
@@ -981,13 +1010,23 @@ struct Slice(T)
# the result could also be `[b, a]`.
#
# If stability is expendable, `#unstable_sort!` provides a performance
- # advantage over stable sort.
+ # advantage over stable sort. As an optimization, if `T` is any primitive
+ # integer type, `Char`, any enum type, any `Pointer` instance, `Symbol`, or
+ # `Time::Span`, then an unstable sort is automatically used.
#
# Raises `ArgumentError` if the comparison between any two elements returns `nil`.
def sort! : self
- Slice.merge_sort!(self)
+ # If two values `x, y : T` have the same binary representation whenever they
+ # compare equal, i.e. `x <=> y == 0` implies
+ # `pointerof(x).memcmp(pointerof(y), 1) == 0`, then swapping the two values
+ # is a no-op and therefore a stable sort isn't required
+ {% if T.union_types.size == 1 && (T <= Int::Primitive || T <= Char || T <= Enum || T <= Pointer || T <= Symbol || T <= Time::Span) %}
+ unstable_sort!
+ {% else %}
+ Slice.merge_sort!(self)
- self
+ self
+ {% end %}
end
# Sorts all elements in `self` based on the return value of the comparison
diff --git a/src/socket.cr b/src/socket.cr
index ca484c0140cc..e97deea9eb04 100644
--- a/src/socket.cr
+++ b/src/socket.cr
@@ -419,10 +419,19 @@ class Socket < IO
self.class.fcntl fd, cmd, arg
end
+ # Finalizes the socket resource.
+ #
+ # This involves releasing the handle to the operating system, i.e. closing it.
+ # It does *not* implicitly call `#flush`, so data waiting in the buffer may be
+ # lost. By default write buffering is disabled, though (`sync? == true`).
+ # It's recommended to always close the socket explicitly via `#close`.
+ #
+ # This method is a no-op if the file descriptor has already been closed.
def finalize
return if closed?
- close rescue nil
+ event_loop?.try(&.remove(self))
+ socket_close { } # ignore error
end
def closed? : Bool
diff --git a/src/socket/addrinfo.cr b/src/socket/addrinfo.cr
index 83ef561c88ac..411c09143411 100644
--- a/src/socket/addrinfo.cr
+++ b/src/socket/addrinfo.cr
@@ -1,17 +1,30 @@
require "uri/punycode"
require "./address"
+require "crystal/system/addrinfo"
class Socket
# Domain name resolver.
+ #
+ # # Query Concurrency Behaviour
+ #
+ # On most platforms, DNS queries are currently resolved synchronously.
+ # Calling a resolve method blocks the entire thread until it returns.
+ # This can cause latencies, especially in single-threaded processes.
+ #
+ # DNS queries resolve asynchronously on the following platforms:
+ #
+ # * Windows 8 and higher
+ #
+ # NOTE: Follow the discussion in [Async DNS resolution (#13619)](https://github.com/crystal-lang/crystal/issues/13619)
+ # for more details.
struct Addrinfo
+ include Crystal::System::Addrinfo
+
getter family : Family
getter type : Type
getter protocol : Protocol
getter size : Int32
- @addr : LibC::SockaddrIn6
- @next : LibC::Addrinfo*
-
# Resolves a domain that best matches the given options.
#
# - *domain* may be an IP address or a domain name.
@@ -23,6 +36,9 @@ class Socket
# specified.
# - *protocol* is the intended socket protocol (e.g. `Protocol::TCP`) and
# should be specified.
+ # - *timeout* is optional and specifies the maximum time to wait before
+ # `IO::TimeoutError` is raised. Currently this is only supported on
+ # Windows.
#
# Example:
# ```
@@ -34,13 +50,10 @@ class Socket
addrinfos = [] of Addrinfo
getaddrinfo(domain, service, family, type, protocol, timeout) do |addrinfo|
- loop do
- addrinfos << addrinfo.not_nil!
- unless addrinfo = addrinfo.next?
- return addrinfos
- end
- end
+ addrinfos << addrinfo
end
+
+ addrinfos
end
# Resolves a domain that best matches the given options.
@@ -57,28 +70,29 @@ class Socket
# The iteration will be stopped once the block returns something that isn't
# an `Exception` (e.g. a `Socket` or `nil`).
def self.resolve(domain : String, service, family : Family? = nil, type : Type = nil, protocol : Protocol = Protocol::IP, timeout = nil, &)
- getaddrinfo(domain, service, family, type, protocol, timeout) do |addrinfo|
- loop do
- value = yield addrinfo.not_nil!
+ exception = nil
- if value.is_a?(Exception)
- unless addrinfo = addrinfo.try(&.next?)
- if value.is_a?(Socket::ConnectError)
- raise Socket::ConnectError.from_os_error("Error connecting to '#{domain}:#{service}'", value.os_error)
- else
- {% if flag?(:win32) && compare_versions(Crystal::LLVM_VERSION, "13.0.0") < 0 %}
- # FIXME: Workaround for https://github.com/crystal-lang/crystal/issues/11047
- array = StaticArray(UInt8, 0).new(0)
- {% end %}
+ getaddrinfo(domain, service, family, type, protocol, timeout) do |addrinfo|
+ value = yield addrinfo
- raise value
- end
- end
- else
- return value
- end
+ if value.is_a?(Exception)
+ exception = value
+ else
+ return value
end
end
+
+ case exception
+ when Socket::ConnectError
+ raise Socket::ConnectError.from_os_error("Error connecting to '#{domain}:#{service}'", exception.os_error)
+ when Exception
+ {% if flag?(:win32) && compare_versions(Crystal::LLVM_VERSION, "13.0.0") < 0 %}
+ # FIXME: Workaround for https://github.com/crystal-lang/crystal/issues/11047
+ array = StaticArray(UInt8, 0).new(0)
+ {% end %}
+
+ raise exception
+ end
end
class Error < Socket::Error
@@ -109,8 +123,11 @@ class Socket
"Hostname lookup for #{domain} failed"
end
- def self.os_error_message(os_error : Errno, *, type, service, protocol, **opts)
- case os_error.value
+ def self.os_error_message(os_error : Errno | WinError, *, type, service, protocol, **opts)
+ # when `EAI_NONAME` etc. is an integer then only `os_error.value` can
+ # match; when `EAI_NONAME` is a `WinError` then `os_error` itself can
+ # match
+ case os_error.is_a?(Errno) ? os_error.value : os_error
when LibC::EAI_NONAME
"No address found"
when LibC::EAI_SOCKTYPE
@@ -118,73 +135,28 @@ class Socket
when LibC::EAI_SERVICE
"The requested service #{service} is not available for the requested socket type #{type}"
else
- {% unless flag?(:win32) %}
- # There's no need for a special win32 branch because the os_error on Windows
- # is of type WinError, which wouldn't match this overload anyways.
-
- String.new(LibC.gai_strerror(os_error.value))
+ # Win32 also has this method, but `WinError` is already sufficient
+ {% if LibC.has_method?(:gai_strerror) %}
+ if os_error.is_a?(Errno)
+ return String.new(LibC.gai_strerror(os_error))
+ end
{% end %}
+
+ super
end
end
end
private def self.getaddrinfo(domain, service, family, type, protocol, timeout, &)
- {% if flag?(:wasm32) %}
- raise NotImplementedError.new "Socket::Addrinfo.getaddrinfo"
- {% else %}
- # RFC 3986 says:
- # > When a non-ASCII registered name represents an internationalized domain name
- # > intended for resolution via the DNS, the name must be transformed to the IDNA
- # > encoding [RFC3490] prior to name lookup.
- domain = URI::Punycode.to_ascii domain
-
- hints = LibC::Addrinfo.new
- hints.ai_family = (family || Family::UNSPEC).to_i32
- hints.ai_socktype = type
- hints.ai_protocol = protocol
- hints.ai_flags = 0
-
- if service.is_a?(Int)
- hints.ai_flags |= LibC::AI_NUMERICSERV
- end
-
- # On OS X < 10.12, the libsystem implementation of getaddrinfo segfaults
- # if AI_NUMERICSERV is set, and servname is NULL or 0.
- {% if flag?(:darwin) %}
- if service.in?(0, nil) && (hints.ai_flags & LibC::AI_NUMERICSERV)
- hints.ai_flags |= LibC::AI_NUMERICSERV
- service = "00"
- end
- {% end %}
- {% if flag?(:win32) %}
- if service.is_a?(Int) && service < 0
- raise Error.from_os_error(nil, WinError::WSATYPE_NOT_FOUND, domain: domain, type: type, protocol: protocol, service: service)
- end
- {% end %}
-
- ret = LibC.getaddrinfo(domain, service.to_s, pointerof(hints), out ptr)
- unless ret.zero?
- {% if flag?(:unix) %}
- # EAI_SYSTEM is not defined on win32
- if ret == LibC::EAI_SYSTEM
- raise Error.from_os_error nil, Errno.value, domain: domain
- end
- {% end %}
-
- error = {% if flag?(:win32) %}
- WinError.new(ret.to_u32!)
- {% else %}
- Errno.new(ret)
- {% end %}
- raise Error.from_os_error(nil, error, domain: domain, type: type, protocol: protocol, service: service)
- end
-
- begin
- yield new(ptr)
- ensure
- LibC.freeaddrinfo(ptr)
- end
- {% end %}
+ # RFC 3986 says:
+ # > When a non-ASCII registered name represents an internationalized domain name
+ # > intended for resolution via the DNS, the name must be transformed to the IDNA
+ # > encoding [RFC3490] prior to name lookup.
+ domain = URI::Punycode.to_ascii domain
+
+ Crystal::System::Addrinfo.getaddrinfo(domain, service, family, type, protocol, timeout) do |addrinfo|
+ yield addrinfo
+ end
end
# Resolves *domain* for the TCP protocol and returns an `Array` of possible
@@ -197,13 +169,13 @@ class Socket
# addrinfos = Socket::Addrinfo.tcp("example.org", 80)
# ```
def self.tcp(domain : String, service, family = Family::UNSPEC, timeout = nil) : Array(Addrinfo)
- resolve(domain, service, family, Type::STREAM, Protocol::TCP)
+ resolve(domain, service, family, Type::STREAM, Protocol::TCP, timeout)
end
# Resolves a domain for the TCP protocol with STREAM type, and yields each
# possible `Addrinfo`. See `#resolve` for details.
def self.tcp(domain : String, service, family = Family::UNSPEC, timeout = nil, &)
- resolve(domain, service, family, Type::STREAM, Protocol::TCP) { |addrinfo| yield addrinfo }
+ resolve(domain, service, family, Type::STREAM, Protocol::TCP, timeout) { |addrinfo| yield addrinfo }
end
# Resolves *domain* for the UDP protocol and returns an `Array` of possible
@@ -216,39 +188,18 @@ class Socket
# addrinfos = Socket::Addrinfo.udp("example.org", 53)
# ```
def self.udp(domain : String, service, family = Family::UNSPEC, timeout = nil) : Array(Addrinfo)
- resolve(domain, service, family, Type::DGRAM, Protocol::UDP)
+ resolve(domain, service, family, Type::DGRAM, Protocol::UDP, timeout)
end
# Resolves a domain for the UDP protocol with DGRAM type, and yields each
# possible `Addrinfo`. See `#resolve` for details.
def self.udp(domain : String, service, family = Family::UNSPEC, timeout = nil, &)
- resolve(domain, service, family, Type::DGRAM, Protocol::UDP) { |addrinfo| yield addrinfo }
- end
-
- protected def initialize(addrinfo : LibC::Addrinfo*)
- @family = Family.from_value(addrinfo.value.ai_family)
- @type = Type.from_value(addrinfo.value.ai_socktype)
- @protocol = Protocol.from_value(addrinfo.value.ai_protocol)
- @size = addrinfo.value.ai_addrlen.to_i
-
- @addr = uninitialized LibC::SockaddrIn6
- @next = addrinfo.value.ai_next
-
- case @family
- when Family::INET6
- addrinfo.value.ai_addr.as(LibC::SockaddrIn6*).copy_to(pointerof(@addr).as(LibC::SockaddrIn6*), 1)
- when Family::INET
- addrinfo.value.ai_addr.as(LibC::SockaddrIn*).copy_to(pointerof(@addr).as(LibC::SockaddrIn*), 1)
- else
- # TODO: (asterite) UNSPEC and UNIX unsupported?
- end
+ resolve(domain, service, family, Type::DGRAM, Protocol::UDP, timeout) { |addrinfo| yield addrinfo }
end
- @ip_address : IPAddress?
-
# Returns an `IPAddress` matching this addrinfo.
- def ip_address : Socket::IPAddress
- @ip_address ||= IPAddress.from(to_unsafe, size)
+ getter(ip_address : Socket::IPAddress) do
+ system_ip_address
end
def inspect(io : IO)
@@ -259,15 +210,5 @@ class Socket
io << protocol
io << ")"
end
-
- def to_unsafe
- pointerof(@addr).as(LibC::Sockaddr*)
- end
-
- protected def next?
- if addrinfo = @next
- Addrinfo.new(addrinfo)
- end
- end
end
end
diff --git a/src/socket/tcp_socket.cr b/src/socket/tcp_socket.cr
index 387417211a1a..4edcb3d08e5f 100644
--- a/src/socket/tcp_socket.cr
+++ b/src/socket/tcp_socket.cr
@@ -25,7 +25,7 @@ class TCPSocket < IPSocket
# connection time to the remote server with `connect_timeout`. Both values
# must be in seconds (integers or floats).
#
- # Note that `dns_timeout` is currently ignored.
+ # NOTE: *dns_timeout* is currently only supported on Windows.
def initialize(host : String, port, dns_timeout = nil, connect_timeout = nil, blocking = false)
Addrinfo.tcp(host, port, timeout: dns_timeout) do |addrinfo|
super(addrinfo.family, addrinfo.type, addrinfo.protocol, blocking)
diff --git a/src/socket/unix_socket.cr b/src/socket/unix_socket.cr
index 201fd8410bf7..d5ce5857c907 100644
--- a/src/socket/unix_socket.cr
+++ b/src/socket/unix_socket.cr
@@ -97,8 +97,8 @@ class UNIXSocket < Socket
UNIXAddress.new(path.to_s)
end
- def receive
- bytes_read, sockaddr, addrlen = recvfrom
- {bytes_read, UNIXAddress.from(sockaddr, addrlen)}
+ def receive(max_message_size = 512) : {String, UNIXAddress}
+ message, address = super(max_message_size)
+ {message, address.as(UNIXAddress)}
end
end
diff --git a/src/spec/dsl.cr b/src/spec/dsl.cr
index 578076b86d69..d712aa59da4f 100644
--- a/src/spec/dsl.cr
+++ b/src/spec/dsl.cr
@@ -298,8 +298,8 @@ module Spec
# If the "log" module is required it is configured to emit no entries by default.
def log_setup
defined?(::Log) do
- if Log.responds_to?(:setup)
- Log.setup_from_env(default_level: :none)
+ if ::Log.responds_to?(:setup)
+ ::Log.setup_from_env(default_level: :none)
end
end
end
diff --git a/src/spec/expectations.cr b/src/spec/expectations.cr
index ac93de54975e..f50658a5d787 100644
--- a/src/spec/expectations.cr
+++ b/src/spec/expectations.cr
@@ -65,11 +65,21 @@ module Spec
end
def failure_message(actual_value)
- "Expected: #{@expected_value.pretty_inspect} (object_id: #{@expected_value.object_id})\n got: #{actual_value.pretty_inspect} (object_id: #{actual_value.object_id})"
+ "Expected: #{@expected_value.pretty_inspect} (#{identify(@expected_value)})\n got: #{actual_value.pretty_inspect} (#{identify(actual_value)})"
end
def negative_failure_message(actual_value)
- "Expected: value.same? #{@expected_value.pretty_inspect} (object_id: #{@expected_value.object_id})\n got: #{actual_value.pretty_inspect} (object_id: #{actual_value.object_id})"
+ "Expected: #{@expected_value.pretty_inspect} (#{identify(@expected_value)})\n got: #{actual_value.pretty_inspect} (#{identify(actual_value)})"
+ end
+
+ private def identify(value)
+ if value.responds_to?(:to_unsafe)
+ if !value.responds_to?(:object_id)
+ return value.to_unsafe
+ end
+ end
+
+ "object_id: #{value.object_id}"
end
end
diff --git a/src/spec/helpers/iterate.cr b/src/spec/helpers/iterate.cr
index be302ebb49c2..7a70f83408ca 100644
--- a/src/spec/helpers/iterate.cr
+++ b/src/spec/helpers/iterate.cr
@@ -47,7 +47,7 @@ module Spec::Methods
# See `.it_iterates` for details.
macro assert_iterates_yielding(expected, method, *, infinite = false, tuple = false)
%remaining = ({{expected}}).size
- %ary = [] of typeof(Enumerable.element_type({{ expected }}))
+ %ary = [] of typeof(::Enumerable.element_type({{ expected }}))
{{ method.id }} do |{% if tuple %}*{% end %}x|
if %remaining == 0
if {{ infinite }}
@@ -73,11 +73,11 @@ module Spec::Methods
#
# See `.it_iterates` for details.
macro assert_iterates_iterator(expected, method, *, infinite = false)
- %ary = [] of typeof(Enumerable.element_type({{ expected }}))
+ %ary = [] of typeof(::Enumerable.element_type({{ expected }}))
%iter = {{ method.id }}
({{ expected }}).size.times do
%v = %iter.next
- if %v.is_a?(Iterator::Stop)
+ if %v.is_a?(::Iterator::Stop)
# Compare the actual value directly. Since there are less
# then expected values, the expectation will fail and raise.
%ary.should eq({{ expected }})
@@ -86,7 +86,7 @@ module Spec::Methods
%ary << %v
end
unless {{ infinite }}
- %iter.next.should be_a(Iterator::Stop)
+ %iter.next.should be_a(::Iterator::Stop)
end
%ary.should eq({{ expected }})
diff --git a/src/static_array.cr b/src/static_array.cr
index 2c09e21df166..3d00705bc21a 100644
--- a/src/static_array.cr
+++ b/src/static_array.cr
@@ -50,7 +50,7 @@ struct StaticArray(T, N)
# * `Number.static_array` is a convenient alternative for designating a
# specific numerical item type.
macro [](*args)
- %array = uninitialized StaticArray(typeof({{args.splat}}), {{args.size}})
+ %array = uninitialized ::StaticArray(typeof({{args.splat}}), {{args.size}})
{% for arg, i in args %}
%array.to_unsafe[{{i}}] = {{arg}}
{% end %}
diff --git a/src/string.cr b/src/string.cr
index d3bc7d6998b2..7507e3b7249e 100644
--- a/src/string.cr
+++ b/src/string.cr
@@ -752,7 +752,8 @@ class String
end
private def to_f_impl(whitespace : Bool = true, strict : Bool = true, &)
- return unless whitespace || '0' <= self[0] <= '9' || self[0].in?('-', '+', 'i', 'I', 'n', 'N')
+ return unless first_char = self[0]?
+ return unless whitespace || '0' <= first_char <= '9' || first_char.in?('-', '+', 'i', 'I', 'n', 'N')
v, endptr = yield
@@ -1506,15 +1507,17 @@ class String
end
end
- # Returns a new `String` with the first letter after any space converted to uppercase and every
- # other letter converted to lowercase.
+ # Returns a new `String` with the first letter after any space converted to uppercase and every other letter converted to lowercase.
+ # Optionally, if *underscore_to_space* is `true`, underscores (`_`) will be converted to a space and the following letter converted to uppercase.
#
# ```
- # "hEllO tAb\tworld".titleize # => "Hello Tab\tWorld"
- # " spaces before".titleize # => " Spaces Before"
- # "x-men: the last stand".titleize # => "X-men: The Last Stand"
+ # "hEllO tAb\tworld".titleize # => "Hello Tab\tWorld"
+ # " spaces before".titleize # => " Spaces Before"
+ # "x-men: the last stand".titleize # => "X-men: The Last Stand"
+ # "foo_bar".titleize # => "Foo_bar"
+ # "foo_bar".titleize(underscore_to_space: true) # => "Foo Bar"
# ```
- def titleize(options : Unicode::CaseOptions = :none) : String
+ def titleize(options : Unicode::CaseOptions = :none, *, underscore_to_space : Bool = false) : String
return self if empty?
if single_byte_optimizable? && (options.none? || options.ascii?)
@@ -1525,9 +1528,15 @@ class String
byte = to_unsafe[i]
if byte < 0x80
char = byte.unsafe_chr
- replaced_char = upcase_next ? char.upcase : char.downcase
+ replaced_char, upcase_next = if upcase_next
+ {char.upcase, false}
+ elsif underscore_to_space && '_' == char
+ {' ', true}
+ else
+ {char.downcase, char.ascii_whitespace?}
+ end
+
buffer[i] = replaced_char.ord.to_u8!
- upcase_next = char.ascii_whitespace?
else
buffer[i] = byte
upcase_next = false
@@ -1537,26 +1546,31 @@ class String
end
end
- String.build(bytesize) { |io| titleize io, options }
+ String.build(bytesize) { |io| titleize io, options, underscore_to_space: underscore_to_space }
end
# Writes a titleized version of `self` to the given *io*.
+ # Optionally, if *underscore_to_space* is `true`, underscores (`_`) will be converted to a space and the following letter converted to uppercase.
#
# ```
# io = IO::Memory.new
# "x-men: the last stand".titleize io
# io.to_s # => "X-men: The Last Stand"
# ```
- def titleize(io : IO, options : Unicode::CaseOptions = :none) : Nil
+ def titleize(io : IO, options : Unicode::CaseOptions = :none, *, underscore_to_space : Bool = false) : Nil
upcase_next = true
each_char_with_index do |char, i|
if upcase_next
+ upcase_next = false
char.titlecase(options) { |c| io << c }
+ elsif underscore_to_space && '_' == char
+ upcase_next = true
+ io << ' '
else
+ upcase_next = char.whitespace?
char.downcase(options) { |c| io << c }
end
- upcase_next = char.whitespace?
end
end
@@ -3335,11 +3349,21 @@ class String
def index(search : Char, offset = 0) : Int32?
# If it's ASCII we can delegate to slice
if single_byte_optimizable?
- # With `single_byte_optimizable?` there are only ASCII characters and invalid UTF-8 byte
- # sequences and we can immediately reject any non-ASCII codepoint.
- return unless search.ascii?
+ # With `single_byte_optimizable?` there are only ASCII characters and
+ # invalid UTF-8 byte sequences, and we can reject anything that is neither
+ # ASCII nor the replacement character.
+ case search
+ when .ascii?
+ return to_slice.fast_index(search.ord.to_u8!, offset)
+ when Char::REPLACEMENT
+ offset.upto(bytesize - 1) do |i|
+ if to_unsafe[i] >= 0x80
+ return i.to_i
+ end
+ end
+ end
- return to_slice.fast_index(search.ord.to_u8, offset)
+ return nil
end
offset += size if offset < 0
@@ -3449,17 +3473,27 @@ class String
# ```
# "Hello, World".rindex('o') # => 8
# "Hello, World".rindex('Z') # => nil
- # "Hello, World".rindex("o", 5) # => 4
- # "Hello, World".rindex("W", 2) # => nil
+ # "Hello, World".rindex('o', 5) # => 4
+ # "Hello, World".rindex('W', 2) # => nil
# ```
def rindex(search : Char, offset = size - 1)
# If it's ASCII we can delegate to slice
if single_byte_optimizable?
- # With `single_byte_optimizable?` there are only ASCII characters and invalid UTF-8 byte
- # sequences and we can immediately reject any non-ASCII codepoint.
- return unless search.ascii?
+ # With `single_byte_optimizable?` there are only ASCII characters and
+ # invalid UTF-8 byte sequences, and we can reject anything that is neither
+ # ASCII nor the replacement character.
+ case search
+ when .ascii?
+ return to_slice.rindex(search.ord.to_u8!, offset)
+ when Char::REPLACEMENT
+ offset.downto(0) do |i|
+ if to_unsafe[i] >= 0x80
+ return i.to_i
+ end
+ end
+ end
- return to_slice.rindex(search.ord.to_u8, offset)
+ return nil
end
offset += size if offset < 0
@@ -3485,7 +3519,16 @@ class String
end
end
- # :ditto:
+ # Returns the index of the _last_ appearance of *search* in the string,
+ # If *offset* is present, it defines the position to _end_ the search
+ # (characters beyond this point are ignored).
+ #
+ # ```
+ # "Hello, World".rindex("orld") # => 8
+ # "Hello, World".rindex("snorlax") # => nil
+ # "Hello, World".rindex("o", 5) # => 4
+ # "Hello, World".rindex("W", 2) # => nil
+ # ```
def rindex(search : String, offset = size - search.size) : Int32?
offset += size if offset < 0
return if offset < 0
@@ -3538,7 +3581,16 @@ class String
end
end
- # :ditto:
+ # Returns the index of the _last_ appearance of *search* in the string,
+ # If *offset* is present, it defines the position to _end_ the search
+ # (characters beyond this point are ignored).
+ #
+ # ```
+ # "Hello, World".rindex(/world/i) # => 7
+ # "Hello, World".rindex(/world/) # => nil
+ # "Hello, World".rindex(/o/, 5) # => 4
+ # "Hello, World".rindex(/W/, 2) # => nil
+ # ```
def rindex(search : Regex, offset = size, *, options : Regex::MatchOptions = Regex::MatchOptions::None) : Int32?
offset += size if offset < 0
return nil unless 0 <= offset <= size
@@ -3552,21 +3604,49 @@ class String
match_result.try &.begin
end
- # :ditto:
- #
+ # Returns the index of the _last_ appearance of *search* in the string,
+ # If *offset* is present, it defines the position to _end_ the search
+ # (characters beyond this point are ignored).
# Raises `Enumerable::NotFoundError` if *search* does not occur in `self`.
- def rindex!(search : Regex, offset = size, *, options : Regex::MatchOptions = Regex::MatchOptions::None) : Int32
- rindex(search, offset, options: options) || raise Enumerable::NotFoundError.new
+ #
+ # ```
+ # "Hello, World".rindex!('o') # => 8
+ # "Hello, World".rindex!('Z') # raises Enumerable::NotFoundError
+ # "Hello, World".rindex!('o', 5) # => 4
+ # "Hello, World".rindex!('W', 2) # raises Enumerable::NotFoundError
+ # ```
+ def rindex!(search : Char, offset = size - 1) : Int32
+ rindex(search, offset) || raise Enumerable::NotFoundError.new
end
- # :ditto:
+ # Returns the index of the _last_ appearance of *search* in the string,
+ # If *offset* is present, it defines the position to _end_ the search
+ # (characters beyond this point are ignored).
+ # Raises `Enumerable::NotFoundError` if *search* does not occur in `self`.
+ #
+ # ```
+ # "Hello, World".rindex!("orld") # => 8
+ # "Hello, World".rindex!("snorlax") # raises Enumerable::NotFoundError
+ # "Hello, World".rindex!("o", 5) # => 4
+ # "Hello, World".rindex!("W", 2) # raises Enumerable::NotFoundError
+ # ```
def rindex!(search : String, offset = size - search.size) : Int32
rindex(search, offset) || raise Enumerable::NotFoundError.new
end
- # :ditto:
- def rindex!(search : Char, offset = size - 1) : Int32
- rindex(search, offset) || raise Enumerable::NotFoundError.new
+ # Returns the index of the _last_ appearance of *search* in the string,
+ # If *offset* is present, it defines the position to _end_ the search
+ # (characters beyond this point are ignored).
+ # Raises `Enumerable::NotFoundError` if *search* does not occur in `self`.
+ #
+ # ```
+ # "Hello, World".rindex!(/world/i) # => 7
+ # "Hello, World".rindex!(/world/) # raises Enumerable::NotFoundError
+ # "Hello, World".rindex!(/o/, 5) # => 4
+ # "Hello, World".rindex!(/W/, 2) # raises Enumerable::NotFoundError
+ # ```
+ def rindex!(search : Regex, offset = size, *, options : Regex::MatchOptions = Regex::MatchOptions::None) : Int32
+ rindex(search, offset, options: options) || raise Enumerable::NotFoundError.new
end
# Searches separator or pattern (`Regex`) in the string, and returns
@@ -3681,7 +3761,7 @@ class String
# "Dizzy Miss Lizzy".byte_index('z'.ord, -4) # => 13
# "Dizzy Miss Lizzy".byte_index('z'.ord, -17) # => nil
# ```
- def byte_index(byte : Int, offset = 0) : Int32?
+ def byte_index(byte : Int, offset : Int32 = 0) : Int32?
offset += bytesize if offset < 0
return if offset < 0
diff --git a/src/string/grapheme/properties.cr b/src/string/grapheme/properties.cr
index 65b51fba0935..4d87254b7600 100644
--- a/src/string/grapheme/properties.cr
+++ b/src/string/grapheme/properties.cr
@@ -58,9 +58,9 @@ struct String::Grapheme
# ranges in this slice are numerically sorted.
#
# These ranges were taken from
- # http://www.unicode.org/Public/15.1.0/ucd/auxiliary/GraphemeBreakProperty.txt
+ # http://www.unicode.org/Public/16.0.0/ucd/auxiliary/GraphemeBreakProperty.txt
# as well as
- # http://www.unicode.org/Public/15.1.0/ucd/emoji/emoji-data.txt
+ # http://www.unicode.org/Public/16.0.0/ucd/emoji/emoji-data.txt
# ("Extended_Pictographic" only). See
# https://www.unicode.org/license.html for the Unicode license agreement.
@@codepoints : Array(Tuple(Int32, Int32, Property))?
@@ -68,7 +68,7 @@ struct String::Grapheme
# :nodoc:
protected def self.codepoints
@@codepoints ||= begin
- data = Array(Tuple(Int32, Int32, Property)).new(1447)
+ data = Array(Tuple(Int32, Int32, Property)).new(1452)
put(data, {0x0000, 0x0009, Property::Control})
put(data, {0x000A, 0x000A, Property::LF})
put(data, {0x000B, 0x000C, Property::Control})
@@ -105,7 +105,7 @@ struct String::Grapheme
put(data, {0x0829, 0x082D, Property::Extend})
put(data, {0x0859, 0x085B, Property::Extend})
put(data, {0x0890, 0x0891, Property::Prepend})
- put(data, {0x0898, 0x089F, Property::Extend})
+ put(data, {0x0897, 0x089F, Property::Extend})
put(data, {0x08CA, 0x08E1, Property::Extend})
put(data, {0x08E2, 0x08E2, Property::Prepend})
put(data, {0x08E3, 0x0902, Property::Extend})
@@ -187,14 +187,12 @@ struct String::Grapheme
put(data, {0x0C82, 0x0C83, Property::SpacingMark})
put(data, {0x0CBC, 0x0CBC, Property::Extend})
put(data, {0x0CBE, 0x0CBE, Property::SpacingMark})
- put(data, {0x0CBF, 0x0CBF, Property::Extend})
- put(data, {0x0CC0, 0x0CC1, Property::SpacingMark})
+ put(data, {0x0CBF, 0x0CC0, Property::Extend})
+ put(data, {0x0CC1, 0x0CC1, Property::SpacingMark})
put(data, {0x0CC2, 0x0CC2, Property::Extend})
put(data, {0x0CC3, 0x0CC4, Property::SpacingMark})
- put(data, {0x0CC6, 0x0CC6, Property::Extend})
- put(data, {0x0CC7, 0x0CC8, Property::SpacingMark})
- put(data, {0x0CCA, 0x0CCB, Property::SpacingMark})
- put(data, {0x0CCC, 0x0CCD, Property::Extend})
+ put(data, {0x0CC6, 0x0CC8, Property::Extend})
+ put(data, {0x0CCA, 0x0CCD, Property::Extend})
put(data, {0x0CD5, 0x0CD6, Property::Extend})
put(data, {0x0CE2, 0x0CE3, Property::Extend})
put(data, {0x0CF3, 0x0CF3, Property::SpacingMark})
@@ -259,10 +257,8 @@ struct String::Grapheme
put(data, {0x1160, 0x11A7, Property::V})
put(data, {0x11A8, 0x11FF, Property::T})
put(data, {0x135D, 0x135F, Property::Extend})
- put(data, {0x1712, 0x1714, Property::Extend})
- put(data, {0x1715, 0x1715, Property::SpacingMark})
- put(data, {0x1732, 0x1733, Property::Extend})
- put(data, {0x1734, 0x1734, Property::SpacingMark})
+ put(data, {0x1712, 0x1715, Property::Extend})
+ put(data, {0x1732, 0x1734, Property::Extend})
put(data, {0x1752, 0x1753, Property::Extend})
put(data, {0x1772, 0x1773, Property::Extend})
put(data, {0x17B4, 0x17B5, Property::Extend})
@@ -302,29 +298,23 @@ struct String::Grapheme
put(data, {0x1AB0, 0x1ACE, Property::Extend})
put(data, {0x1B00, 0x1B03, Property::Extend})
put(data, {0x1B04, 0x1B04, Property::SpacingMark})
- put(data, {0x1B34, 0x1B3A, Property::Extend})
- put(data, {0x1B3B, 0x1B3B, Property::SpacingMark})
- put(data, {0x1B3C, 0x1B3C, Property::Extend})
- put(data, {0x1B3D, 0x1B41, Property::SpacingMark})
- put(data, {0x1B42, 0x1B42, Property::Extend})
- put(data, {0x1B43, 0x1B44, Property::SpacingMark})
+ put(data, {0x1B34, 0x1B3D, Property::Extend})
+ put(data, {0x1B3E, 0x1B41, Property::SpacingMark})
+ put(data, {0x1B42, 0x1B44, Property::Extend})
put(data, {0x1B6B, 0x1B73, Property::Extend})
put(data, {0x1B80, 0x1B81, Property::Extend})
put(data, {0x1B82, 0x1B82, Property::SpacingMark})
put(data, {0x1BA1, 0x1BA1, Property::SpacingMark})
put(data, {0x1BA2, 0x1BA5, Property::Extend})
put(data, {0x1BA6, 0x1BA7, Property::SpacingMark})
- put(data, {0x1BA8, 0x1BA9, Property::Extend})
- put(data, {0x1BAA, 0x1BAA, Property::SpacingMark})
- put(data, {0x1BAB, 0x1BAD, Property::Extend})
+ put(data, {0x1BA8, 0x1BAD, Property::Extend})
put(data, {0x1BE6, 0x1BE6, Property::Extend})
put(data, {0x1BE7, 0x1BE7, Property::SpacingMark})
put(data, {0x1BE8, 0x1BE9, Property::Extend})
put(data, {0x1BEA, 0x1BEC, Property::SpacingMark})
put(data, {0x1BED, 0x1BED, Property::Extend})
put(data, {0x1BEE, 0x1BEE, Property::SpacingMark})
- put(data, {0x1BEF, 0x1BF1, Property::Extend})
- put(data, {0x1BF2, 0x1BF3, Property::SpacingMark})
+ put(data, {0x1BEF, 0x1BF3, Property::Extend})
put(data, {0x1C24, 0x1C2B, Property::SpacingMark})
put(data, {0x1C2C, 0x1C33, Property::Extend})
put(data, {0x1C34, 0x1C35, Property::SpacingMark})
@@ -416,7 +406,8 @@ struct String::Grapheme
put(data, {0xA8FF, 0xA8FF, Property::Extend})
put(data, {0xA926, 0xA92D, Property::Extend})
put(data, {0xA947, 0xA951, Property::Extend})
- put(data, {0xA952, 0xA953, Property::SpacingMark})
+ put(data, {0xA952, 0xA952, Property::SpacingMark})
+ put(data, {0xA953, 0xA953, Property::Extend})
put(data, {0xA960, 0xA97C, Property::L})
put(data, {0xA980, 0xA982, Property::Extend})
put(data, {0xA983, 0xA983, Property::SpacingMark})
@@ -425,7 +416,8 @@ struct String::Grapheme
put(data, {0xA9B6, 0xA9B9, Property::Extend})
put(data, {0xA9BA, 0xA9BB, Property::SpacingMark})
put(data, {0xA9BC, 0xA9BD, Property::Extend})
- put(data, {0xA9BE, 0xA9C0, Property::SpacingMark})
+ put(data, {0xA9BE, 0xA9BF, Property::SpacingMark})
+ put(data, {0xA9C0, 0xA9C0, Property::Extend})
put(data, {0xA9E5, 0xA9E5, Property::Extend})
put(data, {0xAA29, 0xAA2E, Property::Extend})
put(data, {0xAA2F, 0xAA30, Property::SpacingMark})
@@ -1269,8 +1261,9 @@ struct String::Grapheme
put(data, {0x10A3F, 0x10A3F, Property::Extend})
put(data, {0x10AE5, 0x10AE6, Property::Extend})
put(data, {0x10D24, 0x10D27, Property::Extend})
+ put(data, {0x10D69, 0x10D6D, Property::Extend})
put(data, {0x10EAB, 0x10EAC, Property::Extend})
- put(data, {0x10EFD, 0x10EFF, Property::Extend})
+ put(data, {0x10EFC, 0x10EFF, Property::Extend})
put(data, {0x10F46, 0x10F50, Property::Extend})
put(data, {0x10F82, 0x10F85, Property::Extend})
put(data, {0x11000, 0x11000, Property::SpacingMark})
@@ -1298,7 +1291,8 @@ struct String::Grapheme
put(data, {0x11182, 0x11182, Property::SpacingMark})
put(data, {0x111B3, 0x111B5, Property::SpacingMark})
put(data, {0x111B6, 0x111BE, Property::Extend})
- put(data, {0x111BF, 0x111C0, Property::SpacingMark})
+ put(data, {0x111BF, 0x111BF, Property::SpacingMark})
+ put(data, {0x111C0, 0x111C0, Property::Extend})
put(data, {0x111C2, 0x111C3, Property::Prepend})
put(data, {0x111C9, 0x111CC, Property::Extend})
put(data, {0x111CE, 0x111CE, Property::SpacingMark})
@@ -1306,9 +1300,7 @@ struct String::Grapheme
put(data, {0x1122C, 0x1122E, Property::SpacingMark})
put(data, {0x1122F, 0x11231, Property::Extend})
put(data, {0x11232, 0x11233, Property::SpacingMark})
- put(data, {0x11234, 0x11234, Property::Extend})
- put(data, {0x11235, 0x11235, Property::SpacingMark})
- put(data, {0x11236, 0x11237, Property::Extend})
+ put(data, {0x11234, 0x11237, Property::Extend})
put(data, {0x1123E, 0x1123E, Property::Extend})
put(data, {0x11241, 0x11241, Property::Extend})
put(data, {0x112DF, 0x112DF, Property::Extend})
@@ -1322,11 +1314,24 @@ struct String::Grapheme
put(data, {0x11340, 0x11340, Property::Extend})
put(data, {0x11341, 0x11344, Property::SpacingMark})
put(data, {0x11347, 0x11348, Property::SpacingMark})
- put(data, {0x1134B, 0x1134D, Property::SpacingMark})
+ put(data, {0x1134B, 0x1134C, Property::SpacingMark})
+ put(data, {0x1134D, 0x1134D, Property::Extend})
put(data, {0x11357, 0x11357, Property::Extend})
put(data, {0x11362, 0x11363, Property::SpacingMark})
put(data, {0x11366, 0x1136C, Property::Extend})
put(data, {0x11370, 0x11374, Property::Extend})
+ put(data, {0x113B8, 0x113B8, Property::Extend})
+ put(data, {0x113B9, 0x113BA, Property::SpacingMark})
+ put(data, {0x113BB, 0x113C0, Property::Extend})
+ put(data, {0x113C2, 0x113C2, Property::Extend})
+ put(data, {0x113C5, 0x113C5, Property::Extend})
+ put(data, {0x113C7, 0x113C9, Property::Extend})
+ put(data, {0x113CA, 0x113CA, Property::SpacingMark})
+ put(data, {0x113CC, 0x113CD, Property::SpacingMark})
+ put(data, {0x113CE, 0x113D0, Property::Extend})
+ put(data, {0x113D1, 0x113D1, Property::Prepend})
+ put(data, {0x113D2, 0x113D2, Property::Extend})
+ put(data, {0x113E1, 0x113E2, Property::Extend})
put(data, {0x11435, 0x11437, Property::SpacingMark})
put(data, {0x11438, 0x1143F, Property::Extend})
put(data, {0x11440, 0x11441, Property::SpacingMark})
@@ -1363,10 +1368,10 @@ struct String::Grapheme
put(data, {0x116AC, 0x116AC, Property::SpacingMark})
put(data, {0x116AD, 0x116AD, Property::Extend})
put(data, {0x116AE, 0x116AF, Property::SpacingMark})
- put(data, {0x116B0, 0x116B5, Property::Extend})
- put(data, {0x116B6, 0x116B6, Property::SpacingMark})
- put(data, {0x116B7, 0x116B7, Property::Extend})
- put(data, {0x1171D, 0x1171F, Property::Extend})
+ put(data, {0x116B0, 0x116B7, Property::Extend})
+ put(data, {0x1171D, 0x1171D, Property::Extend})
+ put(data, {0x1171E, 0x1171E, Property::SpacingMark})
+ put(data, {0x1171F, 0x1171F, Property::Extend})
put(data, {0x11722, 0x11725, Property::Extend})
put(data, {0x11726, 0x11726, Property::SpacingMark})
put(data, {0x11727, 0x1172B, Property::Extend})
@@ -1377,9 +1382,7 @@ struct String::Grapheme
put(data, {0x11930, 0x11930, Property::Extend})
put(data, {0x11931, 0x11935, Property::SpacingMark})
put(data, {0x11937, 0x11938, Property::SpacingMark})
- put(data, {0x1193B, 0x1193C, Property::Extend})
- put(data, {0x1193D, 0x1193D, Property::SpacingMark})
- put(data, {0x1193E, 0x1193E, Property::Extend})
+ put(data, {0x1193B, 0x1193E, Property::Extend})
put(data, {0x1193F, 0x1193F, Property::Prepend})
put(data, {0x11940, 0x11940, Property::SpacingMark})
put(data, {0x11941, 0x11941, Property::Prepend})
@@ -1436,28 +1439,29 @@ struct String::Grapheme
put(data, {0x11F34, 0x11F35, Property::SpacingMark})
put(data, {0x11F36, 0x11F3A, Property::Extend})
put(data, {0x11F3E, 0x11F3F, Property::SpacingMark})
- put(data, {0x11F40, 0x11F40, Property::Extend})
- put(data, {0x11F41, 0x11F41, Property::SpacingMark})
- put(data, {0x11F42, 0x11F42, Property::Extend})
+ put(data, {0x11F40, 0x11F42, Property::Extend})
+ put(data, {0x11F5A, 0x11F5A, Property::Extend})
put(data, {0x13430, 0x1343F, Property::Control})
put(data, {0x13440, 0x13440, Property::Extend})
put(data, {0x13447, 0x13455, Property::Extend})
+ put(data, {0x1611E, 0x16129, Property::Extend})
+ put(data, {0x1612A, 0x1612C, Property::SpacingMark})
+ put(data, {0x1612D, 0x1612F, Property::Extend})
put(data, {0x16AF0, 0x16AF4, Property::Extend})
put(data, {0x16B30, 0x16B36, Property::Extend})
+ put(data, {0x16D63, 0x16D63, Property::V})
+ put(data, {0x16D67, 0x16D6A, Property::V})
put(data, {0x16F4F, 0x16F4F, Property::Extend})
put(data, {0x16F51, 0x16F87, Property::SpacingMark})
put(data, {0x16F8F, 0x16F92, Property::Extend})
put(data, {0x16FE4, 0x16FE4, Property::Extend})
- put(data, {0x16FF0, 0x16FF1, Property::SpacingMark})
+ put(data, {0x16FF0, 0x16FF1, Property::Extend})
put(data, {0x1BC9D, 0x1BC9E, Property::Extend})
put(data, {0x1BCA0, 0x1BCA3, Property::Control})
put(data, {0x1CF00, 0x1CF2D, Property::Extend})
put(data, {0x1CF30, 0x1CF46, Property::Extend})
- put(data, {0x1D165, 0x1D165, Property::Extend})
- put(data, {0x1D166, 0x1D166, Property::SpacingMark})
- put(data, {0x1D167, 0x1D169, Property::Extend})
- put(data, {0x1D16D, 0x1D16D, Property::SpacingMark})
- put(data, {0x1D16E, 0x1D172, Property::Extend})
+ put(data, {0x1D165, 0x1D169, Property::Extend})
+ put(data, {0x1D16D, 0x1D172, Property::Extend})
put(data, {0x1D173, 0x1D17A, Property::Control})
put(data, {0x1D17B, 0x1D182, Property::Extend})
put(data, {0x1D185, 0x1D18B, Property::Extend})
@@ -1479,6 +1483,7 @@ struct String::Grapheme
put(data, {0x1E2AE, 0x1E2AE, Property::Extend})
put(data, {0x1E2EC, 0x1E2EF, Property::Extend})
put(data, {0x1E4EC, 0x1E4EF, Property::Extend})
+ put(data, {0x1E5EE, 0x1E5EF, Property::Extend})
put(data, {0x1E8D0, 0x1E8D6, Property::Extend})
put(data, {0x1E944, 0x1E94A, Property::Extend})
put(data, {0x1F000, 0x1F0FF, Property::ExtendedPictographic})
diff --git a/src/syscall/aarch64-linux.cr b/src/syscall/aarch64-linux.cr
index 5a61e8e7eed8..77b891fe2a7c 100644
--- a/src/syscall/aarch64-linux.cr
+++ b/src/syscall/aarch64-linux.cr
@@ -334,7 +334,7 @@ module Syscall
end
macro def_syscall(name, return_type, *args)
- @[AlwaysInline]
+ @[::AlwaysInline]
def self.{{name.id}}({{args.splat}}) : {{return_type}}
ret = uninitialized {{return_type}}
diff --git a/src/syscall/arm-linux.cr b/src/syscall/arm-linux.cr
index 97119fc4b3f3..da349dd45301 100644
--- a/src/syscall/arm-linux.cr
+++ b/src/syscall/arm-linux.cr
@@ -409,7 +409,7 @@ module Syscall
end
macro def_syscall(name, return_type, *args)
- @[AlwaysInline]
+ @[::AlwaysInline]
def self.{{name.id}}({{args.splat}}) : {{return_type}}
ret = uninitialized {{return_type}}
diff --git a/src/syscall/i386-linux.cr b/src/syscall/i386-linux.cr
index 843b2d1fd856..a0f94a51160a 100644
--- a/src/syscall/i386-linux.cr
+++ b/src/syscall/i386-linux.cr
@@ -445,7 +445,7 @@ module Syscall
end
macro def_syscall(name, return_type, *args)
- @[AlwaysInline]
+ @[::AlwaysInline]
def self.{{name.id}}({{args.splat}}) : {{return_type}}
ret = uninitialized {{return_type}}
diff --git a/src/syscall/x86_64-linux.cr b/src/syscall/x86_64-linux.cr
index 1f01c9226658..5a63b6ee2e1a 100644
--- a/src/syscall/x86_64-linux.cr
+++ b/src/syscall/x86_64-linux.cr
@@ -368,7 +368,7 @@ module Syscall
end
macro def_syscall(name, return_type, *args)
- @[AlwaysInline]
+ @[::AlwaysInline]
def self.{{name.id}}({{args.splat}}) : {{return_type}}
ret = uninitialized {{return_type}}
diff --git a/src/system/group.cr b/src/system/group.cr
index bd992e6af19d..47b9768cca52 100644
--- a/src/system/group.cr
+++ b/src/system/group.cr
@@ -17,19 +17,20 @@ class System::Group
class NotFoundError < Exception
end
- extend Crystal::System::Group
+ include Crystal::System::Group
# The group's name.
- getter name : String
+ def name : String
+ system_name
+ end
# The group's identifier.
- getter id : String
-
- def_equals_and_hash @id
-
- private def initialize(@name, @id)
+ def id : String
+ system_id
end
+ def_equals_and_hash id
+
# Returns the group associated with the given name.
#
# Raises `NotFoundError` if no such group exists.
@@ -41,7 +42,7 @@ class System::Group
#
# Returns `nil` if no such group exists.
def self.find_by?(*, name : String) : System::Group?
- from_name?(name)
+ Crystal::System::Group.from_name?(name)
end
# Returns the group associated with the given ID.
@@ -55,7 +56,7 @@ class System::Group
#
# Returns `nil` if no such group exists.
def self.find_by?(*, id : String) : System::Group?
- from_id?(id)
+ Crystal::System::Group.from_id?(id)
end
def to_s(io)
diff --git a/src/system/user.cr b/src/system/user.cr
index 7d6c250689da..01c8d11d9e1c 100644
--- a/src/system/user.cr
+++ b/src/system/user.cr
@@ -17,34 +17,43 @@ class System::User
class NotFoundError < Exception
end
- extend Crystal::System::User
+ include Crystal::System::User
# The user's username.
- getter username : String
+ def username : String
+ system_username
+ end
# The user's identifier.
- getter id : String
+ def id : String
+ system_id
+ end
# The user's primary group identifier.
- getter group_id : String
+ def group_id : String
+ system_group_id
+ end
# The user's real or full name.
#
# May not be present on all platforms. Returns the same value as `#username`
# if neither a real nor full name is available.
- getter name : String
+ def name : String
+ system_name
+ end
# The user's home directory.
- getter home_directory : String
+ def home_directory : String
+ system_home_directory
+ end
# The user's login shell.
- getter shell : String
-
- def_equals_and_hash @id
-
- private def initialize(@username, @id, @group_id, @name, @home_directory, @shell)
+ def shell : String
+ system_shell
end
+ def_equals_and_hash id
+
# Returns the user associated with the given username.
#
# Raises `NotFoundError` if no such user exists.
@@ -56,7 +65,7 @@ class System::User
#
# Returns `nil` if no such user exists.
def self.find_by?(*, name : String) : System::User?
- from_username?(name)
+ Crystal::System::User.from_username?(name)
end
# Returns the user associated with the given ID.
@@ -70,7 +79,7 @@ class System::User
#
# Returns `nil` if no such user exists.
def self.find_by?(*, id : String) : System::User?
- from_id?(id)
+ Crystal::System::User.from_id?(id)
end
def to_s(io)
diff --git a/src/unicode/data.cr b/src/unicode/data.cr
index a02db251d0c8..ccb7d702e892 100644
--- a/src/unicode/data.cr
+++ b/src/unicode/data.cr
@@ -8,7 +8,7 @@ module Unicode
# Most case conversions map a range to another range.
# Here we store: {from, to, delta}
private class_getter upcase_ranges : Array({Int32, Int32, Int32}) do
- data = Array({Int32, Int32, Int32}).new(141)
+ data = Array({Int32, Int32, Int32}).new(144)
put(data, 97, 122, -32)
put(data, 181, 181, 743)
put(data, 224, 246, -32)
@@ -19,6 +19,7 @@ module Unicode
put(data, 384, 384, 195)
put(data, 405, 405, 97)
put(data, 410, 410, 163)
+ put(data, 411, 411, 42561)
put(data, 414, 414, 130)
put(data, 447, 447, 56)
put(data, 454, 454, -2)
@@ -39,6 +40,7 @@ module Unicode
put(data, 608, 608, -205)
put(data, 609, 609, 42315)
put(data, 611, 611, -207)
+ put(data, 612, 612, 42343)
put(data, 613, 613, 42280)
put(data, 614, 614, 42308)
put(data, 616, 616, -209)
@@ -147,6 +149,7 @@ module Unicode
put(data, 66995, 67001, -39)
put(data, 67003, 67004, -39)
put(data, 68800, 68850, -64)
+ put(data, 68976, 68997, -32)
put(data, 71872, 71903, -32)
put(data, 93792, 93823, -32)
put(data, 125218, 125251, -34)
@@ -156,7 +159,7 @@ module Unicode
# Most case conversions map a range to another range.
# Here we store: {from, to, delta}
private class_getter downcase_ranges : Array({Int32, Int32, Int32}) do
- data = Array({Int32, Int32, Int32}).new(125)
+ data = Array({Int32, Int32, Int32}).new(128)
put(data, 65, 90, 32)
put(data, 192, 214, 32)
put(data, 216, 222, 32)
@@ -271,6 +274,8 @@ module Unicode
put(data, 42948, 42948, -48)
put(data, 42949, 42949, -42307)
put(data, 42950, 42950, -35384)
+ put(data, 42955, 42955, -42343)
+ put(data, 42972, 42972, -42561)
put(data, 65313, 65338, 32)
put(data, 66560, 66599, 40)
put(data, 66736, 66771, 40)
@@ -279,6 +284,7 @@ module Unicode
put(data, 66956, 66962, 39)
put(data, 66964, 66965, 39)
put(data, 68736, 68786, 64)
+ put(data, 68944, 68965, 32)
put(data, 71840, 71871, 32)
put(data, 93760, 93791, 32)
put(data, 125184, 125217, 34)
@@ -289,7 +295,7 @@ module Unicode
# of uppercase/lowercase transformations
# Here we store {from, to}
private class_getter alternate_ranges : Array({Int32, Int32}) do
- data = Array({Int32, Int32}).new(60)
+ data = Array({Int32, Int32}).new(62)
put(data, 256, 303)
put(data, 306, 311)
put(data, 313, 328)
@@ -326,6 +332,7 @@ module Unicode
put(data, 1162, 1215)
put(data, 1217, 1230)
put(data, 1232, 1327)
+ put(data, 7305, 7306)
put(data, 7680, 7829)
put(data, 7840, 7935)
put(data, 8579, 8580)
@@ -347,8 +354,9 @@ module Unicode
put(data, 42902, 42921)
put(data, 42932, 42947)
put(data, 42951, 42954)
+ put(data, 42956, 42957)
put(data, 42960, 42961)
- put(data, 42966, 42969)
+ put(data, 42966, 42971)
put(data, 42997, 42998)
data
end
@@ -363,7 +371,7 @@ module Unicode
# The values are: 1..10, 11, 13, 15
private class_getter category_Lu : Array({Int32, Int32, Int32}) do
- data = Array({Int32, Int32, Int32}).new(149)
+ data = Array({Int32, Int32, Int32}).new(152)
put(data, 65, 90, 1)
put(data, 192, 214, 1)
put(data, 216, 222, 1)
@@ -420,7 +428,8 @@ module Unicode
put(data, 4256, 4293, 1)
put(data, 4295, 4301, 6)
put(data, 5024, 5109, 1)
- put(data, 7312, 7354, 1)
+ put(data, 7305, 7312, 7)
+ put(data, 7313, 7354, 1)
put(data, 7357, 7359, 1)
put(data, 7680, 7828, 2)
put(data, 7838, 7934, 2)
@@ -469,8 +478,9 @@ module Unicode
put(data, 42928, 42932, 1)
put(data, 42934, 42948, 2)
put(data, 42949, 42951, 1)
- put(data, 42953, 42960, 7)
- put(data, 42966, 42968, 2)
+ put(data, 42953, 42955, 2)
+ put(data, 42956, 42960, 4)
+ put(data, 42966, 42972, 2)
put(data, 42997, 65313, 22316)
put(data, 65314, 65338, 1)
put(data, 66560, 66599, 1)
@@ -480,6 +490,7 @@ module Unicode
put(data, 66956, 66962, 1)
put(data, 66964, 66965, 1)
put(data, 68736, 68786, 1)
+ put(data, 68944, 68965, 1)
put(data, 71840, 71871, 1)
put(data, 93760, 93791, 1)
put(data, 119808, 119833, 1)
@@ -516,7 +527,7 @@ module Unicode
data
end
private class_getter category_Ll : Array({Int32, Int32, Int32}) do
- data = Array({Int32, Int32, Int32}).new(163)
+ data = Array({Int32, Int32, Int32}).new(166)
put(data, 97, 122, 1)
put(data, 181, 223, 42)
put(data, 224, 246, 1)
@@ -572,7 +583,8 @@ module Unicode
put(data, 4349, 4351, 1)
put(data, 5112, 5117, 1)
put(data, 7296, 7304, 1)
- put(data, 7424, 7467, 1)
+ put(data, 7306, 7424, 118)
+ put(data, 7425, 7467, 1)
put(data, 7531, 7543, 1)
put(data, 7545, 7578, 1)
put(data, 7681, 7829, 2)
@@ -631,7 +643,8 @@ module Unicode
put(data, 42927, 42933, 6)
put(data, 42935, 42947, 2)
put(data, 42952, 42954, 2)
- put(data, 42961, 42969, 2)
+ put(data, 42957, 42961, 4)
+ put(data, 42963, 42971, 2)
put(data, 42998, 43002, 4)
put(data, 43824, 43866, 1)
put(data, 43872, 43880, 1)
@@ -646,6 +659,7 @@ module Unicode
put(data, 66995, 67001, 1)
put(data, 67003, 67004, 1)
put(data, 68800, 68850, 1)
+ put(data, 68976, 68997, 1)
put(data, 71872, 71903, 1)
put(data, 93792, 93823, 1)
put(data, 119834, 119859, 1)
@@ -694,7 +708,7 @@ module Unicode
data
end
private class_getter category_Lm : Array({Int32, Int32, Int32}) do
- data = Array({Int32, Int32, Int32}).new(54)
+ data = Array({Int32, Int32, Int32}).new(57)
put(data, 688, 705, 1)
put(data, 710, 721, 1)
put(data, 736, 740, 1)
@@ -739,7 +753,10 @@ module Unicode
put(data, 67456, 67461, 1)
put(data, 67463, 67504, 1)
put(data, 67506, 67514, 1)
+ put(data, 68942, 68975, 33)
put(data, 92992, 92995, 1)
+ put(data, 93504, 93506, 1)
+ put(data, 93547, 93548, 1)
put(data, 94099, 94111, 1)
put(data, 94176, 94177, 1)
put(data, 94179, 110576, 16397)
@@ -752,7 +769,7 @@ module Unicode
data
end
private class_getter category_Lo : Array({Int32, Int32, Int32}) do
- data = Array({Int32, Int32, Int32}).new(486)
+ data = Array({Int32, Int32, Int32}).new(502)
put(data, 170, 186, 16)
put(data, 443, 448, 5)
put(data, 449, 451, 1)
@@ -1052,6 +1069,7 @@ module Unicode
put(data, 66640, 66717, 1)
put(data, 66816, 66855, 1)
put(data, 66864, 66915, 1)
+ put(data, 67008, 67059, 1)
put(data, 67072, 67382, 1)
put(data, 67392, 67413, 1)
put(data, 67424, 67431, 1)
@@ -1083,8 +1101,11 @@ module Unicode
put(data, 68480, 68497, 1)
put(data, 68608, 68680, 1)
put(data, 68864, 68899, 1)
- put(data, 69248, 69289, 1)
+ put(data, 68938, 68941, 1)
+ put(data, 68943, 69248, 305)
+ put(data, 69249, 69289, 1)
put(data, 69296, 69297, 1)
+ put(data, 69314, 69316, 1)
put(data, 69376, 69404, 1)
put(data, 69415, 69424, 9)
put(data, 69425, 69445, 1)
@@ -1120,7 +1141,12 @@ module Unicode
put(data, 70453, 70457, 1)
put(data, 70461, 70480, 19)
put(data, 70493, 70497, 1)
- put(data, 70656, 70708, 1)
+ put(data, 70528, 70537, 1)
+ put(data, 70539, 70542, 3)
+ put(data, 70544, 70581, 1)
+ put(data, 70583, 70609, 26)
+ put(data, 70611, 70656, 45)
+ put(data, 70657, 70708, 1)
put(data, 70727, 70730, 1)
put(data, 70751, 70753, 1)
put(data, 70784, 70831, 1)
@@ -1150,6 +1176,7 @@ module Unicode
put(data, 72284, 72329, 1)
put(data, 72349, 72368, 19)
put(data, 72369, 72440, 1)
+ put(data, 72640, 72672, 1)
put(data, 72704, 72712, 1)
put(data, 72714, 72750, 1)
put(data, 72768, 72818, 50)
@@ -1172,7 +1199,9 @@ module Unicode
put(data, 77712, 77808, 1)
put(data, 77824, 78895, 1)
put(data, 78913, 78918, 1)
+ put(data, 78944, 82938, 1)
put(data, 82944, 83526, 1)
+ put(data, 90368, 90397, 1)
put(data, 92160, 92728, 1)
put(data, 92736, 92766, 1)
put(data, 92784, 92862, 1)
@@ -1180,12 +1209,14 @@ module Unicode
put(data, 92928, 92975, 1)
put(data, 93027, 93047, 1)
put(data, 93053, 93071, 1)
+ put(data, 93507, 93546, 1)
put(data, 93952, 94026, 1)
put(data, 94032, 94208, 176)
put(data, 100343, 100352, 9)
put(data, 100353, 101589, 1)
- put(data, 101632, 101640, 1)
- put(data, 110592, 110882, 1)
+ put(data, 101631, 101632, 1)
+ put(data, 101640, 110592, 8952)
+ put(data, 110593, 110882, 1)
put(data, 110898, 110928, 30)
put(data, 110929, 110930, 1)
put(data, 110933, 110948, 15)
@@ -1201,7 +1232,9 @@ module Unicode
put(data, 123537, 123565, 1)
put(data, 123584, 123627, 1)
put(data, 124112, 124138, 1)
- put(data, 124896, 124902, 1)
+ put(data, 124368, 124397, 1)
+ put(data, 124400, 124896, 496)
+ put(data, 124897, 124902, 1)
put(data, 124904, 124907, 1)
put(data, 124909, 124910, 1)
put(data, 124912, 124926, 1)
@@ -1242,7 +1275,7 @@ module Unicode
data
end
private class_getter category_Mn : Array({Int32, Int32, Int32}) do
- data = Array({Int32, Int32, Int32}).new(308)
+ data = Array({Int32, Int32, Int32}).new(315)
put(data, 768, 879, 1)
put(data, 1155, 1159, 1)
put(data, 1425, 1469, 1)
@@ -1266,7 +1299,7 @@ module Unicode
put(data, 2085, 2087, 1)
put(data, 2089, 2093, 1)
put(data, 2137, 2139, 1)
- put(data, 2200, 2207, 1)
+ put(data, 2199, 2207, 1)
put(data, 2250, 2273, 1)
put(data, 2275, 2306, 1)
put(data, 2362, 2364, 2)
@@ -1435,8 +1468,9 @@ module Unicode
put(data, 68159, 68325, 166)
put(data, 68326, 68900, 574)
put(data, 68901, 68903, 1)
+ put(data, 68969, 68973, 1)
put(data, 69291, 69292, 1)
- put(data, 69373, 69375, 1)
+ put(data, 69372, 69375, 1)
put(data, 69446, 69456, 1)
put(data, 69506, 69509, 1)
put(data, 69633, 69688, 55)
@@ -1465,6 +1499,9 @@ module Unicode
put(data, 70464, 70502, 38)
put(data, 70503, 70508, 1)
put(data, 70512, 70516, 1)
+ put(data, 70587, 70592, 1)
+ put(data, 70606, 70610, 2)
+ put(data, 70625, 70626, 1)
put(data, 70712, 70719, 1)
put(data, 70722, 70724, 1)
put(data, 70726, 70750, 24)
@@ -1482,8 +1519,8 @@ module Unicode
put(data, 71341, 71344, 3)
put(data, 71345, 71349, 1)
put(data, 71351, 71453, 102)
- put(data, 71454, 71455, 1)
- put(data, 71458, 71461, 1)
+ put(data, 71455, 71458, 3)
+ put(data, 71459, 71461, 1)
put(data, 71463, 71467, 1)
put(data, 71727, 71735, 1)
put(data, 71737, 71738, 1)
@@ -1518,8 +1555,10 @@ module Unicode
put(data, 73473, 73526, 53)
put(data, 73527, 73530, 1)
put(data, 73536, 73538, 2)
- put(data, 78912, 78919, 7)
- put(data, 78920, 78933, 1)
+ put(data, 73562, 78912, 5350)
+ put(data, 78919, 78933, 1)
+ put(data, 90398, 90409, 1)
+ put(data, 90413, 90415, 1)
put(data, 92912, 92916, 1)
put(data, 92976, 92982, 1)
put(data, 94031, 94095, 64)
@@ -1548,13 +1587,14 @@ module Unicode
put(data, 123566, 123628, 62)
put(data, 123629, 123631, 1)
put(data, 124140, 124143, 1)
+ put(data, 124398, 124399, 1)
put(data, 125136, 125142, 1)
put(data, 125252, 125258, 1)
put(data, 917760, 917999, 1)
data
end
private class_getter category_Mc : Array({Int32, Int32, Int32}) do
- data = Array({Int32, Int32, Int32}).new(158)
+ data = Array({Int32, Int32, Int32}).new(165)
put(data, 2307, 2363, 56)
put(data, 2366, 2368, 1)
put(data, 2377, 2380, 1)
@@ -1672,7 +1712,12 @@ module Unicode
put(data, 70471, 70472, 1)
put(data, 70475, 70477, 1)
put(data, 70487, 70498, 11)
- put(data, 70499, 70709, 210)
+ put(data, 70499, 70584, 85)
+ put(data, 70585, 70586, 1)
+ put(data, 70594, 70597, 3)
+ put(data, 70599, 70602, 1)
+ put(data, 70604, 70605, 1)
+ put(data, 70607, 70709, 102)
put(data, 70710, 70711, 1)
put(data, 70720, 70721, 1)
put(data, 70725, 70832, 107)
@@ -1687,9 +1732,10 @@ module Unicode
put(data, 71227, 71228, 1)
put(data, 71230, 71340, 110)
put(data, 71342, 71343, 1)
- put(data, 71350, 71456, 106)
- put(data, 71457, 71462, 5)
- put(data, 71724, 71726, 1)
+ put(data, 71350, 71454, 104)
+ put(data, 71456, 71457, 1)
+ put(data, 71462, 71724, 262)
+ put(data, 71725, 71726, 1)
put(data, 71736, 71984, 248)
put(data, 71985, 71989, 1)
put(data, 71991, 71992, 1)
@@ -1708,8 +1754,9 @@ module Unicode
put(data, 73462, 73475, 13)
put(data, 73524, 73525, 1)
put(data, 73534, 73535, 1)
- put(data, 73537, 94033, 20496)
- put(data, 94034, 94087, 1)
+ put(data, 73537, 90410, 16873)
+ put(data, 90411, 90412, 1)
+ put(data, 94033, 94087, 1)
put(data, 94192, 94193, 1)
put(data, 119141, 119142, 1)
put(data, 119149, 119154, 1)
@@ -1725,7 +1772,7 @@ module Unicode
data
end
private class_getter category_Nd : Array({Int32, Int32, Int32}) do
- data = Array({Int32, Int32, Int32}).new(64)
+ data = Array({Int32, Int32, Int32}).new(71)
put(data, 48, 57, 1)
put(data, 1632, 1641, 1)
put(data, 1776, 1785, 1)
@@ -1765,6 +1812,7 @@ module Unicode
put(data, 65296, 65305, 1)
put(data, 66720, 66729, 1)
put(data, 68912, 68921, 1)
+ put(data, 68928, 68937, 1)
put(data, 69734, 69743, 1)
put(data, 69872, 69881, 1)
put(data, 69942, 69951, 1)
@@ -1774,20 +1822,26 @@ module Unicode
put(data, 70864, 70873, 1)
put(data, 71248, 71257, 1)
put(data, 71360, 71369, 1)
+ put(data, 71376, 71395, 1)
put(data, 71472, 71481, 1)
put(data, 71904, 71913, 1)
put(data, 72016, 72025, 1)
+ put(data, 72688, 72697, 1)
put(data, 72784, 72793, 1)
put(data, 73040, 73049, 1)
put(data, 73120, 73129, 1)
put(data, 73552, 73561, 1)
+ put(data, 90416, 90425, 1)
put(data, 92768, 92777, 1)
put(data, 92864, 92873, 1)
put(data, 93008, 93017, 1)
+ put(data, 93552, 93561, 1)
+ put(data, 118000, 118009, 1)
put(data, 120782, 120831, 1)
put(data, 123200, 123209, 1)
put(data, 123632, 123641, 1)
put(data, 124144, 124153, 1)
+ put(data, 124401, 124410, 1)
put(data, 125264, 125273, 1)
put(data, 130032, 130041, 1)
data
@@ -1951,7 +2005,7 @@ module Unicode
# Most casefold conversions map a range to another range.
# Here we store: {from, to, delta}
private class_getter casefold_ranges : Array({Int32, Int32, Int32}) do
- data = Array({Int32, Int32, Int32}).new(681)
+ data = Array({Int32, Int32, Int32}).new(687)
put(data, 65, 90, 32)
put(data, 181, 181, 775)
put(data, 192, 214, 32)
@@ -2276,6 +2330,7 @@ module Unicode
put(data, 7302, 7302, -6204)
put(data, 7303, 7303, -6180)
put(data, 7304, 7304, 35267)
+ put(data, 7305, 7305, 1)
put(data, 7312, 7354, -3008)
put(data, 7357, 7359, -3008)
put(data, 7680, 7680, 1)
@@ -2617,9 +2672,13 @@ module Unicode
put(data, 42950, 42950, -35384)
put(data, 42951, 42951, 1)
put(data, 42953, 42953, 1)
+ put(data, 42955, 42955, -42343)
+ put(data, 42956, 42956, 1)
put(data, 42960, 42960, 1)
put(data, 42966, 42966, 1)
put(data, 42968, 42968, 1)
+ put(data, 42970, 42970, 1)
+ put(data, 42972, 42972, -42561)
put(data, 42997, 42997, 1)
put(data, 43888, 43967, -38864)
put(data, 65313, 65338, 32)
@@ -2630,6 +2689,7 @@ module Unicode
put(data, 66956, 66962, 39)
put(data, 66964, 66965, 39)
put(data, 68736, 68786, 64)
+ put(data, 68944, 68965, 32)
put(data, 71840, 71871, 32)
put(data, 93760, 93791, 32)
put(data, 125184, 125217, 34)
@@ -2963,7 +3023,7 @@ module Unicode
# guarantees that all class values are within `0..254`.
# Here we store: {from, to, class}
private class_getter canonical_combining_classes : Array({Int32, Int32, UInt8}) do
- data = Array({Int32, Int32, UInt8}).new(392)
+ data = Array({Int32, Int32, UInt8}).new(399)
put(data, 768, 788, 230_u8)
put(data, 789, 789, 232_u8)
put(data, 790, 793, 220_u8)
@@ -3084,7 +3144,7 @@ module Unicode
put(data, 2085, 2087, 230_u8)
put(data, 2089, 2093, 230_u8)
put(data, 2137, 2139, 220_u8)
- put(data, 2200, 2200, 230_u8)
+ put(data, 2199, 2200, 230_u8)
put(data, 2201, 2203, 220_u8)
put(data, 2204, 2207, 230_u8)
put(data, 2250, 2254, 230_u8)
@@ -3273,6 +3333,7 @@ module Unicode
put(data, 68325, 68325, 230_u8)
put(data, 68326, 68326, 220_u8)
put(data, 68900, 68903, 230_u8)
+ put(data, 68969, 68973, 230_u8)
put(data, 69291, 69292, 230_u8)
put(data, 69373, 69375, 220_u8)
put(data, 69446, 69447, 220_u8)
@@ -3302,6 +3363,9 @@ module Unicode
put(data, 70477, 70477, 9_u8)
put(data, 70502, 70508, 230_u8)
put(data, 70512, 70516, 230_u8)
+ put(data, 70606, 70606, 9_u8)
+ put(data, 70607, 70607, 9_u8)
+ put(data, 70608, 70608, 9_u8)
put(data, 70722, 70722, 9_u8)
put(data, 70726, 70726, 7_u8)
put(data, 70750, 70750, 230_u8)
@@ -3328,6 +3392,7 @@ module Unicode
put(data, 73111, 73111, 9_u8)
put(data, 73537, 73537, 9_u8)
put(data, 73538, 73538, 9_u8)
+ put(data, 90415, 90415, 9_u8)
put(data, 92912, 92916, 1_u8)
put(data, 92976, 92982, 230_u8)
put(data, 94192, 94193, 6_u8)
@@ -3353,6 +3418,8 @@ module Unicode
put(data, 124140, 124141, 232_u8)
put(data, 124142, 124142, 220_u8)
put(data, 124143, 124143, 230_u8)
+ put(data, 124398, 124398, 230_u8)
+ put(data, 124399, 124399, 220_u8)
put(data, 125136, 125142, 220_u8)
put(data, 125252, 125257, 230_u8)
put(data, 125258, 125258, 7_u8)
@@ -3363,7 +3430,7 @@ module Unicode
# transformation is always 2 codepoints, so we store them all as 2 codepoints
# and 0 means end.
private class_getter canonical_decompositions : Hash(Int32, {Int32, Int32}) do
- data = Hash(Int32, {Int32, Int32}).new(initial_capacity: 2061)
+ data = Hash(Int32, {Int32, Int32}).new(initial_capacity: 2081)
put(data, 192, 65, 768)
put(data, 193, 65, 769)
put(data, 194, 65, 770)
@@ -4857,6 +4924,8 @@ module Unicode
put(data, 64332, 1489, 1471)
put(data, 64333, 1499, 1471)
put(data, 64334, 1508, 1471)
+ put(data, 67017, 67026, 775)
+ put(data, 67044, 67034, 775)
put(data, 69786, 69785, 69818)
put(data, 69788, 69787, 69818)
put(data, 69803, 69797, 69818)
@@ -4864,12 +4933,30 @@ module Unicode
put(data, 69935, 69938, 69927)
put(data, 70475, 70471, 70462)
put(data, 70476, 70471, 70487)
+ put(data, 70531, 70530, 70601)
+ put(data, 70533, 70532, 70587)
+ put(data, 70542, 70539, 70594)
+ put(data, 70545, 70544, 70601)
+ put(data, 70597, 70594, 70594)
+ put(data, 70599, 70594, 70584)
+ put(data, 70600, 70594, 70601)
put(data, 70843, 70841, 70842)
put(data, 70844, 70841, 70832)
put(data, 70846, 70841, 70845)
put(data, 71098, 71096, 71087)
put(data, 71099, 71097, 71087)
put(data, 71992, 71989, 71984)
+ put(data, 90401, 90398, 90398)
+ put(data, 90402, 90398, 90409)
+ put(data, 90403, 90398, 90399)
+ put(data, 90404, 90409, 90399)
+ put(data, 90405, 90398, 90400)
+ put(data, 90406, 90401, 90399)
+ put(data, 90407, 90402, 90399)
+ put(data, 90408, 90401, 90400)
+ put(data, 93544, 93543, 93543)
+ put(data, 93545, 93539, 93543)
+ put(data, 93546, 93545, 93543)
put(data, 119134, 119127, 119141)
put(data, 119135, 119128, 119141)
put(data, 119136, 119135, 119150)
@@ -8669,7 +8756,7 @@ module Unicode
# codepoints.
# Here we store: codepoint => {index, count}
private class_getter compatibility_decompositions : Hash(Int32, {Int32, Int32}) do
- data = Hash(Int32, {Int32, Int32}).new(initial_capacity: 3796)
+ data = Hash(Int32, {Int32, Int32}).new(initial_capacity: 3832)
put(data, 160, 0, 1)
put(data, 168, 1, 2)
put(data, 170, 3, 1)
@@ -11121,6 +11208,42 @@ module Unicode
put(data, 67512, 2953, 1)
put(data, 67513, 2954, 1)
put(data, 67514, 2955, 1)
+ put(data, 117974, 119, 1)
+ put(data, 117975, 121, 1)
+ put(data, 117976, 248, 1)
+ put(data, 117977, 35, 1)
+ put(data, 117978, 122, 1)
+ put(data, 117979, 259, 1)
+ put(data, 117980, 124, 1)
+ put(data, 117981, 125, 1)
+ put(data, 117982, 24, 1)
+ put(data, 117983, 25, 1)
+ put(data, 117984, 126, 1)
+ put(data, 117985, 28, 1)
+ put(data, 117986, 127, 1)
+ put(data, 117987, 47, 1)
+ put(data, 117988, 128, 1)
+ put(data, 117989, 130, 1)
+ put(data, 117990, 263, 1)
+ put(data, 117991, 131, 1)
+ put(data, 117992, 264, 1)
+ put(data, 117993, 132, 1)
+ put(data, 117994, 133, 1)
+ put(data, 117995, 333, 1)
+ put(data, 117996, 134, 1)
+ put(data, 117997, 277, 1)
+ put(data, 117998, 606, 1)
+ put(data, 117999, 54, 1)
+ put(data, 118000, 229, 1)
+ put(data, 118001, 13, 1)
+ put(data, 118002, 6, 1)
+ put(data, 118003, 7, 1)
+ put(data, 118004, 17, 1)
+ put(data, 118005, 230, 1)
+ put(data, 118006, 231, 1)
+ put(data, 118007, 232, 1)
+ put(data, 118008, 233, 1)
+ put(data, 118009, 234, 1)
put(data, 119808, 119, 1)
put(data, 119809, 121, 1)
put(data, 119810, 248, 1)
@@ -12473,7 +12596,7 @@ module Unicode
# composition exclusions.
# Here we store: (first << 21 | second) => codepoint
private class_getter canonical_compositions : Hash(Int64, Int32) do
- data = Hash(Int64, Int32).new(initial_capacity: 941)
+ data = Hash(Int64, Int32).new(initial_capacity: 961)
put(data, 136315648_i64, 192)
put(data, 136315649_i64, 193)
put(data, 136315650_i64, 194)
@@ -13402,6 +13525,8 @@ module Unicode
put(data, 26275229849_i64, 12537)
put(data, 26277327001_i64, 12538)
put(data, 26300395673_i64, 12542)
+ put(data, 140563710727_i64, 67017)
+ put(data, 140580487943_i64, 67044)
put(data, 146349822138_i64, 69786)
put(data, 146354016442_i64, 69788)
put(data, 146374987962_i64, 69803)
@@ -13409,12 +13534,30 @@ module Unicode
put(data, 146670686503_i64, 69935)
put(data, 147788469054_i64, 70475)
put(data, 147788469079_i64, 70476)
+ put(data, 147912201161_i64, 70531)
+ put(data, 147916395451_i64, 70533)
+ put(data, 147931075522_i64, 70542)
+ put(data, 147941561289_i64, 70545)
+ put(data, 148046418882_i64, 70597)
+ put(data, 148046418872_i64, 70599)
+ put(data, 148046418889_i64, 70600)
put(data, 148564415674_i64, 70843)
put(data, 148564415664_i64, 70844)
put(data, 148564415677_i64, 70846)
put(data, 149099189679_i64, 71098)
put(data, 149101286831_i64, 71099)
put(data, 150971947312_i64, 71992)
+ put(data, 189578436894_i64, 90401)
+ put(data, 189578436905_i64, 90402)
+ put(data, 189578436895_i64, 90403)
+ put(data, 189601505567_i64, 90404)
+ put(data, 189578436896_i64, 90405)
+ put(data, 189584728351_i64, 90406)
+ put(data, 189586825503_i64, 90407)
+ put(data, 189584728352_i64, 90408)
+ put(data, 196173983079_i64, 93544)
+ put(data, 196165594471_i64, 93545)
+ put(data, 196178177383_i64, 93546)
data
end
@@ -13422,7 +13565,7 @@ module Unicode
# Form C (yes if absent in this table).
# Here we store: {low, high, result (no or maybe)}
private class_getter nfc_quick_check : Array({Int32, Int32, QuickCheckResult}) do
- data = Array({Int32, Int32, QuickCheckResult}).new(117)
+ data = Array({Int32, Int32, QuickCheckResult}).new(124)
put(data, 768, 772, QuickCheckResult::Maybe)
put(data, 774, 780, QuickCheckResult::Maybe)
put(data, 783, 783, QuickCheckResult::Maybe)
@@ -13532,11 +13675,18 @@ module Unicode
put(data, 69927, 69927, QuickCheckResult::Maybe)
put(data, 70462, 70462, QuickCheckResult::Maybe)
put(data, 70487, 70487, QuickCheckResult::Maybe)
+ put(data, 70584, 70584, QuickCheckResult::Maybe)
+ put(data, 70587, 70587, QuickCheckResult::Maybe)
+ put(data, 70594, 70594, QuickCheckResult::Maybe)
+ put(data, 70597, 70597, QuickCheckResult::Maybe)
+ put(data, 70599, 70601, QuickCheckResult::Maybe)
put(data, 70832, 70832, QuickCheckResult::Maybe)
put(data, 70842, 70842, QuickCheckResult::Maybe)
put(data, 70845, 70845, QuickCheckResult::Maybe)
put(data, 71087, 71087, QuickCheckResult::Maybe)
put(data, 71984, 71984, QuickCheckResult::Maybe)
+ put(data, 90398, 90409, QuickCheckResult::Maybe)
+ put(data, 93543, 93544, QuickCheckResult::Maybe)
put(data, 119134, 119140, QuickCheckResult::No)
put(data, 119227, 119232, QuickCheckResult::No)
put(data, 194560, 195101, QuickCheckResult::No)
@@ -13547,7 +13697,7 @@ module Unicode
# Form KC (yes if absent in this table).
# Here we store: {low, high, result (no or maybe)}
private class_getter nfkc_quick_check : Array({Int32, Int32, QuickCheckResult}) do
- data = Array({Int32, Int32, QuickCheckResult}).new(436)
+ data = Array({Int32, Int32, QuickCheckResult}).new(445)
put(data, 160, 160, QuickCheckResult::No)
put(data, 168, 168, QuickCheckResult::No)
put(data, 170, 170, QuickCheckResult::No)
@@ -13891,11 +14041,20 @@ module Unicode
put(data, 69927, 69927, QuickCheckResult::Maybe)
put(data, 70462, 70462, QuickCheckResult::Maybe)
put(data, 70487, 70487, QuickCheckResult::Maybe)
+ put(data, 70584, 70584, QuickCheckResult::Maybe)
+ put(data, 70587, 70587, QuickCheckResult::Maybe)
+ put(data, 70594, 70594, QuickCheckResult::Maybe)
+ put(data, 70597, 70597, QuickCheckResult::Maybe)
+ put(data, 70599, 70601, QuickCheckResult::Maybe)
put(data, 70832, 70832, QuickCheckResult::Maybe)
put(data, 70842, 70842, QuickCheckResult::Maybe)
put(data, 70845, 70845, QuickCheckResult::Maybe)
put(data, 71087, 71087, QuickCheckResult::Maybe)
put(data, 71984, 71984, QuickCheckResult::Maybe)
+ put(data, 90398, 90409, QuickCheckResult::Maybe)
+ put(data, 93543, 93544, QuickCheckResult::Maybe)
+ put(data, 117974, 117999, QuickCheckResult::No)
+ put(data, 118000, 118009, QuickCheckResult::No)
put(data, 119134, 119140, QuickCheckResult::No)
put(data, 119227, 119232, QuickCheckResult::No)
put(data, 119808, 119892, QuickCheckResult::No)
@@ -13992,7 +14151,7 @@ module Unicode
# codepoints contained here may not appear under NFD.
# Here we store: {low, high}
private class_getter nfd_quick_check : Array({Int32, Int32}) do
- data = Array({Int32, Int32}).new(243)
+ data = Array({Int32, Int32}).new(253)
put(data, 192, 197)
put(data, 199, 207)
put(data, 209, 214)
@@ -14224,15 +14383,25 @@ module Unicode
put(data, 64320, 64321)
put(data, 64323, 64324)
put(data, 64326, 64334)
+ put(data, 67017, 67017)
+ put(data, 67044, 67044)
put(data, 69786, 69786)
put(data, 69788, 69788)
put(data, 69803, 69803)
put(data, 69934, 69935)
put(data, 70475, 70476)
+ put(data, 70531, 70531)
+ put(data, 70533, 70533)
+ put(data, 70542, 70542)
+ put(data, 70545, 70545)
+ put(data, 70597, 70597)
+ put(data, 70599, 70600)
put(data, 70843, 70844)
put(data, 70846, 70846)
put(data, 71098, 71099)
put(data, 71992, 71992)
+ put(data, 90401, 90408)
+ put(data, 93544, 93546)
put(data, 119134, 119140)
put(data, 119227, 119232)
put(data, 194560, 195101)
@@ -14244,7 +14413,7 @@ module Unicode
# codepoints contained here may not appear under NFKD.
# Here we store: {low, high}
private class_getter nfkd_quick_check : Array({Int32, Int32}) do
- data = Array({Int32, Int32}).new(548)
+ data = Array({Int32, Int32}).new(560)
put(data, 160, 160)
put(data, 168, 168)
put(data, 170, 170)
@@ -14693,6 +14862,8 @@ module Unicode
put(data, 65512, 65512)
put(data, 65513, 65516)
put(data, 65517, 65518)
+ put(data, 67017, 67017)
+ put(data, 67044, 67044)
put(data, 67457, 67461)
put(data, 67463, 67504)
put(data, 67506, 67514)
@@ -14701,10 +14872,20 @@ module Unicode
put(data, 69803, 69803)
put(data, 69934, 69935)
put(data, 70475, 70476)
+ put(data, 70531, 70531)
+ put(data, 70533, 70533)
+ put(data, 70542, 70542)
+ put(data, 70545, 70545)
+ put(data, 70597, 70597)
+ put(data, 70599, 70600)
put(data, 70843, 70844)
put(data, 70846, 70846)
put(data, 71098, 71099)
put(data, 71992, 71992)
+ put(data, 90401, 90408)
+ put(data, 93544, 93546)
+ put(data, 117974, 117999)
+ put(data, 118000, 118009)
put(data, 119134, 119140)
put(data, 119227, 119232)
put(data, 119808, 119892)
diff --git a/src/unicode/unicode.cr b/src/unicode/unicode.cr
index 1fb4b530686b..ab49ea31368b 100644
--- a/src/unicode/unicode.cr
+++ b/src/unicode/unicode.cr
@@ -1,7 +1,7 @@
# Provides the `Unicode::CaseOptions` enum for special case conversions like Turkic.
module Unicode
# The currently supported [Unicode](https://home.unicode.org) version.
- VERSION = "15.1.0"
+ VERSION = "16.0.0"
# Case options to pass to various `Char` and `String` methods such as `upcase` or `downcase`.
@[Flags]
diff --git a/src/uri/json.cr b/src/uri/json.cr
index 9767c9e98a02..00b58f419be5 100644
--- a/src/uri/json.cr
+++ b/src/uri/json.cr
@@ -25,4 +25,18 @@ class URI
def to_json(builder : JSON::Builder)
builder.string self
end
+
+ # Deserializes the given JSON *key* into a `URI`
+ #
+ # NOTE: `require "uri/json"` is required to opt-in to this feature.
+ def self.from_json_object_key?(key : String) : URI?
+ parse key
+ rescue URI::Error
+ nil
+ end
+
+ # :nodoc:
+ def to_json_object_key : String
+ to_s
+ end
end
diff --git a/src/uri/params/from_www_form.cr b/src/uri/params/from_www_form.cr
new file mode 100644
index 000000000000..819c9fc9d82e
--- /dev/null
+++ b/src/uri/params/from_www_form.cr
@@ -0,0 +1,67 @@
+# :nodoc:
+def Object.from_www_form(params : URI::Params, name : String)
+ return unless value = params[name]?
+
+ self.from_www_form value
+end
+
+# :nodoc:
+def Array.from_www_form(params : URI::Params, name : String)
+ name = if params.has_key? name
+ name
+ elsif params.has_key? "#{name}[]"
+ "#{name}[]"
+ else
+ return
+ end
+
+ params.fetch_all(name).map { |item| T.from_www_form(item).as T }
+end
+
+# :nodoc:
+def Bool.from_www_form(value : String)
+ case value
+ when "true", "1", "yes", "on" then true
+ when "false", "0", "no", "off" then false
+ end
+end
+
+# :nodoc:
+def Number.from_www_form(value : String)
+ new value, whitespace: false
+end
+
+# :nodoc:
+def String.from_www_form(value : String)
+ value
+end
+
+# :nodoc:
+def Enum.from_www_form(value : String)
+ parse value
+end
+
+# :nodoc:
+def Time.from_www_form(value : String)
+ Time::Format::ISO_8601_DATE_TIME.parse value
+end
+
+# :nodoc:
+def Union.from_www_form(params : URI::Params, name : String)
+ # Process non nilable types first as they are more likely to work.
+ {% for type in T.sort_by { |t| t.nilable? ? 1 : 0 } %}
+ begin
+ return {{type}}.from_www_form params, name
+ rescue
+ # Noop to allow next T to be tried.
+ end
+ {% end %}
+ raise ArgumentError.new "Invalid #{self}: '#{params[name]}'."
+end
+
+# :nodoc:
+def Nil.from_www_form(value : String) : Nil
+ return if value.empty?
+
+ raise ArgumentError.new "Invalid Nil value: '#{value}'."
+end
diff --git a/src/uri/params/serializable.cr b/src/uri/params/serializable.cr
new file mode 100644
index 000000000000..54d3b970e53c
--- /dev/null
+++ b/src/uri/params/serializable.cr
@@ -0,0 +1,129 @@
+require "uri"
+
+require "./to_www_form"
+require "./from_www_form"
+
+struct URI::Params
+ annotation Field; end
+
+ # The `URI::Params::Serializable` module automatically generates methods for `x-www-form-urlencoded` serialization when included.
+ #
+ # NOTE: To use this module, you must explicitly import it with `require "uri/params/serializable"`.
+ #
+ # ### Example
+ #
+ # ```
+ # require "uri/params/serializable"
+ #
+ # struct Applicant
+ # include URI::Params::Serializable
+ #
+ # getter first_name : String
+ # getter last_name : String
+ # getter qualities : Array(String)
+ # end
+ #
+ # applicant = Applicant.from_www_form "first_name=John&last_name=Doe&qualities=kind&qualities=smart"
+ # applicant.first_name # => "John"
+ # applicant.last_name # => "Doe"
+ # applicant.qualities # => ["kind", "smart"]
+ # applicant.to_www_form # => "first_name=John&last_name=Doe&qualities=kind&qualities=smart"
+ # ```
+ #
+ # ### Usage
+ #
+ # Including `URI::Params::Serializable` will create `#to_www_form` and `self.from_www_form` methods on the current class.
+ # By default, these methods serialize into a www form encoded string containing the value of every instance variable, the keys being the instance variable name.
+ # Union types are also supported, including unions with nil.
+ # If multiple types in a union parse correctly, it is undefined which one will be chosen.
+ #
+ # To change how individual instance variables are parsed, the annotation `URI::Params::Field` can be placed on the instance variable.
+ # Annotating property, getter and setter macros is also allowed.
+ #
+ # `URI::Params::Field` properties:
+ # * **converter**: specify an alternate type for parsing. The converter must define `.from_www_form(params : URI::Params, name : String)`.
+ # An example use case would be customizing the format when parsing `Time` instances, or supporting a type not natively supported.
+ #
+ # Deserialization also respects default values of variables:
+ # ```
+ # require "uri/params/serializable"
+ #
+ # struct A
+ # include URI::Params::Serializable
+ #
+ # @a : Int32
+ # @b : Float64 = 1.0
+ # end
+ #
+ # A.from_www_form("a=1") # => A(@a=1, @b=1.0)
+ # ```
+ module Serializable
+ macro included
+ def self.from_www_form(params : ::String)
+ new_from_www_form ::URI::Params.parse params
+ end
+
+ # :nodoc:
+ #
+ # This is needed so that nested types can pass the name thru internally.
+ # Has to be public so the generated code can call it, but should be considered an implementation detail.
+ def self.from_www_form(params : ::URI::Params, name : ::String)
+ new_from_www_form(params, name)
+ end
+
+ protected def self.new_from_www_form(params : ::URI::Params, name : ::String? = nil)
+ instance = allocate
+ instance.initialize(__uri_params: params, name: name)
+ GC.add_finalizer(instance) if instance.responds_to?(:finalize)
+ instance
+ end
+
+ macro inherited
+ def self.from_www_form(params : ::String)
+ new_from_www_form ::URI::Params.parse params
+ end
+
+ # :nodoc:
+ def self.from_www_form(params : ::URI::Params, name : ::String)
+ new_from_www_form(params, name)
+ end
+ end
+ end
+
+ # :nodoc:
+ def initialize(*, __uri_params params : ::URI::Params, name : String?)
+ {% begin %}
+ {% for ivar, idx in @type.instance_vars %}
+ %name{idx} = name.nil? ? {{ivar.name.stringify}} : "#{name}[#{{{ivar.name.stringify}}}]"
+ %value{idx} = {{(ann = ivar.annotation(URI::Params::Field)) && (converter = ann["converter"]) ? converter : ivar.type}}.from_www_form params, %name{idx}
+
+ unless %value{idx}.nil?
+ @{{ivar.name.id}} = %value{idx}
+ else
+ {% unless ivar.type.resolve.nilable? || ivar.has_default_value? %}
+ raise URI::SerializableError.new "Missing required property: '#{%name{idx}}'."
+ {% end %}
+ end
+ {% end %}
+ {% end %}
+ end
+
+ def to_www_form(*, space_to_plus : Bool = true) : String
+ URI::Params.build(space_to_plus: space_to_plus) do |form|
+ {% for ivar in @type.instance_vars %}
+ @{{ivar.name.id}}.to_www_form form, {{ivar.name.stringify}}
+ {% end %}
+ end
+ end
+
+ # :nodoc:
+ def to_www_form(builder : URI::Params::Builder, name : String)
+ {% for ivar in @type.instance_vars %}
+ @{{ivar.name.id}}.to_www_form builder, "#{name}[#{{{ivar.name.stringify}}}]"
+ {% end %}
+ end
+ end
+end
+
+class URI::SerializableError < URI::Error
+end
diff --git a/src/uri/params/to_www_form.cr b/src/uri/params/to_www_form.cr
new file mode 100644
index 000000000000..3a0007781e64
--- /dev/null
+++ b/src/uri/params/to_www_form.cr
@@ -0,0 +1,48 @@
+struct Bool
+ # :nodoc:
+ def to_www_form(builder : URI::Params::Builder, name : String) : Nil
+ builder.add name, to_s
+ end
+end
+
+class Array
+ # :nodoc:
+ def to_www_form(builder : URI::Params::Builder, name : String) : Nil
+ each &.to_www_form builder, name
+ end
+end
+
+class String
+ # :nodoc:
+ def to_www_form(builder : URI::Params::Builder, name : String) : Nil
+ builder.add name, self
+ end
+end
+
+struct Number
+ # :nodoc:
+ def to_www_form(builder : URI::Params::Builder, name : String) : Nil
+ builder.add name, to_s
+ end
+end
+
+struct Nil
+ # :nodoc:
+ def to_www_form(builder : URI::Params::Builder, name : String) : Nil
+ builder.add name, self
+ end
+end
+
+struct Enum
+ # :nodoc:
+ def to_www_form(builder : URI::Params::Builder, name : String) : Nil
+ builder.add name, to_s.underscore
+ end
+end
+
+struct Time
+ # :nodoc:
+ def to_www_form(builder : URI::Params::Builder, name : String) : Nil
+ builder.add name, to_rfc3339
+ end
+end
diff --git a/src/wait_group.cr b/src/wait_group.cr
index 2fd49c593b56..c1ebe67bf508 100644
--- a/src/wait_group.cr
+++ b/src/wait_group.cr
@@ -42,12 +42,46 @@ class WaitGroup
end
end
+ # Yields a `WaitGroup` instance and waits at the end of the block for all of
+ # the work enqueued inside it to complete.
+ #
+ # ```
+ # WaitGroup.wait do |wg|
+ # items.each do |item|
+ # wg.spawn { process item }
+ # end
+ # end
+ # ```
+ def self.wait(&) : Nil
+ instance = new
+ yield instance
+ instance.wait
+ end
+
def initialize(n : Int32 = 0)
@waiting = Crystal::PointerLinkedList(Waiting).new
@lock = Crystal::SpinLock.new
@counter = Atomic(Int32).new(n)
end
+ # Increment the counter by 1, perform the work inside the block in a separate
+ # fiber, decrementing the counter after it completes or raises. Returns the
+ # `Fiber` that was spawned.
+ #
+ # ```
+ # wg = WaitGroup.new
+ # wg.spawn { do_something }
+ # wg.wait
+ # ```
+ def spawn(&block) : Fiber
+ add
+ ::spawn do
+ block.call
+ ensure
+ done
+ end
+ end
+
# Increments the counter by how many fibers we want to wait for.
#
# A negative value decrements the counter. When the counter reaches zero,
diff --git a/src/winerror.cr b/src/winerror.cr
index ab978769d553..fbb2fb553873 100644
--- a/src/winerror.cr
+++ b/src/winerror.cr
@@ -2305,6 +2305,7 @@ enum WinError : UInt32
ERROR_STATE_CONTAINER_NAME_SIZE_LIMIT_EXCEEDED = 15818_u32
ERROR_API_UNAVAILABLE = 15841_u32
- WSA_IO_PENDING = ERROR_IO_PENDING
- WSA_IO_INCOMPLETE = ERROR_IO_INCOMPLETE
+ WSA_IO_PENDING = ERROR_IO_PENDING
+ WSA_IO_INCOMPLETE = ERROR_IO_INCOMPLETE
+ WSA_INVALID_HANDLE = ERROR_INVALID_HANDLE
end
diff --git a/src/xml.cr b/src/xml.cr
index e0529be130f3..a9c9eab5d64e 100644
--- a/src/xml.cr
+++ b/src/xml.cr
@@ -107,12 +107,7 @@ module XML
end
protected def self.with_indent_tree_output(indent : Bool, &)
- ptr = {% if flag?(:win32) %}
- LibXML.__xmlIndentTreeOutput
- {% else %}
- pointerof(LibXML.xmlIndentTreeOutput)
- {% end %}
-
+ ptr = LibXML.__xmlIndentTreeOutput
old, ptr.value = ptr.value, indent ? 1 : 0
begin
yield
@@ -122,12 +117,7 @@ module XML
end
protected def self.with_tree_indent_string(string : String, &)
- ptr = {% if flag?(:win32) %}
- LibXML.__xmlTreeIndentString
- {% else %}
- pointerof(LibXML.xmlTreeIndentString)
- {% end %}
-
+ ptr = LibXML.__xmlTreeIndentString
old, ptr.value = ptr.value, string.to_unsafe
begin
yield
diff --git a/src/xml/error.cr b/src/xml/error.cr
index 868dfeb4bd00..389aa53910c2 100644
--- a/src/xml/error.cr
+++ b/src/xml/error.cr
@@ -11,22 +11,9 @@ class XML::Error < Exception
super(message)
end
- @@errors = [] of self
-
- # :nodoc:
- protected def self.add_errors(errors)
- @@errors.concat(errors)
- end
-
@[Deprecated("This class accessor is deprecated. XML errors are accessible directly in the respective context via `XML::Reader#errors` and `XML::Node#errors`.")]
def self.errors : Array(XML::Error)?
- if @@errors.empty?
- nil
- else
- errors = @@errors.dup
- @@errors.clear
- errors
- end
+ {% raise "`XML::Error.errors` was removed because it leaks memory when it's not used. XML errors are accessible directly in the respective context via `XML::Reader#errors` and `XML::Node#errors`.\nSee https://github.com/crystal-lang/crystal/issues/14934 for details. " %}
end
def self.collect(errors, &)
diff --git a/src/xml/libxml2.cr b/src/xml/libxml2.cr
index e1c2b8d12372..fbfb0702faef 100644
--- a/src/xml/libxml2.cr
+++ b/src/xml/libxml2.cr
@@ -13,14 +13,8 @@ lib LibXML
fun xmlInitParser
- # TODO: check if other platforms also support per-thread globals
- {% if flag?(:win32) %}
- fun __xmlIndentTreeOutput : Int*
- fun __xmlTreeIndentString : UInt8**
- {% else %}
- $xmlIndentTreeOutput : Int
- $xmlTreeIndentString : UInt8*
- {% end %}
+ fun __xmlIndentTreeOutput : Int*
+ fun __xmlTreeIndentString : UInt8**
alias Dtd = Void*
alias Dict = Void*
diff --git a/src/xml/reader.cr b/src/xml/reader.cr
index decdd8468185..d4dbe91f7eeb 100644
--- a/src/xml/reader.cr
+++ b/src/xml/reader.cr
@@ -198,9 +198,7 @@ class XML::Reader
end
private def collect_errors(&)
- Error.collect(@errors) { yield }.tap do
- Error.add_errors(@errors)
- end
+ Error.collect(@errors) { yield }
end
private def check_no_null_byte(attribute)
diff --git a/src/yaml/serialization.cr b/src/yaml/serialization.cr
index d5fae8dfe9c0..4a1521469dea 100644
--- a/src/yaml/serialization.cr
+++ b/src/yaml/serialization.cr
@@ -156,11 +156,11 @@ module YAML
# Define a `new` directly in the included type,
# so it overloads well with other possible initializes
- def self.new(ctx : YAML::ParseContext, node : YAML::Nodes::Node)
+ def self.new(ctx : ::YAML::ParseContext, node : ::YAML::Nodes::Node)
new_from_yaml_node(ctx, node)
end
- private def self.new_from_yaml_node(ctx : YAML::ParseContext, node : YAML::Nodes::Node)
+ private def self.new_from_yaml_node(ctx : ::YAML::ParseContext, node : ::YAML::Nodes::Node)
ctx.read_alias(node, self) do |obj|
return obj
end
@@ -170,7 +170,7 @@ module YAML
ctx.record_anchor(node, instance)
instance.initialize(__context_for_yaml_serializable: ctx, __node_for_yaml_serializable: node)
- GC.add_finalizer(instance) if instance.responds_to?(:finalize)
+ ::GC.add_finalizer(instance) if instance.responds_to?(:finalize)
instance
end
@@ -178,7 +178,7 @@ module YAML
# so it can compete with other possible initializes
macro inherited
- def self.new(ctx : YAML::ParseContext, node : YAML::Nodes::Node)
+ def self.new(ctx : ::YAML::ParseContext, node : ::YAML::Nodes::Node)
new_from_yaml_node(ctx, node)
end
end
@@ -409,17 +409,17 @@ module YAML
{% mapping.raise "Mapping argument must be a HashLiteral or a NamedTupleLiteral, not #{mapping.class_name.id}" %}
{% end %}
- def self.new(ctx : YAML::ParseContext, node : YAML::Nodes::Node)
+ def self.new(ctx : ::YAML::ParseContext, node : ::YAML::Nodes::Node)
ctx.read_alias(node, \{{@type}}) do |obj|
return obj
end
- unless node.is_a?(YAML::Nodes::Mapping)
+ unless node.is_a?(::YAML::Nodes::Mapping)
node.raise "Expected YAML mapping, not #{node.class}"
end
node.each do |key, value|
- next unless key.is_a?(YAML::Nodes::Scalar) && value.is_a?(YAML::Nodes::Scalar)
+ next unless key.is_a?(::YAML::Nodes::Scalar) && value.is_a?(::YAML::Nodes::Scalar)
next unless key.value == {{field.id.stringify}}
discriminator_value = value.value