diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS deleted file mode 100644 index 062096feee50a1..00000000000000 --- a/.github/CODEOWNERS +++ /dev/null @@ -1,7 +0,0 @@ -# YJIT sources and tests -yjit* @ruby/yjit -yjit/**/* @ruby/yjit -doc/yjit/* @ruby/yjit -bootstraptest/test_yjit* @ruby/yjit -test/ruby/test_yjit* @ruby/yjit -yjit/src/cruby_bindings.inc.rs diff --git a/.github/actions/launchable/setup/action.yml b/.github/actions/launchable/setup/action.yml index 458b0c7d72ee47..6d50318ded99f1 100644 --- a/.github/actions/launchable/setup/action.yml +++ b/.github/actions/launchable/setup/action.yml @@ -38,11 +38,6 @@ inputs: Directory to (re-)checkout source codes. Launchable retrives the commit information from the directory. -outputs: - enable-launchable: - description: "The boolean value indicating whether Launchable is enabled or not" - value: ${{ steps.enable-launchable.outputs.enable-launchable }} - runs: using: composite @@ -96,7 +91,7 @@ runs: PATH=$PATH:$(python -msite --user-base)/bin echo "PATH=$PATH" >> $GITHUB_ENV pip install --user launchable - launchable verify + launchable verify || true : # The build name cannot include a slash, so we replace the string here. github_ref="${{ github.ref }}" github_ref="${github_ref//\//_}" diff --git a/.github/actions/setup/directories/action.yml b/.github/actions/setup/directories/action.yml index 5cc19d53f42b58..63abd4ae58b8bd 100644 --- a/.github/actions/setup/directories/action.yml +++ b/.github/actions/setup/directories/action.yml @@ -88,12 +88,12 @@ runs: git config --global init.defaultBranch garbage - if: inputs.checkout - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 with: path: ${{ inputs.srcdir }} fetch-depth: ${{ inputs.fetch-depth }} - - uses: actions/cache@ab5e6d0c87105b4c9c2047343972218f562e4319 # v4.0.1 + - uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2 with: path: ${{ inputs.srcdir }}/.downloaded-cache key: downloaded-cache @@ -164,7 +164,7 @@ runs: echo final='rmdir ${{ inputs.builddir }}' >> $GITHUB_OUTPUT - name: clean - uses: gacts/run-and-post-run@674528335da98a7afc80915ff2b4b860a0b3553a # v1.4.0 + uses: gacts/run-and-post-run@7aec950f3b114c4fcf6012070c3709ecff0eb6f8 # v1.4.0 with: working-directory: post: | diff --git a/.github/auto_request_review.yml b/.github/auto_request_review.yml new file mode 100644 index 00000000000000..8726df577d39eb --- /dev/null +++ b/.github/auto_request_review.yml @@ -0,0 +1,13 @@ +files: + 'yjit*': [team:yjit] + 'yjit/**/*': [team:yjit] + 'yjit/src/cruby_bindings.inc.rs': [] + 'doc/yjit/*': [team:yjit] + 'bootstraptest/test_yjit*': [team:yjit] + 'test/ruby/test_yjit*': [team:yjit] +options: + ignore_draft: true + # This currently doesn't work as intended. We want to skip reviews when only + # cruby_bingings.inc.rs is modified, but this skips reviews even when other + # yjit files are modified as well. To be enabled after fixing the behavior. + #last_files_match_only: true diff --git a/.github/workflows/annocheck.yml b/.github/workflows/annocheck.yml index bf490202aab1d1..bc5b81a9b99b2c 100644 --- a/.github/workflows/annocheck.yml +++ b/.github/workflows/annocheck.yml @@ -39,10 +39,10 @@ jobs: if: >- ${{!(false || contains(github.event.head_commit.message, '[DOC]') - || contains(github.event.head_commit.message, 'Documentation') + || contains(github.event.head_commit.message, 'Document') || contains(github.event.pull_request.title, '[DOC]') - || contains(github.event.pull_request.title, 'Documentation') - || contains(github.event.pull_request.labels.*.name, 'Documentation') + || contains(github.event.pull_request.title, 'Document') + || contains(github.event.pull_request.labels.*.name, 'Document') || (github.event_name == 'push' && github.actor == 'dependabot[bot]') )}} @@ -63,7 +63,7 @@ jobs: - run: id working-directory: - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 with: sparse-checkout-cone-mode: false sparse-checkout: /.github @@ -74,7 +74,7 @@ jobs: builddir: build makeup: true - - uses: ruby/setup-ruby@d4526a55538b775af234ba4af27118ed6f8f6677 # v1.172.0 + - uses: ruby/setup-ruby@5f19ec79cedfadb78ab837f95b87734d0003c899 # v1.173.0 with: ruby-version: '3.0' bundler: none diff --git a/.github/workflows/auto_request_review.yml b/.github/workflows/auto_request_review.yml new file mode 100644 index 00000000000000..ca27244b46547b --- /dev/null +++ b/.github/workflows/auto_request_review.yml @@ -0,0 +1,19 @@ +name: Auto Request Review +on: + pull_request_target: + types: [opened, ready_for_review, reopened] + +permissions: + contents: read + +jobs: + auto-request-review: + name: Auto Request Review + runs-on: ubuntu-latest + if: ${{ github.repository == 'ruby/ruby' && github.base_ref == 'master' }} + steps: + - name: Request review based on files changes and/or groups the author belongs to + uses: necojackarc/auto-request-review@e89da1a8cd7c8c16d9de9c6e763290b6b0e3d424 # v0.13.0 + with: + # scope: public_repo + token: ${{ secrets.MATZBOT_GITHUB_TOKEN }} diff --git a/.github/workflows/baseruby.yml b/.github/workflows/baseruby.yml index 6bb5c37731a42d..2bc1e39e0c807a 100644 --- a/.github/workflows/baseruby.yml +++ b/.github/workflows/baseruby.yml @@ -35,10 +35,10 @@ jobs: if: >- ${{!(false || contains(github.event.head_commit.message, '[DOC]') - || contains(github.event.head_commit.message, 'Documentation') + || contains(github.event.head_commit.message, 'Document') || contains(github.event.pull_request.title, '[DOC]') - || contains(github.event.pull_request.title, 'Documentation') - || contains(github.event.pull_request.labels.*.name, 'Documentation') + || contains(github.event.pull_request.title, 'Document') + || contains(github.event.pull_request.labels.*.name, 'Document') || (github.event_name == 'push' && github.actor == 'dependabot[bot]') )}} @@ -51,12 +51,12 @@ jobs: - ruby-3.3 steps: - - uses: ruby/setup-ruby@d4526a55538b775af234ba4af27118ed6f8f6677 # v1.172.0 + - uses: ruby/setup-ruby@5f19ec79cedfadb78ab837f95b87734d0003c899 # v1.173.0 with: ruby-version: ${{ matrix.ruby }} bundler: none - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 - uses: ./.github/actions/setup/ubuntu diff --git a/.github/workflows/bundled_gems.yml b/.github/workflows/bundled_gems.yml index 81d4a2a9dc0e36..96bca3e3aa5573 100644 --- a/.github/workflows/bundled_gems.yml +++ b/.github/workflows/bundled_gems.yml @@ -31,7 +31,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 with: token: ${{ (github.repository == 'ruby/ruby' && !startsWith(github.event_name, 'pull')) && secrets.MATZBOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} @@ -56,6 +56,13 @@ jobs: run: | ruby -i~ tool/update-bundled_gems.rb gems/bundled_gems >> $GITHUB_OUTPUT + - name: Update spec/bundler/support/builders.rb + run: | + #!ruby + rake_version = File.read("gems/bundled_gems")[/^rake\s+(\S+)/, 1] + print ARGF.read.sub(/^ *def rake_version\s*\K".*?"/) {rake_version.dump} + shell: ruby -i~ {0} spec/bundler/support/builders.rb + - name: Maintain updated gems list in NEWS run: | ruby tool/update-NEWS-gemlist.rb bundled @@ -69,6 +76,7 @@ jobs: git diff --color --no-ext-diff --ignore-submodules --exit-code -- gems/bundled_gems || gems=true git add -- NEWS.md gems/bundled_gems + git add -- spec/bundler/support/builders.rb echo news=$news >> $GITHUB_OUTPUT echo gems=$gems >> $GITHUB_OUTPUT echo update=${news:-$gems} >> $GITHUB_OUTPUT diff --git a/.github/workflows/check_dependencies.yml b/.github/workflows/check_dependencies.yml index ed3d8fbace05ce..5187138be97c8e 100644 --- a/.github/workflows/check_dependencies.yml +++ b/.github/workflows/check_dependencies.yml @@ -37,15 +37,15 @@ jobs: if: >- ${{!(false || contains(github.event.head_commit.message, '[DOC]') - || contains(github.event.head_commit.message, 'Documentation') + || contains(github.event.head_commit.message, 'Document') || contains(github.event.pull_request.title, '[DOC]') - || contains(github.event.pull_request.title, 'Documentation') - || contains(github.event.pull_request.labels.*.name, 'Documentation') + || contains(github.event.pull_request.title, 'Document') + || contains(github.event.pull_request.labels.*.name, 'Document') || (github.event_name == 'push' && github.actor == 'dependabot[bot]') )}} steps: - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 - uses: ./.github/actions/setup/ubuntu if: ${{ contains(matrix.os, 'ubuntu') }} @@ -55,7 +55,7 @@ jobs: - uses: ./.github/actions/setup/directories - - uses: ruby/setup-ruby@d4526a55538b775af234ba4af27118ed6f8f6677 # v1.172.0 + - uses: ruby/setup-ruby@5f19ec79cedfadb78ab837f95b87734d0003c899 # v1.173.0 with: ruby-version: '3.0' bundler: none diff --git a/.github/workflows/check_misc.yml b/.github/workflows/check_misc.yml index ce4397b35aac02..7996ddb4c0dbad 100644 --- a/.github/workflows/check_misc.yml +++ b/.github/workflows/check_misc.yml @@ -18,7 +18,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 with: token: ${{ (github.repository == 'ruby/ruby' && !startsWith(github.event_name, 'pull')) && secrets.MATZBOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 170eb120c21d7a..38148d4f667ce6 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -41,10 +41,10 @@ jobs: if: >- ${{!(false || contains(github.event.head_commit.message, '[DOC]') - || contains(github.event.head_commit.message, 'Documentation') + || contains(github.event.head_commit.message, 'Document') || contains(github.event.pull_request.title, '[DOC]') - || contains(github.event.pull_request.title, 'Documentation') - || contains(github.event.pull_request.labels.*.name, 'Documentation') + || contains(github.event.pull_request.title, 'Document') + || contains(github.event.pull_request.labels.*.name, 'Document') || (github.event_name == 'push' && github.actor == 'dependabot[bot]') )}} @@ -59,11 +59,11 @@ jobs: os: ubuntu-latest # ruby analysis used large memory. We need to use a larger runner. - language: ruby - os: ${{ github.repository == 'ruby/ruby' && 'macos-arm-oss' || 'macos-14' }} + os: ${{ github.repository == 'ruby/ruby' && 'macos-arm-oss' || 'ubuntu-latest' }} steps: - name: Checkout repository - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 - name: Install libraries if: ${{ contains(matrix.os, 'macos') }} @@ -80,15 +80,15 @@ jobs: run: sudo rm /usr/lib/ruby/vendor_ruby/rubygems/defaults/operating_system.rb - name: Initialize CodeQL - uses: github/codeql-action/init@8a470fddafa5cbb6266ee11b37ef4d8aae19c571 # v3.24.6 + uses: github/codeql-action/init@4355270be187e1b672a7a1c7c7bae5afdc1ab94a # v3.24.10 with: languages: ${{ matrix.language }} - name: Autobuild - uses: github/codeql-action/autobuild@8a470fddafa5cbb6266ee11b37ef4d8aae19c571 # v3.24.6 + uses: github/codeql-action/autobuild@4355270be187e1b672a7a1c7c7bae5afdc1ab94a # v3.24.10 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@8a470fddafa5cbb6266ee11b37ef4d8aae19c571 # v3.24.6 + uses: github/codeql-action/analyze@4355270be187e1b672a7a1c7c7bae5afdc1ab94a # v3.24.10 with: category: '/language:${{ matrix.language }}' upload: False @@ -118,7 +118,7 @@ jobs: continue-on-error: true - name: Upload SARIF - uses: github/codeql-action/upload-sarif@8a470fddafa5cbb6266ee11b37ef4d8aae19c571 # v3.24.6 + uses: github/codeql-action/upload-sarif@4355270be187e1b672a7a1c7c7bae5afdc1ab94a # v3.24.10 with: sarif_file: sarif-results/${{ matrix.language }}.sarif continue-on-error: true diff --git a/.github/workflows/compilers.yml b/.github/workflows/compilers.yml index f074141134dd9f..0eebb0eea21939 100644 --- a/.github/workflows/compilers.yml +++ b/.github/workflows/compilers.yml @@ -27,7 +27,7 @@ concurrency: # environment variables (plus the "echo $GITHUB_ENV" hack) is to reroute that # restriction. env: - default_cc: clang-17 + default_cc: clang-18 append_cc: '' # -O1 is faster than -O3 in our tests... Majority of time are consumed trying @@ -81,6 +81,7 @@ jobs: optflags: '-O2' shared: disable # check: true + - { name: clang-19, env: { default_cc: clang-19 } } - { name: clang-18, env: { default_cc: clang-18 } } - { name: clang-17, env: { default_cc: clang-17 } } - { name: clang-16, env: { default_cc: clang-16 } } @@ -213,16 +214,16 @@ jobs: runs-on: ubuntu-latest container: - image: ghcr.io/ruby/ruby-ci-image:${{ matrix.entry.container || matrix.entry.env.default_cc || 'clang-17' }} + image: ghcr.io/ruby/ruby-ci-image:${{ matrix.entry.container || matrix.entry.env.default_cc || 'clang-18' }} options: --user root if: >- ${{!(false || contains(github.event.head_commit.message, '[DOC]') - || contains(github.event.head_commit.message, 'Documentation') + || contains(github.event.head_commit.message, 'Document') || contains(github.event.pull_request.title, '[DOC]') - || contains(github.event.pull_request.title, 'Documentation') - || contains(github.event.pull_request.labels.*.name, 'Documentation') + || contains(github.event.pull_request.title, 'Document') + || contains(github.event.pull_request.labels.*.name, 'Document') || (github.event_name == 'push' && github.actor == 'dependabot[bot]') )}} @@ -232,7 +233,7 @@ jobs: - run: id working-directory: - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 with: sparse-checkout-cone-mode: false sparse-checkout: /.github @@ -265,7 +266,7 @@ jobs: if: ${{ (matrix.entry.static-exts || '') != '' }} - name: Clean up ext/Setup - uses: gacts/run-and-post-run@674528335da98a7afc80915ff2b4b860a0b3553a # v1.4.0 + uses: gacts/run-and-post-run@7aec950f3b114c4fcf6012070c3709ecff0eb6f8 # v1.4.0 with: shell: bash working-directory: build diff --git a/.github/workflows/dependabot_automerge.yml b/.github/workflows/dependabot_automerge.yml index 5858507ae73763..fa50511eb21d50 100644 --- a/.github/workflows/dependabot_automerge.yml +++ b/.github/workflows/dependabot_automerge.yml @@ -11,11 +11,11 @@ jobs: steps: - name: Dependabot metadata - uses: dependabot/fetch-metadata@c9c4182bf1b97f5224aee3906fd373f6b61b4526 # v1.6.0 + uses: dependabot/fetch-metadata@0fb21704c18a42ce5aa8d720ea4b912f5e6babef # v2.0.0 id: metadata - name: Wait for status checks - uses: lewagon/wait-on-check-action@595dabb3acf442d47e29c9ec9ba44db0c6bdd18f # v1.3.3 + uses: lewagon/wait-on-check-action@ccfb013c15c8afb7bf2b7c028fb74dc5a068cccc # v1.3.4 with: repo-token: ${{ secrets.MATZBOT_GITHUB_TOKEN }} ref: ${{ github.event.pull_request.head.sha || github.sha }} diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 99f9257751c1d0..87231228bcef83 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -24,39 +24,35 @@ jobs: make: strategy: matrix: - test_task: ['check'] - test_opts: [''] - os: - - macos-12 - - macos-13 - - ${{ github.repository == 'ruby/ruby' && 'macos-arm-oss' || 'macos-14' }} include: + - test_task: check - test_task: test-all test_opts: --repeat-count=2 - os: ${{ github.repository == 'ruby/ruby' && 'macos-arm-oss' || 'macos-14' }} - test_task: test-bundler-parallel - os: ${{ github.repository == 'ruby/ruby' && 'macos-arm-oss' || 'macos-14' }} - test_task: test-bundled-gems - os: ${{ github.repository == 'ruby/ruby' && 'macos-arm-oss' || 'macos-14' }} + - test_task: check + os: macos-12 + - test_task: check + os: macos-13 fail-fast: false env: GITPULLOPTIONS: --no-tags origin ${{ github.ref }} - runs-on: ${{ matrix.os }} + runs-on: ${{ matrix.os || (github.repository == 'ruby/ruby' && 'macos-arm-oss' || 'macos-14')}} if: >- ${{!(false || contains(github.event.head_commit.message, '[DOC]') - || contains(github.event.head_commit.message, 'Documentation') + || contains(github.event.head_commit.message, 'Document') || contains(github.event.pull_request.title, '[DOC]') - || contains(github.event.pull_request.title, 'Documentation') - || contains(github.event.pull_request.labels.*.name, 'Documentation') + || contains(github.event.pull_request.title, 'Document') + || contains(github.event.pull_request.labels.*.name, 'Document') || (github.event_name == 'push' && github.actor == 'dependabot[bot]') )}} steps: - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 with: sparse-checkout-cone-mode: false sparse-checkout: /.github @@ -90,14 +86,14 @@ jobs: if: ${{ matrix.test_task == 'check' && matrix.skipped_tests }} - name: Set up Launchable - id: enable-launchable uses: ./.github/actions/launchable/setup with: - os: ${{ matrix.os }} + os: ${{ matrix.os || (github.repository == 'ruby/ruby' && 'macos-arm-oss' || 'macos-14')}} test-opts: ${{ matrix.test_opts }} launchable-token: ${{ secrets.LAUNCHABLE_TOKEN }} builddir: build srcdir: src + continue-on-error: true - name: Set extra test options run: echo "TESTS=$TESTS ${{ matrix.test_opts }}" >> $GITHUB_ENV @@ -109,7 +105,7 @@ jobs: timeout-minutes: 60 env: RUBY_TESTOPTS: '-q --tty=no' - TEST_BUNDLED_GEMS_ALLOW_FAILURES: 'minitest,test-unit,debug,bigdecimal,drb,typeprof' + TEST_BUNDLED_GEMS_ALLOW_FAILURES: '' PRECHECK_BUNDLED_GEMS: 'no' - name: make skipped tests diff --git a/.github/workflows/mingw.yml b/.github/workflows/mingw.yml index 67564958c1efe3..1ed83de53521a3 100644 --- a/.github/workflows/mingw.yml +++ b/.github/workflows/mingw.yml @@ -57,16 +57,16 @@ jobs: if: >- ${{!(false || contains(github.event.head_commit.message, '[DOC]') - || contains(github.event.head_commit.message, 'Documentation') + || contains(github.event.head_commit.message, 'Document') || contains(github.event.pull_request.title, '[DOC]') - || contains(github.event.pull_request.title, 'Documentation') - || contains(github.event.pull_request.labels.*.name, 'Documentation') + || contains(github.event.pull_request.title, 'Document') + || contains(github.event.pull_request.labels.*.name, 'Document') || (github.event_name == 'push' && github.actor == 'dependabot[bot]') )}} steps: - name: Set up Ruby & MSYS2 - uses: ruby/setup-ruby@d4526a55538b775af234ba4af27118ed6f8f6677 # v1.172.0 + uses: ruby/setup-ruby@5f19ec79cedfadb78ab837f95b87734d0003c899 # v1.173.0 with: ruby-version: ${{ matrix.baseruby }} @@ -97,7 +97,7 @@ jobs: $result working-directory: - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 with: sparse-checkout-cone-mode: false sparse-checkout: /.github diff --git a/.github/workflows/prism.yml b/.github/workflows/prism.yml index 87862b8a34b46e..adb9206c369178 100644 --- a/.github/workflows/prism.yml +++ b/.github/workflows/prism.yml @@ -47,15 +47,15 @@ jobs: if: >- ${{!(false || contains(github.event.head_commit.message, '[DOC]') - || contains(github.event.head_commit.message, 'Documentation') + || contains(github.event.head_commit.message, 'Document') || contains(github.event.pull_request.title, '[DOC]') - || contains(github.event.pull_request.title, 'Documentation') - || contains(github.event.pull_request.labels.*.name, 'Documentation') + || contains(github.event.pull_request.title, 'Document') + || contains(github.event.pull_request.labels.*.name, 'Document') || (github.event_name == 'push' && github.actor == 'dependabot[bot]') )}} steps: - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 with: sparse-checkout-cone-mode: false sparse-checkout: /.github @@ -92,16 +92,16 @@ jobs: timeout-minutes: 40 env: GNUMAKEFLAGS: '' - RUBY_TESTOPTS: '-q --tty=no --excludes-dir="../src/test/.excludes-prism" --exclude="test_ast.rb" --exclude="test_regexp.rb" --exclude="error_highlight/test_error_highlight.rb" --exclude="irb/test_context.rb" --exclude="prism/encoding_test.rb" --exclude="prism/unescape_test.rb"' + RUBY_TESTOPTS: '-q --tty=no --excludes-dir="../src/test/.excludes-prism" --exclude="test_ast.rb" --exclude="error_highlight/test_error_highlight.rb" --exclude="prism/encoding_test.rb" --exclude="prism/locals_test.rb" --exclude="prism/newline_test.rb"' RUN_OPTS: ${{ matrix.run_opts }} - # - name: make test-spec - # run: | - # $SETARCH make -s test-spec RUN_OPTS="$RUN_OPTS" - # timeout-minutes: 10 - # env: - # GNUMAKEFLAGS: '' - # RUN_OPTS: ${{ matrix.run_opts }} + - name: make test-prism-spec + run: | + $SETARCH make -s test-prism-spec SPECOPTS="$SPECOPTS" + timeout-minutes: 10 + env: + GNUMAKEFLAGS: '' + SPECOPTS: "-T -W:no-experimental -T --parser=prism" - uses: ./.github/actions/slack with: diff --git a/.github/workflows/rjit-bindgen.yml b/.github/workflows/rjit-bindgen.yml index eb023ecd534527..65f7b080a3d678 100644 --- a/.github/workflows/rjit-bindgen.yml +++ b/.github/workflows/rjit-bindgen.yml @@ -38,20 +38,20 @@ jobs: if: >- ${{!(false || contains(github.event.head_commit.message, '[DOC]') - || contains(github.event.head_commit.message, 'Documentation') + || contains(github.event.head_commit.message, 'Document') || contains(github.event.pull_request.title, '[DOC]') - || contains(github.event.pull_request.title, 'Documentation') - || contains(github.event.pull_request.labels.*.name, 'Documentation') + || contains(github.event.pull_request.title, 'Document') + || contains(github.event.pull_request.labels.*.name, 'Document') || (github.event_name == 'push' && github.actor == 'dependabot[bot]') )}} steps: - name: Set up Ruby - uses: ruby/setup-ruby@d4526a55538b775af234ba4af27118ed6f8f6677 # v1.172.0 + uses: ruby/setup-ruby@5f19ec79cedfadb78ab837f95b87734d0003c899 # v1.173.0 with: ruby-version: '3.1' - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 with: sparse-checkout-cone-mode: false sparse-checkout: /.github diff --git a/.github/workflows/rjit.yml b/.github/workflows/rjit.yml index 5fe25c0afbb909..b393074919ad11 100644 --- a/.github/workflows/rjit.yml +++ b/.github/workflows/rjit.yml @@ -47,15 +47,15 @@ jobs: if: >- ${{!(false || contains(github.event.head_commit.message, '[DOC]') - || contains(github.event.head_commit.message, 'Documentation') + || contains(github.event.head_commit.message, 'Document') || contains(github.event.pull_request.title, '[DOC]') - || contains(github.event.pull_request.title, 'Documentation') - || contains(github.event.pull_request.labels.*.name, 'Documentation') + || contains(github.event.pull_request.title, 'Document') + || contains(github.event.pull_request.labels.*.name, 'Document') || (github.event_name == 'push' && github.actor == 'dependabot[bot]') )}} steps: - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 with: sparse-checkout-cone-mode: false sparse-checkout: /.github diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index 18ff7719d3a381..69b31e6b698783 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -32,7 +32,7 @@ jobs: steps: - name: 'Checkout code' - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 with: persist-credentials: false @@ -67,6 +67,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: 'Upload to code-scanning' - uses: github/codeql-action/upload-sarif@8a470fddafa5cbb6266ee11b37ef4d8aae19c571 # v2.1.27 + uses: github/codeql-action/upload-sarif@4355270be187e1b672a7a1c7c7bae5afdc1ab94a # v2.1.27 with: sarif_file: results.sarif diff --git a/.github/workflows/spec_guards.yml b/.github/workflows/spec_guards.yml index cc6ee3f81a3591..f4d8c378373eb0 100644 --- a/.github/workflows/spec_guards.yml +++ b/.github/workflows/spec_guards.yml @@ -27,10 +27,10 @@ jobs: if: >- ${{!(false || contains(github.event.head_commit.message, '[DOC]') - || contains(github.event.head_commit.message, 'Documentation') + || contains(github.event.head_commit.message, 'Document') || contains(github.event.pull_request.title, '[DOC]') - || contains(github.event.pull_request.title, 'Documentation') - || contains(github.event.pull_request.labels.*.name, 'Documentation') + || contains(github.event.pull_request.title, 'Document') + || contains(github.event.pull_request.labels.*.name, 'Document') || (github.event_name == 'push' && github.actor == 'dependabot[bot]') )}} @@ -45,9 +45,9 @@ jobs: - ruby-3.3 steps: - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 - - uses: ruby/setup-ruby@d4526a55538b775af234ba4af27118ed6f8f6677 # v1.172.0 + - uses: ruby/setup-ruby@5f19ec79cedfadb78ab837f95b87734d0003c899 # v1.173.0 with: ruby-version: ${{ matrix.ruby }} bundler: none diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml index 279fb4ade8022e..419684e7f312ce 100644 --- a/.github/workflows/ubuntu.yml +++ b/.github/workflows/ubuntu.yml @@ -24,45 +24,39 @@ jobs: make: strategy: matrix: - os: [ubuntu-22.04, ubuntu-20.04] - test_task: [check] - arch: [''] - configure: ['cppflags=-DVM_CHECK_MODE'] - # specifying other jobs with `include` to avoid redundant tests include: + - test_task: check + configure: 'cppflags=-DVM_CHECK_MODE' - test_task: check arch: i686 - os: ubuntu-22.04 - test_task: check configure: '--disable-yjit' - os: ubuntu-22.04 - test_task: check configure: '--enable-shared --enable-load-relative' - os: ubuntu-22.04 - test_task: test-bundler-parallel - os: ubuntu-22.04 - test_task: test-bundled-gems - os: ubuntu-22.04 + - test_task: check + os: ubuntu-20.04 fail-fast: false env: GITPULLOPTIONS: --no-tags origin ${{ github.ref }} RUBY_DEBUG: ci - runs-on: ${{ matrix.os }} + runs-on: ${{ matrix.os || 'ubuntu-22.04' }} if: >- ${{!(false || contains(github.event.head_commit.message, '[DOC]') - || contains(github.event.head_commit.message, 'Documentation') + || contains(github.event.head_commit.message, 'Document') || contains(github.event.pull_request.title, '[DOC]') - || contains(github.event.pull_request.title, 'Documentation') - || contains(github.event.pull_request.labels.*.name, 'Documentation') + || contains(github.event.pull_request.title, 'Document') + || contains(github.event.pull_request.labels.*.name, 'Document') || (github.event_name == 'push' && github.actor == 'dependabot[bot]') )}} steps: - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 with: sparse-checkout-cone-mode: false sparse-checkout: /.github @@ -71,7 +65,7 @@ jobs: with: arch: ${{ matrix.arch }} - - uses: ruby/setup-ruby@d4526a55538b775af234ba4af27118ed6f8f6677 # v1.172.0 + - uses: ruby/setup-ruby@5f19ec79cedfadb78ab837f95b87734d0003c899 # v1.173.0 with: ruby-version: '3.0' bundler: none @@ -107,14 +101,14 @@ jobs: if: ${{ matrix.test_task == 'check' && matrix.skipped_tests }} - name: Set up Launchable - id: enable-launchable uses: ./.github/actions/launchable/setup with: - os: ${{ matrix.os }} + os: ${{ matrix.os || 'ubuntu-22.04' }} test-opts: ${{ matrix.configure }} launchable-token: ${{ secrets.LAUNCHABLE_TOKEN }} builddir: build srcdir: src + continue-on-error: true - name: make ${{ matrix.test_task }} run: >- @@ -124,7 +118,7 @@ jobs: timeout-minutes: 40 env: RUBY_TESTOPTS: '-q --tty=no' - TEST_BUNDLED_GEMS_ALLOW_FAILURES: 'minitest,test-unit,debug,bigdecimal,drb,typeprof' + TEST_BUNDLED_GEMS_ALLOW_FAILURES: '' PRECHECK_BUNDLED_GEMS: 'no' - name: make skipped tests diff --git a/.github/workflows/wasm.yml b/.github/workflows/wasm.yml index eef1c5ea4e759b..5ec4bfafaed22e 100644 --- a/.github/workflows/wasm.yml +++ b/.github/workflows/wasm.yml @@ -53,15 +53,15 @@ jobs: if: >- ${{!(false || contains(github.event.head_commit.message, '[DOC]') - || contains(github.event.head_commit.message, 'Documentation') + || contains(github.event.head_commit.message, 'Document') || contains(github.event.pull_request.title, '[DOC]') - || contains(github.event.pull_request.title, 'Documentation') - || contains(github.event.pull_request.labels.*.name, 'Documentation') + || contains(github.event.pull_request.title, 'Document') + || contains(github.event.pull_request.labels.*.name, 'Document') || (github.event_name == 'push' && github.actor == 'dependabot[bot]') )}} steps: - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 with: sparse-checkout-cone-mode: false sparse-checkout: /.github @@ -100,7 +100,7 @@ jobs: run: | echo "WASI_SDK_PATH=/opt/wasi-sdk" >> $GITHUB_ENV - - uses: ruby/setup-ruby@d4526a55538b775af234ba4af27118ed6f8f6677 # v1.172.0 + - uses: ruby/setup-ruby@5f19ec79cedfadb78ab837f95b87734d0003c899 # v1.173.0 with: ruby-version: '3.0' bundler: none @@ -120,7 +120,7 @@ jobs: --host wasm32-unknown-wasi \ --with-baseruby=$PWD/../baseruby/install/bin/ruby \ --with-static-linked-ext \ - --with-ext=bigdecimal,cgi/escape,continuation,coverage,date,dbm,digest/bubblebabble,digest,digest/md5,digest/rmd160,digest/sha1,digest/sha2,etc,fcntl,fiber,gdbm,json,json/generator,json/parser,nkf,objspace,pathname,racc/cparse,rbconfig/sizeof,ripper,stringio,strscan,monitor \ + --with-ext=cgi/escape,continuation,coverage,date,digest/bubblebabble,digest,digest/md5,digest/rmd160,digest/sha1,digest/sha2,etc,fcntl,json,json/generator,json/parser,objspace,pathname,rbconfig/sizeof,ripper,stringio,strscan,monitor \ LDFLAGS=" \ -Xlinker --stack-first \ -Xlinker -z -Xlinker stack-size=16777216 \ diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 6e2a4485ab0941..56e2711b5b9d5d 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -42,10 +42,10 @@ jobs: if: >- ${{!(false || contains(github.event.head_commit.message, '[DOC]') - || contains(github.event.head_commit.message, 'Documentation') + || contains(github.event.head_commit.message, 'Document') || contains(github.event.pull_request.title, '[DOC]') - || contains(github.event.pull_request.title, 'Documentation') - || contains(github.event.pull_request.labels.*.name, 'Documentation') + || contains(github.event.pull_request.title, 'Document') + || contains(github.event.pull_request.labels.*.name, 'Document') || (github.event_name == 'push' && github.actor == 'dependabot[bot]') )}} @@ -91,13 +91,13 @@ jobs: ${{ steps.find-tools.outputs.needs }} if: ${{ steps.find-tools.outputs.needs != '' }} - - uses: ruby/setup-ruby@d4526a55538b775af234ba4af27118ed6f8f6677 # v1.172.0 + - uses: ruby/setup-ruby@5f19ec79cedfadb78ab837f95b87734d0003c899 # v1.173.0 with: ruby-version: '3.0' bundler: none windows-toolchain: none - - uses: actions/cache@ab5e6d0c87105b4c9c2047343972218f562e4319 # v4.0.1 + - uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2 with: path: C:\vcpkg\downloads key: ${{ runner.os }}-vcpkg-download-${{ env.OS_VER }}-${{ github.sha }} @@ -105,7 +105,7 @@ jobs: ${{ runner.os }}-vcpkg-download-${{ env.OS_VER }}- ${{ runner.os }}-vcpkg-download- - - uses: actions/cache@ab5e6d0c87105b4c9c2047343972218f562e4319 # v4.0.1 + - uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2 with: path: C:\vcpkg\installed key: ${{ runner.os }}-vcpkg-installed-${{ env.OS_VER }}-${{ github.sha }} @@ -123,7 +123,7 @@ jobs: Join-Path (Resolve-Path ~).Path "scoop\shims" >> $Env:GITHUB_PATH shell: pwsh - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 with: sparse-checkout-cone-mode: false sparse-checkout: /.github diff --git a/.github/workflows/yjit-macos.yml b/.github/workflows/yjit-macos.yml index 685dc495c71772..5e209cc4cae5a1 100644 --- a/.github/workflows/yjit-macos.yml +++ b/.github/workflows/yjit-macos.yml @@ -29,15 +29,15 @@ jobs: if: >- ${{!(false || contains(github.event.head_commit.message, '[DOC]') - || contains(github.event.head_commit.message, 'Documentation') + || contains(github.event.head_commit.message, 'Document') || contains(github.event.pull_request.title, '[DOC]') - || contains(github.event.pull_request.title, 'Documentation') - || contains(github.event.pull_request.labels.*.name, 'Documentation') + || contains(github.event.pull_request.title, 'Document') + || contains(github.event.pull_request.labels.*.name, 'Document') || (github.event_name == 'push' && github.actor == 'dependabot[bot]') )}} steps: - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 - run: RUST_BACKTRACE=1 cargo test working-directory: yjit @@ -71,15 +71,15 @@ jobs: if: >- ${{!(false || contains(github.event.head_commit.message, '[DOC]') - || contains(github.event.head_commit.message, 'Documentation') + || contains(github.event.head_commit.message, 'Document') || contains(github.event.pull_request.title, '[DOC]') - || contains(github.event.pull_request.title, 'Documentation') - || contains(github.event.pull_request.labels.*.name, 'Documentation') + || contains(github.event.pull_request.title, 'Document') + || contains(github.event.pull_request.labels.*.name, 'Document') || (github.event_name == 'push' && github.actor == 'dependabot[bot]') )}} steps: - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 with: sparse-checkout-cone-mode: false sparse-checkout: /.github @@ -93,6 +93,8 @@ jobs: builddir: build makeup: true dummy-files: ${{ matrix.test_task == 'check' }} + # Set fetch-depth: 10 so that Launchable can receive commits information. + fetch-depth: 10 - name: Run configure run: ../src/configure -C --disable-install-doc ${{ matrix.configure }} @@ -112,9 +114,20 @@ jobs: echo "TESTS=${TESTS}" >> $GITHUB_ENV if: ${{ matrix.test_task == 'check' && matrix.skipped_tests }} + - name: Set up Launchable + uses: ./.github/actions/launchable/setup + with: + os: ${{ github.repository == 'ruby/ruby' && 'macos-arm-oss' || 'macos-14' }} + test-opts: ${{ matrix.configure }} + launchable-token: ${{ secrets.LAUNCHABLE_TOKEN }} + builddir: build + srcdir: src + continue-on-error: true + - name: make ${{ matrix.test_task }} - run: | + run: >- make -s ${{ matrix.test_task }} ${TESTS:+TESTS="$TESTS"} + RUN_OPTS="$RUN_OPTS" timeout-minutes: 60 env: RUBY_TESTOPTS: '-q --tty=no' diff --git a/.github/workflows/yjit-ubuntu.yml b/.github/workflows/yjit-ubuntu.yml index cb11061a155cc8..92ff9eadaed607 100644 --- a/.github/workflows/yjit-ubuntu.yml +++ b/.github/workflows/yjit-ubuntu.yml @@ -31,12 +31,12 @@ jobs: ${{!(false || contains(github.event.head_commit.message, '[DOC]') || contains(github.event.pull_request.title, '[DOC]') - || contains(github.event.pull_request.labels.*.name, 'Documentation') + || contains(github.event.pull_request.labels.*.name, 'Document') || (github.event_name == 'push' && github.actor == 'dependabot[bot]') )}} steps: - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 # For now we can't run cargo test --offline because it complains about the # capstone dependency, even though the dependency is optional @@ -63,12 +63,12 @@ jobs: ${{!(false || contains(github.event.head_commit.message, '[DOC]') || contains(github.event.pull_request.title, '[DOC]') - || contains(github.event.pull_request.labels.*.name, 'Documentation') + || contains(github.event.pull_request.labels.*.name, 'Document') || (github.event_name == 'push' && github.actor == 'dependabot[bot]') )}} steps: - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 # Check that we don't have linting errors in release mode, too - run: cargo clippy --all-targets --all-features @@ -117,15 +117,15 @@ jobs: if: >- ${{!(false || contains(github.event.head_commit.message, '[DOC]') - || contains(github.event.head_commit.message, 'Documentation') + || contains(github.event.head_commit.message, 'Document') || contains(github.event.pull_request.title, '[DOC]') - || contains(github.event.pull_request.title, 'Documentation') - || contains(github.event.pull_request.labels.*.name, 'Documentation') + || contains(github.event.pull_request.title, 'Document') + || contains(github.event.pull_request.labels.*.name, 'Document') || (github.event_name == 'push' && github.actor == 'dependabot[bot]') )}} steps: - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 with: sparse-checkout-cone-mode: false sparse-checkout: /.github @@ -138,12 +138,14 @@ jobs: builddir: build makeup: true dummy-files: ${{ matrix.test_task == 'check' }} + # Set fetch-depth: 10 so that Launchable can receive commits information. + fetch-depth: 10 - name: Install Rust if: ${{ matrix.rust_version }} run: rustup install ${{ matrix.rust_version }} --profile minimal - - uses: ruby/setup-ruby@d4526a55538b775af234ba4af27118ed6f8f6677 # v1.172.0 + - uses: ruby/setup-ruby@5f19ec79cedfadb78ab837f95b87734d0003c899 # v1.173.0 with: ruby-version: '3.0' bundler: none @@ -165,12 +167,25 @@ jobs: - name: Check YJIT enabled run: ./miniruby --yjit -v | grep "+YJIT" + - name: Set up Launchable + uses: ./.github/actions/launchable/setup + with: + os: ubuntu-20.04 + test-opts: ${{ matrix.configure }} + launchable-token: ${{ secrets.LAUNCHABLE_TOKEN }} + builddir: build + srcdir: src + continue-on-error: true + - name: make ${{ matrix.test_task }} - run: make -s -j ${{ matrix.test_task }} RUN_OPTS="$RUN_OPTS" MSPECOPT=--debug YJIT_BENCH_OPTS="$YJIT_BENCH_OPTS" YJIT_BINDGEN_DIFF_OPTS="$YJIT_BINDGEN_DIFF_OPTS" + run: >- + make -s -j ${{ matrix.test_task }} ${TESTS:+TESTS="$TESTS"} + RUN_OPTS="$RUN_OPTS" MSPECOPT=--debug + YJIT_BENCH_OPTS="$YJIT_BENCH_OPTS" YJIT_BINDGEN_DIFF_OPTS="$YJIT_BINDGEN_DIFF_OPTS" timeout-minutes: 60 env: RUBY_TESTOPTS: '-q --tty=no' - TEST_BUNDLED_GEMS_ALLOW_FAILURES: 'minitest,test-unit,debug,bigdecimal,drb,typeprof' + TEST_BUNDLED_GEMS_ALLOW_FAILURES: '' PRECHECK_BUNDLED_GEMS: 'no' SYNTAX_SUGGEST_TIMEOUT: '5' YJIT_BINDGEN_DIFF_OPTS: '--exit-code' diff --git a/.travis.yml b/.travis.yml index 5298885697eb05..06de3dd493263f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -121,6 +121,7 @@ matrix: fast_finish: true before_script: + - lscpu - ./autogen.sh - mkdir build - cd build diff --git a/LEGAL b/LEGAL index e352c55ee50005..c931291c8ad9b6 100644 --- a/LEGAL +++ b/LEGAL @@ -58,12 +58,12 @@ mentioned below. [ccan/list/list.h] - This file is licensed under the {MIT License}[rdoc-label:label-MIT+License]. + This file is licensed under the {MIT License}[rdoc-ref:@MIT+License]. [coroutine] Unless otherwise specified, these files are licensed under the - {MIT License}[rdoc-label:label-MIT+License]. + {MIT License}[rdoc-ref:@MIT+License]. [include/ruby/onigmo.h] [include/ruby/oniguruma.h] @@ -546,7 +546,7 @@ mentioned below. [vsnprintf.c] - This file is under the {old-style BSD license}[rdoc-label:label-Old-style+BSD+license]. + This file is under the {old-style BSD license}[rdoc-ref:@Old-style+BSD+license]. >>> Copyright (c) 1990, 1993:: @@ -577,7 +577,7 @@ mentioned below. [missing/crypt.c] - This file is under the {old-style BSD license}[rdoc-label:label-Old-style+BSD+license]. + This file is under the {old-style BSD license}[rdoc-ref:@Old-style+BSD+license]. >>> Copyright (c) 1989, 1993:: @@ -588,7 +588,7 @@ mentioned below. [missing/setproctitle.c] - This file is under the {old-style BSD license}[rdoc-label:label-Old-style+BSD+license]. + This file is under the {old-style BSD license}[rdoc-ref:@Old-style+BSD+license]. >>> Copyright 2003:: Damien Miller @@ -879,7 +879,7 @@ mentioned below. >>> RubyGems is copyrighted free software by Chad Fowler, Rich Kilmer, Jim Weirich and others. You can redistribute it and/or modify it under - either the terms of the {MIT license}[rdoc-label:label-MIT+License], or the conditions + either the terms of the {MIT license}[rdoc-ref:@MIT+License], or the conditions below: 1. You may make and give away verbatim copies of the source form of the @@ -941,7 +941,7 @@ mentioned below. Portions copyright (c) 2010:: Andre Arko Portions copyright (c) 2009:: Engine Yard - {MIT License}[rdoc-label:label-MIT+License] + {MIT License}[rdoc-ref:@MIT+License] [lib/bundler/vendor/thor] @@ -950,16 +950,16 @@ mentioned below. >>> Copyright (c) 2008 Yehuda Katz, Eric Hodel, et al. - {MIT License}[rdoc-label:label-MIT+License] + {MIT License}[rdoc-ref:@MIT+License] -[lib/rubygems/resolver/molinillo] +[lib/rubygems/vendor/molinillo] molinillo is under the following license. >>> Copyright (c) 2014 Samuel E. Giddins segiddins@segiddins.me - {MIT License}[rdoc-label:label-MIT+License] + {MIT License}[rdoc-ref:@MIT+License] [lib/bundler/vendor/pub_grub] @@ -968,7 +968,7 @@ mentioned below. >>> Copyright (c) 2018 John Hawthorn - {MIT License}[rdoc-label:label-MIT+License] + {MIT License}[rdoc-ref:@MIT+License] [lib/bundler/vendor/connection_pool] @@ -977,7 +977,7 @@ mentioned below. >>> Copyright (c) 2011 Mike Perham - {MIT License}[rdoc-label:label-MIT+License] + {MIT License}[rdoc-ref:@MIT+License] [lib/bundler/vendor/net-http-persistent] @@ -986,7 +986,7 @@ mentioned below. >>> Copyright (c) Eric Hodel, Aaron Patterson - {MIT License}[rdoc-label:label-MIT+License] + {MIT License}[rdoc-ref:@MIT+License] [lib/did_you_mean] [lib/did_you_mean.rb] @@ -997,7 +997,7 @@ mentioned below. >>> Copyright (c) 2014-2016 Yuki Nishijima - {MIT License}[rdoc-label:label-MIT+License] + {MIT License}[rdoc-ref:@MIT+License] [lib/error_highlight] [lib/error_highlight.rb] @@ -1008,7 +1008,7 @@ mentioned below. >>> Copyright (c) 2021 Yusuke Endoh - {MIT License}[rdoc-label:label-MIT+License] + {MIT License}[rdoc-ref:@MIT+License] [benchmark/so_ackermann.rb] [benchmark/so_array.rb] diff --git a/NEWS.md b/NEWS.md index aa8e0498238603..bdb4fa1eb1397b 100644 --- a/NEWS.md +++ b/NEWS.md @@ -7,17 +7,31 @@ Note that each entry is kept to a minimum, see links for details. ## Language changes +* String literals in files without a `frozen_string_literal` comment now behave + as if they were frozen. If they are mutated a deprecation warning is emitted. + These warnings can be enabled with `-W:deprecated` or by setting `Warning[:deprecated] = true`. + To disable this change, you can run Ruby with the `--disable-frozen-string-literal` + command line argument. [[Feature #20205]] + * `it` is added to reference a block parameter. [[Feature #18980]] * Keyword splatting `nil` when calling methods is now supported. - `**nil` is treated similar to `**{}`, passing no keywords, - and not calling any conversion methods. - [[Bug #20064]] + `**nil` is treated similarly to `**{}`, passing no keywords, + and not calling any conversion methods. [[Bug #20064]] + +* Block passing is no longer allowed in index. [[Bug #19918]] + +* Keyword arguments are no longer allowed in index. [[Bug #20218]] ## Core classes updates Note: We're only listing outstanding class updates. +* Exception + + * Exception#set_backtrace now accepts arrays of `Thread::Backtrace::Location`. + `Kernel#raise`, `Thread#raise` and `Fiber#raise` also accept this new format. [[Feature #13557]] + ## Stdlib updates The following default gems are updated. @@ -28,29 +42,33 @@ The following default gems are updated. * fiddle 1.1.3 * io-console 0.7.2 * irb 1.12.0 +* json 2.7.2 * net-http 0.4.1 -* prism 0.24.0 -* reline 0.4.3 +* prism 0.25.0 +* rdoc 6.6.3.1 +* reline 0.5.1 +* resolv 0.4.0 * stringio 3.1.1 * strscan 3.1.1 The following bundled gems are updated. -* minitest 5.22.2 +* minitest 5.22.3 +* rake 13.2.1 * test-unit 3.6.2 * net-ftp 0.3.4 * net-imap 0.4.10 -* net-smtp 0.4.0.1 +* net-smtp 0.5.0 * rbs 3.4.4 * typeprof 0.21.11 -* debug 1.9.1 +* debug 1.9.2 The following bundled gems are promoted from default gems. * mutex_m 0.2.0 * getoptlong 0.2.1 * base64 0.2.0 -* bigdecimal 3.1.6 +* bigdecimal 3.1.7 * observer 0.1.2 * abbrev 0.1.2 * resolv-replace 0.1.1 @@ -58,7 +76,7 @@ The following bundled gems are promoted from default gems. * drb 2.2.1 * nkf 0.2.0 * syslog 0.1.2 -* csv 3.2.8 +* csv 3.3.0 See GitHub releases like [GitHub Releases of Logger](https://github.com/ruby/logger/releases) or changelog for details of the default gems or bundled gems. @@ -84,14 +102,23 @@ See GitHub releases like [GitHub Releases of Logger](https://github.com/ruby/log ## C API updates +* `rb_newobj` and `rb_newobj_of` (and corresponding macros `RB_NEWOBJ`, `RB_NEWOBJ_OF`, `NEWOBJ`, `NEWOBJ_OF`) have been removed. [[Feature #20265]] +* Removed deprecated function `rb_gc_force_recycle`. [[Feature #18290]] + ## Implementation improvements * `Array#each` is rewritten in Ruby for better performance [[Feature #20182]]. ## JIT +[Feature #13557]: https://bugs.ruby-lang.org/issues/13557 [Feature #16495]: https://bugs.ruby-lang.org/issues/16495 +[Feature #18290]: https://bugs.ruby-lang.org/issues/18290 [Feature #18980]: https://bugs.ruby-lang.org/issues/18980 [Feature #19117]: https://bugs.ruby-lang.org/issues/19117 +[Bug #19918]: https://bugs.ruby-lang.org/issues/19918 [Bug #20064]: https://bugs.ruby-lang.org/issues/20064 [Feature #20182]: https://bugs.ruby-lang.org/issues/20182 +[Feature #20205]: https://bugs.ruby-lang.org/issues/20205 +[Bug #20218]: https://bugs.ruby-lang.org/issues/20218 +[Feature #20265]: https://bugs.ruby-lang.org/issues/20265 diff --git a/README.md b/README.md index 8fb3786691f052..eb24a73ee3e3e5 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,6 @@ [![Actions Status: RJIT](https://github.com/ruby/ruby/workflows/RJIT/badge.svg)](https://github.com/ruby/ruby/actions?query=workflow%3A"RJIT") [![Actions Status: Ubuntu](https://github.com/ruby/ruby/workflows/Ubuntu/badge.svg)](https://github.com/ruby/ruby/actions?query=workflow%3A"Ubuntu") [![Actions Status: Windows](https://github.com/ruby/ruby/workflows/Windows/badge.svg)](https://github.com/ruby/ruby/actions?query=workflow%3A"Windows") -[![AppVeyor status](https://ci.appveyor.com/api/projects/status/0sy8rrxut4o0k960/branch/master?svg=true)](https://ci.appveyor.com/project/ruby/ruby/branch/master) [![Travis Status](https://app.travis-ci.com/ruby/ruby.svg?branch=master)](https://app.travis-ci.com/ruby/ruby) # What is Ruby? diff --git a/array.c b/array.c index 7a8874a01f0e73..a97c21a515b561 100644 --- a/array.c +++ b/array.c @@ -2540,6 +2540,31 @@ ary_aset_by_rb_ary_splice(VALUE ary, long beg, long len, VALUE val) * * Assigns elements in +self+; returns the given +object+. * + * In brief: + * + * a_orig = [:foo, 'bar', 2] + * # With argument index. + * a = a_orig.dup + * a[0] = 'foo' # => "foo" + * a # => ["foo", "bar", 2] + * a = a_orig.dup + * a[7] = 'foo' # => "foo" + * a # => [:foo, "bar", 2, nil, nil, nil, nil, "foo"] + * # With arguments start and length. + * a = a_orig.dup + * a[0, 2] = 'foo' # => "foo" + * a # => ["foo", 2] + * a = a_orig.dup + * a[6, 50] = 'foo' # => "foo" + * a # => [:foo, "bar", 2, nil, nil, nil, "foo"] + * # With argument range. + * a = a_orig.dup + * a[0..1] = 'foo' # => "foo" + * a # => ["foo", 2] + * a = a_orig.dup + * a[6..50] = 'foo' # => "foo" + * a # => [:foo, "bar", 2, nil, nil, nil, "foo"] + * * When Integer argument +index+ is given, assigns +object+ to an element in +self+. * * If +index+ is non-negative, assigns +object+ the element at offset +index+: @@ -6894,6 +6919,7 @@ ary_sample(rb_execution_context_t *ec, VALUE ary, VALUE randgen, VALUE nv, VALUE }); DATA_PTR(vmemo) = 0; st_free_table(memo); + RB_GC_GUARD(vmemo); } else { result = rb_ary_dup(ary); diff --git a/ast.c b/ast.c index 66e237b1f22e67..70f298c7f8adf5 100644 --- a/ast.c +++ b/ast.c @@ -183,29 +183,6 @@ node_find(VALUE self, const int node_id) extern VALUE rb_e_script; -VALUE -rb_script_lines_for(VALUE path, bool add) -{ - VALUE hash, lines; - ID script_lines; - CONST_ID(script_lines, "SCRIPT_LINES__"); - if (!rb_const_defined_at(rb_cObject, script_lines)) return Qnil; - hash = rb_const_get_at(rb_cObject, script_lines); - if (!RB_TYPE_P(hash, T_HASH)) return Qnil; - if (add) { - rb_hash_aset(hash, path, lines = rb_ary_new()); - } - else if (!RB_TYPE_P((lines = rb_hash_lookup(hash, path)), T_ARRAY)) { - return Qnil; - } - return lines; -} -static VALUE -script_lines(VALUE path) -{ - return rb_script_lines_for(path, false); -} - static VALUE node_id_for_backtrace_location(rb_execution_context_t *ec, VALUE module, VALUE location) { @@ -267,7 +244,7 @@ ast_s_of(rb_execution_context_t *ec, VALUE module, VALUE body, VALUE keep_script rb_raise(rb_eArgError, "cannot get AST for method defined in eval"); } - if (!NIL_P(lines) || !NIL_P(lines = script_lines(path))) { + if (!NIL_P(lines)) { node = rb_ast_parse_array(lines, keep_script_lines, error_tolerant, keep_tokens); } else if (e_option) { @@ -551,6 +528,8 @@ node_children(rb_ast_t *ast, const NODE *node) name[1] = (char)RNODE_BACK_REF(node)->nd_nth; name[2] = '\0'; return rb_ary_new_from_args(1, ID2SYM(rb_intern(name))); + case NODE_MATCH: + return rb_ary_new_from_args(1, rb_node_regx_string_val(node)); case NODE_MATCH2: if (RNODE_MATCH2(node)->nd_args) { return rb_ary_new_from_node_args(ast, 3, RNODE_MATCH2(node)->nd_recv, RNODE_MATCH2(node)->nd_value, RNODE_MATCH2(node)->nd_args); @@ -558,9 +537,6 @@ node_children(rb_ast_t *ast, const NODE *node) return rb_ary_new_from_node_args(ast, 2, RNODE_MATCH2(node)->nd_recv, RNODE_MATCH2(node)->nd_value); case NODE_MATCH3: return rb_ary_new_from_node_args(ast, 2, RNODE_MATCH3(node)->nd_recv, RNODE_MATCH3(node)->nd_value); - case NODE_MATCH: - case NODE_LIT: - return rb_ary_new_from_args(1, RNODE_LIT(node)->nd_lit); case NODE_STR: case NODE_XSTR: return rb_ary_new_from_args(1, rb_node_str_string_val(node)); @@ -774,10 +750,35 @@ ast_node_last_column(rb_execution_context_t *ec, VALUE self) static VALUE ast_node_all_tokens(rb_execution_context_t *ec, VALUE self) { + long i; struct ASTNodeData *data; + rb_parser_ary_t *parser_tokens; + rb_parser_ast_token_t *parser_token; + VALUE str, loc, token, all_tokens; + TypedData_Get_Struct(self, struct ASTNodeData, &rb_node_type, data); - return rb_ast_tokens(data->ast); + parser_tokens = data->ast->node_buffer->tokens; + if (parser_tokens == NULL) { + return Qnil; + } + + all_tokens = rb_ary_new2(parser_tokens->len); + for (i = 0; i < parser_tokens->len; i++) { + parser_token = parser_tokens->data[i]; + str = rb_str_new(parser_token->str->ptr, parser_token->str->len); + loc = rb_ary_new_from_args(4, + INT2FIX(parser_token->loc.beg_pos.lineno), + INT2FIX(parser_token->loc.beg_pos.column), + INT2FIX(parser_token->loc.end_pos.lineno), + INT2FIX(parser_token->loc.end_pos.column) + ); + token = rb_ary_new_from_args(4, INT2FIX(parser_token->id), ID2SYM(rb_intern(parser_token->type_name)), str, loc); + rb_ary_push(all_tokens, token); + } + rb_obj_freeze(all_tokens); + + return all_tokens; } static VALUE diff --git a/basictest/test.rb b/basictest/test.rb index 95875b52a6ef79..711e4f4ab32209 100755 --- a/basictest/test.rb +++ b/basictest/test.rb @@ -879,7 +879,7 @@ def r(val); a,b,*c = *yield(); test_ok([a,b,c] == val, 2); end test_ok($x == [7,5,3,2,1]) # split test -$x = "The Book of Mormon" +$x = +"The Book of Mormon" test_ok($x.split(//).reverse!.join == $x.reverse) test_ok($x.reverse == $x.reverse!) test_ok("1 byte string".split(//).reverse.join(":") == "g:n:i:r:t:s: :e:t:y:b: :1") @@ -1643,7 +1643,7 @@ def shift_test(a) test_ok(/(\s+\d+){2}/ =~ " 1 2" && $& == " 1 2") test_ok(/(?:\s+\d+){2}/ =~ " 1 2" && $& == " 1 2") -$x = < 0) { - lowbits = (BDIGIT)d & ~(~(BDIGIT)1U << rshift); - d >>= rshift; - } - else if (rshift < 0) { - d <<= -rshift; - d |= nds[len-dbl_per_bdig-1] >> (BITSPERDIG+rshift); - } - f = sqrt(BDIGIT_DBL_TO_DOUBLE(d)); - d = (BDIGIT_DBL)ceil(f); - if (BDIGIT_DBL_TO_DOUBLE(d) == f) { - if (lowbits || (lowbits = !bary_zero_p(nds, len-dbl_per_bdig))) - ++d; - } - else { - lowbits = 1; - } - rshift /= 2; - rshift += (2-(len&1))*BITSPERDIG/2; - if (rshift >= 0) { - if (nlz((BDIGIT)d) + rshift >= BITSPERDIG) { - /* (d << rshift) does cause overflow. - * example: Integer.sqrt(0xffff_ffff_ffff_ffff ** 2) - */ - d = ~(BDIGIT_DBL)0; - } - else { - d <<= rshift; - } - } - BDIGITS_ZERO(xds, xn-2); - bdigitdbl2bary(&xds[xn-2], 2, d); - - if (!lowbits) return NULL; /* special case, exact result */ - return xds; -} - VALUE rb_big_isqrt(VALUE n) { BDIGIT *nds = BDIGITS(n); size_t len = BIGNUM_LEN(n); - size_t xn = (len+1) / 2; - VALUE x; - BDIGIT *xds; if (len <= 2) { BDIGIT sq = rb_bdigit_dbl_isqrt(bary2bdigitdbl(nds, len)); @@ -6944,25 +6892,19 @@ rb_big_isqrt(VALUE n) return ULONG2NUM(sq); #endif } - else if ((xds = estimate_initial_sqrt(&x, xn, nds, len)) != 0) { - size_t tn = xn + BIGDIVREM_EXTRA_WORDS; - VALUE t = bignew_1(0, tn, 1); - BDIGIT *tds = BDIGITS(t); - tn = BIGNUM_LEN(t); - - /* t = n/x */ - while (bary_divmod_branch(tds, tn, NULL, 0, nds, len, xds, xn), - bary_cmp(tds, tn, xds, xn) < 0) { - int carry; - BARY_TRUNC(tds, tn); - /* x = (x+t)/2 */ - carry = bary_add(xds, xn, xds, xn, tds, tn); - bary_small_rshift(xds, xds, xn, 1, carry); - tn = BIGNUM_LEN(t); - } + else { + size_t shift = FIX2LONG(rb_big_bit_length(n)) / 4; + VALUE n2 = rb_int_rshift(n, SIZET2NUM(2 * shift)); + VALUE x = FIXNUM_P(n2) ? LONG2FIX(rb_ulong_isqrt(FIX2ULONG(n2))) : rb_big_isqrt(n2); + /* x = (x+n/x)/2 */ + x = rb_int_plus(rb_int_lshift(x, SIZET2NUM(shift - 1)), rb_int_idiv(rb_int_rshift(n, SIZET2NUM(shift + 1)), x)); + VALUE xx = rb_int_mul(x, x); + while (rb_int_gt(xx, n)) { + xx = rb_int_minus(xx, rb_int_minus(rb_int_plus(x, x), INT2FIX(1))); + x = rb_int_minus(x, INT2FIX(1)); + } + return x; } - RBASIC_SET_CLASS_RAW(x, rb_cInteger); - return x; } #if USE_GMP diff --git a/bootstraptest/runner.rb b/bootstraptest/runner.rb index 3cdd64d10dc71c..a381f89e13c49f 100755 --- a/bootstraptest/runner.rb +++ b/bootstraptest/runner.rb @@ -6,7 +6,6 @@ # Never use optparse in this file. # Never use test/unit in this file. # Never use Ruby extensions in this file. -# Maintain Ruby 1.8 compatibility for now $start_time = Time.now @@ -428,7 +427,7 @@ def add as def initialize(*args) super self.class.add self - @category = self.path.match(/test_(.+)\.rb/)[1] + @category = self.path[/\Atest_(.+)\.rb\z/, 1] end def call @@ -551,7 +550,7 @@ def get_result_string(opt = '', **argh) def make_srcfile(frozen_string_literal: nil) filename = "bootstraptest.#{self.path}_#{self.lineno}_#{self.id}.rb" File.open(filename, 'w') {|f| - f.puts "#frozen_string_literal:true" if frozen_string_literal + f.puts "#frozen_string_literal:#{frozen_string_literal}" unless frozen_string_literal.nil? if $stress f.puts "GC.stress = true" if $stress else @@ -572,9 +571,9 @@ def add_assertion src, pr Assertion.new(src, path, lineno, pr) end -def assert_equal(expected, testsrc, message = '', opt = '', **argh) +def assert_equal(expected, testsrc, message = '', opt = '', **kwargs) add_assertion testsrc, -> as do - as.assert_check(message, opt, **argh) {|result| + as.assert_check(message, opt, **kwargs) {|result| if expected == result nil else @@ -585,9 +584,9 @@ def assert_equal(expected, testsrc, message = '', opt = '', **argh) end end -def assert_match(expected_pattern, testsrc, message = '') +def assert_match(expected_pattern, testsrc, message = '', **argh) add_assertion testsrc, -> as do - as.assert_check(message) {|result| + as.assert_check(message, **argh) {|result| if expected_pattern =~ result nil else diff --git a/bootstraptest/test_eval.rb b/bootstraptest/test_eval.rb index 47e2924846b798..d923a957bcbd27 100644 --- a/bootstraptest/test_eval.rb +++ b/bootstraptest/test_eval.rb @@ -364,3 +364,34 @@ def kaboom! end }, 'check escaping the internal value th->base_block' +assert_equal "false", <<~RUBY, "literal strings are mutable", "--disable-frozen-string-literal" + eval("'test'").frozen? +RUBY + +assert_equal "false", <<~RUBY, "literal strings are mutable", "--disable-frozen-string-literal", frozen_string_literal: true + eval("'test'").frozen? +RUBY + +assert_equal "true", <<~RUBY, "literal strings are frozen", "--enable-frozen-string-literal" + eval("'test'").frozen? +RUBY + +assert_equal "true", <<~RUBY, "literal strings are frozen", "--enable-frozen-string-literal", frozen_string_literal: false + eval("'test'").frozen? +RUBY + +assert_equal "false", <<~RUBY, "__FILE__ is mutable", "--disable-frozen-string-literal" + eval("__FILE__").frozen? +RUBY + +assert_equal "false", <<~RUBY, "__FILE__ is mutable", "--disable-frozen-string-literal", frozen_string_literal: true + eval("__FILE__").frozen? +RUBY + +assert_equal "true", <<~RUBY, "__FILE__ is frozen", "--enable-frozen-string-literal" + eval("__FILE__").frozen? +RUBY + +assert_equal "true", <<~RUBY, "__FILE__ is frozen", "--enable-frozen-string-literal", frozen_string_literal: false + eval("__FILE__").frozen? +RUBY diff --git a/bootstraptest/test_finalizer.rb b/bootstraptest/test_finalizer.rb index 22a16b1220e784..ccfa0b55d613f2 100644 --- a/bootstraptest/test_finalizer.rb +++ b/bootstraptest/test_finalizer.rb @@ -6,3 +6,11 @@ ObjectSpace.define_finalizer(a2,proc{a1.inspect}) ObjectSpace.define_finalizer(a1,proc{}) }, '[ruby-dev:35778]' + +assert_equal 'true', %q{ + obj = Object.new + id = obj.object_id + + ObjectSpace.define_finalizer(obj, proc { |i| print(id == i) }) + nil +} diff --git a/bootstraptest/test_flow.rb b/bootstraptest/test_flow.rb index 35f19db5888f85..15528a42130fc1 100644 --- a/bootstraptest/test_flow.rb +++ b/bootstraptest/test_flow.rb @@ -363,7 +363,7 @@ class C ; $a << 8 ; rescue Exception; $a << 99; end; $a} assert_equal %q{[1, 2, 6, 3, 5, 7, 8]}, %q{$a = []; begin; ; $a << 1 - o = "test"; $a << 2 + o = "test".dup; $a << 2 def o.test(a); $a << 3 return a; $a << 4 ensure; $a << 5 diff --git a/bootstraptest/test_insns.rb b/bootstraptest/test_insns.rb index cf3c139c484033..06828a7f7a2a54 100644 --- a/bootstraptest/test_insns.rb +++ b/bootstraptest/test_insns.rb @@ -354,7 +354,7 @@ class X; def != other; true; end; end [ 'opt_ge', %q{ +0.0.next_float >= 0.0 }, ], [ 'opt_ge', %q{ ?z >= ?a }, ], - [ 'opt_ltlt', %q{ '' << 'true' }, ], + [ 'opt_ltlt', %q{ +'' << 'true' }, ], [ 'opt_ltlt', %q{ ([] << 'true').join }, ], [ 'opt_ltlt', %q{ (1 << 31) == 2147483648 }, ], @@ -363,7 +363,7 @@ class X; def != other; true; end; end [ 'opt_aref', %q{ 'true'[0] == ?t }, ], [ 'opt_aset', %q{ [][0] = true }, ], [ 'opt_aset', %q{ {}[0] = true }, ], - [ 'opt_aset', %q{ x = 'frue'; x[0] = 't'; x }, ], + [ 'opt_aset', %q{ x = +'frue'; x[0] = 't'; x }, ], [ 'opt_aset', <<-'},', ], # { # opt_aref / opt_aset mixup situation class X; def x; {}; end; end diff --git a/bootstraptest/test_jump.rb b/bootstraptest/test_jump.rb index d07c47a56d7400..8751343b1f2d2a 100644 --- a/bootstraptest/test_jump.rb +++ b/bootstraptest/test_jump.rb @@ -292,7 +292,7 @@ class << self end end end - s = "foo" + s = +"foo" s.return_eigenclass == class << s; self; end }, '[ruby-core:21379]' diff --git a/bootstraptest/test_literal_suffix.rb b/bootstraptest/test_literal_suffix.rb index 5d813d581866e6..7a4d67d0fac3e3 100644 --- a/bootstraptest/test_literal_suffix.rb +++ b/bootstraptest/test_literal_suffix.rb @@ -47,8 +47,8 @@ '1.0000000000000000001r' assert_equal 'unexpected local variable or method, expecting end-of-input', - %q{begin eval('1ir', nil, '', 0); rescue SyntaxError => e; e.message[/(?:\A:(?:\d+:)? syntax error,|\^) (.*)/, 1] end} + %q{begin eval('1ir', nil, '', 0); rescue SyntaxError => e; e.message[/(?:\^~*|\A:(?:\d+:)? syntax error,) (.*)/, 1]; end} assert_equal 'unexpected local variable or method, expecting end-of-input', - %q{begin eval('1.2ir', nil, '', 0); rescue SyntaxError => e; e.message[/(?:\A:(?:\d+:)? syntax error,|\^) (.*)/, 1] end} + %q{begin eval('1.2ir', nil, '', 0); rescue SyntaxError => e; e.message[/(?:\^~*|\A:(?:\d+:)? syntax error,) (.*)/, 1]; end} assert_equal 'unexpected local variable or method, expecting end-of-input', - %q{begin eval('1e1r', nil, '', 0); rescue SyntaxError => e; e.message[/(?:\A:(?:\d+:)? syntax error,|\^) (.*)/, 1] end} + %q{begin eval('1e1r', nil, '', 0); rescue SyntaxError => e; e.message[/(?:\^~*|\A:(?:\d+:)? syntax error,) (.*)/, 1]; end} diff --git a/bootstraptest/test_method.rb b/bootstraptest/test_method.rb index 964bf39d98b801..d1d1f57d55b0bb 100644 --- a/bootstraptest/test_method.rb +++ b/bootstraptest/test_method.rb @@ -340,24 +340,6 @@ def method_missing(mid, *args, &block) block end assert_equal '1', %q( class C; def m() 1 end; private :m end C.new.send(:m) ) -# with block -assert_equal '[[:ok1, :foo], [:ok2, :foo, :bar]]', -%q{ - class C - def [](a) - $ary << [yield, a] - end - def []=(a, b) - $ary << [yield, a, b] - end - end - - $ary = [] - C.new[:foo, &lambda{:ok1}] - C.new[:foo, &lambda{:ok2}] = :bar - $ary -} - # with assert_equal '[:ok1, [:ok2, 11]]', %q{ class C @@ -404,7 +386,6 @@ def m(*args, &b) # aset and splat assert_equal '4', %q{class Foo;def []=(a,b,c,d);end;end;Foo.new[1,*a=[2,3]]=4} -assert_equal '4', %q{class Foo;def []=(a,b,c,d);end;end;def m(&blk)Foo.new[1,*a=[2,3],&blk]=4;end;m{}} # post test assert_equal %q{[1, 2, :o1, :o2, [], 3, 4, NilClass, nil, nil]}, %q{ @@ -1107,10 +1088,6 @@ class C 'ok' end } -assert_equal 'ok', %q{ - [0][0, &proc{}] += 21 - 'ok' -}, '[ruby-core:30534]' # should not cache when splat assert_equal 'ok', %q{ diff --git a/bootstraptest/test_ractor.rb b/bootstraptest/test_ractor.rb index 08a1475ed5dc4d..9f5a62a65f70f0 100644 --- a/bootstraptest/test_ractor.rb +++ b/bootstraptest/test_ractor.rb @@ -630,7 +630,7 @@ def test n } # send shareable and unshareable objects -assert_equal "ok", %q{ +assert_equal "ok", <<~'RUBY', frozen_string_literal: false echo_ractor = Ractor.new do loop do v = Ractor.receive @@ -697,10 +697,10 @@ module M; end else results.inspect end -} +RUBY # frozen Objects are shareable -assert_equal [false, true, false].inspect, %q{ +assert_equal [false, true, false].inspect, <<~'RUBY', frozen_string_literal: false class C def initialize freeze @a = 1 @@ -723,11 +723,11 @@ def check obj1 results << check(C.new(true)) # false results << check(C.new(true).freeze) # true results << check(C.new(false).freeze) # false -} +RUBY # move example2: String # touching moved object causes an error -assert_equal 'hello world', %q{ +assert_equal 'hello world', <<~'RUBY', frozen_string_literal: false # move r = Ractor.new do obj = Ractor.receive @@ -745,7 +745,7 @@ def check obj1 else raise 'unreachable' end -} +RUBY # move example2: Array assert_equal '[0, 1]', %q{ @@ -948,7 +948,7 @@ def ractor_local_globals } # ivar in shareable-objects are not allowed to access from non-main Ractor -assert_equal "can not get unshareable values from instance variables of classes/modules from non-main Ractors", %q{ +assert_equal "can not get unshareable values from instance variables of classes/modules from non-main Ractors", <<~'RUBY', frozen_string_literal: false class C @iv = 'str' end @@ -959,13 +959,12 @@ class C end end - begin r.take rescue Ractor::RemoteError => e e.cause.message end -} +RUBY # ivar in shareable-objects are not allowed to access from non-main Ractor assert_equal 'can not access instance variables of shareable objects from non-main Ractors', %q{ @@ -1152,7 +1151,7 @@ def self.cv } # Getting non-shareable objects via constants by other Ractors is not allowed -assert_equal 'can not access non-shareable objects in constant C::CONST by non-main Ractor.', %q{ +assert_equal 'can not access non-shareable objects in constant C::CONST by non-main Ractor.', <<~'RUBY', frozen_string_literal: false class C CONST = 'str' end @@ -1164,10 +1163,10 @@ class C rescue Ractor::RemoteError => e e.cause.message end -} + RUBY # Constant cache should care about non-sharable constants -assert_equal "can not access non-shareable objects in constant Object::STR by non-main Ractor.", %q{ +assert_equal "can not access non-shareable objects in constant Object::STR by non-main Ractor.", <<~'RUBY', frozen_string_literal: false STR = "hello" def str; STR; end s = str() # fill const cache @@ -1176,10 +1175,10 @@ def str; STR; end rescue Ractor::RemoteError => e e.cause.message end -} +RUBY # Setting non-shareable objects into constants by other Ractors is not allowed -assert_equal 'can not set constants with non-shareable objects by non-main Ractors', %q{ +assert_equal 'can not set constants with non-shareable objects by non-main Ractors', <<~'RUBY', frozen_string_literal: false class C end r = Ractor.new do @@ -1190,7 +1189,7 @@ class C rescue Ractor::RemoteError => e e.cause.message end -} +RUBY # define_method is not allowed assert_equal "defined with an un-shareable Proc in a different Ractor", %q{ @@ -1243,7 +1242,7 @@ class C } # ObjectSpace._id2ref can not handle unshareable objects with Ractors -assert_equal 'ok', %q{ +assert_equal 'ok', <<~'RUBY', frozen_string_literal: false s = 'hello' Ractor.new s.object_id do |id ;s| @@ -1253,10 +1252,10 @@ class C :ok end end.take -} +RUBY # Ractor.make_shareable(obj) -assert_equal 'true', %q{ +assert_equal 'true', <<~'RUBY', frozen_string_literal: false class C def initialize @a = 'foo' @@ -1327,7 +1326,7 @@ def /(other) } Ractor.shareable?(a) -} +RUBY # Ractor.make_shareable(obj) doesn't freeze shareable objects assert_equal 'true', %q{ @@ -1427,11 +1426,11 @@ class C assert_equal '[6, 10]', %q{ rs = [] TracePoint.new(:line){|tp| rs << tp.lineno if tp.path == __FILE__}.enable do - Ractor.new{ # line 4 + Ractor.new{ # line 5 a = 1 b = 2 }.take - c = 3 # line 8 + c = 3 # line 9 end rs } @@ -1505,7 +1504,7 @@ class C 2.times.map{ Ractor.new do #{n}.times do - obj = '' + obj = +'' obj.instance_variable_set("@a", 1) obj.instance_variable_set("@b", 1) obj.instance_variable_set("@c", 1) @@ -1664,7 +1663,7 @@ class C8; def self.foo = 17; end Warning[:experimental] = $VERBOSE = true STDERR.reopen(STDOUT) eval("Ractor.new{}.take", nil, "test_ractor.rb", 1) -} +}, frozen_string_literal: false # check moved object assert_equal 'ok', %q{ @@ -1795,3 +1794,14 @@ class C8; def self.foo = 17; end } end # if !ENV['GITHUB_WORKFLOW'] + +# Chilled strings are not shareable +assert_equal 'false', %q{ + Ractor.shareable?("chilled") +} + +# Chilled strings can be made shareable +assert_equal 'true', %q{ + shareable = Ractor.make_shareable("chilled") + shareable == "chilled" && Ractor.shareable?(shareable) +} diff --git a/bootstraptest/test_syntax.rb b/bootstraptest/test_syntax.rb index e204290efdd62b..fbc9c6f62e377a 100644 --- a/bootstraptest/test_syntax.rb +++ b/bootstraptest/test_syntax.rb @@ -529,7 +529,7 @@ def lines } def assert_syntax_error expected, code, message = '' assert_match /^#{Regexp.escape(expected)}/, - "begin eval(%q{#{code}}, nil, '', 0)"'; rescue SyntaxError => e; e.message[/(?:\A:(?:\d+:)?(?: syntax error,)?|\^) (.*)/, 1] end', message + "begin eval(%q{#{code}}, nil, '', 0)"'; rescue SyntaxError => e; e.message[/(?:\^~*|\A:(?:\d+:)?(?! syntax errors? found)(?: syntax error,)?) (.*)/, 1] end', message end assert_syntax_error "unterminated string meets end of file", '().."', '[ruby-dev:29732]' assert_equal %q{[]}, %q{$&;[]}, '[ruby-dev:31068]' @@ -629,7 +629,7 @@ class << (ary=[]); def []; 0; end; def []=(x); super(0,x);end;end; ary[]+=1 assert_match /invalid multibyte char/, %q{ $stderr = STDOUT - eval("\"\xf0".force_encoding("utf-8")) + eval("\"\xf0".dup.force_encoding("utf-8")) }, '[ruby-dev:32429]' # method ! and != @@ -904,3 +904,35 @@ def foo(&block) Class end }, '[ruby-core:30293]' + +assert_equal "false", <<~RUBY, "literal strings are mutable", "--disable-frozen-string-literal" + 'test'.frozen? +RUBY + +assert_equal "true", <<~RUBY, "literal strings are frozen", "--disable-frozen-string-literal", frozen_string_literal: true + 'test'.frozen? +RUBY + +assert_equal "true", <<~RUBY, "literal strings are frozen", "--enable-frozen-string-literal" + 'test'.frozen? +RUBY + +assert_equal "false", <<~RUBY, "literal strings are mutable", "--enable-frozen-string-literal", frozen_string_literal: false + 'test'.frozen? +RUBY + +assert_equal "false", <<~RUBY, "__FILE__ is mutable", "--disable-frozen-string-literal" + __FILE__.frozen? +RUBY + +assert_equal "true", <<~RUBY, "__FILE__ is frozen", "--disable-frozen-string-literal", frozen_string_literal: true + __FILE__.frozen? +RUBY + +assert_equal "true", <<~RUBY, "__FILE__ is frozen", "--enable-frozen-string-literal" + __FILE__.frozen? +RUBY + +assert_equal "false", <<~RUBY, "__FILE__ is mutable", "--enable-frozen-string-literal", frozen_string_literal: false + __FILE__.frozen? +RUBY diff --git a/bootstraptest/test_yjit.rb b/bootstraptest/test_yjit.rb index faa92aaa36b5f0..c84e831b76433e 100644 --- a/bootstraptest/test_yjit.rb +++ b/bootstraptest/test_yjit.rb @@ -187,7 +187,7 @@ def carray(iter) end def cstring(iter) - string = "" + string = "".dup string.sum(iter.times { def string.sum(_) = :sum }) end @@ -1252,7 +1252,7 @@ def foo # Test polymorphic getinstancevariable. T_OBJECT -> T_STRING assert_equal 'ok', %q{ @hello = @h1 = @h2 = @h3 = @h4 = 'ok' - str = "" + str = +"" str.instance_variable_set(:@hello, 'ok') public def get @@ -1397,7 +1397,7 @@ def index(obj, idx) } # Test default value block for Hash with opt_aref_with -assert_equal "false", %q{ +assert_equal "false", <<~RUBY, frozen_string_literal: false def index_with_string(h) h["foo"] end @@ -1406,7 +1406,7 @@ def index_with_string(h) index_with_string(h) index_with_string(h) -} +RUBY # A regression test for making sure cfp->sp is proper when # hitting stubs. See :stub-sp-flush: @@ -1906,7 +1906,7 @@ def make_str(foo) } # Test that String unary plus returns the same object ID for an unfrozen string. -assert_equal 'true', %q{ +assert_equal 'true', <<~RUBY, frozen_string_literal: false def jittable_method str = "bar" @@ -1916,7 +1916,7 @@ def jittable_method uplus_str.object_id == old_obj_id end jittable_method -} +RUBY # Test that String unary plus returns a different unfrozen string when given a frozen string assert_equal 'false', %q{ @@ -2050,7 +2050,7 @@ def getbyte(s, i) # Basic test for String#setbyte assert_equal 'AoZ', %q{ - s = "foo" + s = +"foo" s.setbyte(0, 65) s.setbyte(-1, 90) s @@ -2093,7 +2093,7 @@ def to_int 0 end - def ccall = "a".setbyte(self, 98) + def ccall = "a".dup.setbyte(self, 98) ccall @caller.first.split("'").last @@ -4169,7 +4169,7 @@ def foo assert_equal 'Hello World', %q{ def bar args = ["Hello "] - greeting = "World" + greeting = +"World" greeting.insert(0, *args) greeting end @@ -4680,3 +4680,111 @@ def test(klass, args) test(KwInit, [Hash.ruby2_keywords_hash({1 => 1})]) } + +# Chilled string setivar trigger warning +assert_equal 'literal string will be frozen in the future', %q{ + Warning[:deprecated] = true + $VERBOSE = true + $warning = "no-warning" + module ::Warning + def self.warn(message) + $warning = message.split("warning: ").last.strip + end + end + + class String + def setivar! + @ivar = 42 + end + end + + def setivar!(str) + str.setivar! + end + + 10.times { setivar!("mutable".dup) } + 10.times do + setivar!("frozen".freeze) + rescue FrozenError + end + + setivar!("chilled") # Emit warning + $warning +} + +# arity=-2 cfuncs +assert_equal '["", "1/2", [0, [:ok, 1]]]', %q{ + def test_cases(file, chain) + new_chain = chain.allocate # to call initialize directly + new_chain.send(:initialize, [0], ok: 1) + + [ + file.join, + file.join("1", "2"), + new_chain.to_a, + ] + end + + test_cases(File, Enumerator::Chain) +} + +# singleton class should invalidate Type::CString assumption +assert_equal 'foo', %q{ + def define_singleton(str, define) + if define + # Wrap a C method frame to avoid exiting JIT code on defineclass + [nil].reverse_each do + class << str + def +(_) + "foo" + end + end + end + end + "bar" + end + + def entry(define) + str = "" + # When `define` is false, #+ compiles to rb_str_plus() without a class guard. + # When the code is reused with `define` is true, the class of `str` is changed + # to a singleton class, so the block should be invalidated. + str + define_singleton(str, define) + end + + entry(false) + entry(true) +} + +assert_equal '[:ok, :ok, :ok]', %q{ + def identity(x) = x + def foo(x, _) = x + def bar(_, _, _, _, x) = x + + def tests + [ + identity(:ok), + foo(:ok, 2), + bar(1, 2, 3, 4, :ok), + ] + end + + tests +} + +# test integer left shift with constant rhs +assert_equal [0x80000000000, 'a+', :ok].inspect, %q{ + def shift(val) = val << 43 + + def tests + int = shift(1) + str = shift("a") + + Integer.define_method(:<<) { |_| :ok } + redef = shift(1) + + [int, str, redef] + end + + tests +} diff --git a/class.c b/class.c index 98e7dfcc08cc2b..879c0ead2af2ed 100644 --- a/class.c +++ b/class.c @@ -29,6 +29,7 @@ #include "internal/variable.h" #include "ruby/st.h" #include "vm_core.h" +#include "yjit.h" /* Flags of T_CLASS * @@ -225,14 +226,14 @@ rb_class_detach_module_subclasses(VALUE klass) /** * Allocates a struct RClass for a new class. * - * \param flags initial value for basic.flags of the returned class. - * \param klass the class of the returned class. - * \return an uninitialized Class object. - * \pre \p klass must refer \c Class class or an ancestor of Class. - * \pre \code (flags | T_CLASS) != 0 \endcode - * \post the returned class can safely be \c #initialize 'd. + * @param flags initial value for basic.flags of the returned class. + * @param klass the class of the returned class. + * @return an uninitialized Class object. + * @pre `klass` must refer `Class` class or an ancestor of Class. + * @pre `(flags | T_CLASS) != 0` + * @post the returned class can safely be `#initialize` 'd. * - * \note this function is not Class#allocate. + * @note this function is not Class#allocate. */ static VALUE class_alloc(VALUE flags, VALUE klass) @@ -267,14 +268,14 @@ RCLASS_M_TBL_INIT(VALUE c) RCLASS_M_TBL(c) = rb_id_table_create(0); } -/*! +/** * A utility function that wraps class_alloc. * * allocates a class and initializes safely. - * \param super a class from which the new class derives. - * \return a class object. - * \pre \a super must be a class. - * \post the metaclass of the new class is Class. + * @param super a class from which the new class derives. + * @return a class object. + * @pre `super` must be a class. + * @post the metaclass of the new class is Class. */ VALUE rb_class_boot(VALUE super) @@ -732,7 +733,7 @@ rb_singleton_class_internal_p(VALUE sklass) !rb_singleton_class_has_metaclass_p(sklass)); } -/*! +/** * whether k has a metaclass * @retval 1 if \a k has a metaclass * @retval 0 otherwise @@ -741,25 +742,25 @@ rb_singleton_class_internal_p(VALUE sklass) (FL_TEST(METACLASS_OF(k), FL_SINGLETON) && \ rb_singleton_class_has_metaclass_p(k)) -/*! - * ensures \a klass belongs to its own eigenclass. - * @return the eigenclass of \a klass - * @post \a klass belongs to the returned eigenclass. - * i.e. the attached object of the eigenclass is \a klass. +/** + * ensures `klass` belongs to its own eigenclass. + * @return the eigenclass of `klass` + * @post `klass` belongs to the returned eigenclass. + * i.e. the attached object of the eigenclass is `klass`. * @note this macro creates a new eigenclass if necessary. */ #define ENSURE_EIGENCLASS(klass) \ (HAVE_METACLASS_P(klass) ? METACLASS_OF(klass) : make_metaclass(klass)) -/*! - * Creates a metaclass of \a klass - * \param klass a class - * \return created metaclass for the class - * \pre \a klass is a Class object - * \pre \a klass has no singleton class. - * \post the class of \a klass is the returned class. - * \post the returned class is meta^(n+1)-class when \a klass is a meta^(n)-klass for n >= 0 +/** + * Creates a metaclass of `klass` + * @param klass a class + * @return created metaclass for the class + * @pre `klass` is a Class object + * @pre `klass` has no singleton class. + * @post the class of `klass` is the returned class. + * @post the returned class is meta^(n+1)-class when `klass` is a meta^(n)-klass for n >= 0 */ static inline VALUE make_metaclass(VALUE klass) @@ -790,11 +791,11 @@ make_metaclass(VALUE klass) return metaclass; } -/*! - * Creates a singleton class for \a obj. - * \pre \a obj must not a immediate nor a special const. - * \pre \a obj must not a Class object. - * \pre \a obj has no singleton class. +/** + * Creates a singleton class for `obj`. + * @pre `obj` must not be an immediate nor a special const. + * @pre `obj` must not be a Class object. + * @pre `obj` has no singleton class. */ static inline VALUE make_singleton_class(VALUE obj) @@ -805,6 +806,7 @@ make_singleton_class(VALUE obj) FL_SET(klass, FL_SINGLETON); RBASIC_SET_CLASS(obj, klass); rb_singleton_class_attached(klass, obj); + rb_yjit_invalidate_no_singleton_class(orig_class); SET_METACLASS_OF(klass, METACLASS_OF(rb_class_real(orig_class))); return klass; @@ -925,15 +927,15 @@ Init_class_hierarchy(void) } -/*! - * \internal +/** + * @internal * Creates a new *singleton class* for an object. * - * \pre \a obj has no singleton class. - * \note DO NOT USE the function in an extension libraries. Use \ref rb_singleton_class. - * \param obj An object. - * \param unused ignored. - * \return The singleton class of the object. + * @pre `obj` has no singleton class. + * @note DO NOT USE the function in an extension libraries. Use @ref rb_singleton_class. + * @param obj An object. + * @param unused ignored. + * @return The singleton class of the object. */ VALUE rb_make_metaclass(VALUE obj, VALUE unused) @@ -959,13 +961,13 @@ rb_define_class_id(ID id, VALUE super) } -/*! +/** * Calls Class#inherited. - * \param super A class which will be called #inherited. + * @param super A class which will be called #inherited. * NULL means Object class. - * \param klass A Class object which derived from \a super - * \return the value \c Class#inherited's returns - * \pre Each of \a super and \a klass must be a \c Class object. + * @param klass A Class object which derived from `super` + * @return the value `Class#inherited` returns + * @pre Each of `super` and `klass` must be a `Class` object. */ VALUE rb_class_inherited(VALUE super, VALUE klass) @@ -2214,13 +2216,13 @@ rb_special_singleton_class(VALUE obj) return special_singleton_class_of(obj); } -/*! - * \internal - * Returns the singleton class of \a obj. Creates it if necessary. +/** + * @internal + * Returns the singleton class of `obj`. Creates it if necessary. * - * \note DO NOT expose the returned singleton class to + * @note DO NOT expose the returned singleton class to * outside of class.c. - * Use \ref rb_singleton_class instead for + * Use @ref rb_singleton_class instead for * consistency of the metaclass hierarchy. */ static VALUE @@ -2244,7 +2246,10 @@ singleton_class_of(VALUE obj) return klass; case T_STRING: - if (FL_TEST_RAW(obj, RSTRING_FSTR)) { + if (CHILLED_STRING_P(obj)) { + CHILLED_STRING_MUTATED(obj); + } + else if (FL_TEST_RAW(obj, RSTRING_FSTR)) { rb_raise(rb_eTypeError, "can't define singleton"); } } @@ -2273,12 +2278,12 @@ rb_freeze_singleton_class(VALUE x) } } -/*! - * Returns the singleton class of \a obj, or nil if obj is not a +/** + * Returns the singleton class of `obj`, or nil if obj is not a * singleton object. * - * \param obj an arbitrary object. - * \return the singleton class or nil. + * @param obj an arbitrary object. + * @return the singleton class or nil. */ VALUE rb_singleton_class_get(VALUE obj) diff --git a/common.mk b/common.mk index f462569ce90f8b..ac633854a2d4c0 100644 --- a/common.mk +++ b/common.mk @@ -473,8 +473,6 @@ ruby.imp: $(COMMONOBJS) $(Q){ \ $(NM) -Pgp $(COMMONOBJS) | \ awk 'BEGIN{print "#!"}; $$2~/^[A-TV-Z]$$/&&$$1!~/^$(SYMBOL_PREFIX)(Init_|InitVM_|ruby_static_id_|.*_threadptr_|rb_ec_)|^\./{print $$1}'; \ - ($(CHDIR) $(srcdir) && \ - exec sed -n '/^RJIT_FUNC_EXPORTED/!d;N;s/.*\n\(rb_[a-zA-Z_0-9]*\).*/$(SYMBOL_PREFIX)\1/p' cont.c gc.c thread*c vm*.c) \ } | \ sort -u -o $@ @@ -734,10 +732,10 @@ clean-local:: clean-runnable bin/clean-runnable:: PHONY $(Q)$(CHDIR) bin 2>$(NULL) && $(RM) $(PROGRAM) $(WPROGRAM) $(GORUBY)$(EXEEXT) bin/*.$(DLEXT) 2>$(NULL) || $(NULLCMD) lib/clean-runnable:: PHONY - $(Q)$(CHDIR) lib 2>$(NULL) && $(RM) $(LIBRUBY_A) $(LIBRUBY) $(LIBRUBY_ALIASES) $(RUBY_BASE_NAME)/$(RUBY_PROGRAM_VERSION) $(RUBY_BASE_NAME)/vendor_ruby 2>$(NULL) || $(NULLCMD) + $(Q)$(CHDIR) lib 2>$(NULL) && $(RM) $(LIBRUBY_A) $(LIBRUBY) $(LIBRUBY_ALIASES) $(RUBY_BASE_NAME)/$(ruby_version) $(RUBY_BASE_NAME)/vendor_ruby 2>$(NULL) || $(NULLCMD) clean-runnable:: bin/clean-runnable lib/clean-runnable PHONY $(Q)$(RMDIR) lib/$(RUBY_BASE_NAME) lib bin 2>$(NULL) || $(NULLCMD) - -$(Q)$(RM) $(EXTOUT)/$(arch)/rbconfig.rb + -$(Q)$(RM) $(EXTOUT)/$(arch)/rbconfig.rb $(EXTOUT)/common/$(arch) -$(Q)$(RMALL) exe/ clean-ext:: PHONY clean-golf: PHONY @@ -1009,6 +1007,15 @@ yes-test-spec: yes-test-spec-precheck $(ACTIONS_ENDGROUP) no-test-spec: +test-prism-spec: $(TEST_RUNNABLE)-test-prism-spec +yes-test-prism-spec: yes-test-spec-precheck + $(ACTIONS_GROUP) + $(gnumake_recursive)$(Q) \ + $(RUNRUBY) -r./$(arch)-fake -r$(tooldir)/rubyspec_temp \ + $(srcdir)/spec/mspec/bin/mspec run -B $(srcdir)/spec/default.mspec -B $(srcdir)/spec/prism.mspec $(MSPECOPT) $(SPECOPTS) + $(ACTIONS_ENDGROUP) +no-test-prism-spec: + check: $(DOT_WAIT) test-spec RUNNABLE = $(LIBRUBY_RELATIVE:no=un)-runnable @@ -3101,6 +3108,7 @@ class.$(OBJEXT): {$(VPATH)}vm_core.h class.$(OBJEXT): {$(VPATH)}vm_debug.h class.$(OBJEXT): {$(VPATH)}vm_opts.h class.$(OBJEXT): {$(VPATH)}vm_sync.h +class.$(OBJEXT): {$(VPATH)}yjit.h compar.$(OBJEXT): $(hdrdir)/ruby/ruby.h compar.$(OBJEXT): $(hdrdir)/ruby/version.h compar.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h @@ -3528,6 +3536,7 @@ compile.$(OBJEXT): {$(VPATH)}prism/prism.h compile.$(OBJEXT): {$(VPATH)}prism/version.h compile.$(OBJEXT): {$(VPATH)}prism_compile.c compile.$(OBJEXT): {$(VPATH)}prism_compile.h +compile.$(OBJEXT): {$(VPATH)}ractor.h compile.$(OBJEXT): {$(VPATH)}re.h compile.$(OBJEXT): {$(VPATH)}regex.h compile.$(OBJEXT): {$(VPATH)}ruby_assert.h @@ -12347,7 +12356,6 @@ prism/diagnostic.$(OBJEXT): $(top_srcdir)/prism/util/pm_integer.h prism/diagnostic.$(OBJEXT): $(top_srcdir)/prism/util/pm_list.h prism/diagnostic.$(OBJEXT): $(top_srcdir)/prism/util/pm_newline_list.h prism/diagnostic.$(OBJEXT): $(top_srcdir)/prism/util/pm_string.h -prism/diagnostic.$(OBJEXT): {$(VPATH)}config.h prism/diagnostic.$(OBJEXT): {$(VPATH)}prism/ast.h prism/diagnostic.$(OBJEXT): {$(VPATH)}prism/diagnostic.c prism/diagnostic.$(OBJEXT): {$(VPATH)}prism/diagnostic.h @@ -12355,7 +12363,6 @@ prism/encoding.$(OBJEXT): $(top_srcdir)/prism/defines.h prism/encoding.$(OBJEXT): $(top_srcdir)/prism/encoding.c prism/encoding.$(OBJEXT): $(top_srcdir)/prism/encoding.h prism/encoding.$(OBJEXT): $(top_srcdir)/prism/util/pm_strncasecmp.h -prism/encoding.$(OBJEXT): {$(VPATH)}config.h prism/extension.$(OBJEXT): $(hdrdir)/ruby.h prism/extension.$(OBJEXT): $(hdrdir)/ruby/ruby.h prism/extension.$(OBJEXT): $(top_srcdir)/prism/defines.h @@ -12570,7 +12577,6 @@ prism/node.$(OBJEXT): $(top_srcdir)/prism/util/pm_string.h prism/node.$(OBJEXT): $(top_srcdir)/prism/util/pm_string_list.h prism/node.$(OBJEXT): $(top_srcdir)/prism/util/pm_strncasecmp.h prism/node.$(OBJEXT): $(top_srcdir)/prism/util/pm_strpbrk.h -prism/node.$(OBJEXT): {$(VPATH)}config.h prism/node.$(OBJEXT): {$(VPATH)}prism/ast.h prism/node.$(OBJEXT): {$(VPATH)}prism/diagnostic.h prism/node.$(OBJEXT): {$(VPATH)}prism/node.c @@ -12581,7 +12587,6 @@ prism/options.$(OBJEXT): $(top_srcdir)/prism/util/pm_string.h prism/pack.$(OBJEXT): $(top_srcdir)/prism/defines.h prism/pack.$(OBJEXT): $(top_srcdir)/prism/pack.c prism/pack.$(OBJEXT): $(top_srcdir)/prism/pack.h -prism/pack.$(OBJEXT): {$(VPATH)}config.h prism/prettyprint.$(OBJEXT): $(top_srcdir)/prism/defines.h prism/prettyprint.$(OBJEXT): $(top_srcdir)/prism/encoding.h prism/prettyprint.$(OBJEXT): $(top_srcdir)/prism/options.h @@ -12596,7 +12601,6 @@ prism/prettyprint.$(OBJEXT): $(top_srcdir)/prism/util/pm_newline_list.h prism/prettyprint.$(OBJEXT): $(top_srcdir)/prism/util/pm_state_stack.h prism/prettyprint.$(OBJEXT): $(top_srcdir)/prism/util/pm_string.h prism/prettyprint.$(OBJEXT): $(top_srcdir)/prism/util/pm_strncasecmp.h -prism/prettyprint.$(OBJEXT): {$(VPATH)}config.h prism/prettyprint.$(OBJEXT): {$(VPATH)}prism/ast.h prism/prettyprint.$(OBJEXT): {$(VPATH)}prism/prettyprint.c prism/prism.$(OBJEXT): $(top_srcdir)/prism/defines.h @@ -12623,7 +12627,6 @@ prism/prism.$(OBJEXT): $(top_srcdir)/prism/util/pm_string_list.h prism/prism.$(OBJEXT): $(top_srcdir)/prism/util/pm_strncasecmp.h prism/prism.$(OBJEXT): $(top_srcdir)/prism/util/pm_strpbrk.h prism/prism.$(OBJEXT): $(top_srcdir)/prism/version.h -prism/prism.$(OBJEXT): {$(VPATH)}config.h prism/prism.$(OBJEXT): {$(VPATH)}prism/ast.h prism/prism.$(OBJEXT): {$(VPATH)}prism/diagnostic.h prism/prism.$(OBJEXT): {$(VPATH)}prism/version.h @@ -12644,7 +12647,6 @@ prism/regexp.$(OBJEXT): $(top_srcdir)/prism/util/pm_state_stack.h prism/regexp.$(OBJEXT): $(top_srcdir)/prism/util/pm_string.h prism/regexp.$(OBJEXT): $(top_srcdir)/prism/util/pm_string_list.h prism/regexp.$(OBJEXT): $(top_srcdir)/prism/util/pm_strncasecmp.h -prism/regexp.$(OBJEXT): {$(VPATH)}config.h prism/regexp.$(OBJEXT): {$(VPATH)}prism/ast.h prism/serialize.$(OBJEXT): $(top_srcdir)/prism/defines.h prism/serialize.$(OBJEXT): $(top_srcdir)/prism/encoding.h @@ -12668,7 +12670,6 @@ prism/serialize.$(OBJEXT): $(top_srcdir)/prism/util/pm_string.h prism/serialize.$(OBJEXT): $(top_srcdir)/prism/util/pm_string_list.h prism/serialize.$(OBJEXT): $(top_srcdir)/prism/util/pm_strncasecmp.h prism/serialize.$(OBJEXT): $(top_srcdir)/prism/util/pm_strpbrk.h -prism/serialize.$(OBJEXT): {$(VPATH)}config.h prism/serialize.$(OBJEXT): {$(VPATH)}prism/ast.h prism/serialize.$(OBJEXT): {$(VPATH)}prism/diagnostic.h prism/serialize.$(OBJEXT): {$(VPATH)}prism/serialize.c @@ -12697,7 +12698,6 @@ prism/token_type.$(OBJEXT): $(top_srcdir)/prism/util/pm_constant_pool.h prism/token_type.$(OBJEXT): $(top_srcdir)/prism/util/pm_integer.h prism/token_type.$(OBJEXT): $(top_srcdir)/prism/util/pm_newline_list.h prism/token_type.$(OBJEXT): $(top_srcdir)/prism/util/pm_string.h -prism/token_type.$(OBJEXT): {$(VPATH)}config.h prism/token_type.$(OBJEXT): {$(VPATH)}prism/ast.h prism/token_type.$(OBJEXT): {$(VPATH)}prism/token_type.c prism/util/pm_buffer.$(OBJEXT): $(top_srcdir)/prism/defines.h @@ -12705,16 +12705,13 @@ prism/util/pm_buffer.$(OBJEXT): $(top_srcdir)/prism/util/pm_buffer.c prism/util/pm_buffer.$(OBJEXT): $(top_srcdir)/prism/util/pm_buffer.h prism/util/pm_buffer.$(OBJEXT): $(top_srcdir)/prism/util/pm_char.h prism/util/pm_buffer.$(OBJEXT): $(top_srcdir)/prism/util/pm_newline_list.h -prism/util/pm_buffer.$(OBJEXT): {$(VPATH)}config.h prism/util/pm_char.$(OBJEXT): $(top_srcdir)/prism/defines.h prism/util/pm_char.$(OBJEXT): $(top_srcdir)/prism/util/pm_char.c prism/util/pm_char.$(OBJEXT): $(top_srcdir)/prism/util/pm_char.h prism/util/pm_char.$(OBJEXT): $(top_srcdir)/prism/util/pm_newline_list.h -prism/util/pm_char.$(OBJEXT): {$(VPATH)}config.h prism/util/pm_constant_pool.$(OBJEXT): $(top_srcdir)/prism/defines.h prism/util/pm_constant_pool.$(OBJEXT): $(top_srcdir)/prism/util/pm_constant_pool.c prism/util/pm_constant_pool.$(OBJEXT): $(top_srcdir)/prism/util/pm_constant_pool.h -prism/util/pm_constant_pool.$(OBJEXT): {$(VPATH)}config.h prism/util/pm_integer.$(OBJEXT): $(top_srcdir)/prism/defines.h prism/util/pm_integer.$(OBJEXT): $(top_srcdir)/prism/util/pm_buffer.h prism/util/pm_integer.$(OBJEXT): $(top_srcdir)/prism/util/pm_char.h @@ -12724,7 +12721,6 @@ prism/util/pm_integer.$(OBJEXT): $(top_srcdir)/prism/util/pm_newline_list.h prism/util/pm_list.$(OBJEXT): $(top_srcdir)/prism/defines.h prism/util/pm_list.$(OBJEXT): $(top_srcdir)/prism/util/pm_list.c prism/util/pm_list.$(OBJEXT): $(top_srcdir)/prism/util/pm_list.h -prism/util/pm_list.$(OBJEXT): {$(VPATH)}config.h prism/util/pm_memchr.$(OBJEXT): $(top_srcdir)/prism/defines.h prism/util/pm_memchr.$(OBJEXT): $(top_srcdir)/prism/encoding.h prism/util/pm_memchr.$(OBJEXT): $(top_srcdir)/prism/parser.h @@ -12736,25 +12732,20 @@ prism/util/pm_memchr.$(OBJEXT): $(top_srcdir)/prism/util/pm_newline_list.h prism/util/pm_memchr.$(OBJEXT): $(top_srcdir)/prism/util/pm_state_stack.h prism/util/pm_memchr.$(OBJEXT): $(top_srcdir)/prism/util/pm_string.h prism/util/pm_memchr.$(OBJEXT): $(top_srcdir)/prism/util/pm_strncasecmp.h -prism/util/pm_memchr.$(OBJEXT): {$(VPATH)}config.h prism/util/pm_memchr.$(OBJEXT): {$(VPATH)}prism/ast.h prism/util/pm_newline_list.$(OBJEXT): $(top_srcdir)/prism/defines.h prism/util/pm_newline_list.$(OBJEXT): $(top_srcdir)/prism/util/pm_newline_list.c prism/util/pm_newline_list.$(OBJEXT): $(top_srcdir)/prism/util/pm_newline_list.h -prism/util/pm_newline_list.$(OBJEXT): {$(VPATH)}config.h prism/util/pm_state_stack.$(OBJEXT): $(top_srcdir)/prism/defines.h prism/util/pm_state_stack.$(OBJEXT): $(top_srcdir)/prism/util/pm_state_stack.c prism/util/pm_state_stack.$(OBJEXT): $(top_srcdir)/prism/util/pm_state_stack.h -prism/util/pm_state_stack.$(OBJEXT): {$(VPATH)}config.h prism/util/pm_string.$(OBJEXT): $(top_srcdir)/prism/defines.h prism/util/pm_string.$(OBJEXT): $(top_srcdir)/prism/util/pm_string.c prism/util/pm_string.$(OBJEXT): $(top_srcdir)/prism/util/pm_string.h -prism/util/pm_string.$(OBJEXT): {$(VPATH)}config.h prism/util/pm_string_list.$(OBJEXT): $(top_srcdir)/prism/defines.h prism/util/pm_string_list.$(OBJEXT): $(top_srcdir)/prism/util/pm_string.h prism/util/pm_string_list.$(OBJEXT): $(top_srcdir)/prism/util/pm_string_list.c prism/util/pm_string_list.$(OBJEXT): $(top_srcdir)/prism/util/pm_string_list.h -prism/util/pm_string_list.$(OBJEXT): {$(VPATH)}config.h prism/util/pm_strncasecmp.$(OBJEXT): $(top_srcdir)/prism/defines.h prism/util/pm_strncasecmp.$(OBJEXT): $(top_srcdir)/prism/util/pm_strncasecmp.c prism/util/pm_strncasecmp.$(OBJEXT): $(top_srcdir)/prism/util/pm_strncasecmp.h @@ -12773,7 +12764,6 @@ prism/util/pm_strpbrk.$(OBJEXT): $(top_srcdir)/prism/util/pm_string.h prism/util/pm_strpbrk.$(OBJEXT): $(top_srcdir)/prism/util/pm_strncasecmp.h prism/util/pm_strpbrk.$(OBJEXT): $(top_srcdir)/prism/util/pm_strpbrk.c prism/util/pm_strpbrk.$(OBJEXT): $(top_srcdir)/prism/util/pm_strpbrk.h -prism/util/pm_strpbrk.$(OBJEXT): {$(VPATH)}config.h prism/util/pm_strpbrk.$(OBJEXT): {$(VPATH)}prism/ast.h prism/util/pm_strpbrk.$(OBJEXT): {$(VPATH)}prism/diagnostic.h prism_init.$(OBJEXT): $(hdrdir)/ruby.h diff --git a/compile.c b/compile.c index 02cffc9aca723e..3fb1566ad47d90 100644 --- a/compile.c +++ b/compile.c @@ -36,6 +36,7 @@ #include "internal/thread.h" #include "internal/variable.h" #include "iseq.h" +#include "ruby/ractor.h" #include "ruby/re.h" #include "ruby/util.h" #include "vm_core.h" @@ -868,10 +869,6 @@ rb_iseq_compile_node(rb_iseq_t *iseq, const NODE *node) DECL_ANCHOR(ret); INIT_ANCHOR(ret); - if (IMEMO_TYPE_P(node, imemo_ifunc)) { - rb_raise(rb_eArgError, "unexpected imemo_ifunc"); - } - if (node == 0) { NO_CHECK(COMPILE(ret, "nil", node)); iseq_set_local_table(iseq, 0); @@ -999,6 +996,7 @@ rb_iseq_translate_threaded_code(rb_iseq_t *iseq) #if USE_YJIT rb_yjit_live_iseq_count++; + rb_yjit_iseq_alloc_count++; #endif return COMPILE_OK; @@ -1925,9 +1923,6 @@ iseq_set_arguments_keywords(rb_iseq_t *iseq, LINK_ANCHOR *const optargs, } else { switch (nd_type(val_node)) { - case NODE_LIT: - dv = RNODE_LIT(val_node)->nd_lit; - break; case NODE_SYM: dv = rb_node_sym_string_val(val_node); break; @@ -3192,7 +3187,7 @@ ci_argc_set(const rb_iseq_t *iseq, const struct rb_callinfo *ci, int argc) static bool optimize_args_splat_no_copy(rb_iseq_t *iseq, INSN *insn, LINK_ELEMENT *niobj, - unsigned int set_flags, unsigned int unset_flags) + unsigned int set_flags, unsigned int unset_flags, unsigned int remove_flags) { LINK_ELEMENT *iobj = (LINK_ELEMENT *)insn; if ((set_flags & VM_CALL_ARGS_BLOCKARG) && (set_flags & VM_CALL_KW_SPLAT) && @@ -3210,7 +3205,7 @@ optimize_args_splat_no_copy(rb_iseq_t *iseq, INSN *insn, LINK_ELEMENT *niobj, RUBY_ASSERT(flags & VM_CALL_ARGS_SPLAT_MUT); OPERAND_AT(iobj, 0) = Qfalse; const struct rb_callinfo *nci = vm_ci_new(vm_ci_mid(ci), - flags & ~VM_CALL_ARGS_SPLAT_MUT, vm_ci_argc(ci), vm_ci_kwarg(ci)); + flags & ~(VM_CALL_ARGS_SPLAT_MUT|remove_flags), vm_ci_argc(ci), vm_ci_kwarg(ci)); RB_OBJ_WRITTEN(iseq, ci, nci); OPERAND_AT(niobj, 0) = (VALUE)nci; return true; @@ -3902,7 +3897,7 @@ iseq_peephole_optimize(rb_iseq_t *iseq, LINK_ELEMENT *list, const int do_tailcal * send */ if (optimize_args_splat_no_copy(iseq, iobj, niobj, - VM_CALL_ARGS_SPLAT, VM_CALL_KW_SPLAT|VM_CALL_ARGS_BLOCKARG)) goto optimized_splat; + VM_CALL_ARGS_SPLAT, VM_CALL_KW_SPLAT|VM_CALL_ARGS_BLOCKARG, 0)) goto optimized_splat; if (IS_NEXT_INSN_ID(niobj, getlocal) || IS_NEXT_INSN_ID(niobj, getinstancevariable)) { niobj = niobj->next; @@ -3919,7 +3914,7 @@ iseq_peephole_optimize(rb_iseq_t *iseq, LINK_ELEMENT *list, const int do_tailcal * send */ if (optimize_args_splat_no_copy(iseq, iobj, niobj, - VM_CALL_ARGS_SPLAT|VM_CALL_ARGS_BLOCKARG, VM_CALL_KW_SPLAT)) goto optimized_splat; + VM_CALL_ARGS_SPLAT|VM_CALL_ARGS_BLOCKARG, VM_CALL_KW_SPLAT, 0)) goto optimized_splat; /* * Eliminate array allocation for f(*a, **lvar) and f(*a, **@iv) @@ -3933,7 +3928,7 @@ iseq_peephole_optimize(rb_iseq_t *iseq, LINK_ELEMENT *list, const int do_tailcal * send */ if (optimize_args_splat_no_copy(iseq, iobj, niobj, - VM_CALL_ARGS_SPLAT|VM_CALL_KW_SPLAT, VM_CALL_ARGS_BLOCKARG)) goto optimized_splat; + VM_CALL_ARGS_SPLAT|VM_CALL_KW_SPLAT, VM_CALL_ARGS_BLOCKARG, 0)) goto optimized_splat; if (IS_NEXT_INSN_ID(niobj, getlocal) || IS_NEXT_INSN_ID(niobj, getinstancevariable) || IS_NEXT_INSN_ID(niobj, getblockparamproxy)) { @@ -3953,7 +3948,7 @@ iseq_peephole_optimize(rb_iseq_t *iseq, LINK_ELEMENT *list, const int do_tailcal * send */ optimize_args_splat_no_copy(iseq, iobj, niobj, - VM_CALL_ARGS_SPLAT|VM_CALL_KW_SPLAT|VM_CALL_ARGS_BLOCKARG, 0); + VM_CALL_ARGS_SPLAT|VM_CALL_KW_SPLAT|VM_CALL_ARGS_BLOCKARG, 0, 0); } } else if (IS_NEXT_INSN_ID(niobj, getblockparamproxy)) { /* @@ -3968,28 +3963,34 @@ iseq_peephole_optimize(rb_iseq_t *iseq, LINK_ELEMENT *list, const int do_tailcal * send */ optimize_args_splat_no_copy(iseq, iobj, niobj, - VM_CALL_ARGS_SPLAT|VM_CALL_ARGS_BLOCKARG, VM_CALL_KW_SPLAT); + VM_CALL_ARGS_SPLAT|VM_CALL_ARGS_BLOCKARG, VM_CALL_KW_SPLAT, 0); } else if (IS_NEXT_INSN_ID(niobj, duphash)) { niobj = niobj->next; /* - * Eliminate array allocation for f(*a, kw: 1) + * Eliminate array and hash allocation for f(*a, kw: 1) * * splatarray true * duphash * send ARGS_SPLAT|KW_SPLAT|KW_SPLAT_MUT and not ARGS_BLOCKARG * => * splatarray false - * duphash - * send + * putobject + * send ARGS_SPLAT|KW_SPLAT */ - if (optimize_args_splat_no_copy(iseq, iobj, niobj->next, - VM_CALL_ARGS_SPLAT|VM_CALL_KW_SPLAT|VM_CALL_KW_SPLAT_MUT, VM_CALL_ARGS_BLOCKARG)) goto optimized_splat; + if (optimize_args_splat_no_copy(iseq, iobj, niobj, + VM_CALL_ARGS_SPLAT|VM_CALL_KW_SPLAT|VM_CALL_KW_SPLAT_MUT, VM_CALL_ARGS_BLOCKARG, VM_CALL_KW_SPLAT_MUT)) { + + ((INSN*)niobj)->insn_id = BIN(putobject); + OPERAND_AT(niobj, 0) = rb_hash_freeze(rb_hash_resurrect(OPERAND_AT(niobj, 0))); + + goto optimized_splat; + } if (IS_NEXT_INSN_ID(niobj, getlocal) || IS_NEXT_INSN_ID(niobj, getinstancevariable) || IS_NEXT_INSN_ID(niobj, getblockparamproxy)) { /* - * Eliminate array allocation for f(*a, kw: 1, &{arg,lvar,@iv}) + * Eliminate array and hash allocation for f(*a, kw: 1, &{arg,lvar,@iv}) * * splatarray true * duphash @@ -3997,12 +3998,16 @@ iseq_peephole_optimize(rb_iseq_t *iseq, LINK_ELEMENT *list, const int do_tailcal * send ARGS_SPLAT|KW_SPLAT|KW_SPLAT_MUT|ARGS_BLOCKARG * => * splatarray false - * duphash + * putobject * getlocal / getinstancevariable / getblockparamproxy - * send + * send ARGS_SPLAT|KW_SPLAT|ARGS_BLOCKARG */ - optimize_args_splat_no_copy(iseq, iobj, niobj->next, - VM_CALL_ARGS_SPLAT|VM_CALL_KW_SPLAT|VM_CALL_KW_SPLAT_MUT|VM_CALL_ARGS_BLOCKARG, 0); + if (optimize_args_splat_no_copy(iseq, iobj, niobj->next, + VM_CALL_ARGS_SPLAT|VM_CALL_KW_SPLAT|VM_CALL_KW_SPLAT_MUT|VM_CALL_ARGS_BLOCKARG, 0, VM_CALL_KW_SPLAT_MUT)) { + + ((INSN*)niobj)->insn_id = BIN(putobject); + OPERAND_AT(niobj, 0) = rb_hash_freeze(rb_hash_resurrect(OPERAND_AT(niobj, 0))); + } } } } @@ -4333,7 +4338,7 @@ compile_dstr_fragments(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *cons while (list) { const NODE *const head = list->nd_head; if (nd_type_p(head, NODE_STR)) { - lit = rb_fstring(rb_node_str_string_val(head)); + lit = rb_node_str_string_val(head); ADD_INSN1(ret, head, putobject, lit); RB_OBJ_WRITTEN(iseq, Qundef, lit); lit = Qnil; @@ -4372,7 +4377,7 @@ compile_dstr(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node) { int cnt; if (!RNODE_DSTR(node)->nd_next) { - VALUE lit = rb_fstring(rb_node_dstr_string_val(node)); + VALUE lit = rb_node_dstr_string_val(node); ADD_INSN1(ret, node, putstring, lit); RB_OBJ_WRITTEN(iseq, Qundef, lit); } @@ -4498,7 +4503,6 @@ compile_branch_condition(rb_iseq_t *iseq, LINK_ANCHOR *ret, const NODE *cond, else_label = NEW_LABEL(nd_line(cond)); } goto again; - case NODE_LIT: /* NODE_LIT is always true */ case NODE_SYM: case NODE_LINE: case NODE_FILE: @@ -4579,8 +4583,6 @@ static VALUE get_symbol_value(rb_iseq_t *iseq, const NODE *node) { switch (nd_type(node)) { - case NODE_LIT: - return RNODE_LIT(node)->nd_lit; case NODE_SYM: return rb_node_sym_string_val(node); default: @@ -4629,10 +4631,7 @@ compile_keyword_arg(rb_iseq_t *iseq, LINK_ANCHOR *const ret, seen_nodes++; RUBY_ASSERT(nd_type_p(node, NODE_LIST)); - if (key_node && nd_type_p(key_node, NODE_LIT) && SYMBOL_P(RNODE_LIT(key_node)->nd_lit)) { - /* can be keywords */ - } - else if (key_node && nd_type_p(key_node, NODE_SYM)) { + if (key_node && nd_type_p(key_node, NODE_SYM)) { /* can be keywords */ } else { @@ -4707,11 +4706,16 @@ compile_args(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *node, NODE **k return len; } -static inline int +static inline bool +frozen_string_literal_p(const rb_iseq_t *iseq) +{ + return ISEQ_COMPILE_DATA(iseq)->option->frozen_string_literal > 0; +} + +static inline bool static_literal_node_p(const NODE *node, const rb_iseq_t *iseq, bool hash_key) { switch (nd_type(node)) { - case NODE_LIT: case NODE_SYM: case NODE_REGX: case NODE_LINE: @@ -4726,7 +4730,7 @@ static_literal_node_p(const NODE *node, const rb_iseq_t *iseq, bool hash_key) return TRUE; case NODE_STR: case NODE_FILE: - return hash_key || ISEQ_COMPILE_DATA(iseq)->option->frozen_string_literal; + return hash_key || frozen_string_literal_p(iseq); default: return FALSE; } @@ -4761,17 +4765,14 @@ static_literal_value(const NODE *node, rb_iseq_t *iseq) case NODE_FILE: case NODE_STR: if (ISEQ_COMPILE_DATA(iseq)->option->debug_frozen_string_literal || RTEST(ruby_debug)) { - VALUE lit; VALUE debug_info = rb_ary_new_from_args(2, rb_iseq_path(iseq), INT2FIX((int)nd_line(node))); - lit = rb_str_dup(get_string_value(node)); + VALUE lit = rb_str_dup(get_string_value(node)); rb_ivar_set(lit, id_debug_created_info, rb_obj_freeze(debug_info)); return rb_str_freeze(lit); } else { - return rb_fstring(get_string_value(node)); + return get_string_value(node); } - case NODE_LIT: - return RNODE_LIT(node)->nd_lit; default: rb_bug("unexpected node: %s", ruby_node_name(nd_type(node))); } @@ -5050,7 +5051,7 @@ compile_hash(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *node, int meth FLUSH_CHUNK(); const NODE *kw = RNODE_LIST(RNODE_LIST(node)->nd_next)->nd_head; - int empty_kw = nd_type_p(kw, NODE_LIT) && RB_TYPE_P(RNODE_LIT(kw)->nd_lit, T_HASH); /* foo( ..., **{}, ...) */ + int empty_kw = nd_type_p(kw, NODE_HASH) && (!RNODE_HASH(kw)->nd_head); /* foo( ..., **{}, ...) */ int first_kw = first_chunk && stack_len == 0; /* foo(1,2,3, **kw, ...) */ int last_kw = !RNODE_LIST(RNODE_LIST(node)->nd_next)->nd_next; /* foo( ..., **kw) */ int only_kw = last_kw && first_kw; /* foo(1,2,3, **kw) */ @@ -5115,13 +5116,6 @@ VALUE rb_node_case_when_optimizable_literal(const NODE *const node) { switch (nd_type(node)) { - case NODE_LIT: { - VALUE v = RNODE_LIT(node)->nd_lit; - if (SYMBOL_P(v)) { - return v; - } - break; - } case NODE_INTEGER: return rb_node_integer_literal_val(node); case NODE_FLOAT: { @@ -5147,9 +5141,9 @@ rb_node_case_when_optimizable_literal(const NODE *const node) case NODE_LINE: return rb_node_line_lineno_val(node); case NODE_STR: - return rb_fstring(rb_node_str_string_val(node)); + return rb_node_str_string_val(node); case NODE_FILE: - return rb_fstring(rb_node_file_path_val(node)); + return rb_node_file_path_val(node); } return Qundef; } @@ -5171,7 +5165,7 @@ when_vals(rb_iseq_t *iseq, LINK_ANCHOR *const cond_seq, const NODE *vals, if (nd_type_p(val, NODE_STR) || nd_type_p(val, NODE_FILE)) { debugp_param("nd_lit", get_string_value(val)); - lit = rb_fstring(get_string_value(val)); + lit = get_string_value(val); ADD_INSN1(cond_seq, val, putobject, lit); RB_OBJ_WRITTEN(iseq, Qundef, lit); } @@ -5797,7 +5791,6 @@ defined_expr0(rb_iseq_t *iseq, LINK_ANCHOR *const ret, } /* fall through */ case NODE_STR: - case NODE_LIT: case NODE_SYM: case NODE_REGX: case NODE_LINE: @@ -6381,8 +6374,6 @@ optimizable_range_item_p(const NODE *n) { if (!n) return FALSE; switch (nd_type(n)) { - case NODE_LIT: - return RB_INTEGER_TYPE_P(RNODE_LIT(n)->nd_lit); case NODE_LINE: return TRUE; case NODE_INTEGER: @@ -6398,8 +6389,6 @@ static VALUE optimized_range_item(const NODE *n) { switch (nd_type(n)) { - case NODE_LIT: - return RNODE_LIT(n)->nd_lit; case NODE_LINE: return rb_node_line_lineno_val(n); case NODE_INTEGER: @@ -7225,7 +7214,6 @@ iseq_compile_pattern_each(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *c ADD_INSNL(ret, line_node, jump, unmatched); break; } - case NODE_LIT: case NODE_SYM: case NODE_REGX: case NODE_LINE: @@ -8456,7 +8444,7 @@ compile_call_precheck_freeze(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE get_nd_args(node) == NULL && ISEQ_COMPILE_DATA(iseq)->current_block == NULL && ISEQ_COMPILE_DATA(iseq)->option->specialized_instruction) { - VALUE str = rb_fstring(get_string_value(get_nd_recv(node))); + VALUE str = get_string_value(get_nd_recv(node)); if (get_node_call_nd_mid(node) == idUMinus) { ADD_INSN2(ret, line_node, opt_str_uminus, str, new_callinfo(iseq, idUMinus, 0, 0, NULL, FALSE)); @@ -8478,9 +8466,9 @@ compile_call_precheck_freeze(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE nd_type_p(get_nd_args(node), NODE_LIST) && RNODE_LIST(get_nd_args(node))->as.nd_alen == 1 && (nd_type_p(RNODE_LIST(get_nd_args(node))->nd_head, NODE_STR) || nd_type_p(RNODE_LIST(get_nd_args(node))->nd_head, NODE_FILE)) && ISEQ_COMPILE_DATA(iseq)->current_block == NULL && - !ISEQ_COMPILE_DATA(iseq)->option->frozen_string_literal && + !frozen_string_literal_p(iseq) && ISEQ_COMPILE_DATA(iseq)->option->specialized_instruction) { - VALUE str = rb_fstring(get_string_value(RNODE_LIST(get_nd_args(node))->nd_head)); + VALUE str = get_string_value(RNODE_LIST(get_nd_args(node))->nd_head); CHECK(COMPILE(ret, "recv", get_nd_recv(node))); ADD_INSN2(ret, line_node, opt_aref_with, str, new_callinfo(iseq, idAREF, 1, 0, NULL, FALSE)); @@ -8627,9 +8615,6 @@ compile_builtin_attr(rb_iseq_t *iseq, const NODE *node) case NODE_SYM: symbol = rb_node_sym_string_val(node); break; - case NODE_LIT: - symbol = RNODE_LIT(node)->nd_lit; - break; default: goto bad_arg; } @@ -8676,9 +8661,6 @@ compile_builtin_arg(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *node, c case NODE_SYM: name = rb_node_sym_string_val(node); break; - case NODE_LIT: - name = RNODE_LIT(node)->nd_lit; - break; default: goto bad_arg; } @@ -8917,20 +8899,7 @@ compile_call(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node, co labels_table = st_init_numtable(); ISEQ_COMPILE_DATA(iseq)->labels_table = labels_table; } - if (nd_type_p(node->nd_args->nd_head, NODE_LIT) && - SYMBOL_P(node->nd_args->nd_head->nd_lit)) { - - label_name = node->nd_args->nd_head->nd_lit; - if (!st_lookup(labels_table, (st_data_t)label_name, &data)) { - label = NEW_LABEL(nd_line(line_node)); - label->position = nd_line(line_node); - st_insert(labels_table, (st_data_t)label_name, (st_data_t)label); - } - else { - label = (LABEL *)data; - } - } - else { + { COMPILE_ERROR(ERROR_ARGS "invalid goto/label format"); return COMPILE_NG; } @@ -9022,9 +8991,6 @@ compile_op_asgn1(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node unsigned int flag = 0; int asgnflag = 0; ID id = RNODE_OP_ASGN1(node)->nd_mid; - int boff = 0; - int keyword_len = 0; - struct rb_callinfo_kwarg *keywords = NULL; /* * a[x] (op)= y @@ -9058,32 +9024,14 @@ compile_op_asgn1(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node case NODE_ZLIST: argc = INT2FIX(0); break; - case NODE_BLOCK_PASS: - boff = 1; - /* fall through */ default: - argc = setup_args(iseq, ret, RNODE_OP_ASGN1(node)->nd_index, &flag, &keywords); - if (flag & VM_CALL_KW_SPLAT) { - if (boff) { - ADD_INSN(ret, node, splatkw); - } - else { - /* Make sure to_hash is only called once and not twice */ - ADD_INSN(ret, node, dup); - ADD_INSN(ret, node, splatkw); - ADD_INSN(ret, node, pop); - } - } + argc = setup_args(iseq, ret, RNODE_OP_ASGN1(node)->nd_index, &flag, NULL); CHECK(!NIL_P(argc)); } - int dup_argn = FIX2INT(argc) + 1 + boff; - if (keywords) { - keyword_len = keywords->keyword_len; - dup_argn += keyword_len; - } + int dup_argn = FIX2INT(argc) + 1; ADD_INSN1(ret, node, dupn, INT2FIX(dup_argn)); flag |= asgnflag; - ADD_SEND_R(ret, node, idAREF, argc, NULL, INT2FIX(flag & ~(VM_CALL_ARGS_SPLAT_MUT|VM_CALL_KW_SPLAT_MUT)), keywords); + ADD_SEND_R(ret, node, idAREF, argc, NULL, INT2FIX(flag & ~VM_CALL_ARGS_SPLAT_MUT), NULL); if (id == idOROP || id == idANDOP) { /* a[x] ||= y or a[x] &&= y @@ -9111,57 +9059,17 @@ compile_op_asgn1(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node ADD_INSN1(ret, node, setn, INT2FIX(dup_argn+1)); } if (flag & VM_CALL_ARGS_SPLAT) { - if (flag & VM_CALL_KW_SPLAT) { - ADD_INSN1(ret, node, topn, INT2FIX(2 + boff)); - if (!(flag & VM_CALL_ARGS_SPLAT_MUT)) { - ADD_INSN1(ret, node, splatarray, Qtrue); - flag |= VM_CALL_ARGS_SPLAT_MUT; - } + if (!(flag & VM_CALL_ARGS_SPLAT_MUT)) { ADD_INSN(ret, node, swap); - ADD_INSN1(ret, node, pushtoarray, INT2FIX(1)); - ADD_INSN1(ret, node, setn, INT2FIX(2 + boff)); - ADD_INSN(ret, node, pop); - } - else { - if (boff > 0) { - ADD_INSN1(ret, node, dupn, INT2FIX(3)); - ADD_INSN(ret, node, swap); - ADD_INSN(ret, node, pop); - } - if (!(flag & VM_CALL_ARGS_SPLAT_MUT)) { - ADD_INSN(ret, node, swap); - ADD_INSN1(ret, node, splatarray, Qtrue); - ADD_INSN(ret, node, swap); - flag |= VM_CALL_ARGS_SPLAT_MUT; - } - ADD_INSN1(ret, node, pushtoarray, INT2FIX(1)); - if (boff > 0) { - ADD_INSN1(ret, node, setn, INT2FIX(3)); - ADD_INSN(ret, node, pop); - ADD_INSN(ret, node, pop); - } - } - ADD_SEND_R(ret, node, idASET, argc, NULL, INT2FIX(flag), keywords); - } - else if (flag & VM_CALL_KW_SPLAT) { - if (boff > 0) { - ADD_INSN1(ret, node, topn, INT2FIX(2)); + ADD_INSN1(ret, node, splatarray, Qtrue); ADD_INSN(ret, node, swap); - ADD_INSN1(ret, node, setn, INT2FIX(3)); - ADD_INSN(ret, node, pop); + flag |= VM_CALL_ARGS_SPLAT_MUT; } - ADD_INSN(ret, node, swap); - ADD_SEND_R(ret, node, idASET, FIXNUM_INC(argc, 1), NULL, INT2FIX(flag), keywords); - } - else if (keyword_len) { - ADD_INSN1(ret, node, opt_reverse, INT2FIX(keyword_len+boff+1)); - ADD_INSN1(ret, node, opt_reverse, INT2FIX(keyword_len+boff+0)); - ADD_SEND_R(ret, node, idASET, FIXNUM_INC(argc, 1), NULL, INT2FIX(flag), keywords); + ADD_INSN1(ret, node, pushtoarray, INT2FIX(1)); + ADD_SEND_R(ret, node, idASET, argc, NULL, INT2FIX(flag), NULL); } else { - if (boff > 0) - ADD_INSN(ret, node, swap); - ADD_SEND_R(ret, node, idASET, FIXNUM_INC(argc, 1), NULL, INT2FIX(flag), keywords); + ADD_SEND_R(ret, node, idASET, FIXNUM_INC(argc, 1), NULL, INT2FIX(flag), NULL); } ADD_INSN(ret, node, pop); ADD_INSNL(ret, node, jump, lfin); @@ -9180,22 +9088,17 @@ compile_op_asgn1(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node } if (flag & VM_CALL_ARGS_SPLAT) { if (flag & VM_CALL_KW_SPLAT) { - ADD_INSN1(ret, node, topn, INT2FIX(2 + boff)); + ADD_INSN1(ret, node, topn, INT2FIX(2)); if (!(flag & VM_CALL_ARGS_SPLAT_MUT)) { ADD_INSN1(ret, node, splatarray, Qtrue); flag |= VM_CALL_ARGS_SPLAT_MUT; } ADD_INSN(ret, node, swap); ADD_INSN1(ret, node, pushtoarray, INT2FIX(1)); - ADD_INSN1(ret, node, setn, INT2FIX(2 + boff)); + ADD_INSN1(ret, node, setn, INT2FIX(2)); ADD_INSN(ret, node, pop); } else { - if (boff > 0) { - ADD_INSN1(ret, node, dupn, INT2FIX(3)); - ADD_INSN(ret, node, swap); - ADD_INSN(ret, node, pop); - } if (!(flag & VM_CALL_ARGS_SPLAT_MUT)) { ADD_INSN(ret, node, swap); ADD_INSN1(ret, node, splatarray, Qtrue); @@ -9203,35 +9106,11 @@ compile_op_asgn1(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node flag |= VM_CALL_ARGS_SPLAT_MUT; } ADD_INSN1(ret, node, pushtoarray, INT2FIX(1)); - if (boff > 0) { - ADD_INSN1(ret, node, setn, INT2FIX(3)); - ADD_INSN(ret, node, pop); - ADD_INSN(ret, node, pop); - } } - ADD_SEND_R(ret, node, idASET, argc, NULL, INT2FIX(flag), keywords); - } - else if (flag & VM_CALL_KW_SPLAT) { - if (boff > 0) { - ADD_INSN1(ret, node, topn, INT2FIX(2)); - ADD_INSN(ret, node, swap); - ADD_INSN1(ret, node, setn, INT2FIX(3)); - ADD_INSN(ret, node, pop); - } - ADD_INSN(ret, node, swap); - ADD_SEND_R(ret, node, idASET, FIXNUM_INC(argc, 1), NULL, INT2FIX(flag), keywords); - } - else if (keyword_len) { - ADD_INSN(ret, node, dup); - ADD_INSN1(ret, node, opt_reverse, INT2FIX(keyword_len+boff+2)); - ADD_INSN1(ret, node, opt_reverse, INT2FIX(keyword_len+boff+1)); - ADD_INSN(ret, node, pop); - ADD_SEND_R(ret, node, idASET, FIXNUM_INC(argc, 1), NULL, INT2FIX(flag), keywords); + ADD_SEND_R(ret, node, idASET, argc, NULL, INT2FIX(flag), NULL); } else { - if (boff > 0) - ADD_INSN(ret, node, swap); - ADD_SEND_R(ret, node, idASET, FIXNUM_INC(argc, 1), NULL, INT2FIX(flag), keywords); + ADD_SEND_R(ret, node, idASET, FIXNUM_INC(argc, 1), NULL, INT2FIX(flag), NULL); } ADD_INSN(ret, node, pop); } @@ -9358,6 +9237,8 @@ compile_op_asgn2(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node return COMPILE_OK; } +static int compile_shareable_constant_value(rb_iseq_t *iseq, LINK_ANCHOR *ret, enum rb_parser_shareability shareable, const NODE *lhs, const NODE *value); + static int compile_op_cdecl(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node, int popped) { @@ -9401,7 +9282,7 @@ compile_op_cdecl(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node /* cref [obj] */ if (!popped) ADD_INSN(ret, node, pop); /* cref */ if (lassign) ADD_LABEL(ret, lassign); - CHECK(COMPILE(ret, "NODE_OP_CDECL#nd_value", RNODE_OP_CDECL(node)->nd_value)); + CHECK(compile_shareable_constant_value(iseq, ret, RNODE_OP_CDECL(node)->shareability, RNODE_OP_CDECL(node)->nd_head, RNODE_OP_CDECL(node)->nd_value)); /* cref value */ if (popped) ADD_INSN1(ret, node, topn, INT2FIX(1)); /* cref value cref */ @@ -9415,7 +9296,7 @@ compile_op_cdecl(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node ADD_INSN(ret, node, pop); /* [value] */ } else { - CHECK(COMPILE(ret, "NODE_OP_CDECL#nd_value", RNODE_OP_CDECL(node)->nd_value)); + CHECK(compile_shareable_constant_value(iseq, ret, RNODE_OP_CDECL(node)->shareability, RNODE_OP_CDECL(node)->nd_head, RNODE_OP_CDECL(node)->nd_value)); /* cref obj value */ ADD_CALL(ret, node, RNODE_OP_CDECL(node)->nd_aid, INT2FIX(1)); /* cref value */ @@ -9814,8 +9695,7 @@ compile_kw_arg(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node, COMPILE_ERROR(ERROR_ARGS "unreachable"); return COMPILE_NG; } - else if (nd_type_p(default_value, NODE_LIT) || - nd_type_p(default_value, NODE_SYM) || + else if (nd_type_p(default_value, NODE_SYM) || nd_type_p(default_value, NODE_REGX) || nd_type_p(default_value, NODE_LINE) || nd_type_p(default_value, NODE_INTEGER) || @@ -9862,10 +9742,10 @@ compile_attrasgn(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node nd_type_p(RNODE_ATTRASGN(node)->nd_args, NODE_LIST) && RNODE_LIST(RNODE_ATTRASGN(node)->nd_args)->as.nd_alen == 2 && (nd_type_p(RNODE_LIST(RNODE_ATTRASGN(node)->nd_args)->nd_head, NODE_STR) || nd_type_p(RNODE_LIST(RNODE_ATTRASGN(node)->nd_args)->nd_head, NODE_FILE)) && ISEQ_COMPILE_DATA(iseq)->current_block == NULL && - !ISEQ_COMPILE_DATA(iseq)->option->frozen_string_literal && + !frozen_string_literal_p(iseq) && ISEQ_COMPILE_DATA(iseq)->option->specialized_instruction) { - VALUE str = rb_fstring(get_string_value(RNODE_LIST(RNODE_ATTRASGN(node)->nd_args)->nd_head)); + VALUE str = get_string_value(RNODE_LIST(RNODE_ATTRASGN(node)->nd_args)->nd_head); CHECK(COMPILE(ret, "recv", RNODE_ATTRASGN(node)->nd_recv)); CHECK(COMPILE(ret, "value", RNODE_LIST(RNODE_LIST(RNODE_ATTRASGN(node)->nd_args)->nd_next)->nd_head)); if (!popped) { @@ -9901,16 +9781,7 @@ compile_attrasgn(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node ADD_SEQ(ret, recv); ADD_SEQ(ret, args); - if (flag & VM_CALL_ARGS_BLOCKARG) { - ADD_INSN1(ret, node, topn, INT2FIX(1)); - if (flag & VM_CALL_ARGS_SPLAT) { - ADD_INSN1(ret, node, putobject, INT2FIX(-1)); - ADD_SEND_WITH_FLAG(ret, node, idAREF, INT2FIX(1), INT2FIX(asgnflag)); - } - ADD_INSN1(ret, node, setn, FIXNUM_INC(argc, 3)); - ADD_INSN (ret, node, pop); - } - else if (flag & VM_CALL_ARGS_SPLAT) { + if (flag & VM_CALL_ARGS_SPLAT) { ADD_INSN(ret, node, dup); ADD_INSN1(ret, node, putobject, INT2FIX(-1)); ADD_SEND_WITH_FLAG(ret, node, idAREF, INT2FIX(1), INT2FIX(asgnflag)); @@ -9931,6 +9802,367 @@ compile_attrasgn(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node return COMPILE_OK; } +static int +compile_make_shareable_node(rb_iseq_t *iseq, LINK_ANCHOR *ret, LINK_ANCHOR *sub, const NODE *value, bool copy) +{ + ADD_INSN1(ret, value, putobject, rb_mRubyVMFrozenCore); + ADD_SEQ(ret, sub); + + if (copy) { + /* + * NEW_CALL(fcore, rb_intern("make_shareable_copy"), + * NEW_LIST(value, loc), loc); + */ + ADD_SEND_WITH_FLAG(ret, value, rb_intern("make_shareable_copy"), INT2FIX(1), INT2FIX(VM_CALL_ARGS_SIMPLE)); + } + else { + /* + * NEW_CALL(fcore, rb_intern("make_shareable"), + * NEW_LIST(value, loc), loc); + */ + ADD_SEND_WITH_FLAG(ret, value, rb_intern("make_shareable"), INT2FIX(1), INT2FIX(VM_CALL_ARGS_SIMPLE)); + } + + return COMPILE_OK; +} + +static VALUE +node_const_decl_val(const NODE *node) +{ + VALUE path; + switch (nd_type(node)) { + case NODE_CDECL: + if (RNODE_CDECL(node)->nd_vid) { + path = rb_id2str(RNODE_CDECL(node)->nd_vid); + goto end; + } + else { + node = RNODE_CDECL(node)->nd_else; + } + break; + case NODE_COLON2: + break; + case NODE_COLON3: + // ::Const + path = rb_str_new_cstr("::"); + rb_str_append(path, rb_id2str(RNODE_COLON3(node)->nd_mid)); + goto end; + default: + rb_bug("unexpected node: %s", ruby_node_name(nd_type(node))); + UNREACHABLE_RETURN(0); + } + + path = rb_ary_new(); + if (node) { + for (; node && nd_type_p(node, NODE_COLON2); node = RNODE_COLON2(node)->nd_head) { + rb_ary_push(path, rb_id2str(RNODE_COLON2(node)->nd_mid)); + } + if (node && nd_type_p(node, NODE_CONST)) { + // Const::Name + rb_ary_push(path, rb_id2str(RNODE_CONST(node)->nd_vid)); + } + else if (node && nd_type_p(node, NODE_COLON3)) { + // ::Const::Name + rb_ary_push(path, rb_id2str(RNODE_COLON3(node)->nd_mid)); + rb_ary_push(path, rb_str_new(0, 0)); + } + else { + // expression::Name + rb_ary_push(path, rb_str_new_cstr("...")); + } + path = rb_ary_join(rb_ary_reverse(path), rb_str_new_cstr("::")); + } + end: + path = rb_fstring(path); + return path; +} + +static VALUE +const_decl_path(NODE *dest) +{ + VALUE path = Qnil; + if (!nd_type_p(dest, NODE_CALL)) { + path = node_const_decl_val(dest); + } + return path; +} + +static int +compile_ensure_shareable_node(rb_iseq_t *iseq, LINK_ANCHOR *ret, NODE *dest, const NODE *value) +{ + /* + *. RubyVM::FrozenCore.ensure_shareable(value, const_decl_path(dest)) + */ + VALUE path = const_decl_path(dest); + ADD_INSN1(ret, value, putobject, rb_mRubyVMFrozenCore); + CHECK(COMPILE(ret, "compile_ensure_shareable_node", value)); + ADD_INSN1(ret, value, putobject, path); + RB_OBJ_WRITTEN(iseq, Qundef, path); + ADD_SEND_WITH_FLAG(ret, value, rb_intern("ensure_shareable"), INT2FIX(2), INT2FIX(VM_CALL_ARGS_SIMPLE)); + + return COMPILE_OK; +} + +#ifndef SHAREABLE_BARE_EXPRESSION +#define SHAREABLE_BARE_EXPRESSION 1 +#endif + +static int +compile_shareable_literal_constant(rb_iseq_t *iseq, LINK_ANCHOR *ret, enum rb_parser_shareability shareable, NODE *dest, const NODE *node, size_t level, VALUE *value_p, int *shareable_literal_p) +{ +# define compile_shareable_literal_constant_next(node, anchor, value_p, shareable_literal_p) \ + compile_shareable_literal_constant(iseq, anchor, shareable, dest, node, level+1, value_p, shareable_literal_p) + VALUE lit = Qnil; + DECL_ANCHOR(anchor); + + enum node_type type = nd_type(node); + switch (type) { + case NODE_TRUE: + *value_p = Qtrue; + goto compile; + case NODE_FALSE: + *value_p = Qfalse; + goto compile; + case NODE_NIL: + *value_p = Qnil; + goto compile; + case NODE_SYM: + *value_p = rb_node_sym_string_val(node); + goto compile; + case NODE_REGX: + *value_p = rb_node_regx_string_val(node); + goto compile; + case NODE_LINE: + *value_p = rb_node_line_lineno_val(node); + goto compile; + case NODE_INTEGER: + *value_p = rb_node_integer_literal_val(node); + goto compile; + case NODE_FLOAT: + *value_p = rb_node_float_literal_val(node); + goto compile; + case NODE_RATIONAL: + *value_p = rb_node_rational_literal_val(node); + goto compile; + case NODE_IMAGINARY: + *value_p = rb_node_imaginary_literal_val(node); + goto compile; + case NODE_ENCODING: + *value_p = rb_node_encoding_val(node); + + compile: + CHECK(COMPILE(ret, "shareable_literal_constant", node)); + *shareable_literal_p = 1; + return COMPILE_OK; + + case NODE_DSTR: + CHECK(COMPILE(ret, "shareable_literal_constant", node)); + if (shareable == rb_parser_shareable_literal) { + /* + * NEW_CALL(node, idUMinus, 0, loc); + * + * -"#{var}" + */ + ADD_SEND_WITH_FLAG(ret, node, idUMinus, INT2FIX(0), INT2FIX(VM_CALL_ARGS_SIMPLE)); + } + *value_p = Qundef; + *shareable_literal_p = 1; + return COMPILE_OK; + + case NODE_STR:{ + VALUE lit = rb_node_str_string_val(node); + ADD_INSN1(ret, node, putobject, lit); + RB_OBJ_WRITTEN(iseq, Qundef, lit); + *value_p = lit; + *shareable_literal_p = 1; + + return COMPILE_OK; + } + + case NODE_FILE:{ + VALUE lit = rb_node_file_path_val(node); + ADD_INSN1(ret, node, putobject, lit); + RB_OBJ_WRITTEN(iseq, Qundef, lit); + *value_p = lit; + *shareable_literal_p = 1; + + return COMPILE_OK; + } + + case NODE_ZLIST:{ + VALUE lit = rb_ary_new(); + OBJ_FREEZE_RAW(lit); + ADD_INSN1(ret, node, putobject, lit); + RB_OBJ_WRITTEN(iseq, Qundef, lit); + *value_p = lit; + *shareable_literal_p = 1; + + return COMPILE_OK; + } + + case NODE_LIST:{ + INIT_ANCHOR(anchor); + lit = rb_ary_new(); + for (NODE *n = (NODE *)node; n; n = RNODE_LIST(n)->nd_next) { + VALUE val; + int shareable_literal_p2; + NODE *elt = RNODE_LIST(n)->nd_head; + if (elt) { + CHECK(compile_shareable_literal_constant_next(elt, anchor, &val, &shareable_literal_p2)); + if (shareable_literal_p2) { + /* noop */ + } + else if (RTEST(lit)) { + rb_ary_clear(lit); + lit = Qfalse; + } + } + if (RTEST(lit)) { + if (!UNDEF_P(val)) { + rb_ary_push(lit, val); + } + else { + rb_ary_clear(lit); + lit = Qnil; /* make shareable at runtime */ + } + } + } + break; + } + case NODE_HASH:{ + if (!RNODE_HASH(node)->nd_brace) { + *value_p = Qundef; + *shareable_literal_p = 0; + return COMPILE_OK; + } + + INIT_ANCHOR(anchor); + lit = rb_hash_new(); + for (NODE *n = RNODE_HASH(node)->nd_head; n; n = RNODE_LIST(RNODE_LIST(n)->nd_next)->nd_next) { + VALUE key_val; + VALUE value_val; + int shareable_literal_p2; + NODE *key = RNODE_LIST(n)->nd_head; + NODE *val = RNODE_LIST(RNODE_LIST(n)->nd_next)->nd_head; + if (key) { + CHECK(compile_shareable_literal_constant_next(key, anchor, &key_val, &shareable_literal_p2)); + if (shareable_literal_p2) { + /* noop */ + } + else if (RTEST(lit)) { + rb_hash_clear(lit); + lit = Qfalse; + } + } + if (val) { + CHECK(compile_shareable_literal_constant_next(val, anchor, &value_val, &shareable_literal_p2)); + if (shareable_literal_p2) { + /* noop */ + } + else if (RTEST(lit)) { + rb_hash_clear(lit); + lit = Qfalse; + } + } + if (RTEST(lit)) { + if (!UNDEF_P(key_val) && !UNDEF_P(value_val)) { + rb_hash_aset(lit, key_val, value_val); + } + else { + rb_hash_clear(lit); + lit = Qnil; /* make shareable at runtime */ + } + } + } + break; + } + + default: + if (shareable == rb_parser_shareable_literal && + (SHAREABLE_BARE_EXPRESSION || level > 0)) { + CHECK(compile_ensure_shareable_node(iseq, ret, dest, node)); + *value_p = Qundef; + *shareable_literal_p = 1; + return COMPILE_OK; + } + CHECK(COMPILE(ret, "shareable_literal_constant", node)); + *value_p = Qundef; + *shareable_literal_p = 0; + return COMPILE_OK; + } + + /* Array or Hash */ + if (!lit) { + if (nd_type(node) == NODE_LIST) { + ADD_INSN1(anchor, node, newarray, INT2FIX(RNODE_LIST(node)->as.nd_alen)); + } + else if (nd_type(node) == NODE_HASH) { + int len = (int)RNODE_LIST(RNODE_HASH(node)->nd_head)->as.nd_alen; + ADD_INSN1(anchor, node, newhash, INT2FIX(len)); + } + *value_p = Qundef; + *shareable_literal_p = 0; + ADD_SEQ(ret, anchor); + return COMPILE_OK; + } + if (NIL_P(lit)) { + // if shareable_literal, all elements should have been ensured + // as shareable + if (nd_type(node) == NODE_LIST) { + ADD_INSN1(anchor, node, newarray, INT2FIX(RNODE_LIST(node)->as.nd_alen)); + } + else if (nd_type(node) == NODE_HASH) { + int len = (int)RNODE_LIST(RNODE_HASH(node)->nd_head)->as.nd_alen; + ADD_INSN1(anchor, node, newhash, INT2FIX(len)); + } + CHECK(compile_make_shareable_node(iseq, ret, anchor, node, false)); + *value_p = Qundef; + *shareable_literal_p = 1; + } + else { + VALUE val = rb_ractor_make_shareable(lit); + ADD_INSN1(ret, node, putobject, val); + RB_OBJ_WRITTEN(iseq, Qundef, val); + *value_p = val; + *shareable_literal_p = 1; + } + + return COMPILE_OK; +} + +static int +compile_shareable_constant_value(rb_iseq_t *iseq, LINK_ANCHOR *ret, enum rb_parser_shareability shareable, const NODE *lhs, const NODE *value) +{ + int literal_p = 0; + VALUE val; + DECL_ANCHOR(anchor); + INIT_ANCHOR(anchor); + + switch (shareable) { + case rb_parser_shareable_none: + CHECK(COMPILE(ret, "compile_shareable_constant_value", value)); + return COMPILE_OK; + + case rb_parser_shareable_literal: + CHECK(compile_shareable_literal_constant(iseq, anchor, shareable, (NODE *)lhs, value, 0, &val, &literal_p)); + ADD_SEQ(ret, anchor); + return COMPILE_OK; + + case rb_parser_shareable_copy: + case rb_parser_shareable_everything: + CHECK(compile_shareable_literal_constant(iseq, anchor, shareable, (NODE *)lhs, value, 0, &val, &literal_p)); + if (!literal_p) { + CHECK(compile_make_shareable_node(iseq, ret, anchor, value, shareable == rb_parser_shareable_copy)); + } + else { + ADD_SEQ(ret, anchor); + } + return COMPILE_OK; + default: + rb_bug("unexpected rb_parser_shareability: %d", shareable); + } +} + static int iseq_compile_each0(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node, int popped); /** compile each node @@ -10113,7 +10345,7 @@ iseq_compile_each0(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const no } case NODE_CDECL:{ if (RNODE_CDECL(node)->nd_vid) { - CHECK(COMPILE(ret, "lvalue", RNODE_CDECL(node)->nd_value)); + CHECK(compile_shareable_constant_value(iseq, ret, RNODE_CDECL(node)->shareability, node, RNODE_CDECL(node)->nd_value)); if (!popped) { ADD_INSN(ret, node, dup); @@ -10125,7 +10357,7 @@ iseq_compile_each0(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const no } else { compile_cpath(ret, iseq, RNODE_CDECL(node)->nd_else); - CHECK(COMPILE(ret, "lvalue", RNODE_CDECL(node)->nd_value)); + CHECK(compile_shareable_constant_value(iseq, ret, RNODE_CDECL(node)->shareability, node, RNODE_CDECL(node)->nd_value)); ADD_INSN(ret, node, swap); if (!popped) { @@ -10282,14 +10514,6 @@ iseq_compile_each0(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const no case NODE_MATCH3: CHECK(compile_match(iseq, ret, node, popped, type)); break; - case NODE_LIT:{ - debugp_param("lit", RNODE_LIT(node)->nd_lit); - if (!popped) { - ADD_INSN1(ret, node, putobject, RNODE_LIT(node)->nd_lit); - RB_OBJ_WRITTEN(iseq, Qundef, RNODE_LIT(node)->nd_lit); - } - break; - } case NODE_SYM:{ if (!popped) { ADD_INSN1(ret, node, putobject, rb_node_sym_string_val(node)); @@ -10349,23 +10573,27 @@ iseq_compile_each0(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const no debugp_param("nd_lit", get_string_value(node)); if (!popped) { VALUE lit = get_string_value(node); - if (!ISEQ_COMPILE_DATA(iseq)->option->frozen_string_literal) { - lit = rb_fstring(lit); + switch (ISEQ_COMPILE_DATA(iseq)->option->frozen_string_literal) { + case ISEQ_FROZEN_STRING_LITERAL_UNSET: + ADD_INSN1(ret, node, putchilledstring, lit); + RB_OBJ_WRITTEN(iseq, Qundef, lit); + break; + case ISEQ_FROZEN_STRING_LITERAL_DISABLED: ADD_INSN1(ret, node, putstring, lit); RB_OBJ_WRITTEN(iseq, Qundef, lit); - } - else { + break; + case ISEQ_FROZEN_STRING_LITERAL_ENABLED: if (ISEQ_COMPILE_DATA(iseq)->option->debug_frozen_string_literal || RTEST(ruby_debug)) { VALUE debug_info = rb_ary_new_from_args(2, rb_iseq_path(iseq), INT2FIX(line)); lit = rb_str_dup(lit); rb_ivar_set(lit, id_debug_created_info, rb_obj_freeze(debug_info)); lit = rb_str_freeze(lit); } - else { - lit = rb_fstring(lit); - } ADD_INSN1(ret, node, putobject, lit); RB_OBJ_WRITTEN(iseq, Qundef, lit); + break; + default: + rb_bug("invalid frozen_string_literal"); } } break; @@ -10380,7 +10608,7 @@ iseq_compile_each0(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const no } case NODE_XSTR:{ ADD_CALL_RECEIVER(ret, node); - VALUE str = rb_fstring(rb_node_str_string_val(node)); + VALUE str = rb_node_str_string_val(node); ADD_INSN1(ret, node, putobject, str); RB_OBJ_WRITTEN(iseq, Qundef, str); ADD_CALL(ret, node, idBackquote, INT2FIX(1)); @@ -11292,6 +11520,7 @@ iseq_build_from_ary_body(rb_iseq_t *iseq, LINK_ANCHOR *const anchor, } } DATA_PTR(labels_wrapper) = 0; + RB_GC_GUARD(labels_wrapper); validate_labels(iseq, labels_table); if (!ret) return ret; return iseq_setup(iseq, anchor); diff --git a/configure.ac b/configure.ac index eecfcb6dc53167..6d393dd3f21785 100644 --- a/configure.ac +++ b/configure.ac @@ -76,8 +76,10 @@ AC_ARG_WITH(baseruby, [ AC_PATH_PROG([BASERUBY], [ruby], [false]) ]) -# BASERUBY must be >= 3.0.0. Note that `"3.0.0" > "3.0"` is true. -AS_IF([test "$HAVE_BASERUBY" != no -a "`RUBYOPT=- $BASERUBY --disable=gems -e 'print true if RUBY_VERSION > "3.0"' 2>/dev/null`" = true], [ +AS_IF([test "$HAVE_BASERUBY" != no], [ + RUBYOPT=- $BASERUBY --disable=gems "${tooldir}/missing-baseruby.bat" || HAVE_BASERUBY=no +]) +AS_IF([test "${HAVE_BASERUBY:=no}" != no], [ AS_CASE(["$build_os"], [mingw*], [ # Can MSys shell run a command with a drive letter? RUBYOPT=- `cygpath -ma "$BASERUBY"` --disable=gems -e exit 2>/dev/null || HAVE_BASERUBY=no @@ -85,12 +87,10 @@ AS_IF([test "$HAVE_BASERUBY" != no -a "`RUBYOPT=- $BASERUBY --disable=gems -e 'p RUBY_APPEND_OPTION(BASERUBY, "--disable=gems") BASERUBY_VERSION=`$BASERUBY -v` $BASERUBY -C "$srcdir" tool/downloader.rb -d tool -e gnu config.guess config.sub >&AS_MESSAGE_FD -], [ - HAVE_BASERUBY=no ]) AS_IF([test "$HAVE_BASERUBY" = no], [ AS_IF([test "$cross_compiling" = yes], [AC_MSG_ERROR([executable host ruby is required for cross-compiling])]) - BASERUBY=$srcdir/tool/missing-baseruby.bat + BASERUBY=${tooldir}/missing-baseruby.bat ]) AC_SUBST(BASERUBY) AC_SUBST(HAVE_BASERUBY) @@ -1433,7 +1433,7 @@ AC_SYS_LARGEFILE # which is not added by AC_SYS_LARGEFILE. AS_IF([test x"$enable_largefile" != xno], [ AS_CASE(["$target_os"], [solaris*], [ - AC_MSG_CHECKING([wheather _LARGEFILE_SOURCE should be defined]) + AC_MSG_CHECKING([whether _LARGEFILE_SOURCE should be defined]) AS_CASE(["${ac_cv_sys_file_offset_bits}:${ac_cv_sys_large_files}"], ["64:"|"64:no"|"64:unknown"], [ # insert _LARGEFILE_SOURCE before _FILE_OFFSET_BITS line @@ -2100,7 +2100,6 @@ AC_CHECK_FUNCS(gettimeofday) # for making ac_cv_func_gettimeofday AC_CHECK_FUNCS(getuid) AC_CHECK_FUNCS(getuidx) AC_CHECK_FUNCS(gmtime_r) -AC_CHECK_FUNCS(grantpt) AC_CHECK_FUNCS(initgroups) AC_CHECK_FUNCS(ioctl) AC_CHECK_FUNCS(isfinite) @@ -2974,7 +2973,7 @@ AS_IF([test "x$ac_cv_func_ioctl" = xyes], [ } [begin]_group "runtime section" && { -dnl wheather use dln_a_out or not +dnl whether use dln_a_out or not AC_ARG_WITH(dln-a-out, AS_HELP_STRING([--with-dln-a-out], [dln_a_out is deprecated]), [ diff --git a/cont.c b/cont.c index 47f2be8c95408c..12ae525d08f3b5 100644 --- a/cont.c +++ b/cont.c @@ -796,6 +796,9 @@ static inline void ec_switch(rb_thread_t *th, rb_fiber_t *fiber) { rb_execution_context_t *ec = &fiber->cont.saved_ec; +#ifdef RUBY_ASAN_ENABLED + ec->machine.asan_fake_stack_handle = asan_get_thread_fake_stack_handle(); +#endif rb_ractor_set_current_ec(th->ractor, th->ec = ec); // ruby_current_execution_context_ptr = th->ec = ec; @@ -1023,13 +1026,8 @@ cont_mark(void *ptr) cont->machine.stack + cont->machine.stack_size); } else { - /* fiber */ - const rb_fiber_t *fiber = (rb_fiber_t*)cont; - - if (!FIBER_TERMINATED_P(fiber)) { - rb_gc_mark_locations(cont->machine.stack, - cont->machine.stack + cont->machine.stack_size); - } + /* fiber machine context is marked as part of rb_execution_context_mark, no need to + * do anything here. */ } } @@ -1568,11 +1566,10 @@ fiber_setcontext(rb_fiber_t *new_fiber, rb_fiber_t *old_fiber) } } - /* exchange machine_stack_start between old_fiber and new_fiber */ + /* these values are used in rb_gc_mark_machine_context to mark the fiber's stack. */ old_fiber->cont.saved_ec.machine.stack_start = th->ec->machine.stack_start; + old_fiber->cont.saved_ec.machine.stack_end = FIBER_TERMINATED_P(old_fiber) ? NULL : th->ec->machine.stack_end; - /* old_fiber->machine.stack_end should be NULL */ - old_fiber->cont.saved_ec.machine.stack_end = NULL; // if (DEBUG) fprintf(stderr, "fiber_setcontext: %p[%p] -> %p[%p]\n", (void*)old_fiber, old_fiber->stack.base, (void*)new_fiber, new_fiber->stack.base); @@ -2385,7 +2382,7 @@ rb_fiber_initialize(int argc, VALUE* argv, VALUE self) VALUE rb_fiber_new_storage(rb_block_call_func_t func, VALUE obj, VALUE storage) { - return fiber_initialize(fiber_alloc(rb_cFiber), rb_proc_new(func, obj), rb_fiber_pool_default(Qnil), 1, storage); + return fiber_initialize(fiber_alloc(rb_cFiber), rb_proc_new(func, obj), rb_fiber_pool_default(Qnil), 0, storage); } VALUE @@ -3277,6 +3274,8 @@ rb_fiber_raise(VALUE fiber, int argc, const VALUE *argv) * blocks. * * Raises +FiberError+ if called on a Fiber belonging to another +Thread+. + * + * See Kernel#raise for more information. */ static VALUE rb_fiber_m_raise(int argc, VALUE *argv, VALUE self) diff --git a/darray.h b/darray.h index bf3dd9954272f8..41d386e4563347 100644 --- a/darray.h +++ b/darray.h @@ -45,16 +45,12 @@ * void rb_darray_append(rb_darray(T) *ptr_to_ary, T element); */ #define rb_darray_append(ptr_to_ary, element) \ - rb_darray_append_impl(ptr_to_ary, element, rb_xrealloc_mul_add) + rb_darray_append_impl(ptr_to_ary, element) -#define rb_darray_append_without_gc(ptr_to_ary, element) \ - rb_darray_append_impl(ptr_to_ary, element, rb_darray_realloc_mul_add_without_gc) - -#define rb_darray_append_impl(ptr_to_ary, element, realloc_func) do { \ +#define rb_darray_append_impl(ptr_to_ary, element) do { \ rb_darray_ensure_space((ptr_to_ary), \ sizeof(**(ptr_to_ary)), \ - sizeof((*(ptr_to_ary))->data[0]), \ - realloc_func); \ + sizeof((*(ptr_to_ary))->data[0])); \ rb_darray_set(*(ptr_to_ary), \ (*(ptr_to_ary))->meta.size, \ (element)); \ @@ -79,21 +75,15 @@ * void rb_darray_make(rb_darray(T) *ptr_to_ary, size_t size); */ #define rb_darray_make(ptr_to_ary, size) \ - rb_darray_make_impl((ptr_to_ary), size, sizeof(**(ptr_to_ary)), \ - sizeof((*(ptr_to_ary))->data[0]), rb_xcalloc_mul_add) - -#define rb_darray_make_without_gc(ptr_to_ary, size) \ - rb_darray_make_impl((ptr_to_ary), size, sizeof(**(ptr_to_ary)), \ - sizeof((*(ptr_to_ary))->data[0]), rb_darray_calloc_mul_add_without_gc) + rb_darray_make_impl((ptr_to_ary), size, sizeof(**(ptr_to_ary)), sizeof((*(ptr_to_ary))->data[0])) /* Resize the darray to a new capacity. The new capacity must be greater than * or equal to the size of the darray. * * void rb_darray_resize_capa(rb_darray(T) *ptr_to_ary, size_t capa); */ -#define rb_darray_resize_capa_without_gc(ptr_to_ary, capa) \ - rb_darray_resize_capa_impl((ptr_to_ary), rb_darray_next_power_of_two(capa), sizeof(**(ptr_to_ary)), \ - sizeof((*(ptr_to_ary))->data[0]), rb_darray_realloc_mul_add_without_gc) +#define rb_darray_resize_capa(ptr_to_ary, capa) \ + rb_darray_resize_capa_impl((ptr_to_ary), capa, sizeof(**(ptr_to_ary)), sizeof((*(ptr_to_ary))->data[0])) #define rb_darray_data_ptr(ary) ((ary)->data) @@ -139,56 +129,15 @@ rb_darray_free(void *ary) if (meta) ruby_sized_xfree(ary, meta->capa); } -static inline void -rb_darray_free_without_gc(void *ary) -{ - free(ary); -} - -/* Internal function. Like rb_xcalloc_mul_add but does not trigger GC and does - * not check for overflow in arithmetic. */ -static inline void * -rb_darray_calloc_mul_add_without_gc(size_t x, size_t y, size_t z) -{ - size_t size = (x * y) + z; - - void *ptr = calloc(1, size); - if (ptr == NULL) rb_bug("rb_darray_calloc_mul_add_without_gc: failed"); - - return ptr; -} - -/* Internal function. Like rb_xrealloc_mul_add but does not trigger GC and does - * not check for overflow in arithmetic. */ -static inline void * -rb_darray_realloc_mul_add_without_gc(const void *orig_ptr, size_t x, size_t y, size_t z) -{ - size_t size = (x * y) + z; - - void *ptr = realloc((void *)orig_ptr, size); - if (ptr == NULL) rb_bug("rb_darray_realloc_mul_add_without_gc: failed"); - - return ptr; -} - -/* Internal function. Returns the next power of two that is greater than or - * equal to n. */ -static inline size_t -rb_darray_next_power_of_two(size_t n) -{ - return (size_t)(1 << (64 - nlz_int64(n))); -} - /* Internal function. Resizes the capacity of a darray. The new capacity must * be greater than or equal to the size of the darray. */ static inline void -rb_darray_resize_capa_impl(void *ptr_to_ary, size_t new_capa, size_t header_size, size_t element_size, - void *(*realloc_mul_add_impl)(const void *, size_t, size_t, size_t)) +rb_darray_resize_capa_impl(void *ptr_to_ary, size_t new_capa, size_t header_size, size_t element_size) { rb_darray_meta_t **ptr_to_ptr_to_meta = ptr_to_ary; rb_darray_meta_t *meta = *ptr_to_ptr_to_meta; - rb_darray_meta_t *new_ary = realloc_mul_add_impl(meta, new_capa, element_size, header_size); + rb_darray_meta_t *new_ary = rb_xrealloc_mul_add(meta, new_capa, element_size, header_size); if (meta == NULL) { /* First allocation. Initialize size. On subsequence allocations @@ -209,8 +158,7 @@ rb_darray_resize_capa_impl(void *ptr_to_ary, size_t new_capa, size_t header_size // Ensure there is space for one more element. // Note: header_size can be bigger than sizeof(rb_darray_meta_t) when T is __int128_t, for example. static inline void -rb_darray_ensure_space(void *ptr_to_ary, size_t header_size, size_t element_size, - void *(*realloc_mul_add_impl)(const void *, size_t, size_t, size_t)) +rb_darray_ensure_space(void *ptr_to_ary, size_t header_size, size_t element_size) { rb_darray_meta_t **ptr_to_ptr_to_meta = ptr_to_ary; rb_darray_meta_t *meta = *ptr_to_ptr_to_meta; @@ -220,12 +168,11 @@ rb_darray_ensure_space(void *ptr_to_ary, size_t header_size, size_t element_size // Double the capacity size_t new_capa = current_capa == 0 ? 1 : current_capa * 2; - rb_darray_resize_capa_impl(ptr_to_ary, new_capa, header_size, element_size, realloc_mul_add_impl); + rb_darray_resize_capa_impl(ptr_to_ary, new_capa, header_size, element_size); } static inline void -rb_darray_make_impl(void *ptr_to_ary, size_t array_size, size_t header_size, size_t element_size, - void *(*calloc_mul_add_impl)(size_t, size_t, size_t)) +rb_darray_make_impl(void *ptr_to_ary, size_t array_size, size_t header_size, size_t element_size) { rb_darray_meta_t **ptr_to_ptr_to_meta = ptr_to_ary; if (array_size == 0) { @@ -233,7 +180,7 @@ rb_darray_make_impl(void *ptr_to_ary, size_t array_size, size_t header_size, siz return; } - rb_darray_meta_t *meta = calloc_mul_add_impl(array_size, element_size, header_size); + rb_darray_meta_t *meta = rb_xcalloc_mul_add(array_size, element_size, header_size); meta->size = array_size; meta->capa = array_size; diff --git a/debug.c b/debug.c index 755f27531668ab..4717a0bc9c5e90 100644 --- a/debug.c +++ b/debug.c @@ -185,9 +185,9 @@ ruby_env_debug_option(const char *str, int len, void *arg) int ov; size_t retlen; unsigned long n; +#define NAME_MATCH(name) (len == sizeof(name) - 1 && strncmp(str, (name), len) == 0) #define SET_WHEN(name, var, val) do { \ - if (len == sizeof(name) - 1 && \ - strncmp(str, (name), len) == 0) { \ + if (NAME_MATCH(name)) { \ (var) = (val); \ return 1; \ } \ @@ -219,27 +219,27 @@ ruby_env_debug_option(const char *str, int len, void *arg) } \ } while (0) #define SET_WHEN_UINT(name, vals, num, req) \ - if (NAME_MATCH_VALUE(name)) SET_UINT_LIST(name, vals, num); + if (NAME_MATCH_VALUE(name)) { \ + if (!len) req; \ + else SET_UINT_LIST(name, vals, num); \ + return 1; \ + } - SET_WHEN("gc_stress", *ruby_initial_gc_stress_ptr, Qtrue); - SET_WHEN("core", ruby_enable_coredump, 1); - SET_WHEN("ci", ruby_on_ci, 1); - if (NAME_MATCH_VALUE("rgengc")) { - if (!len) ruby_rgengc_debug = 1; - else SET_UINT_LIST("rgengc", &ruby_rgengc_debug, 1); + if (NAME_MATCH("gc_stress")) { + rb_gc_initial_stress_set(Qtrue); return 1; } + SET_WHEN("core", ruby_enable_coredump, 1); + SET_WHEN("ci", ruby_on_ci, 1); + SET_WHEN_UINT("rgengc", &ruby_rgengc_debug, 1, ruby_rgengc_debug = 1); #if defined _WIN32 # if RUBY_MSVCRT_VERSION >= 80 SET_WHEN("rtc_error", ruby_w32_rtc_error, 1); # endif #endif #if defined _WIN32 || defined __CYGWIN__ - if (NAME_MATCH_VALUE("codepage")) { - if (!len) fprintf(stderr, "missing codepage argument"); - else SET_UINT_LIST("codepage", ruby_w32_codepage, numberof(ruby_w32_codepage)); - return 1; - } + SET_WHEN_UINT("codepage", ruby_w32_codepage, numberof(ruby_w32_codepage), + fprintf(stderr, "missing codepage argument")); #endif return 0; } diff --git a/dir.c b/dir.c index 01cef9503be793..84ef5ee6f57c5a 100644 --- a/dir.c +++ b/dir.c @@ -1041,8 +1041,59 @@ dir_chdir0(VALUE path) rb_sys_fail_path(path); } -static int chdir_blocking = 0; -static VALUE chdir_thread = Qnil; +static struct { + VALUE thread; + VALUE path; + int line; + int blocking; +} chdir_lock = { + .blocking = 0, .thread = Qnil, + .path = Qnil, .line = 0, +}; + +static void +chdir_enter(void) +{ + if (chdir_lock.blocking == 0) { + chdir_lock.path = rb_source_location(&chdir_lock.line); + } + chdir_lock.blocking++; + if (NIL_P(chdir_lock.thread)) { + chdir_lock.thread = rb_thread_current(); + } +} + +static void +chdir_leave(void) +{ + chdir_lock.blocking--; + if (chdir_lock.blocking == 0) { + chdir_lock.thread = Qnil; + chdir_lock.path = Qnil; + chdir_lock.line = 0; + } +} + +static int +chdir_alone_block_p(void) +{ + int block_given = rb_block_given_p(); + if (chdir_lock.blocking > 0) { + if (rb_thread_current() != chdir_lock.thread) + rb_raise(rb_eRuntimeError, "conflicting chdir during another chdir block"); + if (!block_given) { + if (!NIL_P(chdir_lock.path)) { + rb_warn("conflicting chdir during another chdir block\n" + "%" PRIsVALUE ":%d: note: previous chdir was here", + chdir_lock.path, chdir_lock.line); + } + else { + rb_warn("conflicting chdir during another chdir block"); + } + } + } + return block_given; +} struct chdir_data { VALUE old_path, new_path; @@ -1056,9 +1107,7 @@ chdir_yield(VALUE v) struct chdir_data *args = (void *)v; dir_chdir0(args->new_path); args->done = TRUE; - chdir_blocking++; - if (NIL_P(chdir_thread)) - chdir_thread = rb_thread_current(); + chdir_enter(); return args->yield_path ? rb_yield(args->new_path) : rb_yield_values2(0, NULL); } @@ -1067,9 +1116,7 @@ chdir_restore(VALUE v) { struct chdir_data *args = (void *)v; if (args->done) { - chdir_blocking--; - if (chdir_blocking == 0) - chdir_thread = Qnil; + chdir_leave(); dir_chdir0(args->old_path); } return Qnil; @@ -1078,14 +1125,7 @@ chdir_restore(VALUE v) static VALUE chdir_path(VALUE path, bool yield_path) { - if (chdir_blocking > 0) { - if (rb_thread_current() != chdir_thread) - rb_raise(rb_eRuntimeError, "conflicting chdir during another chdir block"); - if (!rb_block_given_p()) - rb_warn("conflicting chdir during another chdir block"); - } - - if (rb_block_given_p()) { + if (chdir_alone_block_p()) { struct chdir_data args; args.old_path = rb_str_encode_ospath(rb_dir_getwd()); @@ -1215,9 +1255,7 @@ fchdir_yield(VALUE v) struct fchdir_data *args = (void *)v; dir_fchdir(args->fd); args->done = TRUE; - chdir_blocking++; - if (NIL_P(chdir_thread)) - chdir_thread = rb_thread_current(); + chdir_enter(); return rb_yield_values(0); } @@ -1226,9 +1264,7 @@ fchdir_restore(VALUE v) { struct fchdir_data *args = (void *)v; if (args->done) { - chdir_blocking--; - if (chdir_blocking == 0) - chdir_thread = Qnil; + chdir_leave(); dir_fchdir(RB_NUM2INT(dir_fileno(args->old_dir))); } dir_close(args->old_dir); @@ -1292,14 +1328,7 @@ dir_s_fchdir(VALUE klass, VALUE fd_value) { int fd = RB_NUM2INT(fd_value); - if (chdir_blocking > 0) { - if (rb_thread_current() != chdir_thread) - rb_raise(rb_eRuntimeError, "conflicting chdir during another chdir block"); - if (!rb_block_given_p()) - rb_warn("conflicting chdir during another chdir block"); - } - - if (rb_block_given_p()) { + if (chdir_alone_block_p()) { struct fchdir_data args; args.old_dir = dir_s_alloc(klass); dir_initialize(NULL, args.old_dir, rb_fstring_cstr("."), Qnil); @@ -3626,6 +3655,9 @@ rb_dir_s_empty_p(VALUE obj, VALUE dirname) void Init_Dir(void) { + rb_gc_register_address(&chdir_lock.path); + rb_gc_register_address(&chdir_lock.thread); + rb_cDir = rb_define_class("Dir", rb_cObject); rb_include_module(rb_cDir, rb_mEnumerable); diff --git a/dln_find.c b/dln_find.c index 91c51394a95463..ae37b9dde42426 100644 --- a/dln_find.c +++ b/dln_find.c @@ -11,11 +11,9 @@ #ifdef RUBY_EXPORT #include "ruby/ruby.h" -#define dln_warning rb_warning -#define dln_warning_arg +#define dln_warning(...) rb_warning(__VA_ARGS__) #else -#define dln_warning fprintf -#define dln_warning_arg stderr, +#define dln_warning(...) fprintf(stderr, __VA_ARGS__) #endif #include "dln.h" @@ -109,7 +107,7 @@ dln_find_1(const char *fname, const char *path, char *fbuf, size_t size, static const char pathname_too_long[] = "openpath: pathname too long (ignored)\n\ \tDirectory \"%.*s\"%s\n\tFile \"%.*s\"%s\n"; -#define PATHNAME_TOO_LONG() dln_warning(dln_warning_arg pathname_too_long, \ +#define PATHNAME_TOO_LONG() dln_warning(pathname_too_long, \ ((bp - fbuf) > 100 ? 100 : (int)(bp - fbuf)), fbuf, \ ((bp - fbuf) > 100 ? "..." : ""), \ (fnlen > 100 ? 100 : (int)fnlen), fname, \ @@ -120,8 +118,7 @@ dln_find_1(const char *fname, const char *path, char *fbuf, size_t size, RETURN_IF(!fname); fnlen = strlen(fname); if (fnlen >= size) { - dln_warning(dln_warning_arg - "openpath: pathname too long (ignored)\n\tFile \"%.*s\"%s\n", + dln_warning("openpath: pathname too long (ignored)\n\tFile \"%.*s\"%s\n", (fnlen > 100 ? 100 : (int)fnlen), fname, (fnlen > 100 ? "..." : "")); return NULL; diff --git a/doc/ChangeLog/ChangeLog-2.1.0 b/doc/ChangeLog/ChangeLog-2.1.0 index 5b670b31c97c8c..7964a682ebd9c8 100644 --- a/doc/ChangeLog/ChangeLog-2.1.0 +++ b/doc/ChangeLog/ChangeLog-2.1.0 @@ -659,7 +659,7 @@ Mon Dec 9 02:10:32 2013 NARUSE, Yui * lib/net/http/responses.rb: Add `HTTPIMUsed`, as it is also supported by rack/rails. - RFC - http://tools.ietf.org/html/rfc3229 + RFC - https://www.rfc-editor.org/rfc/rfc3229 by Vipul A M https://github.com/ruby/ruby/pull/447 fix GH-447 diff --git a/doc/ChangeLog/ChangeLog-2.2.0 b/doc/ChangeLog/ChangeLog-2.2.0 index 5a7dbf826d3135..0edcf0122b190d 100644 --- a/doc/ChangeLog/ChangeLog-2.2.0 +++ b/doc/ChangeLog/ChangeLog-2.2.0 @@ -5648,7 +5648,7 @@ Wed Aug 6 04:16:05 2014 NARUSE, Yui * lib/net/http/requests.rb (Net::HTTP::Options::RESPONSE_HAS_BODY): OPTIONS requests may have response bodies. [Feature #8429] - http://tools.ietf.org/html/rfc7231#section-4.3.7 + https://www.rfc-editor.org/rfc/rfc7231#section-4.3.7 Wed Aug 6 03:18:04 2014 NARUSE, Yui diff --git a/doc/ChangeLog/ChangeLog-2.4.0 b/doc/ChangeLog/ChangeLog-2.4.0 index a297a579d1d4ba..30e9ccab3d9aac 100644 --- a/doc/ChangeLog/ChangeLog-2.4.0 +++ b/doc/ChangeLog/ChangeLog-2.4.0 @@ -7356,7 +7356,7 @@ Thu Mar 17 11:51:48 2016 NARUSE, Yui Note: CryptGenRandom function is PRNG and doesn't check its entropy, so it won't block. [Bug #12139] https://msdn.microsoft.com/ja-jp/library/windows/desktop/aa379942.aspx - https://tools.ietf.org/html/rfc4086#section-7.1.3 + https://www.rfc-editor.org/rfc/rfc4086#section-7.1.3 https://eprint.iacr.org/2007/419.pdf http://www.cs.huji.ac.il/~dolev/pubs/thesis/msc-thesis-leo.pdf diff --git a/doc/contributing/building_ruby.md b/doc/contributing/building_ruby.md index 18464458896b74..2991385b94a6e6 100644 --- a/doc/contributing/building_ruby.md +++ b/doc/contributing/building_ruby.md @@ -181,12 +181,16 @@ mkdir build && cd build ../configure CC=clang cflags="-fsanitize=address -fno-omit-frame-pointer -DUSE_MN_THREADS=0" # and any other options you might like make ``` -The compiled Ruby will now automatically crash with a report and a backtrace if ASAN detects a memory safety issue. +The compiled Ruby will now automatically crash with a report and a backtrace if ASAN detects a memory safety issue. To run Ruby's test suite under ASAN, issue the following command. Note that this will take quite a long time (over two hours on my laptop); the `RUBY_TEST_TIMEOUT_SCALE` and `SYNTAX_SUGEST_TIMEOUT` variables are required to make sure tests don't spuriously fail with timeouts when in fact they're just slow. + +``` shell +RUBY_TEST_TIMEOUT_SCALE=5 SYNTAX_SUGGEST_TIMEOUT=600 make check +``` Please note, however, the following caveats! -* ASAN will not work properly on any currently released version of Ruby; the necessary support is currently only present on Ruby's master branch. -* Due to [this bug](https://bugs.ruby-lang.org/issues/20243), Clang generates code for threadlocal variables which doesn't work with M:N threading. Thus, it's necessary to disable M:N threading support at build time for now. +* ASAN will not work properly on any currently released version of Ruby; the necessary support is currently only present on Ruby's master branch (and the whole test suite passes only as of commit [9d0a5148ae062a0481a4a18fbeb9cfd01dc10428](https://bugs.ruby-lang.org/projects/ruby-master/repository/git/revisions/9d0a5148ae062a0481a4a18fbeb9cfd01dc10428)) +* Due to [this bug](https://bugs.ruby-lang.org/issues/20243), Clang generates code for threadlocal variables which doesn't work with M:N threading. Thus, it's necessary to disable M:N threading support at build time for now (with the `-DUSE_MN_THREADS=0` configure argument). * Currently, ASAN will only work correctly when using a recent head build of LLVM/Clang - it requires [this bugfix](https://github.com/llvm/llvm-project/pull/75290) related to multithreaded `fork`, which is not yet in any released version. See [here](https://llvm.org/docs/CMake.html) for instructions on how to build LLVM/Clang from source (note you will need at least the `clang` and `compiler-rt` projects enabled). Then, you will need to replace `CC=clang` in the instructions with an explicit path to your built Clang binary. * ASAN has only been tested so far with Clang on Linux. It may or may not work with other compilers or on other platforms - please file an issue on [https://bugs.ruby-lang.org](https://bugs.ruby-lang.org) if you run into problems with such configurations (or, to report that they actually work properly!) * In particular, although I have not yet tried it, I have reason to believe ASAN will _not_ work properly on macOS yet - the fix for the multithreaded fork issue was actually reverted for macOS (see [here](https://github.com/llvm/llvm-project/commit/2a03854e4ce9bb1bcd79a211063bc63c4657f92c)). Please open an issue on [https://bugs.ruby-lang.org](https://bugs.ruby-lang.org) if this is a problem for you. diff --git a/doc/csv/recipes/generating.rdoc b/doc/csv/recipes/generating.rdoc index a6bd88a7147a1e..e61838d31a5b3c 100644 --- a/doc/csv/recipes/generating.rdoc +++ b/doc/csv/recipes/generating.rdoc @@ -163,7 +163,7 @@ This example defines and uses two custom write converters to strip and upcase ge === RFC 4180 Compliance By default, \CSV generates data that is compliant with -{RFC 4180}[https://tools.ietf.org/html/rfc4180] +{RFC 4180}[https://www.rfc-editor.org/rfc/rfc4180] with respect to: - Column separator. - Quote character. diff --git a/doc/csv/recipes/parsing.rdoc b/doc/csv/recipes/parsing.rdoc index f3528fbdf1bc8b..1b7071e33f6543 100644 --- a/doc/csv/recipes/parsing.rdoc +++ b/doc/csv/recipes/parsing.rdoc @@ -191,7 +191,7 @@ Output: === RFC 4180 Compliance By default, \CSV parses data that is compliant with -{RFC 4180}[https://tools.ietf.org/html/rfc4180] +{RFC 4180}[https://www.rfc-editor.org/rfc/rfc4180] with respect to: - Row separator. - Column separator. diff --git a/doc/irb/indexes.md b/doc/irb/indexes.md index 9659db8c0bf1ac..24a67b969870fc 100644 --- a/doc/irb/indexes.md +++ b/doc/irb/indexes.md @@ -165,9 +165,6 @@ for each entry that is pre-defined, the initial value is given: - `:RC`: Whether a {configuration file}[rdoc-ref:IRB@Configuration+File] was found and interpreted; initial value: `true` if a configuration file was found, `false` otherwise. -- `:RC_NAME_GENERATOR`: \Proc to generate paths of potential - {configuration files}[rdoc-ref:IRB@Configuration+File]; - initial value: `=> #`. - `:SAVE_HISTORY`: Number of commands to save in {input command history}[rdoc-ref:IRB@Input+Command+History]; initial value: `1000`. diff --git a/doc/packed_data.rdoc b/doc/packed_data.rdoc index ec0e2c07f01b6b..17bbf92023d7a5 100644 --- a/doc/packed_data.rdoc +++ b/doc/packed_data.rdoc @@ -554,10 +554,12 @@ for one byte in the input or output string. - 'u' - UU-encoded string: - [0].pack("U") # => "\u0000" - [0x3fffffff].pack("U") # => "\xFC\xBF\xBF\xBF\xBF\xBF" - [0x40000000].pack("U") # => "\xFD\x80\x80\x80\x80\x80" - [0x7fffffff].pack("U") # => "\xFD\xBF\xBF\xBF\xBF\xBF" + [""].pack("u") # => "" + ["a"].pack("u") # => "!80``\n" + ["aaa"].pack("u") # => "#86%A\n" + + "".unpack("u") # => [""] + "#86)C\n".unpack("u") # => ["abc"] == Offset Directives diff --git a/doc/pty/README.expect.ja b/doc/pty/README.expect.ja index 7c0456f24fe504..a4eb6b01df24a0 100644 --- a/doc/pty/README.expect.ja +++ b/doc/pty/README.expect.ja @@ -1,21 +1,23 @@ - README for expect += README for expect by A. Ito, 28 October, 1998 - Expectライブラリは,tcl の expect パッケージと似たような機能を +Expectライブラリは,tcl の expect パッケージと似たような機能を IOクラスに追加します. - 追加されるメソッドの使い方は次の通りです. +追加されるメソッドの使い方は次の通りです. - IO#expect(pattern,timeout=9999999) +[IO#expect(pattern,timeout=9999999)] -pattern は String か Regexp のインスタンス,timeout は Fixnum -のインスタンスです.timeout は省略できます. - このメソッドがブロックなしで呼ばれた場合,まずレシーバである -IOオブジェクトから pattern にマッチするパターンが読みこまれる -まで待ちます.パターンが得られたら,そのパターンに関する配列を -返します.配列の最初の要素は,pattern にマッチするまでに読みこ -まれた内容の文字列です.2番目以降の要素は,pattern の正規表現 -の中にアンカーがあった場合に,そのアンカーにマッチする部分です. -もしタイムアウトが起きた場合は,このメソッドはnilを返します. - このメソッドがブロック付きで呼ばれた場合には,マッチした要素の -配列がブロック引数として渡され,ブロックが評価されます. + _pattern_ は String か Regexp のインスタンス,_timeout_ は Fixnum + のインスタンスです._timeout_ は省略できます. + + このメソッドがブロックなしで呼ばれた場合,まずレシーバである + IOオブジェクトから _pattern_ にマッチするパターンが読みこまれる + まで待ちます.パターンが得られたら,そのパターンに関する配列を + 返します.配列の最初の要素は,_pattern_ にマッチするまでに読みこ + まれた内容の文字列です.2番目以降の要素は,_pattern_ の正規表現 + の中にアンカーがあった場合に,そのアンカーにマッチする部分です. + もしタイムアウトが起きた場合は,このメソッドは +nil+ を返します. + + このメソッドがブロック付きで呼ばれた場合には,マッチした要素の + 配列がブロック引数として渡され,ブロックが評価されます. diff --git a/doc/pty/README.ja b/doc/pty/README.ja index 2d83ffa033e067..a26b4932ff904e 100644 --- a/doc/pty/README.ja +++ b/doc/pty/README.ja @@ -1,27 +1,26 @@ -pty 拡張モジュール version 0.3 by A.ito += pty 拡張モジュール version 0.3 by A.ito 1. はじめに -この拡張モジュールは,仮想tty (pty) を通して適当なコマンドを -実行する機能を ruby に提供します. + この拡張モジュールは,仮想tty (pty) を通して適当なコマンドを + 実行する機能を ruby に提供します. 2. インストール -次のようにしてインストールしてください. + 次のようにしてインストールしてください. -(1) ruby extconf.rb + 1. ruby extconf.rb + を実行すると Makefile が生成されます. - を実行すると Makefile が生成されます. - -(2) make; make install を実行してください. + 2. make; make install を実行してください. 3. 何ができるか -この拡張モジュールは,PTY というモジュールを定義します.その中 -には,次のようなモジュール関数が含まれています. + この拡張モジュールは,PTY というモジュールを定義します.その中 + には,次のようなモジュール関数が含まれています. - getpty(command) - spawn(command) + [PTY.getpty(command)] + [PTY.spawn(command)] この関数は,仮想ttyを確保し,指定されたコマンドをその仮想tty の向こうで実行し,配列を返します.戻り値は3つの要素からなる @@ -35,12 +34,7 @@ pty 拡張モジュール version 0.3 by A.ito のみ例外が発生します.子プロセスをモニターしているスレッドはブロッ クを抜けるときに終了します. - protect_signal - reset_signal - - 廃止予定です. - - PTY.open + [PTY.open] 仮想ttyを確保し,マスター側に対応するIOオブジェクトとスレーブ側に 対応するFileオブジェクトの配列を返します.ブロック付きで呼び出さ @@ -48,7 +42,7 @@ pty 拡張モジュール version 0.3 by A.ito クから返された結果を返します.また、このマスターIOとスレーブFile は、ブロックを抜けるときにクローズ済みでなければクローズされます. - PTY.check(pid[, raise=false]) + [PTY.check(pid[, raise=false])] pidで指定された子プロセスの状態をチェックし,実行中であればnilを 返します.終了しているか停止している場合、第二引数が偽であれば、 @@ -57,20 +51,20 @@ pty 拡張モジュール version 0.3 by A.ito 4. 利用について -伊藤彰則が著作権を保有します. + 伊藤彰則が著作権を保有します. -ソースプログラムまたはドキュメントに元の著作権表示が改変されずに -表示されている場合に限り,誰でも,このソフトウェアを無償かつ著作 -権者に無断で利用・配布・改変できます.利用目的は限定されていませ -ん. + ソースプログラムまたはドキュメントに元の著作権表示が改変されずに + 表示されている場合に限り,誰でも,このソフトウェアを無償かつ著作 + 権者に無断で利用・配布・改変できます.利用目的は限定されていませ + ん. -このプログラムの利用・配布その他このプログラムに関係する行為によ -って生じたいかなる損害に対しても,作者は一切責任を負いません. + このプログラムの利用・配布その他このプログラムに関係する行為によ + って生じたいかなる損害に対しても,作者は一切責任を負いません. 5. バグ報告等 -バグレポートは歓迎します. + バグレポートは歓迎します. aito@ei5sun.yz.yamagata-u.ac.jp -まで電子メールでバグレポートをお送りください. + まで電子メールでバグレポートをお送りください. diff --git a/doc/security.rdoc b/doc/security.rdoc index ae20ed30fa2349..e428036cf571de 100644 --- a/doc/security.rdoc +++ b/doc/security.rdoc @@ -37,7 +37,7 @@ programs for configuration and database persistence of Ruby object trees. Similar to +Marshal+, it is able to deserialize into arbitrary Ruby classes. For example, the following YAML data will create an +ERB+ object when -deserialized: +deserialized, using the `unsafe_load` method: !ruby/object:ERB src: puts `uname` diff --git a/doc/strftime_formatting.rdoc b/doc/strftime_formatting.rdoc index 7694752a21c1ec..5c7b33155df9ec 100644 --- a/doc/strftime_formatting.rdoc +++ b/doc/strftime_formatting.rdoc @@ -356,7 +356,7 @@ each based on an external standard. == HTTP Format The HTTP date format is based on -{RFC 2616}[https://datatracker.ietf.org/doc/html/rfc2616], +{RFC 2616}[https://www.rfc-editor.org/rfc/rfc2616], and treats dates in the format '%a, %d %b %Y %T GMT': d = Date.new(2001, 2, 3) # => # @@ -371,7 +371,7 @@ and treats dates in the format '%a, %d %b %Y %T GMT': == RFC 3339 Format The RFC 3339 date format is based on -{RFC 3339}[https://datatracker.ietf.org/doc/html/rfc3339]: +{RFC 3339}[https://www.rfc-editor.org/rfc/rfc3339]: d = Date.new(2001, 2, 3) # => # # Return 3339-formatted string. @@ -385,7 +385,7 @@ The RFC 3339 date format is based on == RFC 2822 Format The RFC 2822 date format is based on -{RFC 2822}[https://datatracker.ietf.org/doc/html/rfc2822], +{RFC 2822}[https://www.rfc-editor.org/rfc/rfc2822], and treats dates in the format '%a, %-d %b %Y %T %z']: d = Date.new(2001, 2, 3) # => # diff --git a/doc/syntax/calling_methods.rdoc b/doc/syntax/calling_methods.rdoc index 6cc8678450582f..c2c6c61a1039f4 100644 --- a/doc/syntax/calling_methods.rdoc +++ b/doc/syntax/calling_methods.rdoc @@ -210,7 +210,7 @@ definition. If a keyword argument is given that the method did not list, and the method definition does not accept arbitrary keyword arguments, an ArgumentError will be raised. -Keyword argument value can be omitted, meaning the value will be be fetched +Keyword argument value can be omitted, meaning the value will be fetched from the context by the name of the key keyword1 = 'some value' diff --git a/doc/syntax/exceptions.rdoc b/doc/syntax/exceptions.rdoc index 31e2f0175c5f7e..ac5ff78a952fa8 100644 --- a/doc/syntax/exceptions.rdoc +++ b/doc/syntax/exceptions.rdoc @@ -86,7 +86,7 @@ To always run some code whether an exception was raised or not, use +ensure+: rescue # ... ensure - # this always runs + # this always runs BUT does not implicitly return the last evaluated statement. end You may also run some code when an exception is not raised: @@ -96,7 +96,11 @@ You may also run some code when an exception is not raised: rescue # ... else - # this runs only when no exception was raised + # this runs only when no exception was raised AND return the last evaluated statement ensure - # ... + # this always runs. + # It is evaluated after the evaluation of either the `rescue` or the `else` block. + # It will not return implicitly. end + +NB : Without explicit +return+ in the +ensure+ block, +begin+/+end+ block will return the last evaluated statement before entering in the `ensure` block. diff --git a/doc/yjit/yjit.md b/doc/yjit/yjit.md index 8aab1aed22f79c..8ea3409e485e4d 100644 --- a/doc/yjit/yjit.md +++ b/doc/yjit/yjit.md @@ -265,10 +265,12 @@ This section contains tips on writing Ruby code that will run as fast as possibl - Avoid redefining the meaning of `nil`, equality, etc. - Avoid allocating objects in the hot parts of your code - Minimize layers of indirection - - Avoid classes that wrap objects if you can - - Avoid methods that just call another method, trivial one-liner methods -- Try to write code so that the same variables always have the same type -- CRuby method calls are costly. Avoid things such as methods that only return a value from a hash or return a constant. + - Avoid writing wrapper classes if you can (e.g. a class that only wraps a Ruby hash) + - Avoid methods that just call another method +- Ruby method calls are costly. Avoid things such as methods that only return a value from a hash +- Try to write code so that the same variables and method arguments always have the same type +- Avoid using `TracePoint` as it can cause YJIT to deoptimize code +- Avoid using `Binding` as it can cause YJIT to deoptimize code You can also use the `--yjit-stats` command-line option to see which bytecodes cause YJIT to exit, and refactor your code to avoid using these instructions in the hottest methods of your code. @@ -480,13 +482,8 @@ perf script --fields +pid > /tmp/test.perf You can also profile the number of cycles consumed by code generated by each YJIT function. ```bash -# Build perf from source for Python support -# [Optional] libelf-dev libunwind-dev libaudit-dev libslang2-dev libdw-dev -sudo apt-get install libpython3-dev python3-pip flex libtraceevent-dev -git clone https://github.com/torvalds/linux -cd linux/tools/perf -make -make install +# Install perf +apt-get install linux-tools-common linux-tools-generic linux-tools-`uname -r` # [Optional] Allow running perf without sudo echo 0 | sudo tee /proc/sys/kernel/kptr_restrict @@ -496,6 +493,25 @@ echo -1 | sudo tee /proc/sys/kernel/perf_event_paranoid cd ../yjit-bench PERF=record ruby --yjit-perf=codegen -Iharness-perf benchmarks/lobsters/benchmark.rb +# Aggregate results +perf script > /tmp/perf.txt +../ruby/misc/yjit_perf.py /tmp/perf.txt +``` + +#### Building perf with Python support + +The above instructions work fine for most people, but you could also use +a handy `perf script -s` interface if you build perf from source. + +```bash +# Build perf from source for Python support +sudo apt-get install libpython3-dev python3-pip flex libtraceevent-dev \ + libelf-dev libunwind-dev libaudit-dev libslang2-dev libdw-dev +git clone --depth=1 https://github.com/torvalds/linux +cd linux/tools/perf +make +make install + # Aggregate results perf script -s ../ruby/misc/yjit_perf.py ``` diff --git a/enc/ebcdic.h b/enc/ebcdic.h index a3b380a32760ad..5109bf7065abb1 100644 --- a/enc/ebcdic.h +++ b/enc/ebcdic.h @@ -7,5 +7,5 @@ ENC_ALIAS("ebcdic-cp-us", "IBM037"); * hopefully the most widely used one. * * See http://www.iana.org/assignments/character-sets/character-sets.xhtml - * http://tools.ietf.org/html/rfc1345 + * https://www.rfc-editor.org/rfc/rfc1345 */ diff --git a/enc/make_encmake.rb b/enc/make_encmake.rb index 09e19f65514499..9761edd6d9b70d 100755 --- a/enc/make_encmake.rb +++ b/enc/make_encmake.rb @@ -124,7 +124,7 @@ def target_transcoders erb = ERB.new(File.read(depend), trim_mode: '%') erb.filename = depend tmp = erb.result(binding) - dep = "\n#### depend ####\n\n" << depend_rules(tmp).join + dep = "\n#### depend ####\n\n" + depend_rules(tmp).join else dep = "" end diff --git a/error.c b/error.c index ca26186716a940..b2d076a2dfffa7 100644 --- a/error.c +++ b/error.c @@ -257,6 +257,26 @@ rb_warning_s_aset(VALUE mod, VALUE category, VALUE flag) return flag; } +/* + * call-seq: + * categories -> array + * + * Returns a list of the supported category symbols. + */ + +static VALUE +rb_warning_s_categories(VALUE mod) +{ + st_index_t num = warning_categories.id2enum->num_entries; + ID *ids = ALLOCA_N(ID, num); + num = st_keys(warning_categories.id2enum, ids, num); + VALUE ary = rb_ary_new_capa(num); + for (st_index_t i = 0; i < num; ++i) { + rb_ary_push(ary, ID2SYM(ids[i])); + } + return rb_ary_freeze(ary); +} + /* * call-seq: * warn(msg, category: nil) -> nil @@ -1835,7 +1855,7 @@ static VALUE rb_check_backtrace(VALUE bt) { long i; - static const char err[] = "backtrace must be Array of String"; + static const char err[] = "backtrace must be an Array of String or an Array of Thread::Backtrace::Location"; if (!NIL_P(bt)) { if (RB_TYPE_P(bt, T_STRING)) return rb_ary_new3(1, bt); @@ -1858,15 +1878,23 @@ rb_check_backtrace(VALUE bt) * exc.set_backtrace(backtrace) -> array * * Sets the backtrace information associated with +exc+. The +backtrace+ must - * be an array of String objects or a single String in the format described - * in Exception#backtrace. + * be an array of Thread::Backtrace::Location objects or an array of String objects + * or a single String in the format described in Exception#backtrace. * */ static VALUE exc_set_backtrace(VALUE exc, VALUE bt) { - return rb_ivar_set(exc, id_bt, rb_check_backtrace(bt)); + VALUE btobj = rb_location_ary_to_backtrace(bt); + if (RTEST(btobj)) { + rb_ivar_set(exc, id_bt, btobj); + rb_ivar_set(exc, id_bt_locations, btobj); + return bt; + } + else { + return rb_ivar_set(exc, id_bt, rb_check_backtrace(bt)); + } } VALUE @@ -3429,6 +3457,7 @@ Init_Exception(void) rb_mWarning = rb_define_module("Warning"); rb_define_singleton_method(rb_mWarning, "[]", rb_warning_s_aref, 1); rb_define_singleton_method(rb_mWarning, "[]=", rb_warning_s_aset, 2); + rb_define_singleton_method(rb_mWarning, "categories", rb_warning_s_categories, 0); rb_define_method(rb_mWarning, "warn", rb_warning_s_warn, -1); rb_extend_object(rb_mWarning, rb_mWarning); @@ -3847,6 +3876,12 @@ void rb_error_frozen_object(VALUE frozen_obj) { rb_yjit_lazy_push_frame(GET_EC()->cfp->pc); + + if (CHILLED_STRING_P(frozen_obj)) { + CHILLED_STRING_MUTATED(frozen_obj); + return; + } + VALUE debug_info; const ID created_info = id_debug_created_info; VALUE mesg = rb_sprintf("can't modify frozen %"PRIsVALUE": ", diff --git a/eval.c b/eval.c index 38f38de61dd954..e8da342ac66d75 100644 --- a/eval.c +++ b/eval.c @@ -78,7 +78,6 @@ ruby_setup(void) prctl(PR_SET_THP_DISABLE, 1, 0, 0, 0); #endif Init_BareVM(); - Init_heap(); rb_vm_encoded_insn_data_table_init(); Init_vm_objects(); @@ -410,11 +409,11 @@ rb_mod_s_constants(int argc, VALUE *argv, VALUE mod) return rb_const_list(data); } -/*! - * Asserts that \a klass is not a frozen class. - * \param[in] klass a \c Module object - * \exception RuntimeError if \a klass is not a class or frozen. - * \ingroup class +/** + * Asserts that `klass` is not a frozen class. + * @param[in] klass a `Module` object + * @exception RuntimeError if `klass` is not a class or frozen. + * @ingroup class */ void rb_class_modify_check(VALUE klass) @@ -463,7 +462,7 @@ rb_class_modify_check(VALUE klass) } } -NORETURN(static void rb_longjmp(rb_execution_context_t *, int, volatile VALUE, VALUE)); +NORETURN(static void rb_longjmp(rb_execution_context_t *, enum ruby_tag_type, volatile VALUE, VALUE)); static VALUE get_errinfo(void); #define get_ec_errinfo(ec) rb_ec_get_errinfo(ec) @@ -541,7 +540,7 @@ exc_setup_message(const rb_execution_context_t *ec, VALUE mesg, VALUE *cause) } static void -setup_exception(rb_execution_context_t *ec, int tag, volatile VALUE mesg, VALUE cause) +setup_exception(rb_execution_context_t *ec, enum ruby_tag_type tag, volatile VALUE mesg, VALUE cause) { VALUE e; int line; @@ -645,7 +644,7 @@ rb_ec_setup_exception(const rb_execution_context_t *ec, VALUE mesg, VALUE cause) } static void -rb_longjmp(rb_execution_context_t *ec, int tag, volatile VALUE mesg, VALUE cause) +rb_longjmp(rb_execution_context_t *ec, enum ruby_tag_type tag, volatile VALUE mesg, VALUE cause) { mesg = exc_setup_message(ec, mesg, &cause); setup_exception(ec, tag, mesg, cause); @@ -655,10 +654,10 @@ rb_longjmp(rb_execution_context_t *ec, int tag, volatile VALUE mesg, VALUE cause static VALUE make_exception(int argc, const VALUE *argv, int isstr); -NORETURN(static void rb_exc_exception(VALUE mesg, int tag, VALUE cause)); +NORETURN(static void rb_exc_exception(VALUE mesg, enum ruby_tag_type tag, VALUE cause)); static void -rb_exc_exception(VALUE mesg, int tag, VALUE cause) +rb_exc_exception(VALUE mesg, enum ruby_tag_type tag, VALUE cause) { if (!NIL_P(mesg)) { mesg = make_exception(1, &mesg, FALSE); @@ -666,12 +665,12 @@ rb_exc_exception(VALUE mesg, int tag, VALUE cause) rb_longjmp(GET_EC(), tag, mesg, cause); } -/*! +/** * Raises an exception in the current thread. - * \param[in] mesg an Exception class or an \c Exception object. - * \exception always raises an instance of the given exception class or - * the given \c Exception object. - * \ingroup exception + * @param[in] mesg an Exception class or an `Exception` object. + * @exception always raises an instance of the given exception class or + * the given `Exception` object. + * @ingroup exception */ void rb_exc_raise(VALUE mesg) @@ -761,7 +760,8 @@ rb_f_raise(int argc, VALUE *argv) * object that returns an +Exception+ object when sent an +exception+ * message). The optional second parameter sets the message associated with * the exception (accessible via Exception#message), and the third parameter - * is an array of callback information (accessible via Exception#backtrace). + * is an array of callback information (accessible via + * Exception#backtrace_locations or Exception#backtrace). * The +cause+ of the generated exception (accessible via Exception#cause) * is automatically set to the "current" exception ($!), if any. * An alternative value, either an +Exception+ object or +nil+, can be @@ -771,7 +771,7 @@ rb_f_raise(int argc, VALUE *argv) * begin...end blocks. * * raise "Failed to create socket" - * raise ArgumentError, "No parameters", caller + * raise ArgumentError, "No parameters", caller_locations */ static VALUE @@ -985,7 +985,7 @@ rb_protect(VALUE (* proc) (VALUE), VALUE data, int *pstate) VALUE rb_ensure(VALUE (*b_proc)(VALUE), VALUE data1, VALUE (*e_proc)(VALUE), VALUE data2) { - int state; + enum ruby_tag_type state; volatile VALUE result = Qnil; VALUE errinfo; rb_execution_context_t * volatile ec = GET_EC(); @@ -1782,6 +1782,16 @@ rb_obj_extend(int argc, VALUE *argv, VALUE obj) return obj; } +VALUE +rb_top_main_class(const char *method) +{ + VALUE klass = GET_THREAD()->top_wrapper; + + if (!klass) return rb_cObject; + rb_warning("main.%s in the wrapped load is effective only in wrapper module", method); + return klass; +} + /* * call-seq: * include(module, ...) -> self @@ -1794,13 +1804,7 @@ rb_obj_extend(int argc, VALUE *argv, VALUE obj) static VALUE top_include(int argc, VALUE *argv, VALUE self) { - rb_thread_t *th = GET_THREAD(); - - if (th->top_wrapper) { - rb_warning("main.include in the wrapped load is effective only in wrapper module"); - return rb_mod_include(argc, argv, th->top_wrapper); - } - return rb_mod_include(argc, argv, rb_cObject); + return rb_mod_include(argc, argv, rb_top_main_class("include")); } /* diff --git a/eval_intern.h b/eval_intern.h index 4ea23068bb65b4..9a551120ff3ec9 100644 --- a/eval_intern.h +++ b/eval_intern.h @@ -145,7 +145,8 @@ rb_ec_tag_state(const rb_execution_context_t *ec) enum ruby_tag_type state = tag->state; tag->state = TAG_NONE; rb_ec_vm_lock_rec_check(ec, tag->lock_rec); - RBIMPL_ASSUME(state != TAG_NONE); + RBIMPL_ASSUME(state > TAG_NONE); + RBIMPL_ASSUME(state <= TAG_FATAL); return state; } @@ -153,7 +154,7 @@ NORETURN(static inline void rb_ec_tag_jump(const rb_execution_context_t *ec, enu static inline void rb_ec_tag_jump(const rb_execution_context_t *ec, enum ruby_tag_type st) { - RUBY_ASSERT(st != TAG_NONE); + RUBY_ASSERT(st > TAG_NONE && st <= TAG_FATAL, ": Invalid tag jump: %d", (int)st); ec->tag->state = st; ruby_longjmp(RB_VM_TAG_JMPBUF_GET(ec->tag->buf), 1); } @@ -289,9 +290,9 @@ NORETURN(void rb_print_undef(VALUE, ID, rb_method_visibility_t)); NORETURN(void rb_print_undef_str(VALUE, VALUE)); NORETURN(void rb_print_inaccessible(VALUE, ID, rb_method_visibility_t)); NORETURN(void rb_vm_localjump_error(const char *,VALUE, int)); -NORETURN(void rb_vm_jump_tag_but_local_jump(int)); +NORETURN(void rb_vm_jump_tag_but_local_jump(enum ruby_tag_type)); -VALUE rb_vm_make_jump_tag_but_local_jump(int state, VALUE val); +VALUE rb_vm_make_jump_tag_but_local_jump(enum ruby_tag_type state, VALUE val); rb_cref_t *rb_vm_cref(void); rb_cref_t *rb_vm_cref_replace_with_duplicated_cref(void); VALUE rb_vm_call_cfunc(VALUE recv, VALUE (*func)(VALUE), VALUE arg, VALUE block_handler, VALUE filename); @@ -320,16 +321,4 @@ rb_char_next(const char *p) # endif #endif -#if defined DOSISH || defined __CYGWIN__ -static inline void -translit_char(char *p, int from, int to) -{ - while (*p) { - if ((unsigned char)*p == from) - *p = to; - p = CharNext(p); - } -} -#endif - #endif /* RUBY_EVAL_INTERN_H */ diff --git a/ext/-test-/fatal/depend b/ext/-test-/fatal/depend index 730a72e52a4180..45a8c659c4780d 100644 --- a/ext/-test-/fatal/depend +++ b/ext/-test-/fatal/depend @@ -1,4 +1,322 @@ # AUTOGENERATED DEPENDENCIES START +init.o: $(RUBY_EXTCONF_H) +init.o: $(arch_hdrdir)/ruby/config.h +init.o: $(hdrdir)/ruby.h +init.o: $(hdrdir)/ruby/assert.h +init.o: $(hdrdir)/ruby/backward.h +init.o: $(hdrdir)/ruby/backward/2/assume.h +init.o: $(hdrdir)/ruby/backward/2/attributes.h +init.o: $(hdrdir)/ruby/backward/2/bool.h +init.o: $(hdrdir)/ruby/backward/2/inttypes.h +init.o: $(hdrdir)/ruby/backward/2/limits.h +init.o: $(hdrdir)/ruby/backward/2/long_long.h +init.o: $(hdrdir)/ruby/backward/2/stdalign.h +init.o: $(hdrdir)/ruby/backward/2/stdarg.h +init.o: $(hdrdir)/ruby/defines.h +init.o: $(hdrdir)/ruby/intern.h +init.o: $(hdrdir)/ruby/internal/abi.h +init.o: $(hdrdir)/ruby/internal/anyargs.h +init.o: $(hdrdir)/ruby/internal/arithmetic.h +init.o: $(hdrdir)/ruby/internal/arithmetic/char.h +init.o: $(hdrdir)/ruby/internal/arithmetic/double.h +init.o: $(hdrdir)/ruby/internal/arithmetic/fixnum.h +init.o: $(hdrdir)/ruby/internal/arithmetic/gid_t.h +init.o: $(hdrdir)/ruby/internal/arithmetic/int.h +init.o: $(hdrdir)/ruby/internal/arithmetic/intptr_t.h +init.o: $(hdrdir)/ruby/internal/arithmetic/long.h +init.o: $(hdrdir)/ruby/internal/arithmetic/long_long.h +init.o: $(hdrdir)/ruby/internal/arithmetic/mode_t.h +init.o: $(hdrdir)/ruby/internal/arithmetic/off_t.h +init.o: $(hdrdir)/ruby/internal/arithmetic/pid_t.h +init.o: $(hdrdir)/ruby/internal/arithmetic/short.h +init.o: $(hdrdir)/ruby/internal/arithmetic/size_t.h +init.o: $(hdrdir)/ruby/internal/arithmetic/st_data_t.h +init.o: $(hdrdir)/ruby/internal/arithmetic/uid_t.h +init.o: $(hdrdir)/ruby/internal/assume.h +init.o: $(hdrdir)/ruby/internal/attr/alloc_size.h +init.o: $(hdrdir)/ruby/internal/attr/artificial.h +init.o: $(hdrdir)/ruby/internal/attr/cold.h +init.o: $(hdrdir)/ruby/internal/attr/const.h +init.o: $(hdrdir)/ruby/internal/attr/constexpr.h +init.o: $(hdrdir)/ruby/internal/attr/deprecated.h +init.o: $(hdrdir)/ruby/internal/attr/diagnose_if.h +init.o: $(hdrdir)/ruby/internal/attr/enum_extensibility.h +init.o: $(hdrdir)/ruby/internal/attr/error.h +init.o: $(hdrdir)/ruby/internal/attr/flag_enum.h +init.o: $(hdrdir)/ruby/internal/attr/forceinline.h +init.o: $(hdrdir)/ruby/internal/attr/format.h +init.o: $(hdrdir)/ruby/internal/attr/maybe_unused.h +init.o: $(hdrdir)/ruby/internal/attr/noalias.h +init.o: $(hdrdir)/ruby/internal/attr/nodiscard.h +init.o: $(hdrdir)/ruby/internal/attr/noexcept.h +init.o: $(hdrdir)/ruby/internal/attr/noinline.h +init.o: $(hdrdir)/ruby/internal/attr/nonnull.h +init.o: $(hdrdir)/ruby/internal/attr/noreturn.h +init.o: $(hdrdir)/ruby/internal/attr/packed_struct.h +init.o: $(hdrdir)/ruby/internal/attr/pure.h +init.o: $(hdrdir)/ruby/internal/attr/restrict.h +init.o: $(hdrdir)/ruby/internal/attr/returns_nonnull.h +init.o: $(hdrdir)/ruby/internal/attr/warning.h +init.o: $(hdrdir)/ruby/internal/attr/weakref.h +init.o: $(hdrdir)/ruby/internal/cast.h +init.o: $(hdrdir)/ruby/internal/compiler_is.h +init.o: $(hdrdir)/ruby/internal/compiler_is/apple.h +init.o: $(hdrdir)/ruby/internal/compiler_is/clang.h +init.o: $(hdrdir)/ruby/internal/compiler_is/gcc.h +init.o: $(hdrdir)/ruby/internal/compiler_is/intel.h +init.o: $(hdrdir)/ruby/internal/compiler_is/msvc.h +init.o: $(hdrdir)/ruby/internal/compiler_is/sunpro.h +init.o: $(hdrdir)/ruby/internal/compiler_since.h +init.o: $(hdrdir)/ruby/internal/config.h +init.o: $(hdrdir)/ruby/internal/constant_p.h +init.o: $(hdrdir)/ruby/internal/core.h +init.o: $(hdrdir)/ruby/internal/core/rarray.h +init.o: $(hdrdir)/ruby/internal/core/rbasic.h +init.o: $(hdrdir)/ruby/internal/core/rbignum.h +init.o: $(hdrdir)/ruby/internal/core/rclass.h +init.o: $(hdrdir)/ruby/internal/core/rdata.h +init.o: $(hdrdir)/ruby/internal/core/rfile.h +init.o: $(hdrdir)/ruby/internal/core/rhash.h +init.o: $(hdrdir)/ruby/internal/core/robject.h +init.o: $(hdrdir)/ruby/internal/core/rregexp.h +init.o: $(hdrdir)/ruby/internal/core/rstring.h +init.o: $(hdrdir)/ruby/internal/core/rstruct.h +init.o: $(hdrdir)/ruby/internal/core/rtypeddata.h +init.o: $(hdrdir)/ruby/internal/ctype.h +init.o: $(hdrdir)/ruby/internal/dllexport.h +init.o: $(hdrdir)/ruby/internal/dosish.h +init.o: $(hdrdir)/ruby/internal/error.h +init.o: $(hdrdir)/ruby/internal/eval.h +init.o: $(hdrdir)/ruby/internal/event.h +init.o: $(hdrdir)/ruby/internal/fl_type.h +init.o: $(hdrdir)/ruby/internal/gc.h +init.o: $(hdrdir)/ruby/internal/glob.h +init.o: $(hdrdir)/ruby/internal/globals.h +init.o: $(hdrdir)/ruby/internal/has/attribute.h +init.o: $(hdrdir)/ruby/internal/has/builtin.h +init.o: $(hdrdir)/ruby/internal/has/c_attribute.h +init.o: $(hdrdir)/ruby/internal/has/cpp_attribute.h +init.o: $(hdrdir)/ruby/internal/has/declspec_attribute.h +init.o: $(hdrdir)/ruby/internal/has/extension.h +init.o: $(hdrdir)/ruby/internal/has/feature.h +init.o: $(hdrdir)/ruby/internal/has/warning.h +init.o: $(hdrdir)/ruby/internal/intern/array.h +init.o: $(hdrdir)/ruby/internal/intern/bignum.h +init.o: $(hdrdir)/ruby/internal/intern/class.h +init.o: $(hdrdir)/ruby/internal/intern/compar.h +init.o: $(hdrdir)/ruby/internal/intern/complex.h +init.o: $(hdrdir)/ruby/internal/intern/cont.h +init.o: $(hdrdir)/ruby/internal/intern/dir.h +init.o: $(hdrdir)/ruby/internal/intern/enum.h +init.o: $(hdrdir)/ruby/internal/intern/enumerator.h +init.o: $(hdrdir)/ruby/internal/intern/error.h +init.o: $(hdrdir)/ruby/internal/intern/eval.h +init.o: $(hdrdir)/ruby/internal/intern/file.h +init.o: $(hdrdir)/ruby/internal/intern/hash.h +init.o: $(hdrdir)/ruby/internal/intern/io.h +init.o: $(hdrdir)/ruby/internal/intern/load.h +init.o: $(hdrdir)/ruby/internal/intern/marshal.h +init.o: $(hdrdir)/ruby/internal/intern/numeric.h +init.o: $(hdrdir)/ruby/internal/intern/object.h +init.o: $(hdrdir)/ruby/internal/intern/parse.h +init.o: $(hdrdir)/ruby/internal/intern/proc.h +init.o: $(hdrdir)/ruby/internal/intern/process.h +init.o: $(hdrdir)/ruby/internal/intern/random.h +init.o: $(hdrdir)/ruby/internal/intern/range.h +init.o: $(hdrdir)/ruby/internal/intern/rational.h +init.o: $(hdrdir)/ruby/internal/intern/re.h +init.o: $(hdrdir)/ruby/internal/intern/ruby.h +init.o: $(hdrdir)/ruby/internal/intern/select.h +init.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +init.o: $(hdrdir)/ruby/internal/intern/signal.h +init.o: $(hdrdir)/ruby/internal/intern/sprintf.h +init.o: $(hdrdir)/ruby/internal/intern/string.h +init.o: $(hdrdir)/ruby/internal/intern/struct.h +init.o: $(hdrdir)/ruby/internal/intern/thread.h +init.o: $(hdrdir)/ruby/internal/intern/time.h +init.o: $(hdrdir)/ruby/internal/intern/variable.h +init.o: $(hdrdir)/ruby/internal/intern/vm.h +init.o: $(hdrdir)/ruby/internal/interpreter.h +init.o: $(hdrdir)/ruby/internal/iterator.h +init.o: $(hdrdir)/ruby/internal/memory.h +init.o: $(hdrdir)/ruby/internal/method.h +init.o: $(hdrdir)/ruby/internal/module.h +init.o: $(hdrdir)/ruby/internal/newobj.h +init.o: $(hdrdir)/ruby/internal/scan_args.h +init.o: $(hdrdir)/ruby/internal/special_consts.h +init.o: $(hdrdir)/ruby/internal/static_assert.h +init.o: $(hdrdir)/ruby/internal/stdalign.h +init.o: $(hdrdir)/ruby/internal/stdbool.h +init.o: $(hdrdir)/ruby/internal/symbol.h +init.o: $(hdrdir)/ruby/internal/value.h +init.o: $(hdrdir)/ruby/internal/value_type.h +init.o: $(hdrdir)/ruby/internal/variable.h +init.o: $(hdrdir)/ruby/internal/warning_push.h +init.o: $(hdrdir)/ruby/internal/xmalloc.h +init.o: $(hdrdir)/ruby/missing.h +init.o: $(hdrdir)/ruby/ruby.h +init.o: $(hdrdir)/ruby/st.h +init.o: $(hdrdir)/ruby/subst.h +init.o: init.c +invalid.o: $(RUBY_EXTCONF_H) +invalid.o: $(arch_hdrdir)/ruby/config.h +invalid.o: $(hdrdir)/ruby.h +invalid.o: $(hdrdir)/ruby/assert.h +invalid.o: $(hdrdir)/ruby/backward.h +invalid.o: $(hdrdir)/ruby/backward/2/assume.h +invalid.o: $(hdrdir)/ruby/backward/2/attributes.h +invalid.o: $(hdrdir)/ruby/backward/2/bool.h +invalid.o: $(hdrdir)/ruby/backward/2/inttypes.h +invalid.o: $(hdrdir)/ruby/backward/2/limits.h +invalid.o: $(hdrdir)/ruby/backward/2/long_long.h +invalid.o: $(hdrdir)/ruby/backward/2/stdalign.h +invalid.o: $(hdrdir)/ruby/backward/2/stdarg.h +invalid.o: $(hdrdir)/ruby/defines.h +invalid.o: $(hdrdir)/ruby/intern.h +invalid.o: $(hdrdir)/ruby/internal/abi.h +invalid.o: $(hdrdir)/ruby/internal/anyargs.h +invalid.o: $(hdrdir)/ruby/internal/arithmetic.h +invalid.o: $(hdrdir)/ruby/internal/arithmetic/char.h +invalid.o: $(hdrdir)/ruby/internal/arithmetic/double.h +invalid.o: $(hdrdir)/ruby/internal/arithmetic/fixnum.h +invalid.o: $(hdrdir)/ruby/internal/arithmetic/gid_t.h +invalid.o: $(hdrdir)/ruby/internal/arithmetic/int.h +invalid.o: $(hdrdir)/ruby/internal/arithmetic/intptr_t.h +invalid.o: $(hdrdir)/ruby/internal/arithmetic/long.h +invalid.o: $(hdrdir)/ruby/internal/arithmetic/long_long.h +invalid.o: $(hdrdir)/ruby/internal/arithmetic/mode_t.h +invalid.o: $(hdrdir)/ruby/internal/arithmetic/off_t.h +invalid.o: $(hdrdir)/ruby/internal/arithmetic/pid_t.h +invalid.o: $(hdrdir)/ruby/internal/arithmetic/short.h +invalid.o: $(hdrdir)/ruby/internal/arithmetic/size_t.h +invalid.o: $(hdrdir)/ruby/internal/arithmetic/st_data_t.h +invalid.o: $(hdrdir)/ruby/internal/arithmetic/uid_t.h +invalid.o: $(hdrdir)/ruby/internal/assume.h +invalid.o: $(hdrdir)/ruby/internal/attr/alloc_size.h +invalid.o: $(hdrdir)/ruby/internal/attr/artificial.h +invalid.o: $(hdrdir)/ruby/internal/attr/cold.h +invalid.o: $(hdrdir)/ruby/internal/attr/const.h +invalid.o: $(hdrdir)/ruby/internal/attr/constexpr.h +invalid.o: $(hdrdir)/ruby/internal/attr/deprecated.h +invalid.o: $(hdrdir)/ruby/internal/attr/diagnose_if.h +invalid.o: $(hdrdir)/ruby/internal/attr/enum_extensibility.h +invalid.o: $(hdrdir)/ruby/internal/attr/error.h +invalid.o: $(hdrdir)/ruby/internal/attr/flag_enum.h +invalid.o: $(hdrdir)/ruby/internal/attr/forceinline.h +invalid.o: $(hdrdir)/ruby/internal/attr/format.h +invalid.o: $(hdrdir)/ruby/internal/attr/maybe_unused.h +invalid.o: $(hdrdir)/ruby/internal/attr/noalias.h +invalid.o: $(hdrdir)/ruby/internal/attr/nodiscard.h +invalid.o: $(hdrdir)/ruby/internal/attr/noexcept.h +invalid.o: $(hdrdir)/ruby/internal/attr/noinline.h +invalid.o: $(hdrdir)/ruby/internal/attr/nonnull.h +invalid.o: $(hdrdir)/ruby/internal/attr/noreturn.h +invalid.o: $(hdrdir)/ruby/internal/attr/packed_struct.h +invalid.o: $(hdrdir)/ruby/internal/attr/pure.h +invalid.o: $(hdrdir)/ruby/internal/attr/restrict.h +invalid.o: $(hdrdir)/ruby/internal/attr/returns_nonnull.h +invalid.o: $(hdrdir)/ruby/internal/attr/warning.h +invalid.o: $(hdrdir)/ruby/internal/attr/weakref.h +invalid.o: $(hdrdir)/ruby/internal/cast.h +invalid.o: $(hdrdir)/ruby/internal/compiler_is.h +invalid.o: $(hdrdir)/ruby/internal/compiler_is/apple.h +invalid.o: $(hdrdir)/ruby/internal/compiler_is/clang.h +invalid.o: $(hdrdir)/ruby/internal/compiler_is/gcc.h +invalid.o: $(hdrdir)/ruby/internal/compiler_is/intel.h +invalid.o: $(hdrdir)/ruby/internal/compiler_is/msvc.h +invalid.o: $(hdrdir)/ruby/internal/compiler_is/sunpro.h +invalid.o: $(hdrdir)/ruby/internal/compiler_since.h +invalid.o: $(hdrdir)/ruby/internal/config.h +invalid.o: $(hdrdir)/ruby/internal/constant_p.h +invalid.o: $(hdrdir)/ruby/internal/core.h +invalid.o: $(hdrdir)/ruby/internal/core/rarray.h +invalid.o: $(hdrdir)/ruby/internal/core/rbasic.h +invalid.o: $(hdrdir)/ruby/internal/core/rbignum.h +invalid.o: $(hdrdir)/ruby/internal/core/rclass.h +invalid.o: $(hdrdir)/ruby/internal/core/rdata.h +invalid.o: $(hdrdir)/ruby/internal/core/rfile.h +invalid.o: $(hdrdir)/ruby/internal/core/rhash.h +invalid.o: $(hdrdir)/ruby/internal/core/robject.h +invalid.o: $(hdrdir)/ruby/internal/core/rregexp.h +invalid.o: $(hdrdir)/ruby/internal/core/rstring.h +invalid.o: $(hdrdir)/ruby/internal/core/rstruct.h +invalid.o: $(hdrdir)/ruby/internal/core/rtypeddata.h +invalid.o: $(hdrdir)/ruby/internal/ctype.h +invalid.o: $(hdrdir)/ruby/internal/dllexport.h +invalid.o: $(hdrdir)/ruby/internal/dosish.h +invalid.o: $(hdrdir)/ruby/internal/error.h +invalid.o: $(hdrdir)/ruby/internal/eval.h +invalid.o: $(hdrdir)/ruby/internal/event.h +invalid.o: $(hdrdir)/ruby/internal/fl_type.h +invalid.o: $(hdrdir)/ruby/internal/gc.h +invalid.o: $(hdrdir)/ruby/internal/glob.h +invalid.o: $(hdrdir)/ruby/internal/globals.h +invalid.o: $(hdrdir)/ruby/internal/has/attribute.h +invalid.o: $(hdrdir)/ruby/internal/has/builtin.h +invalid.o: $(hdrdir)/ruby/internal/has/c_attribute.h +invalid.o: $(hdrdir)/ruby/internal/has/cpp_attribute.h +invalid.o: $(hdrdir)/ruby/internal/has/declspec_attribute.h +invalid.o: $(hdrdir)/ruby/internal/has/extension.h +invalid.o: $(hdrdir)/ruby/internal/has/feature.h +invalid.o: $(hdrdir)/ruby/internal/has/warning.h +invalid.o: $(hdrdir)/ruby/internal/intern/array.h +invalid.o: $(hdrdir)/ruby/internal/intern/bignum.h +invalid.o: $(hdrdir)/ruby/internal/intern/class.h +invalid.o: $(hdrdir)/ruby/internal/intern/compar.h +invalid.o: $(hdrdir)/ruby/internal/intern/complex.h +invalid.o: $(hdrdir)/ruby/internal/intern/cont.h +invalid.o: $(hdrdir)/ruby/internal/intern/dir.h +invalid.o: $(hdrdir)/ruby/internal/intern/enum.h +invalid.o: $(hdrdir)/ruby/internal/intern/enumerator.h +invalid.o: $(hdrdir)/ruby/internal/intern/error.h +invalid.o: $(hdrdir)/ruby/internal/intern/eval.h +invalid.o: $(hdrdir)/ruby/internal/intern/file.h +invalid.o: $(hdrdir)/ruby/internal/intern/hash.h +invalid.o: $(hdrdir)/ruby/internal/intern/io.h +invalid.o: $(hdrdir)/ruby/internal/intern/load.h +invalid.o: $(hdrdir)/ruby/internal/intern/marshal.h +invalid.o: $(hdrdir)/ruby/internal/intern/numeric.h +invalid.o: $(hdrdir)/ruby/internal/intern/object.h +invalid.o: $(hdrdir)/ruby/internal/intern/parse.h +invalid.o: $(hdrdir)/ruby/internal/intern/proc.h +invalid.o: $(hdrdir)/ruby/internal/intern/process.h +invalid.o: $(hdrdir)/ruby/internal/intern/random.h +invalid.o: $(hdrdir)/ruby/internal/intern/range.h +invalid.o: $(hdrdir)/ruby/internal/intern/rational.h +invalid.o: $(hdrdir)/ruby/internal/intern/re.h +invalid.o: $(hdrdir)/ruby/internal/intern/ruby.h +invalid.o: $(hdrdir)/ruby/internal/intern/select.h +invalid.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +invalid.o: $(hdrdir)/ruby/internal/intern/signal.h +invalid.o: $(hdrdir)/ruby/internal/intern/sprintf.h +invalid.o: $(hdrdir)/ruby/internal/intern/string.h +invalid.o: $(hdrdir)/ruby/internal/intern/struct.h +invalid.o: $(hdrdir)/ruby/internal/intern/thread.h +invalid.o: $(hdrdir)/ruby/internal/intern/time.h +invalid.o: $(hdrdir)/ruby/internal/intern/variable.h +invalid.o: $(hdrdir)/ruby/internal/intern/vm.h +invalid.o: $(hdrdir)/ruby/internal/interpreter.h +invalid.o: $(hdrdir)/ruby/internal/iterator.h +invalid.o: $(hdrdir)/ruby/internal/memory.h +invalid.o: $(hdrdir)/ruby/internal/method.h +invalid.o: $(hdrdir)/ruby/internal/module.h +invalid.o: $(hdrdir)/ruby/internal/newobj.h +invalid.o: $(hdrdir)/ruby/internal/scan_args.h +invalid.o: $(hdrdir)/ruby/internal/special_consts.h +invalid.o: $(hdrdir)/ruby/internal/static_assert.h +invalid.o: $(hdrdir)/ruby/internal/stdalign.h +invalid.o: $(hdrdir)/ruby/internal/stdbool.h +invalid.o: $(hdrdir)/ruby/internal/symbol.h +invalid.o: $(hdrdir)/ruby/internal/value.h +invalid.o: $(hdrdir)/ruby/internal/value_type.h +invalid.o: $(hdrdir)/ruby/internal/variable.h +invalid.o: $(hdrdir)/ruby/internal/warning_push.h +invalid.o: $(hdrdir)/ruby/internal/xmalloc.h +invalid.o: $(hdrdir)/ruby/missing.h +invalid.o: $(hdrdir)/ruby/ruby.h +invalid.o: $(hdrdir)/ruby/st.h +invalid.o: $(hdrdir)/ruby/subst.h +invalid.o: invalid.c rb_fatal.o: $(RUBY_EXTCONF_H) rb_fatal.o: $(arch_hdrdir)/ruby/config.h rb_fatal.o: $(hdrdir)/ruby.h @@ -158,4 +476,5 @@ rb_fatal.o: $(hdrdir)/ruby/ruby.h rb_fatal.o: $(hdrdir)/ruby/st.h rb_fatal.o: $(hdrdir)/ruby/subst.h rb_fatal.o: rb_fatal.c + # AUTOGENERATED DEPENDENCIES END diff --git a/ext/-test-/fatal/extconf.rb b/ext/-test-/fatal/extconf.rb index d5849c073328b5..ca51178a18b200 100644 --- a/ext/-test-/fatal/extconf.rb +++ b/ext/-test-/fatal/extconf.rb @@ -1,2 +1,3 @@ # frozen_string_literal: false -create_makefile("-test-/fatal/rb_fatal") +require_relative "../auto_ext.rb" +auto_ext diff --git a/ext/-test-/fatal/init.c b/ext/-test-/fatal/init.c new file mode 100644 index 00000000000000..3b71708789502c --- /dev/null +++ b/ext/-test-/fatal/init.c @@ -0,0 +1,10 @@ +#include "ruby.h" + +#define init(n) {void Init_##n(VALUE klass); Init_##n(klass);} + +void +Init_fatal(void) +{ + VALUE klass = rb_define_module("Bug"); + TEST_INIT_FUNCS(init); +} diff --git a/ext/-test-/fatal/invalid.c b/ext/-test-/fatal/invalid.c new file mode 100644 index 00000000000000..f0726edb521152 --- /dev/null +++ b/ext/-test-/fatal/invalid.c @@ -0,0 +1,28 @@ +#include + +#if SIZEOF_LONG == SIZEOF_VOIDP +# define NUM2PTR(x) (void *)NUM2ULONG(x) +#elif SIZEOF_LONG_LONG == SIZEOF_VOIDP +# define NUM2PTR(x) (void *)NUM2ULL(x) +#endif + +static VALUE +invalid_call(VALUE obj, VALUE address) +{ + typedef VALUE (*func_type)(VALUE); + + return (*(func_type)NUM2PTR(address))(obj); +} + +static VALUE +invalid_access(VALUE obj, VALUE address) +{ + return *(VALUE *)NUM2PTR(address) == obj ? Qtrue : Qfalse; +} + +void +Init_invalid(VALUE mBug) +{ + rb_define_singleton_method(mBug, "invalid_call", invalid_call, 1); + rb_define_singleton_method(mBug, "invalid_access", invalid_access, 1); +} diff --git a/ext/-test-/fatal/rb_fatal.c b/ext/-test-/fatal/rb_fatal.c index eedbc51f8b83d6..6c7bb89628fdd4 100644 --- a/ext/-test-/fatal/rb_fatal.c +++ b/ext/-test-/fatal/rb_fatal.c @@ -13,8 +13,7 @@ ruby_fatal(VALUE obj, VALUE msg) } void -Init_rb_fatal(void) +Init_rb_fatal(VALUE mBug) { - VALUE mBug = rb_define_module("Bug"); rb_define_singleton_method(mBug, "rb_fatal", ruby_fatal, 1); } diff --git a/ext/-test-/string/chilled.c b/ext/-test-/string/chilled.c new file mode 100644 index 00000000000000..c98fc72c477256 --- /dev/null +++ b/ext/-test-/string/chilled.c @@ -0,0 +1,13 @@ +#include "ruby.h" + +static VALUE +bug_s_rb_str_chilled_p(VALUE self, VALUE str) +{ + return rb_str_chilled_p(str) ? Qtrue : Qfalse; +} + +void +Init_string_chilled(VALUE klass) +{ + rb_define_singleton_method(klass, "rb_str_chilled_p", bug_s_rb_str_chilled_p, 1); +} diff --git a/ext/-test-/string/depend b/ext/-test-/string/depend index eeb4914346bd91..f8f58e7d441ee2 100644 --- a/ext/-test-/string/depend +++ b/ext/-test-/string/depend @@ -173,6 +173,165 @@ capacity.o: $(hdrdir)/ruby/subst.h capacity.o: $(top_srcdir)/internal/compilers.h capacity.o: $(top_srcdir)/internal/string.h capacity.o: capacity.c +chilled.o: $(RUBY_EXTCONF_H) +chilled.o: $(arch_hdrdir)/ruby/config.h +chilled.o: $(hdrdir)/ruby.h +chilled.o: $(hdrdir)/ruby/assert.h +chilled.o: $(hdrdir)/ruby/backward.h +chilled.o: $(hdrdir)/ruby/backward/2/assume.h +chilled.o: $(hdrdir)/ruby/backward/2/attributes.h +chilled.o: $(hdrdir)/ruby/backward/2/bool.h +chilled.o: $(hdrdir)/ruby/backward/2/inttypes.h +chilled.o: $(hdrdir)/ruby/backward/2/limits.h +chilled.o: $(hdrdir)/ruby/backward/2/long_long.h +chilled.o: $(hdrdir)/ruby/backward/2/stdalign.h +chilled.o: $(hdrdir)/ruby/backward/2/stdarg.h +chilled.o: $(hdrdir)/ruby/defines.h +chilled.o: $(hdrdir)/ruby/intern.h +chilled.o: $(hdrdir)/ruby/internal/abi.h +chilled.o: $(hdrdir)/ruby/internal/anyargs.h +chilled.o: $(hdrdir)/ruby/internal/arithmetic.h +chilled.o: $(hdrdir)/ruby/internal/arithmetic/char.h +chilled.o: $(hdrdir)/ruby/internal/arithmetic/double.h +chilled.o: $(hdrdir)/ruby/internal/arithmetic/fixnum.h +chilled.o: $(hdrdir)/ruby/internal/arithmetic/gid_t.h +chilled.o: $(hdrdir)/ruby/internal/arithmetic/int.h +chilled.o: $(hdrdir)/ruby/internal/arithmetic/intptr_t.h +chilled.o: $(hdrdir)/ruby/internal/arithmetic/long.h +chilled.o: $(hdrdir)/ruby/internal/arithmetic/long_long.h +chilled.o: $(hdrdir)/ruby/internal/arithmetic/mode_t.h +chilled.o: $(hdrdir)/ruby/internal/arithmetic/off_t.h +chilled.o: $(hdrdir)/ruby/internal/arithmetic/pid_t.h +chilled.o: $(hdrdir)/ruby/internal/arithmetic/short.h +chilled.o: $(hdrdir)/ruby/internal/arithmetic/size_t.h +chilled.o: $(hdrdir)/ruby/internal/arithmetic/st_data_t.h +chilled.o: $(hdrdir)/ruby/internal/arithmetic/uid_t.h +chilled.o: $(hdrdir)/ruby/internal/assume.h +chilled.o: $(hdrdir)/ruby/internal/attr/alloc_size.h +chilled.o: $(hdrdir)/ruby/internal/attr/artificial.h +chilled.o: $(hdrdir)/ruby/internal/attr/cold.h +chilled.o: $(hdrdir)/ruby/internal/attr/const.h +chilled.o: $(hdrdir)/ruby/internal/attr/constexpr.h +chilled.o: $(hdrdir)/ruby/internal/attr/deprecated.h +chilled.o: $(hdrdir)/ruby/internal/attr/diagnose_if.h +chilled.o: $(hdrdir)/ruby/internal/attr/enum_extensibility.h +chilled.o: $(hdrdir)/ruby/internal/attr/error.h +chilled.o: $(hdrdir)/ruby/internal/attr/flag_enum.h +chilled.o: $(hdrdir)/ruby/internal/attr/forceinline.h +chilled.o: $(hdrdir)/ruby/internal/attr/format.h +chilled.o: $(hdrdir)/ruby/internal/attr/maybe_unused.h +chilled.o: $(hdrdir)/ruby/internal/attr/noalias.h +chilled.o: $(hdrdir)/ruby/internal/attr/nodiscard.h +chilled.o: $(hdrdir)/ruby/internal/attr/noexcept.h +chilled.o: $(hdrdir)/ruby/internal/attr/noinline.h +chilled.o: $(hdrdir)/ruby/internal/attr/nonnull.h +chilled.o: $(hdrdir)/ruby/internal/attr/noreturn.h +chilled.o: $(hdrdir)/ruby/internal/attr/packed_struct.h +chilled.o: $(hdrdir)/ruby/internal/attr/pure.h +chilled.o: $(hdrdir)/ruby/internal/attr/restrict.h +chilled.o: $(hdrdir)/ruby/internal/attr/returns_nonnull.h +chilled.o: $(hdrdir)/ruby/internal/attr/warning.h +chilled.o: $(hdrdir)/ruby/internal/attr/weakref.h +chilled.o: $(hdrdir)/ruby/internal/cast.h +chilled.o: $(hdrdir)/ruby/internal/compiler_is.h +chilled.o: $(hdrdir)/ruby/internal/compiler_is/apple.h +chilled.o: $(hdrdir)/ruby/internal/compiler_is/clang.h +chilled.o: $(hdrdir)/ruby/internal/compiler_is/gcc.h +chilled.o: $(hdrdir)/ruby/internal/compiler_is/intel.h +chilled.o: $(hdrdir)/ruby/internal/compiler_is/msvc.h +chilled.o: $(hdrdir)/ruby/internal/compiler_is/sunpro.h +chilled.o: $(hdrdir)/ruby/internal/compiler_since.h +chilled.o: $(hdrdir)/ruby/internal/config.h +chilled.o: $(hdrdir)/ruby/internal/constant_p.h +chilled.o: $(hdrdir)/ruby/internal/core.h +chilled.o: $(hdrdir)/ruby/internal/core/rarray.h +chilled.o: $(hdrdir)/ruby/internal/core/rbasic.h +chilled.o: $(hdrdir)/ruby/internal/core/rbignum.h +chilled.o: $(hdrdir)/ruby/internal/core/rclass.h +chilled.o: $(hdrdir)/ruby/internal/core/rdata.h +chilled.o: $(hdrdir)/ruby/internal/core/rfile.h +chilled.o: $(hdrdir)/ruby/internal/core/rhash.h +chilled.o: $(hdrdir)/ruby/internal/core/robject.h +chilled.o: $(hdrdir)/ruby/internal/core/rregexp.h +chilled.o: $(hdrdir)/ruby/internal/core/rstring.h +chilled.o: $(hdrdir)/ruby/internal/core/rstruct.h +chilled.o: $(hdrdir)/ruby/internal/core/rtypeddata.h +chilled.o: $(hdrdir)/ruby/internal/ctype.h +chilled.o: $(hdrdir)/ruby/internal/dllexport.h +chilled.o: $(hdrdir)/ruby/internal/dosish.h +chilled.o: $(hdrdir)/ruby/internal/error.h +chilled.o: $(hdrdir)/ruby/internal/eval.h +chilled.o: $(hdrdir)/ruby/internal/event.h +chilled.o: $(hdrdir)/ruby/internal/fl_type.h +chilled.o: $(hdrdir)/ruby/internal/gc.h +chilled.o: $(hdrdir)/ruby/internal/glob.h +chilled.o: $(hdrdir)/ruby/internal/globals.h +chilled.o: $(hdrdir)/ruby/internal/has/attribute.h +chilled.o: $(hdrdir)/ruby/internal/has/builtin.h +chilled.o: $(hdrdir)/ruby/internal/has/c_attribute.h +chilled.o: $(hdrdir)/ruby/internal/has/cpp_attribute.h +chilled.o: $(hdrdir)/ruby/internal/has/declspec_attribute.h +chilled.o: $(hdrdir)/ruby/internal/has/extension.h +chilled.o: $(hdrdir)/ruby/internal/has/feature.h +chilled.o: $(hdrdir)/ruby/internal/has/warning.h +chilled.o: $(hdrdir)/ruby/internal/intern/array.h +chilled.o: $(hdrdir)/ruby/internal/intern/bignum.h +chilled.o: $(hdrdir)/ruby/internal/intern/class.h +chilled.o: $(hdrdir)/ruby/internal/intern/compar.h +chilled.o: $(hdrdir)/ruby/internal/intern/complex.h +chilled.o: $(hdrdir)/ruby/internal/intern/cont.h +chilled.o: $(hdrdir)/ruby/internal/intern/dir.h +chilled.o: $(hdrdir)/ruby/internal/intern/enum.h +chilled.o: $(hdrdir)/ruby/internal/intern/enumerator.h +chilled.o: $(hdrdir)/ruby/internal/intern/error.h +chilled.o: $(hdrdir)/ruby/internal/intern/eval.h +chilled.o: $(hdrdir)/ruby/internal/intern/file.h +chilled.o: $(hdrdir)/ruby/internal/intern/hash.h +chilled.o: $(hdrdir)/ruby/internal/intern/io.h +chilled.o: $(hdrdir)/ruby/internal/intern/load.h +chilled.o: $(hdrdir)/ruby/internal/intern/marshal.h +chilled.o: $(hdrdir)/ruby/internal/intern/numeric.h +chilled.o: $(hdrdir)/ruby/internal/intern/object.h +chilled.o: $(hdrdir)/ruby/internal/intern/parse.h +chilled.o: $(hdrdir)/ruby/internal/intern/proc.h +chilled.o: $(hdrdir)/ruby/internal/intern/process.h +chilled.o: $(hdrdir)/ruby/internal/intern/random.h +chilled.o: $(hdrdir)/ruby/internal/intern/range.h +chilled.o: $(hdrdir)/ruby/internal/intern/rational.h +chilled.o: $(hdrdir)/ruby/internal/intern/re.h +chilled.o: $(hdrdir)/ruby/internal/intern/ruby.h +chilled.o: $(hdrdir)/ruby/internal/intern/select.h +chilled.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +chilled.o: $(hdrdir)/ruby/internal/intern/signal.h +chilled.o: $(hdrdir)/ruby/internal/intern/sprintf.h +chilled.o: $(hdrdir)/ruby/internal/intern/string.h +chilled.o: $(hdrdir)/ruby/internal/intern/struct.h +chilled.o: $(hdrdir)/ruby/internal/intern/thread.h +chilled.o: $(hdrdir)/ruby/internal/intern/time.h +chilled.o: $(hdrdir)/ruby/internal/intern/variable.h +chilled.o: $(hdrdir)/ruby/internal/intern/vm.h +chilled.o: $(hdrdir)/ruby/internal/interpreter.h +chilled.o: $(hdrdir)/ruby/internal/iterator.h +chilled.o: $(hdrdir)/ruby/internal/memory.h +chilled.o: $(hdrdir)/ruby/internal/method.h +chilled.o: $(hdrdir)/ruby/internal/module.h +chilled.o: $(hdrdir)/ruby/internal/newobj.h +chilled.o: $(hdrdir)/ruby/internal/scan_args.h +chilled.o: $(hdrdir)/ruby/internal/special_consts.h +chilled.o: $(hdrdir)/ruby/internal/static_assert.h +chilled.o: $(hdrdir)/ruby/internal/stdalign.h +chilled.o: $(hdrdir)/ruby/internal/stdbool.h +chilled.o: $(hdrdir)/ruby/internal/symbol.h +chilled.o: $(hdrdir)/ruby/internal/value.h +chilled.o: $(hdrdir)/ruby/internal/value_type.h +chilled.o: $(hdrdir)/ruby/internal/variable.h +chilled.o: $(hdrdir)/ruby/internal/warning_push.h +chilled.o: $(hdrdir)/ruby/internal/xmalloc.h +chilled.o: $(hdrdir)/ruby/missing.h +chilled.o: $(hdrdir)/ruby/ruby.h +chilled.o: $(hdrdir)/ruby/st.h +chilled.o: $(hdrdir)/ruby/subst.h +chilled.o: chilled.c coderange.o: $(RUBY_EXTCONF_H) coderange.o: $(arch_hdrdir)/ruby/config.h coderange.o: $(hdrdir)/ruby/assert.h diff --git a/ext/extmk.rb b/ext/extmk.rb index e52fbb4208d85f..6c7c7aeab777e4 100755 --- a/ext/extmk.rb +++ b/ext/extmk.rb @@ -499,26 +499,30 @@ def $mflags.defined?(var) mandatory_exts = {} withes, withouts = [["--with", nil], ["--without", default_exclude_exts]].collect {|w, d| if !(w = %w[-extensions -ext].collect {|o|arg_config(w+o)}).any? - d ? proc {|c1| d.any?(&c1)} : proc {true} + d ? proc {|&c1| d.any?(&c1)} : proc {true} elsif (w = w.grep(String)).empty? proc {true} else w = w.collect {|o| o.split(/,/)}.flatten w.collect! {|o| o == '+' ? d : o}.flatten! - proc {|c1| w.any?(&c1)} + proc {|&c1| w.any?(&c1)} end } cond = proc {|ext, *| - withes.call(proc {|n| - !n or (mandatory_exts[ext] = true if File.fnmatch(n, ext)) - }) and - !withouts.call(proc {|n| File.fnmatch(n, ext)}) + withes.call {|n| !n or (mandatory_exts[ext] = true if File.fnmatch(n, ext))} and + !withouts.call {|n| File.fnmatch(n, ext)} } ($extension || %w[*]).each do |e| e = e.sub(/\A(?:\.\/)+/, '') incl, excl = Dir.glob("#{e}/**/extconf.rb", base: "#$top_srcdir/#{ext_prefix}").collect {|d| File.dirname(d) }.partition {|ext| + if @gemname + ext = ext[%r[\A[^/]+]] # extract gem name + Dir.glob("*.gemspec", base: "#$top_srcdir/#{ext_prefix}/#{ext}") do |g| + break ext = g if ext.start_with?("#{g.chomp!(".gemspec")}-") + end + end with_config(ext, &cond) } incl.sort! diff --git a/ext/fcntl/fcntl.c b/ext/fcntl/fcntl.c index 4af2077a0a64c3..e34a3aeae3b3bf 100644 --- a/ext/fcntl/fcntl.c +++ b/ext/fcntl/fcntl.c @@ -28,7 +28,10 @@ pack up your own arguments to pass as args for locking functions, etc. #include "ruby.h" #include -/* Fcntl loads the constants defined in the system's C header +/* + * Document-module: Fcntl + * + * Fcntl loads the constants defined in the system's C header * file, and used with both the fcntl(2) and open(2) POSIX system calls. * * To perform a fcntl(2) operation, use IO::fcntl. @@ -69,11 +72,11 @@ Init_fcntl(void) { VALUE mFcntl = rb_define_module("Fcntl"); + /* The version string. */ rb_define_const(mFcntl, "VERSION", rb_str_new_cstr(FCNTL_VERSION)); #ifdef F_DUPFD - /* Document-const: F_DUPFD - * + /* * Duplicate a file descriptor to the minimum unused file descriptor * greater than or equal to the argument. * @@ -84,195 +87,164 @@ Init_fcntl(void) rb_define_const(mFcntl, "F_DUPFD", INT2NUM(F_DUPFD)); #endif #ifdef F_GETFD - /* Document-const: F_GETFD - * + /* * Read the close-on-exec flag of a file descriptor. */ rb_define_const(mFcntl, "F_GETFD", INT2NUM(F_GETFD)); #endif #ifdef F_GETLK - /* Document-const: F_GETLK - * + /* * Determine whether a given region of a file is locked. This uses one of * the F_*LK flags. */ rb_define_const(mFcntl, "F_GETLK", INT2NUM(F_GETLK)); #endif #ifdef F_SETFD - /* Document-const: F_SETFD - * + /* * Set the close-on-exec flag of a file descriptor. */ rb_define_const(mFcntl, "F_SETFD", INT2NUM(F_SETFD)); #endif #ifdef F_GETFL - /* Document-const: F_GETFL - * + /* * Get the file descriptor flags. This will be one or more of the O_* * flags. */ rb_define_const(mFcntl, "F_GETFL", INT2NUM(F_GETFL)); #endif #ifdef F_SETFL - /* Document-const: F_SETFL - * + /* * Set the file descriptor flags. This will be one or more of the O_* * flags. */ rb_define_const(mFcntl, "F_SETFL", INT2NUM(F_SETFL)); #endif #ifdef F_SETLK - /* Document-const: F_SETLK - * + /* * Acquire a lock on a region of a file. This uses one of the F_*LCK * flags. */ rb_define_const(mFcntl, "F_SETLK", INT2NUM(F_SETLK)); #endif #ifdef F_SETLKW - /* Document-const: F_SETLKW - * + /* * Acquire a lock on a region of a file, waiting if necessary. This uses * one of the F_*LCK flags */ rb_define_const(mFcntl, "F_SETLKW", INT2NUM(F_SETLKW)); #endif #ifdef FD_CLOEXEC - /* Document-const: FD_CLOEXEC - * + /* * the value of the close-on-exec flag. */ rb_define_const(mFcntl, "FD_CLOEXEC", INT2NUM(FD_CLOEXEC)); #endif #ifdef F_RDLCK - /* Document-const: F_RDLCK - * + /* * Read lock for a region of a file */ rb_define_const(mFcntl, "F_RDLCK", INT2NUM(F_RDLCK)); #endif #ifdef F_UNLCK - /* Document-const: F_UNLCK - * + /* * Remove lock for a region of a file */ rb_define_const(mFcntl, "F_UNLCK", INT2NUM(F_UNLCK)); #endif #ifdef F_WRLCK - /* Document-const: F_WRLCK - * + /* * Write lock for a region of a file */ rb_define_const(mFcntl, "F_WRLCK", INT2NUM(F_WRLCK)); #endif #ifdef F_SETPIPE_SZ - /* Document-const: F_SETPIPE_SZ - * + /* * Change the capacity of the pipe referred to by fd to be at least arg bytes. */ rb_define_const(mFcntl, "F_SETPIPE_SZ", INT2NUM(F_SETPIPE_SZ)); #endif #ifdef F_GETPIPE_SZ - /* Document-const: F_GETPIPE_SZ - * + /* * Return (as the function result) the capacity of the pipe referred to by fd. */ rb_define_const(mFcntl, "F_GETPIPE_SZ", INT2NUM(F_GETPIPE_SZ)); #endif #ifdef O_CREAT - /* Document-const: O_CREAT - * + /* * Create the file if it doesn't exist */ rb_define_const(mFcntl, "O_CREAT", INT2NUM(O_CREAT)); #endif #ifdef O_EXCL - /* Document-const: O_EXCL - * + /* * Used with O_CREAT, fail if the file exists */ rb_define_const(mFcntl, "O_EXCL", INT2NUM(O_EXCL)); #endif #ifdef O_NOCTTY - /* Document-const: O_NOCTTY - * + /* * Open TTY without it becoming the controlling TTY */ rb_define_const(mFcntl, "O_NOCTTY", INT2NUM(O_NOCTTY)); #endif #ifdef O_TRUNC - /* Document-const: O_TRUNC - * + /* * Truncate the file on open */ rb_define_const(mFcntl, "O_TRUNC", INT2NUM(O_TRUNC)); #endif #ifdef O_APPEND - /* Document-const: O_APPEND - * + /* * Open the file in append mode */ rb_define_const(mFcntl, "O_APPEND", INT2NUM(O_APPEND)); #endif #ifdef O_NONBLOCK - /* Document-const: O_NONBLOCK - * + /* * Open the file in non-blocking mode */ rb_define_const(mFcntl, "O_NONBLOCK", INT2NUM(O_NONBLOCK)); #endif #ifdef O_NDELAY - /* Document-const: O_NDELAY - * + /* * Open the file in non-blocking mode */ rb_define_const(mFcntl, "O_NDELAY", INT2NUM(O_NDELAY)); #endif #ifdef O_RDONLY - /* Document-const: O_RDONLY - * + /* * Open the file in read-only mode */ rb_define_const(mFcntl, "O_RDONLY", INT2NUM(O_RDONLY)); #endif #ifdef O_RDWR - /* Document-const: O_RDWR - * + /* * Open the file in read-write mode */ rb_define_const(mFcntl, "O_RDWR", INT2NUM(O_RDWR)); #endif #ifdef O_WRONLY - /* Document-const: O_WRONLY - * + /* * Open the file in write-only mode. */ rb_define_const(mFcntl, "O_WRONLY", INT2NUM(O_WRONLY)); #endif -#ifdef O_ACCMODE - /* Document-const: O_ACCMODE - * +#ifndef O_ACCMODE + int O_ACCMODE = (O_RDONLY | O_WRONLY | O_RDWR); +#endif + /* * Mask to extract the read/write flags */ rb_define_const(mFcntl, "O_ACCMODE", INT2FIX(O_ACCMODE)); -#else - /* Document-const: O_ACCMODE - * - * Mask to extract the read/write flags - */ - rb_define_const(mFcntl, "O_ACCMODE", INT2FIX(O_RDONLY | O_WRONLY | O_RDWR)); -#endif #ifdef F_DUP2FD - /* Document-const: F_DUP2FD - * + /* * It is a FreeBSD specific constant and equivalent * to dup2 call. */ rb_define_const(mFcntl, "F_DUP2FD", INT2NUM(F_DUP2FD)); #endif #ifdef F_DUP2FD_CLOEXEC - /* Document-const: F_DUP2FD_CLOEXEC - * + /* * It is a FreeBSD specific constant and acts * similarly as F_DUP2FD but set the FD_CLOEXEC * flag in addition. diff --git a/ext/fcntl/fcntl.gemspec b/ext/fcntl/fcntl.gemspec index 54aadb4b81a789..d621bc0491a1ad 100644 --- a/ext/fcntl/fcntl.gemspec +++ b/ext/fcntl/fcntl.gemspec @@ -23,9 +23,10 @@ Gem::Specification.new do |spec| spec.licenses = ["Ruby", "BSD-2-Clause"] spec.files = ["ext/fcntl/extconf.rb", "ext/fcntl/fcntl.c"] + spec.extra_rdoc_files = [".document", ".rdoc_options", "LICENSE.txt", "README.md"] spec.bindir = "exe" spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } spec.require_paths = ["lib"] spec.extensions = "ext/fcntl/extconf.rb" - spec.required_ruby_version = ">= 2.3.0" + spec.required_ruby_version = ">= 2.5.0" end diff --git a/ext/json/generator/generator.c b/ext/json/generator/generator.c index a33df848df638c..6d78284bc444cc 100644 --- a/ext/json/generator/generator.c +++ b/ext/json/generator/generator.c @@ -892,7 +892,6 @@ static void generate_json_object(FBuffer *buffer, VALUE Vstate, JSON_Generator_S struct hash_foreach_arg arg; if (max_nesting != 0 && depth > max_nesting) { - fbuffer_free(buffer); rb_raise(eNestingError, "nesting of %ld is too deep", --state->depth); } fbuffer_append_char(buffer, '{'); @@ -927,7 +926,6 @@ static void generate_json_array(FBuffer *buffer, VALUE Vstate, JSON_Generator_St long depth = ++state->depth; int i, j; if (max_nesting != 0 && depth > max_nesting) { - fbuffer_free(buffer); rb_raise(eNestingError, "nesting of %ld is too deep", --state->depth); } fbuffer_append_char(buffer, '['); @@ -1020,10 +1018,8 @@ static void generate_json_float(FBuffer *buffer, VALUE Vstate, JSON_Generator_St VALUE tmp = rb_funcall(obj, i_to_s, 0); if (!allow_nan) { if (isinf(value)) { - fbuffer_free(buffer); rb_raise(eGeneratorError, "%"PRIsVALUE" not allowed in JSON", RB_OBJ_STRING(tmp)); } else if (isnan(value)) { - fbuffer_free(buffer); rb_raise(eGeneratorError, "%"PRIsVALUE" not allowed in JSON", RB_OBJ_STRING(tmp)); } } @@ -1096,11 +1092,45 @@ static FBuffer *cState_prepare_buffer(VALUE self) return buffer; } +struct generate_json_data { + FBuffer *buffer; + VALUE vstate; + JSON_Generator_State *state; + VALUE obj; +}; + +static VALUE generate_json_try(VALUE d) +{ + struct generate_json_data *data = (struct generate_json_data *)d; + + generate_json(data->buffer, data->vstate, data->state, data->obj); + + return Qnil; +} + +static VALUE generate_json_rescue(VALUE d, VALUE exc) +{ + struct generate_json_data *data = (struct generate_json_data *)d; + fbuffer_free(data->buffer); + + rb_exc_raise(exc); + + return Qundef; +} + static VALUE cState_partial_generate(VALUE self, VALUE obj) { FBuffer *buffer = cState_prepare_buffer(self); GET_STATE(self); - generate_json(buffer, self, state, obj); + + struct generate_json_data data = { + .buffer = buffer, + .vstate = self, + .state = state, + .obj = obj + }; + rb_rescue(generate_json_try, (VALUE)&data, generate_json_rescue, (VALUE)&data); + return fbuffer_to_s(buffer); } diff --git a/ext/json/lib/json/common.rb b/ext/json/lib/json/common.rb index 090066012d9a26..95098d3bb48f79 100644 --- a/ext/json/lib/json/common.rb +++ b/ext/json/lib/json/common.rb @@ -1,8 +1,9 @@ #frozen_string_literal: false require 'json/version' -require 'json/generic_object' module JSON + autoload :GenericObject, 'json/generic_object' + NOT_SET = Object.new.freeze private_constant :NOT_SET diff --git a/ext/json/lib/json/generic_object.rb b/ext/json/lib/json/generic_object.rb index b7b8b04ec43f95..56efda64955bb5 100644 --- a/ext/json/lib/json/generic_object.rb +++ b/ext/json/lib/json/generic_object.rb @@ -2,6 +2,7 @@ begin require 'ostruct' rescue LoadError + warn "JSON::GenericObject requires 'ostruct'. Please install it with `gem install ostruct`." end module JSON diff --git a/ext/json/lib/json/version.rb b/ext/json/lib/json/version.rb index b43ceecdcd7ef6..836f47edf40c68 100644 --- a/ext/json/lib/json/version.rb +++ b/ext/json/lib/json/version.rb @@ -1,7 +1,7 @@ # frozen_string_literal: false module JSON # JSON version - VERSION = '2.7.1' + VERSION = '2.7.2' VERSION_ARRAY = VERSION.split(/\./).map { |x| x.to_i } # :nodoc: VERSION_MAJOR = VERSION_ARRAY[0] # :nodoc: VERSION_MINOR = VERSION_ARRAY[1] # :nodoc: diff --git a/ext/objspace/objspace_dump.c b/ext/objspace/objspace_dump.c index d05fe6603751e4..bb479b91c578ad 100644 --- a/ext/objspace/objspace_dump.c +++ b/ext/objspace/objspace_dump.c @@ -476,6 +476,8 @@ dump_object(VALUE obj, struct dump_config *dc) dump_append(dc, ", \"embedded\":true"); if (FL_TEST(obj, RSTRING_FSTR)) dump_append(dc, ", \"fstring\":true"); + if (CHILLED_STRING_P(obj)) + dump_append(dc, ", \"chilled\":true"); if (STR_SHARED_P(obj)) dump_append(dc, ", \"shared\":true"); else diff --git a/ext/openssl/extconf.rb b/ext/openssl/extconf.rb index 4119c72c48767d..dd3732d0a876fc 100644 --- a/ext/openssl/extconf.rb +++ b/ext/openssl/extconf.rb @@ -13,14 +13,7 @@ require "mkmf" -ssl_dirs = nil -if defined?(::TruffleRuby) - # Always respect the openssl prefix chosen by truffle/openssl-prefix - require 'truffle/openssl-prefix' - ssl_dirs = dir_config("openssl", ENV["OPENSSL_PREFIX"]) -else - ssl_dirs = dir_config("openssl") -end +ssl_dirs = dir_config("openssl") dir_config_given = ssl_dirs.any? _, ssl_ldir = ssl_dirs diff --git a/ext/openssl/ossl_kdf.c b/ext/openssl/ossl_kdf.c index 48b161d4f47749..ba197a659ec777 100644 --- a/ext/openssl/ossl_kdf.c +++ b/ext/openssl/ossl_kdf.c @@ -18,7 +18,7 @@ static VALUE mKDF, eKDF; * of _length_ bytes. * * For more information about PBKDF2, see RFC 2898 Section 5.2 - * (https://tools.ietf.org/html/rfc2898#section-5.2). + * (https://www.rfc-editor.org/rfc/rfc2898#section-5.2). * * === Parameters * pass :: The password. @@ -81,10 +81,10 @@ kdf_pbkdf2_hmac(int argc, VALUE *argv, VALUE self) * bcrypt. * * The keyword arguments _N_, _r_ and _p_ can be used to tune scrypt. RFC 7914 - * (published on 2016-08, https://tools.ietf.org/html/rfc7914#section-2) states + * (published on 2016-08, https://www.rfc-editor.org/rfc/rfc7914#section-2) states * that using values r=8 and p=1 appears to yield good results. * - * See RFC 7914 (https://tools.ietf.org/html/rfc7914) for more information. + * See RFC 7914 (https://www.rfc-editor.org/rfc/rfc7914) for more information. * * === Parameters * pass :: Passphrase. @@ -147,7 +147,7 @@ kdf_scrypt(int argc, VALUE *argv, VALUE self) * KDF.hkdf(ikm, salt:, info:, length:, hash:) -> String * * HMAC-based Extract-and-Expand Key Derivation Function (HKDF) as specified in - * {RFC 5869}[https://tools.ietf.org/html/rfc5869]. + * {RFC 5869}[https://www.rfc-editor.org/rfc/rfc5869]. * * New in OpenSSL 1.1.0. * @@ -165,7 +165,7 @@ kdf_scrypt(int argc, VALUE *argv, VALUE self) * The hash function. * * === Example - * # The values from https://datatracker.ietf.org/doc/html/rfc5869#appendix-A.1 + * # The values from https://www.rfc-editor.org/rfc/rfc5869#appendix-A.1 * ikm = ["0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"].pack("H*") * salt = ["000102030405060708090a0b0c"].pack("H*") * info = ["f0f1f2f3f4f5f6f7f8f9"].pack("H*") diff --git a/ext/openssl/ossl_ns_spki.c b/ext/openssl/ossl_ns_spki.c index 9bed1f330ec5e2..9d70b5d87a5d59 100644 --- a/ext/openssl/ossl_ns_spki.c +++ b/ext/openssl/ossl_ns_spki.c @@ -365,8 +365,8 @@ ossl_spki_verify(VALUE self, VALUE key) * * OpenSSL::Netscape is a namespace for SPKI (Simple Public Key * Infrastructure) which implements Signed Public Key and Challenge. - * See {RFC 2692}[http://tools.ietf.org/html/rfc2692] and {RFC - * 2693}[http://tools.ietf.org/html/rfc2692] for details. + * See {RFC 2692}[https://www.rfc-editor.org/rfc/rfc2692] and {RFC + * 2693}[https://www.rfc-editor.org/rfc/rfc2692] for details. */ /* Document-class: OpenSSL::Netscape::SPKIError diff --git a/ext/pty/extconf.rb b/ext/pty/extconf.rb index ba0c4286fd31d1..8cf9e30e69786a 100644 --- a/ext/pty/extconf.rb +++ b/ext/pty/extconf.rb @@ -16,6 +16,7 @@ if have_func("posix_openpt") or (util or have_func("openpty")) or have_func("_getpty") or + have_func("ptsname_r") or have_func("ptsname") or have_func("ioctl") create_makefile('pty') diff --git a/ext/pty/pty.c b/ext/pty/pty.c index 8dca8ba281dde6..5e66abcbe217d4 100644 --- a/ext/pty/pty.c +++ b/ext/pty/pty.c @@ -92,9 +92,13 @@ struct pty_info { static void getDevice(int*, int*, char [DEVICELEN], int); +static int start_new_session(char *errbuf, size_t errbuf_len); +static int obtain_ctty(int master, int slave, const char *slavename, char *errbuf, size_t errbuf_len); +static int drop_privilige(char *errbuf, size_t errbuf_len); + struct child_info { int master, slave; - char *slavename; + const char *slavename; VALUE execarg_obj; struct rb_execarg *eargp; }; @@ -102,26 +106,42 @@ struct child_info { static int chfunc(void *data, char *errbuf, size_t errbuf_len) { - struct child_info *carg = data; + const struct child_info *carg = data; int master = carg->master; int slave = carg->slave; + const char *slavename = carg->slavename; + + if (start_new_session(errbuf, errbuf_len)) + return -1; + + if (obtain_ctty(master, slave, slavename, errbuf, errbuf_len)) + return -1; + + if (drop_privilige(errbuf, errbuf_len)) + return -1; + + return rb_exec_async_signal_safe(carg->eargp, errbuf, errbuf_len); +} #define ERROR_EXIT(str) do { \ strlcpy(errbuf, (str), errbuf_len); \ return -1; \ } while (0) - /* - * Set free from process group and controlling terminal - */ +/* + * Set free from process group and controlling terminal + */ +static int +start_new_session(char *errbuf, size_t errbuf_len) +{ #ifdef HAVE_SETSID (void) setsid(); #else /* HAS_SETSID */ # ifdef HAVE_SETPGRP -# ifdef SETGRP_VOID +# ifdef SETPGRP_VOID if (setpgrp() == -1) ERROR_EXIT("setpgrp()"); -# else /* SETGRP_VOID */ +# else /* SETPGRP_VOID */ if (setpgrp(0, getpid()) == -1) ERROR_EXIT("setpgrp()"); { @@ -132,20 +152,25 @@ chfunc(void *data, char *errbuf, size_t errbuf_len) ERROR_EXIT("ioctl(TIOCNOTTY)"); close(i); } -# endif /* SETGRP_VOID */ +# endif /* SETPGRP_VOID */ # endif /* HAVE_SETPGRP */ #endif /* HAS_SETSID */ + return 0; +} - /* - * obtain new controlling terminal - */ +/* + * obtain new controlling terminal + */ +static int +obtain_ctty(int master, int slave, const char *slavename, char *errbuf, size_t errbuf_len) +{ #if defined(TIOCSCTTY) close(master); (void) ioctl(slave, TIOCSCTTY, (char *)0); /* errors ignored for sun */ #else close(slave); - slave = rb_cloexec_open(carg->slavename, O_RDWR, 0); + slave = rb_cloexec_open(slavename, O_RDWR, 0); if (slave < 0) { ERROR_EXIT("open: pty slave"); } @@ -156,13 +181,19 @@ chfunc(void *data, char *errbuf, size_t errbuf_len) dup2(slave,1); dup2(slave,2); if (slave < 0 || slave > 2) (void)!close(slave); + return 0; +} + +static int +drop_privilige(char *errbuf, size_t errbuf_len) +{ #if defined(HAVE_SETEUID) || defined(HAVE_SETREUID) || defined(HAVE_SETRESUID) if (seteuid(getuid())) ERROR_EXIT("seteuid()"); #endif + return 0; +} - return rb_exec_async_signal_safe(carg->eargp, errbuf, sizeof(errbuf_len)); #undef ERROR_EXIT -} static void establishShell(int argc, VALUE *argv, struct pty_info *info, @@ -225,7 +256,21 @@ establishShell(int argc, VALUE *argv, struct pty_info *info, RB_GC_GUARD(carg.execarg_obj); } -#if defined(HAVE_POSIX_OPENPT) || defined(HAVE_OPENPTY) || defined(HAVE_PTSNAME) +#if defined(HAVE_PTSNAME) && !defined(HAVE_PTSNAME_R) +/* glibc only, not obsolete interface on Tru64 or HP-UX */ +static int +ptsname_r(int fd, char *buf, size_t buflen) +{ + extern char *ptsname(int); + char *name = ptsname(fd); + if (!name) return -1; + strlcpy(buf, name, buflen); + return 0; +} +# define HAVE_PTSNAME_R 1 +#endif + +#if defined(HAVE_POSIX_OPENPT) || defined(HAVE_OPENPTY) || defined(HAVE_PTSNAME_R) static int no_mesg(char *slavedevice, int nomesg) { @@ -258,13 +303,19 @@ get_device_once(int *master, int *slave, char SlaveName[DEVICELEN], int nomesg, /* Unix98 PTY */ int masterfd = -1, slavefd = -1; char *slavedevice; + struct sigaction dfl, old; + + dfl.sa_handler = SIG_DFL; + dfl.sa_flags = 0; + sigemptyset(&dfl.sa_mask); #if defined(__sun) || defined(__OpenBSD__) || (defined(__FreeBSD__) && __FreeBSD_version < 902000) /* workaround for Solaris 10: grantpt() doesn't work if FD_CLOEXEC is set. [ruby-dev:44688] */ /* FreeBSD 9.2 or later supports O_CLOEXEC * http://www.freebsd.org/cgi/query-pr.cgi?pr=162374 */ if ((masterfd = posix_openpt(O_RDWR|O_NOCTTY)) == -1) goto error; - if (rb_grantpt(masterfd) == -1) goto error; + if (sigaction(SIGCHLD, &dfl, &old) == -1) goto error; + if (grantpt(masterfd) == -1) goto grantpt_error; rb_fd_fix_cloexec(masterfd); #else { @@ -278,10 +329,13 @@ get_device_once(int *master, int *slave, char SlaveName[DEVICELEN], int nomesg, if ((masterfd = posix_openpt(flags)) == -1) goto error; } rb_fd_fix_cloexec(masterfd); - if (rb_grantpt(masterfd) == -1) goto error; + if (sigaction(SIGCHLD, &dfl, &old) == -1) goto error; + if (grantpt(masterfd) == -1) goto grantpt_error; #endif + if (sigaction(SIGCHLD, &old, NULL) == -1) goto error; if (unlockpt(masterfd) == -1) goto error; - if ((slavedevice = ptsname(masterfd)) == NULL) goto error; + if (ptsname_r(masterfd, SlaveName, DEVICELEN) != 0) goto error; + slavedevice = SlaveName; if (no_mesg(slavedevice, nomesg) == -1) goto error; if ((slavefd = rb_cloexec_open(slavedevice, O_RDWR|O_NOCTTY, 0)) == -1) goto error; rb_update_max_fd(slavefd); @@ -294,9 +348,10 @@ get_device_once(int *master, int *slave, char SlaveName[DEVICELEN], int nomesg, *master = masterfd; *slave = slavefd; - strlcpy(SlaveName, slavedevice, DEVICELEN); return 0; + grantpt_error: + sigaction(SIGCHLD, &old, NULL); error: if (slavefd != -1) close(slavefd); if (masterfd != -1) close(masterfd); @@ -346,21 +401,25 @@ get_device_once(int *master, int *slave, char SlaveName[DEVICELEN], int nomesg, char *slavedevice; void (*s)(); - extern char *ptsname(int); extern int unlockpt(int); + extern int grantpt(int); #if defined(__sun) /* workaround for Solaris 10: grantpt() doesn't work if FD_CLOEXEC is set. [ruby-dev:44688] */ if((masterfd = open("/dev/ptmx", O_RDWR, 0)) == -1) goto error; - if(rb_grantpt(masterfd) == -1) goto error; + s = signal(SIGCHLD, SIG_DFL); + if(grantpt(masterfd) == -1) goto error; rb_fd_fix_cloexec(masterfd); #else if((masterfd = rb_cloexec_open("/dev/ptmx", O_RDWR, 0)) == -1) goto error; rb_update_max_fd(masterfd); - if(rb_grantpt(masterfd) == -1) goto error; + s = signal(SIGCHLD, SIG_DFL); + if(grantpt(masterfd) == -1) goto error; #endif + signal(SIGCHLD, s); if(unlockpt(masterfd) == -1) goto error; - if((slavedevice = ptsname(masterfd)) == NULL) goto error; + if (ptsname_r(masterfd, SlaveName, DEVICELEN) != 0) goto error; + slavedevice = SlaveName; if (no_mesg(slavedevice, nomesg) == -1) goto error; if((slavefd = rb_cloexec_open(slavedevice, O_RDWR, 0)) == -1) goto error; rb_update_max_fd(slavefd); @@ -371,7 +430,6 @@ get_device_once(int *master, int *slave, char SlaveName[DEVICELEN], int nomesg, #endif *master = masterfd; *slave = slavefd; - strlcpy(SlaveName, slavedevice, DEVICELEN); return 0; error: diff --git a/ext/ripper/ripper_init.c.tmpl b/ext/ripper/ripper_init.c.tmpl index 2386f54c0e6fb8..c9d381da5b151e 100644 --- a/ext/ripper/ripper_init.c.tmpl +++ b/ext/ripper/ripper_init.c.tmpl @@ -2,14 +2,13 @@ #include "ruby/ruby.h" #include "ruby/encoding.h" #include "internal.h" -#include "internal/imemo.h" /* needed by ruby_parser.h */ +#include "rubyparser.h" +#define YYSTYPE_IS_DECLARED +#include "parse.h" #include "internal/parse.h" #include "internal/ruby_parser.h" #include "node.h" -#include "rubyparser.h" #include "eventids1.h" -#define YYSTYPE_IS_DECLARED -#include "parse.h" #include "eventids2.h" #include "ripper_init.h" @@ -94,7 +93,8 @@ ripper_s_allocate(VALUE klass) &parser_data_type, r); #ifdef UNIVERSAL_PARSER - r->p = rb_parser_params_allocate(); + const rb_parser_config_t *config = rb_ruby_parser_config(); + r->p = rb_ripper_parser_params_allocate(config); #else r->p = rb_ruby_ripper_parser_allocate(); #endif diff --git a/ext/ripper/tools/dsl.rb b/ext/ripper/tools/dsl.rb index 8f03233574aef1..3e368813e516d8 100644 --- a/ext/ripper/tools/dsl.rb +++ b/ext/ripper/tools/dsl.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Simple DSL implementation for Ripper code generation # # input: /*% ripper: stmts_add!(stmts_new!, void_stmt!) %*/ @@ -33,7 +35,7 @@ def initialize(code, options, lineno = nil) # struct parser_params *p p = p = "p" - @code = "" + @code = +"" code = code.gsub(%r[\G#{NOT_REF_PATTERN}\K(\$|\$:|@)#{TAG_PATTERN}?#{NAME_PATTERN}]o, '"\&"') @last_value = eval(code) rescue SyntaxError diff --git a/ext/ripper/tools/preproc.rb b/ext/ripper/tools/preproc.rb index 981237a5854a8f..a54302fb91e608 100644 --- a/ext/ripper/tools/preproc.rb +++ b/ext/ripper/tools/preproc.rb @@ -53,34 +53,24 @@ def process(f, out, path, template) def prelude(f, out) @exprs = {} - lex_state_def = false while line = f.gets case line when /\A%%/ out << "%%\n" return - when /\A%token/, /\A%type/, /\A} _\w+)?>/ - # types in %union which have corresponding set_yylval_* macro. - out << line - when /^enum lex_state_(?:bits|e) \{/ - lex_state_def = true - out << line - when /^\}/ - lex_state_def = false - out << line else - out << line - end - if lex_state_def - case line - when /^\s*(EXPR_\w+),\s+\/\*(.+)\*\// - @exprs[$1.chomp("_bit")] = $2.strip - when /^\s*(EXPR_\w+)\s+=\s+(.+)$/ - name = $1 - val = $2.chomp(",") - @exprs[name] = "equals to " + (val.start_with?("(") ? "#{val}" : "+#{val}+") + if (/^enum lex_state_(?:bits|e) \{/ =~ line)..(/^\}/ =~ line) + case line + when /^\s*(EXPR_\w+),\s+\/\*(.+)\*\// + @exprs[$1.chomp("_bit")] = $2.strip + when /^\s*(EXPR_\w+)\s+=\s+(.+)$/ + name = $1 + val = $2.chomp(",") + @exprs[name] = "equals to " + (val.start_with?("(") ? "#{val}" : "+#{val}+") + end end end + out << line end end diff --git a/ext/stringio/.document b/ext/stringio/.document new file mode 100644 index 00000000000000..decba0135a9458 --- /dev/null +++ b/ext/stringio/.document @@ -0,0 +1 @@ +*.[ch] diff --git a/ext/stringio/stringio.c b/ext/stringio/stringio.c index 27c7f65408e9ae..f0e5ee4e85ff09 100644 --- a/ext/stringio/stringio.c +++ b/ext/stringio/stringio.c @@ -15,6 +15,8 @@ static const char *const STRINGIO_VERSION = "3.1.1"; +#include + #include "ruby.h" #include "ruby/io.h" #include "ruby/encoding.h" @@ -48,7 +50,20 @@ static long strio_write(VALUE self, VALUE str); #define IS_STRIO(obj) (rb_typeddata_is_kind_of((obj), &strio_data_type)) #define error_inval(msg) (rb_syserr_fail(EINVAL, msg)) -#define get_enc(ptr) ((ptr)->enc ? (ptr)->enc : rb_enc_get((ptr)->string)) +#define get_enc(ptr) ((ptr)->enc ? (ptr)->enc : !NIL_P((ptr)->string) ? rb_enc_get((ptr)->string) : NULL) +#ifndef HAVE_RB_STR_CHILLED_P +static bool +rb_str_chilled_p(VALUE str) +{ + return false; +} +#endif + +static bool +readonly_string_p(VALUE string) +{ + return OBJ_FROZEN_RAW(string) && !rb_str_chilled_p(string); +} static struct StringIO * strio_alloc(void) @@ -166,7 +181,13 @@ writable(VALUE strio) static void check_modifiable(struct StringIO *ptr) { - if (OBJ_FROZEN(ptr->string)) { + if (NIL_P(ptr->string)) { + /* Null device StringIO */ + } + else if (rb_str_chilled_p(ptr->string)) { + rb_str_modify(ptr->string); + } + else if (OBJ_FROZEN_RAW(ptr->string)) { rb_raise(rb_eIOError, "not modifiable string"); } } @@ -281,13 +302,14 @@ strio_init(int argc, VALUE *argv, struct StringIO *ptr, VALUE self) argc = rb_scan_args(argc, argv, "02:", &string, &vmode, &opt); rb_io_extract_modeenc(&vmode, 0, opt, &oflags, &ptr->flags, &convconfig); - if (argc) { + if (!NIL_P(string)) { StringValue(string); } - else { + else if (!argc) { string = rb_enc_str_new("", 0, rb_default_external_encoding()); } - if (OBJ_FROZEN_RAW(string)) { + + if (!NIL_P(string) && readonly_string_p(string)) { if (ptr->flags & FMODE_WRITABLE) { rb_syserr_fail(EACCES, 0); } @@ -297,11 +319,11 @@ strio_init(int argc, VALUE *argv, struct StringIO *ptr, VALUE self) ptr->flags |= FMODE_WRITABLE; } } - if (ptr->flags & FMODE_TRUNC) { + if (!NIL_P(string) && (ptr->flags & FMODE_TRUNC)) { rb_str_resize(string, 0); } RB_OBJ_WRITE(self, &ptr->string, string); - if (argc == 1) { + if (argc == 1 && !NIL_P(string)) { ptr->enc = rb_enc_get(string); } else { @@ -481,7 +503,7 @@ strio_set_string(VALUE self, VALUE string) rb_io_taint_check(self); ptr->flags &= ~FMODE_READWRITE; StringValue(string); - ptr->flags = OBJ_FROZEN(string) ? FMODE_READABLE : FMODE_READWRITE; + ptr->flags = readonly_string_p(string) ? FMODE_READABLE : FMODE_READWRITE; ptr->pos = 0; ptr->lineno = 0; RB_OBJ_WRITE(self, &ptr->string, string); @@ -595,6 +617,7 @@ static struct StringIO * strio_to_read(VALUE self) { struct StringIO *ptr = readable(self); + if (NIL_P(ptr->string)) return NULL; if (ptr->pos < RSTRING_LEN(ptr->string)) return ptr; return NULL; } @@ -872,7 +895,7 @@ strio_getc(VALUE self) int len; char *p; - if (pos >= RSTRING_LEN(str)) { + if (NIL_P(str) || pos >= RSTRING_LEN(str)) { return Qnil; } p = RSTRING_PTR(str)+pos; @@ -893,7 +916,7 @@ strio_getbyte(VALUE self) { struct StringIO *ptr = readable(self); int c; - if (ptr->pos >= RSTRING_LEN(ptr->string)) { + if (NIL_P(ptr->string) || ptr->pos >= RSTRING_LEN(ptr->string)) { return Qnil; } c = RSTRING_PTR(ptr->string)[ptr->pos++]; @@ -931,6 +954,7 @@ strio_ungetc(VALUE self, VALUE c) rb_encoding *enc, *enc2; check_modifiable(ptr); + if (NIL_P(ptr->string)) return Qnil; if (NIL_P(c)) return Qnil; if (RB_INTEGER_TYPE_P(c)) { int len, cc = NUM2INT(c); @@ -968,12 +992,13 @@ strio_ungetbyte(VALUE self, VALUE c) struct StringIO *ptr = readable(self); check_modifiable(ptr); + if (NIL_P(ptr->string)) return Qnil; if (NIL_P(c)) return Qnil; if (RB_INTEGER_TYPE_P(c)) { - /* rb_int_and() not visible from exts */ - VALUE v = rb_funcall(c, '&', 1, INT2FIX(0xff)); - const char cc = NUM2INT(v) & 0xFF; - strio_unget_bytes(ptr, &cc, 1); + /* rb_int_and() not visible from exts */ + VALUE v = rb_funcall(c, '&', 1, INT2FIX(0xff)); + const char cc = NUM2INT(v) & 0xFF; + strio_unget_bytes(ptr, &cc, 1); } else { long cl; @@ -1154,41 +1179,41 @@ prepare_getline_args(struct StringIO *ptr, struct getline_arg *arg, int argc, VA break; case 1: - if (!NIL_P(rs) && !RB_TYPE_P(rs, T_STRING)) { - VALUE tmp = rb_check_string_type(rs); + if (!NIL_P(rs) && !RB_TYPE_P(rs, T_STRING)) { + VALUE tmp = rb_check_string_type(rs); if (NIL_P(tmp)) { - limit = NUM2LONG(rs); - rs = rb_rs; + limit = NUM2LONG(rs); + rs = rb_rs; } else { - rs = tmp; + rs = tmp; } } break; case 2: - if (!NIL_P(rs)) StringValue(rs); + if (!NIL_P(rs)) StringValue(rs); if (!NIL_P(lim)) limit = NUM2LONG(lim); break; } - if (!NIL_P(rs)) { - rb_encoding *enc_rs, *enc_io; - enc_rs = rb_enc_get(rs); - enc_io = get_enc(ptr); - if (enc_rs != enc_io && - (rb_enc_str_coderange(rs) != ENC_CODERANGE_7BIT || - (RSTRING_LEN(rs) > 0 && !rb_enc_asciicompat(enc_io)))) { - if (rs == rb_rs) { - rs = rb_enc_str_new(0, 0, enc_io); - rb_str_buf_cat_ascii(rs, "\n"); - rs = rs; - } - else { - rb_raise(rb_eArgError, "encoding mismatch: %s IO with %s RS", - rb_enc_name(enc_io), - rb_enc_name(enc_rs)); - } - } + if (!NIL_P(ptr->string) && !NIL_P(rs)) { + rb_encoding *enc_rs, *enc_io; + enc_rs = rb_enc_get(rs); + enc_io = get_enc(ptr); + if (enc_rs != enc_io && + (rb_enc_str_coderange(rs) != ENC_CODERANGE_7BIT || + (RSTRING_LEN(rs) > 0 && !rb_enc_asciicompat(enc_io)))) { + if (rs == rb_rs) { + rs = rb_enc_str_new(0, 0, enc_io); + rb_str_buf_cat_ascii(rs, "\n"); + rs = rs; + } + else { + rb_raise(rb_eArgError, "encoding mismatch: %s IO with %s RS", + rb_enc_name(enc_io), + rb_enc_name(enc_rs)); + } + } } arg->rs = rs; arg->limit = limit; @@ -1200,9 +1225,9 @@ prepare_getline_args(struct StringIO *ptr, struct getline_arg *arg, int argc, VA keywords[0] = rb_intern_const("chomp"); } rb_get_kwargs(opts, keywords, 0, 1, &vchomp); - if (respect_chomp) { + if (respect_chomp) { arg->chomp = (vchomp != Qundef) && RTEST(vchomp); - } + } } return arg; } @@ -1226,7 +1251,7 @@ strio_getline(struct getline_arg *arg, struct StringIO *ptr) long w = 0; rb_encoding *enc = get_enc(ptr); - if (ptr->pos >= (n = RSTRING_LEN(ptr->string))) { + if (NIL_P(ptr->string) || ptr->pos >= (n = RSTRING_LEN(ptr->string))) { return Qnil; } s = RSTRING_PTR(ptr->string); @@ -1242,7 +1267,7 @@ strio_getline(struct getline_arg *arg, struct StringIO *ptr) str = strio_substr(ptr, ptr->pos, e - s - w, enc); } else if ((n = RSTRING_LEN(str)) == 0) { - const char *paragraph_end = NULL; + const char *paragraph_end = NULL; p = s; while (p[(p + 1 < e) && (*p == '\r') && 0] == '\n') { p += *p == '\r'; @@ -1252,18 +1277,18 @@ strio_getline(struct getline_arg *arg, struct StringIO *ptr) } s = p; while ((p = memchr(p, '\n', e - p)) && (p != e)) { - p++; - if (!((p < e && *p == '\n') || - (p + 1 < e && *p == '\r' && *(p+1) == '\n'))) { - continue; - } - paragraph_end = p - ((*(p-2) == '\r') ? 2 : 1); - while ((p < e && *p == '\n') || - (p + 1 < e && *p == '\r' && *(p+1) == '\n')) { - p += (*p == '\r') ? 2 : 1; - } - e = p; - break; + p++; + if (!((p < e && *p == '\n') || + (p + 1 < e && *p == '\r' && *(p+1) == '\n'))) { + continue; + } + paragraph_end = p - ((*(p-2) == '\r') ? 2 : 1); + while ((p < e && *p == '\n') || + (p + 1 < e && *p == '\r' && *(p+1) == '\n')) { + p += (*p == '\r') ? 2 : 1; + } + e = p; + break; } if (arg->chomp && paragraph_end) { w = e - paragraph_end; @@ -1323,6 +1348,7 @@ strio_gets(int argc, VALUE *argv, VALUE self) VALUE str; if (prepare_getline_args(ptr, &arg, argc, argv)->limit == 0) { + if (NIL_P(ptr->string)) return Qnil; return rb_enc_str_new(0, 0, get_enc(ptr)); } @@ -1437,6 +1463,7 @@ strio_write(VALUE self, VALUE str) if (!RB_TYPE_P(str, T_STRING)) str = rb_obj_as_string(str); enc = get_enc(ptr); + if (!enc) return 0; enc2 = rb_enc_get(str); if (enc != enc2 && enc != ascii8bit && enc != (usascii = rb_usascii_encoding())) { VALUE converted = rb_str_conv_enc(str, enc2, enc); @@ -1509,10 +1536,12 @@ strio_putc(VALUE self, VALUE ch) check_modifiable(ptr); if (RB_TYPE_P(ch, T_STRING)) { + if (NIL_P(ptr->string)) return ch; str = rb_str_substr(ch, 0, 1); } else { char c = NUM2CHR(ch); + if (NIL_P(ptr->string)) return ch; str = rb_str_new(&c, 1); } strio_write(self, str); @@ -1555,7 +1584,8 @@ strio_read(int argc, VALUE *argv, VALUE self) if (len < 0) { rb_raise(rb_eArgError, "negative length %ld given", len); } - if (len > 0 && ptr->pos >= RSTRING_LEN(ptr->string)) { + if (len > 0 && + (NIL_P(ptr->string) || ptr->pos >= RSTRING_LEN(ptr->string))) { if (!NIL_P(str)) rb_str_resize(str, 0); return Qnil; } @@ -1564,6 +1594,7 @@ strio_read(int argc, VALUE *argv, VALUE self) } /* fall through */ case 0: + if (NIL_P(ptr->string)) return Qnil; len = RSTRING_LEN(ptr->string); if (len <= ptr->pos) { rb_encoding *enc = get_enc(ptr); @@ -1581,7 +1612,7 @@ strio_read(int argc, VALUE *argv, VALUE self) } break; default: - rb_error_arity(argc, 0, 2); + rb_error_arity(argc, 0, 2); } if (NIL_P(str)) { rb_encoding *enc = binary ? rb_ascii8bit_encoding() : get_enc(ptr); @@ -1617,28 +1648,28 @@ strio_pread(int argc, VALUE *argv, VALUE self) long offset = NUM2LONG(rb_offset); if (len < 0) { - rb_raise(rb_eArgError, "negative string size (or size too big): %" PRIsVALUE, rb_len); + rb_raise(rb_eArgError, "negative string size (or size too big): %" PRIsVALUE, rb_len); } if (len == 0) { - if (NIL_P(rb_buf)) { - return rb_str_new("", 0); - } - return rb_buf; + if (NIL_P(rb_buf)) { + return rb_str_new("", 0); + } + return rb_buf; } if (offset < 0) { - rb_syserr_fail_str(EINVAL, rb_sprintf("pread: Invalid offset argument: %" PRIsVALUE, rb_offset)); + rb_syserr_fail_str(EINVAL, rb_sprintf("pread: Invalid offset argument: %" PRIsVALUE, rb_offset)); } struct StringIO *ptr = readable(self); if (offset >= RSTRING_LEN(ptr->string)) { - rb_eof_error(); + rb_eof_error(); } if (NIL_P(rb_buf)) { - return strio_substr(ptr, offset, len, rb_ascii8bit_encoding()); + return strio_substr(ptr, offset, len, rb_ascii8bit_encoding()); } long rest = RSTRING_LEN(ptr->string) - offset; @@ -1698,8 +1729,14 @@ strio_read_nonblock(int argc, VALUE *argv, VALUE self) return val; } +/* + * See IO#write + */ #define strio_syswrite rb_io_write +/* + * See IO#write_nonblock + */ static VALUE strio_syswrite_nonblock(int argc, VALUE *argv, VALUE self) { @@ -1727,7 +1764,7 @@ strio_size(VALUE self) { VALUE string = StringIO(self)->string; if (NIL_P(string)) { - rb_raise(rb_eIOError, "not opened"); + return INT2FIX(0); } return ULONG2NUM(RSTRING_LEN(string)); } @@ -1744,10 +1781,12 @@ strio_truncate(VALUE self, VALUE len) { VALUE string = writable(self)->string; long l = NUM2LONG(len); - long plen = RSTRING_LEN(string); + long plen; if (l < 0) { error_inval("negative length"); } + if (NIL_P(string)) return 0; + plen = RSTRING_LEN(string); rb_str_resize(string, l); if (plen < l) { MEMZERO(RSTRING_PTR(string) + plen, char, l - plen); @@ -1818,13 +1857,22 @@ strio_set_encoding(int argc, VALUE *argv, VALUE self) } } ptr->enc = enc; - if (WRITABLE(self)) { + if (!NIL_P(ptr->string) && WRITABLE(self)) { rb_enc_associate(ptr->string, enc); } return self; } +/* + * call-seq: + * strio.set_encoding_by_bom => strio or nil + * + * Sets the encoding according to the BOM (Byte Order Mark) in the + * string. + * + * Returns +self+ if the BOM is found, otherwise +nil. + */ static VALUE strio_set_encoding_by_bom(VALUE self) { @@ -1857,10 +1905,15 @@ Init_stringio(void) VALUE StringIO = rb_define_class("StringIO", rb_cObject); + /* The version string */ rb_define_const(StringIO, "VERSION", rb_str_new_cstr(STRINGIO_VERSION)); rb_include_module(StringIO, rb_mEnumerable); rb_define_alloc_func(StringIO, strio_s_allocate); + + /* Maximum length that a StringIO instance can hold */ + rb_define_const(StringIO, "MAX_LENGTH", LONG2NUM(LONG_MAX)); + rb_define_singleton_method(StringIO, "new", strio_s_new, -1); rb_define_singleton_method(StringIO, "open", strio_s_open, -1); rb_define_method(StringIO, "initialize", strio_initialize, -1); diff --git a/ext/stringio/stringio.gemspec b/ext/stringio/stringio.gemspec index 8c950f8ff96fb6..b40b7fc824f422 100644 --- a/ext/stringio/stringio.gemspec +++ b/ext/stringio/stringio.gemspec @@ -28,6 +28,12 @@ Gem::Specification.new do |s| s.extensions = ["ext/stringio/extconf.rb"] s.files += ["ext/stringio/extconf.rb", "ext/stringio/stringio.c"] end + + s.extra_rdoc_files = [ + ".document", ".rdoc_options", "COPYING", "LICENSE.txt", + "NEWS.md", "README.md", "docs/io.rb", "ext/stringio/.document", + ] + s.homepage = "https://github.com/ruby/stringio" s.licenses = ["Ruby", "BSD-2-Clause"] s.required_ruby_version = ">= 2.7" diff --git a/ext/win32ole/win32ole.gemspec b/ext/win32ole/win32ole.gemspec index 9c137a5d700610..625916ae1765cb 100644 --- a/ext/win32ole/win32ole.gemspec +++ b/ext/win32ole/win32ole.gemspec @@ -23,9 +23,11 @@ Gem::Specification.new do |spec| spec.metadata["homepage_uri"] = spec.homepage spec.metadata["source_code_uri"] = spec.homepage - spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do - `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } - end + pathspecs = %W[ + :(exclude,literal)#{File.basename(__FILE__)} + :^/bin/ :^/test/ :^/rakelib/ :^/.git* :^/Gemfile* :^/Rakefile* + ] + spec.files = IO.popen(%w[git ls-files -z --] + pathspecs, chdir: __dir__, err: IO::NULL, exception: false, &:read).split("\x0") spec.bindir = "exe" spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } spec.require_paths = ["lib"] diff --git a/gc.c b/gc.c index 3ab971e38b8cc9..33384625d976e9 100644 --- a/gc.c +++ b/gc.c @@ -445,8 +445,6 @@ typedef struct { size_t oldmalloc_limit_min; size_t oldmalloc_limit_max; double oldmalloc_limit_growth_factor; - - VALUE gc_stress; } ruby_gc_params_t; static ruby_gc_params_t gc_params = { @@ -468,8 +466,6 @@ static ruby_gc_params_t gc_params = { GC_OLDMALLOC_LIMIT_MIN, GC_OLDMALLOC_LIMIT_MAX, GC_OLDMALLOC_LIMIT_GROWTH_FACTOR, - - FALSE, }; /* GC_DEBUG: @@ -741,11 +737,6 @@ struct heap_page_body { /* RVALUE values[]; */ }; -struct gc_list { - VALUE *varptr; - struct gc_list *next; -}; - #define STACK_CHUNK_SIZE 500 typedef struct stack_chunk { @@ -914,7 +905,6 @@ typedef struct rb_objspace { size_t weak_references_count; size_t retained_weak_references_count; } profile; - struct gc_list *global_list; VALUE gc_stress_mode; @@ -965,7 +955,7 @@ typedef struct rb_objspace { rb_postponed_job_handle_t finalize_deferred_pjob; #ifdef RUBY_ASAN_ENABLED - rb_execution_context_t *marking_machine_context_ec; + const rb_execution_context_t *marking_machine_context_ec; #endif } rb_objspace_t; @@ -1161,10 +1151,6 @@ RVALUE_AGE_SET(VALUE obj, int age) if (unless_objspace_vm) objspace = unless_objspace_vm->objspace; \ else /* return; or objspace will be warned uninitialized */ -#define ruby_initial_gc_stress gc_params.gc_stress - -VALUE *ruby_initial_gc_stress_ptr = &ruby_initial_gc_stress; - #define malloc_limit objspace->malloc_params.limit #define malloc_increase objspace->malloc_params.increase #define malloc_allocated_size objspace->malloc_params.allocated_size @@ -1180,7 +1166,6 @@ VALUE *ruby_initial_gc_stress_ptr = &ruby_initial_gc_stress; #define during_gc objspace->flags.during_gc #define finalizing objspace->atomic_flags.finalizing #define finalizer_table objspace->finalizer_table -#define global_list objspace->global_list #define ruby_gc_stressful objspace->flags.gc_stressful #define ruby_gc_stress_mode objspace->gc_stress_mode #if GC_DEBUG_STRESS_TO_CLASS @@ -1439,7 +1424,6 @@ NO_SANITIZE("memory", static inline int is_pointer_to_heap(rb_objspace_t *objspa static size_t obj_memsize_of(VALUE obj, int use_all_types); static void gc_verify_internal_consistency(rb_objspace_t *objspace); -static void gc_stress_set(rb_objspace_t *objspace, VALUE flag); static VALUE gc_disable_no_rest(rb_objspace_t *); static double getrusage_time(void); @@ -1958,37 +1942,12 @@ calloc1(size_t n) return calloc(1, n); } -rb_objspace_t * -rb_objspace_alloc(void) -{ - rb_objspace_t *objspace = calloc1(sizeof(rb_objspace_t)); - objspace->flags.measure_gc = 1; - malloc_limit = gc_params.malloc_limit_min; - objspace->finalize_deferred_pjob = rb_postponed_job_preregister(0, gc_finalize_deferred, objspace); - if (objspace->finalize_deferred_pjob == POSTPONED_JOB_HANDLE_INVALID) { - rb_bug("Could not preregister postponed job for GC"); - } - - for (int i = 0; i < SIZE_POOL_COUNT; i++) { - rb_size_pool_t *size_pool = &size_pools[i]; +static VALUE initial_stress = Qfalse; - size_pool->slot_size = (1 << i) * BASE_SLOT_SIZE; - - ccan_list_head_init(&SIZE_POOL_EDEN_HEAP(size_pool)->pages); - ccan_list_head_init(&SIZE_POOL_TOMB_HEAP(size_pool)->pages); - } - - rb_darray_make_without_gc(&objspace->weak_references, 0); - - dont_gc_on(); - -#if USE_MMTK - if (rb_mmtk_enabled_p()) { - rb_mmtk_main_thread_init(); - } -#endif - - return objspace; +void +rb_gc_initial_stress_set(VALUE flag) +{ + initial_stress = flag; } static void free_stack_chunks(mark_stack_t *); @@ -2005,13 +1964,6 @@ rb_objspace_free(rb_objspace_t *objspace) free(objspace->profile.records); objspace->profile.records = NULL; - if (global_list) { - struct gc_list *list, *next; - for (list = global_list; list; list = next) { - next = list->next; - xfree(list); - } - } if (heap_pages_sorted) { size_t i; size_t total_heap_pages = heap_allocated_pages; @@ -2036,7 +1988,7 @@ rb_objspace_free(rb_objspace_t *objspace) free_stack_chunks(&objspace->mark_stack); mark_stack_free_cache(&objspace->mark_stack); - rb_darray_free_without_gc(objspace->weak_references); + rb_darray_free(objspace->weak_references); free(objspace); } @@ -2191,6 +2143,40 @@ heap_page_free(rb_objspace_t *objspace, struct heap_page *page) free(page); } +static void * +rb_aligned_malloc(size_t alignment, size_t size) +{ + /* alignment must be a power of 2 */ + GC_ASSERT(((alignment - 1) & alignment) == 0); + GC_ASSERT(alignment % sizeof(void*) == 0); + + void *res; + +#if defined __MINGW32__ + res = __mingw_aligned_malloc(size, alignment); +#elif defined _WIN32 + void *_aligned_malloc(size_t, size_t); + res = _aligned_malloc(size, alignment); +#elif defined(HAVE_POSIX_MEMALIGN) + if (posix_memalign(&res, alignment, size) != 0) { + return NULL; + } +#elif defined(HAVE_MEMALIGN) + res = memalign(alignment, size); +#else + char* aligned; + res = malloc(alignment + size + sizeof(void*)); + aligned = (char*)res + alignment + sizeof(void*); + aligned -= ((VALUE)aligned & (alignment - 1)); + ((void**)aligned)[-1] = res; + res = (void*)aligned; +#endif + + GC_ASSERT((uintptr_t)res % alignment == 0); + + return res; +} + static void heap_pages_free_unused_pages(rb_objspace_t *objspace) { @@ -2787,6 +2773,44 @@ rb_gc_size_allocatable_p(size_t size) return size <= size_pool_slot_size(SIZE_POOL_COUNT - 1); } +static size_t size_pool_sizes[SIZE_POOL_COUNT + 1] = { 0 }; + +size_t * +rb_gc_size_pool_sizes(void) +{ + if (size_pool_sizes[0] == 0) { + for (unsigned char i = 0; i < SIZE_POOL_COUNT; i++) { + size_pool_sizes[i] = rb_size_pool_slot_size(i); + } + } + + return size_pool_sizes; +} + +size_t +rb_gc_size_pool_id_for_size(size_t size) +{ + size += RVALUE_OVERHEAD; + + size_t slot_count = CEILDIV(size, BASE_SLOT_SIZE); + + /* size_pool_idx is ceil(log2(slot_count)) */ + size_t size_pool_idx = 64 - nlz_int64(slot_count - 1); + + if (size_pool_idx >= SIZE_POOL_COUNT) { + rb_bug("rb_gc_size_pool_id_for_size: allocation size too large " + "(size=%"PRIuSIZE"u, size_pool_idx=%"PRIuSIZE"u)", size, size_pool_idx); + } + +#if RGENGC_CHECK_MODE + rb_objspace_t *objspace = &rb_objspace; + GC_ASSERT(size <= (size_t)size_pools[size_pool_idx].slot_size); + if (size_pool_idx > 0) GC_ASSERT(size > (size_t)size_pools[size_pool_idx - 1].slot_size); +#endif + + return size_pool_idx; +} + static inline VALUE ractor_cache_allocate_slot(rb_objspace_t *objspace, rb_ractor_newobj_cache_t *cache, size_t size_pool_idx) @@ -2876,30 +2900,6 @@ newobj_fill(VALUE obj, VALUE v1, VALUE v2, VALUE v3) return obj; } -static inline size_t -size_pool_idx_for_size(size_t size) -{ - size += RVALUE_OVERHEAD; - - size_t slot_count = CEILDIV(size, BASE_SLOT_SIZE); - - /* size_pool_idx is ceil(log2(slot_count)) */ - size_t size_pool_idx = 64 - nlz_int64(slot_count - 1); - - if (size_pool_idx >= SIZE_POOL_COUNT) { - rb_bug("size_pool_idx_for_size: allocation size too large " - "(size=%"PRIuSIZE"u, size_pool_idx=%"PRIuSIZE"u)", size, size_pool_idx); - } - -#if RGENGC_CHECK_MODE - rb_objspace_t *objspace = &rb_objspace; - GC_ASSERT(size <= (size_t)size_pools[size_pool_idx].slot_size); - if (size_pool_idx > 0) GC_ASSERT(size > (size_t)size_pools[size_pool_idx - 1].slot_size); -#endif - - return size_pool_idx; -} - static VALUE newobj_alloc(rb_objspace_t *objspace, rb_ractor_newobj_cache_t *cache, size_t size_pool_idx, bool vm_locked) { @@ -3045,11 +3045,7 @@ newobj_of(rb_ractor_t *cr, VALUE klass, VALUE flags, VALUE v1, VALUE v2, VALUE v } } - size_t size_pool_idx = size_pool_idx_for_size(alloc_size); - - if (SHAPE_IN_BASIC_FLAGS || (flags & RUBY_T_MASK) == T_OBJECT) { - flags |= (VALUE)size_pool_idx << SHAPE_FLAG_SHIFT; - } + size_t size_pool_idx = rb_gc_size_pool_id_for_size(alloc_size); #if USE_MMTK if (rb_mmtk_enabled_p()) { @@ -3114,25 +3110,6 @@ rb_wb_protected_newobj_of(rb_execution_context_t *ec, VALUE klass, VALUE flags, return newobj_of(rb_ec_ractor_ptr(ec), klass, flags, 0, 0, 0, TRUE, size); } -/* for compatibility */ - -VALUE -rb_newobj(void) -{ - return newobj_of(GET_RACTOR(), 0, T_NONE, 0, 0, 0, FALSE, RVALUE_SIZE); -} - -VALUE -rb_newobj_of(VALUE klass, VALUE flags) -{ - if ((flags & RUBY_T_MASK) == T_OBJECT) { - return rb_class_allocate_instance(klass); - } - else { - return newobj_of(GET_RACTOR(), klass, flags & ~FL_WB_PROTECTED, 0, 0, 0, flags & FL_WB_PROTECTED, RVALUE_SIZE); - } -} - #define UNEXPECTED_NODE(func) \ rb_bug(#func"(): GC does not handle T_NODE 0x%x(%p) 0x%"PRIxVALUE, \ BUILTIN_TYPE(obj), (void*)(obj), RBASIC(obj)->flags) @@ -3793,10 +3770,37 @@ static const struct st_hash_type object_id_hash_type = { object_id_hash, }; -void -Init_heap(void) +rb_objspace_t * +rb_objspace_alloc(void) { - rb_objspace_t *objspace = &rb_objspace; + rb_objspace_t *objspace = calloc1(sizeof(rb_objspace_t)); + ruby_current_vm_ptr->objspace = objspace; + + objspace->flags.gc_stressful = RTEST(initial_stress); + objspace->gc_stress_mode = initial_stress; + + objspace->flags.measure_gc = 1; + malloc_limit = gc_params.malloc_limit_min; + objspace->finalize_deferred_pjob = rb_postponed_job_preregister(0, gc_finalize_deferred, objspace); + if (objspace->finalize_deferred_pjob == POSTPONED_JOB_HANDLE_INVALID) { + rb_bug("Could not preregister postponed job for GC"); + } + + for (int i = 0; i < SIZE_POOL_COUNT; i++) { + rb_size_pool_t *size_pool = &size_pools[i]; + + size_pool->slot_size = (1 << i) * BASE_SLOT_SIZE; + + ccan_list_head_init(&SIZE_POOL_EDEN_HEAP(size_pool)->pages); + ccan_list_head_init(&SIZE_POOL_TOMB_HEAP(size_pool)->pages); + } + + rb_darray_make(&objspace->weak_references, 0); + + // TODO: debug why on Windows Ruby crashes on boot when GC is on. +#ifdef _WIN32 + dont_gc_on(); +#endif #if defined(INIT_HEAP_PAGE_ALLOC_USE_MMAP) /* Need to determine if we can use mmap at runtime. */ @@ -3830,16 +3834,15 @@ Init_heap(void) } #endif +#if USE_MMTK + if (rb_mmtk_enabled_p()) { + rb_mmtk_main_thread_init(); + } +#endif + objspace->profile.invoke_time = getrusage_time(); finalizer_table = st_init_numtable(); -} - -void -Init_gc_stress(void) -{ - rb_objspace_t *objspace = &rb_objspace; - - gc_stress_set(objspace, ruby_initial_gc_stress); + return objspace; } typedef int each_obj_callback(void *, void *, size_t, void *); @@ -4019,6 +4022,7 @@ objspace_each_objects(rb_objspace_t *objspace, each_obj_callback *callback, void objspace_each_exec(protected, &each_obj_data); } +#if GC_CAN_COMPILE_COMPACTION static void objspace_each_pages(rb_objspace_t *objspace, each_page_callback *callback, void *data, bool protected) { @@ -4030,6 +4034,7 @@ objspace_each_pages(rb_objspace_t *objspace, each_page_callback *callback, void }; objspace_each_exec(protected, &each_obj_data); } +#endif struct os_each_struct { size_t num; @@ -4920,8 +4925,8 @@ id2ref(VALUE objid) if (ptr == Qtrue) return Qtrue; if (ptr == Qfalse) return Qfalse; if (NIL_P(ptr)) return Qnil; - if (FIXNUM_P(ptr)) return (VALUE)ptr; - if (FLONUM_P(ptr)) return (VALUE)ptr; + if (FIXNUM_P(ptr)) return ptr; + if (FLONUM_P(ptr)) return ptr; ptr = obj_id_to_ref(objid); if ((ptr % sizeof(RVALUE)) == (4 << 2)) { @@ -6854,11 +6859,11 @@ gc_mark_machine_stack_location_maybe(rb_objspace_t *objspace, VALUE obj) gc_mark_maybe(objspace, obj); #ifdef RUBY_ASAN_ENABLED - rb_execution_context_t *ec = objspace->marking_machine_context_ec; + const rb_execution_context_t *ec = objspace->marking_machine_context_ec; void *fake_frame_start; void *fake_frame_end; bool is_fake_frame = asan_get_fake_stack_extents( - ec->thread_ptr->asan_fake_stack_handle, obj, + ec->machine.asan_fake_stack_handle, obj, ec->machine.stack_start, ec->machine.stack_end, &fake_frame_start, &fake_frame_end ); @@ -6943,13 +6948,25 @@ mark_current_machine_context(rb_objspace_t *objspace, rb_execution_context_t *ec #endif void -rb_gc_mark_machine_stack(const rb_execution_context_t *ec) +rb_gc_mark_machine_context(const rb_execution_context_t *ec) { + rb_objspace_t *objspace = &rb_objspace; +#ifdef RUBY_ASAN_ENABLED + objspace->marking_machine_context_ec = ec; +#endif + VALUE *stack_start, *stack_end; + GET_STACK_BOUNDS(stack_start, stack_end, 0); RUBY_DEBUG_LOG("ec->th:%u stack_start:%p stack_end:%p", rb_ec_thread_ptr(ec)->serial, stack_start, stack_end); - rb_gc_mark_locations(stack_start, stack_end); + each_stack_location(objspace, ec, stack_start, stack_end, gc_mark_machine_stack_location_maybe); + int num_regs = sizeof(ec->machine.regs)/(sizeof(VALUE)); + each_location(objspace, (VALUE*)&ec->machine.regs, num_regs, gc_mark_machine_stack_location_maybe); + +#ifdef RUBY_ASAN_ENABLED + objspace->marking_machine_context_ec = NULL; +#endif } static void @@ -7242,7 +7259,11 @@ rb_gc_mark_weak(VALUE *ptr) rgengc_check_relation(objspace, obj); - rb_darray_append_without_gc(&objspace->weak_references, ptr); + DURING_GC_COULD_MALLOC_REGION_START(); + { + rb_darray_append(&objspace->weak_references, ptr); + } + DURING_GC_COULD_MALLOC_REGION_END(); objspace->profile.weak_references_count++; } @@ -7620,7 +7641,6 @@ show_mark_ticks(void) static void gc_mark_roots(rb_objspace_t *objspace, const char **categoryp) { - struct gc_list *list; rb_execution_context_t *ec; rb_vm_t *vm; @@ -7700,10 +7720,6 @@ gc_mark_roots(rb_objspace_t *objspace, const char **categoryp) #endif /* mark protected global variables */ - MARK_CHECKPOINT("global_list"); - for (list = global_list; list; list = list->next) { - gc_mark_maybe(objspace, *list->varptr); - } MARK_CHECKPOINT("end_proc"); rb_mark_end_proc(); @@ -8482,7 +8498,12 @@ gc_update_weak_references(rb_objspace_t *objspace) objspace->profile.retained_weak_references_count = retained_weak_references_count; rb_darray_clear(objspace->weak_references); - rb_darray_resize_capa_without_gc(&objspace->weak_references, retained_weak_references_count); + + DURING_GC_COULD_MALLOC_REGION_START(); + { + rb_darray_resize_capa(&objspace->weak_references, retained_weak_references_count); + } + DURING_GC_COULD_MALLOC_REGION_END(); } static void @@ -8642,7 +8663,7 @@ gc_compact_destination_pool(rb_objspace_t *objspace, rb_size_pool_t *src_pool, V } if (rb_gc_size_allocatable_p(obj_size)){ - idx = size_pool_idx_for_size(obj_size); + idx = rb_gc_size_pool_id_for_size(obj_size); } return &size_pools[idx]; } @@ -8665,7 +8686,7 @@ gc_compact_move(rb_objspace_t *objspace, rb_heap_t *heap, rb_size_pool_t *size_p if (RB_TYPE_P(src, T_OBJECT)) { orig_shape = rb_shape_get_shape(src); if (dheap != heap && !rb_shape_obj_too_complex(src)) { - rb_shape_t *initial_shape = rb_shape_get_shape_by_id((shape_id_t)((dest_pool - size_pools) + SIZE_POOL_COUNT)); + rb_shape_t *initial_shape = rb_shape_get_shape_by_id((shape_id_t)((dest_pool - size_pools) + FIRST_T_OBJECT_SHAPE_ID)); new_shape = rb_shape_traverse_from_new_root(initial_shape, orig_shape); if (!new_shape) { @@ -9252,27 +9273,18 @@ rb_gc_writebarrier_remember(VALUE obj) } void -rb_copy_wb_protected_attribute(VALUE dest, VALUE obj) +rb_gc_copy_attributes(VALUE dest, VALUE obj) { - rb_objspace_t *objspace = &rb_objspace; - - if (RVALUE_WB_UNPROTECTED(obj) && !RVALUE_WB_UNPROTECTED(dest)) { - if (!RVALUE_OLD_P(dest)) { - MARK_IN_BITMAP(GET_HEAP_WB_UNPROTECTED_BITS(dest), dest); - RVALUE_AGE_RESET(dest); - } - else { - RVALUE_DEMOTE(objspace, dest); - } +#if USE_MMTK + if (!rb_mmtk_enabled_p()) { +#endif + if (RVALUE_WB_UNPROTECTED(obj)) { + rb_gc_writebarrier_unprotect(dest); } - - check_rvalue_consistency(dest); -} - -VALUE -rb_obj_rgengc_promoted_p(VALUE obj) -{ - return RBOOL(OBJ_PROMOTED(obj)); +#if USE_MMTK + } +#endif + rb_gc_copy_finalizer(dest, obj); } size_t @@ -9333,12 +9345,6 @@ rb_gc_ractor_newobj_cache_clear(rb_ractor_newobj_cache_t *newobj_cache) } } -void -rb_gc_force_recycle(VALUE obj) -{ - /* no-op */ -} - void rb_gc_register_mark_object(VALUE obj) { @@ -9351,15 +9357,14 @@ rb_gc_register_mark_object(VALUE obj) void rb_gc_register_address(VALUE *addr) { - rb_objspace_t *objspace = &rb_objspace; - struct gc_list *tmp; + rb_vm_t *vm = GET_VM(); VALUE obj = *addr; - tmp = ALLOC(struct gc_list); - tmp->next = global_list; + struct global_object_list *tmp = ALLOC(struct global_object_list); + tmp->next = vm->global_object_list; tmp->varptr = addr; - global_list = tmp; + vm->global_object_list = tmp; /* * Because some C extensions have assignment-then-register bugs, @@ -9376,17 +9381,17 @@ rb_gc_register_address(VALUE *addr) void rb_gc_unregister_address(VALUE *addr) { - rb_objspace_t *objspace = &rb_objspace; - struct gc_list *tmp = global_list; + rb_vm_t *vm = GET_VM(); + struct global_object_list *tmp = vm->global_object_list; if (tmp->varptr == addr) { - global_list = tmp->next; + vm->global_object_list = tmp->next; xfree(tmp); return; } while (tmp->next) { if (tmp->next->varptr == addr) { - struct gc_list *t = tmp->next; + struct global_object_list *t = tmp->next; tmp->next = tmp->next->next; xfree(t); @@ -9402,8 +9407,6 @@ rb_global_variable(VALUE *var) rb_gc_register_address(var); } -#define GC_NOTIFY 0 - enum { gc_stress_no_major, gc_stress_no_immediate_sweep, @@ -9552,7 +9555,7 @@ gc_start(rb_objspace_t *objspace, unsigned int reason) /* reason may be clobbered, later, so keep set immediate_sweep here */ objspace->flags.immediate_sweep = !!(reason & GPR_FLAG_IMMEDIATE_SWEEP); - if (!heap_allocated_pages) return FALSE; /* heap is not ready */ + if (!heap_allocated_pages) return TRUE; /* heap is not ready */ if (!(reason & GPR_FLAG_METHOD) && !ready_to_gc(objspace)) return TRUE; /* GC is not allowed */ GC_ASSERT(gc_mode(objspace) == gc_mode_none); @@ -9956,18 +9959,20 @@ gc_set_candidate_object_i(void *vstart, void *vend, size_t stride, void *data) rb_objspace_t *objspace = &rb_objspace; VALUE v = (VALUE)vstart; for (; v != (VALUE)vend; v += stride) { - switch (BUILTIN_TYPE(v)) { - case T_NONE: - case T_ZOMBIE: - break; - case T_STRING: - // precompute the string coderange. This both save time for when it will be - // eventually needed, and avoid mutating heap pages after a potential fork. - rb_enc_str_coderange(v); - // fall through - default: - if (!RVALUE_OLD_P(v) && !RVALUE_WB_UNPROTECTED(v)) { - RVALUE_AGE_SET_CANDIDATE(objspace, v); + asan_unpoisoning_object(v) { + switch (BUILTIN_TYPE(v)) { + case T_NONE: + case T_ZOMBIE: + break; + case T_STRING: + // precompute the string coderange. This both save time for when it will be + // eventually needed, and avoid mutating heap pages after a potential fork. + rb_enc_str_coderange(v); + // fall through + default: + if (!RVALUE_OLD_P(v) && !RVALUE_WB_UNPROTECTED(v)) { + RVALUE_AGE_SET_CANDIDATE(objspace, v); + } } } } @@ -10169,20 +10174,26 @@ gc_move(rb_objspace_t *objspace, VALUE scan, VALUE free, size_t src_slot_size, s DURING_GC_COULD_MALLOC_REGION_END(); } - st_data_t srcid = (st_data_t)src, id; + if (FL_TEST((VALUE)src, FL_SEEN_OBJ_ID)) { + /* If the source object's object_id has been seen, we need to update + * the object to object id mapping. */ + st_data_t srcid = (st_data_t)src, id; - /* If the source object's object_id has been seen, we need to update - * the object to object id mapping. */ - if (st_lookup(objspace->obj_to_id_tbl, srcid, &id)) { gc_report(4, objspace, "Moving object with seen id: %p -> %p\n", (void *)src, (void *)dest); /* Resizing the st table could cause a malloc */ DURING_GC_COULD_MALLOC_REGION_START(); { - st_delete(objspace->obj_to_id_tbl, &srcid, 0); + if (!st_delete(objspace->obj_to_id_tbl, &srcid, &id)) { + rb_bug("gc_move: object ID seen, but not in mapping table: %s", obj_info((VALUE)src)); + } + st_insert(objspace->obj_to_id_tbl, (st_data_t)dest, id); } DURING_GC_COULD_MALLOC_REGION_END(); } + else { + GC_ASSERT(!st_lookup(objspace->obj_to_id_tbl, (st_data_t)src, NULL)); + } /* Move the object */ memcpy(dest, src, MIN(src_slot_size, slot_size)); @@ -10895,9 +10906,7 @@ gc_update_object_references(rb_objspace_t *objspace, VALUE obj) break; case T_SYMBOL: - if (DYNAMIC_SYM_P((VALUE)any)) { - UPDATE_IF_MOVED(objspace, RSYMBOL(any)->fstr); - } + UPDATE_IF_MOVED(objspace, RSYMBOL(any)->fstr); break; case T_FLOAT: @@ -11114,7 +11123,9 @@ gc_compact_stats(VALUE self) static void root_obj_check_moved_i(const char *category, VALUE obj, void *data) { - if (gc_object_moved_p(&rb_objspace, obj)) { + rb_objspace_t *objspace = data; + + if (gc_object_moved_p(objspace, obj)) { rb_bug("ROOT %s points to MOVED: %p -> %s", category, (void *)obj, obj_info(rb_gc_location(obj))); } } @@ -11131,9 +11142,11 @@ reachable_object_check_moved_i(VALUE ref, void *data) static int heap_check_moved_i(void *vstart, void *vend, size_t stride, void *data) { + rb_objspace_t *objspace = data; + VALUE v = (VALUE)vstart; for (; v != (VALUE)vend; v += stride) { - if (gc_object_moved_p(&rb_objspace, v)) { + if (gc_object_moved_p(objspace, v)) { /* Moved object still on the heap, something may have a reference. */ } else { @@ -11249,7 +11262,7 @@ gc_verify_compaction_references(rb_execution_context_t *ec, VALUE self, VALUE do /* Find out which pool has the most pages */ size_t max_existing_pages = 0; - for(int i = 0; i < SIZE_POOL_COUNT; i++) { + for (int i = 0; i < SIZE_POOL_COUNT; i++) { rb_size_pool_t *size_pool = &size_pools[i]; rb_heap_t *heap = SIZE_POOL_EDEN_HEAP(size_pool); max_existing_pages = MAX(max_existing_pages, heap->total_pages); @@ -11298,8 +11311,8 @@ gc_verify_compaction_references(rb_execution_context_t *ec, VALUE self, VALUE do gc_start_internal(NULL, self, Qtrue, Qtrue, Qtrue, Qtrue); - objspace_reachable_objects_from_root(objspace, root_obj_check_moved_i, NULL); - objspace_each_objects(objspace, heap_check_moved_i, NULL, TRUE); + objspace_reachable_objects_from_root(objspace, root_obj_check_moved_i, objspace); + objspace_each_objects(objspace, heap_check_moved_i, objspace, TRUE); objspace->rcompactor.compare_func = NULL; return gc_compact_stats(self); @@ -11906,18 +11919,14 @@ gc_stress_get(rb_execution_context_t *ec, VALUE self) return ruby_gc_stress_mode; } -static void -gc_stress_set(rb_objspace_t *objspace, VALUE flag) -{ - objspace->flags.gc_stressful = RTEST(flag); - objspace->gc_stress_mode = flag; -} - static VALUE gc_stress_set_m(rb_execution_context_t *ec, VALUE self, VALUE flag) { rb_objspace_t *objspace = &rb_objspace; - gc_stress_set(objspace, flag); + + objspace->flags.gc_stressful = RTEST(flag); + objspace->gc_stress_mode = flag; + return flag; } @@ -12423,40 +12432,6 @@ rb_memerror(void) EC_JUMP_TAG(ec, TAG_RAISE); } -void * -rb_aligned_malloc(size_t alignment, size_t size) -{ - /* alignment must be a power of 2 */ - GC_ASSERT(((alignment - 1) & alignment) == 0); - GC_ASSERT(alignment % sizeof(void*) == 0); - - void *res; - -#if defined __MINGW32__ - res = __mingw_aligned_malloc(size, alignment); -#elif defined _WIN32 - void *_aligned_malloc(size_t, size_t); - res = _aligned_malloc(size, alignment); -#elif defined(HAVE_POSIX_MEMALIGN) - if (posix_memalign(&res, alignment, size) != 0) { - return NULL; - } -#elif defined(HAVE_MEMALIGN) - res = memalign(alignment, size); -#else - char* aligned; - res = malloc(alignment + size + sizeof(void*)); - aligned = (char*)res + alignment + sizeof(void*); - aligned -= ((VALUE)aligned & (alignment - 1)); - ((void**)aligned)[-1] = res; - res = (void*)aligned; -#endif - - GC_ASSERT((uintptr_t)res % alignment == 0); - - return res; -} - static void rb_aligned_free(void *ptr, size_t size) { @@ -13902,7 +13877,6 @@ str_len_no_raise(VALUE str) memcpy(buff + pos, (s), rb_strlen_lit(s) + 1); \ } \ } while (0) -#define TF(c) ((c) != 0 ? "true" : "false") #define C(c, s) ((c) != 0 ? (s) : " ") static size_t @@ -14175,7 +14149,6 @@ rb_raw_obj_info_buitin_type(char *const buff, const size_t buff_size, const VALU return pos; } -#undef TF #undef C const char * @@ -14190,17 +14163,6 @@ rb_raw_obj_info(char *const buff, const size_t buff_size, VALUE obj) return buff; } -const char * -rb_raw_obj_info_basic(char *const buff, const size_t buff_size, VALUE obj) -{ - asan_unpoisoning_object(obj) { - size_t pos = rb_raw_obj_info_common(buff, buff_size, obj); - if (pos >= buff_size) {} // truncated - } - - return buff; -} - #undef APPEND_S #undef APPEND_F #undef BUFF_ARGS @@ -14238,7 +14200,12 @@ obj_info_basic(VALUE obj) { rb_atomic_t index = atomic_inc_wraparound(&obj_info_buffers_index, OBJ_INFO_BUFFERS_NUM); char *const buff = obj_info_buffers[index]; - return rb_raw_obj_info_basic(buff, OBJ_INFO_BUFFERS_SIZE, obj); + + asan_unpoisoning_object(obj) { + rb_raw_obj_info_common(buff, OBJ_INFO_BUFFERS_SIZE, obj); + } + + return buff; } #else static const char * @@ -14329,7 +14296,8 @@ rb_gcdebug_sentinel(VALUE obj, const char *name) #endif /* GC_DEBUG */ -/* +/* :nodoc: + * * call-seq: * GC.add_stress_to_class(class[, ...]) * @@ -14348,7 +14316,8 @@ rb_gcdebug_add_stress_to_class(int argc, VALUE *argv, VALUE self) return self; } -/* +/* :nodoc: + * * call-seq: * GC.remove_stress_to_class(class[, ...]) * diff --git a/gems/bundled_gems b/gems/bundled_gems index c816f234a65194..842fbe0ce35e50 100644 --- a/gems/bundled_gems +++ b/gems/bundled_gems @@ -5,26 +5,27 @@ # - repository-url: URL from where clone for test # - revision: revision in repository-url to test # if `revision` is not given, "v"+`version` or `version` will be used. -minitest 5.22.2 https://github.com/minitest/minitest 00affdbf08f42b9b12df043e9fe28e56ad33b622 -power_assert 2.0.3 https://github.com/ruby/power_assert -rake 13.1.0 https://github.com/ruby/rake + +minitest 5.22.3 https://github.com/minitest/minitest 287b35d60c8e19c11ba090efc6eeb225325a8520 +power_assert 2.0.3 https://github.com/ruby/power_assert 84e85124c5014a139af39161d484156cfe87a9ed +rake 13.2.1 https://github.com/ruby/rake test-unit 3.6.2 https://github.com/test-unit/test-unit rexml 3.2.6 https://github.com/ruby/rexml rss 0.3.0 https://github.com/ruby/rss net-ftp 0.3.4 https://github.com/ruby/net-ftp net-imap 0.4.10 https://github.com/ruby/net-imap net-pop 0.1.2 https://github.com/ruby/net-pop -net-smtp 0.4.0.1 https://github.com/ruby/net-smtp +net-smtp 0.5.0 https://github.com/ruby/net-smtp matrix 0.4.2 https://github.com/ruby/matrix prime 0.1.2 https://github.com/ruby/prime -rbs 3.4.4 https://github.com/ruby/rbs 0637ef60e13d5ded71d69d5238dbffcaae3d3740 -typeprof 0.21.11 https://github.com/ruby/typeprof -debug 1.9.1 https://github.com/ruby/debug 2d602636d99114d55a32fedd652c9c704446a749 +rbs 3.4.4 https://github.com/ruby/rbs ba7872795d5de04adb8ff500c0e6afdc81a041dd +typeprof 0.21.11 https://github.com/ruby/typeprof b19a6416da3a05d57fadd6ffdadb382b6d236ca5 +debug 1.9.2 https://github.com/ruby/debug racc 1.7.3 https://github.com/ruby/racc mutex_m 0.2.0 https://github.com/ruby/mutex_m getoptlong 0.2.1 https://github.com/ruby/getoptlong base64 0.2.0 https://github.com/ruby/base64 -bigdecimal 3.1.6 https://github.com/ruby/bigdecimal 741fb93b566959663269ecd988e278a974528a1d +bigdecimal 3.1.7 https://github.com/ruby/bigdecimal observer 0.1.2 https://github.com/ruby/observer abbrev 0.1.2 https://github.com/ruby/abbrev resolv-replace 0.1.1 https://github.com/ruby/resolv-replace @@ -32,4 +33,4 @@ rinda 0.2.0 https://github.com/ruby/rinda drb 2.2.1 https://github.com/ruby/drb nkf 0.2.0 https://github.com/ruby/nkf syslog 0.1.2 https://github.com/ruby/syslog -csv 3.2.8 https://github.com/ruby/csv +csv 3.3.0 https://github.com/ruby/csv diff --git a/hash.c b/hash.c index ad2e4a9843ad74..44f0f538cf10bd 100644 --- a/hash.c +++ b/hash.c @@ -398,6 +398,9 @@ const struct st_hash_type rb_hashtype_ident = { rb_ident_hash, }; +#define RHASH_IDENTHASH_P(hash) (RHASH_TYPE(hash) == &identhash) +#define RHASH_STRING_KEY_P(hash, key) (!RHASH_IDENTHASH_P(hash) && (rb_obj_class(key) == rb_cString)) + typedef st_index_t st_hash_t; /* @@ -2960,7 +2963,7 @@ rb_hash_aset(VALUE hash, VALUE key, VALUE val) rb_hash_modify(hash); - if (RHASH_TYPE(hash) == &identhash || rb_obj_class(key) != rb_cString) { + if (!RHASH_STRING_KEY_P(hash, key)) { RHASH_UPDATE_ITER(hash, iter_p, key, hash_aset, val); } else { @@ -3906,19 +3909,10 @@ rb_hash_invert(VALUE hash) return h; } -static int -rb_hash_update_callback(st_data_t *key, st_data_t *value, struct update_arg *arg, int existing) -{ - *value = arg->arg; - return ST_CONTINUE; -} - -NOINSERT_UPDATE_CALLBACK(rb_hash_update_callback) - static int rb_hash_update_i(VALUE key, VALUE value, VALUE hash) { - RHASH_UPDATE(hash, key, rb_hash_update_callback, value); + rb_hash_aset(hash, key, value); return ST_CONTINUE; } @@ -3930,6 +3924,9 @@ rb_hash_update_block_callback(st_data_t *key, st_data_t *value, struct update_ar if (existing) { newvalue = (st_data_t)rb_yield_values(3, (VALUE)*key, (VALUE)*value, (VALUE)newvalue); } + else if (RHASH_STRING_KEY_P(arg->hash, *key) && !RB_OBJ_FROZEN(*key)) { + *key = rb_hash_key_str(*key); + } *value = newvalue; return ST_CONTINUE; } @@ -4166,7 +4163,7 @@ rb_hash_assoc(VALUE hash, VALUE key) if (RHASH_EMPTY_P(hash)) return Qnil; - if (RHASH_ST_TABLE_P(hash) && RHASH_ST_TABLE(hash)->type != &identhash) { + if (RHASH_ST_TABLE_P(hash) && !RHASH_IDENTHASH_P(hash)) { VALUE value = Qundef; st_table assoctable = *RHASH_ST_TABLE(hash); assoctable.type = &(struct st_hash_type){ @@ -4449,7 +4446,7 @@ rb_hash_compare_by_id(VALUE hash) VALUE rb_hash_compare_by_id_p(VALUE hash) { - return RBOOL(RHASH_ST_TABLE_P(hash) && RHASH_ST_TABLE(hash)->type == &identhash); + return RBOOL(RHASH_IDENTHASH_P(hash)); } VALUE diff --git a/include/ruby/assert.h b/include/ruby/assert.h index ceab090427924a..e9edd9e640a186 100644 --- a/include/ruby/assert.h +++ b/include/ruby/assert.h @@ -281,6 +281,17 @@ RBIMPL_WARNING_IGNORED(-Wgnu-zero-variadic-macro-arguments) # define RUBY_ASSERT_WHEN(cond, expr) RUBY_ASSERT_MESG_WHEN((cond), (expr), #expr) #endif +/** + * A variant of #RUBY_ASSERT that asserts when either #RUBY_DEBUG or built-in + * type of `obj` is `type`. + * + * @param obj Object to check its built-in typue. + * @param type Built-in type constant, T_ARRAY, T_STRING, etc. + */ +#define RUBY_ASSERT_BUILTIN_TYPE(obj, type) \ + RUBY_ASSERT(RB_TYPE_P(obj, type), \ + "Actual type is %s", rb_builtin_type_name(BUILTIN_TYPE(obj))) + /** * This is either #RUBY_ASSERT or #RBIMPL_ASSUME, depending on #RUBY_DEBUG. * diff --git a/include/ruby/internal/core/rbasic.h b/include/ruby/internal/core/rbasic.h index 4617f743a79a20..a1477e260073b3 100644 --- a/include/ruby/internal/core/rbasic.h +++ b/include/ruby/internal/core/rbasic.h @@ -56,22 +56,20 @@ enum ruby_rvalue_flags { }; /** - * Ruby's object's, base components. Every single ruby objects have them in - * common. + * Ruby object's base components. All Ruby objects have them in common. */ struct RUBY_ALIGNAS(SIZEOF_VALUE) RBasic { /** - * Per-object flags. Each ruby objects have their own characteristics - * apart from their classes. For instance whether an object is frozen or - * not is not controlled by its class. This is where such properties are - * stored. + * Per-object flags. Each Ruby object has its own characteristics apart + * from its class. For instance, whether an object is frozen or not is not + * controlled by its class. This is where such properties are stored. * * @see enum ::ruby_fl_type * - * @note This is ::VALUE rather than an enum for alignment purpose. Back + * @note This is ::VALUE rather than an enum for alignment purposes. Back * in the 1990s there were no such thing like `_Alignas` in C. */ VALUE flags; @@ -79,10 +77,10 @@ RBasic { /** * Class of an object. Every object has its class. Also, everything is an * object in Ruby. This means classes are also objects. Classes have - * their own classes, classes of classes have their classes, too ... and - * it recursively continues forever. + * their own classes, classes of classes have their classes too, and it + * recursively continues forever. * - * Also note the `const` qualifier. In ruby an object cannot "change" its + * Also note the `const` qualifier. In Ruby, an object cannot "change" its * class. */ const VALUE klass; diff --git a/include/ruby/internal/fl_type.h b/include/ruby/internal/fl_type.h index 16e0dfe139da50..eb212db7dc9555 100644 --- a/include/ruby/internal/fl_type.h +++ b/include/ruby/internal/fl_type.h @@ -916,6 +916,9 @@ static inline void RB_OBJ_FREEZE_RAW(VALUE obj) { RB_FL_SET_RAW(obj, RUBY_FL_FREEZE); + if (TYPE(obj) == T_STRING) { + RB_FL_UNSET_RAW(obj, FL_USER3); // STR_CHILLED + } } RUBY_SYMBOL_EXPORT_BEGIN diff --git a/include/ruby/internal/gc.h b/include/ruby/internal/gc.h index ac9dfd8842ebe3..462f416af21cfd 100644 --- a/include/ruby/internal/gc.h +++ b/include/ruby/internal/gc.h @@ -209,22 +209,6 @@ void rb_gc_mark_movable(VALUE obj); */ VALUE rb_gc_location(VALUE obj); -/** - * Asserts that the passed object is no longer needed. Such objects are - * reclaimed sooner or later so this function is not mandatory. But sometimes - * you can know from your application knowledge that an object is surely dead - * at some point. Calling this as a hint can be a polite way. - * - * @param[out] obj Object, dead. - * @pre `obj` have never been passed to this function before. - * @post `obj` could be invalidated. - * @warning It is a failure to pass an object multiple times to this - * function. - * @deprecated This is now a no-op function. - */ -RBIMPL_ATTR_DEPRECATED(("this is now a no-op function")) -void rb_gc_force_recycle(VALUE obj); - /** * Triggers a GC process. This was the only GC entry point that we had at the * beginning. Over time our GC evolved. Now what this function does is just a @@ -839,4 +823,7 @@ rb_obj_write( return a; } +RBIMPL_ATTR_DEPRECATED(("Will be removed soon")) +static inline void rb_gc_force_recycle(VALUE obj){} + #endif /* RBIMPL_GC_H */ diff --git a/include/ruby/internal/intern/error.h b/include/ruby/internal/intern/error.h index bf8daadd3e978c..11e147a12103fb 100644 --- a/include/ruby/internal/intern/error.h +++ b/include/ruby/internal/intern/error.h @@ -190,7 +190,6 @@ RBIMPL_ATTR_NONNULL(()) */ void rb_error_frozen(const char *what); -RBIMPL_ATTR_NORETURN() /** * Identical to rb_error_frozen(), except it takes arbitrary Ruby object * instead of C's string. diff --git a/include/ruby/internal/intern/object.h b/include/ruby/internal/intern/object.h index b9ffa57c06c707..9daad7d046aea7 100644 --- a/include/ruby/internal/intern/object.h +++ b/include/ruby/internal/intern/object.h @@ -151,13 +151,12 @@ VALUE rb_obj_is_kind_of(VALUE obj, VALUE klass); * @return An allocated, not yet initialised instance of `klass`. * @note It calls the allocator defined by rb_define_alloc_func(). You * cannot use this function to define an allocator. Use - * rb_newobj_of(), #TypedData_Make_Struct or others, instead. + * TypedData_Make_Struct or others, instead. * @note Usually prefer rb_class_new_instance() to rb_obj_alloc() and * rb_obj_call_init(). * @see rb_class_new_instance() * @see rb_obj_call_init() * @see rb_define_alloc_func() - * @see rb_newobj_of() * @see #TypedData_Make_Struct */ VALUE rb_obj_alloc(VALUE klass); diff --git a/include/ruby/internal/intern/string.h b/include/ruby/internal/intern/string.h index 952dc508c24a67..6827563e8dc3e0 100644 --- a/include/ruby/internal/intern/string.h +++ b/include/ruby/internal/intern/string.h @@ -454,7 +454,7 @@ VALUE rb_interned_str(const char *ptr, long len); RBIMPL_ATTR_NONNULL(()) /** * Identical to rb_interned_str(), except it assumes the passed pointer is a - * pointer to a C's string. It can also be seen as a routine identical to to + * pointer to a C's string. It can also be seen as a routine identical to * rb_str_to_interned_str(), except it takes a C's string instead of Ruby's. * Or it can also be seen as a routine identical to rb_str_new_cstr(), except * it returns an infamous "f"string. @@ -601,6 +601,21 @@ VALUE rb_str_dup(VALUE str); */ VALUE rb_str_resurrect(VALUE str); +/** + * Returns whether a string is chilled or not. + * + * This function is temporary and users must check for its presence using + * #ifdef HAVE_RB_STR_CHILLED_P. If HAVE_RB_STR_CHILLED_P is not defined, then + * strings can't be chilled. + * + * @param[in] str A string. + * @retval 1 The string is chilled. + * @retval 0 Otherwise. + */ +bool rb_str_chilled_p(VALUE str); + +#define HAVE_RB_STR_CHILLED_P 1 + /** * Obtains a "temporary lock" of the string. This advisory locking mechanism * prevents other cooperating threads from tampering the receiver. The same diff --git a/include/ruby/internal/intern/vm.h b/include/ruby/internal/intern/vm.h index 76af796b548bdc..29e0c7f534acb1 100644 --- a/include/ruby/internal/intern/vm.h +++ b/include/ruby/internal/intern/vm.h @@ -229,8 +229,7 @@ void rb_define_alloc_func(VALUE klass, rb_alloc_func_t func); * restrict creation of an instance of a class. For example it rarely makes * sense for a DB adaptor class to allow programmers creating DB row objects * without querying the DB itself. You can kill sporadic creation of such - * objects then, by nullifying the allocator function using this API. Your - * object shall be allocated using #RB_NEWOBJ_OF() directly. + * objects then, by nullifying the allocator function using this API. * * @param[out] klass The class to modify. * @pre `klass` must be an instance of Class. diff --git a/include/ruby/internal/newobj.h b/include/ruby/internal/newobj.h index ba1d7cbe59ae59..6eee2fa5fa7643 100644 --- a/include/ruby/internal/newobj.h +++ b/include/ruby/internal/newobj.h @@ -29,63 +29,14 @@ #include "ruby/internal/value.h" #include "ruby/assert.h" -/** - * Declares, allocates, then assigns a new object to the given variable. - * - * @param obj Variable name. - * @param type Variable type. - * @exception rb_eNoMemError No space left. - * @return An allocated object, not initialised. - * @note Modern programs tend to use #NEWOBJ_OF instead. - * - * @internal - * - * :FIXME: Should we deprecate it? - */ -#define RB_NEWOBJ(obj,type) type *(obj) = RBIMPL_CAST((type *)rb_newobj()) - -/** - * Identical to #RB_NEWOBJ, except it also accepts the allocating object's - * class and flags. - * - * @param obj Variable name. - * @param type Variable type. - * @param klass Object's class. - * @param flags Object's flags. - * @exception rb_eNoMemError No space left. - * @return An allocated object, filled with the arguments. - */ -#define RB_NEWOBJ_OF(obj,type,klass,flags) type *(obj) = RBIMPL_CAST((type *)rb_newobj_of(klass, flags)) - -#define NEWOBJ RB_NEWOBJ /**< @old{RB_NEWOBJ} */ -#define NEWOBJ_OF RB_NEWOBJ_OF /**< @old{RB_NEWOBJ_OF} */ #define OBJSETUP rb_obj_setup /**< @old{rb_obj_setup} */ #define CLONESETUP rb_clone_setup /**< @old{rb_clone_setup} */ #define DUPSETUP rb_dup_setup /**< @old{rb_dup_setup} */ RBIMPL_SYMBOL_EXPORT_BEGIN() -/** - * This is the implementation detail of #RB_NEWOBJ. - * - * @exception rb_eNoMemError No space left. - * @return An allocated object, not initialised. - */ -VALUE rb_newobj(void); - -/** - * This is the implementation detail of #RB_NEWOBJ_OF. - * - * @param klass Object's class. - * @param flags Object's flags. - * @exception rb_eNoMemError No space left. - * @return An allocated object, filled with the arguments. - */ -VALUE rb_newobj_of(VALUE klass, VALUE flags); - /** * Fills common fields in the object. * - * @note Prefer rb_newobj_of() to this function. * @param[in,out] obj A Ruby object to be set up. * @param[in] klass `obj` will belong to this class. * @param[in] type One of ::ruby_value_type. diff --git a/include/ruby/io.h b/include/ruby/io.h index be5681df3b7979..e9dfeda5b1214b 100644 --- a/include/ruby/io.h +++ b/include/ruby/io.h @@ -137,7 +137,124 @@ struct rb_io_encoding { VALUE ecopts; }; -struct rb_io; +#ifndef HAVE_RB_IO_T +#define HAVE_RB_IO_T 1 +/** Ruby's IO, metadata and buffers. */ +struct rb_io { + /** The IO's Ruby level counterpart. */ + RBIMPL_ATTR_DEPRECATED(("with no replacement")) + VALUE self; + + /** stdio ptr for read/write, if available. */ + RBIMPL_ATTR_DEPRECATED(("with no replacement")) + FILE *stdio_file; + + /** file descriptor. */ + RBIMPL_ATTR_DEPRECATED(("rb_io_descriptor")) + int fd; + + /** mode flags: FMODE_XXXs */ + RBIMPL_ATTR_DEPRECATED(("rb_io_mode")) + int mode; + + /** child's pid (for pipes) */ + RBIMPL_ATTR_DEPRECATED(("with no replacement")) + rb_pid_t pid; + + /** number of lines read */ + RBIMPL_ATTR_DEPRECATED(("with no replacement")) + int lineno; + + /** pathname for file */ + RBIMPL_ATTR_DEPRECATED(("rb_io_path")) + VALUE pathv; + + /** finalize proc */ + RBIMPL_ATTR_DEPRECATED(("with no replacement")) + void (*finalize)(struct rb_io*,int); + + /** Write buffer. */ + RBIMPL_ATTR_DEPRECATED(("with no replacement")) + rb_io_buffer_t wbuf; + + /** + * (Byte) read buffer. Note also that there is a field called + * ::rb_io_t::cbuf, which also concerns read IO. + */ + RBIMPL_ATTR_DEPRECATED(("with no replacement")) + rb_io_buffer_t rbuf; + + /** + * Duplex IO object, if set. + * + * @see rb_io_set_write_io() + */ + RBIMPL_ATTR_DEPRECATED(("rb_io_get_write_io")) + VALUE tied_io_for_writing; + + RBIMPL_ATTR_DEPRECATED(("with no replacement")) + struct rb_io_encoding encs; /**< Decomposed encoding flags. */ + + /** Encoding converter used when reading from this IO. */ + RBIMPL_ATTR_DEPRECATED(("with no replacement")) + rb_econv_t *readconv; + + /** + * rb_io_ungetc() destination. This buffer is read before checking + * ::rb_io_t::rbuf + */ + RBIMPL_ATTR_DEPRECATED(("with no replacement")) + rb_io_buffer_t cbuf; + + /** Encoding converter used when writing to this IO. */ + RBIMPL_ATTR_DEPRECATED(("with no replacement")) + rb_econv_t *writeconv; + + /** + * This is, when set, an instance of ::rb_cString which holds the "common" + * encoding. Write conversion can convert strings twice... In case + * conversion from encoding X to encoding Y does not exist, Ruby finds an + * encoding Z that bridges the two, so that X to Z to Y conversion happens. + */ + RBIMPL_ATTR_DEPRECATED(("with no replacement")) + VALUE writeconv_asciicompat; + + /** Whether ::rb_io_t::writeconv is already set up. */ + RBIMPL_ATTR_DEPRECATED(("with no replacement")) + int writeconv_initialized; + + /** + * Value of ::rb_io_t::rb_io_enc_t::ecflags stored right before + * initialising ::rb_io_t::writeconv. + */ + RBIMPL_ATTR_DEPRECATED(("with no replacement")) + int writeconv_pre_ecflags; + + /** + * Value of ::rb_io_t::rb_io_enc_t::ecopts stored right before initialising + * ::rb_io_t::writeconv. + */ + RBIMPL_ATTR_DEPRECATED(("with no replacement")) + VALUE writeconv_pre_ecopts; + + /** + * This is a Ruby level mutex. It avoids multiple threads to write to an + * IO at once; helps for instance rb_io_puts() to ensure newlines right + * next to its arguments. + * + * This of course doesn't help inter-process IO interleaves, though. + */ + RBIMPL_ATTR_DEPRECATED(("with no replacement")) + VALUE write_lock; + + /** + * The timeout associated with this IO when performing blocking operations. + */ + RBIMPL_ATTR_DEPRECATED(("rb_io_timeout/rb_io_set_timeout")) + VALUE timeout; +}; +#endif + typedef struct rb_io rb_io_t; /** @alias{rb_io_enc_t} */ diff --git a/include/ruby/vm.h b/include/ruby/vm.h index 3458c28be7b78d..87797809524e1d 100644 --- a/include/ruby/vm.h +++ b/include/ruby/vm.h @@ -49,6 +49,13 @@ int ruby_vm_destruct(ruby_vm_t *vm); */ void ruby_vm_at_exit(void(*func)(ruby_vm_t *)); +/** + * Returns whether the Ruby VM will free all memory at shutdown. + * + * @return true if free-at-exit is enabled, false otherwise. + */ +bool ruby_free_at_exit_p(void); + RBIMPL_SYMBOL_EXPORT_END() #endif /* RUBY_VM_H */ diff --git a/inits.c b/inits.c index 9ed104f369e02f..677a384f9a37c5 100644 --- a/inits.c +++ b/inits.c @@ -73,7 +73,6 @@ rb_call_inits(void) CALL(vm_trace); CALL(vm_stack_canary); CALL(ast); - CALL(gc_stress); CALL(shape); CALL(Prism); diff --git a/insns.def b/insns.def index 966f51ecfbfb81..9c649904b88278 100644 --- a/insns.def +++ b/insns.def @@ -375,7 +375,17 @@ putstring () (VALUE val) { - val = rb_ec_str_resurrect(ec, str); + val = rb_ec_str_resurrect(ec, str, false); +} + +/* put chilled string val. string will be copied but frozen in the future. */ +DEFINE_INSN +putchilledstring +(VALUE str) +() +(VALUE val) +{ + val = rb_ec_str_resurrect(ec, str, true); } /* put concatenate strings */ diff --git a/internal.h b/internal.h index c66e057f60a3a5..4fb99d1c088a25 100644 --- a/internal.h +++ b/internal.h @@ -40,10 +40,6 @@ #undef RClass #undef RCLASS_SUPER -/* internal/gc.h */ -#undef NEWOBJ_OF -#undef RB_NEWOBJ_OF - /* internal/hash.h */ #undef RHASH_IFNONE #undef RHASH_SIZE diff --git a/internal/eval.h b/internal/eval.h index 73bb656d968078..e594d8516d4a74 100644 --- a/internal/eval.h +++ b/internal/eval.h @@ -21,6 +21,7 @@ extern ID ruby_static_id_status; VALUE rb_refinement_module_get_refined_class(VALUE module); void rb_class_modify_check(VALUE); NORETURN(VALUE rb_f_raise(int argc, VALUE *argv)); +VALUE rb_top_main_class(const char *method); /* eval_error.c */ VALUE rb_get_backtrace(VALUE info); diff --git a/internal/gc.h b/internal/gc.h index b4fabd89562ce1..2aa3361463a475 100644 --- a/internal/gc.h +++ b/internal/gc.h @@ -114,32 +114,17 @@ int ruby_get_stack_grow_direction(volatile VALUE *addr); const char *rb_obj_info(VALUE obj); const char *rb_raw_obj_info(char *const buff, const size_t buff_size, VALUE obj); -const char *rb_raw_obj_info_basic(char *const buff, const size_t buff_size, VALUE obj); size_t rb_size_pool_slot_size(unsigned char pool_id); struct rb_execution_context_struct; /* in vm_core.h */ struct rb_objspace; /* in vm_core.h */ -#ifdef NEWOBJ_OF -# undef NEWOBJ_OF -# undef RB_NEWOBJ_OF -#endif - -#define NEWOBJ_OF_0(var, T, c, f, s, ec) \ - T *(var) = (T *)(((f) & FL_WB_PROTECTED) ? \ - rb_wb_protected_newobj_of(GET_EC(), (c), (f) & ~FL_WB_PROTECTED, s) : \ - rb_wb_unprotected_newobj_of((c), (f), s)) -#define NEWOBJ_OF_ec(var, T, c, f, s, ec) \ +#define NEWOBJ_OF(var, T, c, f, s, ec) \ T *(var) = (T *)(((f) & FL_WB_PROTECTED) ? \ - rb_wb_protected_newobj_of((ec), (c), (f) & ~FL_WB_PROTECTED, s) : \ + rb_wb_protected_newobj_of((ec ? ec : GET_EC()), (c), (f) & ~FL_WB_PROTECTED, s) : \ rb_wb_unprotected_newobj_of((c), (f), s)) -#define NEWOBJ_OF(var, T, c, f, s, ec) \ - NEWOBJ_OF_HELPER(ec)(var, T, c, f, s, ec) - -#define NEWOBJ_OF_HELPER(ec) NEWOBJ_OF_ ## ec - #define RB_OBJ_GC_FLAGS_MAX 6 /* used in ext/objspace */ #ifndef USE_UNALIGNED_MEMBER_ACCESS @@ -204,7 +189,6 @@ typedef struct ractor_newobj_cache { } rb_ractor_newobj_cache_t; /* gc.c */ -extern VALUE *ruby_initial_gc_stress_ptr; extern int ruby_disable_gc; RUBY_ATTR_MALLOC void *ruby_mimmalloc(size_t size); void ruby_mimfree(void *ptr); @@ -213,11 +197,7 @@ void rb_objspace_set_event_hook(const rb_event_flag_t event); VALUE rb_objspace_gc_enable(struct rb_objspace *); VALUE rb_objspace_gc_disable(struct rb_objspace *); void ruby_gc_set_params(void); -void rb_copy_wb_protected_attribute(VALUE dest, VALUE obj); -#if __has_attribute(alloc_align) -__attribute__((__alloc_align__(1))) -#endif -RUBY_ATTR_MALLOC void *rb_aligned_malloc(size_t, size_t) RUBY_ATTR_ALLOC_SIZE((2)); +void rb_gc_copy_attributes(VALUE dest, VALUE obj); size_t rb_size_mul_or_raise(size_t, size_t, VALUE); /* used in compile.c */ size_t rb_size_mul_add_or_raise(size_t, size_t, size_t, VALUE); /* used in iseq.h */ size_t rb_malloc_grow_capa(size_t current_capacity, size_t type_size); @@ -230,8 +210,9 @@ static inline void *ruby_sized_xrealloc_inlined(void *ptr, size_t new_size, size static inline void *ruby_sized_xrealloc2_inlined(void *ptr, size_t new_count, size_t elemsiz, size_t old_count) RUBY_ATTR_RETURNS_NONNULL RUBY_ATTR_ALLOC_SIZE((2, 3)); static inline void ruby_sized_xfree_inlined(void *ptr, size_t size); void rb_gc_ractor_newobj_cache_clear(rb_ractor_newobj_cache_t *newobj_cache); -size_t rb_gc_obj_slot_size(VALUE obj); bool rb_gc_size_allocatable_p(size_t size); +size_t *rb_gc_size_pool_sizes(void); +size_t rb_gc_size_pool_id_for_size(size_t size); int rb_objspace_garbage_object_p(VALUE obj); bool rb_gc_is_ptr_to_obj(const void *ptr); @@ -242,6 +223,8 @@ void rb_gc_remove_weak(VALUE parent_obj, VALUE *ptr); void rb_gc_ref_update_table_values_only(st_table *tbl); +void rb_gc_initial_stress_set(VALUE flag); + #define rb_gc_mark_and_move_ptr(ptr) do { \ VALUE _obj = (VALUE)*(ptr); \ rb_gc_mark_and_move(&_obj); \ diff --git a/internal/inits.h b/internal/inits.h index 03e180f77bd026..03de289dd4236c 100644 --- a/internal/inits.h +++ b/internal/inits.h @@ -19,9 +19,6 @@ void Init_ext(void); /* file.c */ void Init_File(void); -/* gc.c */ -void Init_heap(void); - /* localeinit.c */ int Init_enc_set_filesystem_encoding(void); diff --git a/internal/numeric.h b/internal/numeric.h index b9d51116cfdaf7..6406cfc2fa7480 100644 --- a/internal/numeric.h +++ b/internal/numeric.h @@ -86,6 +86,7 @@ VALUE rb_int_equal(VALUE x, VALUE y); VALUE rb_int_divmod(VALUE x, VALUE y); VALUE rb_int_and(VALUE x, VALUE y); VALUE rb_int_lshift(VALUE x, VALUE y); +VALUE rb_int_rshift(VALUE x, VALUE y); VALUE rb_int_div(VALUE x, VALUE y); int rb_int_positive_p(VALUE num); int rb_int_negative_p(VALUE num); diff --git a/internal/parse.h b/internal/parse.h index e05b2bc02f5dd4..8e82c0f8974a00 100644 --- a/internal/parse.h +++ b/internal/parse.h @@ -18,8 +18,6 @@ struct rb_iseq_struct; /* in vm_core.h */ -#define STRTERM_HEREDOC IMEMO_FL_USER0 - /* structs for managing terminator of string literal and heredocment */ typedef struct rb_strterm_literal_struct { long nest; @@ -40,7 +38,7 @@ typedef struct rb_strterm_heredoc_struct { #define HERETERM_LENGTH_MAX UINT_MAX typedef struct rb_strterm_struct { - VALUE flags; + bool heredoc; union { rb_strterm_literal_t literal; rb_strterm_heredoc_t heredoc; @@ -70,7 +68,6 @@ rb_parser_string_t *rb_str_to_parser_string(rb_parser_t *p, VALUE str); void rb_parser_warn_duplicate_keys(struct parser_params *p, NODE *hash); int rb_parser_dvar_defined_ref(struct parser_params*, ID, ID**); ID rb_parser_internal_id(struct parser_params*); -VALUE rb_parser_node_case_when_optimizable_literal(struct parser_params *p, const NODE *const node); int rb_parser_reg_fragment_check(struct parser_params*, rb_parser_string_t*, int); int rb_reg_named_capture_assign_iter_impl(struct parser_params *p, const char *s, long len, rb_encoding *enc, NODE **succ_block, const rb_code_location_t *loc); int rb_parser_local_defined(struct parser_params *p, ID id, const struct rb_iseq_struct *iseq); @@ -105,6 +102,9 @@ long rb_ruby_ripper_column(rb_parser_t *p); long rb_ruby_ripper_token_len(rb_parser_t *p); rb_parser_string_t *rb_ruby_ripper_lex_lastline(rb_parser_t *p); VALUE rb_ruby_ripper_lex_state_name(struct parser_params *p, int state); +#ifdef UNIVERSAL_PARSER +rb_parser_t *rb_ripper_parser_params_allocate(const rb_parser_config_t *config); +#endif struct parser_params *rb_ruby_ripper_parser_allocate(void); #endif diff --git a/internal/ruby_parser.h b/internal/ruby_parser.h index 7b4c71526894f5..0a00075211fba8 100644 --- a/internal/ruby_parser.h +++ b/internal/ruby_parser.h @@ -5,13 +5,13 @@ #include "internal/bignum.h" #include "internal/compilers.h" #include "internal/complex.h" -#include "internal/imemo.h" #include "internal/rational.h" #include "rubyparser.h" #include "vm.h" RUBY_SYMBOL_EXPORT_BEGIN #ifdef UNIVERSAL_PARSER +const rb_parser_config_t *rb_ruby_parser_config(void); rb_parser_t *rb_parser_params_allocate(void); rb_parser_t *rb_parser_params_new(void); #endif @@ -19,6 +19,7 @@ VALUE rb_parser_set_context(VALUE, const struct rb_iseq_struct *, int); VALUE rb_parser_new(void); rb_ast_t *rb_parser_compile_string_path(VALUE vparser, VALUE fname, VALUE src, int line); VALUE rb_str_new_parser_string(rb_parser_string_t *str); +VALUE rb_str_new_mutable_parser_string(rb_parser_string_t *str); VALUE rb_node_str_string_val(const NODE *); VALUE rb_node_sym_string_val(const NODE *); @@ -28,7 +29,6 @@ VALUE rb_node_dregx_string_val(const NODE *); VALUE rb_node_line_lineno_val(const NODE *); VALUE rb_node_file_path_val(const NODE *); VALUE rb_node_encoding_val(const NODE *); -VALUE rb_node_const_decl_val(const NODE *node); VALUE rb_node_integer_literal_val(const NODE *); VALUE rb_node_float_literal_val(const NODE *); diff --git a/internal/sanitizers.h b/internal/sanitizers.h index 345380cebe833c..b0eb1fc851b415 100644 --- a/internal/sanitizers.h +++ b/internal/sanitizers.h @@ -95,7 +95,7 @@ # define VALGRIND_MAKE_MEM_UNDEFINED(p, n) 0 #endif -/*! +/** * This function asserts that a (continuous) memory region from ptr to size * being "poisoned". Both read / write access to such memory region are * prohibited until properly unpoisoned. The region must be previously @@ -105,8 +105,8 @@ * region to reuse later: poison when you keep it unused, and unpoison when you * reuse. * - * \param[in] ptr pointer to the beginning of the memory region to poison. - * \param[in] size the length of the memory region to poison. + * @param[in] ptr pointer to the beginning of the memory region to poison. + * @param[in] size the length of the memory region to poison. */ static inline void asan_poison_memory_region(const volatile void *ptr, size_t size) @@ -115,10 +115,10 @@ asan_poison_memory_region(const volatile void *ptr, size_t size) __asan_poison_memory_region(ptr, size); } -/*! +/** * This is a variant of asan_poison_memory_region that takes a VALUE. * - * \param[in] obj target object. + * @param[in] obj target object. */ static inline void asan_poison_object(VALUE obj) @@ -135,12 +135,12 @@ asan_poison_object(VALUE obj) #define asan_poison_object_if(ptr, obj) ((void)(ptr), (void)(obj)) #endif -/*! +/** * This function predicates if the given object is fully addressable or not. * - * \param[in] obj target object. - * \retval 0 the given object is fully addressable. - * \retval otherwise pointer to first such byte who is poisoned. + * @param[in] obj target object. + * @retval 0 the given object is fully addressable. + * @retval otherwise pointer to first such byte who is poisoned. */ static inline void * asan_poisoned_object_p(VALUE obj) @@ -149,7 +149,7 @@ asan_poisoned_object_p(VALUE obj) return __asan_region_is_poisoned(ptr, SIZEOF_VALUE); } -/*! +/** * This function asserts that a (formally poisoned) memory region from ptr to * size is now addressable. Write access to such memory region gets allowed. * However read access might or might not be possible depending on situations, @@ -160,9 +160,9 @@ asan_poisoned_object_p(VALUE obj) * the other hand, that memory region is fully defined and can be read * immediately. * - * \param[in] ptr pointer to the beginning of the memory region to unpoison. - * \param[in] size the length of the memory region. - * \param[in] malloc_p if the memory region is like a malloc's return value or not. + * @param[in] ptr pointer to the beginning of the memory region to unpoison. + * @param[in] size the length of the memory region. + * @param[in] malloc_p if the memory region is like a malloc's return value or not. */ static inline void asan_unpoison_memory_region(const volatile void *ptr, size_t size, bool malloc_p) @@ -176,11 +176,11 @@ asan_unpoison_memory_region(const volatile void *ptr, size_t size, bool malloc_p } } -/*! +/** * This is a variant of asan_unpoison_memory_region that takes a VALUE. * - * \param[in] obj target object. - * \param[in] malloc_p if the memory region is like a malloc's return value or not. + * @param[in] obj target object. + * @param[in] malloc_p if the memory region is like a malloc's return value or not. */ static inline void asan_unpoison_object(VALUE obj, bool newobj_p) @@ -207,7 +207,7 @@ asan_poison_object_restore(VALUE obj, void *ptr) } -/*! +/** * Checks if the given pointer is on an ASAN fake stack. If so, it returns the * address this variable has on the real frame; if not, it returns the origin * address unmodified. @@ -219,8 +219,8 @@ asan_poison_object_restore(VALUE obj, void *ptr) * n.b. - this only works for addresses passed in from local variables on the same * thread, because the ASAN fake stacks are threadlocal. * - * \param[in] slot the address of some local variable - * \retval a pointer to something from that frame on the _real_ machine stack + * @param[in] slot the address of some local variable + * @retval a pointer to something from that frame on the _real_ machine stack */ static inline void * asan_get_real_stack_addr(void* slot) @@ -230,10 +230,10 @@ asan_get_real_stack_addr(void* slot) return addr ? addr : slot; } -/*! +/** * Gets the current thread's fake stack handle, which can be passed into get_fake_stack_extents * - * \retval An opaque value which can be passed to asan_get_fake_stack_extents + * @retval An opaque value which can be passed to asan_get_fake_stack_extents */ static inline void * asan_get_thread_fake_stack_handle(void) @@ -241,7 +241,7 @@ asan_get_thread_fake_stack_handle(void) return __asan_get_current_fake_stack(); } -/*! +/** * Checks if the given VALUE _actually_ represents a pointer to an ASAN fake stack. * * If the given slot _is_ actually a reference to an ASAN fake stack, and that fake stack @@ -252,13 +252,13 @@ asan_get_thread_fake_stack_handle(void) * * Note that this function expects "start" to be > "end" on downward-growing stack architectures; * - * \param[in] thread_fake_stack_handle The asan fake stack reference for the thread we're scanning - * \param[in] slot The value on the machine stack we want to inspect - * \param[in] machine_stack_start The extents of the real machine stack on which slot lives - * \param[in] machine_stack_end The extents of the real machine stack on which slot lives - * \param[out] fake_stack_start_out The extents of the fake stack which contains real VALUEs - * \param[out] fake_stack_end_out The extents of the fake stack which contains real VALUEs - * \return Whether slot is a pointer to a fake stack for the given machine stack range + * @param[in] thread_fake_stack_handle The asan fake stack reference for the thread we're scanning + * @param[in] slot The value on the machine stack we want to inspect + * @param[in] machine_stack_start The extents of the real machine stack on which slot lives + * @param[in] machine_stack_end The extents of the real machine stack on which slot lives + * @param[out] fake_stack_start_out The extents of the fake stack which contains real VALUEs + * @param[out] fake_stack_end_out The extents of the fake stack which contains real VALUEs + * @return Whether slot is a pointer to a fake stack for the given machine stack range */ static inline bool diff --git a/internal/signal.h b/internal/signal.h index 660cd95f78742d..2363bf412cfbb2 100644 --- a/internal/signal.h +++ b/internal/signal.h @@ -19,7 +19,6 @@ void (*ruby_posix_signal(int, void (*)(int)))(int); RUBY_SYMBOL_EXPORT_BEGIN /* signal.c (export) */ -int rb_grantpt(int fd); RUBY_SYMBOL_EXPORT_END #endif /* INTERNAL_SIGNAL_H */ diff --git a/internal/string.h b/internal/string.h index 5fac9870358448..088535e19a01b1 100644 --- a/internal/string.h +++ b/internal/string.h @@ -17,6 +17,7 @@ #define STR_NOEMBED FL_USER1 #define STR_SHARED FL_USER2 /* = ELTS_SHARED */ +#define STR_CHILLED FL_USER3 #ifdef rb_fstring_cstr # undef rb_fstring_cstr @@ -81,7 +82,7 @@ VALUE rb_id_quote_unprintable(ID); VALUE rb_sym_proc_call(ID mid, int argc, const VALUE *argv, int kw_splat, VALUE passed_proc); struct rb_execution_context_struct; -VALUE rb_ec_str_resurrect(struct rb_execution_context_struct *ec, VALUE str); +VALUE rb_ec_str_resurrect(struct rb_execution_context_struct *ec, VALUE str, bool chilled); #define rb_fstring_lit(str) rb_fstring_new((str), rb_strlen_lit(str)) #define rb_fstring_literal(str) rb_fstring_lit(str) @@ -112,6 +113,26 @@ STR_SHARED_P(VALUE str) return FL_ALL_RAW(str, STR_NOEMBED | STR_SHARED); } +static inline bool +CHILLED_STRING_P(VALUE obj) +{ + return RB_TYPE_P(obj, T_STRING) && FL_TEST_RAW(obj, STR_CHILLED); +} + +static inline void +CHILLED_STRING_MUTATED(VALUE str) +{ + rb_category_warn(RB_WARN_CATEGORY_DEPRECATED, "literal string will be frozen in the future"); + FL_UNSET_RAW(str, STR_CHILLED | FL_FREEZE); +} + +static inline void +STR_CHILL_RAW(VALUE str) +{ + // Chilled strings are always also frozen + FL_SET_RAW(str, STR_CHILLED | RUBY_FL_FREEZE); +} + static inline bool is_ascii_string(VALUE str) { diff --git a/internal/vm.h b/internal/vm.h index 1adce8a39063d4..74635e6ad8a6fc 100644 --- a/internal/vm.h +++ b/internal/vm.h @@ -114,6 +114,7 @@ void rb_backtrace_print_as_bugreport(FILE*); int rb_backtrace_p(VALUE obj); VALUE rb_backtrace_to_str_ary(VALUE obj); VALUE rb_backtrace_to_location_ary(VALUE obj); +VALUE rb_location_ary_to_backtrace(VALUE ary); void rb_backtrace_each(VALUE (*iter)(VALUE recv, VALUE str), VALUE output); int rb_frame_info_p(VALUE obj); int rb_get_node_id_from_frame_info(VALUE obj); diff --git a/io.c b/io.c index f773de8c3f4d4a..feff4459437cca 100644 --- a/io.c +++ b/io.c @@ -14571,14 +14571,14 @@ argf_write_io(VALUE argf) /* * call-seq: - * ARGF.write(string) -> integer + * ARGF.write(*objects) -> integer * - * Writes _string_ if inplace mode. + * Writes each of the given +objects+ if inplace mode. */ static VALUE -argf_write(VALUE argf, VALUE str) +argf_write(int argc, VALUE *argv, VALUE argf) { - return rb_io_write(argf_write_io(argf), str); + return rb_io_writev(argf_write_io(argf), argc, argv); } void @@ -15823,7 +15823,7 @@ Init_IO(void) rb_define_method(rb_cARGF, "binmode", argf_binmode_m, 0); rb_define_method(rb_cARGF, "binmode?", argf_binmode_p, 0); - rb_define_method(rb_cARGF, "write", argf_write, 1); + rb_define_method(rb_cARGF, "write", argf_write, -1); rb_define_method(rb_cARGF, "print", rb_io_print, -1); rb_define_method(rb_cARGF, "putc", rb_io_putc, 1); rb_define_method(rb_cARGF, "puts", rb_io_puts, -1); diff --git a/iseq.c b/iseq.c index be10fcb7476b35..fc91a34672b806 100644 --- a/iseq.c +++ b/iseq.c @@ -747,18 +747,26 @@ finish_iseq_build(rb_iseq_t *iseq) } static rb_compile_option_t COMPILE_OPTION_DEFAULT = { - OPT_INLINE_CONST_CACHE, /* int inline_const_cache; */ - OPT_PEEPHOLE_OPTIMIZATION, /* int peephole_optimization; */ - OPT_TAILCALL_OPTIMIZATION, /* int tailcall_optimization */ - OPT_SPECIALISED_INSTRUCTION, /* int specialized_instruction; */ - OPT_OPERANDS_UNIFICATION, /* int operands_unification; */ - OPT_INSTRUCTIONS_UNIFICATION, /* int instructions_unification; */ - OPT_FROZEN_STRING_LITERAL, - OPT_DEBUG_FROZEN_STRING_LITERAL, - TRUE, /* coverage_enabled */ + .inline_const_cache = OPT_INLINE_CONST_CACHE, + .peephole_optimization = OPT_PEEPHOLE_OPTIMIZATION, + .tailcall_optimization = OPT_TAILCALL_OPTIMIZATION, + .specialized_instruction = OPT_SPECIALISED_INSTRUCTION, + .operands_unification = OPT_OPERANDS_UNIFICATION, + .instructions_unification = OPT_INSTRUCTIONS_UNIFICATION, + .frozen_string_literal = OPT_FROZEN_STRING_LITERAL, + .debug_frozen_string_literal = OPT_DEBUG_FROZEN_STRING_LITERAL, + .coverage_enabled = TRUE, }; -static const rb_compile_option_t COMPILE_OPTION_FALSE = {0}; +static const rb_compile_option_t COMPILE_OPTION_FALSE = { + .frozen_string_literal = -1, // unspecified +}; + +int +rb_iseq_opt_frozen_string_literal(void) +{ + return COMPILE_OPTION_DEFAULT.frozen_string_literal; +} static void set_compile_option_from_hash(rb_compile_option_t *option, VALUE opt) @@ -791,9 +799,11 @@ set_compile_option_from_ast(rb_compile_option_t *option, const rb_ast_body_t *as { #define SET_COMPILE_OPTION(o, a, mem) \ ((a)->mem < 0 ? 0 : ((o)->mem = (a)->mem > 0)) - SET_COMPILE_OPTION(option, ast, frozen_string_literal); SET_COMPILE_OPTION(option, ast, coverage_enabled); #undef SET_COMPILE_OPTION + if (ast->frozen_string_literal >= 0) { + option->frozen_string_literal = ast->frozen_string_literal; + } return option; } @@ -835,13 +845,14 @@ make_compile_option_value(rb_compile_option_t *option) SET_COMPILE_OPTION(option, opt, specialized_instruction); SET_COMPILE_OPTION(option, opt, operands_unification); SET_COMPILE_OPTION(option, opt, instructions_unification); - SET_COMPILE_OPTION(option, opt, frozen_string_literal); SET_COMPILE_OPTION(option, opt, debug_frozen_string_literal); SET_COMPILE_OPTION(option, opt, coverage_enabled); SET_COMPILE_OPTION_NUM(option, opt, debug_level); } #undef SET_COMPILE_OPTION #undef SET_COMPILE_OPTION_NUM + VALUE frozen_string_literal = option->frozen_string_literal == -1 ? Qnil : RBOOL(option->frozen_string_literal); + rb_hash_aset(opt, ID2SYM(rb_intern("frozen_string_literal")), frozen_string_literal); return opt; } @@ -1171,7 +1182,7 @@ iseq_load(VALUE data, const rb_iseq_t *parent, VALUE opt) tmp_loc.end_pos.column = NUM2INT(rb_ary_entry(code_location, 3)); } - if (RTEST(rb_hash_aref(misc, ID2SYM(rb_intern("prism"))))) { + if (SYM2ID(rb_hash_aref(misc, ID2SYM(rb_intern("parser")))) == rb_intern("prism")) { ISEQ_BODY(iseq)->prism = true; } @@ -1400,18 +1411,30 @@ rb_iseq_remove_coverage_all(void) static void iseqw_mark(void *ptr) { - rb_gc_mark((VALUE)ptr); + rb_gc_mark_movable(*(VALUE *)ptr); } static size_t iseqw_memsize(const void *ptr) { - return rb_iseq_memsize((const rb_iseq_t *)ptr); + return rb_iseq_memsize(*(const rb_iseq_t **)ptr); +} + +static void +iseqw_ref_update(void *ptr) +{ + VALUE *vptr = ptr; + *vptr = rb_gc_location(*vptr); } static const rb_data_type_t iseqw_data_type = { "T_IMEMO/iseq", - {iseqw_mark, NULL, iseqw_memsize,}, + { + iseqw_mark, + RUBY_TYPED_DEFAULT_FREE, + iseqw_memsize, + iseqw_ref_update, + }, 0, 0, RUBY_TYPED_FREE_IMMEDIATELY|RUBY_TYPED_WB_PROTECTED }; @@ -1419,14 +1442,16 @@ static VALUE iseqw_new(const rb_iseq_t *iseq) { if (iseq->wrapper) { + if (*(const rb_iseq_t **)rb_check_typeddata(iseq->wrapper, &iseqw_data_type) != iseq) { + rb_raise(rb_eTypeError, "wrong iseq wrapper: %" PRIsVALUE " for %p", + iseq->wrapper, (void *)iseq); + } return iseq->wrapper; } else { - union { const rb_iseq_t *in; void *out; } deconst; - VALUE obj; - deconst.in = iseq; - obj = TypedData_Wrap_Struct(rb_cISeq, &iseqw_data_type, deconst.out); - RB_OBJ_WRITTEN(obj, Qundef, iseq); + rb_iseq_t **ptr; + VALUE obj = TypedData_Make_Struct(rb_cISeq, rb_iseq_t *, &iseqw_data_type, ptr); + RB_OBJ_WRITE(obj, ptr, iseq); /* cache a wrapper object */ RB_OBJ_WRITE((VALUE)iseq, &iseq->wrapper, obj); @@ -1442,6 +1467,44 @@ rb_iseqw_new(const rb_iseq_t *iseq) return iseqw_new(iseq); } +/** + * Accept the options given to InstructionSequence.compile and + * InstructionSequence.compile_prism and share the logic for creating the + * instruction sequence. + */ +static VALUE +iseqw_s_compile_parser(int argc, VALUE *argv, VALUE self, bool prism) +{ + VALUE src, file = Qnil, path = Qnil, line = Qnil, opt = Qnil; + int i; + + i = rb_scan_args(argc, argv, "1*:", &src, NULL, &opt); + if (i > 4+NIL_P(opt)) rb_error_arity(argc, 1, 5); + switch (i) { + case 5: opt = argv[--i]; + case 4: line = argv[--i]; + case 3: path = argv[--i]; + case 2: file = argv[--i]; + } + + if (NIL_P(file)) file = rb_fstring_lit(""); + if (NIL_P(path)) path = file; + if (NIL_P(line)) line = INT2FIX(1); + + Check_Type(path, T_STRING); + Check_Type(file, T_STRING); + + rb_iseq_t *iseq; + if (prism) { + iseq = pm_iseq_compile_with_option(src, file, path, line, opt); + } + else { + iseq = rb_iseq_compile_with_option(src, file, path, line, opt); + } + + return iseqw_new(iseq); +} + /* * call-seq: * InstructionSequence.compile(source[, file[, path[, line[, options]]]]) -> iseq @@ -1482,26 +1545,7 @@ rb_iseqw_new(const rb_iseq_t *iseq) static VALUE iseqw_s_compile(int argc, VALUE *argv, VALUE self) { - VALUE src, file = Qnil, path = Qnil, line = Qnil, opt = Qnil; - int i; - - i = rb_scan_args(argc, argv, "1*:", &src, NULL, &opt); - if (i > 4+NIL_P(opt)) rb_error_arity(argc, 1, 5); - switch (i) { - case 5: opt = argv[--i]; - case 4: line = argv[--i]; - case 3: path = argv[--i]; - case 2: file = argv[--i]; - } - - if (NIL_P(file)) file = rb_fstring_lit(""); - if (NIL_P(path)) path = file; - if (NIL_P(line)) line = INT2FIX(1); - - Check_Type(path, T_STRING); - Check_Type(file, T_STRING); - - return iseqw_new(rb_iseq_compile_with_option(src, file, path, line, opt)); + return iseqw_s_compile_parser(argc, argv, self, *rb_ruby_prism_ptr()); } /* @@ -1543,26 +1587,7 @@ iseqw_s_compile(int argc, VALUE *argv, VALUE self) static VALUE iseqw_s_compile_prism(int argc, VALUE *argv, VALUE self) { - VALUE src, file = Qnil, path = Qnil, line = Qnil, opt = Qnil; - int i; - - i = rb_scan_args(argc, argv, "1*:", &src, NULL, &opt); - if (i > 4+NIL_P(opt)) rb_error_arity(argc, 1, 5); - switch (i) { - case 5: opt = argv[--i]; - case 4: line = argv[--i]; - case 3: path = argv[--i]; - case 2: file = argv[--i]; - } - - if (NIL_P(file)) file = rb_fstring_lit(""); - if (NIL_P(path)) path = file; - if (NIL_P(line)) line = INT2FIX(1); - - Check_Type(path, T_STRING); - Check_Type(file, T_STRING); - - return iseqw_new(pm_iseq_compile_with_option(src, file, path, line, opt)); + return iseqw_s_compile_parser(argc, argv, self, true); } /* @@ -1750,7 +1775,9 @@ iseqw_s_compile_option_get(VALUE self) static const rb_iseq_t * iseqw_check(VALUE iseqw) { - rb_iseq_t *iseq = DATA_PTR(iseqw); + rb_iseq_t **iseq_ptr; + TypedData_Get_Struct(iseqw, rb_iseq_t *, &iseqw_data_type, iseq_ptr); + rb_iseq_t *iseq = *iseq_ptr; if (!ISEQ_BODY(iseq)) { rb_ibf_load_iseq_complete(iseq); diff --git a/iseq.h b/iseq.h index ec5b145f43b5de..a0b59c441f6a8c 100644 --- a/iseq.h +++ b/iseq.h @@ -47,6 +47,10 @@ extern const ID rb_iseq_shared_exc_local_tbl[]; #define ISEQ_FLIP_CNT(iseq) ISEQ_BODY(iseq)->variable.flip_count +#define ISEQ_FROZEN_STRING_LITERAL_ENABLED 1 +#define ISEQ_FROZEN_STRING_LITERAL_DISABLED 0 +#define ISEQ_FROZEN_STRING_LITERAL_UNSET -1 + static inline rb_snum_t ISEQ_FLIP_CNT_INCREMENT(const rb_iseq_t *iseq) { @@ -172,6 +176,7 @@ void rb_iseq_init_trace(rb_iseq_t *iseq); int rb_iseq_add_local_tracepoint_recursively(const rb_iseq_t *iseq, rb_event_flag_t turnon_events, VALUE tpval, unsigned int target_line, bool target_bmethod); int rb_iseq_remove_local_tracepoint_recursively(const rb_iseq_t *iseq, VALUE tpval); const rb_iseq_t *rb_iseq_load_iseq(VALUE fname); +int rb_iseq_opt_frozen_string_literal(void); #if VM_INSN_INFO_TABLE_IMPL == 2 unsigned int *rb_iseq_insns_info_decode_positions(const struct rb_iseq_constant_body *body); @@ -226,7 +231,7 @@ struct rb_compile_option_struct { unsigned int specialized_instruction: 1; unsigned int operands_unification: 1; unsigned int instructions_unification: 1; - unsigned int frozen_string_literal: 1; + signed int frozen_string_literal: 2; /* -1: not specified, 0: false, 1: true */ unsigned int debug_frozen_string_literal: 1; unsigned int coverage_enabled: 1; int debug_level; diff --git a/kernel.rb b/kernel.rb index 9b853e9560c03d..541d0cfd9d11bf 100644 --- a/kernel.rb +++ b/kernel.rb @@ -58,10 +58,10 @@ def clone(freeze: nil) # a.freeze #=> ["a", "b", "c"] # a.frozen? #=> true #-- - # Determines if the object is frozen. Equivalent to \c Object\#frozen? in Ruby. - # \param[in] obj the object to be determines - # \retval Qtrue if frozen - # \retval Qfalse if not frozen + # Determines if the object is frozen. Equivalent to `Object#frozen?` in Ruby. + # @param[in] obj the object to be determines + # @retval Qtrue if frozen + # @retval Qfalse if not frozen #++ # def frozen? diff --git a/lib/bundled_gems.rb b/lib/bundled_gems.rb index 55286725c0fb45..e61c1ad231b297 100644 --- a/lib/bundled_gems.rb +++ b/lib/bundled_gems.rb @@ -26,6 +26,8 @@ module Gem::BUNDLED_GEMS "resolv-replace" => "3.4.0", "rinda" => "3.4.0", "syslog" => "3.4.0", + "ostruct" => "3.5.0", + "pstore" => "3.5.0", }.freeze EXACT = { @@ -41,6 +43,8 @@ module Gem::BUNDLED_GEMS "resolv-replace" => true, "rinda" => true, "syslog" => true, + "ostruct" => true, + "pstore" => true, }.freeze PREFIXED = { @@ -95,26 +99,23 @@ def self.find_gem(path) end def self.warning?(name, specs: nil) - feature = File.path(name) # name can be a feature name or a file path with String or Pathname - name = feature.tr("/", "-") + # name can be a feature name or a file path with String or Pathname + feature = File.path(name) + # bootsnap expand `require "csv"` to `require "#{LIBDIR}/csv.rb"` + name = feature.delete_prefix(LIBDIR).chomp(".rb").tr("/", "-") name.sub!(LIBEXT, "") return if specs.include?(name) _t, path = $:.resolve_feature_path(feature) if gem = find_gem(path) return if specs.include?(gem) - caller = caller_locations(3, 3).find {|c| c&.absolute_path} + caller = caller_locations(3, 3)&.find {|c| c&.absolute_path} return if find_gem(caller&.absolute_path) - elsif SINCE[name] + elsif SINCE[name] && !path gem = true else return end - # Warning feature is not working correctly with Bootsnap. - # caller_locations returns: - # lib/ruby/3.3.0+0/bundled_gems.rb:65:in `block (2 levels) in replace_require' - # $GEM_HOME/gems/bootsnap-1.17.0/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:32:in `require'" - # ... - return if caller_locations(2).find {|c| c&.path.match?(/bootsnap/) } + return if WARNED[name] WARNED[name] = true if gem == true @@ -134,11 +135,29 @@ def self.build_message(gem) if defined?(Bundler) msg += " Add #{gem} to your Gemfile or gemspec." + # We detect the gem name from caller_locations. We need to skip 2 frames like: # lib/ruby/3.3.0+0/bundled_gems.rb:90:in `warning?'", # lib/ruby/3.3.0+0/bundler/rubygems_integration.rb:247:in `block (2 levels) in replace_require'", - location = caller_locations(3,1)[0]&.path - if File.file?(location) && !location.start_with?(Gem::BUNDLED_GEMS::LIBDIR) + # + # Additionally, we need to skip Bootsnap and Zeitwerk if present, these + # gems decorate Kernel#require, so they are not really the ones issuing + # the require call users should be warned about. Those are upwards. + frames_to_skip = 2 + location = nil + Thread.each_caller_location do |cl| + if frames_to_skip >= 1 + frames_to_skip -= 1 + next + end + + if cl.base_label != "require" + location = cl.path + break + end + end + + if location && File.file?(location) && !location.start_with?(Gem::BUNDLED_GEMS::LIBDIR) caller_gem = nil Gem.path.each do |path| if location =~ %r{#{path}/gems/([\w\-\.]+)} diff --git a/lib/bundler/cli/plugin.rb b/lib/bundler/cli/plugin.rb index 1a33b5fc2727c3..fd61ef0d954dea 100644 --- a/lib/bundler/cli/plugin.rb +++ b/lib/bundler/cli/plugin.rb @@ -5,14 +5,15 @@ module Bundler class CLI::Plugin < Thor desc "install PLUGINS", "Install the plugin from the source" long_desc <<-D - Install plugins either from the rubygems source provided (with --source option) or from a git source provided with --git (for remote repos) or --local_git (for local repos). If no sources are provided, it uses Gem.sources + Install plugins either from the rubygems source provided (with --source option), from a git source provided with --git, or a local path provided with --path. If no sources are provided, it uses Gem.sources D method_option "source", type: :string, default: nil, banner: "URL of the RubyGems source to fetch the plugin from" method_option "version", type: :string, default: nil, banner: "The version of the plugin to fetch" method_option "git", type: :string, default: nil, banner: "URL of the git repo to fetch from" - method_option "local_git", type: :string, default: nil, banner: "Path of the local git repo to fetch from" + method_option "local_git", type: :string, default: nil, banner: "Path of the local git repo to fetch from (deprecated)" method_option "branch", type: :string, default: nil, banner: "The git branch to checkout" method_option "ref", type: :string, default: nil, banner: "The git revision to check out" + method_option "path", type: :string, default: nil, banner: "Path of a local gem to directly use" def install(*plugins) Bundler::Plugin.install(plugins, options) end diff --git a/lib/bundler/gem_version_promoter.rb b/lib/bundler/gem_version_promoter.rb index c7eacd193049a3..ecc65b49560292 100644 --- a/lib/bundler/gem_version_promoter.rb +++ b/lib/bundler/gem_version_promoter.rb @@ -45,17 +45,37 @@ def level=(value) # Given a Resolver::Package and an Array of Specifications of available # versions for a gem, this method will return the Array of Specifications - # sorted (and possibly truncated if strict is true) in an order to give - # preference to the current level (:major, :minor or :patch) when resolution - # is deciding what versions best resolve all dependencies in the bundle. + # sorted in an order to give preference to the current level (:major, :minor + # or :patch) when resolution is deciding what versions best resolve all + # dependencies in the bundle. # @param package [Resolver::Package] The package being resolved. # @param specs [Specification] An array of Specifications for the package. - # @return [Specification] A new instance of the Specification Array sorted and - # possibly filtered. + # @return [Specification] A new instance of the Specification Array sorted. def sort_versions(package, specs) - specs = filter_dep_specs(specs, package) if strict + locked_version = package.locked_version - sort_dep_specs(specs, package) + result = specs.sort do |a, b| + unless package.prerelease_specified? || pre? + a_pre = a.prerelease? + b_pre = b.prerelease? + + next 1 if a_pre && !b_pre + next -1 if b_pre && !a_pre + end + + if major? || locked_version.nil? + b <=> a + elsif either_version_older_than_locked?(a, b, locked_version) + b <=> a + elsif segments_do_not_match?(a, b, :major) + a <=> b + elsif !minor? && segments_do_not_match?(a, b, :minor) + a <=> b + else + b <=> a + end + end + post_sort(result, package.unlock?, locked_version) end # @return [bool] Convenience method for testing value of level variable. @@ -73,9 +93,18 @@ def pre? pre == true end - private + # Given a Resolver::Package and an Array of Specifications of available + # versions for a gem, this method will truncate the Array if strict + # is true. That means filtering out downgrades from the version currently + # locked, and filtering out upgrades that go past the selected level (major, + # minor, or patch). + # @param package [Resolver::Package] The package being resolved. + # @param specs [Specification] An array of Specifications for the package. + # @return [Specification] A new instance of the Specification Array + # truncated. + def filter_versions(package, specs) + return specs unless strict - def filter_dep_specs(specs, package) locked_version = package.locked_version return specs if locked_version.nil? || major? @@ -89,32 +118,7 @@ def filter_dep_specs(specs, package) end end - def sort_dep_specs(specs, package) - locked_version = package.locked_version - - result = specs.sort do |a, b| - unless package.prerelease_specified? || pre? - a_pre = a.prerelease? - b_pre = b.prerelease? - - next -1 if a_pre && !b_pre - next 1 if b_pre && !a_pre - end - - if major? || locked_version.nil? - a <=> b - elsif either_version_older_than_locked?(a, b, locked_version) - a <=> b - elsif segments_do_not_match?(a, b, :major) - b <=> a - elsif !minor? && segments_do_not_match?(a, b, :minor) - b <=> a - else - a <=> b - end - end - post_sort(result, package.unlock?, locked_version) - end + private def either_version_older_than_locked?(a, b, locked_version) a.version < locked_version || b.version < locked_version @@ -133,13 +137,13 @@ def post_sort(result, unlock, locked_version) if unlock || locked_version.nil? result else - move_version_to_end(result, locked_version) + move_version_to_beginning(result, locked_version) end end - def move_version_to_end(result, version) + def move_version_to_beginning(result, version) move, keep = result.partition {|s| s.version.to_s == version.to_s } - keep.concat(move) + move.concat(keep) end end end diff --git a/lib/bundler/man/bundle-add.1 b/lib/bundler/man/bundle-add.1 index f85d21f9599b83..a6cbc88f344e27 100644 --- a/lib/bundler/man/bundle-add.1 +++ b/lib/bundler/man/bundle-add.1 @@ -1,6 +1,6 @@ .\" generated with nRonn/v0.11.1 .\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-ADD" "1" "February 2024" "" +.TH "BUNDLE\-ADD" "1" "March 2024" "" .SH "NAME" \fBbundle\-add\fR \- Add gem to the Gemfile and run bundle install .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-binstubs.1 b/lib/bundler/man/bundle-binstubs.1 index dd0b3cd4e899bc..2b35bc956a7837 100644 --- a/lib/bundler/man/bundle-binstubs.1 +++ b/lib/bundler/man/bundle-binstubs.1 @@ -1,6 +1,6 @@ .\" generated with nRonn/v0.11.1 .\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-BINSTUBS" "1" "February 2024" "" +.TH "BUNDLE\-BINSTUBS" "1" "March 2024" "" .SH "NAME" \fBbundle\-binstubs\fR \- Install the binstubs of the listed gems .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-cache.1 b/lib/bundler/man/bundle-cache.1 index 8e39bb92c3c464..3b86b995a62689 100644 --- a/lib/bundler/man/bundle-cache.1 +++ b/lib/bundler/man/bundle-cache.1 @@ -1,6 +1,6 @@ .\" generated with nRonn/v0.11.1 .\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-CACHE" "1" "February 2024" "" +.TH "BUNDLE\-CACHE" "1" "March 2024" "" .SH "NAME" \fBbundle\-cache\fR \- Package your needed \fB\.gem\fR files into your application .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-check.1 b/lib/bundler/man/bundle-check.1 index 82920d71887425..7f18e265375288 100644 --- a/lib/bundler/man/bundle-check.1 +++ b/lib/bundler/man/bundle-check.1 @@ -1,6 +1,6 @@ .\" generated with nRonn/v0.11.1 .\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-CHECK" "1" "February 2024" "" +.TH "BUNDLE\-CHECK" "1" "March 2024" "" .SH "NAME" \fBbundle\-check\fR \- Verifies if dependencies are satisfied by installed gems .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-clean.1 b/lib/bundler/man/bundle-clean.1 index 04cf55275caea2..0180eb38a213ac 100644 --- a/lib/bundler/man/bundle-clean.1 +++ b/lib/bundler/man/bundle-clean.1 @@ -1,6 +1,6 @@ .\" generated with nRonn/v0.11.1 .\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-CLEAN" "1" "February 2024" "" +.TH "BUNDLE\-CLEAN" "1" "March 2024" "" .SH "NAME" \fBbundle\-clean\fR \- Cleans up unused gems in your bundler directory .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-config.1 b/lib/bundler/man/bundle-config.1 index 4b0728c213c2e1..b768f1e3d29ec6 100644 --- a/lib/bundler/man/bundle-config.1 +++ b/lib/bundler/man/bundle-config.1 @@ -1,6 +1,6 @@ .\" generated with nRonn/v0.11.1 .\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-CONFIG" "1" "February 2024" "" +.TH "BUNDLE\-CONFIG" "1" "March 2024" "" .SH "NAME" \fBbundle\-config\fR \- Set bundler configuration options .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-console.1 b/lib/bundler/man/bundle-console.1 index 467a375f89de92..1368a50eb163fa 100644 --- a/lib/bundler/man/bundle-console.1 +++ b/lib/bundler/man/bundle-console.1 @@ -1,6 +1,6 @@ .\" generated with nRonn/v0.11.1 .\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-CONSOLE" "1" "February 2024" "" +.TH "BUNDLE\-CONSOLE" "1" "March 2024" "" .SH "NAME" \fBbundle\-console\fR \- Deprecated way to open an IRB session with the bundle pre\-loaded .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-doctor.1 b/lib/bundler/man/bundle-doctor.1 index e77bafec29b46c..80eaf2a8882524 100644 --- a/lib/bundler/man/bundle-doctor.1 +++ b/lib/bundler/man/bundle-doctor.1 @@ -1,6 +1,6 @@ .\" generated with nRonn/v0.11.1 .\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-DOCTOR" "1" "February 2024" "" +.TH "BUNDLE\-DOCTOR" "1" "March 2024" "" .SH "NAME" \fBbundle\-doctor\fR \- Checks the bundle for common problems .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-exec.1 b/lib/bundler/man/bundle-exec.1 index 926675aa5fdb06..191863c045ba5f 100644 --- a/lib/bundler/man/bundle-exec.1 +++ b/lib/bundler/man/bundle-exec.1 @@ -1,6 +1,6 @@ .\" generated with nRonn/v0.11.1 .\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-EXEC" "1" "February 2024" "" +.TH "BUNDLE\-EXEC" "1" "March 2024" "" .SH "NAME" \fBbundle\-exec\fR \- Execute a command in the context of the bundle .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-gem.1 b/lib/bundler/man/bundle-gem.1 index 744c4917c7e152..464d8d11264553 100644 --- a/lib/bundler/man/bundle-gem.1 +++ b/lib/bundler/man/bundle-gem.1 @@ -1,6 +1,6 @@ .\" generated with nRonn/v0.11.1 .\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-GEM" "1" "February 2024" "" +.TH "BUNDLE\-GEM" "1" "March 2024" "" .SH "NAME" \fBbundle\-gem\fR \- Generate a project skeleton for creating a rubygem .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-help.1 b/lib/bundler/man/bundle-help.1 index 613ff261dde66a..3604ad6127d6bd 100644 --- a/lib/bundler/man/bundle-help.1 +++ b/lib/bundler/man/bundle-help.1 @@ -1,6 +1,6 @@ .\" generated with nRonn/v0.11.1 .\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-HELP" "1" "February 2024" "" +.TH "BUNDLE\-HELP" "1" "March 2024" "" .SH "NAME" \fBbundle\-help\fR \- Displays detailed help for each subcommand .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-info.1 b/lib/bundler/man/bundle-info.1 index 7df617d859cea3..647f5987befbbc 100644 --- a/lib/bundler/man/bundle-info.1 +++ b/lib/bundler/man/bundle-info.1 @@ -1,6 +1,6 @@ .\" generated with nRonn/v0.11.1 .\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-INFO" "1" "February 2024" "" +.TH "BUNDLE\-INFO" "1" "March 2024" "" .SH "NAME" \fBbundle\-info\fR \- Show information for the given gem in your bundle .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-init.1 b/lib/bundler/man/bundle-init.1 index 4a9c6b01a1b58f..2c41a3c7deff9a 100644 --- a/lib/bundler/man/bundle-init.1 +++ b/lib/bundler/man/bundle-init.1 @@ -1,6 +1,6 @@ .\" generated with nRonn/v0.11.1 .\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-INIT" "1" "February 2024" "" +.TH "BUNDLE\-INIT" "1" "March 2024" "" .SH "NAME" \fBbundle\-init\fR \- Generates a Gemfile into the current working directory .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-inject.1 b/lib/bundler/man/bundle-inject.1 index f88f6dbe605253..c7269db34d0a21 100644 --- a/lib/bundler/man/bundle-inject.1 +++ b/lib/bundler/man/bundle-inject.1 @@ -1,6 +1,6 @@ .\" generated with nRonn/v0.11.1 .\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-INJECT" "1" "February 2024" "" +.TH "BUNDLE\-INJECT" "1" "March 2024" "" .SH "NAME" \fBbundle\-inject\fR \- Add named gem(s) with version requirements to Gemfile .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-install.1 b/lib/bundler/man/bundle-install.1 index f41def305fe71b..3fa1a467e2b7ce 100644 --- a/lib/bundler/man/bundle-install.1 +++ b/lib/bundler/man/bundle-install.1 @@ -1,6 +1,6 @@ .\" generated with nRonn/v0.11.1 .\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-INSTALL" "1" "February 2024" "" +.TH "BUNDLE\-INSTALL" "1" "March 2024" "" .SH "NAME" \fBbundle\-install\fR \- Install the dependencies specified in your Gemfile .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-list.1 b/lib/bundler/man/bundle-list.1 index e5b56767518278..f91fd95739351f 100644 --- a/lib/bundler/man/bundle-list.1 +++ b/lib/bundler/man/bundle-list.1 @@ -1,6 +1,6 @@ .\" generated with nRonn/v0.11.1 .\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-LIST" "1" "February 2024" "" +.TH "BUNDLE\-LIST" "1" "March 2024" "" .SH "NAME" \fBbundle\-list\fR \- List all the gems in the bundle .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-lock.1 b/lib/bundler/man/bundle-lock.1 index a63c6fd5a5283a..f992f5ee5f0a78 100644 --- a/lib/bundler/man/bundle-lock.1 +++ b/lib/bundler/man/bundle-lock.1 @@ -1,6 +1,6 @@ .\" generated with nRonn/v0.11.1 .\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-LOCK" "1" "February 2024" "" +.TH "BUNDLE\-LOCK" "1" "March 2024" "" .SH "NAME" \fBbundle\-lock\fR \- Creates / Updates a lockfile without installing .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-open.1 b/lib/bundler/man/bundle-open.1 index 8cf0ec0e364805..53d3541555153b 100644 --- a/lib/bundler/man/bundle-open.1 +++ b/lib/bundler/man/bundle-open.1 @@ -1,6 +1,6 @@ .\" generated with nRonn/v0.11.1 .\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-OPEN" "1" "February 2024" "" +.TH "BUNDLE\-OPEN" "1" "March 2024" "" .SH "NAME" \fBbundle\-open\fR \- Opens the source directory for a gem in your bundle .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-outdated.1 b/lib/bundler/man/bundle-outdated.1 index 30e795748c0d24..f79eff5ae99808 100644 --- a/lib/bundler/man/bundle-outdated.1 +++ b/lib/bundler/man/bundle-outdated.1 @@ -1,6 +1,6 @@ .\" generated with nRonn/v0.11.1 .\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-OUTDATED" "1" "February 2024" "" +.TH "BUNDLE\-OUTDATED" "1" "March 2024" "" .SH "NAME" \fBbundle\-outdated\fR \- List installed gems with newer versions available .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-platform.1 b/lib/bundler/man/bundle-platform.1 index 4f5240c242c430..d2133ec4d3fecb 100644 --- a/lib/bundler/man/bundle-platform.1 +++ b/lib/bundler/man/bundle-platform.1 @@ -1,6 +1,6 @@ .\" generated with nRonn/v0.11.1 .\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-PLATFORM" "1" "February 2024" "" +.TH "BUNDLE\-PLATFORM" "1" "March 2024" "" .SH "NAME" \fBbundle\-platform\fR \- Displays platform compatibility information .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-plugin.1 b/lib/bundler/man/bundle-plugin.1 index f6ce023727ec73..cbdfac11b6bfb5 100644 --- a/lib/bundler/man/bundle-plugin.1 +++ b/lib/bundler/man/bundle-plugin.1 @@ -1,10 +1,10 @@ .\" generated with nRonn/v0.11.1 .\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-PLUGIN" "1" "February 2024" "" +.TH "BUNDLE\-PLUGIN" "1" "March 2024" "" .SH "NAME" \fBbundle\-plugin\fR \- Manage Bundler plugins .SH "SYNOPSIS" -\fBbundle plugin\fR install PLUGINS [\-\-source=\fISOURCE\fR] [\-\-version=\fIversion\fR] [\-\-git|\-\-local_git=\fIgit\-url\fR] [\-\-branch=\fIbranch\fR|\-\-ref=\fIrev\fR] +\fBbundle plugin\fR install PLUGINS [\-\-source=\fISOURCE\fR] [\-\-version=\fIversion\fR] [\-\-git=\fIgit\-url\fR] [\-\-branch=\fIbranch\fR|\-\-ref=\fIrev\fR] [\-\-path=\fIpath\fR] .br \fBbundle plugin\fR uninstall PLUGINS .br @@ -27,7 +27,7 @@ Install bundler\-graph gem from example\.com\. The global source, specified in s You can specify the version of the gem via \fB\-\-version\fR\. .TP \fBbundle plugin install bundler\-graph \-\-git https://github\.com/rubygems/bundler\-graph\fR -Install bundler\-graph gem from Git repository\. \fB\-\-git\fR can be replaced with \fB\-\-local\-git\fR\. You cannot use both \fB\-\-git\fR and \fB\-\-local\-git\fR\. You can use standard Git URLs like: +Install bundler\-graph gem from Git repository\. You can use standard Git URLs like: .IP \fBssh://[user@]host\.xz[:port]/path/to/repo\.git\fR .br @@ -37,7 +37,10 @@ Install bundler\-graph gem from Git repository\. \fB\-\-git\fR can be replaced w .br \fBfile:///path/to/repo\fR .IP -When you specify \fB\-\-git\fR/\fB\-\-local\-git\fR, you can use \fB\-\-branch\fR or \fB\-\-ref\fR to specify any branch, tag, or commit hash (revision) to use\. When you specify both, only the latter is used\. +When you specify \fB\-\-git\fR, you can use \fB\-\-branch\fR or \fB\-\-ref\fR to specify any branch, tag, or commit hash (revision) to use\. +.TP +\fBbundle plugin install bundler\-graph \-\-path \.\./bundler\-graph\fR +Install bundler\-graph gem from a local path\. .SS "uninstall" Uninstall the plugin(s) specified in PLUGINS\. .SS "list" diff --git a/lib/bundler/man/bundle-plugin.1.ronn b/lib/bundler/man/bundle-plugin.1.ronn index a11df4c1624da4..b0a34660ea84da 100644 --- a/lib/bundler/man/bundle-plugin.1.ronn +++ b/lib/bundler/man/bundle-plugin.1.ronn @@ -4,7 +4,8 @@ bundle-plugin(1) -- Manage Bundler plugins ## SYNOPSIS `bundle plugin` install PLUGINS [--source=] [--version=] - [--git|--local_git=] [--branch=|--ref=]
+ [--git=] [--branch=|--ref=] + [--path=]
`bundle plugin` uninstall PLUGINS
`bundle plugin` list
`bundle plugin` help [COMMAND] @@ -29,14 +30,17 @@ Install the given plugin(s). You can specify the version of the gem via `--version`. * `bundle plugin install bundler-graph --git https://github.com/rubygems/bundler-graph`: - Install bundler-graph gem from Git repository. `--git` can be replaced with `--local-git`. You cannot use both `--git` and `--local-git`. You can use standard Git URLs like: + Install bundler-graph gem from Git repository. You can use standard Git URLs like: `ssh://[user@]host.xz[:port]/path/to/repo.git`
`http[s]://host.xz[:port]/path/to/repo.git`
`/path/to/repo`
`file:///path/to/repo` - When you specify `--git`/`--local-git`, you can use `--branch` or `--ref` to specify any branch, tag, or commit hash (revision) to use. When you specify both, only the latter is used. + When you specify `--git`, you can use `--branch` or `--ref` to specify any branch, tag, or commit hash (revision) to use. + +* `bundle plugin install bundler-graph --path ../bundler-graph`: + Install bundler-graph gem from a local path. ### uninstall diff --git a/lib/bundler/man/bundle-pristine.1 b/lib/bundler/man/bundle-pristine.1 index 81a4758374fa86..faa04d76762a30 100644 --- a/lib/bundler/man/bundle-pristine.1 +++ b/lib/bundler/man/bundle-pristine.1 @@ -1,6 +1,6 @@ .\" generated with nRonn/v0.11.1 .\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-PRISTINE" "1" "February 2024" "" +.TH "BUNDLE\-PRISTINE" "1" "March 2024" "" .SH "NAME" \fBbundle\-pristine\fR \- Restores installed gems to their pristine condition .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-remove.1 b/lib/bundler/man/bundle-remove.1 index 1172577bca9d68..3f8cbbd9b684bf 100644 --- a/lib/bundler/man/bundle-remove.1 +++ b/lib/bundler/man/bundle-remove.1 @@ -1,6 +1,6 @@ .\" generated with nRonn/v0.11.1 .\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-REMOVE" "1" "February 2024" "" +.TH "BUNDLE\-REMOVE" "1" "March 2024" "" .SH "NAME" \fBbundle\-remove\fR \- Removes gems from the Gemfile .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-show.1 b/lib/bundler/man/bundle-show.1 index 338502ae358d18..bc72c6e3b6baf3 100644 --- a/lib/bundler/man/bundle-show.1 +++ b/lib/bundler/man/bundle-show.1 @@ -1,6 +1,6 @@ .\" generated with nRonn/v0.11.1 .\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-SHOW" "1" "February 2024" "" +.TH "BUNDLE\-SHOW" "1" "March 2024" "" .SH "NAME" \fBbundle\-show\fR \- Shows all the gems in your bundle, or the path to a gem .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-update.1 b/lib/bundler/man/bundle-update.1 index 8174d9b84133e9..d1284c2e72b54f 100644 --- a/lib/bundler/man/bundle-update.1 +++ b/lib/bundler/man/bundle-update.1 @@ -1,6 +1,6 @@ .\" generated with nRonn/v0.11.1 .\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-UPDATE" "1" "February 2024" "" +.TH "BUNDLE\-UPDATE" "1" "March 2024" "" .SH "NAME" \fBbundle\-update\fR \- Update your gems to the latest available versions .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-version.1 b/lib/bundler/man/bundle-version.1 index 5361b0493e2fee..05905e1347fc35 100644 --- a/lib/bundler/man/bundle-version.1 +++ b/lib/bundler/man/bundle-version.1 @@ -1,6 +1,6 @@ .\" generated with nRonn/v0.11.1 .\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-VERSION" "1" "February 2024" "" +.TH "BUNDLE\-VERSION" "1" "March 2024" "" .SH "NAME" \fBbundle\-version\fR \- Prints Bundler version information .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-viz.1 b/lib/bundler/man/bundle-viz.1 index ea726ceb499537..681563cd4c3d7f 100644 --- a/lib/bundler/man/bundle-viz.1 +++ b/lib/bundler/man/bundle-viz.1 @@ -1,6 +1,6 @@ .\" generated with nRonn/v0.11.1 .\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-VIZ" "1" "February 2024" "" +.TH "BUNDLE\-VIZ" "1" "March 2024" "" .SH "NAME" \fBbundle\-viz\fR \- Generates a visual dependency graph for your Gemfile .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle.1 b/lib/bundler/man/bundle.1 index 053b56d00a3b9c..1d2c780060f7c1 100644 --- a/lib/bundler/man/bundle.1 +++ b/lib/bundler/man/bundle.1 @@ -1,6 +1,6 @@ .\" generated with nRonn/v0.11.1 .\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE" "1" "February 2024" "" +.TH "BUNDLE" "1" "March 2024" "" .SH "NAME" \fBbundle\fR \- Ruby Dependency Management .SH "SYNOPSIS" diff --git a/lib/bundler/man/gemfile.5 b/lib/bundler/man/gemfile.5 index 6db532c34ae96d..39503f22a66233 100644 --- a/lib/bundler/man/gemfile.5 +++ b/lib/bundler/man/gemfile.5 @@ -1,6 +1,6 @@ .\" generated with nRonn/v0.11.1 .\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "GEMFILE" "5" "February 2024" "" +.TH "GEMFILE" "5" "March 2024" "" .SH "NAME" \fBGemfile\fR \- A format for describing gem dependencies for Ruby programs .SH "SYNOPSIS" diff --git a/lib/bundler/plugin/installer.rb b/lib/bundler/plugin/installer.rb index 256dcf526c5152..4f60862bb47f64 100644 --- a/lib/bundler/plugin/installer.rb +++ b/lib/bundler/plugin/installer.rb @@ -10,6 +10,7 @@ module Plugin class Installer autoload :Rubygems, File.expand_path("installer/rubygems", __dir__) autoload :Git, File.expand_path("installer/git", __dir__) + autoload :Path, File.expand_path("installer/path", __dir__) def install(names, options) check_sources_consistency!(options) @@ -18,8 +19,8 @@ def install(names, options) if options[:git] install_git(names, version, options) - elsif options[:local_git] - install_local_git(names, version, options) + elsif options[:path] + install_path(names, version, options[:path]) else sources = options[:source] || Gem.sources install_rubygems(names, version, sources) @@ -45,20 +46,40 @@ def check_sources_consistency!(options) if options.key?(:git) && options.key?(:local_git) raise InvalidOption, "Remote and local plugin git sources can't be both specified" end + + # back-compat; local_git is an alias for git + if options.key?(:local_git) + Bundler::SharedHelpers.major_deprecation(2, "--local_git is deprecated, use --git") + options[:git] = options.delete(:local_git) + end + + if (options.keys & [:source, :git, :path]).length > 1 + raise InvalidOption, "Only one of --source, --git, or --path may be specified" + end + + if (options.key?(:branch) || options.key?(:ref)) && !options.key?(:git) + raise InvalidOption, "--#{options.key?(:branch) ? "branch" : "ref"} can only be used with git sources" + end + + if options.key?(:branch) && options.key?(:ref) + raise InvalidOption, "--branch and --ref can't be both specified" + end end def install_git(names, version, options) - uri = options.delete(:git) - options["uri"] = uri + source_list = SourceList.new + source = source_list.add_git_source({ "uri" => options[:git], + "branch" => options[:branch], + "ref" => options[:ref] }) - install_all_sources(names, version, options, options[:source]) + install_all_sources(names, version, source_list, source) end - def install_local_git(names, version, options) - uri = options.delete(:local_git) - options["uri"] = uri + def install_path(names, version, path) + source_list = SourceList.new + source = source_list.add_path_source({ "path" => path, "root_path" => SharedHelpers.pwd }) - install_all_sources(names, version, options, options[:source]) + install_all_sources(names, version, source_list, source) end # Installs the plugin from rubygems source and returns the path where the @@ -70,16 +91,15 @@ def install_local_git(names, version, options) # # @return [Hash] map of names to the specs of plugins installed def install_rubygems(names, version, sources) - install_all_sources(names, version, nil, sources) - end - - def install_all_sources(names, version, git_source_options, rubygems_source) source_list = SourceList.new - source_list.add_git_source(git_source_options) if git_source_options - Array(rubygems_source).each {|remote| source_list.add_global_rubygems_remote(remote) } if rubygems_source + Array(sources).each {|remote| source_list.add_global_rubygems_remote(remote) } + + install_all_sources(names, version, source_list) + end - deps = names.map {|name| Dependency.new name, version } + def install_all_sources(names, version, source_list, source = nil) + deps = names.map {|name| Dependency.new(name, version, { "source" => source }) } Bundler.configure_gem_home_and_path(Plugin.root) diff --git a/lib/bundler/plugin/installer/path.rb b/lib/bundler/plugin/installer/path.rb new file mode 100644 index 00000000000000..58a8fa7426b448 --- /dev/null +++ b/lib/bundler/plugin/installer/path.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +module Bundler + module Plugin + class Installer + class Path < Bundler::Source::Path + def root + SharedHelpers.in_bundle? ? Bundler.root : Plugin.root + end + + def generate_bin(spec, disable_extensions = false) + # Need to find a way without code duplication + # For now, we can ignore this + end + end + end + end +end diff --git a/lib/bundler/plugin/source_list.rb b/lib/bundler/plugin/source_list.rb index 547661cf2f73da..746996de5548ac 100644 --- a/lib/bundler/plugin/source_list.rb +++ b/lib/bundler/plugin/source_list.rb @@ -9,6 +9,10 @@ def add_git_source(options = {}) add_source_to_list Plugin::Installer::Git.new(options), git_sources end + def add_path_source(options = {}) + add_source_to_list Plugin::Installer::Path.new(options), path_sources + end + def add_rubygems_source(options = {}) add_source_to_list Plugin::Installer::Rubygems.new(options), @rubygems_sources end @@ -17,10 +21,6 @@ def all_sources path_sources + git_sources + rubygems_sources + [metadata_source] end - def default_source - git_sources.first || global_rubygems_source - end - private def rubygems_aggregate_class diff --git a/lib/bundler/resolver.rb b/lib/bundler/resolver.rb index f60069f421ab08..1a6711ea6fcacd 100644 --- a/lib/bundler/resolver.rb +++ b/lib/bundler/resolver.rb @@ -50,26 +50,26 @@ def setup_solver specs[name] = matches.sort_by {|s| [s.version, s.platform.to_s] } end + @all_versions = Hash.new do |candidates, package| + candidates[package] = all_versions_for(package) + end + @sorted_versions = Hash.new do |candidates, package| - candidates[package] = if package.root? - [root_version] - else - all_versions_for(package).sort - end + candidates[package] = filtered_versions_for(package).sort end + @sorted_versions[root] = [root_version] + root_dependencies = prepare_dependencies(@requirements, @packages) @cached_dependencies = Hash.new do |dependencies, package| - dependencies[package] = if package.root? - { root_version => root_dependencies } - else - Hash.new do |versions, version| - versions[version] = to_dependency_hash(version.dependencies.reject {|d| d.name == package.name }, @packages) - end + dependencies[package] = Hash.new do |versions, version| + versions[version] = to_dependency_hash(version.dependencies.reject {|d| d.name == package.name }, @packages) end end + @cached_dependencies[root] = { root_version => root_dependencies } + logger = Bundler::UI::Shell.new logger.level = debug? ? "debug" : "warn" @@ -156,9 +156,15 @@ def parse_dependency(package, dependency) end def versions_for(package, range=VersionRange.any) - versions = range.select_versions(@sorted_versions[package]) + versions = select_sorted_versions(package, range) - sort_versions(package, versions) + # Conditional avoids (among other things) calling + # sort_versions_by_preferred with the root package + if versions.size > 1 + sort_versions_by_preferred(package, versions) + else + versions + end end def no_versions_incompatibility_for(package, unsatisfied_term) @@ -247,7 +253,7 @@ def all_versions_for(package) locked_requirement = base_requirements[name] results = filter_matching_specs(results, locked_requirement) if locked_requirement - versions = results.group_by(&:version).reduce([]) do |groups, (version, specs)| + results.group_by(&:version).reduce([]) do |groups, (version, specs)| platform_specs = package.platforms.map {|platform| select_best_platform_match(specs, platform) } # If package is a top-level dependency, @@ -274,8 +280,6 @@ def all_versions_for(package) groups end - - sort_versions(package, versions) end def source_for(name) @@ -334,6 +338,21 @@ def raise_not_found!(package) private + def filtered_versions_for(package) + @gem_version_promoter.filter_versions(package, @all_versions[package]) + end + + def raise_all_versions_filtered_out!(package) + level = @gem_version_promoter.level + name = package.name + locked_version = package.locked_version + requirement = package.dependency + + raise GemNotFound, + "#{name} is locked to #{locked_version}, while Gemfile is requesting #{requirement}. " \ + "--strict --#{level} was specified, but there are no #{level} level upgrades from #{locked_version} satisfying #{requirement}, so version solving has failed" + end + def filter_matching_specs(specs, requirements) Array(requirements).flat_map do |requirement| specs.select {| spec| requirement_satisfied_by?(requirement, spec) } @@ -357,12 +376,8 @@ def requirement_satisfied_by?(requirement, spec) requirement.satisfied_by?(spec.version) || spec.source.is_a?(Source::Gemspec) end - def sort_versions(package, versions) - if versions.size > 1 - @gem_version_promoter.sort_versions(package, versions).reverse - else - versions - end + def sort_versions_by_preferred(package, versions) + @gem_version_promoter.sort_versions(package, versions) end def repository_for(package) @@ -379,12 +394,19 @@ def prepare_dependencies(requirements, packages) next [dep_package, dep_constraint] if name == "bundler" - versions = versions_for(dep_package, dep_constraint.range) + dep_range = dep_constraint.range + versions = select_sorted_versions(dep_package, dep_range) if versions.empty? && dep_package.ignores_prereleases? + @all_versions.delete(dep_package) @sorted_versions.delete(dep_package) dep_package.consider_prereleases! - versions = versions_for(dep_package, dep_constraint.range) + versions = select_sorted_versions(dep_package, dep_range) end + + if versions.empty? && select_all_versions(dep_package, dep_range).any? + raise_all_versions_filtered_out!(dep_package) + end + next [dep_package, dep_constraint] unless versions.empty? next unless dep_package.current_platform? @@ -393,6 +415,14 @@ def prepare_dependencies(requirements, packages) end.compact.to_h end + def select_sorted_versions(package, range) + range.select_versions(@sorted_versions[package]) + end + + def select_all_versions(package, range) + range.select_versions(@all_versions[package]) + end + def other_specs_matching_message(specs, requirement) message = String.new("The source contains the following gems matching '#{requirement}':\n") message << specs.map {|s| " * #{s.full_name}" }.join("\n") diff --git a/lib/bundler/resolver/candidate.rb b/lib/bundler/resolver/candidate.rb index e695ef08ee0bde..9e8b9133358c69 100644 --- a/lib/bundler/resolver/candidate.rb +++ b/lib/bundler/resolver/candidate.rb @@ -15,7 +15,7 @@ class Resolver # considered separately. # # Some candidates may also keep some information explicitly about the - # package the refer to. These candidates are referred to as "canonical" and + # package they refer to. These candidates are referred to as "canonical" and # are used when materializing resolution results back into RubyGems # specifications that can be installed, written to lock files, and so on. # diff --git a/lib/bundler/self_manager.rb b/lib/bundler/self_manager.rb index 5accda4bcbaccc..bfd000b1a01da2 100644 --- a/lib/bundler/self_manager.rb +++ b/lib/bundler/self_manager.rb @@ -113,7 +113,7 @@ def resolve_update_version_from(target) end def local_specs - @local_specs ||= Bundler::Source::Rubygems.new("allow_local" => true).specs.select {|spec| spec.name == "bundler" } + @local_specs ||= Bundler::Source::Rubygems.new("allow_local" => true, "allow_cached" => true).specs.select {|spec| spec.name == "bundler" } end def remote_specs diff --git a/lib/bundler/settings.rb b/lib/bundler/settings.rb index d84089ee414201..379abfb24a633e 100644 --- a/lib/bundler/settings.rb +++ b/lib/bundler/settings.rb @@ -492,16 +492,19 @@ def load_config(config_file) valid_file = file.exist? && !file.size.zero? return {} unless valid_file serializer_class.load(file.read).inject({}) do |config, (k, v)| - if k.include?("-") - Bundler.ui.warn "Your #{file} config includes `#{k}`, which contains the dash character (`-`).\n" \ - "This is deprecated, because configuration through `ENV` should be possible, but `ENV` keys cannot include dashes.\n" \ - "Please edit #{file} and replace any dashes in configuration keys with a triple underscore (`___`)." + unless k.start_with?("#") + if k.include?("-") + Bundler.ui.warn "Your #{file} config includes `#{k}`, which contains the dash character (`-`).\n" \ + "This is deprecated, because configuration through `ENV` should be possible, but `ENV` keys cannot include dashes.\n" \ + "Please edit #{file} and replace any dashes in configuration keys with a triple underscore (`___`)." - # string hash keys are frozen - k = k.gsub("-", "___") + # string hash keys are frozen + k = k.gsub("-", "___") + end + + config[k] = v end - config[k] = v config end end diff --git a/lib/bundler/source/rubygems.rb b/lib/bundler/source/rubygems.rb index 861aa8bbebb687..04cfc0a85092eb 100644 --- a/lib/bundler/source/rubygems.rb +++ b/lib/bundler/source/rubygems.rb @@ -17,7 +17,7 @@ def initialize(options = {}) @remotes = [] @dependency_names = [] @allow_remote = false - @allow_cached = false + @allow_cached = options["allow_cached"] || false @allow_local = options["allow_local"] || false @checksum_store = Checksum::Store.new @@ -133,7 +133,7 @@ def specs # sources, and large_idx.merge! small_idx is way faster than # small_idx.merge! large_idx. index = @allow_remote ? remote_specs.dup : Index.new - index.merge!(cached_specs) if @allow_cached || @allow_remote + index.merge!(cached_specs) if @allow_cached index.merge!(installed_specs) if @allow_local index end diff --git a/lib/bundler/source_list.rb b/lib/bundler/source_list.rb index 4419695b7ff1ab..d85e1c1c013806 100644 --- a/lib/bundler/source_list.rb +++ b/lib/bundler/source_list.rb @@ -9,7 +9,7 @@ class SourceList :metadata_source def global_rubygems_source - @global_rubygems_source ||= rubygems_aggregate_class.new("allow_local" => true) + @global_rubygems_source ||= rubygems_aggregate_class.new("allow_local" => true, "allow_cached" => true) end def initialize @@ -174,7 +174,7 @@ def global_replacement_source(replacement_sources) replacement_source = replacement_sources.find {|s| s == global_rubygems_source } return global_rubygems_source unless replacement_source - replacement_source.local! + replacement_source.cached! replacement_source end diff --git a/lib/did_you_mean/jaro_winkler.rb b/lib/did_you_mean/jaro_winkler.rb index 56db130af45993..9a3e57f6d7214d 100644 --- a/lib/did_you_mean/jaro_winkler.rb +++ b/lib/did_you_mean/jaro_winkler.rb @@ -8,8 +8,7 @@ def distance(str1, str2) m = 0.0 t = 0.0 - range = (length2 / 2).floor - 1 - range = 0 if range < 0 + range = length2 > 3 ? length2 / 2 - 1 : 0 flags1 = 0 flags2 = 0 @@ -72,10 +71,8 @@ def distance(str1, str2) codepoints2 = str2.codepoints prefix_bonus = 0 - i = 0 str1.each_codepoint do |char1| - char1 == codepoints2[i] && i < 4 ? prefix_bonus += 1 : break - i += 1 + char1 == codepoints2[prefix_bonus] && prefix_bonus < 4 ? prefix_bonus += 1 : break end jaro_distance + (prefix_bonus * WEIGHT * (1 - jaro_distance)) diff --git a/lib/did_you_mean/spell_checkers/key_error_checker.rb b/lib/did_you_mean/spell_checkers/key_error_checker.rb index be4bea7789c379..955bff1be63fd0 100644 --- a/lib/did_you_mean/spell_checkers/key_error_checker.rb +++ b/lib/did_you_mean/spell_checkers/key_error_checker.rb @@ -14,7 +14,15 @@ def corrections private def exact_matches - @exact_matches ||= @keys.select { |word| @key == word.to_s }.map(&:inspect) + @exact_matches ||= @keys.select { |word| @key == word.to_s }.map { |obj| format_object(obj) } + end + + def format_object(symbol_or_object) + if symbol_or_object.is_a?(Symbol) + ":#{symbol_or_object}" + else + symbol_or_object.to_s + end end end end diff --git a/lib/did_you_mean/spell_checkers/pattern_key_name_checker.rb b/lib/did_you_mean/spell_checkers/pattern_key_name_checker.rb index ed263c8f937320..622d4dee258cb8 100644 --- a/lib/did_you_mean/spell_checkers/pattern_key_name_checker.rb +++ b/lib/did_you_mean/spell_checkers/pattern_key_name_checker.rb @@ -14,7 +14,15 @@ def corrections private def exact_matches - @exact_matches ||= @keys.select { |word| @key == word.to_s }.map(&:inspect) + @exact_matches ||= @keys.select { |word| @key == word.to_s }.map { |obj| format_object(obj) } + end + + def format_object(symbol_or_object) + if symbol_or_object.is_a?(Symbol) + ":#{symbol_or_object}" + else + symbol_or_object.to_s + end end end end diff --git a/lib/ipaddr.rb b/lib/ipaddr.rb index a6734455321f64..c1073ecd2bc4e1 100644 --- a/lib/ipaddr.rb +++ b/lib/ipaddr.rb @@ -471,6 +471,20 @@ def netmask _to_string(@mask_addr) end + # Returns the wildcard mask in string format e.g. 0.0.255.255 + def wildcard_mask + case @family + when Socket::AF_INET + mask = IN4MASK ^ @mask_addr + when Socket::AF_INET6 + mask = IN6MASK ^ @mask_addr + else + raise AddressFamilyError, "unsupported address family" + end + + _to_string(mask) + end + # Returns the IPv6 zone identifier, if present. # Raises InvalidAddressError if not an IPv6 address. def zone_id diff --git a/lib/irb.rb b/lib/irb.rb index 0c481ff1dc76ca..99fd1c5df01bb8 100644 --- a/lib/irb.rb +++ b/lib/irb.rb @@ -1,5 +1,6 @@ # frozen_string_literal: true -# + +# :markup: markdown # irb.rb - irb main module # by Keiju ISHITSUKA(keiju@ruby-lang.org) # @@ -22,545 +23,550 @@ require_relative "irb/debug" require_relative "irb/pager" -# == \IRB +# ## IRB # -# \Module \IRB ("Interactive Ruby") provides a shell-like interface -# that supports user interaction with the Ruby interpreter. +# Module IRB ("Interactive Ruby") provides a shell-like interface that supports +# user interaction with the Ruby interpreter. # -# It operates as a read-eval-print loop -# ({REPL}[https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop]) +# It operates as a *read-eval-print loop* +# ([REPL](https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop)) # that: # -# - _Reads_ each character as you type. -# You can modify the \IRB context to change the way input works. -# See {Input}[rdoc-ref:IRB@Input]. -# - _Evaluates_ the code each time it has read a syntactically complete passage. -# - _Prints_ after evaluating. -# You can modify the \IRB context to change the way output works. -# See {Output}[rdoc-ref:IRB@Output]. +# * ***Reads*** each character as you type. You can modify the IRB context to +# change the way input works. See [Input](rdoc-ref:IRB@Input). +# * ***Evaluates*** the code each time it has read a syntactically complete +# passage. +# * ***Prints*** after evaluating. You can modify the IRB context to change +# the way output works. See [Output](rdoc-ref:IRB@Output). +# # # Example: # -# $ irb -# irb(main):001> File.basename(Dir.pwd) -# => "irb" -# irb(main):002> Dir.entries('.').size -# => 25 -# irb(main):003* Dir.entries('.').select do |entry| -# irb(main):004* entry.start_with?('R') -# irb(main):005> end -# => ["README.md", "Rakefile"] +# $ irb +# irb(main):001> File.basename(Dir.pwd) +# => "irb" +# irb(main):002> Dir.entries('.').size +# => 25 +# irb(main):003* Dir.entries('.').select do |entry| +# irb(main):004* entry.start_with?('R') +# irb(main):005> end +# => ["README.md", "Rakefile"] +# +# The typed input may also include [\IRB-specific +# commands](rdoc-ref:IRB@IRB-Specific+Commands). # -# The typed input may also include -# {\IRB-specific commands}[rdoc-ref:IRB@IRB-Specific+Commands]. +# As seen above, you can start IRB by using the shell command `irb`. # -# As seen above, you can start \IRB by using the shell command +irb+. +# You can stop an IRB session by typing command `exit`: # -# You can stop an \IRB session by typing command +exit+: +# irb(main):006> exit +# $ # -# irb(main):006> exit -# $ +# At that point, IRB calls any hooks found in array `IRB.conf[:AT_EXIT]`, then +# exits. # -# At that point, \IRB calls any hooks found in array IRB.conf[:AT_EXIT], -# then exits. +# ## Startup # -# == Startup +# At startup, IRB: # -# At startup, \IRB: +# 1. Interprets (as Ruby code) the content of the [configuration +# file](rdoc-ref:IRB@Configuration+File) (if given). +# 2. Constructs the initial session context from [hash +# IRB.conf](rdoc-ref:IRB@Hash+IRB.conf) and from default values; the hash +# content may have been affected by [command-line +# options](rdoc-ref:IB@Command-Line+Options), and by direct assignments in +# the configuration file. +# 3. Assigns the context to variable `conf`. +# 4. Assigns command-line arguments to variable `ARGV`. +# 5. Prints the [prompt](rdoc-ref:IRB@Prompt+and+Return+Formats). +# 6. Puts the content of the [initialization +# script](rdoc-ref:IRB@Initialization+Script) onto the IRB shell, just as if +# it were user-typed commands. # -# 1. Interprets (as Ruby code) the content of the -# {configuration file}[rdoc-ref:IRB@Configuration+File] (if given). -# 1. Constructs the initial session context -# from {hash IRB.conf}[rdoc-ref:IRB@Hash+IRB.conf] and from default values; -# the hash content may have been affected -# by {command-line options}[rdoc-ref:IB@Command-Line+Options], -# and by direct assignments in the configuration file. -# 1. Assigns the context to variable +conf+. -# 1. Assigns command-line arguments to variable ARGV. -# 1. Prints the {prompt}[rdoc-ref:IRB@Prompt+and+Return+Formats]. -# 1. Puts the content of the -# {initialization script}[rdoc-ref:IRB@Initialization+Script] -# onto the \IRB shell, just as if it were user-typed commands. # -# === The Command Line +# ### The Command Line # -# On the command line, all options precede all arguments; -# the first item that is not recognized as an option is treated as an argument, -# as are all items that follow. +# On the command line, all options precede all arguments; the first item that is +# not recognized as an option is treated as an argument, as are all items that +# follow. # -# ==== Command-Line Options +# #### Command-Line Options # -# Many command-line options affect entries in hash IRB.conf, -# which in turn affect the initial configuration of the \IRB session. +# Many command-line options affect entries in hash `IRB.conf`, which in turn +# affect the initial configuration of the IRB session. # # Details of the options are described in the relevant subsections below. # -# A cursory list of the \IRB command-line options -# may be seen in the {help message}[https://raw.githubusercontent.com/ruby/irb/master/lib/irb/lc/help-message], -# which is also displayed if you use command-line option --help. +# A cursory list of the IRB command-line options may be seen in the [help +# message](https://raw.githubusercontent.com/ruby/irb/master/lib/irb/lc/help-message), +# which is also displayed if you use command-line option `--help`. # # If you are interested in a specific option, consult the -# {index}[rdoc-ref:doc/irb/indexes.md@Index+of+Command-Line+Options]. +# [index](rdoc-ref:doc/irb/indexes.md@Index+of+Command-Line+Options). # -# ==== Command-Line Arguments +# #### Command-Line Arguments # -# Command-line arguments are passed to \IRB in array +ARGV+: +# Command-line arguments are passed to IRB in array `ARGV`: # -# $ irb --noscript Foo Bar Baz -# irb(main):001> ARGV -# => ["Foo", "Bar", "Baz"] -# irb(main):002> exit -# $ +# $ irb --noscript Foo Bar Baz +# irb(main):001> ARGV +# => ["Foo", "Bar", "Baz"] +# irb(main):002> exit +# $ # -# Command-line option -- causes everything that follows -# to be treated as arguments, even those that look like options: +# Command-line option `--` causes everything that follows to be treated as +# arguments, even those that look like options: # -# $ irb --noscript -- --noscript -- Foo Bar Baz -# irb(main):001> ARGV -# => ["--noscript", "--", "Foo", "Bar", "Baz"] -# irb(main):002> exit -# $ +# $ irb --noscript -- --noscript -- Foo Bar Baz +# irb(main):001> ARGV +# => ["--noscript", "--", "Foo", "Bar", "Baz"] +# irb(main):002> exit +# $ # -# === Configuration File +# ### Configuration File # -# You can initialize \IRB via a configuration file. +# You can initialize IRB via a *configuration file*. # -# If command-line option -f is given, -# no configuration file is looked for. +# If command-line option `-f` is given, no configuration file is looked for. # -# Otherwise, \IRB reads and interprets a configuration file -# if one is available. +# Otherwise, IRB reads and interprets a configuration file if one is available. # # The configuration file can contain any Ruby code, and can usefully include # user code that: # -# - Can then be debugged in \IRB. -# - Configures \IRB itself. -# - Requires or loads files. +# * Can then be debugged in IRB. +# * Configures IRB itself. +# * Requires or loads files. +# # # The path to the configuration file is the first found among: # -# - The value of variable $IRBRC, if defined. -# - The value of variable $XDG_CONFIG_HOME/irb/irbrc, if defined. -# - File $HOME/.irbrc, if it exists. -# - File $HOME/.config/irb/irbrc, if it exists. -# - File +.config/irb/irbrc+ in the current directory, if it exists. -# - File +.irbrc+ in the current directory, if it exists. -# - File +irb.rc+ in the current directory, if it exists. -# - File +_irbrc+ in the current directory, if it exists. -# - File $irbrc in the current directory, if it exists. +# * The value of variable `$IRBRC`, if defined. +# * The value of variable `$XDG_CONFIG_HOME/irb/irbrc`, if defined. +# * File `$HOME/.irbrc`, if it exists. +# * File `$HOME/.config/irb/irbrc`, if it exists. +# * File `.irbrc` in the current directory, if it exists. +# * File `irb.rc` in the current directory, if it exists. +# * File `_irbrc` in the current directory, if it exists. +# * File `$irbrc` in the current directory, if it exists. +# # # If the search fails, there is no configuration file. # -# If the search succeeds, the configuration file is read as Ruby code, -# and so can contain any Ruby programming you like. +# If the search succeeds, the configuration file is read as Ruby code, and so +# can contain any Ruby programming you like. # -# \Method conf.rc? returns +true+ if a configuration file was read, -# +false+ otherwise. -# \Hash entry IRB.conf[:RC] also contains that value. +# Method `conf.rc?` returns `true` if a configuration file was read, `false` +# otherwise. Hash entry `IRB.conf[:RC]` also contains that value. # -# === \Hash IRB.conf +# ### Hash `IRB.conf` # -# The initial entries in hash IRB.conf are determined by: +# The initial entries in hash `IRB.conf` are determined by: # -# - Default values. -# - Command-line options, which may override defaults. -# - Direct assignments in the configuration file. +# * Default values. +# * Command-line options, which may override defaults. +# * Direct assignments in the configuration file. # -# You can see the hash by typing IRB.conf. # -# Details of the entries' meanings are described in the relevant subsections below. +# You can see the hash by typing `IRB.conf`. +# +# Details of the entries' meanings are described in the relevant subsections +# below. # # If you are interested in a specific entry, consult the -# {index}[rdoc-ref:doc/irb/indexes.md@Index+of+IRB.conf+Entries]. +# [index](rdoc-ref:doc/irb/indexes.md@Index+of+IRB.conf+Entries). +# +# ### Notes on Initialization Precedence # -# === Notes on Initialization Precedence +# * Any conflict between an entry in hash `IRB.conf` and a command-line option +# is resolved in favor of the hash entry. +# * Hash `IRB.conf` affects the context only once, when the configuration file +# is interpreted; any subsequent changes to it do not affect the context and +# are therefore essentially meaningless. # -# - Any conflict between an entry in hash IRB.conf and a command-line option -# is resolved in favor of the hash entry. -# - \Hash IRB.conf affects the context only once, -# when the configuration file is interpreted; -# any subsequent changes to it do not affect the context -# and are therefore essentially meaningless. # -# === Initialization Script +# ### Initialization Script # -# By default, the first command-line argument (after any options) -# is the path to a Ruby initialization script. +# By default, the first command-line argument (after any options) is the path to +# a Ruby initialization script. # -# \IRB reads the initialization script and puts its content onto the \IRB shell, +# IRB reads the initialization script and puts its content onto the IRB shell, # just as if it were user-typed commands. # -# Command-line option --noscript causes the first command-line argument -# to be treated as an ordinary argument (instead of an initialization script); -# --script is the default. +# Command-line option `--noscript` causes the first command-line argument to be +# treated as an ordinary argument (instead of an initialization script); +# `--script` is the default. # -# == Input +# ## Input # -# This section describes the features that allow you to change -# the way \IRB input works; -# see also {Input and Output}[rdoc-ref:IRB@Input+and+Output]. +# This section describes the features that allow you to change the way IRB input +# works; see also [Input and Output](rdoc-ref:IRB@Input+and+Output). # -# === Input Command History +# ### Input Command History # -# By default, \IRB stores a history of up to 1000 input commands in a -# file named .irb_history. The history file will be in the same directory -# as the {configuration file}[rdoc-ref:IRB@Configuration+File] if one is found, or -# in ~/ otherwise. +# By default, IRB stores a history of up to 1000 input commands in a file named +# `.irb_history`. The history file will be in the same directory as the +# [configuration file](rdoc-ref:IRB@Configuration+File) if one is found, or in +# `~/` otherwise. # -# A new \IRB session creates the history file if it does not exist, -# and appends to the file if it does exist. +# A new IRB session creates the history file if it does not exist, and appends +# to the file if it does exist. # # You can change the filepath by adding to your configuration file: -# IRB.conf[:HISTORY_FILE] = _filepath_, -# where _filepath_ is a string filepath. +# `IRB.conf[:HISTORY_FILE] = *filepath*`, where *filepath* is a string filepath. # -# During the session, method conf.history_file returns the filepath, -# and method conf.history_file = new_filepath -# copies the history to the file at new_filepath, -# which becomes the history file for the session. +# During the session, method `conf.history_file` returns the filepath, and +# method `conf.history_file = *new_filepath*` copies the history to the file at +# *new_filepath*, which becomes the history file for the session. # -# You can change the number of commands saved by adding to your configuration file: -# IRB.conf[:SAVE_HISTORY] = _n_, -# where _n_ is one of: +# You can change the number of commands saved by adding to your configuration +# file: `IRB.conf[:SAVE_HISTORY] = *n*`, wheHISTORY_FILEre *n* is one of: # -# - Positive integer: the number of commands to be saved, -# - Zero: all commands are to be saved. -# - +nil+: no commands are to be saved,. +# * Positive integer: the number of commands to be saved, +# * Zero: all commands are to be saved. +# * `nil`: no commands are to be saved,. # -# During the session, you can use -# methods conf.save_history or conf.save_history= -# to retrieve or change the count. # -# === Command Aliases +# During the session, you can use methods `conf.save_history` or +# `conf.save_history=` to retrieve or change the count. # -# By default, \IRB defines several command aliases: +# ### Command Aliases # -# irb(main):001> conf.command_aliases -# => {:"$"=>:show_source, :"@"=>:whereami} +# By default, IRB defines several command aliases: +# +# irb(main):001> conf.command_aliases +# => {:"$"=>:show_source, :"@"=>:whereami} # # You can change the initial aliases in the configuration file with: # -# IRB.conf[:COMMAND_ALIASES] = {foo: :show_source, bar: :whereami} +# IRB.conf[:COMMAND_ALIASES] = {foo: :show_source, bar: :whereami} # -# You can replace the current aliases at any time -# with configuration method conf.command_aliases=; -# Because conf.command_aliases is a hash, -# you can modify it. +# You can replace the current aliases at any time with configuration method +# `conf.command_aliases=`; Because `conf.command_aliases` is a hash, you can +# modify it. # -# === End-of-File +# ### End-of-File # -# By default, IRB.conf[:IGNORE_EOF] is +false+, -# which means that typing the end-of-file character Ctrl-D -# causes the session to exit. +# By default, `IRB.conf[:IGNORE_EOF]` is `false`, which means that typing the +# end-of-file character `Ctrl-D` causes the session to exit. # -# You can reverse that behavior by adding IRB.conf[:IGNORE_EOF] = true -# to the configuration file. +# You can reverse that behavior by adding `IRB.conf[:IGNORE_EOF] = true` to the +# configuration file. # -# During the session, method conf.ignore_eof? returns the setting, -# and method conf.ignore_eof = _boolean_ sets it. +# During the session, method `conf.ignore_eof?` returns the setting, and method +# `conf.ignore_eof = *boolean*` sets it. # -# === SIGINT +# ### SIGINT # -# By default, IRB.conf[:IGNORE_SIGINT] is +true+, -# which means that typing the interrupt character Ctrl-C -# causes the session to exit. +# By default, `IRB.conf[:IGNORE_SIGINT]` is `true`, which means that typing the +# interrupt character `Ctrl-C` causes the session to exit. # -# You can reverse that behavior by adding IRB.conf[:IGNORE_SIGING] = false -# to the configuration file. +# You can reverse that behavior by adding `IRB.conf[:IGNORE_SIGING] = false` to +# the configuration file. # -# During the session, method conf.ignore_siging? returns the setting, -# and method conf.ignore_sigint = _boolean_ sets it. +# During the session, method `conf.ignore_siging?` returns the setting, and +# method `conf.ignore_sigint = *boolean*` sets it. # -# === Automatic Completion +# ### Automatic Completion # -# By default, \IRB enables -# {automatic completion}[https://en.wikipedia.org/wiki/Autocomplete#In_command-line_interpreters]: +# By default, IRB enables [automatic +# completion](https://en.wikipedia.org/wiki/Autocomplete#In_command-line_interpr +# eters): # # You can disable it by either of these: # -# - Adding IRB.conf[:USE_AUTOCOMPLETE] = false to the configuration file. -# - Giving command-line option --noautocomplete -# (--autocomplete is the default). +# * Adding `IRB.conf[:USE_AUTOCOMPLETE] = false` to the configuration file. +# * Giving command-line option `--noautocomplete` (`--autocomplete` is the +# default). +# # -# \Method conf.use_autocomplete? returns +true+ -# if automatic completion is enabled, +false+ otherwise. +# Method `conf.use_autocomplete?` returns `true` if automatic completion is +# enabled, `false` otherwise. # # The setting may not be changed during the session. # -# === Automatic Indentation +# ### Automatic Indentation # -# By default, \IRB automatically indents lines of code to show structure -# (e.g., it indent the contents of a block). +# By default, IRB automatically indents lines of code to show structure (e.g., +# it indent the contents of a block). # -# The current setting is returned -# by the configuration method conf.auto_indent_mode. +# The current setting is returned by the configuration method +# `conf.auto_indent_mode`. # -# The default initial setting is +true+: +# The default initial setting is `true`: # -# irb(main):001> conf.auto_indent_mode -# => true -# irb(main):002* Dir.entries('.').select do |entry| -# irb(main):003* entry.start_with?('R') -# irb(main):004> end -# => ["README.md", "Rakefile"] +# irb(main):001> conf.auto_indent_mode +# => true +# irb(main):002* Dir.entries('.').select do |entry| +# irb(main):003* entry.start_with?('R') +# irb(main):004> end +# => ["README.md", "Rakefile"] # -# You can change the initial setting in the -# configuration file with: +# You can change the initial setting in the configuration file with: # -# IRB.conf[:AUTO_INDENT] = false +# IRB.conf[:AUTO_INDENT] = false # -# Note that the _current_ setting may not be changed in the \IRB session. +# Note that the *current* setting *may not* be changed in the IRB session. # -# === Input \Method +# ### Input Method # -# The \IRB input method determines how command input is to be read; -# by default, the input method for a session is IRB::RelineInputMethod. +# The IRB input method determines how command input is to be read; by default, +# the input method for a session is IRB::RelineInputMethod. # # You can set the input method by: # -# - Adding to the configuration file: +# * Adding to the configuration file: +# +# * `IRB.conf[:USE_SINGLELINE] = true` or `IRB.conf[:USE_MULTILINE]= +# false` sets the input method to IRB::ReadlineInputMethod. +# * `IRB.conf[:USE_SINGLELINE] = false` or `IRB.conf[:USE_MULTILINE] = +# true` sets the input method to IRB::RelineInputMethod. +# # -# - IRB.conf[:USE_SINGLELINE] = true -# or IRB.conf[:USE_MULTILINE]= false -# sets the input method to IRB::ReadlineInputMethod. -# - IRB.conf[:USE_SINGLELINE] = false -# or IRB.conf[:USE_MULTILINE] = true -# sets the input method to IRB::RelineInputMethod. +# * Giving command-line options: # -# - Giving command-line options: +# * `--singleline` or `--nomultiline` sets the input method to +# IRB::ReadlineInputMethod. +# * `--nosingleline` or `--multiline` sets the input method to +# IRB::RelineInputMethod. # -# - --singleline -# or --nomultiline -# sets the input method to IRB::ReadlineInputMethod. -# - --nosingleline -# or --multiline/tt> -# sets the input method to IRB::RelineInputMethod. # -# \Method conf.use_multiline? -# and its synonym conf.use_reline return: # -# - +true+ if option --multiline was given. -# - +false+ if option --nomultiline was given. -# - +nil+ if neither was given. +# Method `conf.use_multiline?` and its synonym `conf.use_reline` return: # -# \Method conf.use_singleline? -# and its synonym conf.use_readline return: +# * `true` if option `--multiline` was given. +# * `false` if option `--nomultiline` was given. +# * `nil` if neither was given. # -# - +true+ if option --singleline was given. -# - +false+ if option --nosingleline was given. -# - +nil+ if neither was given. # -# == Output +# Method `conf.use_singleline?` and its synonym `conf.use_readline` return: # -# This section describes the features that allow you to change -# the way \IRB output works; -# see also {Input and Output}[rdoc-ref:IRB@Input+and+Output]. +# * `true` if option `--singleline` was given. +# * `false` if option `--nosingleline` was given. +# * `nil` if neither was given. # -# === Return-Value Printing (Echoing) # -# By default, \IRB prints (echoes) the values returned by all input commands. +# ## Output +# +# This section describes the features that allow you to change the way IRB +# output works; see also [Input and Output](rdoc-ref:IRB@Input+and+Output). +# +# ### Return-Value Printing (Echoing) +# +# By default, IRB prints (echoes) the values returned by all input commands. # # You can change the initial behavior and suppress all echoing by: # -# - Adding to the configuration file: IRB.conf[:ECHO] = false. -# (The default value for this entry is +nil+, which means the same as +true+.) -# - Giving command-line option --noecho. -# (The default is --echo.) +# * Adding to the configuration file: `IRB.conf[:ECHO] = false`. (The default +# value for this entry is `nil`, which means the same as `true`.) +# * Giving command-line option `--noecho`. (The default is `--echo`.) +# # -# During the session, you can change the current setting -# with configuration method conf.echo= (set to +true+ or +false+). +# During the session, you can change the current setting with configuration +# method `conf.echo=` (set to `true` or `false`). # -# As stated above, by default \IRB prints the values returned by all input commands; -# but \IRB offers special treatment for values returned by assignment statements, -# which may be: +# As stated above, by default IRB prints the values returned by all input +# commands; but IRB offers special treatment for values returned by assignment +# statements, which may be: # -# - Printed with truncation (to fit on a single line of output), -# which is the default; -# an ellipsis (... is suffixed, to indicate the truncation): +# * Printed with truncation (to fit on a single line of output), which is the +# default; an ellipsis (`...` is suffixed, to indicate the truncation): # -# irb(main):001> x = 'abc' * 100 -# => "abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabc... +# irb(main):001> x = 'abc' * 100 +# +# +# > "abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabc... +# +# * Printed in full (regardless of the length). +# * Suppressed (not printed at all) # -# - Printed in full (regardless of the length). -# - Suppressed (not printed at all) # # You can change the initial behavior by: # -# - Adding to the configuration file: IRB.conf[:ECHO_ON_ASSIGNMENT] = false. -# (The default value for this entry is +niL+, which means the same as +:truncate+.) -# - Giving command-line option --noecho-on-assignment -# or --echo-on-assignment. -# (The default is --truncate-echo-on-assignment.) +# * Adding to the configuration file: `IRB.conf[:ECHO_ON_ASSIGNMENT] = false`. +# (The default value for this entry is `niL`, which means the same as +# `:truncate`.) +# * Giving command-line option `--noecho-on-assignment` or +# `--echo-on-assignment`. (The default is `--truncate-echo-on-assignment`.) # -# During the session, you can change the current setting -# with configuration method conf.echo_on_assignment= -# (set to +true+, +false+, or +:truncate+). # -# By default, \IRB formats returned values by calling method +inspect+. +# During the session, you can change the current setting with configuration +# method `conf.echo_on_assignment=` (set to `true`, `false`, or `:truncate`). +# +# By default, IRB formats returned values by calling method `inspect`. # # You can change the initial behavior by: # -# - Adding to the configuration file: IRB.conf[:INSPECT_MODE] = false. -# (The default value for this entry is +true+.) -# - Giving command-line option --noinspect. -# (The default is --inspect.) +# * Adding to the configuration file: `IRB.conf[:INSPECT_MODE] = false`. (The +# default value for this entry is `true`.) +# * Giving command-line option `--noinspect`. (The default is `--inspect`.) +# # -# During the session, you can change the setting using method conf.inspect_mode=. +# During the session, you can change the setting using method +# `conf.inspect_mode=`. # -# === Multiline Output +# ### Multiline Output # -# By default, \IRB prefixes a newline to a multiline response. +# By default, IRB prefixes a newline to a multiline response. # # You can change the initial default value by adding to the configuration file: # -# IRB.conf[:NEWLINE_BEFORE_MULTILINE_OUTPUT] = false +# IRB.conf[:NEWLINE_BEFORE_MULTILINE_OUTPUT] = false # -# During a session, you can retrieve or set the value using -# methods conf.newline_before_multiline_output? -# and conf.newline_before_multiline_output=. +# During a session, you can retrieve or set the value using methods +# `conf.newline_before_multiline_output?` and +# `conf.newline_before_multiline_output=`. # # Examples: # -# irb(main):001> conf.inspect_mode = false -# => false -# irb(main):002> "foo\nbar" -# => -# foo -# bar -# irb(main):003> conf.newline_before_multiline_output = false -# => false -# irb(main):004> "foo\nbar" -# => foo -# bar +# irb(main):001> conf.inspect_mode = false +# => false +# irb(main):002> "foo\nbar" +# => +# foo +# bar +# irb(main):003> conf.newline_before_multiline_output = false +# => false +# irb(main):004> "foo\nbar" +# => foo +# bar # -# === Evaluation History +# ### Evaluation History # -# By default, \IRB saves no history of evaluations (returned values), -# and the related methods conf.eval_history, _, -# and __ are undefined. +# By default, IRB saves no history of evaluations (returned values), and the +# related methods `conf.eval_history`, `_`, and `__` are undefined. # -# You can turn on that history, and set the maximum number of evaluations to be stored: +# You can turn on that history, and set the maximum number of evaluations to be +# stored: # -# - In the configuration file: add IRB.conf[:EVAL_HISTORY] = _n_. -# (Examples below assume that we've added IRB.conf[:EVAL_HISTORY] = 5.) -# - In the session (at any time): conf.eval_history = _n_. +# * In the configuration file: add `IRB.conf[:EVAL_HISTORY] = *n*`. (Examples +# below assume that we've added `IRB.conf[:EVAL_HISTORY] = 5`.) +# * In the session (at any time): `conf.eval_history = *n*`. # -# If +n+ is zero, all evaluation history is stored. +# +# If `n` is zero, all evaluation history is stored. # # Doing either of the above: # -# - Sets the maximum size of the evaluation history; -# defines method conf.eval_history, -# which returns the maximum size +n+ of the evaluation history: -# -# irb(main):001> conf.eval_history = 5 -# => 5 -# irb(main):002> conf.eval_history -# => 5 -# -# - Defines variable _, which contains the most recent evaluation, -# or +nil+ if none; same as method conf.last_value: -# -# irb(main):003> _ -# => 5 -# irb(main):004> :foo -# => :foo -# irb(main):005> :bar -# => :bar -# irb(main):006> _ -# => :bar -# irb(main):007> _ -# => :bar -# -# - Defines variable __: -# -# - __ unadorned: contains all evaluation history: -# -# irb(main):008> :foo -# => :foo -# irb(main):009> :bar -# => :bar -# irb(main):010> :baz -# => :baz -# irb(main):011> :bat -# => :bat -# irb(main):012> :bam -# => :bam -# irb(main):013> __ -# => -# 9 :bar -# 10 :baz -# 11 :bat -# 12 :bam -# irb(main):014> __ -# => -# 10 :baz -# 11 :bat -# 12 :bam -# 13 ...self-history... -# -# Note that when the evaluation is multiline, it is displayed differently. -# -# - __[_m_]: -# -# - Positive _m_: contains the evaluation for the given line number, -# or +nil+ if that line number is not in the evaluation history: -# -# irb(main):015> __[12] -# => :bam -# irb(main):016> __[1] -# => nil -# -# - Negative _m_: contains the +mth+-from-end evaluation, -# or +nil+ if that evaluation is not in the evaluation history: -# -# irb(main):017> __[-3] -# => :bam -# irb(main):018> __[-13] -# => nil -# -# - Zero _m_: contains +nil+: -# -# irb(main):019> __[0] -# => nil -# -# === Prompt and Return Formats -# -# By default, \IRB uses the prompt and return value formats -# defined in its +:DEFAULT+ prompt mode. -# -# ==== The Default Prompt and Return Format +# * Sets the maximum size of the evaluation history; defines method +# `conf.eval_history`, which returns the maximum size `n` of the evaluation +# history: +# +# irb(main):001> conf.eval_history = 5 +# => 5 +# irb(main):002> conf.eval_history +# => 5 +# +# * Defines variable `_`, which contains the most recent evaluation, or `nil` +# if none; same as method `conf.last_value`: +# +# irb(main):003> _ +# => 5 +# irb(main):004> :foo +# => :foo +# irb(main):005> :bar +# => :bar +# irb(main):006> _ +# => :bar +# irb(main):007> _ +# => :bar +# +# * Defines variable `__`: +# +# * `__` unadorned: contains all evaluation history: +# +# irb(main):008> :foo +# => :foo +# irb(main):009> :bar +# => :bar +# irb(main):010> :baz +# => :baz +# irb(main):011> :bat +# => :bat +# irb(main):012> :bam +# => :bam +# irb(main):013> __ +# => +# 9 :bar +# 10 :baz +# 11 :bat +# 12 :bam +# irb(main):014> __ +# => +# 10 :baz +# 11 :bat +# 12 :bam +# 13 ...self-history... +# +# Note that when the evaluation is multiline, it is displayed +# differently. +# +# * `__[`*m*`]`: +# +# * Positive *m*: contains the evaluation for the given line number, +# or `nil` if that line number is not in the evaluation history: +# +# irb(main):015> __[12] +# => :bam +# irb(main):016> __[1] +# => nil +# +# * Negative *m*: contains the `mth`-from-end evaluation, or `nil` if +# that evaluation is not in the evaluation history: +# +# irb(main):017> __[-3] +# => :bam +# irb(main):018> __[-13] +# => nil +# +# * Zero *m*: contains `nil`: +# +# irb(main):019> __[0] +# => nil +# +# +# +# +# ### Prompt and Return Formats +# +# By default, IRB uses the prompt and return value formats defined in its +# `:DEFAULT` prompt mode. +# +# #### The Default Prompt and Return Format # # The default prompt and return values look like this: # -# irb(main):001> 1 + 1 -# => 2 -# irb(main):002> 2 + 2 -# => 4 +# irb(main):001> 1 + 1 +# => 2 +# irb(main):002> 2 + 2 +# => 4 # # The prompt includes: # -# - The name of the running program (irb); -# see {IRB Name}[rdoc-ref:IRB@IRB+Name]. -# - The name of the current session (main); -# See {IRB Sessions}[rdoc-ref:IRB@IRB+Sessions]. -# - A 3-digit line number (1-based). +# * The name of the running program (`irb`); see [IRB +# Name](rdoc-ref:IRB@IRB+Name). +# * The name of the current session (`main`); See [IRB +# Sessions](rdoc-ref:IRB@IRB+Sessions). +# * A 3-digit line number (1-based). +# # # The default prompt actually defines three formats: # -# - One for most situations (as above): +# * One for most situations (as above): +# +# irb(main):003> Dir +# => Dir # -# irb(main):003> Dir -# => Dir +# * One for when the typed command is a statement continuation (adds trailing +# asterisk): # -# - One for when the typed command is a statement continuation (adds trailing asterisk): +# irb(main):004* Dir. # -# irb(main):004* Dir. +# * One for when the typed command is a string continuation (adds trailing +# single-quote): # -# - One for when the typed command is a string continuation (adds trailing single-quote): +# irb(main):005' Dir.entries('. # -# irb(main):005' Dir.entries('. # # You can see the prompt change as you type the characters in the following: # @@ -569,258 +575,266 @@ # irb(main):003> end # => ["README.md", "Rakefile"] # -# ==== Pre-Defined Prompts +# #### Pre-Defined Prompts # -# \IRB has several pre-defined prompts, stored in hash IRB.conf[:PROMPT]: +# IRB has several pre-defined prompts, stored in hash `IRB.conf[:PROMPT]`: # -# irb(main):001> IRB.conf[:PROMPT].keys -# => [:NULL, :DEFAULT, :CLASSIC, :SIMPLE, :INF_RUBY, :XMP] +# irb(main):001> IRB.conf[:PROMPT].keys +# => [:NULL, :DEFAULT, :CLASSIC, :SIMPLE, :INF_RUBY, :XMP] # -# To see the full data for these, type IRB.conf[:PROMPT]. +# To see the full data for these, type `IRB.conf[:PROMPT]`. # -# Most of these prompt definitions include specifiers that represent -# values like the \IRB name, session name, and line number; -# see {Prompt Specifiers}[rdoc-ref:IRB@Prompt+Specifiers]. +# Most of these prompt definitions include specifiers that represent values like +# the IRB name, session name, and line number; see [Prompt +# Specifiers](rdoc-ref:IRB@Prompt+Specifiers). # # You can change the initial prompt and return format by: # -# - Adding to the configuration file: IRB.conf[:PROMPT] = _mode_ -# where _mode_ is the symbol name of a prompt mode. -# - Giving a command-line option: +# * Adding to the configuration file: `IRB.conf[:PROMPT] = *mode*` where +# *mode* is the symbol name of a prompt mode. +# * Giving a command-line option: +# +# * `--prompt *mode*`: sets the prompt mode to *mode*. where *mode* is the +# symbol name of a prompt mode. +# * `--simple-prompt` or `--sample-book-mode`: sets the prompt mode to +# `:SIMPLE`. +# * `--inf-ruby-mode`: sets the prompt mode to `:INF_RUBY` and suppresses +# both `--multiline` and `--singleline`. +# * `--noprompt`: suppresses prompting; does not affect echoing. +# # -# - --prompt _mode_: sets the prompt mode to _mode_. -# where _mode_ is the symbol name of a prompt mode. -# - --simple-prompt or --sample-book-mode: -# sets the prompt mode to +:SIMPLE+. -# - --inf-ruby-mode: sets the prompt mode to +:INF_RUBY+ -# and suppresses both --multiline and --singleline. -# - --noprompt: suppresses prompting; does not affect echoing. # # You can retrieve or set the current prompt mode with methods # -# conf.prompt_mode and conf.prompt_mode=. +# `conf.prompt_mode` and `conf.prompt_mode=`. # # If you're interested in prompts and return formats other than the defaults, # you might experiment by trying some of the others. # -# ==== Custom Prompts +# #### Custom Prompts +# +# You can also define custom prompts and return formats, which may be done +# either in an IRB session or in the configuration file. # -# You can also define custom prompts and return formats, -# which may be done either in an \IRB session or in the configuration file. +# A prompt in IRB actually defines three prompts, as seen above. For simple +# custom data, we'll make all three the same: # -# A prompt in \IRB actually defines three prompts, as seen above. -# For simple custom data, we'll make all three the same: +# irb(main):001* IRB.conf[:PROMPT][:MY_PROMPT] = { +# irb(main):002* PROMPT_I: ': ', +# irb(main):003* PROMPT_C: ': ', +# irb(main):004* PROMPT_S: ': ', +# irb(main):005* RETURN: '=> ' +# irb(main):006> } +# => {:PROMPT_I=>": ", :PROMPT_C=>": ", :PROMPT_S=>": ", :RETURN=>"=> "} # -# irb(main):001* IRB.conf[:PROMPT][:MY_PROMPT] = { -# irb(main):002* PROMPT_I: ': ', -# irb(main):003* PROMPT_C: ': ', -# irb(main):004* PROMPT_S: ': ', -# irb(main):005* RETURN: '=> ' -# irb(main):006> } -# => {:PROMPT_I=>": ", :PROMPT_C=>": ", :PROMPT_S=>": ", :RETURN=>"=> "} +# If you define the custom prompt in the configuration file, you can also make +# it the current prompt by adding: # -# If you define the custom prompt in the configuration file, -# you can also make it the current prompt by adding: +# IRB.conf[:PROMPT_MODE] = :MY_PROMPT # -# IRB.conf[:PROMPT_MODE] = :MY_PROMPT +# Regardless of where it's defined, you can make it the current prompt in a +# session: # -# Regardless of where it's defined, you can make it the current prompt in a session: +# conf.prompt_mode = :MY_PROMPT # -# conf.prompt_mode = :MY_PROMPT +# You can view or modify the current prompt data with various configuration +# methods: # -# You can view or modify the current prompt data with various configuration methods: +# * `conf.prompt_mode`, `conf.prompt_mode=`. +# * `conf.prompt_c`, `conf.c=`. +# * `conf.prompt_i`, `conf.i=`. +# * `conf.prompt_s`, `conf.s=`. +# * `conf.return_format`, `return_format=`. # -# - conf.prompt_mode, conf.prompt_mode=. -# - conf.prompt_c, conf.c=. -# - conf.prompt_i, conf.i=. -# - conf.prompt_s, conf.s=. -# - conf.return_format, return_format=. # -# ==== Prompt Specifiers +# #### Prompt Specifiers # -# A prompt's definition can include specifiers for which certain values are substituted: +# A prompt's definition can include specifiers for which certain values are +# substituted: # -# - %N: the name of the running program. -# - %m: the value of self.to_s. -# - %M: the value of self.inspect. -# - %l: an indication of the type of string; -# one of ", ', /, ]. -# - NNi: Indentation level. -# - NNn: Line number. -# - %%: Literal %. +# * `%N`: the name of the running program. +# * `%m`: the value of `self.to_s`. +# * `%M`: the value of `self.inspect`. +# * `%l`: an indication of the type of string; one of `"`, `'`, `/`, `]`. +# * `*NN*i`: Indentation level. +# * `*NN*n`: Line number. +# * `%%`: Literal `%`. # -# === Verbosity # -# By default, \IRB verbosity is disabled, which means that output is smaller +# ### Verbosity +# +# By default, IRB verbosity is disabled, which means that output is smaller # rather than larger. # # You can enable verbosity by: # -# - Adding to the configuration file: IRB.conf[:VERBOSE] = true -# (the default is +nil+). -# - Giving command-line options --verbose -# (the default is --noverbose). +# * Adding to the configuration file: `IRB.conf[:VERBOSE] = true` (the default +# is `nil`). +# * Giving command-line options `--verbose` (the default is `--noverbose`). +# # # During a session, you can retrieve or set verbosity with methods -# conf.verbose and conf.verbose=. +# `conf.verbose` and `conf.verbose=`. # -# === Help +# ### Help # -# Command-line option --version causes \IRB to print its help text -# and exit. +# Command-line option `--version` causes IRB to print its help text and exit. # -# === Version +# ### Version # -# Command-line option --version causes \IRB to print its version text -# and exit. +# Command-line option `--version` causes IRB to print its version text and exit. # -# == Input and Output +# ## Input and Output # -# === \Color Highlighting +# ### Color Highlighting # -# By default, \IRB color highlighting is enabled, and is used for both: +# By default, IRB color highlighting is enabled, and is used for both: +# +# * Input: As you type, IRB reads the typed characters and highlights elements +# that it recognizes; it also highlights errors such as mismatched +# parentheses. +# * Output: IRB highlights syntactical elements. # -# - Input: As you type, \IRB reads the typed characters and highlights -# elements that it recognizes; -# it also highlights errors such as mismatched parentheses. -# - Output: \IRB highlights syntactical elements. # # You can disable color highlighting by: # -# - Adding to the configuration file: IRB.conf[:USE_COLORIZE] = false -# (the default value is +true+). -# - Giving command-line option --nocolorize +# * Adding to the configuration file: `IRB.conf[:USE_COLORIZE] = false` (the +# default value is `true`). +# * Giving command-line option `--nocolorize` +# # -# == Debugging +# ## Debugging # -# Command-line option -d sets variables $VERBOSE -# and $DEBUG to +true+; -# these have no effect on \IRB output. +# Command-line option `-d` sets variables `$VERBOSE` and `$DEBUG` to `true`; +# these have no effect on IRB output. # -# === Warnings +# ### Warnings # -# Command-line option -w suppresses warnings. +# Command-line option `-w` suppresses warnings. # -# Command-line option -W[_level_] -# sets warning level; 0=silence, 1=medium, 2=verbose. +# Command-line option `-W[*level*]` sets warning level; # -# == Other Features +# * 0=silence +# * 1=medium +# * 2=verbose # -# === Load Modules +# ## Other Features +# +# ### Load Modules # # You can specify the names of modules that are to be required at startup. # -# \Array conf.load_modules determines the modules (if any) -# that are to be required during session startup. -# The array is used only during session startup, -# so the initial value is the only one that counts. +# Array `conf.load_modules` determines the modules (if any) that are to be +# required during session startup. The array is used only during session +# startup, so the initial value is the only one that counts. # -# The default initial value is [] (load no modules): +# The default initial value is `[]` (load no modules): # -# irb(main):001> conf.load_modules -# => [] +# irb(main):001> conf.load_modules +# => [] # # You can set the default initial value via: # -# - Command-line option -r +# * Command-line option `-r` +# +# $ irb -r csv -r json +# irb(main):001> conf.load_modules +# => ["csv", "json"] # -# $ irb -r csv -r json -# irb(main):001> conf.load_modules -# => ["csv", "json"] +# * Hash entry `IRB.conf[:LOAD_MODULES] = *array*`: # -# - \Hash entry IRB.conf[:LOAD_MODULES] = _array_: +# IRB.conf[:LOAD_MODULES] = %w[csv, json] # -# IRB.conf[:LOAD_MODULES] = %w[csv, json] # # Note that the configuration file entry overrides the command-line options. # -# === RI Documentation Directories +# ### RI Documentation Directories # -# You can specify the paths to RI documentation directories -# that are to be loaded (in addition to the default directories) at startup; -# see details about RI by typing ri --help. +# You can specify the paths to RI documentation directories that are to be +# loaded (in addition to the default directories) at startup; see details about +# RI by typing `ri --help`. # -# \Array conf.extra_doc_dirs determines the directories (if any) -# that are to be loaded during session startup. -# The array is used only during session startup, +# Array `conf.extra_doc_dirs` determines the directories (if any) that are to be +# loaded during session startup. The array is used only during session startup, # so the initial value is the only one that counts. # -# The default initial value is [] (load no extra documentation): +# The default initial value is `[]` (load no extra documentation): # -# irb(main):001> conf.extra_doc_dirs -# => [] +# irb(main):001> conf.extra_doc_dirs +# => [] # # You can set the default initial value via: # -# - Command-line option --extra_doc_dir +# * Command-line option `--extra_doc_dir` # -# $ irb --extra-doc-dir your_doc_dir --extra-doc-dir my_doc_dir -# irb(main):001> conf.extra_doc_dirs -# => ["your_doc_dir", "my_doc_dir"] +# $ irb --extra-doc-dir your_doc_dir --extra-doc-dir my_doc_dir +# irb(main):001> conf.extra_doc_dirs +# => ["your_doc_dir", "my_doc_dir"] # -# - \Hash entry IRB.conf[:EXTRA_DOC_DIRS] = _array_: +# * Hash entry `IRB.conf[:EXTRA_DOC_DIRS] = *array*`: +# +# IRB.conf[:EXTRA_DOC_DIRS] = %w[your_doc_dir my_doc_dir] # -# IRB.conf[:EXTRA_DOC_DIRS] = %w[your_doc_dir my_doc_dir] # # Note that the configuration file entry overrides the command-line options. # -# === \IRB Name +# ### IRB Name # -# You can specify a name for \IRB. +# You can specify a name for IRB. # -# The default initial value is 'irb': +# The default initial value is `'irb'`: # -# irb(main):001> conf.irb_name -# => "irb" +# irb(main):001> conf.irb_name +# => "irb" # -# You can set the default initial value -# via hash entry IRB.conf[:IRB_NAME] = _string_: +# You can set the default initial value via hash entry `IRB.conf[:IRB_NAME] = +# *string*`: # -# IRB.conf[:IRB_NAME] = 'foo' +# IRB.conf[:IRB_NAME] = 'foo' # -# === Application Name +# ### Application Name # -# You can specify an application name for the \IRB session. +# You can specify an application name for the IRB session. # -# The default initial value is 'irb': +# The default initial value is `'irb'`: # -# irb(main):001> conf.ap_name -# => "irb" +# irb(main):001> conf.ap_name +# => "irb" # -# You can set the default initial value -# via hash entry IRB.conf[:AP_NAME] = _string_: +# You can set the default initial value via hash entry `IRB.conf[:AP_NAME] = +# *string*`: # -# IRB.conf[:AP_NAME] = 'my_ap_name' +# IRB.conf[:AP_NAME] = 'my_ap_name' # -# === Configuration Monitor +# ### Configuration Monitor # -# You can monitor changes to the configuration by assigning a proc -# to IRB.conf[:IRB_RC] in the configuration file: +# You can monitor changes to the configuration by assigning a proc to +# `IRB.conf[:IRB_RC]` in the configuration file: # -# IRB.conf[:IRB_RC] = proc {|conf| puts conf.class } +# IRB.conf[:IRB_RC] = proc {|conf| puts conf.class } # -# Each time the configuration is changed, -# that proc is called with argument +conf+: +# Each time the configuration is changed, that proc is called with argument +# `conf`: # -# === Encodings +# ### Encodings # -# Command-line option -E _ex_[:_in_] -# sets initial external (ex) and internal (in) encodings. +# Command-line option `-E *ex*[:*in*]` sets initial external (ex) and internal +# (in) encodings. # -# Command-line option -U sets both to UTF-8. +# Command-line option `-U` sets both to UTF-8. # -# === Commands +# ### Commands # # Please use the `help` command to see the list of available commands. # -# === IRB Sessions +# ### IRB Sessions # # IRB has a special feature, that allows you to manage many sessions at once. # # You can create new sessions with Irb.irb, and get a list of current sessions -# with the +jobs+ command in the prompt. +# with the `jobs` command in the prompt. # -# ==== Configuration +# #### Configuration # # The command line options, or IRB.conf, specify the default behavior of # Irb.irb. @@ -828,32 +842,33 @@ # On the other hand, each conf in IRB@Command-Line+Options is used to # individually configure IRB.irb. # -# If a proc is set for IRB.conf[:IRB_RC], its will be invoked after execution +# If a proc is set for `IRB.conf[:IRB_RC]`, its will be invoked after execution # of that proc with the context of the current session as its argument. Each # session can be configured using this mechanism. # -# ==== Session variables +# #### Session variables # # There are a few variables in every Irb session that can come in handy: # -# _:: -# The value command executed, as a local variable -# __:: -# The history of evaluated commands. Available only if -# IRB.conf[:EVAL_HISTORY] is not +nil+ (which is the default). -# See also IRB::Context#eval_history= and IRB::History. -# __[line_no]:: -# Returns the evaluation value at the given line number, +line_no+. -# If +line_no+ is a negative, the return value +line_no+ many lines before -# the most recent return value. +# `_` +# : The value command executed, as a local variable +# `__` +# : The history of evaluated commands. Available only if +# `IRB.conf[:EVAL_HISTORY]` is not `nil` (which is the default). See also +# IRB::Context#eval_history= and IRB::History. +# `__[line_no]` +# : Returns the evaluation value at the given line number, `line_no`. If +# `line_no` is a negative, the return value `line_no` many lines before the +# most recent return value. +# # -# == Restrictions +# ## Restrictions # -# Ruby code typed into \IRB behaves the same as Ruby code in a file, except that: +# Ruby code typed into IRB behaves the same as Ruby code in a file, except that: # -# - Because \IRB evaluates input immediately after it is syntactically complete, -# some results may be slightly different. -# - Forking may not be well behaved. +# * Because IRB evaluates input immediately after it is syntactically +# complete, some results may be slightly different. +# * Forking may not be well behaved. # module IRB @@ -862,14 +877,14 @@ class Abort < Exception;end # The current IRB::Context of the session, see IRB.conf # - # irb - # irb(main):001:0> IRB.CurrentContext.irb_name = "foo" - # foo(main):002:0> IRB.conf[:MAIN_CONTEXT].irb_name #=> "foo" - def IRB.CurrentContext + # irb + # irb(main):001:0> IRB.CurrentContext.irb_name = "foo" + # foo(main):002:0> IRB.conf[:MAIN_CONTEXT].irb_name #=> "foo" + def IRB.CurrentContext # :nodoc: IRB.conf[:MAIN_CONTEXT] end - # Initializes IRB and creates a new Irb.irb object at the +TOPLEVEL_BINDING+ + # Initializes IRB and creates a new Irb.irb object at the `TOPLEVEL_BINDING` def IRB.start(ap_path = nil) STDOUT.sync = true $0 = File::basename(ap_path, ".rb") if ap_path @@ -885,22 +900,22 @@ def IRB.start(ap_path = nil) end # Quits irb - def IRB.irb_exit(*) + def IRB.irb_exit(*) # :nodoc: throw :IRB_EXIT, false end # Aborts then interrupts irb. # - # Will raise an Abort exception, or the given +exception+. - def IRB.irb_abort(irb, exception = Abort) + # Will raise an Abort exception, or the given `exception`. + def IRB.irb_abort(irb, exception = Abort) # :nodoc: irb.context.thread.raise exception, "abort then interrupt!" end class Irb # Note: instance and index assignment expressions could also be written like: - # "foo.bar=(1)" and "foo.[]=(1, bar)", when expressed that way, the former - # be parsed as :assign and echo will be suppressed, but the latter is - # parsed as a :method_add_arg and the output won't be suppressed + # "foo.bar=(1)" and "foo.[]=(1, bar)", when expressed that way, the former be + # parsed as :assign and echo will be suppressed, but the latter is parsed as a + # :method_add_arg and the output won't be suppressed PROMPT_MAIN_TRUNCATE_LENGTH = 32 PROMPT_MAIN_TRUNCATE_OMISSION = '...' @@ -914,13 +929,14 @@ class Irb # Creates a new irb session def initialize(workspace = nil, input_method = nil) @context = Context.new(self, workspace, input_method) - @context.workspace.load_commands_to_main + @context.workspace.load_helper_methods_to_main @signal_status = :IN_IRB @scanner = RubyLex.new @line_no = 1 end - # A hook point for `debug` command's breakpoint after :IRB_EXIT as well as its clean-up + # A hook point for `debug` command's breakpoint after :IRB_EXIT as well as its + # clean-up def debug_break # it means the debug integration has been activated if defined?(DEBUGGER__) && DEBUGGER__.respond_to?(:capture_frames_without_irb) @@ -934,17 +950,19 @@ def debug_break def debug_readline(binding) workspace = IRB::WorkSpace.new(binding) context.replace_workspace(workspace) - context.workspace.load_commands_to_main + context.workspace.load_helper_methods_to_main @line_no += 1 # When users run: - # 1. Debugging commands, like `step 2` - # 2. Any input that's not irb-command, like `foo = 123` + # 1. Debugging commands, like `step 2` + # 2. Any input that's not irb-command, like `foo = 123` # - # Irb#eval_input will simply return the input, and we need to pass it to the debugger. + # + # Irb#eval_input will simply return the input, and we need to pass it to the + # debugger. input = if IRB.conf[:SAVE_HISTORY] && context.io.support_history_saving? - # Previous IRB session's history has been saved when `Irb#run` is exited - # We need to make sure the saved history is not saved again by resetting the counter + # Previous IRB session's history has been saved when `Irb#run` is exited We need + # to make sure the saved history is not saved again by resetting the counter context.io.reset_history_counter begin @@ -1004,12 +1022,27 @@ def eval_input each_top_level_statement do |statement, line_no| signal_status(:IN_EVAL) do begin - # If the integration with debugger is activated, we return certain input if it should be dealt with by debugger + # If the integration with debugger is activated, we return certain input if it + # should be dealt with by debugger if @context.with_debugger && statement.should_be_handled_by_debugger? return statement.code end - @context.evaluate(statement.evaluable_code, line_no) + case statement + when Statement::EmptyInput + # Do nothing + when Statement::Expression + @context.evaluate(statement.code, line_no) + when Statement::Command + ret = statement.command_class.execute(@context, statement.arg) + # TODO: Remove this output once we have a better way to handle it + # This is to notify `debug`'s test framework that the current input has been processed + # We also need to have a way to restart/stop threads around command execution + # when being used as `debug`'s console. + # https://github.com/ruby/debug/blob/master/lib/debug/irb_integration.rb#L8-L13 + puts "INTERNAL_INFO: {}" if @context.with_debugger && ENV['RUBY_DEBUG_TEST_UI'] == 'terminal' + @context.set_last_value(ret) + end if @context.echo? && !statement.suppresses_echo? if statement.is_assignment? @@ -1065,9 +1098,7 @@ def readmultiline end code << line - - # Accept any single-line input for symbol aliases or commands that transform args - return code if single_line_command?(code) + return code if command?(code) tokens, opens, terminated = @scanner.check_code_state(code, local_variables: @context.local_variables) return code if terminated @@ -1094,23 +1125,36 @@ def build_statement(code) end code.force_encoding(@context.io.encoding) - command_or_alias, arg = code.split(/\s/, 2) - # Transform a non-identifier alias (@, $) or keywords (next, break) - command_name = @context.command_aliases[command_or_alias.to_sym] - command = command_name || command_or_alias - command_class = ExtendCommandBundle.load_command(command) - - if command_class - Statement::Command.new(code, command, arg, command_class) + if (command, arg = parse_command(code)) + command_class = ExtendCommandBundle.load_command(command) + Statement::Command.new(code, command_class, arg) else is_assignment_expression = @scanner.assignment_expression?(code, local_variables: @context.local_variables) Statement::Expression.new(code, is_assignment_expression) end end - def single_line_command?(code) - command = code.split(/\s/, 2).first - @context.symbol_alias?(command) || @context.transform_args?(command) + def parse_command(code) + command_name, arg = code.strip.split(/\s+/, 2) + return unless code.lines.size == 1 && command_name + + arg ||= '' + command = command_name.to_sym + # Command aliases are always command. example: $, @ + if (alias_name = @context.command_aliases[command]) + return [alias_name, arg] + end + + # Check visibility + public_method = !!Kernel.instance_method(:public_method).bind_call(@context.main, command) rescue false + private_method = !public_method && !!Kernel.instance_method(:method).bind_call(@context.main, command) rescue false + if ExtendCommandBundle.execute_as_command?(command, public_method: public_method, private_method: private_method) + [command, arg] + end + end + + def command?(code) + !!parse_command(code) end def configure_io @@ -1128,8 +1172,7 @@ def configure_io false end else - # Accept any single-line input for symbol aliases or commands that transform args - next true if single_line_command?(code) + next true if command?(code) _tokens, _opens, terminated = @scanner.check_code_state(code, local_variables: @context.local_variables) terminated @@ -1143,7 +1186,8 @@ def configure_io tokens_until_line = [] line_results.map.with_index do |(line_tokens, _prev_opens, next_opens, _min_depth), line_num_offset| line_tokens.each do |token, _s| - # Avoid appending duplicated token. Tokens that include "\n" like multiline tstring_content can exist in multiple lines. + # Avoid appending duplicated token. Tokens that include "n" like multiline + # tstring_content can exist in multiple lines. tokens_until_line << token if token != tokens_until_line.last end continue = @scanner.should_continue?(tokens_until_line) @@ -1200,6 +1244,13 @@ def handle_exception(exc) irb_bug = true else irb_bug = false + # This is mostly to make IRB work nicely with Rails console's backtrace filtering, which patches WorkSpace#filter_backtrace + # In such use case, we want to filter the exception's backtrace before its displayed through Exception#full_message + # And we clone the exception object in order to avoid mutating the original exception + # TODO: introduce better API to expose exception backtrace externally + backtrace = exc.backtrace.map { |l| @context.workspace.filter_backtrace(l) }.compact + exc = exc.clone + exc.set_backtrace(backtrace) end if RUBY_VERSION < '3.0.0' @@ -1224,7 +1275,6 @@ def handle_exception(exc) lines = m.split("\n").reverse end unless irb_bug - lines = lines.map { |l| @context.workspace.filter_backtrace(l) }.compact if lines.size > @context.back_trace_limit omit = lines.size - @context.back_trace_limit lines = lines[0..(@context.back_trace_limit - 1)] @@ -1247,11 +1297,10 @@ def handle_exception(exc) end end - # Evaluates the given block using the given +path+ as the Context#irb_path - # and +name+ as the Context#irb_name. + # Evaluates the given block using the given `path` as the Context#irb_path and + # `name` as the Context#irb_name. # - # Used by the irb command +source+, see IRB@IRB+Sessions for more - # information. + # Used by the irb command `source`, see IRB@IRB+Sessions for more information. def suspend_name(path = nil, name = nil) @context.irb_path, back_path = path, @context.irb_path if path @context.irb_name, back_name = name, @context.irb_name if name @@ -1263,11 +1312,10 @@ def suspend_name(path = nil, name = nil) end end - # Evaluates the given block using the given +workspace+ as the + # Evaluates the given block using the given `workspace` as the # Context#workspace. # - # Used by the irb command +irb_load+, see IRB@IRB+Sessions for more - # information. + # Used by the irb command `irb_load`, see IRB@IRB+Sessions for more information. def suspend_workspace(workspace) current_workspace = @context.workspace @context.replace_workspace(workspace) @@ -1276,11 +1324,10 @@ def suspend_workspace(workspace) @context.replace_workspace current_workspace end - # Evaluates the given block using the given +input_method+ as the - # Context#io. + # Evaluates the given block using the given `input_method` as the Context#io. # - # Used by the irb commands +source+ and +irb_load+, see IRB@IRB+Sessions - # for more information. + # Used by the irb commands `source` and `irb_load`, see IRB@IRB+Sessions for + # more information. def suspend_input_method(input_method) back_io = @context.io @context.instance_eval{@io = input_method} @@ -1313,7 +1360,7 @@ def signal_handle end end - # Evaluates the given block using the given +status+. + # Evaluates the given block using the given `status`. def signal_status(status) return yield if @signal_status == :IN_LOAD @@ -1363,8 +1410,8 @@ def output_value(omit = false) # :nodoc: Pager.page_content(format(@context.return_format, str), retain_content: true) end - # Outputs the local variables to this current session, including - # #signal_status and #context, using IRB::Locale. + # Outputs the local variables to this current session, including #signal_status + # and #context, using IRB::Locale. def inspect ary = [] for iv in instance_variables @@ -1463,12 +1510,11 @@ def format_prompt(format, ltype, indent, line_no) # :nodoc: end class Binding - # Opens an IRB session where +binding.irb+ is called which allows for - # interactive debugging. You can call any methods or variables available in - # the current scope, and mutate state if you need to. - # + # Opens an IRB session where `binding.irb` is called which allows for + # interactive debugging. You can call any methods or variables available in the + # current scope, and mutate state if you need to. # - # Given a Ruby file called +potato.rb+ containing the following code: + # Given a Ruby file called `potato.rb` containing the following code: # # class Potato # def initialize @@ -1480,8 +1526,8 @@ class Binding # # Potato.new # - # Running ruby potato.rb will open an IRB session where - # +binding.irb+ is called, and you will see the following: + # Running `ruby potato.rb` will open an IRB session where `binding.irb` is + # called, and you will see the following: # # $ ruby potato.rb # @@ -1511,8 +1557,8 @@ class Binding # irb(#):004:0> @cooked = true # => true # - # You can exit the IRB session with the +exit+ command. Note that exiting will - # resume execution where +binding.irb+ had paused it, as you can see from the + # You can exit the IRB session with the `exit` command. Note that exiting will + # resume execution where `binding.irb` had paused it, as you can see from the # output printed to standard output in this example: # # irb(#):005:0> exit @@ -1535,13 +1581,14 @@ def irb(show_code: true) # If we're already in a debugger session, set the workspace and irb_path for the original IRB instance debugger_irb.context.replace_workspace(workspace) debugger_irb.context.irb_path = irb_path - # If we've started a debugger session and hit another binding.irb, we don't want to start an IRB session - # instead, we want to resume the irb:rdbg session. + # If we've started a debugger session and hit another binding.irb, we don't want + # to start an IRB session instead, we want to resume the irb:rdbg session. IRB::Debug.setup(debugger_irb) IRB::Debug.insert_debug_break debugger_irb.debug_break else - # If we're not in a debugger session, create a new IRB instance with the current workspace + # If we're not in a debugger session, create a new IRB instance with the current + # workspace binding_irb = IRB::Irb.new(workspace) binding_irb.context.irb_path = irb_path binding_irb.run(IRB.conf) diff --git a/lib/irb/cmd/nop.rb b/lib/irb/cmd/nop.rb index 49f89bac95eed8..9d2e3c4d47cd6e 100644 --- a/lib/irb/cmd/nop.rb +++ b/lib/irb/cmd/nop.rb @@ -1,4 +1,4 @@ # frozen_string_literal: true # This file is just a placeholder for backward-compatibility. -# Please require 'irb' and inheirt your command from `IRB::Command::Base` instead. +# Please require 'irb' and inherit your command from `IRB::Command::Base` instead. diff --git a/lib/irb/command.rb b/lib/irb/command.rb index 4dc2994ba9d1f8..43cbda36b515d8 100644 --- a/lib/irb/command.rb +++ b/lib/irb/command.rb @@ -12,29 +12,17 @@ module Command; end # Installs the default irb extensions command bundle. module ExtendCommandBundle - EXCB = ExtendCommandBundle # :nodoc: - - # See #install_alias_method. + # See ExtendCommandBundle.execute_as_command?. NO_OVERRIDE = 0 - # See #install_alias_method. OVERRIDE_PRIVATE_ONLY = 0x01 - # See #install_alias_method. OVERRIDE_ALL = 0x02 - # Displays current configuration. - # - # Modifying the configuration is achieved by sending a message to IRB.conf. - def irb_context - IRB.CurrentContext - end - - @ALIASES = [ - [:context, :irb_context, NO_OVERRIDE], - [:conf, :irb_context, NO_OVERRIDE], - ] - - @EXTEND_COMMANDS = [ + [ + :irb_context, :Context, "command/context", + [:context, NO_OVERRIDE], + [:conf, NO_OVERRIDE], + ], [ :irb_exit, :Exit, "command/exit", [:exit, OVERRIDE_PRIVATE_ONLY], @@ -196,9 +184,34 @@ def irb_context :irb_history, :History, "command/history", [:history, NO_OVERRIDE], [:hist, NO_OVERRIDE], - ] + ], + + [ + :irb_disable_irb, :DisableIrb, "command/disable_irb", + [:disable_irb, NO_OVERRIDE], + ], ] + def self.command_override_policies + @@command_override_policies ||= @EXTEND_COMMANDS.flat_map do |cmd_name, cmd_class, load_file, *aliases| + [[cmd_name, OVERRIDE_ALL]] + aliases + end.to_h + end + + def self.execute_as_command?(name, public_method:, private_method:) + case command_override_policies[name] + when OVERRIDE_ALL + true + when OVERRIDE_PRIVATE_ONLY + !public_method + when NO_OVERRIDE + !public_method && !private_method + end + end + + def self.command_names + command_override_policies.keys.map(&:to_s) + end @@commands = [] @@ -242,77 +255,13 @@ def self.load_command(command) nil end - # Installs the default irb commands. - def self.install_extend_commands - for args in @EXTEND_COMMANDS - def_extend_command(*args) - end - end - - # Evaluate the given +cmd_name+ on the given +cmd_class+ Class. - # - # Will also define any given +aliases+ for the method. - # - # The optional +load_file+ parameter will be required within the method - # definition. def self.def_extend_command(cmd_name, cmd_class, load_file, *aliases) - case cmd_class - when Symbol - cmd_class = cmd_class.id2name - when String - when Class - cmd_class = cmd_class.name - end - - line = __LINE__; eval %[ - def #{cmd_name}(*opts, **kwargs, &b) - Kernel.require_relative "#{load_file}" - ::IRB::Command::#{cmd_class}.execute(irb_context, *opts, **kwargs, &b) - end - ], nil, __FILE__, line - - for ali, flag in aliases - @ALIASES.push [ali, cmd_name, flag] - end - end + @EXTEND_COMMANDS.delete_if { |name,| name == cmd_name } + @EXTEND_COMMANDS << [cmd_name, cmd_class, load_file, *aliases] - # Installs alias methods for the default irb commands, see - # ::install_extend_commands. - def install_alias_method(to, from, override = NO_OVERRIDE) - to = to.id2name unless to.kind_of?(String) - from = from.id2name unless from.kind_of?(String) - - if override == OVERRIDE_ALL or - (override == OVERRIDE_PRIVATE_ONLY) && !respond_to?(to) or - (override == NO_OVERRIDE) && !respond_to?(to, true) - target = self - (class << self; self; end).instance_eval{ - if target.respond_to?(to, true) && - !target.respond_to?(EXCB.irb_original_method_name(to), true) - alias_method(EXCB.irb_original_method_name(to), to) - end - alias_method to, from - } - else - Kernel.warn "irb: warn: can't alias #{to} from #{from}.\n" - end + # Just clear memoized values + @@commands = [] + @@command_override_policies = nil end - - def self.irb_original_method_name(method_name) # :nodoc: - "irb_" + method_name + "_org" - end - - # Installs alias methods for the default irb commands on the given object - # using #install_alias_method. - def self.extend_object(obj) - unless (class << obj; ancestors; end).include?(EXCB) - super - for ali, com, flg in @ALIASES - obj.install_alias_method(ali, com, flg) - end - end - end - - install_extend_commands end end diff --git a/lib/irb/command/backtrace.rb b/lib/irb/command/backtrace.rb index 47e5e60724029d..687bb075ace9fc 100644 --- a/lib/irb/command/backtrace.rb +++ b/lib/irb/command/backtrace.rb @@ -7,12 +7,8 @@ module IRB module Command class Backtrace < DebugCommand - def self.transform_args(args) - args&.dump - end - - def execute(*args) - super(pre_cmds: ["backtrace", *args].join(" ")) + def execute(arg) + execute_debug_command(pre_cmds: "backtrace #{arg}") end end end diff --git a/lib/irb/command/base.rb b/lib/irb/command/base.rb index 3ce4f4d6c19922..ff74b5fb35438b 100644 --- a/lib/irb/command/base.rb +++ b/lib/irb/command/base.rb @@ -10,6 +10,10 @@ module IRB module Command class CommandArgumentError < StandardError; end + def self.extract_ruby_args(*args, **kwargs) + throw :EXTRACT_RUBY_ARGS, [args, kwargs] + end + class Base class << self def category(category = nil) @@ -29,19 +33,13 @@ def help_message(help_message = nil) private - def string_literal?(args) - sexp = Ripper.sexp(args) - sexp && sexp.size == 2 && sexp.last&.first&.first == :string_literal - end - def highlight(text) Color.colorize(text, [:BOLD, :BLUE]) end end - def self.execute(irb_context, *opts, **kwargs, &block) - command = new(irb_context) - command.execute(*opts, **kwargs, &block) + def self.execute(irb_context, arg) + new(irb_context).execute(arg) rescue CommandArgumentError => e puts e.message end @@ -52,7 +50,26 @@ def initialize(irb_context) attr_reader :irb_context - def execute(*opts) + def unwrap_string_literal(str) + return if str.empty? + + sexp = Ripper.sexp(str) + if sexp && sexp.size == 2 && sexp.last&.first&.first == :string_literal + @irb_context.workspace.binding.eval(str).to_s + else + str + end + end + + def ruby_args(arg) + # Use throw and catch to handle arg that includes `;` + # For example: "1, kw: (2; 3); 4" will be parsed to [[1], { kw: 3 }] + catch(:EXTRACT_RUBY_ARGS) do + @irb_context.workspace.binding.eval "IRB::Command.extract_ruby_args #{arg}" + end || [[], {}] + end + + def execute(arg) #nop end end diff --git a/lib/irb/command/break.rb b/lib/irb/command/break.rb index fa200f2d7811dc..a8f81fe665077b 100644 --- a/lib/irb/command/break.rb +++ b/lib/irb/command/break.rb @@ -7,12 +7,8 @@ module IRB module Command class Break < DebugCommand - def self.transform_args(args) - args&.dump - end - - def execute(args = nil) - super(pre_cmds: "break #{args}") + def execute(arg) + execute_debug_command(pre_cmds: "break #{arg}") end end end diff --git a/lib/irb/command/catch.rb b/lib/irb/command/catch.rb index 6b2edff5e5e6f9..529dcbca5aed09 100644 --- a/lib/irb/command/catch.rb +++ b/lib/irb/command/catch.rb @@ -7,12 +7,8 @@ module IRB module Command class Catch < DebugCommand - def self.transform_args(args) - args&.dump - end - - def execute(*args) - super(pre_cmds: ["catch", *args].join(" ")) + def execute(arg) + execute_debug_command(pre_cmds: "catch #{arg}") end end end diff --git a/lib/irb/command/chws.rb b/lib/irb/command/chws.rb index e1047686c520cf..e0a406885f87c1 100644 --- a/lib/irb/command/chws.rb +++ b/lib/irb/command/chws.rb @@ -14,7 +14,7 @@ class CurrentWorkingWorkspace < Base category "Workspace" description "Show the current workspace." - def execute(*obj) + def execute(_arg) irb_context.main end end @@ -23,8 +23,13 @@ class ChangeWorkspace < Base category "Workspace" description "Change the current workspace to an object." - def execute(*obj) - irb_context.change_workspace(*obj) + def execute(arg) + if arg.empty? + irb_context.change_workspace + else + obj = eval(arg, irb_context.workspace.binding) + irb_context.change_workspace(obj) + end irb_context.main end end diff --git a/lib/irb/command/context.rb b/lib/irb/command/context.rb new file mode 100644 index 00000000000000..b4fc807343c0f5 --- /dev/null +++ b/lib/irb/command/context.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +module IRB + module Command + class Context < Base + category "IRB" + description "Displays current configuration." + + def execute(_arg) + # This command just displays the configuration. + # Modifying the configuration is achieved by sending a message to IRB.conf. + Pager.page_content(IRB.CurrentContext.inspect) + end + end + end +end diff --git a/lib/irb/command/continue.rb b/lib/irb/command/continue.rb index 8b6ffc860ce6c9..0daa029b154042 100644 --- a/lib/irb/command/continue.rb +++ b/lib/irb/command/continue.rb @@ -7,8 +7,8 @@ module IRB module Command class Continue < DebugCommand - def execute(*args) - super(do_cmds: ["continue", *args].join(" ")) + def execute(arg) + execute_debug_command(do_cmds: "continue #{arg}") end end end diff --git a/lib/irb/command/debug.rb b/lib/irb/command/debug.rb index bdf91766bcc335..f9aca0a672b4c6 100644 --- a/lib/irb/command/debug.rb +++ b/lib/irb/command/debug.rb @@ -13,7 +13,14 @@ class Debug < Base binding.method(:irb).source_location.first, ].map { |file| /\A#{Regexp.escape(file)}:\d+:in (`|'Binding#)irb'\z/ } - def execute(pre_cmds: nil, do_cmds: nil) + def execute(_arg) + execute_debug_command + end + + def execute_debug_command(pre_cmds: nil, do_cmds: nil) + pre_cmds = pre_cmds&.rstrip + do_cmds = do_cmds&.rstrip + if irb_context.with_debugger # If IRB is already running with a debug session, throw the command and IRB.debug_readline will pass it to the debugger. if cmd = pre_cmds || do_cmds diff --git a/lib/irb/command/delete.rb b/lib/irb/command/delete.rb index a36b4577b0c23e..2a57a4a3de06d2 100644 --- a/lib/irb/command/delete.rb +++ b/lib/irb/command/delete.rb @@ -7,8 +7,8 @@ module IRB module Command class Delete < DebugCommand - def execute(*args) - super(pre_cmds: ["delete", *args].join(" ")) + def execute(arg) + execute_debug_command(pre_cmds: "delete #{arg}") end end end diff --git a/lib/irb/command/disable_irb.rb b/lib/irb/command/disable_irb.rb new file mode 100644 index 00000000000000..0b00d0302b056d --- /dev/null +++ b/lib/irb/command/disable_irb.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +module IRB + # :stopdoc: + + module Command + class DisableIrb < Base + category "IRB" + description "Disable binding.irb." + + def execute(*) + ::Binding.define_method(:irb) {} + IRB.irb_exit + end + end + end + + # :startdoc: +end diff --git a/lib/irb/command/edit.rb b/lib/irb/command/edit.rb index ab8c62663ea250..480100bfc7c2df 100644 --- a/lib/irb/command/edit.rb +++ b/lib/irb/command/edit.rb @@ -26,19 +26,9 @@ class Edit < Base edit Foo#bar HELP_MESSAGE - class << self - def transform_args(args) - # Return a string literal as is for backward compatibility - if args.nil? || args.empty? || string_literal?(args) - args - else # Otherwise, consider the input as a String for convenience - args.strip.dump - end - end - end - - def execute(*args) - path = args.first + def execute(arg) + # Accept string literal for backward compatibility + path = unwrap_string_literal(arg) if path.nil? path = @irb_context.irb_path diff --git a/lib/irb/command/finish.rb b/lib/irb/command/finish.rb index 05501819e2d0cd..3311a0e6e9e405 100644 --- a/lib/irb/command/finish.rb +++ b/lib/irb/command/finish.rb @@ -7,8 +7,8 @@ module IRB module Command class Finish < DebugCommand - def execute(*args) - super(do_cmds: ["finish", *args].join(" ")) + def execute(arg) + execute_debug_command(do_cmds: "finish #{arg}") end end end diff --git a/lib/irb/command/help.rb b/lib/irb/command/help.rb index 19113dbbfed843..c9f16e05bc50b4 100644 --- a/lib/irb/command/help.rb +++ b/lib/irb/command/help.rb @@ -6,27 +6,16 @@ class Help < Base category "Help" description "List all available commands. Use `help ` to get information about a specific command." - class << self - def transform_args(args) - # Return a string literal as is for backward compatibility - if args.empty? || string_literal?(args) - args - else # Otherwise, consider the input as a String for convenience - args.strip.dump - end - end - end - - def execute(command_name = nil) + def execute(command_name) content = - if command_name + if command_name.empty? + help_message + else if command_class = ExtendCommandBundle.load_command(command_name) command_class.help_message || command_class.description else "Can't find command `#{command_name}`. Please check the command name and try again.\n\n" end - else - help_message end Pager.page_content(content) end diff --git a/lib/irb/command/history.rb b/lib/irb/command/history.rb index a47a8795ddc3f0..90f87f91023554 100644 --- a/lib/irb/command/history.rb +++ b/lib/irb/command/history.rb @@ -12,14 +12,12 @@ class History < Base category "IRB" description "Shows the input history. `-g [query]` or `-G [query]` allows you to filter the output." - def self.transform_args(args) - match = args&.match(/(-g|-G)\s+(?.+)\s*\n\z/) - return unless match + def execute(arg) - "grep: #{Regexp.new(match[:grep]).inspect}" - end + if (match = arg&.match(/(-g|-G)\s+(?.+)\s*\n\z/)) + grep = Regexp.new(match[:grep]) + end - def execute(grep: nil) formatted_inputs = irb_context.io.class::HISTORY.each_with_index.reverse_each.filter_map do |input, index| next if grep && !input.match?(grep) diff --git a/lib/irb/command/info.rb b/lib/irb/command/info.rb index a67be3eb858658..d08ce00a320aab 100644 --- a/lib/irb/command/info.rb +++ b/lib/irb/command/info.rb @@ -7,12 +7,8 @@ module IRB module Command class Info < DebugCommand - def self.transform_args(args) - args&.dump - end - - def execute(*args) - super(pre_cmds: ["info", *args].join(" ")) + def execute(arg) + execute_debug_command(pre_cmds: "info #{arg}") end end end diff --git a/lib/irb/command/irb_info.rb b/lib/irb/command/irb_info.rb index 31e6d77d25cea0..6d868de94c8c76 100644 --- a/lib/irb/command/irb_info.rb +++ b/lib/irb/command/irb_info.rb @@ -8,12 +8,12 @@ class IrbInfo < Base category "IRB" description "Show information about IRB." - def execute + def execute(_arg) str = "Ruby version: #{RUBY_VERSION}\n" str += "IRB version: #{IRB.version}\n" str += "InputMethod: #{IRB.CurrentContext.io.inspect}\n" str += "Completion: #{IRB.CurrentContext.io.respond_to?(:completion_info) ? IRB.CurrentContext.io.completion_info : 'off'}\n" - rc_files = IRB.rc_files.select { |rc| File.exist?(rc) } + rc_files = IRB.irbrc_files str += ".irbrc paths: #{rc_files.join(", ")}\n" if rc_files.any? str += "RUBY_PLATFORM: #{RUBY_PLATFORM}\n" str += "LANG env: #{ENV["LANG"]}\n" if ENV["LANG"] && !ENV["LANG"].empty? diff --git a/lib/irb/command/load.rb b/lib/irb/command/load.rb index 9e89a7b7f35751..33e327f4a93838 100644 --- a/lib/irb/command/load.rb +++ b/lib/irb/command/load.rb @@ -21,7 +21,12 @@ class Load < LoaderCommand category "IRB" description "Load a Ruby file." - def execute(file_name = nil, priv = nil) + def execute(arg) + args, kwargs = ruby_args(arg) + execute_internal(*args, **kwargs) + end + + def execute_internal(file_name = nil, priv = nil) raise_cmd_argument_error unless file_name irb_load(file_name, priv) end @@ -30,7 +35,13 @@ def execute(file_name = nil, priv = nil) class Require < LoaderCommand category "IRB" description "Require a Ruby file." - def execute(file_name = nil) + + def execute(arg) + args, kwargs = ruby_args(arg) + execute_internal(*args, **kwargs) + end + + def execute_internal(file_name = nil) raise_cmd_argument_error unless file_name rex = Regexp.new("#{Regexp.quote(file_name)}(\.o|\.rb)?") @@ -63,7 +74,12 @@ class Source < LoaderCommand category "IRB" description "Loads a given file in the current session." - def execute(file_name = nil) + def execute(arg) + args, kwargs = ruby_args(arg) + execute_internal(*args, **kwargs) + end + + def execute_internal(file_name = nil) raise_cmd_argument_error unless file_name source_file(file_name) diff --git a/lib/irb/command/ls.rb b/lib/irb/command/ls.rb index 6b6136c2feb78e..f6b19648640489 100644 --- a/lib/irb/command/ls.rb +++ b/lib/irb/command/ls.rb @@ -20,27 +20,35 @@ class Ls < Base -g [query] Filter the output with a query. HELP_MESSAGE - def self.transform_args(args) - if match = args&.match(/\A(?.+\s|)(-g|-G)\s+(?[^\s]+)\s*\n\z/) - args = match[:args] - "#{args}#{',' unless args.chomp.empty?} grep: /#{match[:grep]}/" + def execute(arg) + if match = arg.match(/\A(?.+\s|)(-g|-G)\s+(?.+)$/) + if match[:target].empty? + use_main = true + else + obj = @irb_context.workspace.binding.eval(match[:target]) + end + grep = Regexp.new(match[:grep]) else - args + args, kwargs = ruby_args(arg) + use_main = args.empty? + obj = args.first + grep = kwargs[:grep] + end + + if use_main + obj = irb_context.workspace.main + locals = irb_context.workspace.binding.local_variables end - end - def execute(*arg, grep: nil) o = Output.new(grep: grep) - obj = arg.empty? ? irb_context.workspace.main : arg.first - locals = arg.empty? ? irb_context.workspace.binding.local_variables : [] klass = (obj.class == Class || obj.class == Module ? obj : obj.class) o.dump("constants", obj.constants) if obj.respond_to?(:constants) dump_methods(o, klass, obj) o.dump("instance variables", obj.instance_variables) o.dump("class variables", klass.class_variables) - o.dump("locals", locals) + o.dump("locals", locals) if locals o.print_result end diff --git a/lib/irb/command/measure.rb b/lib/irb/command/measure.rb index ee7927b010bdc0..70dc69cdec5ef5 100644 --- a/lib/irb/command/measure.rb +++ b/lib/irb/command/measure.rb @@ -10,15 +10,19 @@ def initialize(*args) super(*args) end - def execute(type = nil, arg = nil) - # Please check IRB.init_config in lib/irb/init.rb that sets - # IRB.conf[:MEASURE_PROC] to register default "measure" methods, - # "measure :time" (abbreviated as "measure") and "measure :stackprof". - - if block_given? + def execute(arg) + if arg&.match?(/^do$|^do[^\w]|^\{/) warn 'Configure IRB.conf[:MEASURE_PROC] to add custom measure methods.' return end + args, kwargs = ruby_args(arg) + execute_internal(*args, **kwargs) + end + + def execute_internal(type = nil, arg = nil) + # Please check IRB.init_config in lib/irb/init.rb that sets + # IRB.conf[:MEASURE_PROC] to register default "measure" methods, + # "measure :time" (abbreviated as "measure") and "measure :stackprof". case type when :off diff --git a/lib/irb/command/next.rb b/lib/irb/command/next.rb index 6487c9d24cb6ee..3fc6b68d21f4b3 100644 --- a/lib/irb/command/next.rb +++ b/lib/irb/command/next.rb @@ -7,8 +7,8 @@ module IRB module Command class Next < DebugCommand - def execute(*args) - super(do_cmds: ["next", *args].join(" ")) + def execute(arg) + execute_debug_command(do_cmds: "next #{arg}") end end end diff --git a/lib/irb/command/pushws.rb b/lib/irb/command/pushws.rb index 888fe466f1a176..b51928c6508a92 100644 --- a/lib/irb/command/pushws.rb +++ b/lib/irb/command/pushws.rb @@ -14,7 +14,7 @@ class Workspaces < Base category "Workspace" description "Show workspaces." - def execute(*obj) + def execute(_arg) inspection_resuls = irb_context.instance_variable_get(:@workspace_stack).map do |ws| truncated_inspect(ws.main) end @@ -39,8 +39,13 @@ class PushWorkspace < Workspaces category "Workspace" description "Push an object to the workspace stack." - def execute(*obj) - irb_context.push_workspace(*obj) + def execute(arg) + if arg.empty? + irb_context.push_workspace + else + obj = eval(arg, irb_context.workspace.binding) + irb_context.push_workspace(obj) + end super end end @@ -49,8 +54,8 @@ class PopWorkspace < Workspaces category "Workspace" description "Pop a workspace from the workspace stack." - def execute(*obj) - irb_context.pop_workspace(*obj) + def execute(_arg) + irb_context.pop_workspace super end end diff --git a/lib/irb/command/show_doc.rb b/lib/irb/command/show_doc.rb index 4dde28bee9bbaf..f9393cd3b5cab9 100644 --- a/lib/irb/command/show_doc.rb +++ b/lib/irb/command/show_doc.rb @@ -3,17 +3,6 @@ module IRB module Command class ShowDoc < Base - class << self - def transform_args(args) - # Return a string literal as is for backward compatibility - if args.empty? || string_literal?(args) - args - else # Otherwise, consider the input as a String for convenience - args.strip.dump - end - end - end - category "Context" description "Look up documentation with RI." @@ -31,7 +20,9 @@ def transform_args(args) HELP_MESSAGE - def execute(*names) + def execute(arg) + # Accept string literal for backward compatibility + name = unwrap_string_literal(arg) require 'rdoc/ri/driver' unless ShowDoc.const_defined?(:Ri) @@ -39,15 +30,13 @@ def execute(*names) ShowDoc.const_set(:Ri, RDoc::RI::Driver.new(opts)) end - if names.empty? + if name.nil? Ri.interactive else - names.each do |name| - begin - Ri.display_name(name.to_s) - rescue RDoc::RI::Error - puts $!.message - end + begin + Ri.display_name(name) + rescue RDoc::RI::Error + puts $!.message end end diff --git a/lib/irb/command/show_source.rb b/lib/irb/command/show_source.rb index 32bdf74d313e92..c4c8fc004121c2 100644 --- a/lib/irb/command/show_source.rb +++ b/lib/irb/command/show_source.rb @@ -24,18 +24,9 @@ class ShowSource < Base show_source Foo::BAR HELP_MESSAGE - class << self - def transform_args(args) - # Return a string literal as is for backward compatibility - if args.empty? || string_literal?(args) - args - else # Otherwise, consider the input as a String for convenience - args.strip.dump - end - end - end - - def execute(str = nil) + def execute(arg) + # Accept string literal for backward compatibility + str = unwrap_string_literal(arg) unless str.is_a?(String) puts "Error: Expected a string but got #{str.inspect}" return diff --git a/lib/irb/command/step.rb b/lib/irb/command/step.rb index cce7d2b0f8443a..29e5e35ac0135b 100644 --- a/lib/irb/command/step.rb +++ b/lib/irb/command/step.rb @@ -7,8 +7,8 @@ module IRB module Command class Step < DebugCommand - def execute(*args) - super(do_cmds: ["step", *args].join(" ")) + def execute(arg) + execute_debug_command(do_cmds: "step #{arg}") end end end diff --git a/lib/irb/command/subirb.rb b/lib/irb/command/subirb.rb index 5cc7b8c6f85fa2..24428a5c130ed7 100644 --- a/lib/irb/command/subirb.rb +++ b/lib/irb/command/subirb.rb @@ -9,10 +9,6 @@ module IRB module Command class MultiIRBCommand < Base - def execute(*args) - extend_irb_context - end - private def print_deprecated_warning @@ -36,7 +32,12 @@ class IrbCommand < MultiIRBCommand category "Multi-irb (DEPRECATED)" description "Start a child IRB." - def execute(*obj) + def execute(arg) + args, kwargs = ruby_args(arg) + execute_internal(*args, **kwargs) + end + + def execute_internal(*obj) print_deprecated_warning if irb_context.with_debugger @@ -44,7 +45,7 @@ def execute(*obj) return end - super + extend_irb_context IRB.irb(nil, *obj) end end @@ -53,7 +54,7 @@ class Jobs < MultiIRBCommand category "Multi-irb (DEPRECATED)" description "List of current sessions." - def execute + def execute(_arg) print_deprecated_warning if irb_context.with_debugger @@ -61,7 +62,7 @@ def execute return end - super + extend_irb_context IRB.JobManager end end @@ -70,7 +71,12 @@ class Foreground < MultiIRBCommand category "Multi-irb (DEPRECATED)" description "Switches to the session of the given number." - def execute(key = nil) + def execute(arg) + args, kwargs = ruby_args(arg) + execute_internal(*args, **kwargs) + end + + def execute_internal(key = nil) print_deprecated_warning if irb_context.with_debugger @@ -78,7 +84,7 @@ def execute(key = nil) return end - super + extend_irb_context raise CommandArgumentError.new("Please specify the id of target IRB job (listed in the `jobs` command).") unless key IRB.JobManager.switch(key) @@ -89,7 +95,12 @@ class Kill < MultiIRBCommand category "Multi-irb (DEPRECATED)" description "Kills the session with the given number." - def execute(*keys) + def execute(arg) + args, kwargs = ruby_args(arg) + execute_internal(*args, **kwargs) + end + + def execute_internal(*keys) print_deprecated_warning if irb_context.with_debugger @@ -97,7 +108,7 @@ def execute(*keys) return end - super + extend_irb_context IRB.JobManager.kill(*keys) end end diff --git a/lib/irb/command/whereami.rb b/lib/irb/command/whereami.rb index d6658d70430e7f..c8439f12120333 100644 --- a/lib/irb/command/whereami.rb +++ b/lib/irb/command/whereami.rb @@ -8,7 +8,7 @@ class Whereami < Base category "Context" description "Show the source code around binding.irb again." - def execute(*) + def execute(_arg) code = irb_context.workspace.code_around_binding if code puts code diff --git a/lib/irb/completion.rb b/lib/irb/completion.rb index ceb6eb087a1c56..8a1df11561283b 100644 --- a/lib/irb/completion.rb +++ b/lib/irb/completion.rb @@ -86,6 +86,14 @@ def retrieve_files_to_require_from_load_path ) end + def command_completions(preposing, target) + if preposing.empty? && !target.empty? + IRB::ExtendCommandBundle.command_names.select { _1.start_with?(target) } + else + [] + end + end + def retrieve_files_to_require_relative_from_current_dir @files_from_current_dir ||= Dir.glob("**/*.{rb,#{RbConfig::CONFIG['DLEXT']}}", base: '.').map { |path| path.sub(/\.(rb|#{RbConfig::CONFIG['DLEXT']})\z/, '') @@ -103,9 +111,11 @@ def inspect end def completion_candidates(preposing, target, _postposing, bind:) + commands = command_completions(preposing, target) result = ReplTypeCompletor.analyze(preposing + target, binding: bind, filename: @context.irb_path) - return [] unless result - result.completion_candidates.map { target + _1 } + return commands unless result + + commands | result.completion_candidates.map { target + _1 } end def doc_namespace(preposing, matched, _postposing, bind:) @@ -181,7 +191,8 @@ def completion_candidates(preposing, target, postposing, bind:) result = complete_require_path(target, preposing, postposing) return result if result end - retrieve_completion_data(target, bind: bind, doc_namespace: false).compact.map{ |i| i.encode(Encoding.default_external) } + commands = command_completions(preposing || '', target) + commands | retrieve_completion_data(target, bind: bind, doc_namespace: false).compact.map{ |i| i.encode(Encoding.default_external) } end def doc_namespace(_preposing, matched, _postposing, bind:) @@ -388,7 +399,7 @@ def retrieve_completion_data(input, bind:, doc_namespace:) if doc_namespace rec_class = rec.is_a?(Module) ? rec : rec.class - "#{rec_class.name}#{sep}#{candidates.find{ |i| i == message }}" + "#{rec_class.name}#{sep}#{candidates.find{ |i| i == message }}" rescue nil else select_message(receiver, message, candidates, sep) end @@ -418,7 +429,7 @@ def retrieve_completion_data(input, bind:, doc_namespace:) vars = (bind.local_variables | bind.eval_instance_variables).collect{|m| m.to_s} perfect_match_var = vars.find{|m| m.to_s == input} if perfect_match_var - eval("#{perfect_match_var}.class.name", bind) + eval("#{perfect_match_var}.class.name", bind) rescue nil else candidates = (bind.eval_methods | bind.eval_private_methods | bind.local_variables | bind.eval_instance_variables | bind.eval_class_constants).collect{|m| m.to_s} candidates |= ReservedWords diff --git a/lib/irb/context.rb b/lib/irb/context.rb index 60dfb9668dac3c..e3c41924595a29 100644 --- a/lib/irb/context.rb +++ b/lib/irb/context.rb @@ -646,17 +646,5 @@ def inspect # :nodoc: def local_variables # :nodoc: workspace.binding.local_variables end - - # Return true if it's aliased from the argument and it's not an identifier. - def symbol_alias?(command) - return nil if command.match?(/\A\w+\z/) - command_aliases.key?(command.to_sym) - end - - # Return true if the command supports transforming args - def transform_args?(command) - command = command_aliases.fetch(command.to_sym, command) - ExtendCommandBundle.load_command(command)&.respond_to?(:transform_args) - end end end diff --git a/lib/irb/ext/change-ws.rb b/lib/irb/ext/change-ws.rb index 87fe03e23d953d..60e8afe31f88f2 100644 --- a/lib/irb/ext/change-ws.rb +++ b/lib/irb/ext/change-ws.rb @@ -29,11 +29,9 @@ def change_workspace(*_main) return main end - replace_workspace(WorkSpace.new(_main[0])) - - if !(class< e warn "Error loading RC file '#{rc}':\n#{e.full_message(highlight: false)}" end @@ -406,53 +404,27 @@ def IRB.run_config end IRBRC_EXT = "rc" - def IRB.rc_file(ext = IRBRC_EXT) - warn "rc_file is deprecated, please use rc_files instead." - rc_files(ext).first - end - - def IRB.rc_files(ext = IRBRC_EXT) - if !@CONF[:RC_NAME_GENERATOR] - @CONF[:RC_NAME_GENERATOR] ||= [] - existing_rc_file_generators = [] - rc_file_generators do |rcgen| - @CONF[:RC_NAME_GENERATOR] << rcgen - existing_rc_file_generators << rcgen if File.exist?(rcgen.call(ext)) - end + def IRB.rc_file(ext) + prepare_irbrc_name_generators - if existing_rc_file_generators.any? - @CONF[:RC_NAME_GENERATOR] = existing_rc_file_generators - end + # When irbrc exist in default location + if (rcgen = @existing_rc_name_generators.first) + return rcgen.call(ext) end - @CONF[:RC_NAME_GENERATOR].map do |rc| - rc_file = rc.call(ext) - fail IllegalRCNameGenerator unless rc_file.is_a?(String) - rc_file + # When irbrc does not exist in default location + rc_file_generators do |rcgen| + return rcgen.call(ext) end + + # When HOME and XDG_CONFIG_HOME are not available + nil end - # enumerate possible rc-file base name generators - def IRB.rc_file_generators - if irbrc = ENV["IRBRC"] - yield proc{|rc| rc == "rc" ? irbrc : irbrc+rc} - end - if xdg_config_home = ENV["XDG_CONFIG_HOME"] - irb_home = File.join(xdg_config_home, "irb") - if File.directory?(irb_home) - yield proc{|rc| irb_home + "/irb#{rc}"} - end - end - if home = ENV["HOME"] - yield proc{|rc| home+"/.irb#{rc}"} - yield proc{|rc| home+"/.config/irb/irb#{rc}"} - end - current_dir = Dir.pwd - yield proc{|rc| current_dir+"/.irb#{rc}"} - yield proc{|rc| current_dir+"/irb#{rc.sub(/\A_?/, '.')}"} - yield proc{|rc| current_dir+"/_irb#{rc}"} - yield proc{|rc| current_dir+"/$irb#{rc}"} + def IRB.irbrc_files + prepare_irbrc_name_generators + @irbrc_files end # loading modules @@ -468,6 +440,50 @@ def IRB.load_modules class << IRB private + + def prepare_irbrc_name_generators + return if @existing_rc_name_generators + + @existing_rc_name_generators = [] + @irbrc_files = [] + rc_file_generators do |rcgen| + irbrc = rcgen.call(IRBRC_EXT) + if File.exist?(irbrc) + @irbrc_files << irbrc + @existing_rc_name_generators << rcgen + end + end + generate_current_dir_irbrc_files.each do |irbrc| + @irbrc_files << irbrc if File.exist?(irbrc) + end + @irbrc_files.uniq! + end + + # enumerate possible rc-file base name generators + def rc_file_generators + if irbrc = ENV["IRBRC"] + yield proc{|rc| rc == "rc" ? irbrc : irbrc+rc} + end + if xdg_config_home = ENV["XDG_CONFIG_HOME"] + irb_home = File.join(xdg_config_home, "irb") + if File.directory?(irb_home) + yield proc{|rc| irb_home + "/irb#{rc}"} + end + end + if home = ENV["HOME"] + yield proc{|rc| home+"/.irb#{rc}"} + if xdg_config_home.nil? || xdg_config_home.empty? + yield proc{|rc| home+"/.config/irb/irb#{rc}"} + end + end + end + + # possible irbrc files in current directory + def generate_current_dir_irbrc_files + current_dir = Dir.pwd + %w[.irbrc irbrc _irbrc $irbrc].map { |file| "#{current_dir}/#{file}" } + end + def set_encoding(extern, intern = nil, override: true) verbose, $VERBOSE = $VERBOSE, nil Encoding.default_external = extern unless extern.nil? || extern.empty? diff --git a/lib/irb/input-method.rb b/lib/irb/input-method.rb index c1998309283fc3..e5adb350e8ceef 100644 --- a/lib/irb/input-method.rb +++ b/lib/irb/input-method.rb @@ -308,6 +308,20 @@ def retrieve_doc_namespace(matched) @completor.doc_namespace(preposing, matched, postposing, bind: bind) end + def rdoc_ri_driver + return @rdoc_ri_driver if defined?(@rdoc_ri_driver) + + begin + require 'rdoc' + rescue LoadError + @rdoc_ri_driver = nil + else + options = {} + options[:extra_doc_dirs] = IRB.conf[:EXTRA_DOC_DIRS] unless IRB.conf[:EXTRA_DOC_DIRS].empty? + @rdoc_ri_driver = RDoc::RI::Driver.new(options) + end + end + def show_doc_dialog_proc input_method = self # self is changed in the lambda below. ->() { @@ -331,9 +345,7 @@ def show_doc_dialog_proc show_easter_egg = name&.match?(/\ARubyVM/) && !ENV['RUBY_YES_I_AM_NOT_A_NORMAL_USER'] - options = {} - options[:extra_doc_dirs] = IRB.conf[:EXTRA_DOC_DIRS] unless IRB.conf[:EXTRA_DOC_DIRS].empty? - driver = RDoc::RI::Driver.new(options) + driver = input_method.rdoc_ri_driver if key.match?(dialog.name) if show_easter_egg @@ -421,12 +433,9 @@ def show_doc_dialog_proc } end - def display_document(matched, driver: nil) - begin - require 'rdoc' - rescue LoadError - return - end + def display_document(matched) + driver = rdoc_ri_driver + return unless driver if matched =~ /\A(?:::)?RubyVM/ and not ENV['RUBY_YES_I_AM_NOT_A_NORMAL_USER'] IRB.__send__(:easter_egg) @@ -436,7 +445,6 @@ def display_document(matched, driver: nil) namespace = retrieve_doc_namespace(matched) return unless namespace - driver ||= RDoc::RI::Driver.new if namespace.is_a?(Array) out = RDoc::Markup::Document.new namespace.each do |m| diff --git a/lib/irb/lc/error.rb b/lib/irb/lc/error.rb index 9041ec4d6c8b7d..ee0f047822223d 100644 --- a/lib/irb/lc/error.rb +++ b/lib/irb/lc/error.rb @@ -47,11 +47,6 @@ def initialize(val) super("Undefined prompt mode(#{val}).") end end - class IllegalRCGenerator < StandardError - def initialize - super("Define illegal RC_NAME_GENERATOR.") - end - end # :startdoc: end diff --git a/lib/irb/lc/ja/error.rb b/lib/irb/lc/ja/error.rb index f7f2b13c45ca1e..9e2e5b8870750c 100644 --- a/lib/irb/lc/ja/error.rb +++ b/lib/irb/lc/ja/error.rb @@ -47,11 +47,6 @@ def initialize(val) super("プロンプトモード(#{val})は定義されていません.") end end - class IllegalRCGenerator < StandardError - def initialize - super("RC_NAME_GENERATORが正しく定義されていません.") - end - end # :startdoc: end diff --git a/lib/irb/lc/ja/help-message b/lib/irb/lc/ja/help-message index cec339cf2fb96c..99f4449b3b6006 100644 --- a/lib/irb/lc/ja/help-message +++ b/lib/irb/lc/ja/help-message @@ -9,10 +9,18 @@ Usage: irb.rb [options] [programfile] [arguments] -W[level=2] ruby -W と同じ. --context-mode n 新しいワークスペースを作成した時に関連する Binding オブジェクトの作成方法を 0 から 3 のいずれかに設定する. + --extra-doc-dir 指定したディレクトリのドキュメントを追加で読み込む. --echo 実行結果を表示する(デフォルト). --noecho 実行結果を表示しない. + --echo-on-assignment + 代入結果を表示する. + --noecho-on-assignment + 代入結果を表示しない. + --truncate-echo-on-assignment + truncateされた代入結果を表示する(デフォルト). --inspect 結果出力にinspectを用いる. --noinspect 結果出力にinspectを用いない. + --no-pager ページャを使用しない. --multiline マルチラインエディタを利用する. --nomultiline マルチラインエディタを利用しない. --singleline シングルラインエディタを利用する. @@ -34,6 +42,8 @@ Usage: irb.rb [options] [programfile] [arguments] --sample-book-mode/--simple-prompt 非常にシンプルなプロンプトを用いるモードです. --noprompt プロンプト表示を行なわない. + --script スクリプトモード(最初の引数をスクリプトファイルとして扱う、デフォルト) + --noscript 引数をargvとして扱う. --single-irb irb 中で self を実行して得られるオブジェクトをサ ブ irb と共有する. --tracer コマンド実行時にトレースを行なう. diff --git a/lib/irb/statement.rb b/lib/irb/statement.rb index 1e026d112ff796..a3391c12a3f900 100644 --- a/lib/irb/statement.rb +++ b/lib/irb/statement.rb @@ -16,10 +16,6 @@ def should_be_handled_by_debugger? raise NotImplementedError end - def evaluable_code - raise NotImplementedError - end - class EmptyInput < Statement def is_assignment? false @@ -37,10 +33,6 @@ def should_be_handled_by_debugger? def code "" end - - def evaluable_code - code - end end class Expression < Statement @@ -60,18 +52,15 @@ def should_be_handled_by_debugger? def is_assignment? @is_assignment end - - def evaluable_code - @code - end end class Command < Statement - def initialize(code, command, arg, command_class) - @code = code - @command = command - @arg = arg + attr_reader :command_class, :arg + + def initialize(original_code, command_class, arg) + @code = original_code @command_class = command_class + @arg = arg end def is_assignment? @@ -86,17 +75,6 @@ def should_be_handled_by_debugger? require_relative 'command/debug' IRB::Command::DebugCommand > @command_class end - - def evaluable_code - # Hook command-specific transformation to return valid Ruby code - if @command_class.respond_to?(:transform_args) - arg = @command_class.transform_args(@arg) - else - arg = @arg - end - - [@command, arg].compact.join(' ') - end end end end diff --git a/lib/irb/workspace.rb b/lib/irb/workspace.rb index 4c3b5e425e1183..1490f7b478d432 100644 --- a/lib/irb/workspace.rb +++ b/lib/irb/workspace.rb @@ -108,8 +108,10 @@ def initialize(*main) # IRB.conf[:__MAIN__] attr_reader :main - def load_commands_to_main - main.extend ExtendCommandBundle + def load_helper_methods_to_main + if !(class<" diff --git a/lib/prism/prism.gemspec b/lib/prism/prism.gemspec index d43fcfd36b219b..6cf28460c2d5af 100644 --- a/lib/prism/prism.gemspec +++ b/lib/prism/prism.gemspec @@ -2,7 +2,7 @@ Gem::Specification.new do |spec| spec.name = "prism" - spec.version = "0.24.0" + spec.version = "0.25.0" spec.authors = ["Shopify"] spec.email = ["ruby@shopify.com"] diff --git a/lib/prism/translation/parser.rb b/lib/prism/translation/parser.rb index 7a2d8e547bf6cc..0d11b8f5668cca 100644 --- a/lib/prism/translation/parser.rb +++ b/lib/prism/translation/parser.rb @@ -124,7 +124,7 @@ def error_diagnostic(error, offset_cache) when :argument_block_multi Diagnostic.new(:error, :block_and_blockarg, {}, diagnostic_location, []) when :argument_formal_constant - Diagnostic.new(:error, :formal_argument, {}, diagnostic_location, []) + Diagnostic.new(:error, :argument_const, {}, diagnostic_location, []) when :argument_formal_class Diagnostic.new(:error, :argument_cvar, {}, diagnostic_location, []) when :argument_formal_global @@ -135,6 +135,8 @@ def error_diagnostic(error, offset_cache) Diagnostic.new(:error, :no_anonymous_blockarg, {}, diagnostic_location, []) when :argument_no_forwarding_star Diagnostic.new(:error, :no_anonymous_restarg, {}, diagnostic_location, []) + when :argument_no_forwarding_star_star + Diagnostic.new(:error, :no_anonymous_kwrestarg, {}, diagnostic_location, []) when :begin_lonely_else location = location.copy(length: 4) diagnostic_location = build_range(location, offset_cache) @@ -171,6 +173,8 @@ def error_diagnostic(error, offset_cache) Diagnostic.new(:error, :duplicate_argument, {}, diagnostic_location, []) when :parameter_numbered_reserved Diagnostic.new(:error, :reserved_for_numparam, { name: location.slice }, diagnostic_location, []) + when :regexp_unknown_options + Diagnostic.new(:error, :regexp_options, { options: location.slice[1..] }, diagnostic_location, []) when :singleton_for_literals Diagnostic.new(:error, :singleton_literal, {}, diagnostic_location, []) when :string_literal_eof @@ -193,8 +197,12 @@ def warning_diagnostic(warning, offset_cache) Diagnostic.new(:warning, :ambiguous_prefix, { prefix: "+" }, diagnostic_location, []) when :ambiguous_first_argument_minus Diagnostic.new(:warning, :ambiguous_prefix, { prefix: "-" }, diagnostic_location, []) + when :ambiguous_prefix_ampersand + Diagnostic.new(:warning, :ambiguous_prefix, { prefix: "&" }, diagnostic_location, []) when :ambiguous_prefix_star Diagnostic.new(:warning, :ambiguous_prefix, { prefix: "*" }, diagnostic_location, []) + when :ambiguous_prefix_star_star + Diagnostic.new(:warning, :ambiguous_prefix, { prefix: "**" }, diagnostic_location, []) when :ambiguous_slash Diagnostic.new(:warning, :ambiguous_regexp, {}, diagnostic_location, []) when :dot_dot_dot_eol diff --git a/lib/prism/translation/parser/compiler.rb b/lib/prism/translation/parser/compiler.rb index d64e382c7845e7..0b1893cade9a4e 100644 --- a/lib/prism/translation/parser/compiler.rb +++ b/lib/prism/translation/parser/compiler.rb @@ -406,9 +406,6 @@ def visit_class_variable_read_node(node) # @@foo = 1 # ^^^^^^^^^ - # - # @@foo, @@bar = 1 - # ^^^^^ ^^^^^ def visit_class_variable_write_node(node) builder.assign( builder.assignable(builder.cvar(token(node.name_loc))), @@ -701,9 +698,6 @@ def visit_global_variable_read_node(node) # $foo = 1 # ^^^^^^^^ - # - # $foo, $bar = 1 - # ^^^^ ^^^^ def visit_global_variable_write_node(node) builder.assign( builder.assignable(builder.gvar(token(node.name_loc))), @@ -953,14 +947,35 @@ def visit_interpolated_regular_expression_node(node) def visit_interpolated_string_node(node) if node.heredoc? children, closing = visit_heredoc(node) - builder.string_compose(token(node.opening_loc), children, closing) + + return builder.string_compose(token(node.opening_loc), children, closing) + end + + parts = if node.parts.one? { |part| part.type == :string_node } + node.parts.flat_map do |node| + if node.type == :string_node && node.unescaped.lines.count >= 2 + start_offset = node.content_loc.start_offset + + node.unescaped.lines.map do |line| + end_offset = start_offset + line.length + offsets = srange_offsets(start_offset, end_offset) + start_offset = end_offset + + builder.string_internal([line, offsets]) + end + else + visit(node) + end + end else - builder.string_compose( - token(node.opening_loc), - visit_all(node.parts), - token(node.closing_loc) - ) + visit_all(node.parts) end + + builder.string_compose( + token(node.opening_loc), + parts, + token(node.closing_loc) + ) end # :"foo #{bar}" @@ -1007,6 +1022,7 @@ def visit_keyword_rest_parameter_node(node) end # -> {} + # ^^^^^ def visit_lambda_node(node) parameters = node.parameters @@ -1423,6 +1439,11 @@ def visit_self_node(node) builder.self(token(node.location)) end + # A shareable constant. + def visit_shareable_constant_node(node) + visit(node.write) + end + # class << self; end # ^^^^^^^^^^^^^^^^^^ def visit_singleton_class_node(node) @@ -1487,9 +1508,23 @@ def visit_string_node(node) elsif node.opening == "?" builder.character([node.unescaped, srange(node.location)]) else + parts = if node.content.lines.count <= 1 || node.unescaped.lines.count <= 1 + [builder.string_internal([node.unescaped, srange(node.content_loc)])] + else + start_offset = node.content_loc.start_offset + + [node.content.lines, node.unescaped.lines].transpose.map do |content_line, unescaped_line| + end_offset = start_offset + content_line.length + offsets = srange_offsets(start_offset, end_offset) + start_offset = end_offset + + builder.string_internal([unescaped_line, offsets]) + end + end + builder.string_compose( token(node.opening_loc), - [builder.string_internal([node.unescaped, srange(node.content_loc)])], + parts, token(node.closing_loc) ) end @@ -1664,9 +1699,23 @@ def visit_x_string_node(node) children, closing = visit_heredoc(node.to_interpolated) builder.xstring_compose(token(node.opening_loc), children, closing) else + parts = if node.unescaped.lines.one? + [builder.string_internal([node.unescaped, srange(node.content_loc)])] + else + start_offset = node.content_loc.start_offset + + node.unescaped.lines.map do |line| + end_offset = start_offset + line.length + offsets = srange_offsets(start_offset, end_offset) + start_offset = end_offset + + builder.string_internal([line, offsets]) + end + end + builder.xstring_compose( token(node.opening_loc), - [builder.string_internal([node.unescaped, srange(node.content_loc)])], + parts, token(node.closing_loc) ) end diff --git a/lib/prism/translation/parser/lexer.rb b/lib/prism/translation/parser/lexer.rb index b28273b03f0b73..9d7caae0ba32d6 100644 --- a/lib/prism/translation/parser/lexer.rb +++ b/lib/prism/translation/parser/lexer.rb @@ -167,7 +167,7 @@ class Lexer TILDE: :tTILDE, UAMPERSAND: :tAMPER, UCOLON_COLON: :tCOLON3, - UDOT_DOT: :tDOT2, + UDOT_DOT: :tBDOT2, UDOT_DOT_DOT: :tBDOT3, UMINUS: :tUMINUS, UMINUS_NUM: :tUNARY_NUM, @@ -217,6 +217,8 @@ def to_a index = 0 length = lexed.length + heredoc_identifier_stack = [] + while index < length token, state = lexed[index] index += 1 @@ -250,7 +252,7 @@ def to_a when :tNL value = nil when :tFLOAT - value = Float(value) + value = parse_float(value) when :tIMAGINARY value = parse_complex(value) when :tINTEGER @@ -259,7 +261,7 @@ def to_a location = Range.new(source_buffer, offset_cache[token.location.start_offset + 1], offset_cache[token.location.end_offset]) end - value = Integer(value) + value = parse_integer(value) when :tLABEL value.chomp!(":") when :tLABEL_END @@ -267,7 +269,7 @@ def to_a when :tLCURLY type = :tLBRACE if state == EXPR_BEG | EXPR_LABEL when :tNTH_REF - value = Integer(value.delete_prefix("$")) + value = parse_integer(value.delete_prefix("$")) when :tOP_ASGN value.chomp!("=") when :tRATIONAL @@ -275,28 +277,52 @@ def to_a when :tSPACE value = nil when :tSTRING_BEG + if token.type == :HEREDOC_START + heredoc_identifier_stack.push(value.match(/<<[-~]?["'`]?(?.*?)["'`]?\z/)[:heredoc_identifier]) + end if ["\"", "'"].include?(value) && (next_token = lexed[index][0]) && next_token.type == :STRING_END next_location = token.location.join(next_token.location) type = :tSTRING value = "" location = Range.new(source_buffer, offset_cache[next_location.start_offset], offset_cache[next_location.end_offset]) index += 1 - elsif ["\"", "'"].include?(value) && (next_token = lexed[index][0]) && next_token.type == :STRING_CONTENT && (next_next_token = lexed[index + 1][0]) && next_next_token.type == :STRING_END + elsif ["\"", "'"].include?(value) && (next_token = lexed[index][0]) && next_token.type == :STRING_CONTENT && next_token.value.lines.count <= 1 && (next_next_token = lexed[index + 1][0]) && next_next_token.type == :STRING_END next_location = token.location.join(next_next_token.location) type = :tSTRING - value = next_token.value + value = next_token.value.gsub("\\\\", "\\") location = Range.new(source_buffer, offset_cache[next_location.start_offset], offset_cache[next_location.end_offset]) index += 2 elsif value.start_with?("<<") quote = value[2] == "-" || value[2] == "~" ? value[3] : value[2] - value = "<<#{quote == "'" || quote == "\"" ? quote : "\""}" + if quote == "`" + type = :tXSTRING_BEG + value = "<<`" + else + value = "<<#{quote == "'" || quote == "\"" ? quote : "\""}" + end end when :tSTRING_CONTENT unless (lines = token.value.lines).one? start_offset = offset_cache[token.location.start_offset] lines.map do |line| - end_offset = start_offset + line.length - tokens << [:tSTRING_CONTENT, [line, Range.new(source_buffer, offset_cache[start_offset], offset_cache[end_offset])]] + newline = line.end_with?("\r\n") ? "\r\n" : "\n" + chomped_line = line.chomp + if match = chomped_line.match(/(?\\+)\z/) + adjustment = match[:backslashes].size / 2 + adjusted_line = chomped_line.delete_suffix("\\" * adjustment) + if match[:backslashes].size.odd? + adjusted_line.delete_suffix!("\\") + adjustment += 2 + else + adjusted_line << newline + end + else + adjusted_line = line + adjustment = 0 + end + + end_offset = start_offset + adjusted_line.length + adjustment + tokens << [:tSTRING_CONTENT, [adjusted_line, Range.new(source_buffer, offset_cache[start_offset], offset_cache[end_offset])]] start_offset = end_offset end next @@ -306,7 +332,7 @@ def to_a when :tSTRING_END if token.type == :HEREDOC_END && value.end_with?("\n") newline_length = value.end_with?("\r\n") ? 2 : 1 - value = value.sub(/\r?\n\z/, '') + value = heredoc_identifier_stack.pop location = Range.new(source_buffer, offset_cache[token.location.start_offset], offset_cache[token.location.end_offset - newline_length]) elsif token.type == :REGEXP_END value = value[0] @@ -325,6 +351,10 @@ def to_a if !tokens.empty? && tokens.dig(-1, 0) == :kDEF type = :tIDENTIFIER end + when :tXSTRING_BEG + if (next_token = lexed[index][0]) && next_token.type != :STRING_CONTENT && next_token.type != :STRING_END + type = :tBACK_REF2 + end end tokens << [type, [value, location]] @@ -339,6 +369,20 @@ def to_a private + # Parse an integer from the string representation. + def parse_integer(value) + Integer(value) + rescue ArgumentError + 0 + end + + # Parse a float from the string representation. + def parse_float(value) + Float(value) + rescue ArgumentError + 0.0 + end + # Parse a complex from the string representation. def parse_complex(value) value.chomp!("i") @@ -346,10 +390,12 @@ def parse_complex(value) if value.end_with?("r") Complex(0, parse_rational(value)) elsif value.start_with?(/0[BbOoDdXx]/) - Complex(0, Integer(value)) + Complex(0, parse_integer(value)) else Complex(0, value) end + rescue ArgumentError + 0i end # Parse a rational from the string representation. @@ -357,10 +403,12 @@ def parse_rational(value) value.chomp!("r") if value.start_with?(/0[BbOoDdXx]/) - Rational(Integer(value)) + Rational(parse_integer(value)) else Rational(value) end + rescue ArgumentError + 0r end end end diff --git a/lib/prism/translation/parser/rubocop.rb b/lib/prism/translation/parser/rubocop.rb index 91602d16eff9cc..6c9687a5cc0ecc 100644 --- a/lib/prism/translation/parser/rubocop.rb +++ b/lib/prism/translation/parser/rubocop.rb @@ -6,8 +6,8 @@ require "parser" require "rubocop" -require "prism" -require "prism/translation/parser" +require_relative "../../prism" +require_relative "../parser" module Prism module Translation @@ -31,12 +31,12 @@ def parser_class(ruby_version) if ruby_version == Prism::Translation::Parser::VERSION_3_3 warn "WARN: Setting `TargetRubyVersion: 80_82_73_83_77.33` is deprecated. " \ "Set to `ParserEngine: parser_prism` and `TargetRubyVersion: 3.3` instead." - require "prism/translation/parser33" + require_relative "../parser33" Prism::Translation::Parser33 elsif ruby_version == Prism::Translation::Parser::VERSION_3_4 warn "WARN: Setting `TargetRubyVersion: 80_82_73_83_77.34` is deprecated. " \ "Set to `ParserEngine: parser_prism` and `TargetRubyVersion: 3.4` instead." - require "prism/translation/parser34" + require_relative "../parser34" Prism::Translation::Parser34 else super @@ -49,12 +49,12 @@ def parser_class(ruby_version, _parser_engine) if ruby_version == Prism::Translation::Parser::VERSION_3_3 warn "WARN: Setting `TargetRubyVersion: 80_82_73_83_77.33` is deprecated. " \ "Set to `ParserEngine: parser_prism` and `TargetRubyVersion: 3.3` instead." - require "prism/translation/parser33" + require_relative "../parser33" Prism::Translation::Parser33 elsif ruby_version == Prism::Translation::Parser::VERSION_3_4 warn "WARN: Setting `TargetRubyVersion: 80_82_73_83_77.34` is deprecated. " \ "Set to `ParserEngine: parser_prism` and `TargetRubyVersion: 3.4` instead." - require "prism/translation/parser34" + require_relative "../parser34" Prism::Translation::Parser34 else super diff --git a/lib/prism/translation/ripper.rb b/lib/prism/translation/ripper.rb index 94156d49888c35..3c06f6a40d50d2 100644 --- a/lib/prism/translation/ripper.rb +++ b/lib/prism/translation/ripper.rb @@ -583,13 +583,23 @@ def visit_alias_global_variable_node(node) # foo => bar | baz # ^^^^^^^^^ def visit_alternation_pattern_node(node) - left = visit(node.left) - right = visit(node.right) + left = visit_pattern_node(node.left) + right = visit_pattern_node(node.right) bounds(node.location) on_binary(left, :|, right) end + # Visit a pattern within a pattern match. This is used to bypass the + # parenthesis node that can be used to wrap patterns. + private def visit_pattern_node(node) + if node.is_a?(ParenthesesNode) + visit(node.body) + else + visit(node) + end + end + # a and b # ^^^^^^^ def visit_and_node(node) @@ -1952,7 +1962,7 @@ def visit_in_node(node) # This is a special case where we're not going to call on_in directly # because we don't have access to the consequent. Instead, we'll return # the component parts and let the parent node handle it. - pattern = visit(node.pattern) + pattern = visit_pattern_node(node.pattern) statements = if node.statements.nil? bounds(node.location) @@ -2389,7 +2399,7 @@ def visit_match_last_line_node(node) # ^^^^^^^^^^ def visit_match_predicate_node(node) value = visit(node.value) - pattern = on_in(visit(node.pattern), nil, nil) + pattern = on_in(visit_pattern_node(node.pattern), nil, nil) on_case(value, pattern) end @@ -2398,7 +2408,7 @@ def visit_match_predicate_node(node) # ^^^^^^^^^^ def visit_match_required_node(node) value = visit(node.value) - pattern = on_in(visit(node.pattern), nil, nil) + pattern = on_in(visit_pattern_node(node.pattern), nil, nil) on_case(value, pattern) end @@ -2848,6 +2858,11 @@ def visit_self_node(node) on_var_ref(on_kw("self")) end + # A shareable constant. + def visit_shareable_constant_node(node) + visit(node.write) + end + # class << self; end # ^^^^^^^^^^^^^^^^^^ def visit_singleton_class_node(node) diff --git a/lib/prism/translation/ripper/sexp.rb b/lib/prism/translation/ripper/sexp.rb index 32cc63e47a6d30..dc26a639a351fd 100644 --- a/lib/prism/translation/ripper/sexp.rb +++ b/lib/prism/translation/ripper/sexp.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require "prism/translation/ripper" +require_relative "../ripper" module Prism module Translation diff --git a/lib/prism/translation/ruby_parser.rb b/lib/prism/translation/ruby_parser.rb index 466406184102ca..5c59fe31810562 100644 --- a/lib/prism/translation/ruby_parser.rb +++ b/lib/prism/translation/ruby_parser.rb @@ -192,19 +192,19 @@ def visit_block_parameters_node(node) if node.opening == "(" result.line = node.opening_loc.start_line - result.max_line = node.closing_loc.end_line + result.line_max = node.closing_loc.end_line shadow_loc = false end if node.locals.any? shadow = s(node, :shadow).concat(visit_all(node.locals)) shadow.line = node.locals.first.location.start_line - shadow.max_line = node.locals.last.location.end_line + shadow.line_max = node.locals.last.location.end_line result << shadow if shadow_loc result.line = shadow.line - result.max_line = shadow.max_line + result.line_max = shadow.line_max end end @@ -1274,6 +1274,11 @@ def visit_self_node(node) s(node, :self) end + # A shareable constant. + def visit_shareable_constant_node(node) + visit(node.write) + end + # class << self; end # ^^^^^^^^^^^^^^^^^^ def visit_singleton_class_node(node) @@ -1407,7 +1412,7 @@ def visit_x_string_node(node) if node.heredoc? result.line = node.content_loc.start_line - result.max_line = node.content_loc.end_line + result.line_max = node.content_loc.end_line end result @@ -1434,7 +1439,7 @@ def s(node, *arguments) result = Sexp.new(*arguments) result.file = file result.line = node.location.start_line - result.max_line = node.location.end_line + result.line_max = node.location.end_line result end diff --git a/lib/random/formatter.rb b/lib/random/formatter.rb index 0548e86ccaafc6..037f9d8748f27b 100644 --- a/lib/random/formatter.rb +++ b/lib/random/formatter.rb @@ -165,7 +165,7 @@ def urlsafe_base64(n=nil, padding=false) # # The result contains 122 random bits (15.25 random bytes). # - # See RFC4122[https://datatracker.ietf.org/doc/html/rfc4122] for details of UUID. + # See RFC4122[https://www.rfc-editor.org/rfc/rfc4122] for details of UUID. # def uuid ary = random_bytes(16) diff --git a/lib/rdoc/markdown.rb b/lib/rdoc/markdown.rb index 157f2fa3fe1add..5c72a5f2248ae6 100644 --- a/lib/rdoc/markdown.rb +++ b/lib/rdoc/markdown.rb @@ -16445,12 +16445,12 @@ def _DefinitionListItem return _tmp end - # DefinitionListLabel = StrChunk:label @Sp @Newline { label } + # DefinitionListLabel = Inline:label @Sp @Newline { label } def _DefinitionListLabel _save = self.pos while true # sequence - _tmp = apply(:_StrChunk) + _tmp = apply(:_Inline) label = @result unless _tmp self.pos = _save @@ -16777,7 +16777,7 @@ def _DefinitionListDefinition Rules[:_TableAlign] = rule_info("TableAlign", "< /:?-+:?/ > @Sp { text.start_with?(\":\") ? (text.end_with?(\":\") ? :center : :left) : (text.end_with?(\":\") ? :right : nil) }") Rules[:_DefinitionList] = rule_info("DefinitionList", "&{ definition_lists? } DefinitionListItem+:list { RDoc::Markup::List.new :NOTE, *list.flatten }") Rules[:_DefinitionListItem] = rule_info("DefinitionListItem", "DefinitionListLabel+:label DefinitionListDefinition+:defns { list_items = [] list_items << RDoc::Markup::ListItem.new(label, defns.shift) list_items.concat defns.map { |defn| RDoc::Markup::ListItem.new nil, defn } unless list_items.empty? list_items }") - Rules[:_DefinitionListLabel] = rule_info("DefinitionListLabel", "StrChunk:label @Sp @Newline { label }") + Rules[:_DefinitionListLabel] = rule_info("DefinitionListLabel", "Inline:label @Sp @Newline { label }") Rules[:_DefinitionListDefinition] = rule_info("DefinitionListDefinition", "@NonindentSpace \":\" @Space Inlines:a @BlankLine+ { paragraph a }") # :startdoc: end diff --git a/lib/rdoc/ri/driver.rb b/lib/rdoc/ri/driver.rb index 08c4d08f81e329..64783dc163781c 100644 --- a/lib/rdoc/ri/driver.rb +++ b/lib/rdoc/ri/driver.rb @@ -1088,7 +1088,7 @@ def interactive loop do name = if defined? Readline then - Readline.readline ">> " + Readline.readline ">> ", true else print ">> " $stdin.gets diff --git a/lib/rdoc/store.rb b/lib/rdoc/store.rb index fe749edc1cea9a..cd27d47dd1dfdc 100644 --- a/lib/rdoc/store.rb +++ b/lib/rdoc/store.rb @@ -559,9 +559,7 @@ def load_all def load_cache #orig_enc = @encoding - File.open cache_path, 'rb' do |io| - @cache = Marshal.load io - end + @cache = marshal_load(cache_path) load_enc = @cache[:encoding] @@ -618,9 +616,7 @@ def load_class klass_name def load_class_data klass_name file = class_file klass_name - File.open file, 'rb' do |io| - Marshal.load io - end + marshal_load(file) rescue Errno::ENOENT => e error = MissingFileError.new(self, file, klass_name) error.set_backtrace e.backtrace @@ -633,14 +629,10 @@ def load_class_data klass_name def load_method klass_name, method_name file = method_file klass_name, method_name - File.open file, 'rb' do |io| - obj = Marshal.load io - obj.store = self - obj.parent = - find_class_or_module(klass_name) || load_class(klass_name) unless - obj.parent - obj - end + obj = marshal_load(file) + obj.store = self + obj.parent ||= find_class_or_module(klass_name) || load_class(klass_name) + obj rescue Errno::ENOENT => e error = MissingFileError.new(self, file, klass_name + method_name) error.set_backtrace e.backtrace @@ -653,11 +645,9 @@ def load_method klass_name, method_name def load_page page_name file = page_file page_name - File.open file, 'rb' do |io| - obj = Marshal.load io - obj.store = self - obj - end + obj = marshal_load(file) + obj.store = self + obj rescue Errno::ENOENT => e error = MissingFileError.new(self, file, page_name) error.set_backtrace e.backtrace @@ -979,4 +969,21 @@ def unique_modules @unique_modules end + private + def marshal_load(file) + File.open(file, 'rb') {|io| Marshal.load(io, MarshalFilter)} + end + + MarshalFilter = proc do |obj| + case obj + when true, false, nil, Array, Class, Encoding, Hash, Integer, String, Symbol, RDoc::Text + else + unless obj.class.name.start_with?("RDoc::") + raise TypeError, "not permitted class: #{obj.class.name}" + end + end + obj + end + private_constant :MarshalFilter + end diff --git a/lib/rdoc/version.rb b/lib/rdoc/version.rb index 04dfaebf746d87..87842d9847a9ee 100644 --- a/lib/rdoc/version.rb +++ b/lib/rdoc/version.rb @@ -5,6 +5,6 @@ module RDoc ## # RDoc version you are using - VERSION = '6.6.2' + VERSION = '6.6.3.1' end diff --git a/lib/reline.rb b/lib/reline.rb index fb199820810874..f0060f5c9c4a5e 100644 --- a/lib/reline.rb +++ b/lib/reline.rb @@ -75,10 +75,10 @@ class Core def initialize self.output = STDOUT + @mutex = Mutex.new @dialog_proc_list = {} yield self @completion_quote_character = nil - @bracketed_paste_finished = false end def io_gate @@ -220,26 +220,16 @@ def get_screen_size Reline::DEFAULT_DIALOG_PROC_AUTOCOMPLETE = ->() { # autocomplete - return nil unless config.autocompletion - if just_cursor_moving and completion_journey_data.nil? - # Auto complete starts only when edited - return nil - end - pre, target, post = retrieve_completion_block(true) - if target.nil? or target.empty? or (completion_journey_data&.pointer == -1 and target.size <= 3) - return nil - end - if completion_journey_data and completion_journey_data.list - result = completion_journey_data.list.dup - result.shift - pointer = completion_journey_data.pointer - 1 - else - result = call_completion_proc_with_checking_args(pre, target, post) - pointer = nil - end - if result and result.size == 1 and result[0] == target and pointer != 0 - result = nil - end + return unless config.autocompletion + + journey_data = completion_journey_data + return unless journey_data + + target = journey_data.list[journey_data.pointer] + result = journey_data.list.drop(1) + pointer = journey_data.pointer - 1 + return if target.empty? || (result == [target] && pointer < 0) + target_width = Reline::Unicode.calculate_width(target) x = cursor_pos.x - target_width if x < 0 @@ -265,12 +255,15 @@ def get_screen_size Reline::DEFAULT_DIALOG_CONTEXT = Array.new def readmultiline(prompt = '', add_hist = false, &confirm_multiline_termination) - Reline.update_iogate - io_gate.with_raw_input do + @mutex.synchronize do unless confirm_multiline_termination raise ArgumentError.new('#readmultiline needs block to confirm multiline termination') end - inner_readline(prompt, add_hist, true, &confirm_multiline_termination) + + Reline.update_iogate + io_gate.with_raw_input do + inner_readline(prompt, add_hist, true, &confirm_multiline_termination) + end whole_buffer = line_editor.whole_buffer.dup whole_buffer.taint if RUBY_VERSION < '2.7' @@ -278,23 +271,32 @@ def readmultiline(prompt = '', add_hist = false, &confirm_multiline_termination) Reline::HISTORY << whole_buffer end - line_editor.reset_line if line_editor.whole_buffer.nil? - whole_buffer + if line_editor.eof? + line_editor.reset_line + # Return nil if the input is aborted by C-d. + nil + else + whole_buffer + end end end def readline(prompt = '', add_hist = false) - Reline.update_iogate - inner_readline(prompt, add_hist, false) + @mutex.synchronize do + Reline.update_iogate + io_gate.with_raw_input do + inner_readline(prompt, add_hist, false) + end - line = line_editor.line.dup - line.taint if RUBY_VERSION < '2.7' - if add_hist and line and line.chomp("\n").size > 0 - Reline::HISTORY << line.chomp("\n") - end + line = line_editor.line.dup + line.taint if RUBY_VERSION < '2.7' + if add_hist and line and line.chomp("\n").size > 0 + Reline::HISTORY << line.chomp("\n") + end - line_editor.reset_line if line_editor.line.nil? - line + line_editor.reset_line if line_editor.line.nil? + line + end end private def inner_readline(prompt, add_hist, multiline, &confirm_multiline_termination) @@ -326,9 +328,11 @@ def readline(prompt = '', add_hist = false) line_editor.prompt_proc = prompt_proc line_editor.auto_indent_proc = auto_indent_proc line_editor.dig_perfect_match_proc = dig_perfect_match_proc - line_editor.pre_input_hook = pre_input_hook - @dialog_proc_list.each_pair do |name_sym, d| - line_editor.add_dialog_proc(name_sym, d.dialog_proc, d.context) + pre_input_hook&.call + unless Reline::IOGate == Reline::GeneralIO + @dialog_proc_list.each_pair do |name_sym, d| + line_editor.add_dialog_proc(name_sym, d.dialog_proc, d.context) + end end unless config.test_mode @@ -337,47 +341,32 @@ def readline(prompt = '', add_hist = false) io_gate.set_default_key_bindings(config) end + line_editor.print_nomultiline_prompt(prompt) + line_editor.update_dialogs line_editor.rerender begin line_editor.set_signal_handlers - prev_pasting_state = false loop do - prev_pasting_state = io_gate.in_pasting? read_io(config.keyseq_timeout) { |inputs| line_editor.set_pasting_state(io_gate.in_pasting?) - inputs.each { |c| - line_editor.input_key(c) - line_editor.rerender - } - if @bracketed_paste_finished - line_editor.rerender_all - @bracketed_paste_finished = false - end + inputs.each { |key| line_editor.update(key) } } - if prev_pasting_state == true and not io_gate.in_pasting? and not line_editor.finished? - line_editor.set_pasting_state(false) - prev_pasting_state = false - line_editor.rerender_all + if line_editor.finished? + line_editor.render_finished + break + else + line_editor.set_pasting_state(io_gate.in_pasting?) + line_editor.rerender end - break if line_editor.finished? end io_gate.move_cursor_column(0) rescue Errno::EIO # Maybe the I/O has been closed. - rescue StandardError => e + ensure line_editor.finalize io_gate.deprep(otio) - raise e - rescue Exception - # Including Interrupt - line_editor.finalize - io_gate.deprep(otio) - raise end - - line_editor.finalize - io_gate.deprep(otio) end # GNU Readline waits for "keyseq-timeout" milliseconds to see if the ESC @@ -395,7 +384,6 @@ def readline(prompt = '', add_hist = false) c = io_gate.getc(Float::INFINITY) if c == -1 result = :unmatched - @bracketed_paste_finished = true else buffer << c result = key_stroke.match_status(buffer) diff --git a/lib/reline/ansi.rb b/lib/reline/ansi.rb index c2e5075ea8b5b4..a3719f502c957a 100644 --- a/lib/reline/ansi.rb +++ b/lib/reline/ansi.rb @@ -3,6 +3,8 @@ require_relative 'terminfo' class Reline::ANSI + RESET_COLOR = "\e[0m" + CAPNAME_KEY_BINDINGS = { 'khome' => :ed_move_to_beg, 'kend' => :ed_move_to_end, @@ -149,7 +151,11 @@ def self.output=(val) end def self.with_raw_input - @@input.raw { yield } + if @@input.tty? + @@input.raw(intr: true) { yield } + else + yield + end end @@buf = [] @@ -157,11 +163,13 @@ def self.inner_getc(timeout_second) unless @@buf.empty? return @@buf.shift end - until c = @@input.raw(intr: true) { @@input.wait_readable(0.1) && @@input.getbyte } - timeout_second -= 0.1 + until @@input.wait_readable(0.01) + timeout_second -= 0.01 return nil if timeout_second <= 0 - Reline.core.line_editor.resize + + Reline.core.line_editor.handle_signal end + c = @@input.getbyte (c == 0x16 && @@input.raw(min: 0, time: 0, &:getbyte)) || c rescue Errno::EIO # Maybe the I/O has been closed. @@ -307,7 +315,7 @@ def self.move_cursor_down(x) end def self.hide_cursor - if Reline::Terminfo.enabled? + if Reline::Terminfo.enabled? && Reline::Terminfo.term_supported? begin @@output.write Reline::Terminfo.tigetstr('civis') rescue Reline::Terminfo::TerminfoError @@ -319,7 +327,7 @@ def self.hide_cursor end def self.show_cursor - if Reline::Terminfo.enabled? + if Reline::Terminfo.enabled? && Reline::Terminfo.term_supported? begin @@output.write Reline::Terminfo.tigetstr('cnorm') rescue Reline::Terminfo::TerminfoError diff --git a/lib/reline/general_io.rb b/lib/reline/general_io.rb index eaae63f925daf4..d52151ad3cd231 100644 --- a/lib/reline/general_io.rb +++ b/lib/reline/general_io.rb @@ -1,6 +1,8 @@ require 'io/wait' class Reline::GeneralIO + RESET_COLOR = '' # Do not send color reset sequence + def self.reset(encoding: nil) @@pasting = false if encoding @@ -44,6 +46,7 @@ def self.getc(_timeout_second) end c = nil loop do + Reline.core.line_editor.handle_signal result = @@input.wait_readable(0.1) next if result.nil? c = @@input.read(1) @@ -57,7 +60,7 @@ def self.ungetc(c) end def self.get_screen_size - [1, 1] + [24, 80] end def self.cursor_pos @@ -100,14 +103,6 @@ def self.in_pasting? @@pasting end - def self.start_pasting - @@pasting = true - end - - def self.finish_pasting - @@pasting = false - end - def self.prep end diff --git a/lib/reline/kill_ring.rb b/lib/reline/kill_ring.rb index 84d94a7ff6997a..201f6f3ca0c2ba 100644 --- a/lib/reline/kill_ring.rb +++ b/lib/reline/kill_ring.rb @@ -68,7 +68,7 @@ def initialize(max = 1024) def append(string, before_p = false) case @state when State::FRESH, State::YANK - @ring << RingPoint.new(string) + @ring << RingPoint.new(+string) @state = State::CONTINUED when State::CONTINUED, State::PROCESSED if before_p diff --git a/lib/reline/line_editor.rb b/lib/reline/line_editor.rb index b7cc6b747dc4b2..c236044e41fed9 100644 --- a/lib/reline/line_editor.rb +++ b/lib/reline/line_editor.rb @@ -6,7 +6,6 @@ class Reline::LineEditor # TODO: undo # TODO: Use "private alias_method" idiom after drop Ruby 2.5. - attr_reader :line attr_reader :byte_pointer attr_accessor :confirm_multiline_termination_proc attr_accessor :completion_proc @@ -14,7 +13,6 @@ class Reline::LineEditor attr_accessor :output_modifier_proc attr_accessor :prompt_proc attr_accessor :auto_indent_proc - attr_accessor :pre_input_hook attr_accessor :dig_perfect_match_proc attr_writer :output @@ -43,20 +41,43 @@ module CompletionState NORMAL = :normal COMPLETION = :completion MENU = :menu - JOURNEY = :journey MENU_WITH_PERFECT_MATCH = :menu_with_perfect_match PERFECT_MATCH = :perfect_match end - CompletionJourneyData = Struct.new(:preposing, :postposing, :list, :pointer) - MenuInfo = Struct.new(:target, :list) + RenderedScreen = Struct.new(:base_y, :lines, :cursor_y, keyword_init: true) + + CompletionJourneyState = Struct.new(:line_index, :pre, :target, :post, :list, :pointer) + + class MenuInfo + attr_reader :list + + def initialize(list) + @list = list + end + + def lines(screen_width) + return [] if @list.empty? + + list = @list.sort + sizes = list.map { |item| Reline::Unicode.calculate_width(item) } + item_width = sizes.max + 2 + num_cols = [screen_width / item_width, 1].max + num_rows = list.size.fdiv(num_cols).ceil + list_with_padding = list.zip(sizes).map { |item, size| item + ' ' * (item_width - size) } + aligned = (list_with_padding + [nil] * (num_rows * num_cols - list_with_padding.size)).each_slice(num_rows).to_a.transpose + aligned.map do |row| + row.join.rstrip + end + end + end - PROMPT_LIST_CACHE_TIMEOUT = 0.5 MINIMUM_SCROLLBAR_HEIGHT = 1 def initialize(config, encoding) @config = config @completion_append_character = '' + @screen_size = Reline::IOGate.get_screen_size reset_variables(encoding: encoding) end @@ -65,73 +86,38 @@ def io_gate end def set_pasting_state(in_pasting) + # While pasting, text to be inserted is stored to @continuous_insertion_buffer. + # After pasting, this buffer should be force inserted. + process_insert(force: true) if @in_pasting && !in_pasting @in_pasting = in_pasting end - def simplified_rendering? - if finished? - false - elsif @just_cursor_moving and not @rerender_all - true - else - not @rerender_all and not finished? and @in_pasting - end - end - private def check_mode_string - mode_string = nil if @config.show_mode_in_prompt if @config.editing_mode_is?(:vi_command) - mode_string = @config.vi_cmd_mode_string + @config.vi_cmd_mode_string elsif @config.editing_mode_is?(:vi_insert) - mode_string = @config.vi_ins_mode_string + @config.vi_ins_mode_string elsif @config.editing_mode_is?(:emacs) - mode_string = @config.emacs_mode_string + @config.emacs_mode_string else - mode_string = '?' + '?' end end - if mode_string != @prev_mode_string - @rerender_all = true - end - @prev_mode_string = mode_string - mode_string end - private def check_multiline_prompt(buffer, force_recalc: false) + private def check_multiline_prompt(buffer, mode_string) if @vi_arg prompt = "(arg: #{@vi_arg}) " - @rerender_all = true elsif @searching_prompt prompt = @searching_prompt - @rerender_all = true else prompt = @prompt end - if simplified_rendering? && !force_recalc - mode_string = check_mode_string - prompt = mode_string + prompt if mode_string - return [prompt, calculate_width(prompt, true), [prompt] * buffer.size] - end if @prompt_proc - use_cached_prompt_list = false - if @cached_prompt_list - if @just_cursor_moving - use_cached_prompt_list = true - elsif Time.now.to_f < (@prompt_cache_time + PROMPT_LIST_CACHE_TIMEOUT) and buffer.size == @cached_prompt_list.size - use_cached_prompt_list = true - end - end - use_cached_prompt_list = false if @rerender_all - if use_cached_prompt_list - prompt_list = @cached_prompt_list - else - prompt_list = @cached_prompt_list = @prompt_proc.(buffer).map { |pr| pr.gsub("\n", "\\n") } - @prompt_cache_time = Time.now.to_f - end + prompt_list = @prompt_proc.(buffer).map { |pr| pr.gsub("\n", "\\n") } prompt_list.map!{ prompt } if @vi_arg or @searching_prompt prompt_list = [prompt] if prompt_list.empty? - mode_string = check_mode_string prompt_list = prompt_list.map{ |pr| mode_string + pr } if mode_string prompt = prompt_list[@line_index] prompt = prompt_list[0] if prompt.nil? @@ -141,24 +127,17 @@ def simplified_rendering? prompt_list << prompt_list.last end end - prompt_width = calculate_width(prompt, true) - [prompt, prompt_width, prompt_list] + prompt_list else - mode_string = check_mode_string prompt = mode_string + prompt if mode_string - prompt_width = calculate_width(prompt, true) - [prompt, prompt_width, nil] + [prompt] * buffer.size end end def reset(prompt = '', encoding:) - @rest_height = (Reline::IOGate.get_screen_size.first - 1) - Reline::IOGate.cursor_pos.y @screen_size = Reline::IOGate.get_screen_size - @screen_height = @screen_size.first reset_variables(prompt, encoding: encoding) - Reline::IOGate.set_winch_handler do - @resized = true - end + @rendered_screen.base_y = Reline::IOGate.cursor_pos.y if ENV.key?('RELINE_ALT_SCROLLBAR') @full_block = '::' @upper_half_block = "''" @@ -182,67 +161,53 @@ def reset(prompt = '', encoding:) end end - def resize + def handle_signal + handle_interrupted + handle_resized + end + + private def handle_resized return unless @resized - @resized = false - @rest_height = (Reline::IOGate.get_screen_size.first - 1) - Reline::IOGate.cursor_pos.y - old_screen_size = @screen_size + @screen_size = Reline::IOGate.get_screen_size - @screen_height = @screen_size.first - if old_screen_size.last < @screen_size.last # columns increase - @rerender_all = true - rerender + @resized = false + scroll_into_view + Reline::IOGate.move_cursor_up @rendered_screen.cursor_y + @rendered_screen.base_y = Reline::IOGate.cursor_pos.y + @rendered_screen.lines = [] + @rendered_screen.cursor_y = 0 + render_differential + end + + private def handle_interrupted + return unless @interrupted + + @interrupted = false + clear_dialogs + scrolldown = render_differential + Reline::IOGate.scroll_down scrolldown + Reline::IOGate.move_cursor_column 0 + @rendered_screen.lines = [] + @rendered_screen.cursor_y = 0 + case @old_trap + when 'DEFAULT', 'SYSTEM_DEFAULT' + raise Interrupt + when 'IGNORE' + # Do nothing + when 'EXIT' + exit else - back = 0 - new_buffer = whole_lines - prompt, prompt_width, prompt_list = check_multiline_prompt(new_buffer) - new_buffer.each_with_index do |line, index| - prompt_width = calculate_width(prompt_list[index], true) if @prompt_proc - width = prompt_width + calculate_width(line) - height = calculate_height_by_width(width) - back += height - end - @highest_in_all = back - @highest_in_this = calculate_height_by_width(prompt_width + @cursor_max) - @first_line_started_from = - if @line_index.zero? - 0 - else - calculate_height_by_lines(@buffer_of_lines[0..(@line_index - 1)], prompt_list || prompt) - end - if @prompt_proc - prompt = prompt_list[@line_index] - prompt_width = calculate_width(prompt, true) - end - calculate_nearest_cursor - @started_from = calculate_height_by_width(prompt_width + @cursor) - 1 - Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last) - @highest_in_this = calculate_height_by_width(prompt_width + @cursor_max) - @rerender_all = true + @old_trap.call if @old_trap.respond_to?(:call) end end def set_signal_handlers - @old_trap = Signal.trap('INT') { - clear_dialog(0) - if @scroll_partial_screen - move_cursor_down(@screen_height - (@line_index - @scroll_partial_screen) - 1) - else - move_cursor_down(@highest_in_all - @line_index - 1) - end - Reline::IOGate.move_cursor_column(0) - scroll_down(1) - case @old_trap - when 'DEFAULT', 'SYSTEM_DEFAULT' - raise Interrupt - when 'IGNORE' - # Do nothing - when 'EXIT' - exit - else - @old_trap.call if @old_trap.respond_to?(:call) - end - } + Reline::IOGate.set_winch_handler do + @resized = true + end + @old_trap = Signal.trap('INT') do + @interrupted = true + end end def finalize @@ -259,8 +224,6 @@ def reset_variables(prompt = '', encoding:) @encoding = encoding @is_multiline = false @finished = false - @cleared = false - @rerender_all = false @history_pointer = nil @kill_ring ||= Reline::KillRing.new @vi_clipboard = '' @@ -268,47 +231,34 @@ def reset_variables(prompt = '', encoding:) @waiting_proc = nil @waiting_operator_proc = nil @waiting_operator_vi_arg = nil - @completion_journey_data = nil + @completion_journey_state = nil @completion_state = CompletionState::NORMAL @perfect_matched = nil @menu_info = nil - @first_prompt = true @searching_prompt = nil @first_char = true - @add_newline_to_end_of_buffer = false - @just_cursor_moving = nil - @cached_prompt_list = nil - @prompt_cache_time = nil + @just_cursor_moving = false @eof = false @continuous_insertion_buffer = String.new(encoding: @encoding) - @scroll_partial_screen = nil - @prev_mode_string = nil + @scroll_partial_screen = 0 @drop_terminate_spaces = false @in_pasting = false @auto_indent_proc = nil @dialogs = [] - @previous_rendered_dialog_y = 0 - @last_key = nil + @interrupted = false @resized = false + @cache = {} + @rendered_screen = RenderedScreen.new(base_y: 0, lines: [], cursor_y: 0) reset_line end def reset_line - @cursor = 0 - @cursor_max = 0 @byte_pointer = 0 @buffer_of_lines = [String.new(encoding: @encoding)] @line_index = 0 - @previous_line_index = nil - @line = @buffer_of_lines[0] - @first_line_started_from = 0 - @move_up = 0 - @started_from = 0 - @highest_in_this = 1 - @highest_in_all = 1 + @cache.clear @line_backup_in_history = nil @multibyte_buffer = String.new(encoding: 'ASCII-8BIT') - @check_new_auto_indent = false end def multiline_on @@ -319,68 +269,44 @@ def multiline_off @is_multiline = false end - private def calculate_height_by_lines(lines, prompt) - result = 0 - prompt_list = prompt.is_a?(Array) ? prompt : nil - lines.each_with_index { |line, i| - prompt = prompt_list[i] if prompt_list and prompt_list[i] - result += calculate_height_by_width(calculate_width(prompt, true) + calculate_width(line)) - } - result - end - private def insert_new_line(cursor_line, next_line) - @line = cursor_line @buffer_of_lines.insert(@line_index + 1, String.new(next_line, encoding: @encoding)) - @previous_line_index = @line_index + @buffer_of_lines[@line_index] = cursor_line @line_index += 1 - @just_cursor_moving = false - end - - private def calculate_height_by_width(width) - width.div(@screen_size.last) + 1 + @byte_pointer = 0 + if @auto_indent_proc && !@in_pasting + if next_line.empty? + ( + # For compatibility, use this calculation instead of just `process_auto_indent @line_index - 1, cursor_dependent: false` + indent1 = @auto_indent_proc.(@buffer_of_lines.take(@line_index - 1).push(''), @line_index - 1, 0, true) + indent2 = @auto_indent_proc.(@buffer_of_lines.take(@line_index), @line_index - 1, @buffer_of_lines[@line_index - 1].bytesize, false) + indent = indent2 || indent1 + @buffer_of_lines[@line_index - 1] = ' ' * indent + @buffer_of_lines[@line_index - 1].gsub(/\A */, '') + ) + process_auto_indent @line_index, add_newline: true + else + process_auto_indent @line_index - 1, cursor_dependent: false + process_auto_indent @line_index, add_newline: true # Need for compatibility + process_auto_indent @line_index, cursor_dependent: false + end + end end private def split_by_width(str, max_width) Reline::Unicode.split_by_width(str, max_width, @encoding) end - private def scroll_down(val) - if val <= @rest_height - Reline::IOGate.move_cursor_down(val) - @rest_height -= val - else - Reline::IOGate.move_cursor_down(@rest_height) - Reline::IOGate.scroll_down(val - @rest_height) - @rest_height = 0 - end - end - - private def move_cursor_up(val) - if val > 0 - Reline::IOGate.move_cursor_up(val) - @rest_height += val - elsif val < 0 - move_cursor_down(-val) - end + def current_byte_pointer_cursor + calculate_width(current_line.byteslice(0, @byte_pointer)) end - private def move_cursor_down(val) - if val > 0 - Reline::IOGate.move_cursor_down(val) - @rest_height -= val - @rest_height = 0 if @rest_height < 0 - elsif val < 0 - move_cursor_up(-val) - end - end - - private def calculate_nearest_cursor(line_to_calc = @line, cursor = @cursor, started_from = @started_from, byte_pointer = @byte_pointer, update = true) + private def calculate_nearest_cursor(cursor) + line_to_calc = current_line new_cursor_max = calculate_width(line_to_calc) new_cursor = 0 new_byte_pointer = 0 height = 1 - max_width = @screen_size.last + max_width = screen_width if @config.editing_mode_is?(:vi_command) last_byte_size = Reline::Unicode.get_prev_mbchar_size(line_to_calc, line_to_calc.bytesize) if last_byte_size > 0 @@ -406,113 +332,234 @@ def multiline_off end new_byte_pointer += gc.bytesize end - new_started_from = height - 1 - if update - @cursor = new_cursor - @cursor_max = new_cursor_max - @started_from = new_started_from - @byte_pointer = new_byte_pointer - else - [new_cursor, new_cursor_max, new_started_from, new_byte_pointer] + @byte_pointer = new_byte_pointer + end + + def with_cache(key, *deps) + cached_deps, value = @cache[key] + if cached_deps != deps + @cache[key] = [deps, value = yield(*deps, cached_deps, value)] end + value end - def rerender_all - @rerender_all = true - process_insert(force: true) - rerender + def modified_lines + with_cache(__method__, whole_lines, finished?) do |whole, complete| + modify_lines(whole, complete) + end end - def rerender - return if @line.nil? - if @menu_info - scroll_down(@highest_in_all - @first_line_started_from) - @rerender_all = true + def prompt_list + with_cache(__method__, whole_lines, check_mode_string, @vi_arg, @searching_prompt) do |lines, mode_string| + check_multiline_prompt(lines, mode_string) end - if @menu_info - show_menu - @menu_info = nil - end - prompt, prompt_width, prompt_list = check_multiline_prompt(whole_lines) - cursor_column = (prompt_width + @cursor) % @screen_size.last - if @cleared - clear_screen_buffer(prompt, prompt_list, prompt_width) - @cleared = false - return + end + + def screen_height + @screen_size.first + end + + def screen_width + @screen_size.last + end + + def screen_scroll_top + @scroll_partial_screen + end + + def wrapped_lines + with_cache(__method__, @buffer_of_lines.size, modified_lines, prompt_list, screen_width) do |n, lines, prompts, width, prev_cache_key, cached_value| + prev_n, prev_lines, prev_prompts, prev_width = prev_cache_key + cached_wraps = {} + if prev_width == width + prev_n.times do |i| + cached_wraps[[prev_prompts[i], prev_lines[i]]] = cached_value[i] + end + end + + n.times.map do |i| + prompt = prompts[i] + line = lines[i] + cached_wraps[[prompt, line]] || split_by_width("#{prompt}#{line}", width).first.compact + end end - if @is_multiline and finished? and @scroll_partial_screen - # Re-output all code higher than the screen when finished. - Reline::IOGate.move_cursor_up(@first_line_started_from + @started_from - @scroll_partial_screen) - Reline::IOGate.move_cursor_column(0) - @scroll_partial_screen = nil - new_lines = whole_lines - prompt, prompt_width, prompt_list = check_multiline_prompt(new_lines) - modify_lines(new_lines).each_with_index do |line, index| - @output.write "#{prompt_list ? prompt_list[index] : prompt}#{line}\r\n" - Reline::IOGate.erase_after_cursor - end - @output.flush - clear_dialog(cursor_column) - return + end + + def calculate_overlay_levels(overlay_levels) + levels = [] + overlay_levels.each do |x, w, l| + levels.fill(l, x, w) end - new_highest_in_this = calculate_height_by_width(prompt_width + calculate_width(@line.nil? ? '' : @line)) - rendered = false - if @add_newline_to_end_of_buffer - clear_dialog_with_trap_key(cursor_column) - rerender_added_newline(prompt, prompt_width, prompt_list) - @add_newline_to_end_of_buffer = false - else - if @just_cursor_moving and not @rerender_all - clear_dialog_with_trap_key(cursor_column) - rendered = just_move_cursor - @just_cursor_moving = false - return - elsif @previous_line_index or new_highest_in_this != @highest_in_this - clear_dialog_with_trap_key(cursor_column) - rerender_changed_current_line - @previous_line_index = nil - rendered = true - elsif @rerender_all - rerender_all_lines - @rerender_all = false - rendered = true + levels + end + + def render_line_differential(old_items, new_items) + old_levels = calculate_overlay_levels(old_items.zip(new_items).each_with_index.map {|((x, w, c), (nx, _nw, nc)), i| [x, w, c == nc && x == nx ? i : -1] if x }.compact) + new_levels = calculate_overlay_levels(new_items.each_with_index.map { |(x, w), i| [x, w, i] if x }.compact).take(screen_width) + base_x = 0 + new_levels.zip(old_levels).chunk { |n, o| n == o ? :skip : n || :blank }.each do |level, chunk| + width = chunk.size + if level == :skip + # do nothing + elsif level == :blank + Reline::IOGate.move_cursor_column base_x + @output.write "#{Reline::IOGate::RESET_COLOR}#{' ' * width}" else + x, w, content = new_items[level] + content = Reline::Unicode.take_range(content, base_x - x, width) unless x == base_x && w == width + Reline::IOGate.move_cursor_column base_x + @output.write "#{Reline::IOGate::RESET_COLOR}#{content}#{Reline::IOGate::RESET_COLOR}" end + base_x += width end - if @is_multiline - if finished? - # Always rerender on finish because output_modifier_proc may return a different output. - new_lines = whole_lines - line = modify_lines(new_lines)[@line_index] - clear_dialog(cursor_column) - prompt, prompt_width, prompt_list = check_multiline_prompt(new_lines) - render_partial(prompt, prompt_width, line, @first_line_started_from) - move_cursor_down(@highest_in_all - (@first_line_started_from + @highest_in_this - 1) - 1) - scroll_down(1) - Reline::IOGate.move_cursor_column(0) - Reline::IOGate.erase_after_cursor - else - if not rendered and not @in_pasting - line = modify_lines(whole_lines)[@line_index] - prompt, prompt_width, prompt_list = check_multiline_prompt(whole_lines) - render_partial(prompt, prompt_width, line, @first_line_started_from) - end - render_dialog(cursor_column) + if old_levels.size > new_levels.size + Reline::IOGate.move_cursor_column new_levels.size + Reline::IOGate.erase_after_cursor + end + end + + # Calculate cursor position in word wrapped content. + def wrapped_cursor_position + prompt_width = calculate_width(prompt_list[@line_index], true) + line_before_cursor = whole_lines[@line_index].byteslice(0, @byte_pointer) + wrapped_line_before_cursor = split_by_width(' ' * prompt_width + line_before_cursor, screen_width).first.compact + wrapped_cursor_y = wrapped_lines[0...@line_index].sum(&:size) + wrapped_line_before_cursor.size - 1 + wrapped_cursor_x = calculate_width(wrapped_line_before_cursor.last) + [wrapped_cursor_x, wrapped_cursor_y] + end + + def clear_dialogs + @dialogs.each do |dialog| + dialog.contents = nil + dialog.trap_key = nil + end + end + + def update_dialogs(key = nil) + wrapped_cursor_x, wrapped_cursor_y = wrapped_cursor_position + @dialogs.each do |dialog| + dialog.trap_key = nil + update_each_dialog(dialog, wrapped_cursor_x, wrapped_cursor_y - screen_scroll_top, key) + end + end + + def render_finished + clear_rendered_lines + render_full_content + end + + def clear_rendered_lines + Reline::IOGate.move_cursor_up @rendered_screen.cursor_y + Reline::IOGate.move_cursor_column 0 + + num_lines = @rendered_screen.lines.size + return unless num_lines && num_lines >= 1 + + Reline::IOGate.move_cursor_down num_lines - 1 + (num_lines - 1).times do + Reline::IOGate.erase_after_cursor + Reline::IOGate.move_cursor_up 1 + end + Reline::IOGate.erase_after_cursor + @rendered_screen.lines = [] + @rendered_screen.cursor_y = 0 + end + + def render_full_content + lines = @buffer_of_lines.size.times.map do |i| + line = prompt_list[i] + modified_lines[i] + wrapped_lines, = split_by_width(line, screen_width) + wrapped_lines.last.empty? ? "#{line} " : line + end + @output.puts lines.map { |l| "#{l}\r\n" }.join + end + + def print_nomultiline_prompt(prompt) + return unless prompt && !@is_multiline + + # Readline's test `TestRelineAsReadline#test_readline` requires first output to be prompt, not cursor reset escape sequence. + @rendered_screen.lines = [[[0, Reline::Unicode.calculate_width(prompt, true), prompt]]] + @rendered_screen.cursor_y = 0 + @output.write prompt + end + + def render_differential + wrapped_cursor_x, wrapped_cursor_y = wrapped_cursor_position + + rendered_lines = @rendered_screen.lines + new_lines = wrapped_lines.flatten[screen_scroll_top, screen_height].map do |l| + [[0, Reline::Unicode.calculate_width(l, true), l]] + end + if @menu_info + @menu_info.lines(screen_width).each do |item| + new_lines << [[0, Reline::Unicode.calculate_width(item), item]] end - @buffer_of_lines[@line_index] = @line - @rest_height = 0 if @scroll_partial_screen - else - line = modify_lines(whole_lines)[@line_index] - render_partial(prompt, prompt_width, line, 0) - if finished? - scroll_down(1) - Reline::IOGate.move_cursor_column(0) - Reline::IOGate.erase_after_cursor + @menu_info = nil # TODO: do not change state here + end + + @dialogs.each_with_index do |dialog, index| + next unless dialog.contents + + x_range, y_range = dialog_range dialog, wrapped_cursor_y - screen_scroll_top + y_range.each do |row| + next if row < 0 || row >= screen_height + dialog_rows = new_lines[row] ||= [] + dialog_rows[index + 1] = [x_range.begin, dialog.width, dialog.contents[row - y_range.begin]] end end + + cursor_y = @rendered_screen.cursor_y + if new_lines != rendered_lines + # Hide cursor while rendering to avoid cursor flickering. + Reline::IOGate.hide_cursor + num_lines = [[new_lines.size, rendered_lines.size].max, screen_height].min + if @rendered_screen.base_y + num_lines > screen_height + Reline::IOGate.scroll_down(num_lines - cursor_y - 1) + @rendered_screen.base_y = screen_height - num_lines + cursor_y = num_lines - 1 + end + num_lines.times do |i| + rendered_line = rendered_lines[i] || [] + line_to_render = new_lines[i] || [] + next if rendered_line == line_to_render + + Reline::IOGate.move_cursor_down i - cursor_y + cursor_y = i + unless rendered_lines[i] + Reline::IOGate.move_cursor_column 0 + Reline::IOGate.erase_after_cursor + end + render_line_differential(rendered_line, line_to_render) + end + @rendered_screen.lines = new_lines + Reline::IOGate.show_cursor + end + y = wrapped_cursor_y - screen_scroll_top + Reline::IOGate.move_cursor_column wrapped_cursor_x + Reline::IOGate.move_cursor_down y - cursor_y + @rendered_screen.cursor_y = y + new_lines.size - y + end + + def current_row + wrapped_lines.flatten[wrapped_cursor_y] + end + + def upper_space_height(wrapped_cursor_y) + wrapped_cursor_y - screen_scroll_top + end + + def rest_height(wrapped_cursor_y) + screen_height - wrapped_cursor_y + screen_scroll_top - @rendered_screen.base_y - 1 + end + + def rerender + render_differential unless @in_pasting end class DialogProcScope + CompletionJourneyData = Struct.new(:preposing, :postposing, :list, :pointer) + def initialize(line_editor, config, proc_to_exec, context) @line_editor = line_editor @config = config @@ -563,21 +610,20 @@ def just_cursor_moving end def screen_width - @line_editor.instance_variable_get(:@screen_size).last + @line_editor.screen_width end def screen_height - @line_editor.instance_variable_get(:@screen_size).first + @line_editor.screen_height end def preferred_dialog_height - rest_height = @line_editor.instance_variable_get(:@rest_height) - scroll_partial_screen = @line_editor.instance_variable_get(:@scroll_partial_screen) || 0 - [cursor_pos.y - scroll_partial_screen, rest_height, (screen_height + 6) / 5].max + _wrapped_cursor_x, wrapped_cursor_y = @line_editor.wrapped_cursor_position + [@line_editor.upper_space_height(wrapped_cursor_y), @line_editor.rest_height(wrapped_cursor_y), (screen_height + 6) / 5].max end def completion_journey_data - @line_editor.instance_variable_get(:@completion_journey_data) + @line_editor.dialog_proc_scope_completion_journey_data end def config @@ -646,14 +692,6 @@ def add_dialog_proc(name, p, context = nil) end DIALOG_DEFAULT_HEIGHT = 20 - private def render_dialog(cursor_column) - changes = @dialogs.map do |dialog| - old_dialog = dialog.dup - update_each_dialog(dialog, cursor_column) - [old_dialog, dialog] - end - render_dialog_changes(changes, cursor_column) - end private def padding_space_with_escape_sequences(str, width) padding_width = width - calculate_width(str, true) @@ -662,118 +700,15 @@ def add_dialog_proc(name, p, context = nil) str + (' ' * padding_width) end - private def range_subtract(base_ranges, subtract_ranges) - indices = base_ranges.flat_map(&:to_a).uniq.sort - subtract_ranges.flat_map(&:to_a) - chunks = indices.chunk_while { |a, b| a + 1 == b } - chunks.map { |a| a.first...a.last + 1 } - end - private def dialog_range(dialog, dialog_y) x_range = dialog.column...dialog.column + dialog.width y_range = dialog_y + dialog.vertical_offset...dialog_y + dialog.vertical_offset + dialog.contents.size [x_range, y_range] end - private def render_dialog_changes(changes, cursor_column) - # Collect x-coordinate range and content of previous and current dialogs for each line - old_dialog_ranges = {} - new_dialog_ranges = {} - new_dialog_contents = {} - changes.each do |old_dialog, new_dialog| - if old_dialog.contents - x_range, y_range = dialog_range(old_dialog, @previous_rendered_dialog_y) - y_range.each do |y| - (old_dialog_ranges[y] ||= []) << x_range - end - end - if new_dialog.contents - x_range, y_range = dialog_range(new_dialog, @first_line_started_from + @started_from) - y_range.each do |y| - (new_dialog_ranges[y] ||= []) << x_range - (new_dialog_contents[y] ||= []) << [x_range, new_dialog.contents[y - y_range.begin]] - end - end - end - return if old_dialog_ranges.empty? && new_dialog_ranges.empty? - - # Calculate x-coordinate ranges to restore text that was hidden behind dialogs for each line - ranges_to_restore = {} - subtract_cache = {} - old_dialog_ranges.each do |y, old_x_ranges| - new_x_ranges = new_dialog_ranges[y] || [] - ranges = subtract_cache[[old_x_ranges, new_x_ranges]] ||= range_subtract(old_x_ranges, new_x_ranges) - ranges_to_restore[y] = ranges if ranges.any? - end - - # Create visual_lines for restoring text hidden behind dialogs - if ranges_to_restore.any? - lines = whole_lines - prompt, _prompt_width, prompt_list = check_multiline_prompt(lines, force_recalc: true) - modified_lines = modify_lines(lines, force_recalc: true) - visual_lines = [] - modified_lines.each_with_index { |l, i| - pr = prompt_list ? prompt_list[i] : prompt - vl, = split_by_width(pr + l, @screen_size.last) - vl.compact! - visual_lines.concat(vl) - } - end - - # Clear and rerender all dialogs line by line - Reline::IOGate.hide_cursor - ymin, ymax = (ranges_to_restore.keys + new_dialog_ranges.keys).minmax - scroll_partial_screen = @scroll_partial_screen || 0 - screen_y_range = scroll_partial_screen..(scroll_partial_screen + @screen_height - 1) - ymin = ymin.clamp(screen_y_range.begin, screen_y_range.end) - ymax = ymax.clamp(screen_y_range.begin, screen_y_range.end) - dialog_y = @first_line_started_from + @started_from - cursor_y = dialog_y - if @highest_in_all <= ymax - scroll_down(ymax - cursor_y) - move_cursor_up(ymax - cursor_y) - end - (ymin..ymax).each do |y| - move_cursor_down(y - cursor_y) - cursor_y = y - new_x_ranges = new_dialog_ranges[y] - restore_ranges = ranges_to_restore[y] - # Restore text that was hidden behind dialogs - if restore_ranges - line = visual_lines[y] || '' - restore_ranges.each do |range| - col = range.begin - width = range.end - range.begin - s = padding_space_with_escape_sequences(Reline::Unicode.take_range(line, col, width), width) - Reline::IOGate.move_cursor_column(col) - @output.write "\e[0m#{s}\e[0m" - end - max_column = [calculate_width(line, true), new_x_ranges&.map(&:end)&.max || 0].max - if max_column < restore_ranges.map(&:end).max - Reline::IOGate.move_cursor_column(max_column) - Reline::IOGate.erase_after_cursor - end - end - # Render dialog contents - new_dialog_contents[y]&.each do |x_range, content| - Reline::IOGate.move_cursor_column(x_range.begin) - @output.write "\e[0m#{content}\e[0m" - end - end - move_cursor_up(cursor_y - dialog_y) - Reline::IOGate.move_cursor_column(cursor_column) - Reline::IOGate.show_cursor - - @previous_rendered_dialog_y = dialog_y - end - - private def update_each_dialog(dialog, cursor_column) - if @in_pasting - dialog.contents = nil - dialog.trap_key = nil - return - end - dialog.set_cursor_pos(cursor_column, @first_line_started_from + @started_from) - dialog_render_info = dialog.call(@last_key) + private def update_each_dialog(dialog, cursor_column, cursor_row, key = nil) + dialog.set_cursor_pos(cursor_column, cursor_row) + dialog_render_info = dialog.call(key) if dialog_render_info.nil? or dialog_render_info.contents.nil? or dialog_render_info.contents.empty? dialog.contents = nil dialog.trap_key = nil @@ -813,23 +748,22 @@ def add_dialog_proc(name, p, context = nil) else scrollbar_pos = nil end - upper_space = @first_line_started_from - @started_from dialog.column = dialog_render_info.pos.x dialog.width += @block_elem_width if scrollbar_pos - diff = (dialog.column + dialog.width) - (@screen_size.last) + diff = (dialog.column + dialog.width) - screen_width if diff > 0 dialog.column -= diff end - if (@rest_height - dialog_render_info.pos.y) >= height + if rest_height(screen_scroll_top + cursor_row) - dialog_render_info.pos.y >= height dialog.vertical_offset = dialog_render_info.pos.y + 1 - elsif upper_space >= height + elsif cursor_row >= height dialog.vertical_offset = dialog_render_info.pos.y - height else dialog.vertical_offset = dialog_render_info.pos.y + 1 end if dialog.column < 0 dialog.column = 0 - dialog.width = @screen_size.last + dialog.width = screen_width end face = Reline::Face[dialog_render_info.face || :default] scrollbar_sgr = face[:scrollbar] @@ -856,379 +790,20 @@ def add_dialog_proc(name, p, context = nil) end end - private def clear_dialog(cursor_column) - changes = @dialogs.map do |dialog| - old_dialog = dialog.dup - dialog.contents = nil - [old_dialog, dialog] - end - render_dialog_changes(changes, cursor_column) - end - - private def clear_dialog_with_trap_key(cursor_column) - clear_dialog(cursor_column) - @dialogs.each do |dialog| - dialog.trap_key = nil - end - end - - private def calculate_scroll_partial_screen(highest_in_all, cursor_y) - if @screen_height < highest_in_all - old_scroll_partial_screen = @scroll_partial_screen - if cursor_y == 0 - @scroll_partial_screen = 0 - elsif cursor_y == (highest_in_all - 1) - @scroll_partial_screen = highest_in_all - @screen_height - else - if @scroll_partial_screen - if cursor_y <= @scroll_partial_screen - @scroll_partial_screen = cursor_y - elsif (@scroll_partial_screen + @screen_height - 1) < cursor_y - @scroll_partial_screen = cursor_y - (@screen_height - 1) - end - else - if cursor_y > (@screen_height - 1) - @scroll_partial_screen = cursor_y - (@screen_height - 1) - else - @scroll_partial_screen = 0 - end - end - end - if @scroll_partial_screen != old_scroll_partial_screen - @rerender_all = true - end - else - if @scroll_partial_screen - @rerender_all = true - end - @scroll_partial_screen = nil - end - end - - private def rerender_added_newline(prompt, prompt_width, prompt_list) - @buffer_of_lines[@previous_line_index] = @line - @line = @buffer_of_lines[@line_index] - @previous_line_index = nil - if @in_pasting - scroll_down(1) - else - lines = whole_lines - prev_line_prompt = @prompt_proc ? prompt_list[@line_index - 1] : prompt - prev_line_prompt_width = @prompt_proc ? calculate_width(prev_line_prompt, true) : prompt_width - prev_line = modify_lines(lines)[@line_index - 1] - move_cursor_up(@started_from) - render_partial(prev_line_prompt, prev_line_prompt_width, prev_line, @first_line_started_from + @started_from, with_control: false) - scroll_down(1) - render_partial(prompt, prompt_width, @line, @first_line_started_from + @started_from + 1, with_control: false) - end - @cursor = @cursor_max = calculate_width(@line) - @byte_pointer = @line.bytesize - @highest_in_all += @highest_in_this - @highest_in_this = calculate_height_by_width(prompt_width + @cursor_max) - @first_line_started_from += @started_from + 1 - @started_from = calculate_height_by_width(prompt_width + @cursor) - 1 - end - - def just_move_cursor - prompt, prompt_width, prompt_list = check_multiline_prompt(@buffer_of_lines) - move_cursor_up(@started_from) - new_first_line_started_from = - if @line_index.zero? - 0 - else - calculate_height_by_lines(@buffer_of_lines[0..(@line_index - 1)], prompt_list || prompt) - end - first_line_diff = new_first_line_started_from - @first_line_started_from - @cursor, @cursor_max, _, @byte_pointer = calculate_nearest_cursor(@buffer_of_lines[@line_index], @cursor, @started_from, @byte_pointer, false) - new_started_from = calculate_height_by_width(prompt_width + @cursor) - 1 - calculate_scroll_partial_screen(@highest_in_all, new_first_line_started_from + new_started_from) - @previous_line_index = nil - @line = @buffer_of_lines[@line_index] - if @rerender_all - rerender_all_lines - @rerender_all = false - true - else - @first_line_started_from = new_first_line_started_from - @started_from = new_started_from - move_cursor_down(first_line_diff + @started_from) - Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last) - false - end - end - - private def rerender_changed_current_line - new_lines = whole_lines - prompt, prompt_width, prompt_list = check_multiline_prompt(new_lines) - all_height = calculate_height_by_lines(new_lines, prompt_list || prompt) - diff = all_height - @highest_in_all - move_cursor_down(@highest_in_all - @first_line_started_from - @started_from - 1) - if diff > 0 - scroll_down(diff) - move_cursor_up(all_height - 1) - elsif diff < 0 - (-diff).times do - Reline::IOGate.move_cursor_column(0) - Reline::IOGate.erase_after_cursor - move_cursor_up(1) - end - move_cursor_up(all_height - 1) - else - move_cursor_up(all_height - 1) - end - @highest_in_all = all_height - back = render_whole_lines(new_lines, prompt_list || prompt, prompt_width) - move_cursor_up(back) - if @previous_line_index - @buffer_of_lines[@previous_line_index] = @line - @line = @buffer_of_lines[@line_index] - end - @first_line_started_from = - if @line_index.zero? - 0 - else - calculate_height_by_lines(@buffer_of_lines[0..(@line_index - 1)], prompt_list || prompt) - end - if @prompt_proc - prompt = prompt_list[@line_index] - prompt_width = calculate_width(prompt, true) - end - move_cursor_down(@first_line_started_from) - calculate_nearest_cursor - @started_from = calculate_height_by_width(prompt_width + @cursor) - 1 - move_cursor_down(@started_from) - Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last) - @highest_in_this = calculate_height_by_width(prompt_width + @cursor_max) - end - - private def rerender_all_lines - move_cursor_up(@first_line_started_from + @started_from) - Reline::IOGate.move_cursor_column(0) - back = 0 - new_buffer = whole_lines - prompt, prompt_width, prompt_list = check_multiline_prompt(new_buffer) - new_buffer.each_with_index do |line, index| - prompt_width = calculate_width(prompt_list[index], true) if @prompt_proc - width = prompt_width + calculate_width(line) - height = calculate_height_by_width(width) - back += height - end - old_highest_in_all = @highest_in_all - if @line_index.zero? - new_first_line_started_from = 0 - else - new_first_line_started_from = calculate_height_by_lines(new_buffer[0..(@line_index - 1)], prompt_list || prompt) - end - new_started_from = calculate_height_by_width(prompt_width + @cursor) - 1 - calculate_scroll_partial_screen(back, new_first_line_started_from + new_started_from) - if @scroll_partial_screen - move_cursor_up(@first_line_started_from + @started_from) - scroll_down(@screen_height - 1) - move_cursor_up(@screen_height) - Reline::IOGate.move_cursor_column(0) - elsif back > old_highest_in_all - scroll_down(back - 1) - move_cursor_up(back - 1) - elsif back < old_highest_in_all - scroll_down(back) - Reline::IOGate.erase_after_cursor - (old_highest_in_all - back - 1).times do - scroll_down(1) - Reline::IOGate.erase_after_cursor - end - move_cursor_up(old_highest_in_all - 1) - end - render_whole_lines(new_buffer, prompt_list || prompt, prompt_width) - if @prompt_proc - prompt = prompt_list[@line_index] - prompt_width = calculate_width(prompt, true) - end - @highest_in_this = calculate_height_by_width(prompt_width + @cursor_max) - @highest_in_all = back - @first_line_started_from = new_first_line_started_from - @started_from = new_started_from - if @scroll_partial_screen - Reline::IOGate.move_cursor_up(@screen_height - (@first_line_started_from + @started_from - @scroll_partial_screen) - 1) - Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last) - else - move_cursor_down(@first_line_started_from + @started_from - back + 1) - Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last) - end - end - - private def render_whole_lines(lines, prompt, prompt_width) - rendered_height = 0 - modify_lines(lines).each_with_index do |line, index| - if prompt.is_a?(Array) - line_prompt = prompt[index] - prompt_width = calculate_width(line_prompt, true) - else - line_prompt = prompt - end - height = render_partial(line_prompt, prompt_width, line, rendered_height, with_control: false) - if index < (lines.size - 1) - if @scroll_partial_screen - if (@scroll_partial_screen - height) < rendered_height and (@scroll_partial_screen + @screen_height - 1) >= (rendered_height + height) - move_cursor_down(1) - end - else - scroll_down(1) - end - rendered_height += height - else - rendered_height += height - 1 - end - end - rendered_height - end - - private def render_partial(prompt, prompt_width, line_to_render, this_started_from, with_control: true) - visual_lines, height = split_by_width(line_to_render.nil? ? prompt : prompt + line_to_render, @screen_size.last) - cursor_up_from_last_line = 0 - if @scroll_partial_screen - last_visual_line = this_started_from + (height - 1) - last_screen_line = @scroll_partial_screen + (@screen_height - 1) - if (@scroll_partial_screen - this_started_from) >= height - # Render nothing because this line is before the screen. - visual_lines = [] - elsif this_started_from > last_screen_line - # Render nothing because this line is after the screen. - visual_lines = [] - else - deleted_lines_before_screen = [] - if @scroll_partial_screen > this_started_from and last_visual_line >= @scroll_partial_screen - # A part of visual lines are before the screen. - deleted_lines_before_screen = visual_lines.shift((@scroll_partial_screen - this_started_from) * 2) - deleted_lines_before_screen.compact! - end - if this_started_from <= last_screen_line and last_screen_line < last_visual_line - # A part of visual lines are after the screen. - visual_lines.pop((last_visual_line - last_screen_line) * 2) - end - move_cursor_up(deleted_lines_before_screen.size - @started_from) - cursor_up_from_last_line = @started_from - deleted_lines_before_screen.size - end - end - if with_control - if height > @highest_in_this - diff = height - @highest_in_this - scroll_down(diff) - @highest_in_all += diff - @highest_in_this = height - move_cursor_up(diff) - elsif height < @highest_in_this - diff = @highest_in_this - height - @highest_in_all -= diff - @highest_in_this = height - end - move_cursor_up(@started_from) - @started_from = calculate_height_by_width(prompt_width + @cursor) - 1 - cursor_up_from_last_line = height - 1 - @started_from - end - if Reline::Unicode::CSI_REGEXP.match?(prompt + line_to_render) - @output.write "\e[0m" # clear character decorations - end - visual_lines.each_with_index do |line, index| - Reline::IOGate.move_cursor_column(0) - if line.nil? - if calculate_width(visual_lines[index - 1], true) == Reline::IOGate.get_screen_size.last - # reaches the end of line - if Reline::IOGate.win? and Reline::IOGate.win_legacy_console? - # A newline is automatically inserted if a character is rendered at - # eol on command prompt. - else - # When the cursor is at the end of the line and erases characters - # after the cursor, some terminals delete the character at the - # cursor position. - move_cursor_down(1) - Reline::IOGate.move_cursor_column(0) - end - else - Reline::IOGate.erase_after_cursor - move_cursor_down(1) - Reline::IOGate.move_cursor_column(0) - end - next - end - @output.write line - if Reline::IOGate.win? and Reline::IOGate.win_legacy_console? and calculate_width(line, true) == Reline::IOGate.get_screen_size.last - # A newline is automatically inserted if a character is rendered at eol on command prompt. - @rest_height -= 1 if @rest_height > 0 - end - @output.flush - if @first_prompt - @first_prompt = false - @pre_input_hook&.call - end - end - unless visual_lines.empty? - Reline::IOGate.erase_after_cursor - Reline::IOGate.move_cursor_column(0) - end - if with_control - # Just after rendring, so the cursor is on the last line. - if finished? - Reline::IOGate.move_cursor_column(0) - else - # Moves up from bottom of lines to the cursor position. - move_cursor_up(cursor_up_from_last_line) - # This logic is buggy if a fullwidth char is wrapped because there is only one halfwidth at end of a line. - Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last) - end - end - height - end - - private def modify_lines(before, force_recalc: false) - return before if !force_recalc && (before.nil? || before.empty? || simplified_rendering?) - - if after = @output_modifier_proc&.call("#{before.join("\n")}\n", complete: finished?) + private def modify_lines(before, complete) + if after = @output_modifier_proc&.call("#{before.join("\n")}\n", complete: complete) after.lines("\n").map { |l| l.chomp('') } else - before - end - end - - private def show_menu - scroll_down(@highest_in_all - @first_line_started_from) - @rerender_all = true - @menu_info.list.sort!.each do |item| - Reline::IOGate.move_cursor_column(0) - @output.write item - @output.flush - scroll_down(1) - end - scroll_down(@highest_in_all - 1) - move_cursor_up(@highest_in_all - 1 - @first_line_started_from) - end - - private def clear_screen_buffer(prompt, prompt_list, prompt_width) - Reline::IOGate.clear_screen - back = 0 - modify_lines(whole_lines).each_with_index do |line, index| - if @prompt_proc - pr = prompt_list[index] - height = render_partial(pr, calculate_width(pr), line, back, with_control: false) - else - height = render_partial(prompt, prompt_width, line, back, with_control: false) - end - if index < (@buffer_of_lines.size - 1) - move_cursor_down(1) - back += height - end + before.map { |l| Reline::Unicode.escape_for_print(l) } end - move_cursor_up(back) - move_cursor_down(@first_line_started_from + @started_from) - @rest_height = (Reline::IOGate.get_screen_size.first - 1) - Reline::IOGate.cursor_pos.y - Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last) end def editing_mode @config.editing_mode end - private def menu(target, list) - @menu_info = MenuInfo.new(target, list) + private def menu(_target, list) + @menu_info = MenuInfo.new(list) end private def complete_internal_proc(list, is_menu) @@ -1256,7 +831,7 @@ def editing_mode item_mbchars = item.grapheme_clusters end size = [memo_mbchars.size, item_mbchars.size].min - result = '' + result = +'' size.times do |i| if @config.completion_ignore_case if memo_mbchars[i].casecmp?(item_mbchars[i]) @@ -1277,9 +852,9 @@ def editing_mode [target, preposing, completed, postposing] end - private def complete(list, just_show_list = false) + private def complete(list, just_show_list) case @completion_state - when CompletionState::NORMAL, CompletionState::JOURNEY + when CompletionState::NORMAL @completion_state = CompletionState::COMPLETION when CompletionState::PERFECT_MATCH @dig_perfect_match_proc&.(@perfect_matched) @@ -1312,81 +887,73 @@ def editing_mode @completion_state = CompletionState::MENU end if not just_show_list and target < completed - @line = (preposing + completed + completion_append_character.to_s + postposing).split("\n")[@line_index] || String.new(encoding: @encoding) + @buffer_of_lines[@line_index] = (preposing + completed + completion_append_character.to_s + postposing).split("\n")[@line_index] || String.new(encoding: @encoding) line_to_pointer = (preposing + completed + completion_append_character.to_s).split("\n")[@line_index] || String.new(encoding: @encoding) - @cursor_max = calculate_width(@line) - @cursor = calculate_width(line_to_pointer) @byte_pointer = line_to_pointer.bytesize end end end - private def move_completed_list(list, direction) - case @completion_state - when CompletionState::NORMAL, CompletionState::COMPLETION, - CompletionState::MENU, CompletionState::MENU_WITH_PERFECT_MATCH - @completion_state = CompletionState::JOURNEY - result = retrieve_completion_block - return if result.nil? - preposing, target, postposing = result - @completion_journey_data = CompletionJourneyData.new( - preposing, postposing, - [target] + list.select{ |item| item.start_with?(target) }, 0) - if @completion_journey_data.list.size == 1 - @completion_journey_data.pointer = 0 - else - case direction - when :up - @completion_journey_data.pointer = @completion_journey_data.list.size - 1 - when :down - @completion_journey_data.pointer = 1 - end - end - @completion_state = CompletionState::JOURNEY - else - case direction - when :up - @completion_journey_data.pointer -= 1 - if @completion_journey_data.pointer < 0 - @completion_journey_data.pointer = @completion_journey_data.list.size - 1 - end - when :down - @completion_journey_data.pointer += 1 - if @completion_journey_data.pointer >= @completion_journey_data.list.size - @completion_journey_data.pointer = 0 - end - end + def dialog_proc_scope_completion_journey_data + return nil unless @completion_journey_state + line_index = @completion_journey_state.line_index + pre_lines = @buffer_of_lines[0...line_index].map { |line| line + "\n" } + post_lines = @buffer_of_lines[(line_index + 1)..-1].map { |line| line + "\n" } + DialogProcScope::CompletionJourneyData.new( + pre_lines.join + @completion_journey_state.pre, + @completion_journey_state.post + post_lines.join, + @completion_journey_state.list, + @completion_journey_state.pointer + ) + end + + private def move_completed_list(direction) + @completion_journey_state ||= retrieve_completion_journey_state + return false unless @completion_journey_state + + if (delta = { up: -1, down: +1 }[direction]) + @completion_journey_state.pointer = (@completion_journey_state.pointer + delta) % @completion_journey_state.list.size end - completed = @completion_journey_data.list[@completion_journey_data.pointer] - new_line = (@completion_journey_data.preposing + completed + @completion_journey_data.postposing).split("\n")[@line_index] - @line = new_line.nil? ? String.new(encoding: @encoding) : new_line - line_to_pointer = (@completion_journey_data.preposing + completed).split("\n")[@line_index] - line_to_pointer = String.new(encoding: @encoding) if line_to_pointer.nil? - @cursor_max = calculate_width(@line) - @cursor = calculate_width(line_to_pointer) - @byte_pointer = line_to_pointer.bytesize + completed = @completion_journey_state.list[@completion_journey_state.pointer] + set_current_line(@completion_journey_state.pre + completed + @completion_journey_state.post, @completion_journey_state.pre.bytesize + completed.bytesize) + true + end + + private def retrieve_completion_journey_state + preposing, target, postposing = retrieve_completion_block + list = call_completion_proc + return unless list.is_a?(Array) + + candidates = list.select{ |item| item.start_with?(target) } + return if candidates.empty? + + pre = preposing.split("\n", -1).last || '' + post = postposing.split("\n", -1).first || '' + CompletionJourneyState.new( + @line_index, pre, target, post, [target] + candidates, 0 + ) end private def run_for_operators(key, method_symbol, &block) if @waiting_operator_proc if VI_MOTIONS.include?(method_symbol) - old_cursor, old_byte_pointer = @cursor, @byte_pointer + old_byte_pointer = @byte_pointer @vi_arg = @waiting_operator_vi_arg if @waiting_operator_vi_arg&.> 1 block.(true) unless @waiting_proc - cursor_diff, byte_pointer_diff = @cursor - old_cursor, @byte_pointer - old_byte_pointer - @cursor, @byte_pointer = old_cursor, old_byte_pointer - @waiting_operator_proc.(cursor_diff, byte_pointer_diff) + byte_pointer_diff = @byte_pointer - old_byte_pointer + @byte_pointer = old_byte_pointer + @waiting_operator_proc.(byte_pointer_diff) else old_waiting_proc = @waiting_proc old_waiting_operator_proc = @waiting_operator_proc current_waiting_operator_proc = @waiting_operator_proc @waiting_proc = proc { |k| - old_cursor, old_byte_pointer = @cursor, @byte_pointer + old_byte_pointer = @byte_pointer old_waiting_proc.(k) - cursor_diff, byte_pointer_diff = @cursor - old_cursor, @byte_pointer - old_byte_pointer - @cursor, @byte_pointer = old_cursor, old_byte_pointer - current_waiting_operator_proc.(cursor_diff, byte_pointer_diff) + byte_pointer_diff = @byte_pointer - old_byte_pointer + @byte_pointer = old_byte_pointer + current_waiting_operator_proc.(byte_pointer_diff) @waiting_operator_proc = old_waiting_operator_proc } end @@ -1397,7 +964,6 @@ def editing_mode @waiting_operator_proc = nil @waiting_operator_vi_arg = nil if @vi_arg - @rerender_all = true @vi_arg = nil end else @@ -1451,7 +1017,6 @@ def wrap_method_call(method_symbol, method_obj, key, with_operator = false) end @kill_ring.process if @vi_arg - @rerender_al = true @vi_arg = nil end elsif @vi_arg @@ -1471,7 +1036,6 @@ def wrap_method_call(method_symbol, method_obj, key, with_operator = false) end @kill_ring.process if @vi_arg - @rerender_all = true @vi_arg = nil end end @@ -1493,7 +1057,6 @@ def wrap_method_call(method_symbol, method_obj, key, with_operator = false) end private def normal_char(key) - method_symbol = method_obj = nil if key.combined_char.is_a?(Symbol) process_key(key.combined_char, key.combined_char) return @@ -1523,87 +1086,99 @@ def wrap_method_call(method_symbol, method_obj, key, with_operator = false) end @multibyte_buffer.clear end - if @config.editing_mode_is?(:vi_command) and @cursor > 0 and @cursor == @cursor_max - byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer) + if @config.editing_mode_is?(:vi_command) and @byte_pointer > 0 and @byte_pointer == current_line.bytesize + byte_size = Reline::Unicode.get_prev_mbchar_size(@buffer_of_lines[@line_index], @byte_pointer) @byte_pointer -= byte_size - mbchar = @line.byteslice(@byte_pointer, byte_size) - width = Reline::Unicode.get_mbchar_width(mbchar) - @cursor -= width + end + end + + def update(key) + modified = input_key(key) + unless @in_pasting + scroll_into_view + @just_cursor_moving = !modified + update_dialogs(key) + @just_cursor_moving = false end end def input_key(key) - @last_key = key @config.reset_oneshot_key_bindings @dialogs.each do |dialog| if key.char.instance_of?(Symbol) and key.char == dialog.name return end end - @just_cursor_moving = nil if key.char.nil? if @first_char - @line = nil + @eof = true end finish return end - old_line = @line.dup + old_lines = @buffer_of_lines.dup @first_char = false completion_occurs = false if @config.editing_mode_is?(:emacs, :vi_insert) and key.char == "\C-i".ord - unless @config.disable_completion - result = call_completion_proc - if result.is_a?(Array) - completion_occurs = true - process_insert - if @config.autocompletion - move_completed_list(result, :down) - else - complete(result) + if !@config.disable_completion + process_insert(force: true) + if @config.autocompletion + @completion_state = CompletionState::NORMAL + completion_occurs = move_completed_list(:down) + else + @completion_journey_state = nil + result = call_completion_proc + if result.is_a?(Array) + completion_occurs = true + complete(result, false) end end end elsif @config.editing_mode_is?(:emacs, :vi_insert) and key.char == :completion_journey_up if not @config.disable_completion and @config.autocompletion - result = call_completion_proc - if result.is_a?(Array) - completion_occurs = true - process_insert - move_completed_list(result, :up) - end + process_insert(force: true) + @completion_state = CompletionState::NORMAL + completion_occurs = move_completed_list(:up) end - elsif not @config.disable_completion and @config.editing_mode_is?(:vi_insert) and ["\C-p".ord, "\C-n".ord].include?(key.char) - unless @config.disable_completion - result = call_completion_proc - if result.is_a?(Array) - completion_occurs = true - process_insert - move_completed_list(result, "\C-p".ord == key.char ? :up : :down) - end + elsif @config.editing_mode_is?(:vi_insert) and ["\C-p".ord, "\C-n".ord].include?(key.char) + # In vi mode, move completed list even if autocompletion is off + if not @config.disable_completion + process_insert(force: true) + @completion_state = CompletionState::NORMAL + completion_occurs = move_completed_list("\C-p".ord == key.char ? :up : :down) end elsif Symbol === key.char and respond_to?(key.char, true) process_key(key.char, key.char) else normal_char(key) end + unless completion_occurs @completion_state = CompletionState::NORMAL - @completion_journey_data = nil + @completion_journey_state = nil end - if not @in_pasting and @just_cursor_moving.nil? - if @previous_line_index and @buffer_of_lines[@previous_line_index] == @line - @just_cursor_moving = true - elsif @previous_line_index.nil? and @buffer_of_lines[@line_index] == @line and old_line == @line - @just_cursor_moving = true - else - @just_cursor_moving = false - end - else - @just_cursor_moving = false + + if @in_pasting + clear_dialogs + return + end + + modified = old_lines != @buffer_of_lines + if !completion_occurs && modified && !@config.disable_completion && @config.autocompletion + # Auto complete starts only when edited + process_insert(force: true) + @completion_journey_state = retrieve_completion_journey_state end - if @is_multiline and @auto_indent_proc and not simplified_rendering? and @line - process_auto_indent + modified + end + + def scroll_into_view + _wrapped_cursor_x, wrapped_cursor_y = wrapped_cursor_position + if wrapped_cursor_y < screen_scroll_top + @scroll_partial_screen = wrapped_cursor_y + end + if wrapped_cursor_y >= screen_scroll_top + screen_height + @scroll_partial_screen = wrapped_cursor_y - screen_height + 1 end end @@ -1637,43 +1212,40 @@ def call_completion_proc_with_checking_args(pre, target, post) result end - private def process_auto_indent - return if not @check_new_auto_indent and @previous_line_index # move cursor up or down - if @check_new_auto_indent and @previous_line_index and @previous_line_index > 0 and @line_index > @previous_line_index - # Fix indent of a line when a newline is inserted to the next - new_lines = whole_lines - new_indent = @auto_indent_proc.(new_lines[0..-3].push(''), @line_index - 1, 0, true) - md = @line.match(/\A */) - prev_indent = md[0].count(' ') - @line = ' ' * new_indent + @line.lstrip - - new_indent = nil - result = @auto_indent_proc.(new_lines[0..-2], @line_index - 1, (new_lines[@line_index - 1].bytesize + 1), false) - if result - new_indent = result - end - if new_indent&.>= 0 - @line = ' ' * new_indent + @line.lstrip - end - end - new_lines = whole_lines - new_indent = @auto_indent_proc.(new_lines, @line_index, @byte_pointer, @check_new_auto_indent) - if new_indent&.>= 0 - md = new_lines[@line_index].match(/\A */) - prev_indent = md[0].count(' ') - if @check_new_auto_indent - line = @buffer_of_lines[@line_index] = ' ' * new_indent + @buffer_of_lines[@line_index].lstrip - @cursor = new_indent - @cursor_max = calculate_width(line) - @byte_pointer = new_indent - else - @line = ' ' * new_indent + @line.lstrip - @cursor += new_indent - prev_indent - @cursor_max = calculate_width(@line) - @byte_pointer += new_indent - prev_indent - end + private def process_auto_indent(line_index = @line_index, cursor_dependent: true, add_newline: false) + return if @in_pasting + return unless @auto_indent_proc + + line = @buffer_of_lines[line_index] + byte_pointer = cursor_dependent && @line_index == line_index ? @byte_pointer : line.bytesize + new_indent = @auto_indent_proc.(@buffer_of_lines.take(line_index + 1).push(''), line_index, byte_pointer, add_newline) + return unless new_indent + + new_line = ' ' * new_indent + line.lstrip + @buffer_of_lines[line_index] = new_line + if @line_index == line_index + indent_diff = new_line.bytesize - line.bytesize + @byte_pointer = [@byte_pointer + indent_diff, 0].max end - @check_new_auto_indent = false + end + + def line() + current_line unless eof? + end + + def current_line + @buffer_of_lines[@line_index] + end + + def set_current_line(line, byte_pointer = nil) + cursor = current_byte_pointer_cursor + @buffer_of_lines[@line_index] = line + if byte_pointer + @byte_pointer = byte_pointer + else + calculate_nearest_cursor(cursor) + end + process_auto_indent end def retrieve_completion_block(set_completion_quote_character = false) @@ -1687,7 +1259,7 @@ def retrieve_completion_block(set_completion_quote_character = false) else quote_characters_regexp = /\A[#{Regexp.escape(Reline.completer_quote_characters)}]/ end - before = @line.byteslice(0, @byte_pointer) + before = current_line.byteslice(0, @byte_pointer) rest = nil break_pointer = nil quote = nil @@ -1695,7 +1267,7 @@ def retrieve_completion_block(set_completion_quote_character = false) escaped_quote = nil i = 0 while i < @byte_pointer do - slice = @line.byteslice(i, @byte_pointer - i) + slice = current_line.byteslice(i, @byte_pointer - i) unless slice.valid_encoding? i += 1 next @@ -1717,15 +1289,15 @@ def retrieve_completion_block(set_completion_quote_character = false) elsif word_break_regexp and not quote and slice =~ word_break_regexp rest = $' i += 1 - before = @line.byteslice(i, @byte_pointer - i) + before = current_line.byteslice(i, @byte_pointer - i) break_pointer = i else i += 1 end end - postposing = @line.byteslice(@byte_pointer, @line.bytesize - @byte_pointer) + postposing = current_line.byteslice(@byte_pointer, current_line.bytesize - @byte_pointer) if rest - preposing = @line.byteslice(0, break_pointer) + preposing = current_line.byteslice(0, break_pointer) target = rest if set_completion_quote_character and quote Reline.core.instance_variable_set(:@completion_quote_character, quote) @@ -1736,7 +1308,7 @@ def retrieve_completion_block(set_completion_quote_character = false) else preposing = '' if break_pointer - preposing = @line.byteslice(0, break_pointer) + preposing = current_line.byteslice(0, break_pointer) else preposing = '' end @@ -1756,106 +1328,67 @@ def retrieve_completion_block(set_completion_quote_character = false) def confirm_multiline_termination temp_buffer = @buffer_of_lines.dup - if @previous_line_index and @line_index == (@buffer_of_lines.size - 1) - temp_buffer[@previous_line_index] = @line - else - temp_buffer[@line_index] = @line - end @confirm_multiline_termination_proc.(temp_buffer.join("\n") + "\n") end def insert_text(text) - width = calculate_width(text) - if @cursor == @cursor_max - @line += text + if @buffer_of_lines[@line_index].bytesize == @byte_pointer + @buffer_of_lines[@line_index] += text else - @line = byteinsert(@line, @byte_pointer, text) + @buffer_of_lines[@line_index] = byteinsert(@buffer_of_lines[@line_index], @byte_pointer, text) end @byte_pointer += text.bytesize - @cursor += width - @cursor_max += width + process_auto_indent end def delete_text(start = nil, length = nil) if start.nil? and length.nil? if @is_multiline if @buffer_of_lines.size == 1 - @line&.clear + @buffer_of_lines[@line_index] = '' @byte_pointer = 0 - @cursor = 0 - @cursor_max = 0 elsif @line_index == (@buffer_of_lines.size - 1) and @line_index > 0 @buffer_of_lines.pop @line_index -= 1 - @line = @buffer_of_lines[@line_index] @byte_pointer = 0 - @cursor = 0 - @cursor_max = calculate_width(@line) elsif @line_index < (@buffer_of_lines.size - 1) @buffer_of_lines.delete_at(@line_index) - @line = @buffer_of_lines[@line_index] @byte_pointer = 0 - @cursor = 0 - @cursor_max = calculate_width(@line) end else - @line&.clear - @byte_pointer = 0 - @cursor = 0 - @cursor_max = 0 + set_current_line('', 0) end elsif not start.nil? and not length.nil? - if @line - before = @line.byteslice(0, start) - after = @line.byteslice(start + length, @line.bytesize) - @line = before + after - @byte_pointer = @line.bytesize if @byte_pointer > @line.bytesize - str = @line.byteslice(0, @byte_pointer) - @cursor = calculate_width(str) - @cursor_max = calculate_width(@line) + if current_line + before = current_line.byteslice(0, start) + after = current_line.byteslice(start + length, current_line.bytesize) + set_current_line(before + after) end elsif start.is_a?(Range) range = start first = range.first last = range.last - last = @line.bytesize - 1 if last > @line.bytesize - last += @line.bytesize if last < 0 - first += @line.bytesize if first < 0 + last = current_line.bytesize - 1 if last > current_line.bytesize + last += current_line.bytesize if last < 0 + first += current_line.bytesize if first < 0 range = range.exclude_end? ? first...last : first..last - @line = @line.bytes.reject.with_index{ |c, i| range.include?(i) }.map{ |c| c.chr(Encoding::ASCII_8BIT) }.join.force_encoding(@encoding) - @byte_pointer = @line.bytesize if @byte_pointer > @line.bytesize - str = @line.byteslice(0, @byte_pointer) - @cursor = calculate_width(str) - @cursor_max = calculate_width(@line) + line = current_line.bytes.reject.with_index{ |c, i| range.include?(i) }.map{ |c| c.chr(Encoding::ASCII_8BIT) }.join.force_encoding(@encoding) + set_current_line(line) else - @line = @line.byteslice(0, start) - @byte_pointer = @line.bytesize if @byte_pointer > @line.bytesize - str = @line.byteslice(0, @byte_pointer) - @cursor = calculate_width(str) - @cursor_max = calculate_width(@line) + set_current_line(current_line.byteslice(0, start)) end end def byte_pointer=(val) @byte_pointer = val - str = @line.byteslice(0, @byte_pointer) - @cursor = calculate_width(str) - @cursor_max = calculate_width(@line) end def whole_lines - index = @previous_line_index || @line_index - temp_lines = @buffer_of_lines.dup - temp_lines[index] = @line - temp_lines + @buffer_of_lines.dup end def whole_buffer - if @buffer_of_lines.size == 1 and @line.nil? - nil - else - whole_lines.join("\n") - end + whole_lines.join("\n") end def finished? @@ -1864,7 +1397,6 @@ def finished? def finish @finished = true - @rerender_all = true @config.reset end @@ -1895,14 +1427,9 @@ def finish private def key_newline(key) if @is_multiline - if (@buffer_of_lines.size - 1) == @line_index and @line.bytesize == @byte_pointer - @add_newline_to_end_of_buffer = true - end - next_line = @line.byteslice(@byte_pointer, @line.bytesize - @byte_pointer) - cursor_line = @line.byteslice(0, @byte_pointer) + next_line = current_line.byteslice(@byte_pointer, current_line.bytesize - @byte_pointer) + cursor_line = current_line.byteslice(0, @byte_pointer) insert_new_line(cursor_line, next_line) - @cursor = 0 - @check_new_auto_indent = true unless @in_pasting end end @@ -1912,16 +1439,7 @@ def finish private def process_insert(force: false) return if @continuous_insertion_buffer.empty? or (@in_pasting and not force) - width = Reline::Unicode.calculate_width(@continuous_insertion_buffer) - bytesize = @continuous_insertion_buffer.bytesize - if @cursor == @cursor_max - @line += @continuous_insertion_buffer - else - @line = byteinsert(@line, @byte_pointer, @continuous_insertion_buffer) - end - @byte_pointer += bytesize - @cursor += width - @cursor_max += width + insert_text(@continuous_insertion_buffer) @continuous_insertion_buffer.clear end @@ -1939,9 +1457,6 @@ def finish # million. # GNU Readline:: +self-insert+ (a, b, A, 1, !, …) Insert yourself. private def ed_insert(key) - str = nil - width = nil - bytesize = nil if key.instance_of?(String) begin key.encode(Encoding::UTF_8) @@ -1949,7 +1464,6 @@ def finish return end str = key - bytesize = key.bytesize else begin key.chr.encode(Encoding::UTF_8) @@ -1957,7 +1471,6 @@ def finish return end str = key.chr - bytesize = 1 end if @in_pasting @continuous_insertion_buffer << str @@ -1965,28 +1478,8 @@ def finish elsif not @continuous_insertion_buffer.empty? process_insert end - width = Reline::Unicode.get_mbchar_width(str) - if @cursor == @cursor_max - @line += str - else - @line = byteinsert(@line, @byte_pointer, str) - end - last_byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer) - @byte_pointer += bytesize - last_mbchar = @line.byteslice((@byte_pointer - bytesize - last_byte_size), last_byte_size) - combined_char = last_mbchar + str - if last_byte_size != 0 and combined_char.grapheme_clusters.size == 1 - # combined char - last_mbchar_width = Reline::Unicode.get_mbchar_width(last_mbchar) - combined_char_width = Reline::Unicode.get_mbchar_width(combined_char) - if combined_char_width > last_mbchar_width - width = combined_char_width - last_mbchar_width - else - width = 0 - end - end - @cursor += width - @cursor_max += width + + insert_text(str) end alias_method :ed_digit, :ed_insert alias_method :self_insert, :ed_insert @@ -2008,18 +1501,11 @@ def finish alias_method :quoted_insert, :ed_quoted_insert private def ed_next_char(key, arg: 1) - byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer) - if (@byte_pointer < @line.bytesize) - mbchar = @line.byteslice(@byte_pointer, byte_size) - width = Reline::Unicode.get_mbchar_width(mbchar) - @cursor += width if width + byte_size = Reline::Unicode.get_next_mbchar_size(current_line, @byte_pointer) + if (@byte_pointer < current_line.bytesize) @byte_pointer += byte_size - elsif @is_multiline and @config.editing_mode_is?(:emacs) and @byte_pointer == @line.bytesize and @line_index < @buffer_of_lines.size - 1 - next_line = @buffer_of_lines[@line_index + 1] - @cursor = 0 + elsif @is_multiline and @config.editing_mode_is?(:emacs) and @byte_pointer == current_line.bytesize and @line_index < @buffer_of_lines.size - 1 @byte_pointer = 0 - @cursor_max = calculate_width(next_line) - @previous_line_index = @line_index @line_index += 1 end arg -= 1 @@ -2028,19 +1514,12 @@ def finish alias_method :forward_char, :ed_next_char private def ed_prev_char(key, arg: 1) - if @cursor > 0 - byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer) + if @byte_pointer > 0 + byte_size = Reline::Unicode.get_prev_mbchar_size(current_line, @byte_pointer) @byte_pointer -= byte_size - mbchar = @line.byteslice(@byte_pointer, byte_size) - width = Reline::Unicode.get_mbchar_width(mbchar) - @cursor -= width elsif @is_multiline and @config.editing_mode_is?(:emacs) and @byte_pointer == 0 and @line_index > 0 - prev_line = @buffer_of_lines[@line_index - 1] - @cursor = calculate_width(prev_line) - @byte_pointer = prev_line.bytesize - @cursor_max = calculate_width(prev_line) - @previous_line_index = @line_index @line_index -= 1 + @byte_pointer = current_line.bytesize end arg -= 1 ed_prev_char(key, arg: arg) if arg > 0 @@ -2048,24 +1527,18 @@ def finish alias_method :backward_char, :ed_prev_char private def vi_first_print(key) - @byte_pointer, @cursor = Reline::Unicode.vi_first_print(@line) + @byte_pointer, = Reline::Unicode.vi_first_print(current_line) end private def ed_move_to_beg(key) - @byte_pointer = @cursor = 0 + @byte_pointer = 0 end alias_method :beginning_of_line, :ed_move_to_beg private def ed_move_to_end(key) @byte_pointer = 0 - @cursor = 0 - byte_size = 0 - while @byte_pointer < @line.bytesize - byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer) - if byte_size > 0 - mbchar = @line.byteslice(@byte_pointer, byte_size) - @cursor += Reline::Unicode.get_mbchar_width(mbchar) - end + while @byte_pointer < current_line.bytesize + byte_size = Reline::Unicode.get_next_mbchar_size(current_line, @byte_pointer) @byte_pointer += byte_size end end @@ -2167,19 +1640,16 @@ def finish @buffer_of_lines = hit.split("\n") @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty? @line_index = @buffer_of_lines.size - 1 - @line = @buffer_of_lines.last - @byte_pointer = @line.bytesize - @cursor = @cursor_max = calculate_width(@line) - @rerender_all = true + @byte_pointer = current_line.bytesize @searching_prompt = "(%s)`%s'" % [prompt_name, search_word] else - @line = hit + @buffer_of_lines = [hit] + @byte_pointer = hit.bytesize @searching_prompt = "(%s)`%s': %s" % [prompt_name, search_word, hit] end last_hit = hit else if @is_multiline - @rerender_all = true @searching_prompt = "(failed %s)`%s'" % [prompt_name, search_word] else @searching_prompt = "(failed %s)`%s': %s" % [prompt_name, search_word, last_hit] @@ -2194,7 +1664,7 @@ def finish if @is_multiline @line_backup_in_history = whole_buffer else - @line_backup_in_history = @line + @line_backup_in_history = current_line end end searcher = generate_searcher @@ -2214,35 +1684,26 @@ def finish @buffer_of_lines = buffer.split("\n") @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty? @line_index = @buffer_of_lines.size - 1 - @line = @buffer_of_lines.last - @rerender_all = true else - @line = buffer + @buffer_of_lines = [buffer] end @searching_prompt = nil @waiting_proc = nil - @cursor_max = calculate_width(@line) - @cursor = @byte_pointer = 0 - @rerender_all = true - @cached_prompt_list = nil + @byte_pointer = 0 searcher.resume(-1) when "\C-g".ord if @is_multiline @buffer_of_lines = @line_backup_in_history.split("\n") @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty? @line_index = @buffer_of_lines.size - 1 - @line = @buffer_of_lines.last - @rerender_all = true else - @line = @line_backup_in_history + @buffer_of_lines = [@line_backup_in_history] end @history_pointer = nil @searching_prompt = nil @waiting_proc = nil @line_backup_in_history = nil - @cursor_max = calculate_width(@line) - @cursor = @byte_pointer = 0 - @rerender_all = true + @byte_pointer = 0 else chr = k.is_a?(String) ? k : k.chr(Encoding::ASCII_8BIT) if chr.match?(/[[:print:]]/) or k == "\C-h".ord or k == "\C-?".ord or k == "\C-r".ord or k == "\C-s".ord @@ -2258,18 +1719,13 @@ def finish @buffer_of_lines = line.split("\n") @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty? @line_index = @buffer_of_lines.size - 1 - @line = @buffer_of_lines.last - @rerender_all = true else - @line_backup_in_history = @line - @line = line + @line_backup_in_history = current_line + @buffer_of_lines = [line] end @searching_prompt = nil @waiting_proc = nil - @cursor_max = calculate_width(@line) - @cursor = @byte_pointer = 0 - @rerender_all = true - @cached_prompt_list = nil + @byte_pointer = 0 searcher.resume(-1) end end @@ -2290,9 +1746,9 @@ def finish history = nil h_pointer = nil line_no = nil - substr = @line.slice(0, @byte_pointer) + substr = current_line.slice(0, @byte_pointer) if @history_pointer.nil? - return if not @line.empty? and substr.empty? + return if not current_line.empty? and substr.empty? history = Reline::HISTORY elsif @history_pointer.zero? history = nil @@ -2318,23 +1774,23 @@ def finish end return if h_pointer.nil? @history_pointer = h_pointer + cursor = current_byte_pointer_cursor if @is_multiline @buffer_of_lines = Reline::HISTORY[@history_pointer].split("\n") @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty? @line_index = line_no - @line = @buffer_of_lines[@line_index] - @rerender_all = true + calculate_nearest_cursor(cursor) else - @line = Reline::HISTORY[@history_pointer] + @buffer_of_lines = [Reline::HISTORY[@history_pointer]] + calculate_nearest_cursor(cursor) end - @cursor_max = calculate_width(@line) arg -= 1 ed_search_prev_history(key, arg: arg) if arg > 0 end alias_method :history_search_backward, :ed_search_prev_history private def ed_search_next_history(key, arg: 1) - substr = @line.slice(0, @byte_pointer) + substr = current_line.slice(0, @byte_pointer) if @history_pointer.nil? return elsif @history_pointer == (Reline::HISTORY.size - 1) and not substr.empty? @@ -2365,21 +1821,21 @@ def finish if @history_pointer.nil? and substr.empty? @buffer_of_lines = [] @line_index = 0 + @byte_pointer = 0 else + cursor = current_byte_pointer_cursor @buffer_of_lines = Reline::HISTORY[@history_pointer].split("\n") @line_index = line_no + calculate_nearest_cursor(cursor) end @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty? - @line = @buffer_of_lines[@line_index] - @rerender_all = true else if @history_pointer.nil? and substr.empty? - @line = '' + set_current_line('', 0) else - @line = Reline::HISTORY[@history_pointer] + set_current_line(Reline::HISTORY[@history_pointer]) end end - @cursor_max = calculate_width(@line) arg -= 1 ed_search_next_history(key, arg: arg) if arg > 0 end @@ -2387,8 +1843,9 @@ def finish private def ed_prev_history(key, arg: 1) if @is_multiline and @line_index > 0 - @previous_line_index = @line_index + cursor = current_byte_pointer_cursor @line_index -= 1 + calculate_nearest_cursor(cursor) return end if Reline::HISTORY.empty? @@ -2396,16 +1853,17 @@ def finish end if @history_pointer.nil? @history_pointer = Reline::HISTORY.size - 1 + cursor = current_byte_pointer_cursor if @is_multiline @line_backup_in_history = whole_buffer @buffer_of_lines = Reline::HISTORY[@history_pointer].split("\n") @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty? @line_index = @buffer_of_lines.size - 1 - @line = @buffer_of_lines.last - @rerender_all = true + calculate_nearest_cursor(cursor) else - @line_backup_in_history = @line - @line = Reline::HISTORY[@history_pointer] + @line_backup_in_history = whole_buffer + @buffer_of_lines = [Reline::HISTORY[@history_pointer]] + calculate_nearest_cursor(cursor) end elsif @history_pointer.zero? return @@ -2416,20 +1874,16 @@ def finish @buffer_of_lines = Reline::HISTORY[@history_pointer].split("\n") @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty? @line_index = @buffer_of_lines.size - 1 - @line = @buffer_of_lines.last - @rerender_all = true else - Reline::HISTORY[@history_pointer] = @line + Reline::HISTORY[@history_pointer] = whole_buffer @history_pointer -= 1 - @line = Reline::HISTORY[@history_pointer] + @buffer_of_lines = [Reline::HISTORY[@history_pointer]] end end if @config.editing_mode_is?(:emacs, :vi_insert) - @cursor_max = @cursor = calculate_width(@line) - @byte_pointer = @line.bytesize + @byte_pointer = current_line.bytesize elsif @config.editing_mode_is?(:vi_command) - @byte_pointer = @cursor = 0 - @cursor_max = calculate_width(@line) + @byte_pointer = 0 end arg -= 1 ed_prev_history(key, arg: arg) if arg > 0 @@ -2438,8 +1892,9 @@ def finish private def ed_next_history(key, arg: 1) if @is_multiline and @line_index < (@buffer_of_lines.size - 1) - @previous_line_index = @line_index + cursor = current_byte_pointer_cursor @line_index += 1 + calculate_nearest_cursor(cursor) return end if @history_pointer.nil? @@ -2450,11 +1905,9 @@ def finish @buffer_of_lines = @line_backup_in_history.split("\n") @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty? @line_index = 0 - @line = @buffer_of_lines.first - @rerender_all = true else @history_pointer = nil - @line = @line_backup_in_history + @buffer_of_lines = [@line_backup_in_history] end else if @is_multiline @@ -2463,21 +1916,16 @@ def finish @buffer_of_lines = Reline::HISTORY[@history_pointer].split("\n") @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty? @line_index = 0 - @line = @buffer_of_lines.first - @rerender_all = true else - Reline::HISTORY[@history_pointer] = @line + Reline::HISTORY[@history_pointer] = whole_buffer @history_pointer += 1 - @line = Reline::HISTORY[@history_pointer] + @buffer_of_lines = [Reline::HISTORY[@history_pointer]] end end - @line = '' unless @line if @config.editing_mode_is?(:emacs, :vi_insert) - @cursor_max = @cursor = calculate_width(@line) - @byte_pointer = @line.bytesize + @byte_pointer = current_line.bytesize elsif @config.editing_mode_is?(:vi_command) - @byte_pointer = @cursor = 0 - @cursor_max = calculate_width(@line) + @byte_pointer = 0 end arg -= 1 ed_next_history(key, arg: arg) if arg > 0 @@ -2503,14 +1951,14 @@ def finish end else # should check confirm_multiline_termination to finish? - @previous_line_index = @line_index @line_index = @buffer_of_lines.size - 1 + @byte_pointer = current_line.bytesize finish end end else if @history_pointer - Reline::HISTORY[@history_pointer] = @line + Reline::HISTORY[@history_pointer] = whole_buffer @history_pointer = nil end finish @@ -2518,25 +1966,18 @@ def finish end private def em_delete_prev_char(key, arg: 1) - if @is_multiline and @cursor == 0 and @line_index > 0 - @buffer_of_lines[@line_index] = @line - @cursor = calculate_width(@buffer_of_lines[@line_index - 1]) - @byte_pointer = @buffer_of_lines[@line_index - 1].bytesize - @buffer_of_lines[@line_index - 1] += @buffer_of_lines.delete_at(@line_index) - @line_index -= 1 - @line = @buffer_of_lines[@line_index] - @cursor_max = calculate_width(@line) - @rerender_all = true - elsif @cursor > 0 - byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer) - @byte_pointer -= byte_size - @line, mbchar = byteslice!(@line, @byte_pointer, byte_size) - width = Reline::Unicode.get_mbchar_width(mbchar) - @cursor -= width - @cursor_max -= width + arg.times do + if @is_multiline and @byte_pointer == 0 and @line_index > 0 + @byte_pointer = @buffer_of_lines[@line_index - 1].bytesize + @buffer_of_lines[@line_index - 1] += @buffer_of_lines.delete_at(@line_index) + @line_index -= 1 + elsif @byte_pointer > 0 + byte_size = Reline::Unicode.get_prev_mbchar_size(current_line, @byte_pointer) + line, = byteslice!(current_line, @byte_pointer - byte_size, byte_size) + set_current_line(line, @byte_pointer - byte_size) + end end - arg -= 1 - em_delete_prev_char(key, arg: arg) if arg > 0 + process_auto_indent end alias_method :backward_delete_char, :em_delete_prev_char @@ -2546,19 +1987,12 @@ def finish # the line. With a negative numeric argument, kill backward # from the cursor to the beginning of the current line. private def ed_kill_line(key) - if @line.bytesize > @byte_pointer - @line, deleted = byteslice!(@line, @byte_pointer, @line.bytesize - @byte_pointer) - @byte_pointer = @line.bytesize - @cursor = @cursor_max = calculate_width(@line) + if current_line.bytesize > @byte_pointer + line, deleted = byteslice!(current_line, @byte_pointer, current_line.bytesize - @byte_pointer) + set_current_line(line, line.bytesize) @kill_ring.append(deleted) - elsif @is_multiline and @byte_pointer == @line.bytesize and @buffer_of_lines.size > @line_index + 1 - @cursor = calculate_width(@line) - @byte_pointer = @line.bytesize - @line += @buffer_of_lines.delete_at(@line_index + 1) - @cursor_max = calculate_width(@line) - @buffer_of_lines[@line_index] = @line - @rerender_all = true - @rest_height += 1 + elsif @is_multiline and @byte_pointer == current_line.bytesize and @buffer_of_lines.size > @line_index + 1 + set_current_line(current_line + @buffer_of_lines.delete_at(@line_index + 1), current_line.bytesize) end end alias_method :kill_line, :ed_kill_line @@ -2577,11 +2011,9 @@ def finish # to the beginning of the current line. private def vi_kill_line_prev(key) if @byte_pointer > 0 - @line, deleted = byteslice!(@line, 0, @byte_pointer) - @byte_pointer = 0 + line, deleted = byteslice!(current_line, 0, @byte_pointer) + set_current_line(line, 0) @kill_ring.append(deleted, true) - @cursor_max = calculate_width(@line) - @cursor = 0 end end alias_method :unix_line_discard, :vi_kill_line_prev @@ -2591,47 +2023,32 @@ def finish # GNU Readline:: +kill-whole-line+ (not bound) Kill all characters on the # current line, no matter where point is. private def em_kill_line(key) - if @line.size > 0 - @kill_ring.append(@line.dup, true) - @line.clear - @byte_pointer = 0 - @cursor_max = 0 - @cursor = 0 + if current_line.size > 0 + @kill_ring.append(current_line.dup, true) + set_current_line('', 0) end end alias_method :kill_whole_line, :em_kill_line private def em_delete(key) - if @line.empty? and (not @is_multiline or @buffer_of_lines.size == 1) and key == "\C-d".ord - @line = nil - if @buffer_of_lines.size > 1 - scroll_down(@highest_in_all - @first_line_started_from) - end - Reline::IOGate.move_cursor_column(0) + if current_line.empty? and (not @is_multiline or @buffer_of_lines.size == 1) and key == "\C-d".ord @eof = true finish - elsif @byte_pointer < @line.bytesize - splitted_last = @line.byteslice(@byte_pointer, @line.bytesize) + elsif @byte_pointer < current_line.bytesize + splitted_last = current_line.byteslice(@byte_pointer, current_line.bytesize) mbchar = splitted_last.grapheme_clusters.first - width = Reline::Unicode.get_mbchar_width(mbchar) - @cursor_max -= width - @line, = byteslice!(@line, @byte_pointer, mbchar.bytesize) - elsif @is_multiline and @byte_pointer == @line.bytesize and @buffer_of_lines.size > @line_index + 1 - @cursor = calculate_width(@line) - @byte_pointer = @line.bytesize - @line += @buffer_of_lines.delete_at(@line_index + 1) - @cursor_max = calculate_width(@line) - @buffer_of_lines[@line_index] = @line - @rerender_all = true - @rest_height += 1 + line, = byteslice!(current_line, @byte_pointer, mbchar.bytesize) + set_current_line(line) + elsif @is_multiline and @byte_pointer == current_line.bytesize and @buffer_of_lines.size > @line_index + 1 + set_current_line(current_line + @buffer_of_lines.delete_at(@line_index + 1), current_line.bytesize) end end alias_method :delete_char, :em_delete private def em_delete_or_list(key) - if @line.empty? or @byte_pointer < @line.bytesize + if current_line.empty? or @byte_pointer < current_line.bytesize em_delete(key) - else # show completed list + elsif !@config.autocompletion # show completed list result = call_completion_proc if result.is_a?(Array) complete(result, true) @@ -2642,164 +2059,136 @@ def finish private def em_yank(key) yanked = @kill_ring.yank - if yanked - @line = byteinsert(@line, @byte_pointer, yanked) - yanked_width = calculate_width(yanked) - @cursor += yanked_width - @cursor_max += yanked_width - @byte_pointer += yanked.bytesize - end + insert_text(yanked) if yanked end alias_method :yank, :em_yank private def em_yank_pop(key) yanked, prev_yank = @kill_ring.yank_pop if yanked - prev_yank_width = calculate_width(prev_yank) - @cursor -= prev_yank_width - @cursor_max -= prev_yank_width - @byte_pointer -= prev_yank.bytesize - @line, = byteslice!(@line, @byte_pointer, prev_yank.bytesize) - @line = byteinsert(@line, @byte_pointer, yanked) - yanked_width = calculate_width(yanked) - @cursor += yanked_width - @cursor_max += yanked_width - @byte_pointer += yanked.bytesize + line, = byteslice!(current_line, @byte_pointer - prev_yank.bytesize, prev_yank.bytesize) + set_current_line(line, @byte_pointer - prev_yank.bytesize) + insert_text(yanked) end end alias_method :yank_pop, :em_yank_pop private def ed_clear_screen(key) - @cleared = true + Reline::IOGate.clear_screen + @screen_size = Reline::IOGate.get_screen_size + @rendered_screen.lines = [] + @rendered_screen.base_y = 0 + @rendered_screen.cursor_y = 0 end alias_method :clear_screen, :ed_clear_screen private def em_next_word(key) - if @line.bytesize > @byte_pointer - byte_size, width = Reline::Unicode.em_forward_word(@line, @byte_pointer) + if current_line.bytesize > @byte_pointer + byte_size, _ = Reline::Unicode.em_forward_word(current_line, @byte_pointer) @byte_pointer += byte_size - @cursor += width end end alias_method :forward_word, :em_next_word private def ed_prev_word(key) if @byte_pointer > 0 - byte_size, width = Reline::Unicode.em_backward_word(@line, @byte_pointer) + byte_size, _ = Reline::Unicode.em_backward_word(current_line, @byte_pointer) @byte_pointer -= byte_size - @cursor -= width end end alias_method :backward_word, :ed_prev_word private def em_delete_next_word(key) - if @line.bytesize > @byte_pointer - byte_size, width = Reline::Unicode.em_forward_word(@line, @byte_pointer) - @line, word = byteslice!(@line, @byte_pointer, byte_size) + if current_line.bytesize > @byte_pointer + byte_size, _ = Reline::Unicode.em_forward_word(current_line, @byte_pointer) + line, word = byteslice!(current_line, @byte_pointer, byte_size) + set_current_line(line) @kill_ring.append(word) - @cursor_max -= width end end alias_method :kill_word, :em_delete_next_word private def ed_delete_prev_word(key) if @byte_pointer > 0 - byte_size, width = Reline::Unicode.em_backward_word(@line, @byte_pointer) - @line, word = byteslice!(@line, @byte_pointer - byte_size, byte_size) + byte_size, _ = Reline::Unicode.em_backward_word(current_line, @byte_pointer) + line, word = byteslice!(current_line, @byte_pointer - byte_size, byte_size) + set_current_line(line, @byte_pointer - byte_size) @kill_ring.append(word, true) - @byte_pointer -= byte_size - @cursor -= width - @cursor_max -= width end end alias_method :backward_kill_word, :ed_delete_prev_word private def ed_transpose_chars(key) if @byte_pointer > 0 - if @cursor_max > @cursor - byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer) - mbchar = @line.byteslice(@byte_pointer, byte_size) - width = Reline::Unicode.get_mbchar_width(mbchar) - @cursor += width + if @byte_pointer < current_line.bytesize + byte_size = Reline::Unicode.get_next_mbchar_size(current_line, @byte_pointer) @byte_pointer += byte_size end - back1_byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer) + back1_byte_size = Reline::Unicode.get_prev_mbchar_size(current_line, @byte_pointer) if (@byte_pointer - back1_byte_size) > 0 - back2_byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer - back1_byte_size) + back2_byte_size = Reline::Unicode.get_prev_mbchar_size(current_line, @byte_pointer - back1_byte_size) back2_pointer = @byte_pointer - back1_byte_size - back2_byte_size - @line, back2_mbchar = byteslice!(@line, back2_pointer, back2_byte_size) - @line = byteinsert(@line, @byte_pointer - back2_byte_size, back2_mbchar) + line, back2_mbchar = byteslice!(current_line, back2_pointer, back2_byte_size) + set_current_line(byteinsert(line, @byte_pointer - back2_byte_size, back2_mbchar)) end end end alias_method :transpose_chars, :ed_transpose_chars private def ed_transpose_words(key) - left_word_start, middle_start, right_word_start, after_start = Reline::Unicode.ed_transpose_words(@line, @byte_pointer) - before = @line.byteslice(0, left_word_start) - left_word = @line.byteslice(left_word_start, middle_start - left_word_start) - middle = @line.byteslice(middle_start, right_word_start - middle_start) - right_word = @line.byteslice(right_word_start, after_start - right_word_start) - after = @line.byteslice(after_start, @line.bytesize - after_start) + left_word_start, middle_start, right_word_start, after_start = Reline::Unicode.ed_transpose_words(current_line, @byte_pointer) + before = current_line.byteslice(0, left_word_start) + left_word = current_line.byteslice(left_word_start, middle_start - left_word_start) + middle = current_line.byteslice(middle_start, right_word_start - middle_start) + right_word = current_line.byteslice(right_word_start, after_start - right_word_start) + after = current_line.byteslice(after_start, current_line.bytesize - after_start) return if left_word.empty? or right_word.empty? - @line = before + right_word + middle + left_word + after from_head_to_left_word = before + right_word + middle + left_word - @byte_pointer = from_head_to_left_word.bytesize - @cursor = calculate_width(from_head_to_left_word) + set_current_line(from_head_to_left_word + after, from_head_to_left_word.bytesize) end alias_method :transpose_words, :ed_transpose_words private def em_capitol_case(key) - if @line.bytesize > @byte_pointer - byte_size, _, new_str = Reline::Unicode.em_forward_word_with_capitalization(@line, @byte_pointer) - before = @line.byteslice(0, @byte_pointer) - after = @line.byteslice((@byte_pointer + byte_size)..-1) - @line = before + new_str + after - @byte_pointer += new_str.bytesize - @cursor += calculate_width(new_str) + if current_line.bytesize > @byte_pointer + byte_size, _, new_str = Reline::Unicode.em_forward_word_with_capitalization(current_line, @byte_pointer) + before = current_line.byteslice(0, @byte_pointer) + after = current_line.byteslice((@byte_pointer + byte_size)..-1) + set_current_line(before + new_str + after, @byte_pointer + new_str.bytesize) end end alias_method :capitalize_word, :em_capitol_case private def em_lower_case(key) - if @line.bytesize > @byte_pointer - byte_size, = Reline::Unicode.em_forward_word(@line, @byte_pointer) - part = @line.byteslice(@byte_pointer, byte_size).grapheme_clusters.map { |mbchar| + if current_line.bytesize > @byte_pointer + byte_size, = Reline::Unicode.em_forward_word(current_line, @byte_pointer) + part = current_line.byteslice(@byte_pointer, byte_size).grapheme_clusters.map { |mbchar| mbchar =~ /[A-Z]/ ? mbchar.downcase : mbchar }.join - rest = @line.byteslice((@byte_pointer + byte_size)..-1) - @line = @line.byteslice(0, @byte_pointer) + part - @byte_pointer = @line.bytesize - @cursor = calculate_width(@line) - @cursor_max = @cursor + calculate_width(rest) - @line += rest + rest = current_line.byteslice((@byte_pointer + byte_size)..-1) + line = current_line.byteslice(0, @byte_pointer) + part + set_current_line(line + rest, line.bytesize) end end alias_method :downcase_word, :em_lower_case private def em_upper_case(key) - if @line.bytesize > @byte_pointer - byte_size, = Reline::Unicode.em_forward_word(@line, @byte_pointer) - part = @line.byteslice(@byte_pointer, byte_size).grapheme_clusters.map { |mbchar| + if current_line.bytesize > @byte_pointer + byte_size, = Reline::Unicode.em_forward_word(current_line, @byte_pointer) + part = current_line.byteslice(@byte_pointer, byte_size).grapheme_clusters.map { |mbchar| mbchar =~ /[a-z]/ ? mbchar.upcase : mbchar }.join - rest = @line.byteslice((@byte_pointer + byte_size)..-1) - @line = @line.byteslice(0, @byte_pointer) + part - @byte_pointer = @line.bytesize - @cursor = calculate_width(@line) - @cursor_max = @cursor + calculate_width(rest) - @line += rest + rest = current_line.byteslice((@byte_pointer + byte_size)..-1) + line = current_line.byteslice(0, @byte_pointer) + part + set_current_line(line + rest, line.bytesize) end end alias_method :upcase_word, :em_upper_case private def em_kill_region(key) if @byte_pointer > 0 - byte_size, width = Reline::Unicode.em_big_backward_word(@line, @byte_pointer) - @line, deleted = byteslice!(@line, @byte_pointer - byte_size, byte_size) - @byte_pointer -= byte_size - @cursor -= width - @cursor_max -= width + byte_size, _ = Reline::Unicode.em_big_backward_word(current_line, @byte_pointer) + line, deleted = byteslice!(current_line, @byte_pointer - byte_size, byte_size) + set_current_line(line, @byte_pointer - byte_size) @kill_ring.append(deleted, true) end end @@ -2827,10 +2216,9 @@ def finish alias_method :vi_movement_mode, :vi_command_mode private def vi_next_word(key, arg: 1) - if @line.bytesize > @byte_pointer - byte_size, width = Reline::Unicode.vi_forward_word(@line, @byte_pointer, @drop_terminate_spaces) + if current_line.bytesize > @byte_pointer + byte_size, _ = Reline::Unicode.vi_forward_word(current_line, @byte_pointer, @drop_terminate_spaces) @byte_pointer += byte_size - @cursor += width end arg -= 1 vi_next_word(key, arg: arg) if arg > 0 @@ -2838,38 +2226,32 @@ def finish private def vi_prev_word(key, arg: 1) if @byte_pointer > 0 - byte_size, width = Reline::Unicode.vi_backward_word(@line, @byte_pointer) + byte_size, _ = Reline::Unicode.vi_backward_word(current_line, @byte_pointer) @byte_pointer -= byte_size - @cursor -= width end arg -= 1 vi_prev_word(key, arg: arg) if arg > 0 end private def vi_end_word(key, arg: 1, inclusive: false) - if @line.bytesize > @byte_pointer - byte_size, width = Reline::Unicode.vi_forward_end_word(@line, @byte_pointer) + if current_line.bytesize > @byte_pointer + byte_size, _ = Reline::Unicode.vi_forward_end_word(current_line, @byte_pointer) @byte_pointer += byte_size - @cursor += width end arg -= 1 if inclusive and arg.zero? - byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer) + byte_size = Reline::Unicode.get_next_mbchar_size(current_line, @byte_pointer) if byte_size > 0 - c = @line.byteslice(@byte_pointer, byte_size) - width = Reline::Unicode.get_mbchar_width(c) @byte_pointer += byte_size - @cursor += width end end vi_end_word(key, arg: arg) if arg > 0 end private def vi_next_big_word(key, arg: 1) - if @line.bytesize > @byte_pointer - byte_size, width = Reline::Unicode.vi_big_forward_word(@line, @byte_pointer) + if current_line.bytesize > @byte_pointer + byte_size, _ = Reline::Unicode.vi_big_forward_word(current_line, @byte_pointer) @byte_pointer += byte_size - @cursor += width end arg -= 1 vi_next_big_word(key, arg: arg) if arg > 0 @@ -2877,50 +2259,39 @@ def finish private def vi_prev_big_word(key, arg: 1) if @byte_pointer > 0 - byte_size, width = Reline::Unicode.vi_big_backward_word(@line, @byte_pointer) + byte_size, _ = Reline::Unicode.vi_big_backward_word(current_line, @byte_pointer) @byte_pointer -= byte_size - @cursor -= width end arg -= 1 vi_prev_big_word(key, arg: arg) if arg > 0 end private def vi_end_big_word(key, arg: 1, inclusive: false) - if @line.bytesize > @byte_pointer - byte_size, width = Reline::Unicode.vi_big_forward_end_word(@line, @byte_pointer) + if current_line.bytesize > @byte_pointer + byte_size, _ = Reline::Unicode.vi_big_forward_end_word(current_line, @byte_pointer) @byte_pointer += byte_size - @cursor += width end arg -= 1 if inclusive and arg.zero? - byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer) + byte_size = Reline::Unicode.get_next_mbchar_size(current_line, @byte_pointer) if byte_size > 0 - c = @line.byteslice(@byte_pointer, byte_size) - width = Reline::Unicode.get_mbchar_width(c) @byte_pointer += byte_size - @cursor += width end end vi_end_big_word(key, arg: arg) if arg > 0 end private def vi_delete_prev_char(key) - if @is_multiline and @cursor == 0 and @line_index > 0 - @buffer_of_lines[@line_index] = @line - @cursor = calculate_width(@buffer_of_lines[@line_index - 1]) + if @is_multiline and @byte_pointer == 0 and @line_index > 0 @byte_pointer = @buffer_of_lines[@line_index - 1].bytesize @buffer_of_lines[@line_index - 1] += @buffer_of_lines.delete_at(@line_index) @line_index -= 1 - @line = @buffer_of_lines[@line_index] - @cursor_max = calculate_width(@line) - @rerender_all = true - elsif @cursor > 0 - byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer) + process_auto_indent cursor_dependent: false + elsif @byte_pointer > 0 + byte_size = Reline::Unicode.get_prev_mbchar_size(current_line, @byte_pointer) @byte_pointer -= byte_size - @line, mbchar = byteslice!(@line, @byte_pointer, byte_size) - width = Reline::Unicode.get_mbchar_width(mbchar) - @cursor -= width - @cursor_max -= width + line, _ = byteslice!(current_line, @byte_pointer, byte_size) + set_current_line(line) end end @@ -2935,16 +2306,14 @@ def finish end private def ed_delete_prev_char(key, arg: 1) - deleted = '' + deleted = +'' arg.times do - if @cursor > 0 - byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer) + if @byte_pointer > 0 + byte_size = Reline::Unicode.get_prev_mbchar_size(current_line, @byte_pointer) @byte_pointer -= byte_size - @line, mbchar = byteslice!(@line, @byte_pointer, byte_size) + line, mbchar = byteslice!(current_line, @byte_pointer, byte_size) + set_current_line(line) deleted.prepend(mbchar) - width = Reline::Unicode.get_mbchar_width(mbchar) - @cursor -= width - @cursor_max -= width end end copy_for_vi(deleted) @@ -2952,20 +2321,18 @@ def finish private def vi_zero(key) @byte_pointer = 0 - @cursor = 0 end private def vi_change_meta(key, arg: 1) @drop_terminate_spaces = true - @waiting_operator_proc = proc { |cursor_diff, byte_pointer_diff| + @waiting_operator_proc = proc { |byte_pointer_diff| if byte_pointer_diff > 0 - @line, cut = byteslice!(@line, @byte_pointer, byte_pointer_diff) + line, cut = byteslice!(current_line, @byte_pointer, byte_pointer_diff) elsif byte_pointer_diff < 0 - @line, cut = byteslice!(@line, @byte_pointer + byte_pointer_diff, -byte_pointer_diff) + line, cut = byteslice!(current_line, @byte_pointer + byte_pointer_diff, -byte_pointer_diff) end + set_current_line(line) copy_for_vi(cut) - @cursor += cursor_diff if cursor_diff < 0 - @cursor_max -= cursor_diff.abs @byte_pointer += byte_pointer_diff if byte_pointer_diff < 0 @config.editing_mode = :vi_insert @drop_terminate_spaces = false @@ -2974,26 +2341,24 @@ def finish end private def vi_delete_meta(key, arg: 1) - @waiting_operator_proc = proc { |cursor_diff, byte_pointer_diff| + @waiting_operator_proc = proc { |byte_pointer_diff| if byte_pointer_diff > 0 - @line, cut = byteslice!(@line, @byte_pointer, byte_pointer_diff) + line, cut = byteslice!(current_line, @byte_pointer, byte_pointer_diff) elsif byte_pointer_diff < 0 - @line, cut = byteslice!(@line, @byte_pointer + byte_pointer_diff, -byte_pointer_diff) + line, cut = byteslice!(current_line, @byte_pointer + byte_pointer_diff, -byte_pointer_diff) end copy_for_vi(cut) - @cursor += cursor_diff if cursor_diff < 0 - @cursor_max -= cursor_diff.abs - @byte_pointer += byte_pointer_diff if byte_pointer_diff < 0 + set_current_line(line || '', @byte_pointer + (byte_pointer_diff < 0 ? byte_pointer_diff : 0)) } @waiting_operator_vi_arg = arg end private def vi_yank(key, arg: 1) - @waiting_operator_proc = proc { |cursor_diff, byte_pointer_diff| + @waiting_operator_proc = proc { |byte_pointer_diff| if byte_pointer_diff > 0 - cut = @line.byteslice(@byte_pointer, byte_pointer_diff) + cut = current_line.byteslice(@byte_pointer, byte_pointer_diff) elsif byte_pointer_diff < 0 - cut = @line.byteslice(@byte_pointer + byte_pointer_diff, -byte_pointer_diff) + cut = current_line.byteslice(@byte_pointer + byte_pointer_diff, -byte_pointer_diff) end copy_for_vi(cut) } @@ -3001,12 +2366,8 @@ def finish end private def vi_list_or_eof(key) - if (not @is_multiline and @line.empty?) or (@is_multiline and @line.empty? and @buffer_of_lines.size == 1) - @line = nil - if @buffer_of_lines.size > 1 - scroll_down(@highest_in_all - @first_line_started_from) - end - Reline::IOGate.move_cursor_column(0) + if (not @is_multiline and current_line.empty?) or (@is_multiline and current_line.empty? and @buffer_of_lines.size == 1) + set_current_line('', 0) @eof = true finish else @@ -3017,18 +2378,15 @@ def finish alias_method :vi_eof_maybe, :vi_list_or_eof private def ed_delete_next_char(key, arg: 1) - byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer) - unless @line.empty? || byte_size == 0 - @line, mbchar = byteslice!(@line, @byte_pointer, byte_size) + byte_size = Reline::Unicode.get_next_mbchar_size(current_line, @byte_pointer) + unless current_line.empty? || byte_size == 0 + line, mbchar = byteslice!(current_line, @byte_pointer, byte_size) copy_for_vi(mbchar) - width = Reline::Unicode.get_mbchar_width(mbchar) - @cursor_max -= width - if @cursor > 0 and @cursor >= @cursor_max - byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer) - mbchar = @line.byteslice(@byte_pointer - byte_size, byte_size) - width = Reline::Unicode.get_mbchar_width(mbchar) - @byte_pointer -= byte_size - @cursor -= width + if @byte_pointer > 0 && current_line.bytesize == @byte_pointer + byte_size + byte_size = Reline::Unicode.get_prev_mbchar_size(line, @byte_pointer) + set_current_line(line, @byte_pointer - byte_size) + else + set_current_line(line, @byte_pointer) end end arg -= 1 @@ -3041,20 +2399,14 @@ def finish end if @history_pointer.nil? @history_pointer = 0 - @line_backup_in_history = @line - @line = Reline::HISTORY[@history_pointer] - @cursor_max = calculate_width(@line) - @cursor = 0 - @byte_pointer = 0 + @line_backup_in_history = current_line + set_current_line(Reline::HISTORY[@history_pointer], 0) elsif @history_pointer.zero? return else - Reline::HISTORY[@history_pointer] = @line + Reline::HISTORY[@history_pointer] = current_line @history_pointer = 0 - @line = Reline::HISTORY[@history_pointer] - @cursor_max = calculate_width(@line) - @cursor = 0 - @byte_pointer = 0 + set_current_line(Reline::HISTORY[@history_pointer], 0) end end @@ -3063,7 +2415,7 @@ def finish if @is_multiline fp.write whole_lines.join("\n") else - fp.write @line + fp.write current_line end fp.path } @@ -3072,21 +2424,16 @@ def finish @buffer_of_lines = File.read(path).split("\n") @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty? @line_index = 0 - @line = @buffer_of_lines[@line_index] - @rerender_all = true else - @line = File.read(path) + @buffer_of_lines = File.read(path).split("\n") end finish end private def vi_paste_prev(key, arg: 1) if @vi_clipboard.size > 0 - @line = byteinsert(@line, @byte_pointer, @vi_clipboard) - @cursor_max += calculate_width(@vi_clipboard) cursor_point = @vi_clipboard.grapheme_clusters[0..-2].join - @cursor += calculate_width(cursor_point) - @byte_pointer += cursor_point.bytesize + set_current_line(byteinsert(current_line, @byte_pointer, @vi_clipboard), @byte_pointer + cursor_point.bytesize) end arg -= 1 vi_paste_prev(key, arg: arg) if arg > 0 @@ -3094,11 +2441,9 @@ def finish private def vi_paste_next(key, arg: 1) if @vi_clipboard.size > 0 - byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer) - @line = byteinsert(@line, @byte_pointer + byte_size, @vi_clipboard) - @cursor_max += calculate_width(@vi_clipboard) - @cursor += calculate_width(@vi_clipboard) - @byte_pointer += @vi_clipboard.bytesize + byte_size = Reline::Unicode.get_next_mbchar_size(current_line, @byte_pointer) + line = byteinsert(current_line, @byte_pointer + byte_size, @vi_clipboard) + set_current_line(line, @byte_pointer + @vi_clipboard.bytesize) end arg -= 1 vi_paste_next(key, arg: arg) if arg > 0 @@ -3122,12 +2467,13 @@ def finish end private def vi_to_column(key, arg: 0) - @byte_pointer, @cursor = @line.grapheme_clusters.inject([0, 0]) { |total, gc| + current_row_width = calculate_width(current_row) + @byte_pointer, = current_line.grapheme_clusters.inject([0, 0]) { |total, gc| # total has [byte_size, cursor] mbchar_width = Reline::Unicode.get_mbchar_width(gc) if (total.last + mbchar_width) >= arg break total - elsif (total.last + mbchar_width) >= @cursor_max + elsif (total.last + mbchar_width) >= current_row_width break total else total = [total.first + gc.bytesize, total.last + mbchar_width] @@ -3139,26 +2485,22 @@ def finish private def vi_replace_char(key, arg: 1) @waiting_proc = ->(k) { if arg == 1 - byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer) - before = @line.byteslice(0, @byte_pointer) + byte_size = Reline::Unicode.get_next_mbchar_size(current_line, @byte_pointer) + before = current_line.byteslice(0, @byte_pointer) remaining_point = @byte_pointer + byte_size - after = @line.byteslice(remaining_point, @line.bytesize - remaining_point) - @line = before + k.chr + after - @cursor_max = calculate_width(@line) + after = current_line.byteslice(remaining_point, current_line.bytesize - remaining_point) + set_current_line(before + k.chr + after) @waiting_proc = nil elsif arg > 1 byte_size = 0 arg.times do - byte_size += Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer + byte_size) + byte_size += Reline::Unicode.get_next_mbchar_size(current_line, @byte_pointer + byte_size) end - before = @line.byteslice(0, @byte_pointer) + before = current_line.byteslice(0, @byte_pointer) remaining_point = @byte_pointer + byte_size - after = @line.byteslice(remaining_point, @line.bytesize - remaining_point) + after = current_line.byteslice(remaining_point, current_line.bytesize - remaining_point) replaced = k.chr * arg - @line = before + replaced + after - @byte_pointer += replaced.bytesize - @cursor += calculate_width(replaced) - @cursor_max = calculate_width(@line) + set_current_line(before + replaced + after, @byte_pointer + replaced.bytesize) @waiting_proc = nil end } @@ -3181,7 +2523,7 @@ def finish prev_total = nil total = nil found = false - @line.byteslice(@byte_pointer..-1).grapheme_clusters.each do |mbchar| + current_line.byteslice(@byte_pointer..-1).grapheme_clusters.each do |mbchar| # total has [byte_size, cursor] unless total # skip cursor point @@ -3201,21 +2543,16 @@ def finish end end if not need_prev_char and found and total - byte_size, width = total + byte_size, _ = total @byte_pointer += byte_size - @cursor += width elsif need_prev_char and found and prev_total - byte_size, width = prev_total + byte_size, _ = prev_total @byte_pointer += byte_size - @cursor += width end if inclusive - byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer) + byte_size = Reline::Unicode.get_next_mbchar_size(current_line, @byte_pointer) if byte_size > 0 - c = @line.byteslice(@byte_pointer, byte_size) - width = Reline::Unicode.get_mbchar_width(c) @byte_pointer += byte_size - @cursor += width end end @waiting_proc = nil @@ -3238,7 +2575,7 @@ def finish prev_total = nil total = nil found = false - @line.byteslice(0..@byte_pointer).grapheme_clusters.reverse_each do |mbchar| + current_line.byteslice(0..@byte_pointer).grapheme_clusters.reverse_each do |mbchar| # total has [byte_size, cursor] unless total # skip cursor point @@ -3258,26 +2595,19 @@ def finish end end if not need_next_char and found and total - byte_size, width = total + byte_size, _ = total @byte_pointer -= byte_size - @cursor -= width elsif need_next_char and found and prev_total - byte_size, width = prev_total + byte_size, _ = prev_total @byte_pointer -= byte_size - @cursor -= width end @waiting_proc = nil end private def vi_join_lines(key, arg: 1) if @is_multiline and @buffer_of_lines.size > @line_index + 1 - @cursor = calculate_width(@line) - @byte_pointer = @line.bytesize - @line += ' ' + @buffer_of_lines.delete_at(@line_index + 1).lstrip - @cursor_max = calculate_width(@line) - @buffer_of_lines[@line_index] = @line - @rerender_all = true - @rest_height += 1 + next_line = @buffer_of_lines.delete_at(@line_index + 1).lstrip + set_current_line(current_line + ' ' + next_line, current_line.bytesize) end arg -= 1 vi_join_lines(key, arg: arg) if arg > 0 @@ -3291,10 +2621,7 @@ def finish private def em_exchange_mark(key) return unless @mark_pointer new_pointer = [@byte_pointer, @line_index] - @previous_line_index = @line_index @byte_pointer, @line_index = @mark_pointer - @cursor = calculate_width(@line.byteslice(0, @byte_pointer)) - @cursor_max = calculate_width(@line) @mark_pointer = new_pointer end alias_method :exchange_point_and_mark, :em_exchange_mark diff --git a/lib/reline/terminfo.rb b/lib/reline/terminfo.rb index 2cfa32b9f7a9e0..6885a0c6be9242 100644 --- a/lib/reline/terminfo.rb +++ b/lib/reline/terminfo.rb @@ -80,23 +80,11 @@ module Reline::Terminfo def self.setupterm(term, fildes) errret_int = Fiddle::Pointer.malloc(Fiddle::SIZEOF_INT) ret = @setupterm.(term, fildes, errret_int) - errret = errret_int[0, Fiddle::SIZEOF_INT].unpack1('i') case ret when 0 # OK - 0 + @term_supported = true when -1 # ERR - case errret - when 1 - raise TerminfoError.new('The terminal is hardcopy, cannot be used for curses applications.') - when 0 - raise TerminfoError.new('The terminal could not be found, or that it is a generic type, having too little information for curses applications to run.') - when -1 - raise TerminfoError.new('The terminfo database could not be found.') - else # unknown - -1 - end - else # unknown - -2 + @term_supported = false end end @@ -148,9 +136,14 @@ def self.tigetnum(capname) num end + # NOTE: This means Fiddle and curses are enabled. def self.enabled? true end + + def self.term_supported? + @term_supported + end end if Reline::Terminfo.curses_dl module Reline::Terminfo diff --git a/lib/reline/version.rb b/lib/reline/version.rb index 4ddc186ebb7062..acb5e279b8cc77 100644 --- a/lib/reline/version.rb +++ b/lib/reline/version.rb @@ -1,3 +1,3 @@ module Reline - VERSION = '0.4.3' + VERSION = '0.5.1' end diff --git a/lib/reline/windows.rb b/lib/reline/windows.rb index 6f635f630fda69..ee3f73e3830575 100644 --- a/lib/reline/windows.rb +++ b/lib/reline/windows.rb @@ -1,6 +1,8 @@ require 'fiddle/import' class Reline::Windows + RESET_COLOR = "\e[0m" + def self.encoding Encoding::UTF_8 end @@ -85,7 +87,7 @@ def initialize(dllname, func, import, export = "0", calltype = :stdcall) def call(*args) import = @proto.split("") args.each_with_index do |x, i| - args[i], = [x == 0 ? nil : x].pack("p").unpack(POINTER_TYPE) if import[i] == "S" + args[i], = [x == 0 ? nil : +x].pack("p").unpack(POINTER_TYPE) if import[i] == "S" args[i], = [x].pack("I").unpack("i") if import[i] == "I" end ret, = @func.call(*args) @@ -257,7 +259,7 @@ def self.process_key_event(repeat_count, virtual_key_code, virtual_scan_code, ch def self.check_input_event num_of_events = 0.chr * 8 while @@output_buf.empty? - Reline.core.line_editor.resize + Reline.core.line_editor.handle_signal if @@WaitForSingleObject.(@@hConsoleInputHandle, 100) != 0 # max 0.1 sec # prevent for background consolemode change @@legacy_console = (getconsolemode() & ENABLE_VIRTUAL_TERMINAL_PROCESSING == 0) diff --git a/lib/resolv.rb b/lib/resolv.rb index b585b10ca90af8..e36dbce2595d82 100644 --- a/lib/resolv.rb +++ b/lib/resolv.rb @@ -37,7 +37,7 @@ class Resolv - VERSION = "0.3.0" + VERSION = "0.4.0" ## # Looks up the first IP address for +name+. diff --git a/lib/ruby_vm/rjit/insn_compiler.rb b/lib/ruby_vm/rjit/insn_compiler.rb index 1bcee7cb704f42..2346c92bd1d0a8 100644 --- a/lib/ruby_vm/rjit/insn_compiler.rb +++ b/lib/ruby_vm/rjit/insn_compiler.rb @@ -57,6 +57,7 @@ def compile(jit, ctx, asm, insn) when :putobject then putobject(jit, ctx, asm) when :putspecialobject then putspecialobject(jit, ctx, asm) when :putstring then putstring(jit, ctx, asm) + when :putchilledstring then putchilledstring(jit, ctx, asm) when :concatstrings then concatstrings(jit, ctx, asm) when :anytostring then anytostring(jit, ctx, asm) when :toregexp then toregexp(jit, ctx, asm) @@ -776,6 +777,27 @@ def putstring(jit, ctx, asm) asm.mov(C_ARGS[0], EC) asm.mov(C_ARGS[1], to_value(put_val)) + asm.mov(C_ARGS[2], 0) + asm.call(C.rb_ec_str_resurrect) + + stack_top = ctx.stack_push(Type::TString) + asm.mov(stack_top, C_RET) + + KeepCompiling + end + + # @param jit [RubyVM::RJIT::JITState] + # @param ctx [RubyVM::RJIT::Context] + # @param asm [RubyVM::RJIT::Assembler] + def putchilledstring(jit, ctx, asm) + put_val = jit.operand(0, ruby: true) + + # Save the PC and SP because the callee will allocate + jit_prepare_routine_call(jit, ctx, asm) + + asm.mov(C_ARGS[0], EC) + asm.mov(C_ARGS[1], to_value(put_val)) + asm.mov(C_ARGS[2], 1) asm.call(C.rb_ec_str_resurrect) stack_top = ctx.stack_push(Type::TString) diff --git a/lib/rubygems/command_manager.rb b/lib/rubygems/command_manager.rb index c2e4f4ce49ebc4..8e578dc1966172 100644 --- a/lib/rubygems/command_manager.rb +++ b/lib/rubygems/command_manager.rb @@ -60,6 +60,7 @@ class Gem::CommandManager :push, :query, :rdoc, + :rebuild, :search, :server, :signin, diff --git a/lib/rubygems/commands/build_command.rb b/lib/rubygems/commands/build_command.rb index 0ebdec565b5606..2ec83241418d13 100644 --- a/lib/rubygems/commands/build_command.rb +++ b/lib/rubygems/commands/build_command.rb @@ -1,11 +1,13 @@ # frozen_string_literal: true require_relative "../command" +require_relative "../gemspec_helpers" require_relative "../package" require_relative "../version_option" class Gem::Commands::BuildCommand < Gem::Command include Gem::VersionOption + include Gem::GemspecHelpers def initialize super "build", "Build a gem from a gemspec" @@ -75,17 +77,6 @@ def execute private - def find_gemspec(glob = "*.gemspec") - gemspecs = Dir.glob(glob).sort - - if gemspecs.size > 1 - alert_error "Multiple gemspecs found: #{gemspecs}, please specify one" - terminate_interaction(1) - end - - gemspecs.first - end - def build_gem gemspec = resolve_gem_name diff --git a/lib/rubygems/commands/rebuild_command.rb b/lib/rubygems/commands/rebuild_command.rb new file mode 100644 index 00000000000000..77a474ef1ddf32 --- /dev/null +++ b/lib/rubygems/commands/rebuild_command.rb @@ -0,0 +1,262 @@ +# frozen_string_literal: true + +require "date" +require "digest" +require "fileutils" +require "tmpdir" +require_relative "../gemspec_helpers" +require_relative "../package" + +class Gem::Commands::RebuildCommand < Gem::Command + include Gem::GemspecHelpers + + def initialize + super "rebuild", "Attempt to reproduce a build of a gem." + + add_option "--diff", "If the files don't match, compare them using diffoscope." do |_value, options| + options[:diff] = true + end + + add_option "--force", "Skip validation of the spec." do |_value, options| + options[:force] = true + end + + add_option "--strict", "Consider warnings as errors when validating the spec." do |_value, options| + options[:strict] = true + end + + add_option "--source GEM_SOURCE", "Specify the source to download the gem from." do |value, options| + options[:source] = value + end + + add_option "--original GEM_FILE", "Specify a local file to compare against (instead of downloading it)." do |value, options| + options[:original_gem_file] = value + end + + add_option "--gemspec GEMSPEC_FILE", "Specify the name of the gemspec file." do |value, options| + options[:gemspec_file] = value + end + + add_option "-C PATH", "Run as if gem build was started in instead of the current working directory." do |value, options| + options[:build_path] = value + end + end + + def arguments # :nodoc: + "GEM_NAME gem name on gem server\n" \ + "GEM_VERSION gem version you are attempting to rebuild" + end + + def description # :nodoc: + <<-EOF +The rebuild command allows you to (attempt to) reproduce a build of a gem +from a ruby gemspec. + +This command assumes the gemspec can be built with the `gem build` command. +If you use any of `gem build`, `rake build`, or`rake release` in the +build/release process for a gem, it is a potential candidate. + +You will need to match the RubyGems version used, since this is included in +the Gem metadata. + +If the gem includes lockfiles (e.g. Gemfile.lock) and similar, it will +require more effort to reproduce a build. For example, it might require +more precisely matched versions of Ruby and/or Bundler to be used. + EOF + end + + def usage # :nodoc: + "#{program_name} GEM_NAME GEM_VERSION" + end + + def execute + gem_name, gem_version = get_gem_name_and_version + + old_dir, new_dir = prep_dirs + + gem_filename = "#{gem_name}-#{gem_version}.gem" + old_file = File.join(old_dir, gem_filename) + new_file = File.join(new_dir, gem_filename) + + if options[:original_gem_file] + FileUtils.copy_file(options[:original_gem_file], old_file) + else + download_gem(gem_name, gem_version, old_file) + end + + rg_version = rubygems_version(old_file) + unless rg_version == Gem::VERSION + alert_error <<-EOF +You need to use the same RubyGems version #{gem_name} v#{gem_version} was built with. + +#{gem_name} v#{gem_version} was built using RubyGems v#{rg_version}. +Gem files include the version of RubyGems used to build them. +This means in order to reproduce #{gem_filename}, you must also use RubyGems v#{rg_version}. + +You're using RubyGems v#{Gem::VERSION}. + +Please install RubyGems v#{rg_version} and try again. + EOF + terminate_interaction 1 + end + + source_date_epoch = get_timestamp(old_file).to_s + + if build_path = options[:build_path] + Dir.chdir(build_path) { build_gem(gem_name, source_date_epoch, new_file) } + else + build_gem(gem_name, source_date_epoch, new_file) + end + + compare(source_date_epoch, old_file, new_file) + end + + private + + def sha256(file) + Digest::SHA256.hexdigest(Gem.read_binary(file)) + end + + def get_timestamp(file) + mtime = nil + File.open(file, Gem.binary_mode) do |f| + Gem::Package::TarReader.new(f) do |tar| + mtime = tar.seek("metadata.gz") {|tf| tf.header.mtime } + end + end + + mtime + end + + def compare(source_date_epoch, old_file, new_file) + date = Time.at(source_date_epoch.to_i).strftime("%F %T %Z") + + old_hash = sha256(old_file) + new_hash = sha256(new_file) + + say + say "Built at: #{date} (#{source_date_epoch})" + say "Original build saved to: #{old_file}" + say "Reproduced build saved to: #{new_file}" + say "Working directory: #{options[:build_path] || Dir.pwd}" + say + say "Hash comparison:" + say " #{old_hash}\t#{old_file}" + say " #{new_hash}\t#{new_file}" + say + + if old_hash == new_hash + say "SUCCESS - original and rebuild hashes matched" + else + say "FAILURE - original and rebuild hashes did not match" + say + + if options[:diff] + if system("diffoscope", old_file, new_file).nil? + alert_error "error: could not find `diffoscope` executable" + end + else + say "Pass --diff for more details (requires diffoscope to be installed)." + end + + terminate_interaction 1 + end + end + + def prep_dirs + rebuild_dir = Dir.mktmpdir("gem_rebuild") + old_dir = File.join(rebuild_dir, "old") + new_dir = File.join(rebuild_dir, "new") + + FileUtils.mkdir_p(old_dir) + FileUtils.mkdir_p(new_dir) + + [old_dir, new_dir] + end + + def get_gem_name_and_version + args = options[:args] || [] + if args.length == 2 + gem_name, gem_version = args + elsif args.length > 2 + raise Gem::CommandLineError, "Too many arguments" + else + raise Gem::CommandLineError, "Expected GEM_NAME and GEM_VERSION arguments (gem rebuild GEM_NAME GEM_VERSION)" + end + + [gem_name, gem_version] + end + + def build_gem(gem_name, source_date_epoch, output_file) + gemspec = options[:gemspec_file] || find_gemspec("#{gem_name}.gemspec") + + if gemspec + build_package(gemspec, source_date_epoch, output_file) + else + alert_error error_message(gem_name) + terminate_interaction(1) + end + end + + def build_package(gemspec, source_date_epoch, output_file) + with_source_date_epoch(source_date_epoch) do + spec = Gem::Specification.load(gemspec) + if spec + Gem::Package.build( + spec, + options[:force], + options[:strict], + output_file + ) + else + alert_error "Error loading gemspec. Aborting." + terminate_interaction 1 + end + end + end + + def with_source_date_epoch(source_date_epoch) + old_sde = ENV["SOURCE_DATE_EPOCH"] + ENV["SOURCE_DATE_EPOCH"] = source_date_epoch.to_s + + yield + ensure + ENV["SOURCE_DATE_EPOCH"] = old_sde + end + + def error_message(gem_name) + if gem_name + "Couldn't find a gemspec file matching '#{gem_name}' in #{Dir.pwd}" + else + "Couldn't find a gemspec file in #{Dir.pwd}" + end + end + + def download_gem(gem_name, gem_version, old_file) + # This code was based loosely off the `gem fetch` command. + version = "= #{gem_version}" + dep = Gem::Dependency.new gem_name, version + + specs_and_sources, errors = + Gem::SpecFetcher.fetcher.spec_for_dependency dep + + # There should never be more than one item in specs_and_sources, + # since we search for an exact version. + spec, source = specs_and_sources[0] + + if spec.nil? + show_lookup_failure gem_name, version, errors, options[:domain] + terminate_interaction 1 + end + + download_path = source.download spec + + FileUtils.move(download_path, old_file) + + say "Downloaded #{gem_name} version #{gem_version} as #{old_file}." + end + + def rubygems_version(gem_file) + Gem::Package.new(gem_file).spec.rubygems_version + end +end diff --git a/lib/rubygems/config_file.rb b/lib/rubygems/config_file.rb index 6f83fe2c79d0b5..7874ad0dc9a1ca 100644 --- a/lib/rubygems/config_file.rb +++ b/lib/rubygems/config_file.rb @@ -210,22 +210,34 @@ def initialize(args) @hash = @hash.merge environment_config end + @hash.transform_keys! do |k| + # gemhome and gempath are not working with symbol keys + if %w[backtrace bulk_threshold verbose update_sources cert_expiration_length_days + install_extension_in_lib ipv4_fallback_enabled sources disable_default_gem_server + ssl_verify_mode ssl_ca_cert ssl_client_cert].include?(k) + k.to_sym + else + k + end + end + # HACK: these override command-line args, which is bad @backtrace = @hash[:backtrace] if @hash.key? :backtrace @bulk_threshold = @hash[:bulk_threshold] if @hash.key? :bulk_threshold - @home = @hash[:gemhome] if @hash.key? :gemhome - @path = @hash[:gempath] if @hash.key? :gempath - @update_sources = @hash[:update_sources] if @hash.key? :update_sources @verbose = @hash[:verbose] if @hash.key? :verbose - @disable_default_gem_server = @hash[:disable_default_gem_server] if @hash.key? :disable_default_gem_server - @sources = @hash[:sources] if @hash.key? :sources + @update_sources = @hash[:update_sources] if @hash.key? :update_sources + # TODO: We should handle concurrent_downloads same as other options @cert_expiration_length_days = @hash[:cert_expiration_length_days] if @hash.key? :cert_expiration_length_days @install_extension_in_lib = @hash[:install_extension_in_lib] if @hash.key? :install_extension_in_lib @ipv4_fallback_enabled = @hash[:ipv4_fallback_enabled] if @hash.key? :ipv4_fallback_enabled - @ssl_verify_mode = @hash[:ssl_verify_mode] if @hash.key? :ssl_verify_mode - @ssl_ca_cert = @hash[:ssl_ca_cert] if @hash.key? :ssl_ca_cert - @ssl_client_cert = @hash[:ssl_client_cert] if @hash.key? :ssl_client_cert + @home = @hash[:gemhome] if @hash.key? :gemhome + @path = @hash[:gempath] if @hash.key? :gempath + @sources = @hash[:sources] if @hash.key? :sources + @disable_default_gem_server = @hash[:disable_default_gem_server] if @hash.key? :disable_default_gem_server + @ssl_verify_mode = @hash[:ssl_verify_mode] if @hash.key? :ssl_verify_mode + @ssl_ca_cert = @hash[:ssl_ca_cert] if @hash.key? :ssl_ca_cert + @ssl_client_cert = @hash[:ssl_client_cert] if @hash.key? :ssl_client_cert @api_keys = nil @rubygems_api_key = nil diff --git a/lib/rubygems/exceptions.rb b/lib/rubygems/exceptions.rb index 65caaab8b1f3c0..0308b4687f9f49 100644 --- a/lib/rubygems/exceptions.rb +++ b/lib/rubygems/exceptions.rb @@ -292,9 +292,3 @@ def version @dependency.requirement end end - -## -# Backwards compatible typo'd exception class for early RubyGems 2.0.x - -Gem::UnsatisfiableDepedencyError = Gem::UnsatisfiableDependencyError # :nodoc: -Gem.deprecate_constant :UnsatisfiableDepedencyError diff --git a/lib/rubygems/gemspec_helpers.rb b/lib/rubygems/gemspec_helpers.rb new file mode 100644 index 00000000000000..2b20fcafa12120 --- /dev/null +++ b/lib/rubygems/gemspec_helpers.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +require_relative "../rubygems" + +## +# Mixin methods for commands that work with gemspecs. + +module Gem::GemspecHelpers + def find_gemspec(glob = "*.gemspec") + gemspecs = Dir.glob(glob).sort + + if gemspecs.size > 1 + alert_error "Multiple gemspecs found: #{gemspecs}, please specify one" + terminate_interaction(1) + end + + gemspecs.first + end +end diff --git a/lib/rubygems/package.rb b/lib/rubygems/package.rb index 387e40ffd753bb..1d5d7642376a55 100644 --- a/lib/rubygems/package.rb +++ b/lib/rubygems/package.rb @@ -59,7 +59,7 @@ class FormatError < Error def initialize(message, source = nil) if source - @path = source.path + @path = source.is_a?(String) ? source : source.path message += " in #{path}" if path end @@ -454,7 +454,7 @@ def extract_tar_gz(io, destination_dir, pattern = "*") # :nodoc: if entry.file? File.open(destination, "wb") {|out| copy_stream(entry, out) } - FileUtils.chmod file_mode(entry.header.mode), destination + FileUtils.chmod file_mode(entry.header.mode) & ~File.umask, destination end verbose destination diff --git a/lib/rubygems/resolver/spec_specification.rb b/lib/rubygems/resolver/spec_specification.rb index 79a34d8063f6fa..00ef9fdba05bc0 100644 --- a/lib/rubygems/resolver/spec_specification.rb +++ b/lib/rubygems/resolver/spec_specification.rb @@ -66,4 +66,11 @@ def platform def version spec.version end + + ## + # The hash value for this specification. + + def hash + spec.hash + end end diff --git a/lib/rubygems/vendor/net-http/lib/net/http.rb b/lib/rubygems/vendor/net-http/lib/net/http.rb index 3b931c25f54581..7b15c3cf548156 100644 --- a/lib/rubygems/vendor/net-http/lib/net/http.rb +++ b/lib/rubygems/vendor/net-http/lib/net/http.rb @@ -106,7 +106,7 @@ class HTTPHeaderSyntaxError < StandardError; end # It consists of some or all of: scheme, hostname, path, query, and fragment; # see {URI syntax}[https://en.wikipedia.org/wiki/Uniform_Resource_Identifier#Syntax]. # - # A Ruby {Gem::URI::Generic}[https://docs.ruby-lang.org/en/master/Gem/URI/Generic.html] object + # A Ruby {Gem::URI::Generic}[rdoc-ref:Gem::URI::Generic] object # represents an internet URI. # It provides, among others, methods # +scheme+, +hostname+, +path+, +query+, and +fragment+. diff --git a/lib/rubygems/vendor/resolv/lib/resolv.rb b/lib/rubygems/vendor/resolv/lib/resolv.rb index 8e31eb1bee97df..ac0ba0b3136a79 100644 --- a/lib/rubygems/vendor/resolv/lib/resolv.rb +++ b/lib/rubygems/vendor/resolv/lib/resolv.rb @@ -37,7 +37,7 @@ class Gem::Resolv - VERSION = "0.3.0" + VERSION = "0.4.0" ## # Looks up the first IP address for +name+. @@ -194,17 +194,10 @@ def lazy_initialize # :nodoc: File.open(@filename, 'rb') {|f| f.each {|line| line.sub!(/#.*/, '') - addr, hostname, *aliases = line.split(/\s+/) + addr, *hostnames = line.split(/\s+/) next unless addr - @addr2name[addr] = [] unless @addr2name.include? addr - @addr2name[addr] << hostname - @addr2name[addr].concat(aliases) - @name2addr[hostname] = [] unless @name2addr.include? hostname - @name2addr[hostname] << addr - aliases.each {|n| - @name2addr[n] = [] unless @name2addr.include? n - @name2addr[n] << addr - } + (@addr2name[addr] ||= []).concat(hostnames) + hostnames.each {|hostname| (@name2addr[hostname] ||= []) << addr} } } @name2addr.each {|name, arr| arr.reverse!} @@ -2544,8 +2537,70 @@ class ANY < Query TypeValue = 255 # :nodoc: end + ## + # CAA resource record defined in RFC 8659 + # + # These records identify certificate authority allowed to issue + # certificates for the given domain. + + class CAA < Resource + TypeValue = 257 + + ## + # Creates a new CAA for +flags+, +tag+ and +value+. + + def initialize(flags, tag, value) + unless (0..255) === flags + raise ArgumentError.new('flags must be an Integer between 0 and 255') + end + unless (1..15) === tag.bytesize + raise ArgumentError.new('length of tag must be between 1 and 15') + end + + @flags = flags + @tag = tag + @value = value + end + + ## + # Flags for this proprty: + # - Bit 0 : 0 = not critical, 1 = critical + + attr_reader :flags + + ## + # Property tag ("issue", "issuewild", "iodef"...). + + attr_reader :tag + + ## + # Property value. + + attr_reader :value + + ## + # Whether the critical flag is set on this property. + + def critical? + flags & 0x80 != 0 + end + + def encode_rdata(msg) # :nodoc: + msg.put_pack('C', @flags) + msg.put_string(@tag) + msg.put_bytes(@value) + end + + def self.decode_rdata(msg) # :nodoc: + flags, = msg.get_unpack('C') + tag = msg.get_string + value = msg.get_bytes + self.new flags, tag, value + end + end + ClassInsensitiveTypes = [ # :nodoc: - NS, CNAME, SOA, PTR, HINFO, MINFO, MX, TXT, LOC, ANY + NS, CNAME, SOA, PTR, HINFO, MINFO, MX, TXT, LOC, ANY, CAA ] ## diff --git a/lib/tmpdir.rb b/lib/tmpdir.rb index 65c86e9b90e4ef..fe3e0e19d1f00c 100644 --- a/lib/tmpdir.rb +++ b/lib/tmpdir.rb @@ -107,9 +107,10 @@ def self.mktmpdir(prefix_suffix=nil, *rest, **options) yield path.dup ensure unless base - stat = File.stat(File.dirname(path)) + base = File.dirname(path) + stat = File.stat(base) if stat.world_writable? and !stat.sticky? - raise ArgumentError, "parent directory is world writable but not sticky" + raise ArgumentError, "parent directory is world writable but not sticky: #{base}" end end FileUtils.remove_entry path diff --git a/lib/uri.rb b/lib/uri.rb index cd8083b65aa071..dfdb052a79057c 100644 --- a/lib/uri.rb +++ b/lib/uri.rb @@ -1,6 +1,6 @@ # frozen_string_literal: false # URI is a module providing classes to handle Uniform Resource Identifiers -# (RFC2396[https://datatracker.ietf.org/doc/html/rfc2396]). +# (RFC2396[https://www.rfc-editor.org/rfc/rfc2396]). # # == Features # @@ -47,14 +47,14 @@ # A good place to view an RFC spec is http://www.ietf.org/rfc.html. # # Here is a list of all related RFC's: -# - RFC822[https://datatracker.ietf.org/doc/html/rfc822] -# - RFC1738[https://datatracker.ietf.org/doc/html/rfc1738] -# - RFC2255[https://datatracker.ietf.org/doc/html/rfc2255] -# - RFC2368[https://datatracker.ietf.org/doc/html/rfc2368] -# - RFC2373[https://datatracker.ietf.org/doc/html/rfc2373] -# - RFC2396[https://datatracker.ietf.org/doc/html/rfc2396] -# - RFC2732[https://datatracker.ietf.org/doc/html/rfc2732] -# - RFC3986[https://datatracker.ietf.org/doc/html/rfc3986] +# - RFC822[https://www.rfc-editor.org/rfc/rfc822] +# - RFC1738[https://www.rfc-editor.org/rfc/rfc1738] +# - RFC2255[https://www.rfc-editor.org/rfc/rfc2255] +# - RFC2368[https://www.rfc-editor.org/rfc/rfc2368] +# - RFC2373[https://www.rfc-editor.org/rfc/rfc2373] +# - RFC2396[https://www.rfc-editor.org/rfc/rfc2396] +# - RFC2732[https://www.rfc-editor.org/rfc/rfc2732] +# - RFC3986[https://www.rfc-editor.org/rfc/rfc3986] # # == Class tree # diff --git a/lib/uri/generic.rb b/lib/uri/generic.rb index baa6a4c34c3de3..bdd366661ecfd4 100644 --- a/lib/uri/generic.rb +++ b/lib/uri/generic.rb @@ -945,7 +945,7 @@ def fragment=(v) # == Description # # URI has components listed in order of decreasing significance from left to right, - # see RFC3986 https://tools.ietf.org/html/rfc3986 1.2.3. + # see RFC3986 https://www.rfc-editor.org/rfc/rfc3986 1.2.3. # # == Usage # diff --git a/lib/uri/http.rb b/lib/uri/http.rb index 306daf19656d93..900b132c8c1cbf 100644 --- a/lib/uri/http.rb +++ b/lib/uri/http.rb @@ -85,7 +85,7 @@ def request_uri # == Description # # Returns the authority for an HTTP uri, as defined in - # https://datatracker.ietf.org/doc/html/rfc3986/#section-3.2. + # https://www.rfc-editor.org/rfc/rfc3986#section-3.2. # # # Example: @@ -106,7 +106,7 @@ def authority # == Description # # Returns the origin for an HTTP uri, as defined in - # https://datatracker.ietf.org/doc/html/rfc6454. + # https://www.rfc-editor.org/rfc/rfc6454. # # # Example: diff --git a/mini_builtin.c b/mini_builtin.c index a93a5ebddb4106..dce822a86c176d 100644 --- a/mini_builtin.c +++ b/mini_builtin.c @@ -28,16 +28,16 @@ builtin_iseq_load(const char *feature_name, const struct rb_builtin_function *ta } vm->builtin_function_table = table; static const rb_compile_option_t optimization = { - TRUE, /* unsigned int inline_const_cache; */ - TRUE, /* unsigned int peephole_optimization; */ - FALSE,/* unsigned int tailcall_optimization; */ - TRUE, /* unsigned int specialized_instruction; */ - TRUE, /* unsigned int operands_unification; */ - TRUE, /* unsigned int instructions_unification; */ - TRUE, /* unsigned int frozen_string_literal; */ - FALSE, /* unsigned int debug_frozen_string_literal; */ - FALSE, /* unsigned int coverage_enabled; */ - 0, /* int debug_level; */ + .inline_const_cache = TRUE, + .peephole_optimization = TRUE, + .tailcall_optimization = FALSE, + .specialized_instruction = TRUE, + .operands_unification = TRUE, + .instructions_unification = TRUE, + .frozen_string_literal = TRUE, + .debug_frozen_string_literal = FALSE, + .coverage_enabled = FALSE, + .debug_level = 0, }; const rb_iseq_t *iseq = rb_iseq_new_with_opt(&ast->body, name_str, name_str, Qnil, 0, NULL, 0, ISEQ_TYPE_TOP, &optimization); GET_VM()->builtin_function_table = NULL; diff --git a/misc/lldb_rb/utils.py b/misc/lldb_rb/utils.py index a321426234befd..86b5bdda2d8a13 100644 --- a/misc/lldb_rb/utils.py +++ b/misc/lldb_rb/utils.py @@ -119,6 +119,10 @@ def inspect(self, val): self.result.write('T_STRING: %s' % flaginfo) tRString = self.target.FindFirstType("struct RString").GetPointerType() + chilled = self.ruby_globals["RUBY_FL_USER3"] + if (rval.flags & chilled) != 0: + self.result.write("[CHILLED] ") + rb_enc_mask = self.ruby_globals["RUBY_ENCODING_MASK"] rb_enc_shift = self.ruby_globals["RUBY_ENCODING_SHIFT"] encidx = ((rval.flags & rb_enc_mask) >> rb_enc_shift) @@ -367,8 +371,6 @@ def inspect(self, val): self._append_expression("*(struct RNode_MATCH2 *) %0#x" % val.GetValueAsUnsigned()) elif nd_type == self.ruby_globals["NODE_MATCH3"]: self._append_expression("*(struct RNode_MATCH3 *) %0#x" % val.GetValueAsUnsigned()) - elif nd_type == self.ruby_globals["NODE_LIT"]: - self._append_expression("*(struct RNode_LIT *) %0#x" % val.GetValueAsUnsigned()) elif nd_type == self.ruby_globals["NODE_STR"]: self._append_expression("*(struct RNode_STR *) %0#x" % val.GetValueAsUnsigned()) elif nd_type == self.ruby_globals["NODE_DSTR"]: diff --git a/misc/yjit_perf.py b/misc/yjit_perf.py old mode 100644 new mode 100755 index 44c232254e15fd..61434e5eb4d3e3 --- a/misc/yjit_perf.py +++ b/misc/yjit_perf.py @@ -1,12 +1,9 @@ +#!/usr/bin/env python3 import os import sys from collections import Counter, defaultdict import os.path -sys.path.append(os.environ['PERF_EXEC_PATH'] + '/scripts/python/Perf-Trace-Util/lib/Perf/Trace') -from perf_trace_context import * -from EventClass import * - # Aggregating cycles per symbol and dso total_cycles = 0 category_cycles = Counter() @@ -57,11 +54,10 @@ def categorize_symbol(dso, symbol): def process_event(event): global total_cycles, category_cycles, detailed_category_cycles, categories - sample = event["sample"] full_dso = event.get("dso", "Unknown_dso") dso = os.path.basename(full_dso) symbol = event.get("symbol", "[unknown]") - cycles = sample["period"] + cycles = event["sample"]["period"] total_cycles += cycles category = categorize_symbol(dso, symbol) @@ -94,3 +90,27 @@ def trace_end(): for (dso, symbol), cycles in symbols.most_common(): symbol_ratio = (cycles / category_total) * 100 print("{:<20} {:<50} {:>20.2f}% {:>15}".format(dso, truncate_symbol(symbol), symbol_ratio, cycles)) + +# There are two ways to use this script: +# 1) perf script -s misc/yjit_perf.py -- native interface +# 2) perf script > perf.txt && misc/yjit_perf.py perf.txt -- hack, which doesn't require perf with Python support +# +# In both cases, __name__ is "__main__". The following code implements (2) when sys.argv is 2. +if __name__ == "__main__" and len(sys.argv) == 2: + if len(sys.argv) != 2: + print("Usage: yjit_perf.py ") + sys.exit(1) + + with open(sys.argv[1], "r") as file: + for line in file: + # [Example] + # ruby 78207 3482.848465: 1212775 cpu_core/cycles:P/: 5c0333f682e1 [JIT] getlocal_WC_0+0x0 (/tmp/perf-78207.map) + row = line.split(maxsplit=6) + + period = row[3] # "1212775" + symbol, dso = row[6].split(" (") # "[JIT] getlocal_WC_0+0x0", "/tmp/perf-78207.map)\n" + symbol = symbol.split("+")[0] # "[JIT] getlocal_WC_0" + dso = dso.split(")")[0] # "/tmp/perf-78207.map" + + process_event({"dso": dso, "symbol": symbol, "sample": {"period": int(period)}}) + trace_end() diff --git a/node.c b/node.c index 8011d61255b93d..79520f0a1e942c 100644 --- a/node.c +++ b/node.c @@ -15,7 +15,6 @@ #include "node.h" #include "rubyparser.h" #include "internal/parse.h" -#define T_NODE 0x1b #else @@ -39,7 +38,7 @@ init_node_buffer_elem(node_buffer_elem_t *nbe, size_t allocated, void *xmalloc(s } static void -init_node_buffer_list(node_buffer_list_t * nb, node_buffer_elem_t *head, void *xmalloc(size_t)) +init_node_buffer_list(node_buffer_list_t *nb, node_buffer_elem_t *head, void *xmalloc(size_t)) { init_node_buffer_elem(head, NODE_BUF_DEFAULT_SIZE, xmalloc); nb->head = nb->last = head; @@ -48,7 +47,6 @@ init_node_buffer_list(node_buffer_list_t * nb, node_buffer_elem_t *head, void *x #ifdef UNIVERSAL_PARSER #define ruby_xmalloc config->malloc -#define Qnil config->qnil #endif #ifdef UNIVERSAL_PARSER @@ -60,16 +58,15 @@ rb_node_buffer_new(void) #endif { const size_t bucket_size = offsetof(node_buffer_elem_t, buf) + NODE_BUF_DEFAULT_SIZE; - const size_t alloc_size = sizeof(node_buffer_t) + (bucket_size * 2); + const size_t alloc_size = sizeof(node_buffer_t) + (bucket_size); STATIC_ASSERT( integer_overflow, offsetof(node_buffer_elem_t, buf) + NODE_BUF_DEFAULT_SIZE - > sizeof(node_buffer_t) + 2 * sizeof(node_buffer_elem_t)); + > sizeof(node_buffer_t) + sizeof(node_buffer_elem_t)); node_buffer_t *nb = ruby_xmalloc(alloc_size); - init_node_buffer_list(&nb->unmarkable, (node_buffer_elem_t*)&nb[1], ruby_xmalloc); - init_node_buffer_list(&nb->markable, (node_buffer_elem_t*)((size_t)nb->unmarkable.head + bucket_size), ruby_xmalloc); + init_node_buffer_list(&nb->buffer_list, (node_buffer_elem_t*)&nb[1], ruby_xmalloc); nb->local_tables = 0; - nb->tokens = Qnil; + nb->tokens = 0; #ifdef UNIVERSAL_PARSER nb->config = config; #endif @@ -81,34 +78,18 @@ rb_node_buffer_new(void) #define ruby_xmalloc ast->node_buffer->config->malloc #undef xfree #define xfree ast->node_buffer->config->free -#define rb_ident_hash_new ast->node_buffer->config->ident_hash_new #define rb_xmalloc_mul_add ast->node_buffer->config->xmalloc_mul_add #define ruby_xrealloc(var,size) (ast->node_buffer->config->realloc_n((void *)var, 1, size)) -#define rb_gc_mark ast->node_buffer->config->gc_mark -#define rb_gc_location ast->node_buffer->config->gc_location #define rb_gc_mark_and_move ast->node_buffer->config->gc_mark_and_move -#undef Qnil -#define Qnil ast->node_buffer->config->qnil -#define Qtrue ast->node_buffer->config->qtrue -#define NIL_P ast->node_buffer->config->nil_p -#define rb_hash_aset ast->node_buffer->config->hash_aset -#define rb_hash_delete ast->node_buffer->config->hash_delete -#define RB_OBJ_WRITE(old, slot, young) ast->node_buffer->config->obj_write((VALUE)(old), (VALUE *)(slot), (VALUE)(young)) #endif typedef void node_itr_t(rb_ast_t *ast, void *ctx, NODE *node); static void iterate_node_values(rb_ast_t *ast, node_buffer_list_t *nb, node_itr_t * func, void *ctx); -/* Setup NODE structure. - * NODE is not an object managed by GC, but it imitates an object - * so that it can work with `RB_TYPE_P(obj, T_NODE)`. - * This dirty hack is needed because Ripper jumbles NODEs and other type - * objects. - */ void rb_node_init(NODE *n, enum node_type type) { - RNODE(n)->flags = T_NODE; + RNODE(n)->flags = 0; nd_init_type(RNODE(n), type); RNODE(n)->nd_loc.beg_pos.lineno = 0; RNODE(n)->nd_loc.beg_pos.column = 0; @@ -176,6 +157,24 @@ parser_string_free(rb_ast_t *ast, rb_parser_string_t *str) xfree(str); } +static void +parser_ast_token_free(rb_ast_t *ast, rb_parser_ast_token_t *token) +{ + if (!token) return; + parser_string_free(ast, token->str); + xfree(token); +} + +static void +parser_tokens_free(rb_ast_t *ast, rb_parser_ary_t *tokens) +{ + for (long i = 0; i < tokens->len; i++) { + parser_ast_token_free(ast, tokens->data[i]); + } + xfree(tokens->data); + xfree(tokens); +} + static void free_ast_value(rb_ast_t *ast, void *ctx, NODE *node) { @@ -228,9 +227,11 @@ free_ast_value(rb_ast_t *ast, void *ctx, NODE *node) static void rb_node_buffer_free(rb_ast_t *ast, node_buffer_t *nb) { - iterate_node_values(ast, &nb->unmarkable, free_ast_value, NULL); - node_buffer_list_free(ast, &nb->unmarkable); - node_buffer_list_free(ast, &nb->markable); + if (ast->node_buffer && ast->node_buffer->tokens) { + parser_tokens_free(ast, ast->node_buffer->tokens); + } + iterate_node_values(ast, &nb->buffer_list, free_ast_value, NULL); + node_buffer_list_free(ast, &nb->buffer_list); struct rb_ast_local_table_link *local_table = nb->local_tables; while (local_table) { struct rb_ast_local_table_link *next_table = local_table->next; @@ -267,39 +268,14 @@ ast_newnode_in_bucket(rb_ast_t *ast, node_buffer_list_t *nb, size_t size, size_t return ptr; } -RBIMPL_ATTR_PURE() -static bool -nodetype_markable_p(enum node_type type) -{ - switch (type) { - case NODE_LIT: - return true; - default: - return false; - } -} - NODE * rb_ast_newnode(rb_ast_t *ast, enum node_type type, size_t size, size_t alignment) { node_buffer_t *nb = ast->node_buffer; - node_buffer_list_t *bucket = - (nodetype_markable_p(type) ? &nb->markable : &nb->unmarkable); + node_buffer_list_t *bucket = &nb->buffer_list; return ast_newnode_in_bucket(ast, bucket, size, alignment); } -#if RUBY_DEBUG -void -rb_ast_node_type_change(NODE *n, enum node_type type) -{ - enum node_type old_type = nd_type(n); - if (nodetype_markable_p(old_type) != nodetype_markable_p(type)) { - rb_bug("node type changed: %s -> %s", - ruby_node_name(old_type), ruby_node_name(type)); - } -} -#endif - rb_ast_id_table_t * rb_ast_new_local_table(rb_ast_t *ast, int size) { @@ -368,31 +344,10 @@ iterate_node_values(rb_ast_t *ast, node_buffer_list_t *nb, node_itr_t * func, vo } } -static void -mark_and_move_ast_value(rb_ast_t *ast, void *ctx, NODE *node) -{ -#ifdef UNIVERSAL_PARSER - bug_report_func rb_bug = ast->node_buffer->config->bug; -#endif - - switch (nd_type(node)) { - case NODE_LIT: - rb_gc_mark_and_move(&RNODE_LIT(node)->nd_lit); - break; - default: - rb_bug("unreachable node %s", ruby_node_name(nd_type(node))); - } -} - void rb_ast_mark_and_move(rb_ast_t *ast, bool reference_updating) { if (ast->node_buffer) { - rb_gc_mark_and_move(&ast->node_buffer->tokens); - - node_buffer_t *nb = ast->node_buffer; - iterate_node_values(ast, &nb->markable, mark_and_move_ast_value, NULL); - if (ast->body.script_lines) rb_gc_mark_and_move(&ast->body.script_lines); } } @@ -426,8 +381,7 @@ rb_ast_memsize(const rb_ast_t *ast) if (nb) { size += sizeof(node_buffer_t); - size += buffer_list_size(&nb->unmarkable); - size += buffer_list_size(&nb->markable); + size += buffer_list_size(&nb->buffer_list); } return size; } @@ -438,23 +392,8 @@ rb_ast_dispose(rb_ast_t *ast) rb_ast_free(ast); } -VALUE -rb_ast_tokens(rb_ast_t *ast) -{ - return ast->node_buffer->tokens; -} - -void -rb_ast_set_tokens(rb_ast_t *ast, VALUE tokens) -{ - RB_OBJ_WRITE(ast, &ast->node_buffer->tokens, tokens); -} - VALUE rb_node_set_type(NODE *n, enum node_type t) { -#if RUBY_DEBUG - rb_ast_node_type_change(n, t); -#endif return nd_init_type(n, t); } diff --git a/node.h b/node.h index 2e8868428aece1..d5522c82eca355 100644 --- a/node.h +++ b/node.h @@ -15,8 +15,7 @@ #include "rubyparser.h" #include "ruby/backward/2/attributes.h" -typedef void (*bug_report_func)(const char *fmt, ...); - +typedef void (*bug_report_func)(const char *fmt, ...) RUBYPARSER_ATTRIBUTE_FORMAT(1, 2); typedef struct node_buffer_elem_struct { struct node_buffer_elem_struct *next; long len; /* Length of nodes */ @@ -32,15 +31,14 @@ typedef struct { } node_buffer_list_t; struct node_buffer_struct { - node_buffer_list_t unmarkable; - node_buffer_list_t markable; + node_buffer_list_t buffer_list; struct rb_ast_local_table_link *local_tables; // - id (sequence number) // - token_type // - text of token // - location info // Array, whose entry is array - VALUE tokens; + rb_parser_ary_t *tokens; #ifdef UNIVERSAL_PARSER const rb_parser_config_t *config; #endif @@ -55,17 +53,12 @@ rb_ast_t *rb_ast_new(void); #endif size_t rb_ast_memsize(const rb_ast_t*); void rb_ast_dispose(rb_ast_t*); -VALUE rb_ast_tokens(rb_ast_t *ast); -#if RUBY_DEBUG -void rb_ast_node_type_change(NODE *n, enum node_type type); -#endif const char *ruby_node_name(int node); void rb_node_init(NODE *n, enum node_type type); void rb_ast_mark_and_move(rb_ast_t *ast, bool reference_updating); void rb_ast_update_references(rb_ast_t*); void rb_ast_free(rb_ast_t*); -void rb_ast_set_tokens(rb_ast_t*, VALUE); NODE *rb_ast_newnode(rb_ast_t*, enum node_type type, size_t size, size_t alignment); void rb_ast_delete_node(rb_ast_t*, NODE *n); rb_ast_id_table_t *rb_ast_new_local_table(rb_ast_t*, int); @@ -76,10 +69,6 @@ VALUE rb_parser_dump_tree(const NODE *node, int comment); const struct kwtable *rb_reserved_word(const char *, unsigned int); struct parser_params; -void *rb_parser_malloc(struct parser_params *, size_t); -void *rb_parser_realloc(struct parser_params *, void *, size_t); -void *rb_parser_calloc(struct parser_params *, size_t, size_t); -void rb_parser_free(struct parser_params *, void *); PRINTF_ARGS(void rb_parser_printf(struct parser_params *parser, const char *fmt, ...), 2, 3); VALUE rb_node_set_type(NODE *n, enum node_type t); diff --git a/node_dump.c b/node_dump.c index c4195d571ce1f4..37abea8441b4ea 100644 --- a/node_dump.c +++ b/node_dump.c @@ -60,6 +60,22 @@ field_flag; /* should be optimized away */ \ reset, field_flag = 0) +#define A_SHAREABILITY(shareability) \ + switch (shareability) { \ + case rb_parser_shareable_none: \ + rb_str_cat_cstr(buf, "none"); \ + break; \ + case rb_parser_shareable_literal: \ + rb_str_cat_cstr(buf, "literal"); \ + break; \ + case rb_parser_shareable_copy: \ + rb_str_cat_cstr(buf, "experimental_copy"); \ + break; \ + case rb_parser_shareable_everything: \ + rb_str_cat_cstr(buf, "experimental_everything"); \ + break; \ + } + #define SIMPLE_FIELD1(name, ann) SIMPLE_FIELD(FIELD_NAME_LEN(name, ann), FIELD_NAME_DESC(name, ann)) #define F_CUSTOM1(name, ann) SIMPLE_FIELD1(#name, ann) #define F_ID(name, type, ann) SIMPLE_FIELD1(#name, ann) A_ID(type(node)->name) @@ -68,6 +84,7 @@ #define F_LIT(name, type, ann) SIMPLE_FIELD1(#name, ann) A_LIT(type(node)->name) #define F_VALUE(name, val, ann) SIMPLE_FIELD1(#name, ann) A_LIT(val) #define F_MSG(name, ann, desc) SIMPLE_FIELD1(#name, ann) A(desc) +#define F_SHAREABILITY(name, type, ann) SIMPLE_FIELD1(#name, ann) A_SHAREABILITY(type(node)->name) #define F_NODE(name, type, ann) \ COMPOUND_FIELD1(#name, ann) {dump_node(buf, indent, comment, RNODE(type(node)->name));} @@ -463,6 +480,7 @@ dump_node(VALUE buf, VALUE indent, int comment, const NODE * node) F_MSG(nd_vid, "constant", "0 (see extension field)"); F_NODE(nd_else, RNODE_CDECL, "extension"); } + F_SHAREABILITY(shareability, RNODE_CDECL, "shareability"); LAST_NODE; F_NODE(nd_value, RNODE_CDECL, "rvalue"); return; @@ -513,6 +531,7 @@ dump_node(VALUE buf, VALUE indent, int comment, const NODE * node) ANN("example: A::B ||= 1"); F_NODE(nd_head, RNODE_OP_CDECL, "constant"); F_ID(nd_aid, RNODE_OP_CDECL, "operator"); + F_SHAREABILITY(shareability, RNODE_OP_CDECL, "shareability"); LAST_NODE; F_NODE(nd_value, RNODE_OP_CDECL, "rvalue"); return; @@ -705,12 +724,6 @@ dump_node(VALUE buf, VALUE indent, int comment, const NODE * node) F_NODE(nd_value, RNODE_MATCH3, "regexp (argument)"); return; - case NODE_LIT: - ANN("literal"); - ANN("format: [nd_lit]"); - ANN("example: :sym, /foo/"); - F_LIT(nd_lit, RNODE_LIT, "literal"); - return; case NODE_STR: ANN("string literal"); ANN("format: [nd_lit]"); diff --git a/numeric.c b/numeric.c index 4cc8777bc7be01..f613d4120d7e70 100644 --- a/numeric.c +++ b/numeric.c @@ -5169,7 +5169,7 @@ fix_rshift(long val, unsigned long i) * */ -static VALUE +VALUE rb_int_rshift(VALUE x, VALUE y) { if (FIXNUM_P(x)) { @@ -5453,11 +5453,12 @@ rb_fix_digits(VALUE fix, long base) return rb_ary_new_from_args(1, INT2FIX(0)); digits = rb_ary_new(); - while (x > 0) { + while (x >= base) { long q = x % base; rb_ary_push(digits, LONG2NUM(q)); x /= base; } + rb_ary_push(digits, LONG2NUM(x)); return digits; } @@ -6251,19 +6252,25 @@ Init_Numeric(void) rb_define_method(rb_cInteger, "digits", rb_int_digits, -1); - rb_fix_to_s_static[0] = rb_fstring_literal("0"); - rb_fix_to_s_static[1] = rb_fstring_literal("1"); - rb_fix_to_s_static[2] = rb_fstring_literal("2"); - rb_fix_to_s_static[3] = rb_fstring_literal("3"); - rb_fix_to_s_static[4] = rb_fstring_literal("4"); - rb_fix_to_s_static[5] = rb_fstring_literal("5"); - rb_fix_to_s_static[6] = rb_fstring_literal("6"); - rb_fix_to_s_static[7] = rb_fstring_literal("7"); - rb_fix_to_s_static[8] = rb_fstring_literal("8"); - rb_fix_to_s_static[9] = rb_fstring_literal("9"); - for(int i = 0; i < 10; i++) { - rb_vm_register_global_object(rb_fix_to_s_static[i]); - } +#define fix_to_s_static(n) do { \ + VALUE lit = rb_fstring_literal(#n); \ + rb_fix_to_s_static[n] = lit; \ + rb_vm_register_global_object(lit); \ + RB_GC_GUARD(lit); \ + } while (0) + + fix_to_s_static(0); + fix_to_s_static(1); + fix_to_s_static(2); + fix_to_s_static(3); + fix_to_s_static(4); + fix_to_s_static(5); + fix_to_s_static(6); + fix_to_s_static(7); + fix_to_s_static(8); + fix_to_s_static(9); + +#undef fix_to_s_static rb_cFloat = rb_define_class("Float", rb_cNumeric); diff --git a/object.c b/object.c index 121fe15c9231c3..cf45a8399149db 100644 --- a/object.c +++ b/object.c @@ -138,9 +138,8 @@ rb_class_allocate_instance(VALUE klass) RUBY_ASSERT(rb_shape_get_shape(obj)->type == SHAPE_ROOT); - // Set the shape to the specific T_OBJECT shape which is always - // SIZE_POOL_COUNT away from the root shape. - ROBJECT_SET_SHAPE_ID(obj, ROBJECT_SHAPE_ID(obj) + SIZE_POOL_COUNT); + // Set the shape to the specific T_OBJECT shape. + ROBJECT_SET_SHAPE_ID(obj, (shape_id_t)(rb_gc_size_pool_id_for_size(size) + FIRST_T_OBJECT_SHAPE_ID)); #if RUBY_DEBUG RUBY_ASSERT(!rb_shape_obj_too_complex(obj)); @@ -401,16 +400,8 @@ init_copy(VALUE dest, VALUE obj) RBASIC(dest)->flags &= ~(T_MASK|FL_EXIVAR); // Copies the shape id from obj to dest RBASIC(dest)->flags |= RBASIC(obj)->flags & (T_MASK|FL_EXIVAR); -#if USE_MMTK - if (!rb_mmtk_enabled_p()) { -#endif - rb_copy_wb_protected_attribute(dest, obj); -#if USE_MMTK - } -#endif + rb_gc_copy_attributes(dest, obj); rb_copy_generic_ivar(dest, obj); - rb_gc_copy_finalizer(dest, obj); - if (RB_TYPE_P(obj, T_OBJECT)) { rb_obj_copy_ivar(dest, obj); } @@ -513,7 +504,10 @@ rb_obj_clone_setup(VALUE obj, VALUE clone, VALUE kwfreeze) case Qnil: rb_funcall(clone, id_init_clone, 1, obj); RBASIC(clone)->flags |= RBASIC(obj)->flags & FL_FREEZE; - if (RB_OBJ_FROZEN(obj)) { + if (CHILLED_STRING_P(obj)) { + STR_CHILL_RAW(clone); + } + else if (RB_OBJ_FROZEN(obj)) { rb_shape_t * next_shape = rb_shape_transition_shape_frozen(clone); if (!rb_shape_obj_too_complex(clone) && next_shape->type == SHAPE_OBJ_TOO_COMPLEX) { rb_evict_ivars_to_hash(clone); @@ -668,9 +662,9 @@ rb_obj_size(VALUE self, VALUE args, VALUE obj) /** * :nodoc: *-- - * Default implementation of \c #initialize_copy - * \param[in,out] obj the receiver being initialized - * \param[in] orig the object to be copied from. + * Default implementation of `#initialize_copy` + * @param[in,out] obj the receiver being initialized + * @param[in] orig the object to be copied from. *++ */ VALUE @@ -684,13 +678,13 @@ rb_obj_init_copy(VALUE obj, VALUE orig) return obj; } -/*! +/** * :nodoc: *-- - * Default implementation of \c #initialize_dup + * Default implementation of `#initialize_dup` * - * \param[in,out] obj the receiver being initialized - * \param[in] orig the object to be dup from. + * @param[in,out] obj the receiver being initialized + * @param[in] orig the object to be dup from. *++ **/ VALUE @@ -700,14 +694,14 @@ rb_obj_init_dup_clone(VALUE obj, VALUE orig) return obj; } -/*! +/** * :nodoc: *-- - * Default implementation of \c #initialize_clone + * Default implementation of `#initialize_clone` * - * \param[in] The number of arguments - * \param[in] The array of arguments - * \param[in] obj the receiver being initialized + * @param[in] The number of arguments + * @param[in] The array of arguments + * @param[in] obj the receiver being initialized *++ **/ static VALUE @@ -2221,12 +2215,12 @@ rb_class_new_instance(int argc, const VALUE *argv, VALUE klass) * BasicObject.superclass #=> nil * *-- - * Returns the superclass of \a klass. Equivalent to \c Class\#superclass in Ruby. + * Returns the superclass of `klass`. Equivalent to `Class#superclass` in Ruby. * * It skips modules. - * \param[in] klass a Class object - * \return the superclass, or \c Qnil if \a klass does not have a parent class. - * \sa rb_class_get_superclass + * @param[in] klass a Class object + * @return the superclass, or `Qnil` if `klass` does not have a parent class. + * @sa rb_class_get_superclass *++ */ @@ -4104,7 +4098,7 @@ rb_f_loop_size(VALUE self, VALUE args, VALUE eobj) * end * * def respond_to_missing?(name, include_private = false) - * DELEGATE.include?(name) or super + * DELEGATE.include?(name) * end * end * diff --git a/parse.y b/parse.y index de90ee797ff058..58e39e8f2db94d 100644 --- a/parse.y +++ b/parse.y @@ -19,8 +19,6 @@ #define YYDEBUG 1 #define YYERROR_VERBOSE 1 #define YYSTACK_USE_ALLOCA 0 -#define YYLTYPE rb_code_location_t -#define YYLTYPE_IS_DECLARED 1 /* For Ripper */ #ifdef RUBY_EXTCONF_H @@ -51,7 +49,6 @@ #include "internal/encoding.h" #include "internal/error.h" #include "internal/hash.h" -#include "internal/imemo.h" #include "internal/io.h" #include "internal/numeric.h" #include "internal/parse.h" @@ -74,6 +71,19 @@ #include "symbol.h" #ifndef RIPPER +static VALUE +syntax_error_new(void) +{ + return rb_class_new_instance(0, 0, rb_eSyntaxError); +} +#endif + +static NODE *reg_named_capture_assign(struct parser_params* p, VALUE regexp, const YYLTYPE *loc); + +#define compile_callback rb_suppress_tracing +VALUE rb_io_gets_internal(VALUE io); +#endif /* !UNIVERSAL_PARSER */ + static int rb_parser_string_hash_cmp(rb_parser_string_t *str1, rb_parser_string_t *str2); static int @@ -117,15 +127,8 @@ rb_parser_regx_hash_cmp(rb_node_regx_t *n1, rb_node_regx_t *n2) rb_parser_string_hash_cmp(n1->string, n2->string)); } -static int -node_integer_line_cmp(const NODE *node_i, const NODE *line) -{ - VALUE num = rb_node_integer_literal_val(node_i); - - return !(FIXNUM_P(num) && line->nd_loc.beg_pos.lineno == FIX2INT(num)); -} - static st_index_t rb_parser_str_hash(rb_parser_string_t *str); +static st_index_t rb_char_p_hash(const char *c); static int literal_cmp(st_data_t val, st_data_t lit) @@ -137,22 +140,6 @@ literal_cmp(st_data_t val, st_data_t lit) enum node_type type_val = nd_type(node_val); enum node_type type_lit = nd_type(node_lit); - /* Special case for Integer and __LINE__ */ - if (type_val == NODE_INTEGER && type_lit == NODE_LINE) { - return node_integer_line_cmp(node_val, node_lit); - } - if (type_lit == NODE_INTEGER && type_val == NODE_LINE) { - return node_integer_line_cmp(node_lit, node_val); - } - - /* Special case for String and __FILE__ */ - if (type_val == NODE_STR && type_lit == NODE_FILE) { - return rb_parser_string_hash_cmp(RNODE_STR(node_val)->string, RNODE_FILE(node_lit)->path); - } - if (type_lit == NODE_STR && type_val == NODE_FILE) { - return rb_parser_string_hash_cmp(RNODE_STR(node_lit)->string, RNODE_FILE(node_val)->path); - } - if (type_val != type_lit) { return -1; } @@ -179,7 +166,11 @@ literal_cmp(st_data_t val, st_data_t lit) case NODE_ENCODING: return RNODE_ENCODING(node_val)->enc != RNODE_ENCODING(node_lit)->enc; default: +#ifdef UNIVERSAL_PARSER + abort(); +#else rb_bug("unexpected node: %s, %s", ruby_node_name(type_val), ruby_node_name(type_lit)); +#endif } } @@ -187,23 +178,17 @@ static st_index_t literal_hash(st_data_t a) { NODE *node = (NODE *)a; - VALUE val; enum node_type type = nd_type(node); switch (type) { case NODE_INTEGER: - val = rb_node_integer_literal_val(node); - if (!FIXNUM_P(val)) val = rb_big_hash(val); - return FIX2LONG(val); + return rb_char_p_hash(RNODE_INTEGER(node)->val); case NODE_FLOAT: - val = rb_node_float_literal_val(node); - return rb_dbl_long_hash(RFLOAT_VALUE(val)); + return rb_char_p_hash(RNODE_FLOAT(node)->val); case NODE_RATIONAL: - val = rb_node_rational_literal_val(node); - return rb_rational_hash(val); + return rb_char_p_hash(RNODE_RATIONAL(node)->val); case NODE_IMAGINARY: - val = rb_node_imaginary_literal_val(node); - return rb_complex_hash(val); + return rb_char_p_hash(RNODE_IMAGINARY(node)->val); case NODE_STR: return rb_parser_str_hash(RNODE_STR(node)->string); case NODE_SYM: @@ -211,33 +196,20 @@ literal_hash(st_data_t a) case NODE_REGX: return rb_parser_str_hash(RNODE_REGX(node)->string); case NODE_LINE: - /* Same with NODE_INTEGER FIXNUM case */ return (st_index_t)node->nd_loc.beg_pos.lineno; case NODE_FILE: - /* Same with NODE_STR */ return rb_parser_str_hash(RNODE_FILE(node)->path); case NODE_ENCODING: return (st_index_t)RNODE_ENCODING(node)->enc; default: +#ifdef UNIVERSAL_PARSER + abort(); +#else rb_bug("unexpected node: %s", ruby_node_name(type)); +#endif } } -static VALUE -syntax_error_new(void) -{ - return rb_class_new_instance(0, 0, rb_eSyntaxError); -} -#endif - -static NODE *reg_named_capture_assign(struct parser_params* p, VALUE regexp, const YYLTYPE *loc); - -#define compile_callback rb_suppress_tracing -VALUE rb_io_gets_internal(VALUE io); - -VALUE rb_node_case_when_optimizable_literal(const NODE *const node); -#endif /* !UNIVERSAL_PARSER */ - static inline int parse_isascii(int c) { @@ -326,13 +298,6 @@ VALUE rb_ripper_none; #include "ripper_init.h" #endif -enum shareability { - shareable_none, - shareable_literal, - shareable_copy, - shareable_everything, -}; - enum rescue_context { before_rescue, after_rescue, @@ -346,7 +311,7 @@ struct lex_context { unsigned int in_argdef: 1; unsigned int in_def: 1; unsigned int in_class: 1; - BITFIELD(enum shareability, shareable_constant_value, 2); + BITFIELD(enum rb_parser_shareability, shareable_constant_value, 2); BITFIELD(enum rescue_context, in_rescue, 2); }; @@ -367,8 +332,6 @@ RBIMPL_WARNING_POP() #define NO_LEX_CTXT (struct lex_context){0} -#define AREF(ary, i) RARRAY_AREF(ary, i) - #ifndef WARN_PAST_SCOPE # define WARN_PAST_SCOPE 0 #endif @@ -377,10 +340,6 @@ RBIMPL_WARNING_POP() #define yydebug (p->debug) /* disable the global variable definition */ -#define YYMALLOC(size) rb_parser_malloc(p, (size)) -#define YYREALLOC(ptr, size) rb_parser_realloc(p, (ptr), (size)) -#define YYCALLOC(nelem, size) rb_parser_calloc(p, (nelem), (size)) -#define YYFREE(ptr) rb_parser_free(p, (ptr)) #define YYFPRINTF(out, ...) rb_parser_printf(p, __VA_ARGS__) #define YY_LOCATION_PRINT(File, loc, p) \ rb_parser_printf(p, "%d.%d-%d.%d", \ @@ -522,8 +481,6 @@ typedef struct parser_string_buffer { token */ struct parser_params { - rb_imemo_tmpbuf_t *heap; - YYSTYPE *lval; YYLTYPE *yylloc; @@ -567,7 +524,7 @@ struct parser_params { VALUE ruby_sourcefile_string; rb_encoding *enc; token_info *token_info; - VALUE case_labels; + st_table *case_labels; rb_node_exits_t *exits; VALUE debug_buffer; @@ -634,7 +591,7 @@ struct parser_params { /* id for terms */ int token_id; /* Array for term tokens */ - VALUE tokens; + rb_parser_ary_t *tokens; #else /* Ripper only */ @@ -875,170 +832,170 @@ peek_end_expect_token_locations(struct parser_params *p) return p->end_expect_token_locations; } -static ID -parser_token2id(struct parser_params *p, enum yytokentype tok) +static const char * +parser_token2char(struct parser_params *p, enum yytokentype tok) { switch ((int) tok) { -#define TOKEN2ID(tok) case tok: return rb_intern(#tok); -#define TOKEN2ID2(tok, name) case tok: return rb_intern(name); - TOKEN2ID2(' ', "words_sep") - TOKEN2ID2('!', "!") - TOKEN2ID2('%', "%"); - TOKEN2ID2('&', "&"); - TOKEN2ID2('*', "*"); - TOKEN2ID2('+', "+"); - TOKEN2ID2('-', "-"); - TOKEN2ID2('/', "/"); - TOKEN2ID2('<', "<"); - TOKEN2ID2('=', "="); - TOKEN2ID2('>', ">"); - TOKEN2ID2('?', "?"); - TOKEN2ID2('^', "^"); - TOKEN2ID2('|', "|"); - TOKEN2ID2('~', "~"); - TOKEN2ID2(':', ":"); - TOKEN2ID2(',', ","); - TOKEN2ID2('.', "."); - TOKEN2ID2(';', ";"); - TOKEN2ID2('`', "`"); - TOKEN2ID2('\n', "nl"); - TOKEN2ID2('{', "{"); - TOKEN2ID2('}', "}"); - TOKEN2ID2('[', "["); - TOKEN2ID2(']', "]"); - TOKEN2ID2('(', "("); - TOKEN2ID2(')', ")"); - TOKEN2ID2('\\', "backslash"); - TOKEN2ID(keyword_class); - TOKEN2ID(keyword_module); - TOKEN2ID(keyword_def); - TOKEN2ID(keyword_undef); - TOKEN2ID(keyword_begin); - TOKEN2ID(keyword_rescue); - TOKEN2ID(keyword_ensure); - TOKEN2ID(keyword_end); - TOKEN2ID(keyword_if); - TOKEN2ID(keyword_unless); - TOKEN2ID(keyword_then); - TOKEN2ID(keyword_elsif); - TOKEN2ID(keyword_else); - TOKEN2ID(keyword_case); - TOKEN2ID(keyword_when); - TOKEN2ID(keyword_while); - TOKEN2ID(keyword_until); - TOKEN2ID(keyword_for); - TOKEN2ID(keyword_break); - TOKEN2ID(keyword_next); - TOKEN2ID(keyword_redo); - TOKEN2ID(keyword_retry); - TOKEN2ID(keyword_in); - TOKEN2ID(keyword_do); - TOKEN2ID(keyword_do_cond); - TOKEN2ID(keyword_do_block); - TOKEN2ID(keyword_do_LAMBDA); - TOKEN2ID(keyword_return); - TOKEN2ID(keyword_yield); - TOKEN2ID(keyword_super); - TOKEN2ID(keyword_self); - TOKEN2ID(keyword_nil); - TOKEN2ID(keyword_true); - TOKEN2ID(keyword_false); - TOKEN2ID(keyword_and); - TOKEN2ID(keyword_or); - TOKEN2ID(keyword_not); - TOKEN2ID(modifier_if); - TOKEN2ID(modifier_unless); - TOKEN2ID(modifier_while); - TOKEN2ID(modifier_until); - TOKEN2ID(modifier_rescue); - TOKEN2ID(keyword_alias); - TOKEN2ID(keyword_defined); - TOKEN2ID(keyword_BEGIN); - TOKEN2ID(keyword_END); - TOKEN2ID(keyword__LINE__); - TOKEN2ID(keyword__FILE__); - TOKEN2ID(keyword__ENCODING__); - TOKEN2ID(tIDENTIFIER); - TOKEN2ID(tFID); - TOKEN2ID(tGVAR); - TOKEN2ID(tIVAR); - TOKEN2ID(tCONSTANT); - TOKEN2ID(tCVAR); - TOKEN2ID(tLABEL); - TOKEN2ID(tINTEGER); - TOKEN2ID(tFLOAT); - TOKEN2ID(tRATIONAL); - TOKEN2ID(tIMAGINARY); - TOKEN2ID(tCHAR); - TOKEN2ID(tNTH_REF); - TOKEN2ID(tBACK_REF); - TOKEN2ID(tSTRING_CONTENT); - TOKEN2ID(tREGEXP_END); - TOKEN2ID(tDUMNY_END); - TOKEN2ID(tSP); - TOKEN2ID(tUPLUS); - TOKEN2ID(tUMINUS); - TOKEN2ID(tPOW); - TOKEN2ID(tCMP); - TOKEN2ID(tEQ); - TOKEN2ID(tEQQ); - TOKEN2ID(tNEQ); - TOKEN2ID(tGEQ); - TOKEN2ID(tLEQ); - TOKEN2ID(tANDOP); - TOKEN2ID(tOROP); - TOKEN2ID(tMATCH); - TOKEN2ID(tNMATCH); - TOKEN2ID(tDOT2); - TOKEN2ID(tDOT3); - TOKEN2ID(tBDOT2); - TOKEN2ID(tBDOT3); - TOKEN2ID(tAREF); - TOKEN2ID(tASET); - TOKEN2ID(tLSHFT); - TOKEN2ID(tRSHFT); - TOKEN2ID(tANDDOT); - TOKEN2ID(tCOLON2); - TOKEN2ID(tCOLON3); - TOKEN2ID(tOP_ASGN); - TOKEN2ID(tASSOC); - TOKEN2ID(tLPAREN); - TOKEN2ID(tLPAREN_ARG); - TOKEN2ID(tRPAREN); - TOKEN2ID(tLBRACK); - TOKEN2ID(tLBRACE); - TOKEN2ID(tLBRACE_ARG); - TOKEN2ID(tSTAR); - TOKEN2ID(tDSTAR); - TOKEN2ID(tAMPER); - TOKEN2ID(tLAMBDA); - TOKEN2ID(tSYMBEG); - TOKEN2ID(tSTRING_BEG); - TOKEN2ID(tXSTRING_BEG); - TOKEN2ID(tREGEXP_BEG); - TOKEN2ID(tWORDS_BEG); - TOKEN2ID(tQWORDS_BEG); - TOKEN2ID(tSYMBOLS_BEG); - TOKEN2ID(tQSYMBOLS_BEG); - TOKEN2ID(tSTRING_END); - TOKEN2ID(tSTRING_DEND); - TOKEN2ID(tSTRING_DBEG); - TOKEN2ID(tSTRING_DVAR); - TOKEN2ID(tLAMBEG); - TOKEN2ID(tLABEL_END); - TOKEN2ID(tIGNORED_NL); - TOKEN2ID(tCOMMENT); - TOKEN2ID(tEMBDOC_BEG); - TOKEN2ID(tEMBDOC); - TOKEN2ID(tEMBDOC_END); - TOKEN2ID(tHEREDOC_BEG); - TOKEN2ID(tHEREDOC_END); - TOKEN2ID(k__END__); - TOKEN2ID(tLOWEST); - TOKEN2ID(tUMINUS_NUM); - TOKEN2ID(tLAST_TOKEN); -#undef TOKEN2ID -#undef TOKEN2ID2 +#define TOKEN2CHAR(tok) case tok: return (#tok); +#define TOKEN2CHAR2(tok, name) case tok: return (name); + TOKEN2CHAR2(' ', "word_sep"); + TOKEN2CHAR2('!', "!") + TOKEN2CHAR2('%', "%"); + TOKEN2CHAR2('&', "&"); + TOKEN2CHAR2('*', "*"); + TOKEN2CHAR2('+', "+"); + TOKEN2CHAR2('-', "-"); + TOKEN2CHAR2('/', "/"); + TOKEN2CHAR2('<', "<"); + TOKEN2CHAR2('=', "="); + TOKEN2CHAR2('>', ">"); + TOKEN2CHAR2('?', "?"); + TOKEN2CHAR2('^', "^"); + TOKEN2CHAR2('|', "|"); + TOKEN2CHAR2('~', "~"); + TOKEN2CHAR2(':', ":"); + TOKEN2CHAR2(',', ","); + TOKEN2CHAR2('.', "."); + TOKEN2CHAR2(';', ";"); + TOKEN2CHAR2('`', "`"); + TOKEN2CHAR2('\n', "nl"); + TOKEN2CHAR2('{', "\"{\""); + TOKEN2CHAR2('}', "\"}\""); + TOKEN2CHAR2('[', "\"[\""); + TOKEN2CHAR2(']', "\"]\""); + TOKEN2CHAR2('(', "\"(\""); + TOKEN2CHAR2(')', "\")\""); + TOKEN2CHAR2('\\', "backslash"); + TOKEN2CHAR(keyword_class); + TOKEN2CHAR(keyword_module); + TOKEN2CHAR(keyword_def); + TOKEN2CHAR(keyword_undef); + TOKEN2CHAR(keyword_begin); + TOKEN2CHAR(keyword_rescue); + TOKEN2CHAR(keyword_ensure); + TOKEN2CHAR(keyword_end); + TOKEN2CHAR(keyword_if); + TOKEN2CHAR(keyword_unless); + TOKEN2CHAR(keyword_then); + TOKEN2CHAR(keyword_elsif); + TOKEN2CHAR(keyword_else); + TOKEN2CHAR(keyword_case); + TOKEN2CHAR(keyword_when); + TOKEN2CHAR(keyword_while); + TOKEN2CHAR(keyword_until); + TOKEN2CHAR(keyword_for); + TOKEN2CHAR(keyword_break); + TOKEN2CHAR(keyword_next); + TOKEN2CHAR(keyword_redo); + TOKEN2CHAR(keyword_retry); + TOKEN2CHAR(keyword_in); + TOKEN2CHAR(keyword_do); + TOKEN2CHAR(keyword_do_cond); + TOKEN2CHAR(keyword_do_block); + TOKEN2CHAR(keyword_do_LAMBDA); + TOKEN2CHAR(keyword_return); + TOKEN2CHAR(keyword_yield); + TOKEN2CHAR(keyword_super); + TOKEN2CHAR(keyword_self); + TOKEN2CHAR(keyword_nil); + TOKEN2CHAR(keyword_true); + TOKEN2CHAR(keyword_false); + TOKEN2CHAR(keyword_and); + TOKEN2CHAR(keyword_or); + TOKEN2CHAR(keyword_not); + TOKEN2CHAR(modifier_if); + TOKEN2CHAR(modifier_unless); + TOKEN2CHAR(modifier_while); + TOKEN2CHAR(modifier_until); + TOKEN2CHAR(modifier_rescue); + TOKEN2CHAR(keyword_alias); + TOKEN2CHAR(keyword_defined); + TOKEN2CHAR(keyword_BEGIN); + TOKEN2CHAR(keyword_END); + TOKEN2CHAR(keyword__LINE__); + TOKEN2CHAR(keyword__FILE__); + TOKEN2CHAR(keyword__ENCODING__); + TOKEN2CHAR(tIDENTIFIER); + TOKEN2CHAR(tFID); + TOKEN2CHAR(tGVAR); + TOKEN2CHAR(tIVAR); + TOKEN2CHAR(tCONSTANT); + TOKEN2CHAR(tCVAR); + TOKEN2CHAR(tLABEL); + TOKEN2CHAR(tINTEGER); + TOKEN2CHAR(tFLOAT); + TOKEN2CHAR(tRATIONAL); + TOKEN2CHAR(tIMAGINARY); + TOKEN2CHAR(tCHAR); + TOKEN2CHAR(tNTH_REF); + TOKEN2CHAR(tBACK_REF); + TOKEN2CHAR(tSTRING_CONTENT); + TOKEN2CHAR(tREGEXP_END); + TOKEN2CHAR(tDUMNY_END); + TOKEN2CHAR(tSP); + TOKEN2CHAR(tUPLUS); + TOKEN2CHAR(tUMINUS); + TOKEN2CHAR(tPOW); + TOKEN2CHAR(tCMP); + TOKEN2CHAR(tEQ); + TOKEN2CHAR(tEQQ); + TOKEN2CHAR(tNEQ); + TOKEN2CHAR(tGEQ); + TOKEN2CHAR(tLEQ); + TOKEN2CHAR(tANDOP); + TOKEN2CHAR(tOROP); + TOKEN2CHAR(tMATCH); + TOKEN2CHAR(tNMATCH); + TOKEN2CHAR(tDOT2); + TOKEN2CHAR(tDOT3); + TOKEN2CHAR(tBDOT2); + TOKEN2CHAR(tBDOT3); + TOKEN2CHAR(tAREF); + TOKEN2CHAR(tASET); + TOKEN2CHAR(tLSHFT); + TOKEN2CHAR(tRSHFT); + TOKEN2CHAR(tANDDOT); + TOKEN2CHAR(tCOLON2); + TOKEN2CHAR(tCOLON3); + TOKEN2CHAR(tOP_ASGN); + TOKEN2CHAR(tASSOC); + TOKEN2CHAR(tLPAREN); + TOKEN2CHAR(tLPAREN_ARG); + TOKEN2CHAR(tRPAREN); + TOKEN2CHAR(tLBRACK); + TOKEN2CHAR(tLBRACE); + TOKEN2CHAR(tLBRACE_ARG); + TOKEN2CHAR(tSTAR); + TOKEN2CHAR(tDSTAR); + TOKEN2CHAR(tAMPER); + TOKEN2CHAR(tLAMBDA); + TOKEN2CHAR(tSYMBEG); + TOKEN2CHAR(tSTRING_BEG); + TOKEN2CHAR(tXSTRING_BEG); + TOKEN2CHAR(tREGEXP_BEG); + TOKEN2CHAR(tWORDS_BEG); + TOKEN2CHAR(tQWORDS_BEG); + TOKEN2CHAR(tSYMBOLS_BEG); + TOKEN2CHAR(tQSYMBOLS_BEG); + TOKEN2CHAR(tSTRING_END); + TOKEN2CHAR(tSTRING_DEND); + TOKEN2CHAR(tSTRING_DBEG); + TOKEN2CHAR(tSTRING_DVAR); + TOKEN2CHAR(tLAMBEG); + TOKEN2CHAR(tLABEL_END); + TOKEN2CHAR(tIGNORED_NL); + TOKEN2CHAR(tCOMMENT); + TOKEN2CHAR(tEMBDOC_BEG); + TOKEN2CHAR(tEMBDOC); + TOKEN2CHAR(tEMBDOC_END); + TOKEN2CHAR(tHEREDOC_BEG); + TOKEN2CHAR(tHEREDOC_END); + TOKEN2CHAR(k__END__); + TOKEN2CHAR(tLOWEST); + TOKEN2CHAR(tUMINUS_NUM); + TOKEN2CHAR(tLAST_TOKEN); +#undef TOKEN2CHAR +#undef TOKEN2CHAR2 } rb_bug("parser_token2id: unknown token %d", tok); @@ -1125,13 +1082,13 @@ static rb_node_lasgn_t *rb_node_lasgn_new(struct parser_params *p, ID nd_vid, NO static rb_node_dasgn_t *rb_node_dasgn_new(struct parser_params *p, ID nd_vid, NODE *nd_value, const YYLTYPE *loc); static rb_node_gasgn_t *rb_node_gasgn_new(struct parser_params *p, ID nd_vid, NODE *nd_value, const YYLTYPE *loc); static rb_node_iasgn_t *rb_node_iasgn_new(struct parser_params *p, ID nd_vid, NODE *nd_value, const YYLTYPE *loc); -static rb_node_cdecl_t *rb_node_cdecl_new(struct parser_params *p, ID nd_vid, NODE *nd_value, NODE *nd_else, const YYLTYPE *loc); +static rb_node_cdecl_t *rb_node_cdecl_new(struct parser_params *p, ID nd_vid, NODE *nd_value, NODE *nd_else, enum rb_parser_shareability shareability, const YYLTYPE *loc); static rb_node_cvasgn_t *rb_node_cvasgn_new(struct parser_params *p, ID nd_vid, NODE *nd_value, const YYLTYPE *loc); static rb_node_op_asgn1_t *rb_node_op_asgn1_new(struct parser_params *p, NODE *nd_recv, ID nd_mid, NODE *index, NODE *rvalue, const YYLTYPE *loc); static rb_node_op_asgn2_t *rb_node_op_asgn2_new(struct parser_params *p, NODE *nd_recv, NODE *nd_value, ID nd_vid, ID nd_mid, bool nd_aid, const YYLTYPE *loc); static rb_node_op_asgn_or_t *rb_node_op_asgn_or_new(struct parser_params *p, NODE *nd_head, NODE *nd_value, const YYLTYPE *loc); static rb_node_op_asgn_and_t *rb_node_op_asgn_and_new(struct parser_params *p, NODE *nd_head, NODE *nd_value, const YYLTYPE *loc); -static rb_node_op_cdecl_t *rb_node_op_cdecl_new(struct parser_params *p, NODE *nd_head, NODE *nd_value, ID nd_aid, const YYLTYPE *loc); +static rb_node_op_cdecl_t *rb_node_op_cdecl_new(struct parser_params *p, NODE *nd_head, NODE *nd_value, ID nd_aid, enum rb_parser_shareability shareability, const YYLTYPE *loc); static rb_node_call_t *rb_node_call_new(struct parser_params *p, NODE *nd_recv, ID nd_mid, NODE *nd_args, const YYLTYPE *loc); static rb_node_opcall_t *rb_node_opcall_new(struct parser_params *p, NODE *nd_recv, ID nd_mid, NODE *nd_args, const YYLTYPE *loc); static rb_node_fcall_t *rb_node_fcall_new(struct parser_params *p, ID nd_mid, NODE *nd_args, const YYLTYPE *loc); @@ -1155,7 +1112,6 @@ static rb_node_nth_ref_t *rb_node_nth_ref_new(struct parser_params *p, long nd_n static rb_node_back_ref_t *rb_node_back_ref_new(struct parser_params *p, long nd_nth, const YYLTYPE *loc); static rb_node_match2_t *rb_node_match2_new(struct parser_params *p, NODE *nd_recv, NODE *nd_value, const YYLTYPE *loc); static rb_node_match3_t *rb_node_match3_new(struct parser_params *p, NODE *nd_recv, NODE *nd_value, const YYLTYPE *loc); -static rb_node_lit_t *rb_node_lit_new(struct parser_params *p, VALUE nd_lit, const YYLTYPE *loc); static rb_node_integer_t * rb_node_integer_new(struct parser_params *p, char* val, int base, const YYLTYPE *loc); static rb_node_float_t * rb_node_float_new(struct parser_params *p, char* val, const YYLTYPE *loc); static rb_node_rational_t * rb_node_rational_new(struct parser_params *p, char* val, int base, int seen_point, const YYLTYPE *loc); @@ -1169,7 +1125,7 @@ static rb_node_evstr_t *rb_node_evstr_new(struct parser_params *p, NODE *nd_body static rb_node_regx_t *rb_node_regx_new(struct parser_params *p, rb_parser_string_t *string, int options, const YYLTYPE *loc); static rb_node_once_t *rb_node_once_new(struct parser_params *p, NODE *nd_body, const YYLTYPE *loc); static rb_node_args_t *rb_node_args_new(struct parser_params *p, const YYLTYPE *loc); -static rb_node_args_aux_t *rb_node_args_aux_new(struct parser_params *p, ID nd_pid, long nd_plen, const YYLTYPE *loc); +static rb_node_args_aux_t *rb_node_args_aux_new(struct parser_params *p, ID nd_pid, int nd_plen, const YYLTYPE *loc); static rb_node_opt_arg_t *rb_node_opt_arg_new(struct parser_params *p, NODE *nd_body, const YYLTYPE *loc); static rb_node_kw_arg_t *rb_node_kw_arg_new(struct parser_params *p, NODE *nd_body, const YYLTYPE *loc); static rb_node_postarg_t *rb_node_postarg_new(struct parser_params *p, NODE *nd_1st, NODE *nd_2nd, const YYLTYPE *loc); @@ -1234,13 +1190,13 @@ static rb_node_error_t *rb_node_error_new(struct parser_params *p, const YYLTYPE #define NEW_DASGN(v,val,loc) (NODE *)rb_node_dasgn_new(p,v,val,loc) #define NEW_GASGN(v,val,loc) (NODE *)rb_node_gasgn_new(p,v,val,loc) #define NEW_IASGN(v,val,loc) (NODE *)rb_node_iasgn_new(p,v,val,loc) -#define NEW_CDECL(v,val,path,loc) (NODE *)rb_node_cdecl_new(p,v,val,path,loc) +#define NEW_CDECL(v,val,path,share,loc) (NODE *)rb_node_cdecl_new(p,v,val,path,share,loc) #define NEW_CVASGN(v,val,loc) (NODE *)rb_node_cvasgn_new(p,v,val,loc) #define NEW_OP_ASGN1(r,id,idx,rval,loc) (NODE *)rb_node_op_asgn1_new(p,r,id,idx,rval,loc) #define NEW_OP_ASGN2(r,t,i,o,val,loc) (NODE *)rb_node_op_asgn2_new(p,r,val,i,o,t,loc) #define NEW_OP_ASGN_OR(i,val,loc) (NODE *)rb_node_op_asgn_or_new(p,i,val,loc) #define NEW_OP_ASGN_AND(i,val,loc) (NODE *)rb_node_op_asgn_and_new(p,i,val,loc) -#define NEW_OP_CDECL(v,op,val,loc) (NODE *)rb_node_op_cdecl_new(p,v,val,op,loc) +#define NEW_OP_CDECL(v,op,val,share,loc) (NODE *)rb_node_op_cdecl_new(p,v,val,op,share,loc) #define NEW_CALL(r,m,a,loc) (NODE *)rb_node_call_new(p,r,m,a,loc) #define NEW_OPCALL(r,m,a,loc) (NODE *)rb_node_opcall_new(p,r,m,a,loc) #define NEW_FCALL(m,a,loc) rb_node_fcall_new(p,m,a,loc) @@ -1264,7 +1220,6 @@ static rb_node_error_t *rb_node_error_new(struct parser_params *p, const YYLTYPE #define NEW_BACK_REF(n,loc) (NODE *)rb_node_back_ref_new(p,n,loc) #define NEW_MATCH2(n1,n2,loc) (NODE *)rb_node_match2_new(p,n1,n2,loc) #define NEW_MATCH3(r,n2,loc) (NODE *)rb_node_match3_new(p,r,n2,loc) -#define NEW_LIT(l,loc) (NODE *)rb_node_lit_new(p,l,loc) #define NEW_INTEGER(val, base,loc) (NODE *)rb_node_integer_new(p,val,base,loc) #define NEW_FLOAT(val,loc) (NODE *)rb_node_float_new(p,val,loc) #define NEW_RATIONAL(val,base,seen_point,loc) (NODE *)rb_node_rational_new(p,val,base,seen_point,loc) @@ -1615,6 +1570,9 @@ static void numparam_pop(struct parser_params *p, NODE *prev_inner); #define RE_OPTION_MASK 0xff #define RE_OPTION_ARG_ENCODING_NONE 32 +#define CHECK_LITERAL_WHEN (st_table *)1 +#define CASE_LABELS_ENABLED_P(case_labels) (case_labels && case_labels != CHECK_LITERAL_WHEN) + #define yytnamerr(yyres, yystr) (YYSIZE_T)rb_yytnamerr(p, yyres, yystr) size_t rb_yytnamerr(struct parser_params *p, char *yyres, const char *yystr); @@ -1847,7 +1805,6 @@ extern const ID id_warn, id_warning, id_gets, id_assoc; # define WARN_S(s) STR_NEW2(s) # define WARN_I(i) INT2NUM(i) # define WARN_ID(i) rb_id2str(i) -# define WARN_IVAL(i) i # define PRIsWARN PRIsVALUE # define rb_warn0L_experimental(l,fmt) WARN_CALL(WARN_ARGS_L(l, fmt, 1)) # define WARN_ARGS(fmt,n) p->value, id_warn, n, rb_usascii_str_new_lit(fmt) @@ -1870,7 +1827,6 @@ extern const ID id_warn, id_warning, id_gets, id_assoc; # define WARN_S(s) s # define WARN_I(i) i # define WARN_ID(i) rb_id2name(i) -# define WARN_IVAL(i) NUM2INT(i) # define PRIsWARN PRIsVALUE # define WARN_ARGS(fmt,n) WARN_ARGS_L(p->ruby_sourceline,fmt,n) # define WARN_ARGS_L(l,fmt,n) p->ruby_sourcefile, (l), (fmt) @@ -2063,8 +2019,9 @@ get_nd_args(struct parser_params *p, NODE *node) return RNODE_FCALL(node)->nd_args; case NODE_QCALL: return RNODE_QCALL(node)->nd_args; - case NODE_VCALL: case NODE_SUPER: + return RNODE_SUPER(node)->nd_args; + case NODE_VCALL: case NODE_ZSUPER: case NODE_YIELD: case NODE_RETURN: @@ -2077,8 +2034,6 @@ get_nd_args(struct parser_params *p, NODE *node) } } -#ifndef RIPPER -#ifndef UNIVERSAL_PARSER static st_index_t djb2(const uint8_t *str, size_t len) { @@ -2096,8 +2051,6 @@ parser_memhash(const void *ptr, long len) { return djb2(ptr, len); } -#endif -#endif #define PARSER_STRING_PTR(str) (str->ptr) #define PARSER_STRING_LEN(str) (str->len) @@ -2161,15 +2114,17 @@ rb_parser_string_free(rb_parser_t *p, rb_parser_string_t *str) xfree(str); } -#ifndef RIPPER -#ifndef UNIVERSAL_PARSER static st_index_t rb_parser_str_hash(rb_parser_string_t *str) { return parser_memhash((const void *)PARSER_STRING_PTR(str), PARSER_STRING_LEN(str)); } -#endif -#endif + +static st_index_t +rb_char_p_hash(const char *c) +{ + return parser_memhash((const void *)c, strlen(c)); +} static size_t rb_parser_str_capacity(rb_parser_string_t *str, const int termlen) @@ -2565,8 +2520,6 @@ rb_parser_str_resize(struct parser_params *p, rb_parser_string_t *str, long len) return str; } -#ifndef UNIVERSAL_PARSER -#ifndef RIPPER # define PARSER_ENC_STRING_GETMEM(str, ptrvar, lenvar, encvar) \ ((ptrvar) = str->ptr, \ (lenvar) = str->len, \ @@ -2586,8 +2539,68 @@ rb_parser_string_hash_cmp(rb_parser_string_t *str1, rb_parser_string_t *str2) enc1 != enc2 || memcmp(ptr1, ptr2, len1) != 0); } -#endif -#endif + +#ifndef RIPPER +static void +rb_parser_ary_extend(rb_parser_t *p, rb_parser_ary_t *ary, long len) +{ + long i; + if (ary->capa < len) { + ary->capa = len; + ary->data = xrealloc(ary->data, sizeof(void *) * len); + for (i = ary->len; i < len; i++) { + ary->data[i] = 0; + } + } +} + +static rb_parser_ary_t * +rb_parser_ary_new_capa(rb_parser_t *p, long len) +{ + if (len < 0) { + rb_bug("negative array size (or size too big): %ld", len); + } + rb_parser_ary_t *ary = xcalloc(1, sizeof(rb_parser_ary_t)); + ary->len = 0; + ary->capa = len; + if (0 < len) { + ary->data = (rb_parser_ast_token_t **)xcalloc(len, sizeof(rb_parser_ast_token_t *)); + } + else { + ary->data = NULL; + } + return ary; +} +#define rb_parser_ary_new2 rb_parser_ary_new_capa + +static rb_parser_ary_t * +rb_parser_ary_push(rb_parser_t *p, rb_parser_ary_t *ary, rb_parser_ast_token_t *val) +{ + if (ary->len == ary->capa) { + rb_parser_ary_extend(p, ary, ary->len == 0 ? 1 : ary->len * 2); + } + ary->data[ary->len++] = val; + return ary; +} + +static void +rb_parser_ast_token_free(rb_parser_t *p, rb_parser_ast_token_t *token) +{ + if (!token) return; + rb_parser_string_free(p, token->str); + xfree(token); +} + +static void +rb_parser_tokens_free(rb_parser_t *p, rb_parser_ary_t *tokens) +{ + for (long i = 0; i < tokens->len; i++) { + rb_parser_ast_token_free(p, tokens->data[i]); + } + xfree(tokens); +} + +#endif /* !RIPPER */ %} %expect 0 @@ -2619,9 +2632,6 @@ rb_parser_string_hash_cmp(rb_parser_string_t *str1, rb_parser_string_t *str2) case NODE_IMAGINARY: rb_parser_printf(p, "%+"PRIsVALUE, rb_node_imaginary_literal_val($$)); break; - case NODE_LIT: - rb_parser_printf(p, "%+"PRIsVALUE, RNODE_LIT($$)->nd_lit); - break; default: break; } @@ -2633,6 +2643,10 @@ rb_parser_string_hash_cmp(rb_parser_string_t *str1, rb_parser_string_t *str2) rb_parser_printf(p, "$%c", (int)RNODE_BACK_REF($$)->nd_nth); } tBACK_REF +%destructor { + if (CASE_LABELS_ENABLED_P($$)) st_free_table($$); +} + %lex-param {struct parser_params *p} %parse-param {struct parser_params *p} %initial-action @@ -2646,7 +2660,6 @@ rb_parser_string_hash_cmp(rb_parser_string_t *str1, rb_parser_string_t *str2) %after-pop-stack after_pop_stack %union { - VALUE val; NODE *node; rb_node_fcall_t *node_fcall; rb_node_args_t *node_args; @@ -2660,6 +2673,7 @@ rb_parser_string_hash_cmp(rb_parser_string_t *str1, rb_parser_string_t *str2) ID id; int num; st_table *tbl; + st_table *labels; const struct vtable *vars; struct rb_strterm_struct *strterm; struct lex_context ctxt; @@ -3431,7 +3445,7 @@ command : fcall command_args %prec tLOWEST { set_embraced_location($5, &@4, &@6); $$ = new_command_qcall(p, idCOLON2, $1, $3, Qnull, $5, &@3, &@$); - /*% ripper: method_add_block!(command_call!($:1, $:2, $:3, Qundef), $:5) %*/ + /*% ripper: method_add_block!(command_call!($:1, $:2, $:3, Qnil), $:5) %*/ } | keyword_super command_args { @@ -4475,28 +4489,28 @@ primary : literal } | k_case expr_value terms? { - $$ = p->case_labels; - p->case_labels = Qnil; - } + $$ = p->case_labels; + p->case_labels = CHECK_LITERAL_WHEN; + } case_body k_end { - if (RTEST(p->case_labels)) rb_hash_clear(p->case_labels); - p->case_labels = $4; + if (CASE_LABELS_ENABLED_P(p->case_labels)) st_free_table(p->case_labels); + p->case_labels = $4; $$ = NEW_CASE($2, $5, &@$); fixpos($$, $2); /*% ripper: case!($:2, $:5) %*/ } | k_case terms? { - $$ = p->case_labels; + $$ = p->case_labels; p->case_labels = 0; - } + } case_body k_end { - if (RTEST(p->case_labels)) rb_hash_clear(p->case_labels); - p->case_labels = $3; + if (p->case_labels) st_free_table(p->case_labels); + p->case_labels = $3; $$ = NEW_CASE2($4, &@$); /*% ripper: case!(Qnil, $:4) %*/ } @@ -6821,7 +6835,6 @@ singleton : var_ref case NODE_DXSTR: case NODE_REGX: case NODE_DREGX: - case NODE_LIT: case NODE_SYM: case NODE_LINE: case NODE_FILE: @@ -7035,35 +7048,100 @@ parser_has_token(struct parser_params *p) return pcur > ptok; } -static VALUE -code_loc_to_ary(struct parser_params *p, const rb_code_location_t *loc) +static const char * +escaped_char(int c) { - VALUE ary = rb_ary_new_from_args(4, - INT2NUM(loc->beg_pos.lineno), INT2NUM(loc->beg_pos.column), - INT2NUM(loc->end_pos.lineno), INT2NUM(loc->end_pos.column)); - rb_obj_freeze(ary); - - return ary; + switch (c) { + case '"': return "\\\""; + case '\\': return "\\\\"; + case '\0': return "\\0"; + case '\n': return "\\n"; + case '\r': return "\\r"; + case '\t': return "\\t"; + case '\f': return "\\f"; + case '\013': return "\\v"; + case '\010': return "\\b"; + case '\007': return "\\a"; + case '\033': return "\\e"; + case '\x7f': return "\\c?"; + } + return NULL; } -static void -parser_append_tokens(struct parser_params *p, VALUE str, enum yytokentype t, int line) +static rb_parser_string_t * +rb_parser_str_escape(struct parser_params *p, rb_parser_string_t *str) { - VALUE ary; - int token_id; + rb_encoding *enc = p->enc; + const char *ptr = str->ptr; + const char *pend = ptr + str->len; + const char *prev = ptr; + char charbuf[5] = {'\\', 'x', 0, 0, 0}; + rb_parser_string_t * result = rb_parser_string_new(p, 0, 0); + int asciicompat = rb_enc_asciicompat(enc); + + while (ptr < pend) { + unsigned int c; + const char *cc; + int n = rb_enc_precise_mbclen(ptr, pend, enc); + if (!MBCLEN_CHARFOUND_P(n)) { + if (ptr > prev) rb_parser_str_buf_cat(p, result, prev, ptr - prev); + n = rb_enc_mbminlen(enc); + if (pend < ptr + n) + n = (int)(pend - ptr); + while (n--) { + c = *ptr & 0xf0 >> 4; + charbuf[2] = (c < 10) ? '0' + c : 'A' + c - 10; + c = *ptr & 0x0f; + charbuf[3] = (c < 10) ? '0' + c : 'A' + c - 10; + rb_parser_str_buf_cat(p, result, charbuf, 4); + prev = ++ptr; + } + continue; + } + n = MBCLEN_CHARFOUND_LEN(n); + c = rb_enc_mbc_to_codepoint(ptr, pend, enc); + ptr += n; + cc = escaped_char(c); + if (cc) { + if (ptr - n > prev) rb_parser_str_buf_cat(p, result, prev, ptr - n - prev); + rb_parser_str_buf_cat(p, result, cc, strlen(cc)); + prev = ptr; + } + else if (asciicompat && rb_enc_isascii(c, enc) && ISPRINT(c)) { + } + else { + if (ptr - n > prev) { + rb_parser_str_buf_cat(p, result, prev, ptr - n - prev); + prev = ptr - n; + } + rb_parser_str_buf_cat(p, result, prev, ptr - prev); + prev = ptr; + } + } + if (ptr > prev) rb_parser_str_buf_cat(p, result, prev, ptr - prev); - ary = rb_ary_new2(4); - token_id = p->token_id; - rb_ary_push(ary, INT2FIX(token_id)); - rb_ary_push(ary, ID2SYM(parser_token2id(p, t))); - rb_ary_push(ary, str); - rb_ary_push(ary, code_loc_to_ary(p, p->yylloc)); - rb_obj_freeze(ary); - rb_ary_push(p->tokens, ary); + return result; +} + +static void +parser_append_tokens(struct parser_params *p, rb_parser_string_t *str, enum yytokentype t, int line) +{ + rb_parser_ast_token_t *token = xcalloc(1, sizeof(rb_parser_ast_token_t)); + token->id = p->token_id; + token->type_name = parser_token2char(p, t); + token->str = str; + token->loc.beg_pos = p->yylloc->beg_pos; + token->loc.end_pos = p->yylloc->end_pos; + rb_parser_ary_push(p, p->tokens, token); p->token_id++; if (p->debug) { - rb_parser_printf(p, "Append tokens (line: %d) %"PRIsVALUE"\n", line, ary); + rb_parser_string_t *str_escaped = rb_parser_str_escape(p, str); + rb_parser_printf(p, "Append tokens (line: %d) [%d, :%s, \"%s\", [%d, %d, %d, %d]]\n", + line, token->id, token->type_name, str_escaped->ptr, + token->loc.beg_pos.lineno, token->loc.beg_pos.column, + token->loc.end_pos.lineno, token->loc.end_pos.column); + rb_parser_string_free(p, str_escaped); } } @@ -7077,7 +7155,7 @@ parser_dispatch_scan_event(struct parser_params *p, enum yytokentype t, int line RUBY_SET_YYLLOC(*p->yylloc); if (p->keep_tokens) { - VALUE str = STR_NEW(p->lex.ptok, p->lex.pcur - p->lex.ptok); + rb_parser_string_t *str = rb_parser_encoding_string_new(p, p->lex.ptok, p->lex.pcur - p->lex.ptok, p->enc); parser_append_tokens(p, str, t, line); } @@ -7095,7 +7173,8 @@ parser_dispatch_delayed_token(struct parser_params *p, enum yytokentype t, int l RUBY_SET_YYLLOC_OF_DELAYED_TOKEN(*p->yylloc); if (p->keep_tokens) { - parser_append_tokens(p, p->delayed.token, t, line); + rb_parser_string_t *str = rb_str_to_parser_string(p, p->delayed.token); + parser_append_tokens(p, str, t, line); } p->delayed.token = Qnil; @@ -7607,7 +7686,7 @@ yycompile0(VALUE arg) tree = NEW_NIL(&NULL_LOC); } else { - VALUE tokens = p->tokens; + rb_parser_ary_t *tokens = p->tokens; NODE *prelude; NODE *body = parser_append_options(p, RNODE_SCOPE(tree)->nd_body); prelude = block_append(p, p->eval_tree_begin, body); @@ -7615,8 +7694,8 @@ yycompile0(VALUE arg) p->ast->body.frozen_string_literal = p->frozen_string_literal; p->ast->body.coverage_enabled = cov; if (p->keep_tokens) { - rb_obj_freeze(tokens); - rb_ast_set_tokens(p->ast, tokens); + p->ast->node_buffer->tokens = tokens; + p->tokens = NULL; } } p->ast->body.root = tree; @@ -7796,7 +7875,7 @@ parser_str_new(struct parser_params *p, const char *ptr, long len, rb_encoding * static int strterm_is_heredoc(rb_strterm_t *strterm) { - return strterm->flags & STRTERM_HEREDOC; + return strterm->heredoc; } static rb_strterm_t * @@ -7813,7 +7892,7 @@ static rb_strterm_t * new_heredoc(struct parser_params *p) { rb_strterm_t *strterm = ZALLOC(rb_strterm_t); - strterm->flags |= STRTERM_HEREDOC; + strterm->heredoc = true; return strterm; } @@ -7880,7 +7959,7 @@ nextline(struct parser_params *p, int set_encoding) } #ifndef RIPPER if (p->debug_lines) { - VALUE v = rb_str_new_parser_string(str); + VALUE v = rb_str_new_mutable_parser_string(str); if (set_encoding) rb_enc_associate(v, p->enc); rb_ary_push(p->debug_lines, v); } @@ -8042,7 +8121,7 @@ escaped_control_code(int c) } #define WARN_SPACE_CHAR(c, prefix) \ - rb_warn1("invalid character syntax; use "prefix"\\%c", WARN_I(c2)) + rb_warn1("invalid character syntax; use "prefix"\\%c", WARN_I(c)) static int tokadd_codepoint(struct parser_params *p, rb_encoding **encp, @@ -9230,7 +9309,7 @@ parser_dispatch_heredoc_end(struct parser_params *p, int line) dispatch_delayed_token(p, tSTRING_CONTENT); if (p->keep_tokens) { - VALUE str = STR_NEW(p->lex.ptok, p->lex.pend - p->lex.ptok); + rb_parser_string_t *str = rb_parser_encoding_string_new(p, p->lex.ptok, p->lex.pend - p->lex.ptok, p->enc); RUBY_SET_YYLLOC_OF_HEREDOC_END(*p->yylloc); parser_append_tokens(p, str, tHEREDOC_END, line); } @@ -9649,23 +9728,23 @@ parser_set_shareable_constant_value(struct parser_params *p, const char *name, c switch (*val) { case 'n': case 'N': if (STRCASECMP(val, "none") == 0) { - p->ctxt.shareable_constant_value = shareable_none; + p->ctxt.shareable_constant_value = rb_parser_shareable_none; return; } break; case 'l': case 'L': if (STRCASECMP(val, "literal") == 0) { - p->ctxt.shareable_constant_value = shareable_literal; + p->ctxt.shareable_constant_value = rb_parser_shareable_literal; return; } break; case 'e': case 'E': if (STRCASECMP(val, "experimental_copy") == 0) { - p->ctxt.shareable_constant_value = shareable_copy; + p->ctxt.shareable_constant_value = rb_parser_shareable_copy; return; } if (STRCASECMP(val, "experimental_everything") == 0) { - p->ctxt.shareable_constant_value = shareable_everything; + p->ctxt.shareable_constant_value = rb_parser_shareable_everything; return; } break; @@ -10037,6 +10116,7 @@ parse_numeric(struct parser_params *p, int c) /* prefixed octal */ c = nextc(p); if (c == -1 || c == '_' || !ISDIGIT(c)) { + tokfix(p); return no_digits(p); } } @@ -11440,7 +11520,7 @@ yylex(YYSTYPE *lval, YYLTYPE *yylloc, struct parser_params *p) enum yytokentype t; p->lval = lval; - lval->val = Qundef; + lval->node = 0; p->yylloc = yylloc; t = parser_yylex(p); @@ -12130,15 +12210,6 @@ rb_node_back_ref_new(struct parser_params *p, long nd_nth, const YYLTYPE *loc) return n; } -static rb_node_lit_t * -rb_node_lit_new(struct parser_params *p, VALUE nd_lit, const YYLTYPE *loc) -{ - rb_node_lit_t *n = NODE_NEWNODE(NODE_LIT, rb_node_lit_t, loc); - n->nd_lit = nd_lit; - - return n; -} - static rb_node_integer_t * rb_node_integer_new(struct parser_params *p, char* val, int base, const YYLTYPE *loc) { @@ -12342,7 +12413,7 @@ rb_node_args_new(struct parser_params *p, const YYLTYPE *loc) } static rb_node_args_aux_t * -rb_node_args_aux_new(struct parser_params *p, ID nd_pid, long nd_plen, const YYLTYPE *loc) +rb_node_args_aux_new(struct parser_params *p, ID nd_pid, int nd_plen, const YYLTYPE *loc) { rb_node_args_aux_t *n = NODE_NEWNODE(NODE_ARGS_AUX, rb_node_args_aux_t, loc); n->nd_pid = nd_pid; @@ -12549,23 +12620,25 @@ rb_node_encoding_new(struct parser_params *p, const YYLTYPE *loc) } static rb_node_cdecl_t * -rb_node_cdecl_new(struct parser_params *p, ID nd_vid, NODE *nd_value, NODE *nd_else, const YYLTYPE *loc) +rb_node_cdecl_new(struct parser_params *p, ID nd_vid, NODE *nd_value, NODE *nd_else, enum rb_parser_shareability shareability, const YYLTYPE *loc) { rb_node_cdecl_t *n = NODE_NEWNODE(NODE_CDECL, rb_node_cdecl_t, loc); n->nd_vid = nd_vid; n->nd_value = nd_value; n->nd_else = nd_else; + n->shareability = shareability; return n; } static rb_node_op_cdecl_t * -rb_node_op_cdecl_new(struct parser_params *p, NODE *nd_head, NODE *nd_value, ID nd_aid, const YYLTYPE *loc) +rb_node_op_cdecl_new(struct parser_params *p, NODE *nd_head, NODE *nd_value, ID nd_aid, enum rb_parser_shareability shareability, const YYLTYPE *loc) { rb_node_op_cdecl_t *n = NODE_NEWNODE(NODE_OP_CDECL, rb_node_op_cdecl_t, loc); n->nd_head = nd_head; n->nd_value = nd_value; n->nd_aid = nd_aid; + n->shareability = shareability; return n; } @@ -13358,36 +13431,33 @@ new_xstring(struct parser_params *p, NODE *node, const YYLTYPE *loc) return node; } -#ifndef RIPPER -VALUE -rb_parser_node_case_when_optimizable_literal(struct parser_params *p, const NODE *const node) -{ - return rb_node_case_when_optimizable_literal(node); -} -#endif +static const +struct st_hash_type literal_type = { + literal_cmp, + literal_hash, +}; + +static int nd_type_st_key_enable_p(NODE *node); static void check_literal_when(struct parser_params *p, NODE *arg, const YYLTYPE *loc) { - VALUE lit; - + /* See https://bugs.ruby-lang.org/issues/20331 for discussion about what is warned. */ if (!arg || !p->case_labels) return; + if (!nd_type_st_key_enable_p(arg)) return; - lit = rb_parser_node_case_when_optimizable_literal(p, arg); - if (UNDEF_P(lit)) return; - - if (NIL_P(p->case_labels)) { - p->case_labels = rb_obj_hide(rb_hash_new()); + if (p->case_labels == CHECK_LITERAL_WHEN) { + p->case_labels = st_init_table(&literal_type); } else { - VALUE line = rb_hash_lookup(p->case_labels, lit); - if (!NIL_P(line)) { + st_data_t line; + if (st_lookup(p->case_labels, (st_data_t)arg, &line)) { rb_warning1("duplicated 'when' clause with line %d is ignored", - WARN_IVAL(line)); + WARN_I((int)line)); return; } } - rb_hash_aset(p->case_labels, lit, INT2NUM(p->ruby_sourceline)); + st_insert(p->case_labels, (st_data_t)arg, (st_data_t)p->ruby_sourceline); } #ifdef RIPPER @@ -13670,7 +13740,7 @@ assignable(struct parser_params *p, ID id, NODE *val, const YYLTYPE *loc) case NODE_LASGN: return NEW_LASGN(id, val, loc); case NODE_GASGN: return NEW_GASGN(id, val, loc); case NODE_IASGN: return NEW_IASGN(id, val, loc); - case NODE_CDECL: return NEW_CDECL(id, val, 0, loc); + case NODE_CDECL: return NEW_CDECL(id, val, 0, p->ctxt.shareable_constant_value, loc); case NODE_CVASGN: return NEW_CVASGN(id, val, loc); } /* TODO: FIXME */ @@ -13744,11 +13814,44 @@ new_bv(struct parser_params *p, ID name) } if (!shadowing_lvar_0(p, name)) return; dyna_var(p, name); + ID *vidp = 0; + if (dvar_defined_ref(p, name, &vidp)) { + if (vidp) *vidp |= LVAR_USED; + } +} + +static void +aryset_check(struct parser_params *p, NODE *args) +{ + NODE *block = 0, *kwds = 0; + if (args && nd_type_p(args, NODE_BLOCK_PASS)) { + block = RNODE_BLOCK_PASS(args)->nd_body; + args = RNODE_BLOCK_PASS(args)->nd_head; + } + if (args && nd_type_p(args, NODE_ARGSCAT)) { + args = RNODE_ARGSCAT(args)->nd_body; + } + if (args && nd_type_p(args, NODE_ARGSPUSH)) { + kwds = RNODE_ARGSPUSH(args)->nd_body; + } + else { + for (NODE *next = args; next && nd_type_p(next, NODE_LIST); + next = RNODE_LIST(next)->nd_next) { + kwds = RNODE_LIST(next)->nd_head; + } + } + if (kwds && nd_type_p(kwds, NODE_HASH) && !RNODE_HASH(kwds)->nd_brace) { + yyerror1(&kwds->nd_loc, "keyword arg given in index"); + } + if (block) { + yyerror1(&block->nd_loc, "block arg given in index"); + } } static NODE * aryset(struct parser_params *p, NODE *recv, NODE *idx, const YYLTYPE *loc) { + aryset_check(p, idx); return NEW_ATTRASGN(recv, tASET, idx, loc); } @@ -13912,256 +14015,8 @@ mark_lvar_used(struct parser_params *p, NODE *rhs) } } -static NODE * -const_decl_path(struct parser_params *p, NODE **dest) -{ - NODE *n = *dest; - if (!nd_type_p(n, NODE_CALL)) { - const YYLTYPE *loc = &n->nd_loc; - VALUE path = rb_node_const_decl_val(n); - *dest = n = NEW_LIT(path, loc); - RB_OBJ_WRITTEN(p->ast, Qnil, RNODE_LIT(n)->nd_lit); - } - return n; -} - -static NODE * -make_shareable_node(struct parser_params *p, NODE *value, bool copy, const YYLTYPE *loc) -{ - NODE *fcore = NEW_LIT(rb_mRubyVMFrozenCore, loc); - - if (copy) { - return NEW_CALL(fcore, rb_intern("make_shareable_copy"), - NEW_LIST(value, loc), loc); - } - else { - return NEW_CALL(fcore, rb_intern("make_shareable"), - NEW_LIST(value, loc), loc); - } -} - -static NODE * -ensure_shareable_node(struct parser_params *p, NODE **dest, NODE *value, const YYLTYPE *loc) -{ - NODE *fcore = NEW_LIT(rb_mRubyVMFrozenCore, loc); - NODE *args = NEW_LIST(value, loc); - args = list_append(p, args, const_decl_path(p, dest)); - return NEW_CALL(fcore, rb_intern("ensure_shareable"), args, loc); -} - static int is_static_content(NODE *node); -static VALUE -shareable_literal_value(struct parser_params *p, NODE *node) -{ - if (!node) return Qnil; - enum node_type type = nd_type(node); - switch (type) { - case NODE_TRUE: - return Qtrue; - case NODE_FALSE: - return Qfalse; - case NODE_NIL: - return Qnil; - case NODE_SYM: - return rb_node_sym_string_val(node); - case NODE_LINE: - return rb_node_line_lineno_val(node); - case NODE_INTEGER: - return rb_node_integer_literal_val(node); - case NODE_FLOAT: - return rb_node_float_literal_val(node); - case NODE_RATIONAL: - return rb_node_rational_literal_val(node); - case NODE_IMAGINARY: - return rb_node_imaginary_literal_val(node); - case NODE_ENCODING: - return rb_node_encoding_val(node); - case NODE_REGX: - return rb_node_regx_string_val(node); - case NODE_LIT: - return RNODE_LIT(node)->nd_lit; - default: - return Qundef; - } -} - -#ifndef SHAREABLE_BARE_EXPRESSION -#define SHAREABLE_BARE_EXPRESSION 1 -#endif - -static NODE * -shareable_literal_constant(struct parser_params *p, enum shareability shareable, - NODE **dest, NODE *value, const YYLTYPE *loc, size_t level) -{ -# define shareable_literal_constant_next(n) \ - shareable_literal_constant(p, shareable, dest, (n), &(n)->nd_loc, level+1) - VALUE lit = Qnil; - - if (!value) return 0; - enum node_type type = nd_type(value); - switch (type) { - case NODE_TRUE: - case NODE_FALSE: - case NODE_NIL: - case NODE_LIT: - case NODE_SYM: - case NODE_REGX: - case NODE_LINE: - case NODE_INTEGER: - case NODE_FLOAT: - case NODE_RATIONAL: - case NODE_IMAGINARY: - case NODE_ENCODING: - return value; - - case NODE_DSTR: - if (shareable == shareable_literal) { - value = NEW_CALL(value, idUMinus, 0, loc); - } - return value; - - case NODE_STR: - lit = rb_str_to_interned_str(rb_node_str_string_val(value)); - value = NEW_LIT(lit, loc); - RB_OBJ_WRITE(p->ast, &RNODE_LIT(value)->nd_lit, lit); - return value; - - case NODE_FILE: - lit = rb_str_to_interned_str(rb_node_file_path_val(value)); - value = NEW_LIT(lit, loc); - RB_OBJ_WRITTEN(p->ast, Qnil, RNODE_LIT(value)->nd_lit); - return value; - - case NODE_ZLIST: - lit = rb_ary_new(); - OBJ_FREEZE_RAW(lit); - NODE *n = NEW_LIT(lit, loc); - RB_OBJ_WRITTEN(p->ast, Qnil, RNODE_LIT(n)->nd_lit); - return n; - - case NODE_LIST: - lit = rb_ary_new(); - for (NODE *n = value; n; n = RNODE_LIST(n)->nd_next) { - NODE *elt = RNODE_LIST(n)->nd_head; - if (elt) { - elt = shareable_literal_constant_next(elt); - if (elt) { - RNODE_LIST(n)->nd_head = elt; - } - else if (RTEST(lit)) { - rb_ary_clear(lit); - lit = Qfalse; - } - } - if (RTEST(lit)) { - VALUE e = shareable_literal_value(p, elt); - if (!UNDEF_P(e)) { - rb_ary_push(lit, e); - } - else { - rb_ary_clear(lit); - lit = Qnil; /* make shareable at runtime */ - } - } - } - break; - - case NODE_HASH: - if (!RNODE_HASH(value)->nd_brace) return 0; - lit = rb_hash_new(); - for (NODE *n = RNODE_HASH(value)->nd_head; n; n = RNODE_LIST(RNODE_LIST(n)->nd_next)->nd_next) { - NODE *key = RNODE_LIST(n)->nd_head; - NODE *val = RNODE_LIST(RNODE_LIST(n)->nd_next)->nd_head; - if (key) { - key = shareable_literal_constant_next(key); - if (key) { - RNODE_LIST(n)->nd_head = key; - } - else if (RTEST(lit)) { - rb_hash_clear(lit); - lit = Qfalse; - } - } - if (val) { - val = shareable_literal_constant_next(val); - if (val) { - RNODE_LIST(RNODE_LIST(n)->nd_next)->nd_head = val; - } - else if (RTEST(lit)) { - rb_hash_clear(lit); - lit = Qfalse; - } - } - if (RTEST(lit)) { - VALUE k = shareable_literal_value(p, key); - VALUE v = shareable_literal_value(p, val); - if (!UNDEF_P(k) && !UNDEF_P(v)) { - rb_hash_aset(lit, k, v); - } - else { - rb_hash_clear(lit); - lit = Qnil; /* make shareable at runtime */ - } - } - } - break; - - default: - if (shareable == shareable_literal && - (SHAREABLE_BARE_EXPRESSION || level > 0)) { - return ensure_shareable_node(p, dest, value, loc); - } - return 0; - } - - /* Array or Hash */ - if (!lit) return 0; - if (NIL_P(lit)) { - // if shareable_literal, all elements should have been ensured - // as shareable - value = make_shareable_node(p, value, false, loc); - } - else { - value = NEW_LIT(rb_ractor_make_shareable(lit), loc); - RB_OBJ_WRITTEN(p->ast, Qnil, RNODE_LIT(value)->nd_lit); - } - - return value; -# undef shareable_literal_constant_next -} - -static NODE * -shareable_constant_value(struct parser_params *p, enum shareability shareable, - NODE *lhs, NODE *value, const YYLTYPE *loc) -{ - if (!value) return 0; - switch (shareable) { - case shareable_none: - return value; - - case shareable_literal: - { - NODE *lit = shareable_literal_constant(p, shareable, &lhs, value, loc, 0); - if (lit) return lit; - return value; - } - break; - - case shareable_copy: - case shareable_everything: - { - NODE *lit = shareable_literal_constant(p, shareable, &lhs, value, loc, 0); - if (lit) return lit; - return make_shareable_node(p, value, shareable == shareable_copy, loc); - } - break; - - default: - UNREACHABLE_RETURN(0); - } -} - static NODE * node_assign(struct parser_params *p, NODE *lhs, NODE *rhs, struct lex_context ctxt, const YYLTYPE *loc) { @@ -14169,9 +14024,6 @@ node_assign(struct parser_params *p, NODE *lhs, NODE *rhs, struct lex_context ct switch (nd_type(lhs)) { case NODE_CDECL: - rhs = shareable_constant_value(p, ctxt.shareable_constant_value, lhs, rhs, loc); - /* fallthru */ - case NODE_GASGN: case NODE_IASGN: case NODE_LASGN: @@ -14358,7 +14210,6 @@ void_expr(struct parser_params *p, NODE *node) case NODE_CONST: useless = "a constant"; break; - case NODE_LIT: case NODE_SYM: case NODE_LINE: case NODE_FILE: @@ -14504,7 +14355,6 @@ is_static_content(NODE *node) do { if (!is_static_content(RNODE_LIST(node)->nd_head)) return 0; } while ((node = RNODE_LIST(node)->nd_next) != 0); - case NODE_LIT: case NODE_SYM: case NODE_REGX: case NODE_LINE: @@ -14639,23 +14489,9 @@ cond0(struct parser_params *p, NODE *node, enum cond_type type, const YYLTYPE *l case NODE_SYM: case NODE_DSYM: - warn_symbol: SWITCH_BY_COND_TYPE(type, warning, "symbol "); break; - case NODE_LIT: - if (RNODE_LIT(node)->nd_lit == Qtrue || - RNODE_LIT(node)->nd_lit == Qfalse) { - /* booleans are OK, e.g., while true */ - } - else if (SYMBOL_P(RNODE_LIT(node)->nd_lit)) { - goto warn_symbol; - } - else { - SWITCH_BY_COND_TYPE(type, warning, ""); - } - break; - case NODE_LINE: SWITCH_BY_COND_TYPE(type, warning, ""); break; @@ -14827,10 +14663,10 @@ new_args(struct parser_params *p, rb_node_args_aux_t *pre_args, rb_node_opt_arg_ rest_arg = idFWD_REST; } - args->pre_args_num = pre_args ? rb_long2int(pre_args->nd_plen) : 0; + args->pre_args_num = pre_args ? pre_args->nd_plen : 0; args->pre_init = pre_args ? pre_args->nd_next : 0; - args->post_args_num = post_args ? rb_long2int(post_args->nd_plen) : 0; + args->post_args_num = post_args ? post_args->nd_plen : 0; args->post_init = post_args ? post_args->nd_next : 0; args->first_post_arg = post_args ? post_args->nd_pid : 0; @@ -15018,7 +14854,6 @@ dsym_node(struct parser_params *p, NODE *node, const YYLTYPE *loc) return node; } -#ifndef RIPPER static int nd_type_st_key_enable_p(NODE *node) { @@ -15039,6 +14874,7 @@ nd_type_st_key_enable_p(NODE *node) } } +#ifndef RIPPER static VALUE nd_value(struct parser_params *p, NODE *node) { @@ -15072,14 +14908,7 @@ nd_value(struct parser_params *p, NODE *node) void rb_parser_warn_duplicate_keys(struct parser_params *p, NODE *hash) { -#ifndef UNIVERSAL_PARSER - static const -#endif - struct st_hash_type literal_type = { - literal_cmp, - literal_hash, - }; - + /* See https://bugs.ruby-lang.org/issues/20331 for discussion about what is warned. */ st_table *literal_keys = st_init_table_with_size(&literal_type, RNODE_LIST(hash)->as.nd_alen / 2); while (hash && RNODE_LIST(hash)->nd_next) { NODE *head = RNODE_LIST(hash)->nd_head; @@ -15157,28 +14986,12 @@ new_op_assign(struct parser_params *p, NODE *lhs, ID op, NODE *rhs, struct lex_c if (lhs) { ID vid = get_nd_vid(p, lhs); YYLTYPE lhs_loc = lhs->nd_loc; - int shareable = ctxt.shareable_constant_value; - if (shareable) { - switch (nd_type(lhs)) { - case NODE_CDECL: - case NODE_COLON2: - case NODE_COLON3: - break; - default: - shareable = 0; - break; - } - } if (op == tOROP) { - rhs = shareable_constant_value(p, shareable, lhs, rhs, &rhs->nd_loc); set_nd_value(p, lhs, rhs); nd_set_loc(lhs, loc); asgn = NEW_OP_ASGN_OR(gettable(p, vid, &lhs_loc), lhs, loc); } else if (op == tANDOP) { - if (shareable) { - rhs = shareable_constant_value(p, shareable, lhs, rhs, &rhs->nd_loc); - } set_nd_value(p, lhs, rhs); nd_set_loc(lhs, loc); asgn = NEW_OP_ASGN_AND(gettable(p, vid, &lhs_loc), lhs, loc); @@ -15186,9 +14999,6 @@ new_op_assign(struct parser_params *p, NODE *lhs, ID op, NODE *rhs, struct lex_c else { asgn = lhs; rhs = NEW_CALL(gettable(p, vid, &lhs_loc), op, NEW_LIST(rhs, &rhs->nd_loc), loc); - if (shareable) { - rhs = shareable_constant_value(p, shareable, lhs, rhs, &rhs->nd_loc); - } set_nd_value(p, asgn, rhs); nd_set_loc(asgn, loc); } @@ -15205,6 +15015,7 @@ new_ary_op_assign(struct parser_params *p, NODE *ary, { NODE *asgn; + aryset_check(p, args); args = make_list(args, args_loc); asgn = NEW_OP_ASGN1(ary, op, args, rhs, loc); fixpos(asgn, ary); @@ -15228,8 +15039,7 @@ new_const_op_assign(struct parser_params *p, NODE *lhs, ID op, NODE *rhs, struct NODE *asgn; if (lhs) { - rhs = shareable_constant_value(p, ctxt.shareable_constant_value, lhs, rhs, loc); - asgn = NEW_OP_CDECL(lhs, op, rhs, loc); + asgn = NEW_OP_CDECL(lhs, op, rhs, ctxt.shareable_constant_value, loc); } else { asgn = NEW_ERROR(loc); @@ -15244,7 +15054,7 @@ const_decl(struct parser_params *p, NODE *path, const YYLTYPE *loc) if (p->ctxt.in_def) { yyerror1(loc, "dynamic constant assignment"); } - return NEW_CDECL(0, 0, (path), loc); + return NEW_CDECL(0, 0, (path), p->ctxt.shareable_constant_value, loc); } #ifdef RIPPER static VALUE @@ -15973,7 +15783,7 @@ parser_initialize(struct parser_params *p) p->error_buffer = Qfalse; p->end_expect_token_locations = NULL; p->token_id = 0; - p->tokens = Qnil; + p->tokens = NULL; #else p->result = Qnil; p->parsing_thread = Qnil; @@ -16001,12 +15811,10 @@ rb_ruby_parser_mark(void *ptr) rb_gc_mark(p->lex.input); rb_gc_mark(p->ruby_sourcefile_string); rb_gc_mark((VALUE)p->ast); - rb_gc_mark(p->case_labels); rb_gc_mark(p->delayed.token); #ifndef RIPPER rb_gc_mark(p->debug_lines); rb_gc_mark(p->error_buffer); - rb_gc_mark(p->tokens); #else rb_gc_mark(p->value); rb_gc_mark(p->result); @@ -16017,9 +15825,6 @@ rb_ruby_parser_mark(void *ptr) #endif rb_gc_mark(p->debug_buffer); rb_gc_mark(p->debug_output); -#ifdef YYMALLOC - rb_gc_mark((VALUE)p->heap); -#endif } void @@ -16028,6 +15833,12 @@ rb_ruby_parser_free(void *ptr) struct parser_params *p = (struct parser_params*)ptr; struct local_vars *local, *prev; +#ifndef RIPPER + if (p->tokens) { + rb_parser_tokens_free(p, p->tokens); + } +#endif + if (p->tokenbuf) { ruby_sized_xfree(p->tokenbuf, p->toksiz); } @@ -16050,6 +15861,10 @@ rb_ruby_parser_free(void *ptr) st_free_table(p->pvtbl); } + if (CASE_LABELS_ENABLED_P(p->case_labels)) { + st_free_table(p->case_labels); + } + xfree(ptr); } @@ -16145,8 +15960,7 @@ void rb_ruby_parser_keep_tokens(rb_parser_t *p) { p->keep_tokens = 1; - // TODO - p->tokens = rb_ary_new(); + p->tokens = rb_parser_ary_new_capa(p, 10); } #ifndef UNIVERSAL_PARSER @@ -16453,6 +16267,16 @@ rb_ruby_ripper_lex_state_name(struct parser_params *p, int state) return rb_parser_lex_state_name(p, (enum lex_state_e)state); } +#ifdef UNIVERSAL_PARSER +rb_parser_t * +rb_ripper_parser_params_allocate(const rb_parser_config_t *config) +{ + rb_parser_t *p = (rb_parser_t *)config->calloc(1, sizeof(rb_parser_t)); + p->config = config; + return p; +} +#endif + struct parser_params* rb_ruby_ripper_parser_allocate(void) { @@ -16461,69 +16285,6 @@ rb_ruby_ripper_parser_allocate(void) #endif /* RIPPER */ #ifndef RIPPER -#ifdef YYMALLOC -#define HEAPCNT(n, size) ((n) * (size) / sizeof(YYSTYPE)) -/* Keep the order; NEWHEAP then xmalloc and ADD2HEAP to get rid of - * potential memory leak */ -#define NEWHEAP() rb_imemo_tmpbuf_parser_heap(0, p->heap, 0) -#define ADD2HEAP(new, cnt, ptr) ((p->heap = (new))->ptr = (ptr), \ - (new)->cnt = (cnt), (ptr)) - -void * -rb_parser_malloc(struct parser_params *p, size_t size) -{ - size_t cnt = HEAPCNT(1, size); - rb_imemo_tmpbuf_t *n = NEWHEAP(); - void *ptr = xmalloc(size); - - return ADD2HEAP(n, cnt, ptr); -} - -void * -rb_parser_calloc(struct parser_params *p, size_t nelem, size_t size) -{ - size_t cnt = HEAPCNT(nelem, size); - rb_imemo_tmpbuf_t *n = NEWHEAP(); - void *ptr = xcalloc(nelem, size); - - return ADD2HEAP(n, cnt, ptr); -} - -void * -rb_parser_realloc(struct parser_params *p, void *ptr, size_t size) -{ - rb_imemo_tmpbuf_t *n; - size_t cnt = HEAPCNT(1, size); - - if (ptr && (n = p->heap) != NULL) { - do { - if (n->ptr == ptr) { - n->ptr = ptr = xrealloc(ptr, size); - if (n->cnt) n->cnt = cnt; - return ptr; - } - } while ((n = n->next) != NULL); - } - n = NEWHEAP(); - ptr = xrealloc(ptr, size); - return ADD2HEAP(n, cnt, ptr); -} - -void -rb_parser_free(struct parser_params *p, void *ptr) -{ - rb_imemo_tmpbuf_t **prev = &p->heap, *n; - - while ((n = *prev) != NULL) { - if (n->ptr == ptr) { - *prev = n->next; - break; - } - prev = &n->next; - } -} -#endif - void rb_parser_printf(struct parser_params *p, const char *fmt, ...) { diff --git a/prism/api_pack.c b/prism/api_pack.c index c9f0b18a397bc7..98509ae65ccae0 100644 --- a/prism/api_pack.c +++ b/prism/api_pack.c @@ -1,5 +1,12 @@ #include "prism/extension.h" +#ifdef PRISM_EXCLUDE_PACK + +void +Init_prism_pack(void) {} + +#else + static VALUE rb_cPrism; static VALUE rb_cPrismPack; static VALUE rb_cPrismPackDirective; @@ -265,3 +272,5 @@ Init_prism_pack(void) { pack_symbol = ID2SYM(rb_intern("pack")); unpack_symbol = ID2SYM(rb_intern("unpack")); } + +#endif diff --git a/prism/config.yml b/prism/config.yml index 08d4f78c475583..6341233f83ec42 100644 --- a/prism/config.yml +++ b/prism/config.yml @@ -1,5 +1,6 @@ errors: - ALIAS_ARGUMENT + - ALIAS_ARGUMENT_NUMBERED_REFERENCE - AMPAMPEQ_MULTI_ASSIGN - ARGUMENT_AFTER_BLOCK - ARGUMENT_AFTER_FORWARDING_ELLIPSES @@ -15,6 +16,7 @@ errors: - ARGUMENT_NO_FORWARDING_AMP - ARGUMENT_NO_FORWARDING_ELLIPSES - ARGUMENT_NO_FORWARDING_STAR + - ARGUMENT_NO_FORWARDING_STAR_STAR - ARGUMENT_SPLAT_AFTER_ASSOC_SPLAT - ARGUMENT_SPLAT_AFTER_SPLAT - ARGUMENT_TERM_PAREN @@ -45,6 +47,7 @@ errors: - CLASS_SUPERCLASS - CLASS_TERM - CLASS_UNEXPECTED_END + - CLASS_VARIABLE_BARE - CONDITIONAL_ELSIF_PREDICATE - CONDITIONAL_IF_PREDICATE - CONDITIONAL_PREDICATE_TERM @@ -106,6 +109,7 @@ errors: - FOR_IN - FOR_INDEX - FOR_TERM + - GLOBAL_VARIABLE_BARE - HASH_EXPRESSION_AFTER_LABEL - HASH_KEY - HASH_ROCKET @@ -117,6 +121,8 @@ errors: - INCOMPLETE_VARIABLE_CLASS_3_3_0 - INCOMPLETE_VARIABLE_INSTANCE - INCOMPLETE_VARIABLE_INSTANCE_3_3_0 + - INSTANCE_VARIABLE_BARE + - INVALID_BLOCK_EXIT - INVALID_CHARACTER - INVALID_ENCODING_MAGIC_COMMENT - INVALID_FLOAT_EXPONENT @@ -130,8 +136,12 @@ errors: - INVALID_NUMBER_UNDERSCORE - INVALID_PERCENT - INVALID_PRINTABLE_CHARACTER + - INVALID_RETRY_AFTER_ELSE + - INVALID_RETRY_AFTER_ENSURE + - INVALID_RETRY_WITHOUT_RESCUE - INVALID_VARIABLE_GLOBAL - INVALID_VARIABLE_GLOBAL_3_3_0 + - INVALID_YIELD - IT_NOT_ALLOWED_NUMBERED - IT_NOT_ALLOWED_ORDINARY - LAMBDA_OPEN @@ -165,7 +175,7 @@ errors: - PARAMETER_BLOCK_MULTI - PARAMETER_CIRCULAR - PARAMETER_METHOD_NAME - - PARAMETER_NAME_REPEAT + - PARAMETER_NAME_DUPLICATED - PARAMETER_NO_DEFAULT - PARAMETER_NO_DEFAULT_KW - PARAMETER_NUMBERED_RESERVED @@ -174,6 +184,7 @@ errors: - PARAMETER_STAR - PARAMETER_UNEXPECTED_FWD - PARAMETER_WILD_LOOSE_COMMA + - PATTERN_CAPTURE_DUPLICATE - PATTERN_EXPRESSION_AFTER_BRACKET - PATTERN_EXPRESSION_AFTER_COMMA - PATTERN_EXPRESSION_AFTER_HROCKET @@ -185,6 +196,7 @@ errors: - PATTERN_EXPRESSION_AFTER_RANGE - PATTERN_EXPRESSION_AFTER_REST - PATTERN_HASH_KEY + - PATTERN_HASH_KEY_DUPLICATE - PATTERN_HASH_KEY_LABEL - PATTERN_IDENT_AFTER_HROCKET - PATTERN_LABEL_AFTER_COMMA @@ -198,12 +210,14 @@ errors: - REGEXP_INVALID_UNICODE_RANGE - REGEXP_NON_ESCAPED_MBC - REGEXP_TERM + - REGEXP_UNKNOWN_OPTIONS - REGEXP_UTF8_CHAR_NON_UTF8_REGEXP - RESCUE_EXPRESSION - RESCUE_MODIFIER_VALUE - RESCUE_TERM - RESCUE_VARIABLE - RETURN_INVALID + - SCRIPT_NOT_FOUND - SINGLETON_FOR_LITERALS - STATEMENT_ALIAS - STATEMENT_POSTEXE_END @@ -233,16 +247,29 @@ errors: warnings: - AMBIGUOUS_FIRST_ARGUMENT_MINUS - AMBIGUOUS_FIRST_ARGUMENT_PLUS + - AMBIGUOUS_PREFIX_AMPERSAND - AMBIGUOUS_PREFIX_STAR + - AMBIGUOUS_PREFIX_STAR_STAR - AMBIGUOUS_SLASH + - COMPARISON_AFTER_COMPARISON - DOT_DOT_DOT_EOL - EQUAL_IN_CONDITIONAL + - EQUAL_IN_CONDITIONAL_3_3_0 - END_IN_METHOD - DUPLICATED_HASH_KEY - DUPLICATED_WHEN_CLAUSE - FLOAT_OUT_OF_RANGE + - IGNORED_FROZEN_STRING_LITERAL - INTEGER_IN_FLIP_FLOP + - INVALID_CHARACTER + - INVALID_NUMBERED_REFERENCE + - INVALID_SHAREABLE_CONSTANT_VALUE - KEYWORD_EOL + - LITERAL_IN_CONDITION_DEFAULT + - LITERAL_IN_CONDITION_VERBOSE + - SHEBANG_CARRIAGE_RETURN + - UNEXPECTED_CARRIAGE_RETURN + - UNUSED_LOCAL_VARIABLE tokens: - name: EOF value: 1 @@ -613,6 +640,13 @@ flags: - name: HEXADECIMAL comment: "0x prefix" comment: Flags for integer nodes that correspond to the base of the integer. + - name: InterpolatedStringNodeFlags + values: + - name: FROZEN + comment: "frozen by virtue of a `frozen_string_literal: true` comment or `--enable-frozen-string-literal`; only for adjacent string literals like `'a' 'b'`" + - name: MUTABLE + comment: "mutable by virtue of a `frozen_string_literal: false` comment or `--disable-frozen-string-literal`; only for adjacent string literals like `'a' 'b'`" + comment: Flags for interpolated string nodes that indicated mutability if they are also marked as literals. - name: KeywordHashNodeFlags values: - name: SYMBOL_KEYS @@ -658,6 +692,15 @@ flags: - name: FORCED_US_ASCII_ENCODING comment: "internal bytes forced the encoding to US-ASCII" comment: Flags for regular expression and match last line nodes. + - name: ShareableConstantNodeFlags + values: + - name: LITERAL + comment: "constant writes that should be modified with shareable constant value literal" + - name: EXPERIMENTAL_EVERYTHING + comment: "constant writes that should be modified with shareable constant value experimental everything" + - name: EXPERIMENTAL_COPY + comment: "constant writes that should be modified with shareable constant value experimental copy" + comment: Flags for shareable constant nodes. - name: StringFlags values: - name: FORCED_UTF8_ENCODING @@ -665,7 +708,9 @@ flags: - name: FORCED_BINARY_ENCODING comment: "internal bytes forced the encoding to binary" - name: FROZEN - comment: "frozen by virtue of a `frozen_string_literal` comment" + comment: "frozen by virtue of a `frozen_string_literal: true` comment or `--enable-frozen-string-literal`" + - name: MUTABLE + comment: "mutable by virtue of a `frozen_string_literal: false` comment or `--disable-frozen-string-literal`" comment: Flags for string nodes. - name: SymbolFlags values: @@ -1302,12 +1347,37 @@ nodes: fields: - name: name type: constant + comment: | + The name of the class variable, which is a `@@` followed by an [identifier](https://github.com/ruby/prism/blob/main/docs/parsing_rules.md#identifiers). + + @@abc = 123 # name `@@abc` + + @@_test = :test # name `@@_test` - name: name_loc type: location + comment: | + The location of the variable name. + + @@foo = :bar + ^^^^^ - name: value type: node + comment: | + The value to assign to the class variable. Can be any node that + represents a non-void expression. + + @@foo = :bar + ^^^^ + + @@_xyz = 123 + ^^^ - name: operator_loc - type: location? + type: location + comment: | + The location of the `=` operator. + + @@foo = :bar + ^ comment: | Represents writing to a class variable. @@ -2118,12 +2188,37 @@ nodes: fields: - name: name type: constant + comment: | + The name of the instance variable, which is a `@` followed by an [identifier](https://github.com/ruby/prism/blob/main/docs/parsing_rules.md#identifiers). + + @x = :y # name `:@x` + + @_foo = "bar" # name `@_foo` - name: name_loc type: location + comment: | + The location of the variable name. + + @_x = 1 + ^^^ - name: value type: node + comment: | + The value to assign to the instance variable. Can be any node that + represents a non-void expression. + + @foo = :bar + ^^^^ + + @_x = 1234 + ^^^^ - name: operator_loc type: location + comment: | + The location of the `=` operator. + + @x = y + ^ comment: | Represents writing to an instance variable. @@ -2186,6 +2281,9 @@ nodes: ^^^^^^^^^^^^^^^^ - name: InterpolatedStringNode fields: + - name: flags + type: flags + kind: InterpolatedStringNodeFlags - name: opening_loc type: location? - name: parts @@ -2617,13 +2715,13 @@ nodes: - name: number type: uint32 comment: | - The (1-indexed, from the left) number of the capture group. Numbered references that would overflow a `uint32` result in a `number` of exactly `2**32 - 1`. + The (1-indexed, from the left) number of the capture group. Numbered references that are too large result in this value being `0`. $1 # number `1` $5432 # number `5432` - $4294967296 # number `4294967295` + $4294967296 # number `0` comment: | Represents reading a numbered reference to a capture in the previous match. @@ -3002,6 +3100,29 @@ nodes: self ^^^^ + - name: ShareableConstantNode + fields: + - name: flags + type: flags + kind: ShareableConstantNodeFlags + - name: write + type: node + kind: + - ConstantWriteNode + - ConstantAndWriteNode + - ConstantOrWriteNode + - ConstantOperatorWriteNode + - ConstantPathWriteNode + - ConstantPathAndWriteNode + - ConstantPathOrWriteNode + - ConstantPathOperatorWriteNode + comment: The constant write that should be modified with the shareability state. + comment: | + This node wraps a constant write to indicate that when the value is written, it should have its shareability state modified. + + # shareable_constant_value: literal + C = { a: 1 } + ^^^^^^^^^^^^ - name: SingletonClassNode fields: - name: locals @@ -3029,6 +3150,9 @@ nodes: ^^^^^^^^^^^^ - name: SourceFileNode fields: + - name: flags + type: flags + kind: StringFlags - name: filepath type: string comment: | diff --git a/prism/defines.h b/prism/defines.h index 5995d54cb8d941..849ca6d05160ff 100644 --- a/prism/defines.h +++ b/prism/defines.h @@ -10,6 +10,7 @@ #define PRISM_DEFINES_H #include +#include #include #include #include @@ -22,7 +23,6 @@ * some platforms they aren't included unless this is already defined. */ #define __STDC_FORMAT_MACROS - #include /** @@ -49,7 +49,11 @@ * compiler-agnostic way. */ #if defined(__GNUC__) -# define PRISM_ATTRIBUTE_FORMAT(string_index, argument_index) __attribute__((format(printf, string_index, argument_index))) +# if defined(__MINGW_PRINTF_FORMAT) +# define PRISM_ATTRIBUTE_FORMAT(string_index, argument_index) __attribute__((format(__MINGW_PRINTF_FORMAT, string_index, argument_index))) +# else +# define PRISM_ATTRIBUTE_FORMAT(string_index, argument_index) __attribute__((format(printf, string_index, argument_index))) +# endif #elif defined(__clang__) # define PRISM_ATTRIBUTE_FORMAT(string_index, argument_index) __attribute__((__format__(__printf__, string_index, argument_index))) #else @@ -147,7 +151,7 @@ #else #ifndef xmalloc /** - * The malloc function that should be used. This can be overriden with + * The malloc function that should be used. This can be overridden with * the PRISM_XALLOCATOR define. */ #define xmalloc malloc @@ -155,7 +159,7 @@ #ifndef xrealloc /** - * The realloc function that should be used. This can be overriden with + * The realloc function that should be used. This can be overridden with * the PRISM_XALLOCATOR define. */ #define xrealloc realloc @@ -163,7 +167,7 @@ #ifndef xcalloc /** - * The calloc function that should be used. This can be overriden with + * The calloc function that should be used. This can be overridden with * the PRISM_XALLOCATOR define. */ #define xcalloc calloc @@ -171,11 +175,32 @@ #ifndef xfree /** - * The free function that should be used. This can be overriden with the + * The free function that should be used. This can be overridden with the * PRISM_XALLOCATOR define. */ #define xfree free #endif #endif +/** + * If PRISM_BUILD_MINIMAL is defined, then we're going to define every possible + * switch that will turn off certain features of prism. + */ +#ifdef PRISM_BUILD_MINIMAL + /** Exclude the serialization API. */ + #define PRISM_EXCLUDE_SERIALIZATION + + /** Exclude the JSON serialization API. */ + #define PRISM_EXCLUDE_JSON + + /** Exclude the Array#pack parser API. */ + #define PRISM_EXCLUDE_PACK + + /** Exclude the prettyprint API. */ + #define PRISM_EXCLUDE_PRETTYPRINT + + /** Exclude the full set of encodings, using the minimal only. */ + #define PRISM_ENCODING_EXCLUDE_FULL +#endif + #endif diff --git a/prism/encoding.c b/prism/encoding.c index dc63cccc2db2a4..a4aeed104f89b9 100644 --- a/prism/encoding.c +++ b/prism/encoding.c @@ -2358,6 +2358,8 @@ pm_encoding_utf_8_isupper_char(const uint8_t *b, ptrdiff_t n) { } } +#ifndef PRISM_ENCODING_EXCLUDE_FULL + static pm_unicode_codepoint_t pm_cesu_8_codepoint(const uint8_t *b, ptrdiff_t n, size_t *width) { if (b[0] < 0x80) { @@ -2452,6 +2454,8 @@ pm_encoding_cesu_8_isupper_char(const uint8_t *b, ptrdiff_t n) { } } +#endif + #undef UNICODE_ALPHA_CODEPOINTS_LENGTH #undef UNICODE_ALNUM_CODEPOINTS_LENGTH #undef UNICODE_ISUPPER_CODEPOINTS_LENGTH @@ -2480,6 +2484,8 @@ static const uint8_t pm_encoding_ascii_table[256] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // Fx }; +#ifndef PRISM_ENCODING_EXCLUDE_FULL + /** * Each element of the following table contains a bitfield that indicates a * piece of information about the corresponding CP850 character. @@ -3918,6 +3924,7 @@ PRISM_ENCODING_TABLE(windows_1258) PRISM_ENCODING_TABLE(windows_874) #undef PRISM_ENCODING_TABLE +#endif /** * Returns the size of the next character in the ASCII encoding. This basically @@ -3976,22 +3983,129 @@ pm_encoding_ascii_isupper_char(const uint8_t *b, PRISM_ATTRIBUTE_UNUSED ptrdiff_ } /** - * Certain encodings are equivalent to ASCII below 0x80, so it works for our - * purposes to have a function here that first checks the bounds and then falls - * back to checking the ASCII lookup table. + * For a lot of encodings the default is that they are a single byte long no + * matter what the codepoint, so this function is shared between them. + */ +static size_t +pm_encoding_single_char_width(PRISM_ATTRIBUTE_UNUSED const uint8_t *b, PRISM_ATTRIBUTE_UNUSED ptrdiff_t n) { + return 1; +} + +/** + * Returns the size of the next character in the EUC-JP encoding, or 0 if a + * character cannot be decoded from the given bytes. + */ +static size_t +pm_encoding_euc_jp_char_width(const uint8_t *b, ptrdiff_t n) { + // These are the single byte characters. + if (*b < 0x80) { + return 1; + } + + // These are the double byte characters. + if ((n > 1) && ((b[0] == 0x8E) || (b[0] >= 0xA1 && b[0] <= 0xFE)) && (b[1] >= 0xA1 && b[1] <= 0xFE)) { + return 2; + } + + // These are the triple byte characters. + if ((n > 2) && (b[0] == 0x8F) && (b[1] >= 0xA1 && b[2] <= 0xFE) && (b[2] >= 0xA1 && b[2] <= 0xFE)) { + return 3; + } + + return 0; +} + +/** + * Returns the size of the next character in the EUC-JP encoding if it is an + * uppercase character. */ static bool -pm_encoding_ascii_isupper_char_7bit(const uint8_t *b, ptrdiff_t n) { - return (*b < 0x80) && pm_encoding_ascii_isupper_char(b, n); +pm_encoding_euc_jp_isupper_char(const uint8_t *b, ptrdiff_t n) { + size_t width = pm_encoding_euc_jp_char_width(b, n); + + if (width == 1) { + return pm_encoding_ascii_isupper_char(b, n); + } else if (width == 2) { + return ( + (b[0] == 0xA3 && b[1] >= 0xC1 && b[1] <= 0xDA) || + (b[0] == 0xA6 && b[1] >= 0xA1 && b[1] <= 0xB8) || + (b[0] == 0xA7 && b[1] >= 0xA1 && b[1] <= 0xC1) + ); + } else { + return false; + } } /** - * For a lot of encodings the default is that they are a single byte long no - * matter what the codepoint, so this function is shared between them. + * Returns the size of the next character in the Shift_JIS encoding, or 0 if a + * character cannot be decoded from the given bytes. */ static size_t -pm_encoding_single_char_width(PRISM_ATTRIBUTE_UNUSED const uint8_t *b, PRISM_ATTRIBUTE_UNUSED ptrdiff_t n) { - return 1; +pm_encoding_shift_jis_char_width(const uint8_t *b, ptrdiff_t n) { + // These are the single byte characters. + if (b[0] < 0x80 || (b[0] >= 0xA1 && b[0] <= 0xDF)) { + return 1; + } + + // These are the double byte characters. + if ((n > 1) && ((b[0] >= 0x81 && b[0] <= 0x9F) || (b[0] >= 0xE0 && b[0] <= 0xFC)) && (b[1] >= 0x40 && b[1] <= 0xFC && b[1] != 0x7F)) { + return 2; + } + + return 0; +} + +/** + * Returns the size of the next character in the Shift_JIS encoding if it is an + * alphanumeric character. + */ +static size_t +pm_encoding_shift_jis_alnum_char(const uint8_t *b, ptrdiff_t n) { + size_t width = pm_encoding_shift_jis_char_width(b, n); + return width == 1 ? ((b[0] >= 0x80) || pm_encoding_ascii_alnum_char(b, n)) : width; +} + +/** + * Returns the size of the next character in the Shift_JIS encoding if it is an + * alphabetical character. + */ +static size_t +pm_encoding_shift_jis_alpha_char(const uint8_t *b, ptrdiff_t n) { + size_t width = pm_encoding_shift_jis_char_width(b, n); + return width == 1 ? ((b[0] >= 0x80) || pm_encoding_ascii_alpha_char(b, n)) : width; +} + +/** + * Returns the size of the next character in the Shift_JIS encoding if it is an + * uppercase character. + */ +static bool +pm_encoding_shift_jis_isupper_char(const uint8_t *b, ptrdiff_t n) { + size_t width = pm_encoding_shift_jis_char_width(b, n); + + if (width == 1) { + return pm_encoding_ascii_isupper_char(b, n); + } else if (width == 2) { + return ( + ((b[0] == 0x82) && (b[1] >= 0x60 && b[1] <= 0x79)) || + ((b[0] == 0x83) && (b[1] >= 0x9F && b[1] <= 0xB6)) || + ((b[0] == 0x84) && (b[1] >= 0x40 && b[1] <= 0x60)) + ); + } else { + return width; + } +} + +#ifndef PRISM_ENCODING_EXCLUDE_FULL + +/** + * Certain encodings are equivalent to ASCII below 0x80, so it works for our + * purposes to have a function here that first checks the bounds and then falls + * back to checking the ASCII lookup table. + */ +static bool +pm_encoding_ascii_isupper_char_7bit(const uint8_t *b, ptrdiff_t n) { + return (*b < 0x80) && pm_encoding_ascii_isupper_char(b, n); } /** @@ -4075,51 +4189,6 @@ pm_encoding_emacs_mule_char_width(const uint8_t *b, ptrdiff_t n) { return 0; } -/** - * Returns the size of the next character in the EUC-JP encoding, or 0 if a - * character cannot be decoded from the given bytes. - */ -static size_t -pm_encoding_euc_jp_char_width(const uint8_t *b, ptrdiff_t n) { - // These are the single byte characters. - if (*b < 0x80) { - return 1; - } - - // These are the double byte characters. - if ((n > 1) && ((b[0] == 0x8E) || (b[0] >= 0xA1 && b[0] <= 0xFE)) && (b[1] >= 0xA1 && b[1] <= 0xFE)) { - return 2; - } - - // These are the triple byte characters. - if ((n > 2) && (b[0] == 0x8F) && (b[1] >= 0xA1 && b[2] <= 0xFE) && (b[2] >= 0xA1 && b[2] <= 0xFE)) { - return 3; - } - - return 0; -} - -/** - * Returns the size of the next character in the EUC-JP encoding if it is an - * uppercase character. - */ -static bool -pm_encoding_euc_jp_isupper_char(const uint8_t *b, ptrdiff_t n) { - size_t width = pm_encoding_euc_jp_char_width(b, n); - - if (width == 1) { - return pm_encoding_ascii_isupper_char(b, n); - } else if (width == 2) { - return ( - (b[0] == 0xA3 && b[1] >= 0xC1 && b[1] <= 0xDA) || - (b[0] == 0xA6 && b[1] >= 0xA1 && b[1] <= 0xB8) || - (b[0] == 0xA7 && b[1] >= 0xA1 && b[1] <= 0xC1) - ); - } else { - return false; - } -} - /** * Returns the size of the next character in the EUC-KR encoding, or 0 if a * character cannot be decoded from the given bytes. @@ -4218,65 +4287,7 @@ pm_encoding_gbk_char_width(const uint8_t *b, ptrdiff_t n) { return 0; } -/** - * Returns the size of the next character in the Shift_JIS encoding, or 0 if a - * character cannot be decoded from the given bytes. - */ -static size_t -pm_encoding_shift_jis_char_width(const uint8_t *b, ptrdiff_t n) { - // These are the single byte characters. - if (b[0] < 0x80 || (b[0] >= 0xA1 && b[0] <= 0xDF)) { - return 1; - } - - // These are the double byte characters. - if ((n > 1) && ((b[0] >= 0x81 && b[0] <= 0x9F) || (b[0] >= 0xE0 && b[0] <= 0xFC)) && (b[1] >= 0x40 && b[1] <= 0xFC && b[1] != 0x7F)) { - return 2; - } - - return 0; -} - -/** - * Returns the size of the next character in the Shift_JIS encoding if it is an - * alphanumeric character. - */ -static size_t -pm_encoding_shift_jis_alnum_char(const uint8_t *b, ptrdiff_t n) { - size_t width = pm_encoding_shift_jis_char_width(b, n); - return width == 1 ? ((b[0] >= 0x80) || pm_encoding_ascii_alnum_char(b, n)) : width; -} - -/** - * Returns the size of the next character in the Shift_JIS encoding if it is an - * alphabetical character. - */ -static size_t -pm_encoding_shift_jis_alpha_char(const uint8_t *b, ptrdiff_t n) { - size_t width = pm_encoding_shift_jis_char_width(b, n); - return width == 1 ? ((b[0] >= 0x80) || pm_encoding_ascii_alpha_char(b, n)) : width; -} - -/** - * Returns the size of the next character in the Shift_JIS encoding if it is an - * uppercase character. - */ -static bool -pm_encoding_shift_jis_isupper_char(const uint8_t *b, ptrdiff_t n) { - size_t width = pm_encoding_shift_jis_char_width(b, n); - - if (width == 1) { - return pm_encoding_ascii_isupper_char(b, n); - } else if (width == 2) { - return ( - ((b[0] == 0x82) && (b[1] >= 0x60 && b[1] <= 0x79)) || - ((b[0] == 0x83) && (b[1] >= 0x9F && b[1] <= 0xB6)) || - ((b[0] == 0x84) && (b[1] >= 0x40 && b[1] <= 0x60)) - ); - } else { - return width; - } -} +#endif /** * This is the table of all of the encodings that prism supports. @@ -4290,6 +4301,14 @@ const pm_encoding_t pm_encodings[] = { .isupper_char = pm_encoding_utf_8_isupper_char, .multibyte = true }, + [PM_ENCODING_US_ASCII] = { + .name = "US-ASCII", + .char_width = pm_encoding_ascii_char_width, + .alnum_char = pm_encoding_ascii_alnum_char, + .alpha_char = pm_encoding_ascii_alpha_char, + .isupper_char = pm_encoding_ascii_isupper_char, + .multibyte = false + }, [PM_ENCODING_ASCII_8BIT] = { .name = "ASCII-8BIT", .char_width = pm_encoding_single_char_width, @@ -4298,6 +4317,24 @@ const pm_encoding_t pm_encodings[] = { .isupper_char = pm_encoding_ascii_isupper_char, .multibyte = false }, + [PM_ENCODING_EUC_JP] = { + .name = "EUC-JP", + .char_width = pm_encoding_euc_jp_char_width, + .alnum_char = pm_encoding_ascii_alnum_char_7bit, + .alpha_char = pm_encoding_ascii_alpha_char_7bit, + .isupper_char = pm_encoding_euc_jp_isupper_char, + .multibyte = true + }, + [PM_ENCODING_WINDOWS_31J] = { + .name = "Windows-31J", + .char_width = pm_encoding_shift_jis_char_width, + .alnum_char = pm_encoding_shift_jis_alnum_char, + .alpha_char = pm_encoding_shift_jis_alpha_char, + .isupper_char = pm_encoding_shift_jis_isupper_char, + .multibyte = true + }, + +#ifndef PRISM_ENCODING_EXCLUDE_FULL [PM_ENCODING_BIG5] = { .name = "Big5", .char_width = pm_encoding_big5_char_width, @@ -4394,14 +4431,6 @@ const pm_encoding_t pm_encodings[] = { .isupper_char = pm_encoding_ascii_isupper_char_7bit, .multibyte = true }, - [PM_ENCODING_EUC_JP] = { - .name = "EUC-JP", - .char_width = pm_encoding_euc_jp_char_width, - .alnum_char = pm_encoding_ascii_alnum_char_7bit, - .alpha_char = pm_encoding_ascii_alpha_char_7bit, - .isupper_char = pm_encoding_euc_jp_isupper_char, - .multibyte = true - }, [PM_ENCODING_EUC_JP_MS] = { .name = "eucJP-ms", .char_width = pm_encoding_euc_jp_char_width, @@ -4874,14 +4903,6 @@ const pm_encoding_t pm_encodings[] = { .isupper_char = pm_encoding_tis_620_isupper_char, .multibyte = false }, - [PM_ENCODING_US_ASCII] = { - .name = "US-ASCII", - .char_width = pm_encoding_ascii_char_width, - .alnum_char = pm_encoding_ascii_alnum_char, - .alpha_char = pm_encoding_ascii_alpha_char, - .isupper_char = pm_encoding_ascii_isupper_char, - .multibyte = false - }, [PM_ENCODING_UTF8_MAC] = { .name = "UTF8-MAC", .char_width = pm_encoding_utf_8_char_width, @@ -4986,14 +5007,6 @@ const pm_encoding_t pm_encodings[] = { .isupper_char = pm_encoding_windows_1258_isupper_char, .multibyte = false }, - [PM_ENCODING_WINDOWS_31J] = { - .name = "Windows-31J", - .char_width = pm_encoding_shift_jis_char_width, - .alnum_char = pm_encoding_shift_jis_alnum_char, - .alpha_char = pm_encoding_shift_jis_alpha_char, - .isupper_char = pm_encoding_shift_jis_isupper_char, - .multibyte = true - }, [PM_ENCODING_WINDOWS_874] = { .name = "Windows-874", .char_width = pm_encoding_single_char_width, @@ -5002,6 +5015,7 @@ const pm_encoding_t pm_encodings[] = { .isupper_char = pm_encoding_windows_874_isupper_char, .multibyte = false } +#endif }; /** @@ -5016,11 +5030,13 @@ pm_encoding_find(const uint8_t *start, const uint8_t *end) { // UTF-8 can contain extra information at the end about the platform it is // encoded on, such as UTF-8-MAC or UTF-8-UNIX. We'll ignore those suffixes. if ((start + 5 <= end) && (pm_strncasecmp(start, (const uint8_t *) "UTF-8", 5) == 0)) { +#ifndef PRISM_ENCODING_EXCLUDE_FULL // We need to explicitly handle UTF-8-HFS, as that one needs to switch // over to being UTF8-MAC. if (width == 9 && (pm_strncasecmp(start + 5, (const uint8_t *) "-HFS", 4) == 0)) { return &pm_encodings[PM_ENCODING_UTF8_MAC]; } +#endif // Otherwise we'll return the default UTF-8 encoding. return PM_ENCODING_UTF_8_ENTRY; @@ -5040,11 +5056,16 @@ pm_encoding_find(const uint8_t *start, const uint8_t *end) { break; case 'B': case 'b': ENCODING1("BINARY", PM_ENCODING_ASCII_8BIT); +#ifndef PRISM_ENCODING_EXCLUDE_FULL ENCODING1("Big5", PM_ENCODING_BIG5); ENCODING2("Big5-HKSCS", "Big5-HKSCS:2008", PM_ENCODING_BIG5_HKSCS); ENCODING1("Big5-UAO", PM_ENCODING_BIG5_UAO); +#endif break; case 'C': case 'c': + ENCODING1("CP65001", PM_ENCODING_UTF_8); + ENCODING2("CP932", "csWindows31J", PM_ENCODING_WINDOWS_31J); +#ifndef PRISM_ENCODING_EXCLUDE_FULL ENCODING1("CESU-8", PM_ENCODING_CESU_8); ENCODING1("CP437", PM_ENCODING_IBM437); ENCODING1("CP720", PM_ENCODING_IBM720); @@ -5064,7 +5085,6 @@ pm_encoding_find(const uint8_t *start, const uint8_t *end) { ENCODING1("CP874", PM_ENCODING_WINDOWS_874); ENCODING1("CP878", PM_ENCODING_KOI8_R); ENCODING1("CP863", PM_ENCODING_IBM863); - ENCODING2("CP932", "csWindows31J", PM_ENCODING_WINDOWS_31J); ENCODING1("CP936", PM_ENCODING_GBK); ENCODING1("CP949", PM_ENCODING_CP949); ENCODING1("CP950", PM_ENCODING_CP950); @@ -5079,25 +5099,30 @@ pm_encoding_find(const uint8_t *start, const uint8_t *end) { ENCODING1("CP1257", PM_ENCODING_WINDOWS_1257); ENCODING1("CP1258", PM_ENCODING_WINDOWS_1258); ENCODING1("CP51932", PM_ENCODING_CP51932); - ENCODING1("CP65001", PM_ENCODING_UTF_8); +#endif break; case 'E': case 'e': ENCODING2("EUC-JP", "eucJP", PM_ENCODING_EUC_JP); +#ifndef PRISM_ENCODING_EXCLUDE_FULL ENCODING2("eucJP-ms", "euc-jp-ms", PM_ENCODING_EUC_JP_MS); ENCODING2("EUC-JIS-2004", "EUC-JISX0213", PM_ENCODING_EUC_JIS_2004); ENCODING2("EUC-KR", "eucKR", PM_ENCODING_EUC_KR); ENCODING2("EUC-CN", "eucCN", PM_ENCODING_GB2312); ENCODING2("EUC-TW", "eucTW", PM_ENCODING_EUC_TW); ENCODING1("Emacs-Mule", PM_ENCODING_EMACS_MULE); +#endif break; case 'G': case 'g': +#ifndef PRISM_ENCODING_EXCLUDE_FULL ENCODING1("GBK", PM_ENCODING_GBK); ENCODING1("GB12345", PM_ENCODING_GB12345); ENCODING1("GB18030", PM_ENCODING_GB18030); ENCODING1("GB1988", PM_ENCODING_GB1988); ENCODING1("GB2312", PM_ENCODING_GB2312); +#endif break; case 'I': case 'i': +#ifndef PRISM_ENCODING_EXCLUDE_FULL ENCODING1("IBM437", PM_ENCODING_IBM437); ENCODING1("IBM720", PM_ENCODING_IBM720); ENCODING1("IBM737", PM_ENCODING_IBM737); @@ -5129,12 +5154,16 @@ pm_encoding_find(const uint8_t *start, const uint8_t *end) { ENCODING2("ISO-8859-14", "ISO8859-14", PM_ENCODING_ISO_8859_14); ENCODING2("ISO-8859-15", "ISO8859-15", PM_ENCODING_ISO_8859_15); ENCODING2("ISO-8859-16", "ISO8859-16", PM_ENCODING_ISO_8859_16); +#endif break; case 'K': case 'k': +#ifndef PRISM_ENCODING_EXCLUDE_FULL ENCODING1("KOI8-R", PM_ENCODING_KOI8_R); ENCODING1("KOI8-U", PM_ENCODING_KOI8_U); +#endif break; case 'M': case 'm': +#ifndef PRISM_ENCODING_EXCLUDE_FULL ENCODING1("macCentEuro", PM_ENCODING_MAC_CENT_EURO); ENCODING1("macCroatian", PM_ENCODING_MAC_CROATIAN); ENCODING1("macCyrillic", PM_ENCODING_MAC_CYRILLIC); @@ -5147,31 +5176,39 @@ pm_encoding_find(const uint8_t *start, const uint8_t *end) { ENCODING1("macThai", PM_ENCODING_MAC_THAI); ENCODING1("macTurkish", PM_ENCODING_MAC_TURKISH); ENCODING1("macUkraine", PM_ENCODING_MAC_UKRAINE); +#endif break; case 'P': case 'p': ENCODING1("PCK", PM_ENCODING_WINDOWS_31J); break; case 'S': case 's': - ENCODING1("Shift_JIS", PM_ENCODING_SHIFT_JIS); ENCODING1("SJIS", PM_ENCODING_WINDOWS_31J); +#ifndef PRISM_ENCODING_EXCLUDE_FULL + ENCODING1("Shift_JIS", PM_ENCODING_SHIFT_JIS); ENCODING1("SJIS-DoCoMo", PM_ENCODING_SJIS_DOCOMO); ENCODING1("SJIS-KDDI", PM_ENCODING_SJIS_KDDI); ENCODING1("SJIS-SoftBank", PM_ENCODING_SJIS_SOFTBANK); ENCODING1("stateless-ISO-2022-JP", PM_ENCODING_STATELESS_ISO_2022_JP); ENCODING1("stateless-ISO-2022-JP-KDDI", PM_ENCODING_STATELESS_ISO_2022_JP_KDDI); +#endif break; case 'T': case 't': +#ifndef PRISM_ENCODING_EXCLUDE_FULL ENCODING1("TIS-620", PM_ENCODING_TIS_620); +#endif break; case 'U': case 'u': ENCODING1("US-ASCII", PM_ENCODING_US_ASCII); +#ifndef PRISM_ENCODING_EXCLUDE_FULL ENCODING2("UTF8-MAC", "UTF-8-HFS", PM_ENCODING_UTF8_MAC); ENCODING1("UTF8-DoCoMo", PM_ENCODING_UTF8_DOCOMO); ENCODING1("UTF8-KDDI", PM_ENCODING_UTF8_KDDI); ENCODING1("UTF8-SoftBank", PM_ENCODING_UTF8_SOFTBANK); +#endif break; case 'W': case 'w': ENCODING1("Windows-31J", PM_ENCODING_WINDOWS_31J); +#ifndef PRISM_ENCODING_EXCLUDE_FULL ENCODING1("Windows-874", PM_ENCODING_WINDOWS_874); ENCODING1("Windows-1250", PM_ENCODING_WINDOWS_1250); ENCODING1("Windows-1251", PM_ENCODING_WINDOWS_1251); @@ -5182,6 +5219,7 @@ pm_encoding_find(const uint8_t *start, const uint8_t *end) { ENCODING1("Windows-1256", PM_ENCODING_WINDOWS_1256); ENCODING1("Windows-1257", PM_ENCODING_WINDOWS_1257); ENCODING1("Windows-1258", PM_ENCODING_WINDOWS_1258); +#endif break; case '6': ENCODING1("646", PM_ENCODING_US_ASCII); diff --git a/prism/encoding.h b/prism/encoding.h index 0850e291d81f7e..5f7724821f5b31 100644 --- a/prism/encoding.h +++ b/prism/encoding.h @@ -135,7 +135,14 @@ extern const uint8_t pm_encoding_unicode_table[256]; */ typedef enum { PM_ENCODING_UTF_8 = 0, + PM_ENCODING_US_ASCII, PM_ENCODING_ASCII_8BIT, + PM_ENCODING_EUC_JP, + PM_ENCODING_WINDOWS_31J, + +// We optionally support excluding the full set of encodings to only support the +// minimum necessary to process Ruby code without encoding comments. +#ifndef PRISM_ENCODING_EXCLUDE_FULL PM_ENCODING_BIG5, PM_ENCODING_BIG5_HKSCS, PM_ENCODING_BIG5_UAO, @@ -148,7 +155,6 @@ typedef enum { PM_ENCODING_CP950, PM_ENCODING_CP951, PM_ENCODING_EMACS_MULE, - PM_ENCODING_EUC_JP, PM_ENCODING_EUC_JP_MS, PM_ENCODING_EUC_JIS_2004, PM_ENCODING_EUC_KR, @@ -208,7 +214,6 @@ typedef enum { PM_ENCODING_STATELESS_ISO_2022_JP, PM_ENCODING_STATELESS_ISO_2022_JP_KDDI, PM_ENCODING_TIS_620, - PM_ENCODING_US_ASCII, PM_ENCODING_UTF8_MAC, PM_ENCODING_UTF8_DOCOMO, PM_ENCODING_UTF8_KDDI, @@ -222,8 +227,9 @@ typedef enum { PM_ENCODING_WINDOWS_1256, PM_ENCODING_WINDOWS_1257, PM_ENCODING_WINDOWS_1258, - PM_ENCODING_WINDOWS_31J, PM_ENCODING_WINDOWS_874, +#endif + PM_ENCODING_MAXIMUM } pm_encoding_type_t; diff --git a/prism/extension.c b/prism/extension.c index 292e67891f386b..27e799a8da645e 100644 --- a/prism/extension.c +++ b/prism/extension.c @@ -23,13 +23,13 @@ VALUE rb_cPrismParseResult; VALUE rb_cPrismDebugEncoding; -ID rb_option_id_filepath; +ID rb_option_id_command_line; ID rb_option_id_encoding; -ID rb_option_id_line; +ID rb_option_id_filepath; ID rb_option_id_frozen_string_literal; -ID rb_option_id_version; +ID rb_option_id_line; ID rb_option_id_scopes; -ID rb_option_id_command_line; +ID rb_option_id_version; /******************************************************************************/ /* IO of Ruby code */ @@ -139,7 +139,7 @@ build_options_i(VALUE key, VALUE value, VALUE argument) { } else if (key_id == rb_option_id_line) { if (!NIL_P(value)) pm_options_line_set(options, NUM2INT(value)); } else if (key_id == rb_option_id_frozen_string_literal) { - if (!NIL_P(value)) pm_options_frozen_string_literal_set(options, value == Qtrue); + if (!NIL_P(value)) pm_options_frozen_string_literal_set(options, RTEST(value)); } else if (key_id == rb_option_id_version) { if (!NIL_P(value)) { const char *version = check_string(value); @@ -267,6 +267,8 @@ file_options(int argc, VALUE *argv, pm_string_t *input, pm_options_t *options) { } } +#ifndef PRISM_EXCLUDE_SERIALIZATION + /******************************************************************************/ /* Serializing the AST */ /******************************************************************************/ @@ -308,7 +310,7 @@ dump(int argc, VALUE *argv, VALUE self) { pm_options_t options = { 0 }; string_options(argc, argv, &input, &options); -#ifdef PRISM_DEBUG_MODE_BUILD +#ifdef PRISM_BUILD_DEBUG size_t length = pm_string_length(&input); char* dup = xmalloc(length); memcpy(dup, pm_string_source(&input), length); @@ -317,7 +319,7 @@ dump(int argc, VALUE *argv, VALUE self) { VALUE value = dump_input(&input, &options); -#ifdef PRISM_DEBUG_MODE_BUILD +#ifdef PRISM_BUILD_DEBUG xfree(dup); #endif @@ -348,6 +350,8 @@ dump_file(int argc, VALUE *argv, VALUE self) { return value; } +#endif + /******************************************************************************/ /* Extracting values for the parse result */ /******************************************************************************/ @@ -441,12 +445,15 @@ parser_errors(pm_parser_t *parser, rb_encoding *encoding, VALUE source) { VALUE level = Qnil; switch (error->level) { - case PM_ERROR_LEVEL_FATAL: - level = ID2SYM(rb_intern("fatal")); + case PM_ERROR_LEVEL_SYNTAX: + level = ID2SYM(rb_intern("syntax")); break; case PM_ERROR_LEVEL_ARGUMENT: level = ID2SYM(rb_intern("argument")); break; + case PM_ERROR_LEVEL_LOAD: + level = ID2SYM(rb_intern("load")); + break; default: rb_raise(rb_eRuntimeError, "Unknown level: %" PRIu8, error->level); } @@ -723,7 +730,7 @@ parse_input(pm_string_t *input, const pm_options_t *options) { * parsed. This should be an array of arrays of symbols or nil. Scopes are * ordered from the outermost scope to the innermost one. * * `version` - the version of Ruby syntax that prism should used to parse Ruby - * code. By default prism assumes you want to parse with the latest vesion + * code. By default prism assumes you want to parse with the latest version * of Ruby syntax (which you can trigger with `nil` or `"latest"`). You * may also restrict the syntax to a specific version of Ruby. The * supported values are `"3.3.0"` and `"3.4.0"`. @@ -734,7 +741,7 @@ parse(int argc, VALUE *argv, VALUE self) { pm_options_t options = { 0 }; string_options(argc, argv, &input, &options); -#ifdef PRISM_DEBUG_MODE_BUILD +#ifdef PRISM_BUILD_DEBUG size_t length = pm_string_length(&input); char* dup = xmalloc(length); memcpy(dup, pm_string_source(&input), length); @@ -743,7 +750,7 @@ parse(int argc, VALUE *argv, VALUE self) { VALUE value = parse_input(&input, &options); -#ifdef PRISM_DEBUG_MODE_BUILD +#ifdef PRISM_BUILD_DEBUG xfree(dup); #endif @@ -1126,6 +1133,8 @@ profile_file(VALUE self, VALUE filepath) { return Qnil; } +#ifndef PRISM_EXCLUDE_PRETTYPRINT + /** * call-seq: * Debug::inspect_node(source) -> inspected @@ -1156,6 +1165,8 @@ inspect_node(VALUE self, VALUE source) { return string; } +#endif + /** * call-seq: * Debug::format_errors(source, colorize) -> String @@ -1173,7 +1184,7 @@ format_errors(VALUE self, VALUE source, VALUE colorize) { pm_node_t *node = pm_parse(&parser); pm_buffer_t buffer = { 0 }; - pm_parser_errors_format(&parser, &buffer, RTEST(colorize)); + pm_parser_errors_format(&parser, &parser.error_list, &buffer, RTEST(colorize), true); rb_encoding *encoding = rb_enc_find(parser.encoding->name); VALUE result = rb_enc_str_new(pm_buffer_value(&buffer), pm_buffer_length(&buffer), encoding); @@ -1186,6 +1197,40 @@ format_errors(VALUE self, VALUE source, VALUE colorize) { return result; } +/** + * call-seq: + * Debug::static_inspect(source) -> String + * + * Inspect the node as it would be inspected by the warnings used in static + * literal sets. + */ +static VALUE +static_inspect(int argc, VALUE *argv, VALUE self) { + pm_string_t input; + pm_options_t options = { 0 }; + string_options(argc, argv, &input, &options); + + pm_parser_t parser; + pm_parser_init(&parser, pm_string_source(&input), pm_string_length(&input), &options); + + pm_node_t *program = pm_parse(&parser); + pm_node_t *node = ((pm_program_node_t *) program)->statements->body.nodes[0]; + + pm_buffer_t buffer = { 0 }; + pm_static_literal_inspect(&buffer, &parser, node); + + rb_encoding *encoding = rb_enc_find(parser.encoding->name); + VALUE result = rb_enc_str_new(pm_buffer_value(&buffer), pm_buffer_length(&buffer), encoding); + + pm_buffer_free(&buffer); + pm_node_destroy(&parser, program); + pm_parser_free(&parser); + pm_string_free(&input); + pm_options_free(&options); + + return result; +} + /** * call-seq: Debug::Encoding.all -> Array[Debug::Encoding] * @@ -1297,13 +1342,13 @@ Init_prism(void) { // Intern all of the options that we support so that we don't have to do it // every time we parse. - rb_option_id_filepath = rb_intern_const("filepath"); + rb_option_id_command_line = rb_intern_const("command_line"); rb_option_id_encoding = rb_intern_const("encoding"); - rb_option_id_line = rb_intern_const("line"); + rb_option_id_filepath = rb_intern_const("filepath"); rb_option_id_frozen_string_literal = rb_intern_const("frozen_string_literal"); - rb_option_id_version = rb_intern_const("version"); + rb_option_id_line = rb_intern_const("line"); rb_option_id_scopes = rb_intern_const("scopes"); - rb_option_id_command_line = rb_intern_const("command_line"); + rb_option_id_version = rb_intern_const("version"); /** * The version of the prism library. @@ -1311,8 +1356,6 @@ Init_prism(void) { rb_define_const(rb_cPrism, "VERSION", rb_str_new2(EXPECTED_PRISM_VERSION)); // First, the functions that have to do with lexing and parsing. - rb_define_singleton_method(rb_cPrism, "dump", dump, -1); - rb_define_singleton_method(rb_cPrism, "dump_file", dump_file, -1); rb_define_singleton_method(rb_cPrism, "lex", lex, -1); rb_define_singleton_method(rb_cPrism, "lex_file", lex_file, -1); rb_define_singleton_method(rb_cPrism, "parse", parse, -1); @@ -1325,6 +1368,11 @@ Init_prism(void) { rb_define_singleton_method(rb_cPrism, "parse_success?", parse_success_p, -1); rb_define_singleton_method(rb_cPrism, "parse_file_success?", parse_file_success_p, -1); +#ifndef PRISM_EXCLUDE_SERIALIZATION + rb_define_singleton_method(rb_cPrism, "dump", dump, -1); + rb_define_singleton_method(rb_cPrism, "dump_file", dump_file, -1); +#endif + // Next, the functions that will be called by the parser to perform various // internal tasks. We expose these to make them easier to test. VALUE rb_cPrismDebug = rb_define_module_under(rb_cPrism, "Debug"); @@ -1332,8 +1380,12 @@ Init_prism(void) { rb_define_singleton_method(rb_cPrismDebug, "integer_parse", integer_parse, 1); rb_define_singleton_method(rb_cPrismDebug, "memsize", memsize, 1); rb_define_singleton_method(rb_cPrismDebug, "profile_file", profile_file, 1); - rb_define_singleton_method(rb_cPrismDebug, "inspect_node", inspect_node, 1); rb_define_singleton_method(rb_cPrismDebug, "format_errors", format_errors, 2); + rb_define_singleton_method(rb_cPrismDebug, "static_inspect", static_inspect, -1); + +#ifndef PRISM_EXCLUDE_PRETTYPRINT + rb_define_singleton_method(rb_cPrismDebug, "inspect_node", inspect_node, 1); +#endif // Next, define the functions that are exposed through the private // Debug::Encoding class. diff --git a/prism/extension.h b/prism/extension.h index 13a9aabde3e5c3..59a3f0232ac9e6 100644 --- a/prism/extension.h +++ b/prism/extension.h @@ -1,7 +1,7 @@ #ifndef PRISM_EXT_NODE_H #define PRISM_EXT_NODE_H -#define EXPECTED_PRISM_VERSION "0.24.0" +#define EXPECTED_PRISM_VERSION "0.25.0" #include #include diff --git a/prism/node.h b/prism/node.h index a001b4a9e470e4..8736e59a94d1a4 100644 --- a/prism/node.h +++ b/prism/node.h @@ -11,15 +11,11 @@ #include "prism/util/pm_buffer.h" /** - * Attempts to grow the node list to the next size. If there is already - * capacity in the list, this function does nothing. Otherwise it reallocates - * the list to be twice as large as it was before. If the reallocation fails, - * this function returns false, otherwise it returns true. - * - * @param list The list to grow. - * @return True if the list was successfully grown, false otherwise. + * Loop through each node in the node list, writing each node to the given + * pm_node_t pointer. */ -bool pm_node_list_grow(pm_node_list_t *list); +#define PM_NODE_LIST_FOREACH(list, index, node) \ + for (size_t index = 0; index < (list)->size && ((node) = (list)->nodes[index]); index++) /** * Append a new node onto the end of the node list. @@ -35,8 +31,15 @@ void pm_node_list_append(pm_node_list_t *list, pm_node_t *node); * @param list The list to prepend to. * @param node The node to prepend. */ -void -pm_node_list_prepend(pm_node_list_t *list, pm_node_t *node); +void pm_node_list_prepend(pm_node_list_t *list, pm_node_t *node); + +/** + * Concatenate the given node list onto the end of the other node list. + * + * @param list The list to concatenate onto. + * @param other The list to concatenate. + */ +void pm_node_list_concat(pm_node_list_t *list, pm_node_list_t *other); /** * Free the internal memory associated with the given node list. diff --git a/prism/options.c b/prism/options.c index d94cfad5502130..2854b765b9323b 100644 --- a/prism/options.c +++ b/prism/options.c @@ -29,7 +29,7 @@ pm_options_line_set(pm_options_t *options, int32_t line) { */ PRISM_EXPORTED_FUNCTION void pm_options_frozen_string_literal_set(pm_options_t *options, bool frozen_string_literal) { - options->frozen_string_literal = frozen_string_literal; + options->frozen_string_literal = frozen_string_literal ? PM_OPTIONS_FROZEN_STRING_LITERAL_ENABLED : PM_OPTIONS_FROZEN_STRING_LITERAL_DISABLED; } /** @@ -201,7 +201,7 @@ pm_options_read(pm_options_t *options, const char *data) { data += encoding_length; } - options->frozen_string_literal = (*data++) ? true : false; + options->frozen_string_literal = (int8_t) *data++; options->command_line = (uint8_t) *data++; options->version = (pm_options_version_t) *data++; diff --git a/prism/options.h b/prism/options.h index ce979656a5b23e..d0b46a086491c7 100644 --- a/prism/options.h +++ b/prism/options.h @@ -13,6 +13,22 @@ #include #include +/** + * String literals should be made frozen. + */ +#define PM_OPTIONS_FROZEN_STRING_LITERAL_DISABLED ((int8_t) -1) + +/** + * String literals may be frozen or mutable depending on the implementation + * default. + */ +#define PM_OPTIONS_FROZEN_STRING_LITERAL_UNSET ((int8_t) 0) + +/** + * String literals should be made mutable. + */ +#define PM_OPTIONS_FROZEN_STRING_LITERAL_ENABLED ((int8_t) 1) + /** * A scope of locals surrounding the code that is being parsed. */ @@ -79,8 +95,14 @@ typedef struct { /** A bitset of the various options that were set on the command line. */ uint8_t command_line; - /** Whether or not the frozen string literal option has been set. */ - bool frozen_string_literal; + /** + * Whether or not the frozen string literal option has been set. + * May be: + * - PM_OPTIONS_FROZEN_STRING_LITERAL_DISABLED + * - PM_OPTIONS_FROZEN_STRING_LITERAL_ENABLED + * - PM_OPTIONS_FROZEN_STRING_LITERAL_UNSET + */ + int8_t frozen_string_literal; } pm_options_t; /** @@ -249,14 +271,14 @@ PRISM_EXPORTED_FUNCTION void pm_options_free(pm_options_t *options); * | `0` | use the latest version of prism | * | `1` | use the version of prism that is vendored in CRuby 3.3.0 | * - * Each scope is layed out as follows: + * Each scope is laid out as follows: * * | # bytes | field | * | ------- | -------------------------- | * | `4` | the number of locals | * | ... | the locals | * - * Each local is layed out as follows: + * Each local is laid out as follows: * * | # bytes | field | * | ------- | -------------------------- | diff --git a/prism/pack.c b/prism/pack.c index d5bfc4d6fdf97d..1388ca8a3b5210 100644 --- a/prism/pack.c +++ b/prism/pack.c @@ -1,16 +1,43 @@ #include "prism/pack.h" +// We optionally support parsing String#pack templates. For systems that don't +// want or need this functionality, it can be turned off with the +// PRISM_EXCLUDE_PACK define. +#ifdef PRISM_EXCLUDE_PACK + +void pm_pack_parse(void) {} + +#else + #include #include static uintmax_t -strtoumaxc(const char **format); +strtoumaxc(const char **format) { + uintmax_t value = 0; + while (**format >= '0' && **format <= '9') { + if (value > UINTMAX_MAX / 10) { + errno = ERANGE; + } + value = value * 10 + ((uintmax_t) (**format - '0')); + (*format)++; + } + return value; +} PRISM_EXPORTED_FUNCTION pm_pack_result -pm_pack_parse(pm_pack_variant variant, const char **format, const char *format_end, - pm_pack_type *type, pm_pack_signed *signed_type, pm_pack_endian *endian, pm_pack_size *size, - pm_pack_length_type *length_type, uint64_t *length, pm_pack_encoding *encoding) { - +pm_pack_parse( + pm_pack_variant variant, + const char **format, + const char *format_end, + pm_pack_type *type, + pm_pack_signed *signed_type, + pm_pack_endian *endian, + pm_pack_size *size, + pm_pack_length_type *length_type, + uint64_t *length, + pm_pack_encoding *encoding +) { if (*encoding == PM_PACK_ENCODING_START) { *encoding = PM_PACK_ENCODING_US_ASCII; } @@ -479,15 +506,4 @@ pm_size_to_native(pm_pack_size size) { } } -static uintmax_t -strtoumaxc(const char **format) { - uintmax_t value = 0; - while (**format >= '0' && **format <= '9') { - if (value > UINTMAX_MAX / 10) { - errno = ERANGE; - } - value = value * 10 + ((uintmax_t) (**format - '0')); - (*format)++; - } - return value; -} +#endif diff --git a/prism/pack.h b/prism/pack.h index e49484838970a8..0b0b4b19cc85d4 100644 --- a/prism/pack.h +++ b/prism/pack.h @@ -8,6 +8,15 @@ #include "prism/defines.h" +// We optionally support parsing String#pack templates. For systems that don't +// want or need this functionality, it can be turned off with the +// PRISM_EXCLUDE_PACK define. +#ifdef PRISM_EXCLUDE_PACK + +void pm_pack_parse(void); + +#else + #include #include @@ -150,3 +159,5 @@ pm_pack_parse( PRISM_EXPORTED_FUNCTION size_t pm_size_to_native(pm_pack_size size); #endif + +#endif diff --git a/prism/parser.h b/prism/parser.h index 7f75bb3cb275b4..b0ff2f38fcf38d 100644 --- a/prism/parser.h +++ b/prism/parser.h @@ -6,8 +6,8 @@ #ifndef PRISM_PARSER_H #define PRISM_PARSER_H -#include "prism/ast.h" #include "prism/defines.h" +#include "prism/ast.h" #include "prism/encoding.h" #include "prism/options.h" #include "prism/util/pm_constant_pool.h" @@ -173,7 +173,7 @@ typedef struct pm_lex_mode { * This is the character set that should be used to delimit the * tokens within the regular expression. */ - uint8_t breakpoints[6]; + uint8_t breakpoints[7]; } regexp; struct { @@ -206,7 +206,7 @@ typedef struct pm_lex_mode { * This is the character set that should be used to delimit the * tokens within the string. */ - uint8_t breakpoints[6]; + uint8_t breakpoints[7]; } string; struct { @@ -268,12 +268,30 @@ typedef enum { /** a begin statement */ PM_CONTEXT_BEGIN, + /** an ensure statement with an explicit begin */ + PM_CONTEXT_BEGIN_ENSURE, + + /** a rescue else statement with an explicit begin */ + PM_CONTEXT_BEGIN_ELSE, + + /** a rescue statement with an explicit begin */ + PM_CONTEXT_BEGIN_RESCUE, + /** expressions in block arguments using braces */ PM_CONTEXT_BLOCK_BRACES, /** expressions in block arguments using do..end */ PM_CONTEXT_BLOCK_KEYWORDS, + /** an ensure statement within a do..end block */ + PM_CONTEXT_BLOCK_ENSURE, + + /** a rescue else statement within a do..end block */ + PM_CONTEXT_BLOCK_ELSE, + + /** a rescue statement within a do..end block */ + PM_CONTEXT_BLOCK_RESCUE, + /** a case when statements */ PM_CONTEXT_CASE_WHEN, @@ -283,12 +301,33 @@ typedef enum { /** a class declaration */ PM_CONTEXT_CLASS, + /** an ensure statement within a class statement */ + PM_CONTEXT_CLASS_ENSURE, + + /** a rescue else statement within a class statement */ + PM_CONTEXT_CLASS_ELSE, + + /** a rescue statement within a class statement */ + PM_CONTEXT_CLASS_RESCUE, + /** a method definition */ PM_CONTEXT_DEF, + /** an ensure statement within a method definition */ + PM_CONTEXT_DEF_ENSURE, + + /** a rescue else statement within a method definition */ + PM_CONTEXT_DEF_ELSE, + + /** a rescue statement within a method definition */ + PM_CONTEXT_DEF_RESCUE, + /** a method definition's parameters */ PM_CONTEXT_DEF_PARAMS, + /** a defined? expression */ + PM_CONTEXT_DEFINED, + /** a method definition's default parameter */ PM_CONTEXT_DEFAULT_PARAMS, @@ -301,12 +340,6 @@ typedef enum { /** an interpolated expression */ PM_CONTEXT_EMBEXPR, - /** an ensure statement */ - PM_CONTEXT_ENSURE, - - /** an ensure statement within a method definition */ - PM_CONTEXT_ENSURE_DEF, - /** a for loop */ PM_CONTEXT_FOR, @@ -322,12 +355,30 @@ typedef enum { /** a lambda expression with do..end */ PM_CONTEXT_LAMBDA_DO_END, + /** an ensure statement within a lambda expression */ + PM_CONTEXT_LAMBDA_ENSURE, + + /** a rescue else statement within a lambda expression */ + PM_CONTEXT_LAMBDA_ELSE, + + /** a rescue statement within a lambda expression */ + PM_CONTEXT_LAMBDA_RESCUE, + /** the top level context */ PM_CONTEXT_MAIN, /** a module declaration */ PM_CONTEXT_MODULE, + /** an ensure statement within a module statement */ + PM_CONTEXT_MODULE_ENSURE, + + /** a rescue else statement within a module statement */ + PM_CONTEXT_MODULE_ELSE, + + /** a rescue statement within a module statement */ + PM_CONTEXT_MODULE_RESCUE, + /** a parenthesized expression */ PM_CONTEXT_PARENS, @@ -340,20 +391,23 @@ typedef enum { /** a BEGIN block */ PM_CONTEXT_PREEXE, - /** a rescue else statement */ - PM_CONTEXT_RESCUE_ELSE, + /** a modifier rescue clause */ + PM_CONTEXT_RESCUE_MODIFIER, - /** a rescue else statement within a method definition */ - PM_CONTEXT_RESCUE_ELSE_DEF, + /** a singleton class definition */ + PM_CONTEXT_SCLASS, - /** a rescue statement */ - PM_CONTEXT_RESCUE, + /** an ensure statement with a singleton class */ + PM_CONTEXT_SCLASS_ENSURE, - /** a rescue statement within a method definition */ - PM_CONTEXT_RESCUE_DEF, + /** a rescue else statement with a singleton class */ + PM_CONTEXT_SCLASS_ELSE, - /** a singleton class definition */ - PM_CONTEXT_SCLASS, + /** a rescue statement with a singleton class */ + PM_CONTEXT_SCLASS_RESCUE, + + /** a ternary expression */ + PM_CONTEXT_TERNARY, /** an unless statement */ PM_CONTEXT_UNLESS, @@ -448,6 +502,50 @@ typedef struct { void (*callback)(void *data, pm_parser_t *parser, pm_token_t *token); } pm_lex_callback_t; +/** The type of shareable constant value that can be set. */ +typedef uint8_t pm_shareable_constant_value_t; +static const pm_shareable_constant_value_t PM_SCOPE_SHAREABLE_CONSTANT_NONE = 0x0; +static const pm_shareable_constant_value_t PM_SCOPE_SHAREABLE_CONSTANT_LITERAL = 0x1; +static const pm_shareable_constant_value_t PM_SCOPE_SHAREABLE_CONSTANT_EXPERIMENTAL_EVERYTHING = 0x2; +static const pm_shareable_constant_value_t PM_SCOPE_SHAREABLE_CONSTANT_EXPERIMENTAL_COPY = 0x4; + +/** + * This tracks an individual local variable in a certain lexical context, as + * well as the number of times is it read. + */ +typedef struct { + /** The name of the local variable. */ + pm_constant_id_t name; + + /** The location of the local variable in the source. */ + pm_location_t location; + + /** The index of the local variable in the local table. */ + uint32_t index; + + /** The number of times the local variable is read. */ + uint32_t reads; + + /** The hash of the local variable. */ + uint32_t hash; +} pm_local_t; + +/** + * This is a set of local variables in a certain lexical context (method, class, + * module, etc.). We need to track how many times these variables are read in + * order to warn if they only get written. + */ +typedef struct pm_locals { + /** The number of local variables in the set. */ + uint32_t size; + + /** The capacity of the local variables set. */ + uint32_t capacity; + + /** The nullable allocated memory for the local variables in the set. */ + pm_local_t *locals; +} pm_locals_t; + /** * This struct represents a node in a linked list of scopes. Some scopes can see * into their parent scopes, while others cannot. @@ -457,7 +555,7 @@ typedef struct pm_scope { struct pm_scope *previous; /** The IDs of the locals in the given scope. */ - pm_constant_id_list_t locals; + pm_locals_t locals; /** * This is a bitfield that indicates the parameters that are being used in @@ -487,6 +585,12 @@ typedef struct pm_scope { */ int8_t numbered_parameters; + /** + * The current state of constant shareability for this scope. This is + * changed by magic shareable_constant_value comments. + */ + pm_shareable_constant_value_t shareable_constant; + /** * A boolean indicating whether or not this scope can see into its parent. * If closed is true, then the scope cannot see into its parent. @@ -700,8 +804,18 @@ struct pm_parser { */ const pm_encoding_t *explicit_encoding; - /** The current parameter name id on parsing its default value. */ - pm_constant_id_t current_param_name; + /** + * When parsing block exits (e.g., break, next, redo), we need to validate + * that they are in correct contexts. For the most part we can do this by + * looking at our parent contexts. However, modifier while and until + * expressions can change that context to make block exits valid. In these + * cases, we need to keep track of the block exits and then validate them + * after the expression has been parsed. + * + * We use a pointer here because we don't want to keep a whole list attached + * since this will only be used in the context of begin/end expressions. + */ + pm_node_list_t *current_block_exits; /** The version of prism that we should use to parse. */ pm_options_version_t version; @@ -709,6 +823,22 @@ struct pm_parser { /** The command line flags given from the options. */ uint8_t command_line; + /** + * Whether or not we have found a frozen_string_literal magic comment with + * a true or false value. + * May be: + * - PM_OPTIONS_FROZEN_STRING_LITERAL_DISABLED + * - PM_OPTIONS_FROZEN_STRING_LITERAL_ENABLED + * - PM_OPTIONS_FROZEN_STRING_LITERAL_UNSET + */ + int8_t frozen_string_literal; + + /** + * Whether or not we are parsing an eval string. This impacts whether or not + * we should evaluate if block exits/yields are valid. + */ + bool parsing_eval; + /** Whether or not we're at the beginning of a command. */ bool command_start; @@ -737,12 +867,6 @@ struct pm_parser { */ bool semantic_token_seen; - /** - * Whether or not we have found a frozen_string_literal magic comment with - * a true value. - */ - bool frozen_string_literal; - /** * True if the current regular expression being lexed contains only ASCII * characters. diff --git a/prism/prettyprint.h b/prism/prettyprint.h index 351b92df39510f..5a52b2b6b8eb43 100644 --- a/prism/prettyprint.h +++ b/prism/prettyprint.h @@ -8,6 +8,12 @@ #include "prism/defines.h" +#ifdef PRISM_EXCLUDE_PRETTYPRINT + +void pm_prettyprint(void); + +#else + #include #include "prism/ast.h" @@ -24,3 +30,5 @@ PRISM_EXPORTED_FUNCTION void pm_prettyprint(pm_buffer_t *output_buffer, const pm_parser_t *parser, const pm_node_t *node); #endif + +#endif diff --git a/prism/prism.c b/prism/prism.c index be1f73653adcaf..a0ab6d7cee41b4 100644 --- a/prism/prism.c +++ b/prism/prism.c @@ -33,39 +33,57 @@ PRISM_ATTRIBUTE_UNUSED static const char * debug_context(pm_context_t context) { switch (context) { case PM_CONTEXT_BEGIN: return "BEGIN"; - case PM_CONTEXT_CLASS: return "CLASS"; + case PM_CONTEXT_BEGIN_ENSURE: return "BEGIN_ENSURE"; + case PM_CONTEXT_BEGIN_ELSE: return "BEGIN_ELSE"; + case PM_CONTEXT_BEGIN_RESCUE: return "BEGIN_RESCUE"; + case PM_CONTEXT_BLOCK_BRACES: return "BLOCK_BRACES"; + case PM_CONTEXT_BLOCK_KEYWORDS: return "BLOCK_KEYWORDS"; + case PM_CONTEXT_BLOCK_ENSURE: return "BLOCK_ENSURE"; + case PM_CONTEXT_BLOCK_ELSE: return "BLOCK_ELSE"; + case PM_CONTEXT_BLOCK_RESCUE: return "BLOCK_RESCUE"; case PM_CONTEXT_CASE_IN: return "CASE_IN"; case PM_CONTEXT_CASE_WHEN: return "CASE_WHEN"; + case PM_CONTEXT_CLASS: return "CLASS"; + case PM_CONTEXT_CLASS_ELSE: return "CLASS_ELSE"; + case PM_CONTEXT_CLASS_ENSURE: return "CLASS_ENSURE"; + case PM_CONTEXT_CLASS_RESCUE: return "CLASS_RESCUE"; case PM_CONTEXT_DEF: return "DEF"; case PM_CONTEXT_DEF_PARAMS: return "DEF_PARAMS"; + case PM_CONTEXT_DEF_ENSURE: return "DEF_ENSURE"; + case PM_CONTEXT_DEF_ELSE: return "DEF_ELSE"; + case PM_CONTEXT_DEF_RESCUE: return "DEF_RESCUE"; case PM_CONTEXT_DEFAULT_PARAMS: return "DEFAULT_PARAMS"; - case PM_CONTEXT_ENSURE: return "ENSURE"; - case PM_CONTEXT_ENSURE_DEF: return "ENSURE_DEF"; + case PM_CONTEXT_DEFINED: return "DEFINED"; case PM_CONTEXT_ELSE: return "ELSE"; case PM_CONTEXT_ELSIF: return "ELSIF"; case PM_CONTEXT_EMBEXPR: return "EMBEXPR"; - case PM_CONTEXT_BLOCK_BRACES: return "BLOCK_BRACES"; - case PM_CONTEXT_BLOCK_KEYWORDS: return "BLOCK_KEYWORDS"; - case PM_CONTEXT_FOR: return "FOR"; case PM_CONTEXT_FOR_INDEX: return "FOR_INDEX"; + case PM_CONTEXT_FOR: return "FOR"; case PM_CONTEXT_IF: return "IF"; + case PM_CONTEXT_LAMBDA_BRACES: return "LAMBDA_BRACES"; + case PM_CONTEXT_LAMBDA_DO_END: return "LAMBDA_DO_END"; + case PM_CONTEXT_LAMBDA_ENSURE: return "LAMBDA_ENSURE"; + case PM_CONTEXT_LAMBDA_ELSE: return "LAMBDA_ELSE"; + case PM_CONTEXT_LAMBDA_RESCUE: return "LAMBDA_RESCUE"; case PM_CONTEXT_MAIN: return "MAIN"; case PM_CONTEXT_MODULE: return "MODULE"; + case PM_CONTEXT_MODULE_ELSE: return "MODULE_ELSE"; + case PM_CONTEXT_MODULE_ENSURE: return "MODULE_ENSURE"; + case PM_CONTEXT_MODULE_RESCUE: return "MODULE_RESCUE"; case PM_CONTEXT_NONE: return "NONE"; case PM_CONTEXT_PARENS: return "PARENS"; case PM_CONTEXT_POSTEXE: return "POSTEXE"; case PM_CONTEXT_PREDICATE: return "PREDICATE"; case PM_CONTEXT_PREEXE: return "PREEXE"; - case PM_CONTEXT_RESCUE: return "RESCUE"; - case PM_CONTEXT_RESCUE_ELSE: return "RESCUE_ELSE"; - case PM_CONTEXT_RESCUE_ELSE_DEF: return "RESCUE_ELSE_DEF"; - case PM_CONTEXT_RESCUE_DEF: return "RESCUE_DEF"; + case PM_CONTEXT_RESCUE_MODIFIER: return "RESCUE_MODIFIER"; case PM_CONTEXT_SCLASS: return "SCLASS"; + case PM_CONTEXT_SCLASS_ENSURE: return "SCLASS_ENSURE"; + case PM_CONTEXT_SCLASS_ELSE: return "SCLASS_ELSE"; + case PM_CONTEXT_SCLASS_RESCUE: return "SCLASS_RESCUE"; + case PM_CONTEXT_TERNARY: return "TERNARY"; case PM_CONTEXT_UNLESS: return "UNLESS"; case PM_CONTEXT_UNTIL: return "UNTIL"; case PM_CONTEXT_WHILE: return "WHILE"; - case PM_CONTEXT_LAMBDA_BRACES: return "LAMBDA_BRACES"; - case PM_CONTEXT_LAMBDA_DO_END: return "LAMBDA_DO_END"; } return NULL; } @@ -260,10 +278,13 @@ lex_mode_push_list(pm_parser_t *parser, bool interpolation, uint8_t delimiter) { // We'll use strpbrk to find the first of these characters. uint8_t *breakpoints = lex_mode.as.list.breakpoints; memcpy(breakpoints, "\\ \t\f\r\v\n\0\0\0", sizeof(lex_mode.as.list.breakpoints)); - - // Now we'll add the terminator to the list of breakpoints. size_t index = 7; - breakpoints[index++] = terminator; + + // Now we'll add the terminator to the list of breakpoints. If the + // terminator is not already a NULL byte, add it to the list. + if (terminator != '\0') { + breakpoints[index++] = terminator; + } // If interpolation is allowed, then we're going to check for the # // character. Otherwise we'll only look for escapes and the terminator. @@ -308,14 +329,17 @@ lex_mode_push_regexp(pm_parser_t *parser, uint8_t incrementor, uint8_t terminato // regular expression. We'll use strpbrk to find the first of these // characters. uint8_t *breakpoints = lex_mode.as.regexp.breakpoints; - memcpy(breakpoints, "\n\\#\0\0", sizeof(lex_mode.as.regexp.breakpoints)); + memcpy(breakpoints, "\r\n\\#\0\0", sizeof(lex_mode.as.regexp.breakpoints)); + size_t index = 4; // First we'll add the terminator. - breakpoints[3] = terminator; + if (terminator != '\0') { + breakpoints[index++] = terminator; + } // Next, if there is an incrementor, then we'll check for that as well. if (incrementor != '\0') { - breakpoints[4] = incrementor; + breakpoints[index++] = incrementor; } return lex_mode_push(parser, lex_mode); @@ -340,11 +364,14 @@ lex_mode_push_string(pm_parser_t *parser, bool interpolation, bool label_allowed // These are the places where we need to split up the content of the // string. We'll use strpbrk to find the first of these characters. uint8_t *breakpoints = lex_mode.as.string.breakpoints; - memcpy(breakpoints, "\n\\\0\0\0", sizeof(lex_mode.as.string.breakpoints)); + memcpy(breakpoints, "\r\n\\\0\0\0", sizeof(lex_mode.as.string.breakpoints)); + size_t index = 3; - // Now add in the terminator. - size_t index = 2; - breakpoints[index++] = terminator; + // Now add in the terminator. If the terminator is not already a NULL byte, + // then we'll add it. + if (terminator != '\0') { + breakpoints[index++] = terminator; + } // If interpolation is allowed, then we're going to check for the # // character. Otherwise we'll only look for escapes and the terminator. @@ -478,6 +505,31 @@ debug_lex_state_set(pm_parser_t *parser, pm_lex_state_t state, char const * call #define lex_state_set(parser, state) debug_lex_state_set(parser, state, __func__, __LINE__) #endif +/******************************************************************************/ +/* Command-line macro helpers */ +/******************************************************************************/ + +/** True if the parser has the given command-line option. */ +#define PM_PARSER_COMMAND_LINE_OPTION(parser, option) ((parser)->command_line & (option)) + +/** True if the -a command line option was given. */ +#define PM_PARSER_COMMAND_LINE_OPTION_A(parser) PM_PARSER_COMMAND_LINE_OPTION(parser, PM_OPTIONS_COMMAND_LINE_A) + +/** True if the -e command line option was given. */ +#define PM_PARSER_COMMAND_LINE_OPTION_E(parser) PM_PARSER_COMMAND_LINE_OPTION(parser, PM_OPTIONS_COMMAND_LINE_E) + +/** True if the -l command line option was given. */ +#define PM_PARSER_COMMAND_LINE_OPTION_L(parser) PM_PARSER_COMMAND_LINE_OPTION(parser, PM_OPTIONS_COMMAND_LINE_L) + +/** True if the -n command line option was given. */ +#define PM_PARSER_COMMAND_LINE_OPTION_N(parser) PM_PARSER_COMMAND_LINE_OPTION(parser, PM_OPTIONS_COMMAND_LINE_N) + +/** True if the -p command line option was given. */ +#define PM_PARSER_COMMAND_LINE_OPTION_P(parser) PM_PARSER_COMMAND_LINE_OPTION(parser, PM_OPTIONS_COMMAND_LINE_P) + +/** True if the -x command line option was given. */ +#define PM_PARSER_COMMAND_LINE_OPTION_X(parser) PM_PARSER_COMMAND_LINE_OPTION(parser, PM_OPTIONS_COMMAND_LINE_X) + /******************************************************************************/ /* Diagnostic-related functions */ /******************************************************************************/ @@ -613,6 +665,382 @@ pm_parser_warn_node(pm_parser_t *parser, const pm_node_t *node, pm_diagnostic_id #define PM_PARSER_WARN_TOKEN_FORMAT_CONTENT(parser, token, diag_id) \ PM_PARSER_WARN_TOKEN_FORMAT(parser, token, diag_id, (int) ((token).end - (token).start), (const char *) (token).start) +/** + * Append a warning to the list of warnings on the parser using the location of + * the given node and a format string. + */ +#define PM_PARSER_WARN_NODE_FORMAT(parser, node, diag_id, ...) \ + PM_PARSER_WARN_FORMAT(parser, (node)->location.start, (node)->location.end, diag_id, __VA_ARGS__) + +/******************************************************************************/ +/* Scope-related functions */ +/******************************************************************************/ + +/** + * Allocate and initialize a new scope. Push it onto the scope stack. + */ +static bool +pm_parser_scope_push(pm_parser_t *parser, bool closed) { + pm_scope_t *scope = (pm_scope_t *) xmalloc(sizeof(pm_scope_t)); + if (scope == NULL) return false; + + *scope = (pm_scope_t) { + .previous = parser->current_scope, + .locals = { 0 }, + .parameters = PM_SCOPE_PARAMETERS_NONE, + .numbered_parameters = PM_SCOPE_NUMBERED_PARAMETERS_NONE, + .shareable_constant = (closed || parser->current_scope == NULL) ? PM_SCOPE_SHAREABLE_CONSTANT_NONE : parser->current_scope->shareable_constant, + .closed = closed + }; + + parser->current_scope = scope; + return true; +} + +/** + * Determine if the current scope is at the top level. This means it is either + * the top-level scope or it is open to the top-level. + */ +static bool +pm_parser_scope_toplevel_p(pm_parser_t *parser) { + pm_scope_t *scope = parser->current_scope; + + do { + if (scope->previous == NULL) return true; + if (scope->closed) return false; + } while ((scope = scope->previous) != NULL); + + assert(false && "unreachable"); + return true; +} + +/** + * Retrieve the scope at the given depth. + */ +static pm_scope_t * +pm_parser_scope_find(pm_parser_t *parser, uint32_t depth) { + pm_scope_t *scope = parser->current_scope; + + while (depth-- > 0) { + assert(scope != NULL); + scope = scope->previous; + } + + return scope; +} + +static void +pm_parser_scope_forwarding_param_check(pm_parser_t *parser, const pm_token_t * token, const uint8_t mask, pm_diagnostic_id_t diag) { + pm_scope_t *scope = parser->current_scope; + while (scope) { + if (scope->parameters & mask) { + if (!scope->closed) { + pm_parser_err_token(parser, token, diag); + return; + } + return; + } + if (scope->closed) break; + scope = scope->previous; + } + + pm_parser_err_token(parser, token, diag); +} + +static inline void +pm_parser_scope_forwarding_block_check(pm_parser_t *parser, const pm_token_t * token) { + pm_parser_scope_forwarding_param_check(parser, token, PM_SCOPE_PARAMETERS_FORWARDING_BLOCK, PM_ERR_ARGUMENT_NO_FORWARDING_AMP); +} + +static inline void +pm_parser_scope_forwarding_positionals_check(pm_parser_t *parser, const pm_token_t * token) { + pm_parser_scope_forwarding_param_check(parser, token, PM_SCOPE_PARAMETERS_FORWARDING_POSITIONALS, PM_ERR_ARGUMENT_NO_FORWARDING_STAR); +} + +static inline void +pm_parser_scope_forwarding_all_check(pm_parser_t *parser, const pm_token_t * token) { + pm_parser_scope_forwarding_param_check(parser, token, PM_SCOPE_PARAMETERS_FORWARDING_ALL, PM_ERR_ARGUMENT_NO_FORWARDING_ELLIPSES); +} + +static inline void +pm_parser_scope_forwarding_keywords_check(pm_parser_t *parser, const pm_token_t * token) { + pm_parser_scope_forwarding_param_check(parser, token, PM_SCOPE_PARAMETERS_FORWARDING_KEYWORDS, PM_ERR_ARGUMENT_NO_FORWARDING_STAR_STAR); +} + +/** + * Get the current state of constant shareability. + */ +static inline pm_shareable_constant_value_t +pm_parser_scope_shareable_constant_get(pm_parser_t *parser) { + return parser->current_scope->shareable_constant; +} + +/** + * Set the current state of constant shareability. We'll set it on all of the + * open scopes so that reads are quick. + */ +static void +pm_parser_scope_shareable_constant_set(pm_parser_t *parser, pm_shareable_constant_value_t shareable_constant) { + pm_scope_t *scope = parser->current_scope; + + do { + scope->shareable_constant = shareable_constant; + } while (!scope->closed && (scope = scope->previous) != NULL); +} + +/******************************************************************************/ +/* Local variable-related functions */ +/******************************************************************************/ + +/** + * The point at which the set of locals switches from being a list to a hash. + */ +#define PM_LOCALS_HASH_THRESHOLD 9 + +static void +pm_locals_free(pm_locals_t *locals) { + if (locals->capacity > 0) { + xfree(locals->locals); + } +} + +/** + * Use as simple and fast a hash function as we can that still properly mixes + * the bits. + */ +static uint32_t +pm_locals_hash(pm_constant_id_t name) { + name = ((name >> 16) ^ name) * 0x45d9f3b; + name = ((name >> 16) ^ name) * 0x45d9f3b; + name = (name >> 16) ^ name; + return name; +} + +/** + * Resize the locals list to be twice its current size. If the next capacity is + * above the threshold for switching to a hash, then we'll switch to a hash. + */ +static void +pm_locals_resize(pm_locals_t *locals) { + uint32_t next_capacity = locals->capacity == 0 ? 4 : (locals->capacity * 2); + assert(next_capacity > locals->capacity); + + pm_local_t *next_locals = xcalloc(next_capacity, sizeof(pm_local_t)); + if (next_locals == NULL) abort(); + + if (next_capacity < PM_LOCALS_HASH_THRESHOLD) { + if (locals->size > 0) { + memcpy(next_locals, locals->locals, locals->size * sizeof(pm_local_t)); + } + } else { + // If we just switched from a list to a hash, then we need to fill in + // the hash values of all of the locals. + bool hash_needed = (locals->capacity <= PM_LOCALS_HASH_THRESHOLD); + uint32_t mask = next_capacity - 1; + + for (uint32_t index = 0; index < locals->capacity; index++) { + pm_local_t *local = &locals->locals[index]; + + if (local->name != PM_CONSTANT_ID_UNSET) { + if (hash_needed) local->hash = pm_locals_hash(local->name); + + uint32_t hash = local->hash; + while (next_locals[hash & mask].name != PM_CONSTANT_ID_UNSET) hash++; + next_locals[hash & mask] = *local; + } + } + } + + pm_locals_free(locals); + locals->locals = next_locals; + locals->capacity = next_capacity; +} + +/** + * Add a new local to the set of locals. This will automatically rehash the + * locals if the size is greater than 3/4 of the capacity. + * + * @param locals The set of locals to add to. + * @param name The name of the local. + * @param start The source location that represents the start of the local. This + * is used for the location of the warning in case this local is not read. + * @param end The source location that represents the end of the local. This is + * used for the location of the warning in case this local is not read. + * @param reads The initial number of reads for this local. Usually this is set + * to 0, but for some locals (like parameters) we want to initialize it with + * 1 so that we never warn on unused parameters. + * @return True if the local was added, and false if the local already exists. + */ +static bool +pm_locals_write(pm_locals_t *locals, pm_constant_id_t name, const uint8_t *start, const uint8_t *end, uint32_t reads) { + if (locals->size >= (locals->capacity / 4 * 3)) { + pm_locals_resize(locals); + } + + if (locals->capacity < PM_LOCALS_HASH_THRESHOLD) { + for (uint32_t index = 0; index < locals->capacity; index++) { + pm_local_t *local = &locals->locals[index]; + + if (local->name == PM_CONSTANT_ID_UNSET) { + *local = (pm_local_t) { + .name = name, + .location = { .start = start, .end = end }, + .index = locals->size++, + .reads = reads, + .hash = 0 + }; + return true; + } else if (local->name == name) { + return false; + } + } + } else { + uint32_t mask = locals->capacity - 1; + uint32_t hash = pm_locals_hash(name); + uint32_t initial_hash = hash; + + do { + pm_local_t *local = &locals->locals[hash & mask]; + + if (local->name == PM_CONSTANT_ID_UNSET) { + *local = (pm_local_t) { + .name = name, + .location = { .start = start, .end = end }, + .index = locals->size++, + .reads = reads, + .hash = initial_hash + }; + return true; + } else if (local->name == name) { + return false; + } else { + hash++; + } + } while ((hash & mask) != initial_hash); + } + + assert(false && "unreachable"); + return true; +} + +/** + * Finds the index of a local variable in the locals set. If it is not found, + * this returns UINT32_MAX. + */ +static uint32_t +pm_locals_find(pm_locals_t *locals, pm_constant_id_t name) { + if (locals->capacity < PM_LOCALS_HASH_THRESHOLD) { + for (uint32_t index = 0; index < locals->size; index++) { + pm_local_t *local = &locals->locals[index]; + if (local->name == name) return index; + } + } else { + uint32_t mask = locals->capacity - 1; + uint32_t hash = pm_locals_hash(name); + uint32_t initial_hash = hash & mask; + + do { + pm_local_t *local = &locals->locals[hash & mask]; + + if (local->name == PM_CONSTANT_ID_UNSET) { + return UINT32_MAX; + } else if (local->name == name) { + return hash & mask; + } else { + hash++; + } + } while ((hash & mask) != initial_hash); + } + + return UINT32_MAX; +} + +/** + * Called when a variable is read in a certain lexical context. Tracks the read + * by adding to the reads count. + */ +static void +pm_locals_read(pm_locals_t *locals, pm_constant_id_t name) { + uint32_t index = pm_locals_find(locals, name); + assert(index != UINT32_MAX); + + pm_local_t *local = &locals->locals[index]; + assert(local->reads < UINT32_MAX); + + local->reads++; +} + +/** + * Called when a variable read is transformed into a variable write, because a + * write operator is found after the variable name. + */ +static void +pm_locals_unread(pm_locals_t *locals, pm_constant_id_t name) { + uint32_t index = pm_locals_find(locals, name); + assert(index != UINT32_MAX); + + pm_local_t *local = &locals->locals[index]; + assert(local->reads > 0); + + local->reads--; +} + +/** + * Returns the current number of reads for a local variable. + */ +static uint32_t +pm_locals_reads(pm_locals_t *locals, pm_constant_id_t name) { + uint32_t index = pm_locals_find(locals, name); + assert(index != UINT32_MAX); + + return locals->locals[index].reads; +} + +/** + * Write out the locals into the given list of constant ids in the correct + * order. This is used to set the list of locals on the nodes in the tree once + * we're sure no additional locals will be added to the set. + * + * This function is also responsible for warning when a local variable has been + * written but not read in certain contexts. + */ +static void +pm_locals_order(PRISM_ATTRIBUTE_UNUSED pm_parser_t *parser, pm_locals_t *locals, pm_constant_id_list_t *list, bool toplevel) { + pm_constant_id_list_init_capacity(list, locals->size); + + // If we're still below the threshold for switching to a hash, then we only + // need to loop over the locals until we hit the size because the locals are + // stored in a list. + uint32_t capacity = locals->capacity < PM_LOCALS_HASH_THRESHOLD ? locals->size : locals->capacity; + + // We will only warn for unused variables if we're not at the top level, or + // if we're parsing a file outside of eval or -e. + bool warn_unused = !toplevel || (!parser->parsing_eval && !PM_PARSER_COMMAND_LINE_OPTION_E(parser)); + + for (uint32_t index = 0; index < capacity; index++) { + pm_local_t *local = &locals->locals[index]; + + if (local->name != PM_CONSTANT_ID_UNSET) { + pm_constant_id_list_insert(list, (size_t) local->index, local->name); + + if (warn_unused && local->reads == 0) { + pm_constant_t *constant = pm_constant_pool_id_to_constant(&parser->constant_pool, local->name); + + if (constant->length >= 1 && *constant->start != '_') { + PM_PARSER_WARN_FORMAT( + parser, + local->location.start, + local->location.end, + PM_WARN_UNUSED_LOCAL_VARIABLE, + (int) constant->length, + (const char *) constant->start + ); + } + } + } + } +} + /******************************************************************************/ /* Node-related functions */ /******************************************************************************/ @@ -755,13 +1183,79 @@ pm_assert_value_expression(pm_parser_t *parser, pm_node_t *node) { } /** - * Check one side of a flip-flop for integer literals. If -e was not supplied at - * the command-line, then warn. + * When we're handling the predicate of a conditional, we need to know our + * context in order to determine the kind of warning we should deliver to the + * user. + */ +typedef enum { + PM_CONDITIONAL_PREDICATE_TYPE_CONDITIONAL, + PM_CONDITIONAL_PREDICATE_TYPE_FLIP_FLOP, + PM_CONDITIONAL_PREDICATE_TYPE_NOT +} pm_conditional_predicate_type_t; + +/** + * Add a warning to the parser if the predicate of a conditional is a literal. + */ +static void +pm_parser_warn_conditional_predicate_literal(pm_parser_t *parser, pm_node_t *node, pm_conditional_predicate_type_t type, pm_diagnostic_id_t diag_id, const char *prefix) { + switch (type) { + case PM_CONDITIONAL_PREDICATE_TYPE_CONDITIONAL: + PM_PARSER_WARN_NODE_FORMAT(parser, node, diag_id, prefix, "condition"); + break; + case PM_CONDITIONAL_PREDICATE_TYPE_FLIP_FLOP: + PM_PARSER_WARN_NODE_FORMAT(parser, node, diag_id, prefix, "flip-flop"); + break; + case PM_CONDITIONAL_PREDICATE_TYPE_NOT: + break; + } +} + +/** + * Return true if the value being written within the predicate of a conditional + * is a literal value. + */ +static bool +pm_conditional_predicate_warn_write_literal_p(const pm_node_t *node) { + switch (PM_NODE_TYPE(node)) { + case PM_ARRAY_NODE: { + const pm_array_node_t *cast = (const pm_array_node_t *) node; + const pm_node_t *element; + + PM_NODE_LIST_FOREACH(&cast->elements, index, element) { + if (!pm_conditional_predicate_warn_write_literal_p(element)) { + return false; + } + } + + return true; + } + case PM_FALSE_NODE: + case PM_FLOAT_NODE: + case PM_IMAGINARY_NODE: + case PM_INTEGER_NODE: + case PM_NIL_NODE: + case PM_RATIONAL_NODE: + case PM_REGULAR_EXPRESSION_NODE: + case PM_SOURCE_ENCODING_NODE: + case PM_SOURCE_FILE_NODE: + case PM_SOURCE_LINE_NODE: + case PM_STRING_NODE: + case PM_SYMBOL_NODE: + case PM_TRUE_NODE: + return true; + default: + return false; + } +} + +/** + * Add a warning to the parser if the value that is being written inside of a + * predicate to a conditional is a literal. */ static inline void -pm_flip_flop_predicate(pm_parser_t *parser, pm_node_t *node) { - if (PM_NODE_TYPE_P(node, PM_INTEGER_NODE) && !(parser->command_line & PM_OPTIONS_COMMAND_LINE_E)) { - pm_parser_warn_node(parser, node, PM_WARN_INTEGER_IN_FLIP_FLOP); +pm_conditional_predicate_warn_write_literal(pm_parser_t *parser, const pm_node_t *node) { + if (pm_conditional_predicate_warn_write_literal_p(node)) { + pm_parser_warn_node(parser, node, parser->version == PM_OPTIONS_VERSION_CRUBY_3_3_0 ? PM_WARN_EQUAL_IN_CONDITIONAL_3_3_0 : PM_WARN_EQUAL_IN_CONDITIONAL); } } @@ -773,20 +1267,23 @@ pm_flip_flop_predicate(pm_parser_t *parser, pm_node_t *node) { * if foo and bar .. baz => RangeNode becomes FlipFlopNode * if /foo/ => RegularExpressionNode becomes MatchLastLineNode * if /foo #{bar}/ => InterpolatedRegularExpressionNode becomes InterpolatedMatchLastLineNode + * + * We also want to warn the user if they're using a static literal as a + * predicate or writing a static literal as the predicate. */ static void -pm_conditional_predicate(pm_parser_t *parser, pm_node_t *node) { +pm_conditional_predicate(pm_parser_t *parser, pm_node_t *node, pm_conditional_predicate_type_t type) { switch (PM_NODE_TYPE(node)) { case PM_AND_NODE: { pm_and_node_t *cast = (pm_and_node_t *) node; - pm_conditional_predicate(parser, cast->left); - pm_conditional_predicate(parser, cast->right); + pm_conditional_predicate(parser, cast->left, PM_CONDITIONAL_PREDICATE_TYPE_CONDITIONAL); + pm_conditional_predicate(parser, cast->right, PM_CONDITIONAL_PREDICATE_TYPE_CONDITIONAL); break; } case PM_OR_NODE: { pm_or_node_t *cast = (pm_or_node_t *) node; - pm_conditional_predicate(parser, cast->left); - pm_conditional_predicate(parser, cast->right); + pm_conditional_predicate(parser, cast->left, PM_CONDITIONAL_PREDICATE_TYPE_CONDITIONAL); + pm_conditional_predicate(parser, cast->right, PM_CONDITIONAL_PREDICATE_TYPE_CONDITIONAL); break; } case PM_PARENTHESES_NODE: { @@ -794,22 +1291,24 @@ pm_conditional_predicate(pm_parser_t *parser, pm_node_t *node) { if ((cast->body != NULL) && PM_NODE_TYPE_P(cast->body, PM_STATEMENTS_NODE)) { pm_statements_node_t *statements = (pm_statements_node_t *) cast->body; - if (statements->body.size == 1) pm_conditional_predicate(parser, statements->body.nodes[0]); + if (statements->body.size == 1) pm_conditional_predicate(parser, statements->body.nodes[0], type); } break; } + case PM_BEGIN_NODE: { + pm_begin_node_t *cast = (pm_begin_node_t *) node; + if (cast->statements != NULL) { + pm_statements_node_t *statements = cast->statements; + if (statements->body.size == 1) pm_conditional_predicate(parser, statements->body.nodes[0], type); + } + break; + } case PM_RANGE_NODE: { pm_range_node_t *cast = (pm_range_node_t *) node; - if (cast->left) { - pm_flip_flop_predicate(parser, cast->left); - pm_conditional_predicate(parser, cast->left); - } - if (cast->right) { - pm_flip_flop_predicate(parser, cast->right); - pm_conditional_predicate(parser, cast->right); - } + if (cast->left != NULL) pm_conditional_predicate(parser, cast->left, PM_CONDITIONAL_PREDICATE_TYPE_FLIP_FLOP); + if (cast->right != NULL) pm_conditional_predicate(parser, cast->right, PM_CONDITIONAL_PREDICATE_TYPE_FLIP_FLOP); // Here we change the range node into a flip flop node. We can do // this since the nodes are exactly the same except for the type. @@ -827,6 +1326,11 @@ pm_conditional_predicate(pm_parser_t *parser, pm_node_t *node) { // for the type. assert(sizeof(pm_regular_expression_node_t) == sizeof(pm_match_last_line_node_t)); node->type = PM_MATCH_LAST_LINE_NODE; + + if (!PM_PARSER_COMMAND_LINE_OPTION_E(parser)) { + pm_parser_warn_conditional_predicate_literal(parser, node, type, PM_WARN_LITERAL_IN_CONDITION_DEFAULT, "regex "); + } + break; case PM_INTERPOLATED_REGULAR_EXPRESSION_NODE: // Here we change the interpolated regular expression node into an @@ -834,6 +1338,54 @@ pm_conditional_predicate(pm_parser_t *parser, pm_node_t *node) { // are exactly the same except for the type. assert(sizeof(pm_interpolated_regular_expression_node_t) == sizeof(pm_interpolated_match_last_line_node_t)); node->type = PM_INTERPOLATED_MATCH_LAST_LINE_NODE; + + if (!PM_PARSER_COMMAND_LINE_OPTION_E(parser)) { + pm_parser_warn_conditional_predicate_literal(parser, node, type, PM_WARN_LITERAL_IN_CONDITION_VERBOSE, "regex "); + } + + break; + case PM_INTEGER_NODE: + if (type == PM_CONDITIONAL_PREDICATE_TYPE_FLIP_FLOP) { + if (!PM_PARSER_COMMAND_LINE_OPTION_E(parser)) { + pm_parser_warn_node(parser, node, PM_WARN_INTEGER_IN_FLIP_FLOP); + } + } else { + pm_parser_warn_conditional_predicate_literal(parser, node, type, PM_WARN_LITERAL_IN_CONDITION_VERBOSE, ""); + } + break; + case PM_STRING_NODE: + case PM_SOURCE_FILE_NODE: + case PM_INTERPOLATED_STRING_NODE: + pm_parser_warn_conditional_predicate_literal(parser, node, type, PM_WARN_LITERAL_IN_CONDITION_DEFAULT, "string "); + break; + case PM_SYMBOL_NODE: + case PM_INTERPOLATED_SYMBOL_NODE: + pm_parser_warn_conditional_predicate_literal(parser, node, type, PM_WARN_LITERAL_IN_CONDITION_VERBOSE, "symbol "); + break; + case PM_SOURCE_LINE_NODE: + case PM_SOURCE_ENCODING_NODE: + case PM_FLOAT_NODE: + case PM_RATIONAL_NODE: + case PM_IMAGINARY_NODE: + pm_parser_warn_conditional_predicate_literal(parser, node, type, PM_WARN_LITERAL_IN_CONDITION_VERBOSE, ""); + break; + case PM_CLASS_VARIABLE_WRITE_NODE: + pm_conditional_predicate_warn_write_literal(parser, ((pm_class_variable_write_node_t *) node)->value); + break; + case PM_CONSTANT_WRITE_NODE: + pm_conditional_predicate_warn_write_literal(parser, ((pm_constant_write_node_t *) node)->value); + break; + case PM_GLOBAL_VARIABLE_WRITE_NODE: + pm_conditional_predicate_warn_write_literal(parser, ((pm_global_variable_write_node_t *) node)->value); + break; + case PM_INSTANCE_VARIABLE_WRITE_NODE: + pm_conditional_predicate_warn_write_literal(parser, ((pm_instance_variable_write_node_t *) node)->value); + break; + case PM_LOCAL_VARIABLE_WRITE_NODE: + pm_conditional_predicate_warn_write_literal(parser, ((pm_local_variable_write_node_t *) node)->value); + break; + case PM_MULTI_WRITE_NODE: + pm_conditional_predicate_warn_write_literal(parser, ((pm_multi_write_node_t *) node)->value); break; default: break; @@ -842,9 +1394,9 @@ pm_conditional_predicate(pm_parser_t *parser, pm_node_t *node) { /** * In a lot of places in the tree you can have tokens that are not provided but - * that do not cause an error. For example, in a method call without - * parentheses. In these cases we set the token to the "not provided" type. For - * example: + * that do not cause an error. For example, this happens in a method call + * without parentheses. In these cases we set the token to the "not provided" type. + * For example: * * pm_token_t token = not_provided(parser); */ @@ -1031,6 +1583,77 @@ token_is_setter_name(pm_token_t *token) { ); } +/** + * Returns true if the given local variable is a keyword. + */ +static bool +pm_local_is_keyword(const char *source, size_t length) { +#define KEYWORD(name) if (memcmp(source, name, length) == 0) return true + + switch (length) { + case 2: + switch (source[0]) { + case 'd': KEYWORD("do"); return false; + case 'i': KEYWORD("if"); KEYWORD("in"); return false; + case 'o': KEYWORD("or"); return false; + default: return false; + } + case 3: + switch (source[0]) { + case 'a': KEYWORD("and"); return false; + case 'd': KEYWORD("def"); return false; + case 'e': KEYWORD("end"); return false; + case 'f': KEYWORD("for"); return false; + case 'n': KEYWORD("nil"); KEYWORD("not"); return false; + default: return false; + } + case 4: + switch (source[0]) { + case 'c': KEYWORD("case"); return false; + case 'e': KEYWORD("else"); return false; + case 'n': KEYWORD("next"); return false; + case 'r': KEYWORD("redo"); return false; + case 's': KEYWORD("self"); return false; + case 't': KEYWORD("then"); KEYWORD("true"); return false; + case 'w': KEYWORD("when"); return false; + default: return false; + } + case 5: + switch (source[0]) { + case 'a': KEYWORD("alias"); return false; + case 'b': KEYWORD("begin"); KEYWORD("break"); return false; + case 'c': KEYWORD("class"); return false; + case 'e': KEYWORD("elsif"); return false; + case 'f': KEYWORD("false"); return false; + case 'r': KEYWORD("retry"); return false; + case 's': KEYWORD("super"); return false; + case 'u': KEYWORD("undef"); KEYWORD("until"); return false; + case 'w': KEYWORD("while"); return false; + case 'y': KEYWORD("yield"); return false; + default: return false; + } + case 6: + switch (source[0]) { + case 'e': KEYWORD("ensure"); return false; + case 'm': KEYWORD("module"); return false; + case 'r': KEYWORD("rescue"); KEYWORD("return"); return false; + case 'u': KEYWORD("unless"); return false; + default: return false; + } + case 8: + KEYWORD("__LINE__"); + KEYWORD("__FILE__"); + return false; + case 12: + KEYWORD("__ENCODING__"); + return false; + default: + return false; + } + +#undef KEYWORD +} + /******************************************************************************/ /* Node flag handling functions */ /******************************************************************************/ @@ -1072,40 +1695,6 @@ pm_node_flag_set_repeated_parameter(pm_node_t *node) { /* Node creation functions */ /******************************************************************************/ -/** - * Parse the decimal number represented by the range of bytes. returns - * UINT32_MAX if the number fails to parse. This function assumes that the range - * of bytes has already been validated to contain only decimal digits. - */ -static uint32_t -parse_decimal_number(pm_parser_t *parser, const uint8_t *start, const uint8_t *end) { - ptrdiff_t diff = end - start; - assert(diff > 0 && ((unsigned long) diff < SIZE_MAX)); - size_t length = (size_t) diff; - - char *digits = xcalloc(length + 1, sizeof(char)); - memcpy(digits, start, length); - digits[length] = '\0'; - - char *endptr; - errno = 0; - unsigned long value = strtoul(digits, &endptr, 10); - - if ((digits == endptr) || (*endptr != '\0') || (errno == ERANGE)) { - pm_parser_err(parser, start, end, PM_ERR_INVALID_NUMBER_DECIMAL); - value = UINT32_MAX; - } - - xfree(digits); - - if (value > UINT32_MAX) { - pm_parser_err(parser, start, end, PM_ERR_INVALID_NUMBER_DECIMAL); - value = UINT32_MAX; - } - - return (uint32_t) value; -} - /** * When you have an encoding flag on a regular expression, it takes precedence * over all of the previously set encoding flags. So we need to mask off any @@ -1117,10 +1706,12 @@ parse_decimal_number(pm_parser_t *parser, const uint8_t *start, const uint8_t *e * Parse out the options for a regular expression. */ static inline pm_node_flags_t -pm_regular_expression_flags_create(const pm_token_t *closing) { +pm_regular_expression_flags_create(pm_parser_t *parser, const pm_token_t *closing) { pm_node_flags_t flags = 0; if (closing->type == PM_TOKEN_REGEXP_END) { + pm_buffer_t unknown_flags = { 0 }; + for (const uint8_t *flag = closing->start + 1; flag < closing->end; flag++) { switch (*flag) { case 'i': flags |= PM_REGULAR_EXPRESSION_FLAGS_IGNORE_CASE; break; @@ -1133,9 +1724,16 @@ pm_regular_expression_flags_create(const pm_token_t *closing) { case 's': flags = (pm_node_flags_t) (((pm_node_flags_t) (flags & PM_REGULAR_EXPRESSION_ENCODING_MASK)) | PM_REGULAR_EXPRESSION_FLAGS_WINDOWS_31J); break; case 'u': flags = (pm_node_flags_t) (((pm_node_flags_t) (flags & PM_REGULAR_EXPRESSION_ENCODING_MASK)) | PM_REGULAR_EXPRESSION_FLAGS_UTF_8); break; - default: assert(false && "unreachable"); + default: pm_buffer_append_byte(&unknown_flags, *flag); } } + + size_t unknown_flags_length = pm_buffer_length(&unknown_flags); + if (unknown_flags_length != 0) { + const char *word = unknown_flags_length >= 2 ? "options" : "option"; + PM_PARSER_ERR_TOKEN_FORMAT(parser, parser->previous, PM_ERR_REGEXP_UNKNOWN_OPTIONS, word, unknown_flags_length, pm_buffer_value(&unknown_flags)); + } + pm_buffer_free(&unknown_flags); } return flags; @@ -1402,9 +2000,9 @@ pm_array_pattern_node_node_list_create(pm_parser_t *parser, pm_node_list_t *node // For now we're going to just copy over each pointer manually. This could be // much more efficient, as we could instead resize the node list. bool found_rest = false; - for (size_t index = 0; index < nodes->size; index++) { - pm_node_t *child = nodes->nodes[index]; + pm_node_t *child; + PM_NODE_LIST_FOREACH(nodes, index, child) { if (!found_rest && (PM_NODE_TYPE_P(child, PM_SPLAT_NODE) || PM_NODE_TYPE_P(child, PM_IMPLICIT_REST_NODE))) { node->rest = child; found_rest = true; @@ -1780,7 +2378,6 @@ pm_block_parameters_node_closing_set(pm_block_parameters_node_t *node, const pm_ */ static pm_block_local_variable_node_t * pm_block_local_variable_node_create(pm_parser_t *parser, const pm_token_t *name) { - assert(name->type == PM_TOKEN_IDENTIFIER || name->type == PM_TOKEN_MISSING); pm_block_local_variable_node_t *node = PM_ALLOC_NODE(parser, pm_block_local_variable_node_t); *node = (pm_block_local_variable_node_t) { @@ -1828,6 +2425,12 @@ pm_break_node_create(pm_parser_t *parser, const pm_token_t *keyword, pm_argument return node; } +// There are certain flags that we want to use internally but don't want to +// expose because they are not relevant beyond parsing. Therefore we'll define +// them here and not define them in config.yml/a header file. +static const pm_node_flags_t PM_CALL_NODE_FLAGS_COMPARISON = 0x10; +static const pm_node_flags_t PM_CALL_NODE_FLAGS_INDEX = 0x20; + /** * Allocate and initialize a new CallNode node. This sets everything to NULL or * PM_TOKEN_NOT_PROVIDED as appropriate such that its values can be overridden @@ -1873,7 +2476,12 @@ static pm_call_node_t * pm_call_node_aref_create(pm_parser_t *parser, pm_node_t *receiver, pm_arguments_t *arguments) { pm_assert_value_expression(parser, receiver); - pm_call_node_t *node = pm_call_node_create(parser, pm_call_node_ignore_visibility_flag(receiver)); + pm_node_flags_t flags = pm_call_node_ignore_visibility_flag(receiver); + if (arguments->block == NULL || PM_NODE_TYPE_P(arguments->block, PM_BLOCK_ARGUMENT_NODE)) { + flags |= PM_CALL_NODE_FLAGS_INDEX; + } + + pm_call_node_t *node = pm_call_node_create(parser, flags); node->base.location.start = receiver->location.start; node->base.location.end = pm_arguments_end(arguments); @@ -1895,11 +2503,11 @@ pm_call_node_aref_create(pm_parser_t *parser, pm_node_t *receiver, pm_arguments_ * Allocate and initialize a new CallNode node from a binary expression. */ static pm_call_node_t * -pm_call_node_binary_create(pm_parser_t *parser, pm_node_t *receiver, pm_token_t *operator, pm_node_t *argument) { +pm_call_node_binary_create(pm_parser_t *parser, pm_node_t *receiver, pm_token_t *operator, pm_node_t *argument, pm_node_flags_t flags) { pm_assert_value_expression(parser, receiver); pm_assert_value_expression(parser, argument); - pm_call_node_t *node = pm_call_node_create(parser, pm_call_node_ignore_visibility_flag(receiver)); + pm_call_node_t *node = pm_call_node_create(parser, pm_call_node_ignore_visibility_flag(receiver) | flags); node->base.location.start = MIN(receiver->location.start, argument->location.start); node->base.location.end = MAX(receiver->location.end, argument->location.end); @@ -2008,6 +2616,7 @@ pm_call_node_fcall_synthesized_create(pm_parser_t *parser, pm_arguments_node_t * static pm_call_node_t * pm_call_node_not_create(pm_parser_t *parser, pm_node_t *receiver, pm_token_t *message, pm_arguments_t *arguments) { pm_assert_value_expression(parser, receiver); + if (receiver != NULL) pm_conditional_predicate(parser, receiver, PM_CONDITIONAL_PREDICATE_TYPE_NOT); pm_call_node_t *node = pm_call_node_create(parser, receiver == NULL ? 0 : pm_call_node_ignore_visibility_flag(receiver)); @@ -2089,30 +2698,6 @@ pm_call_node_variable_call_create(pm_parser_t *parser, pm_token_t *message) { return node; } -/** - * Returns whether or not this call node is a "vcall" (a call to a method name - * without a receiver that could also have been a local variable read). - */ -static inline bool -pm_call_node_variable_call_p(pm_call_node_t *node) { - return PM_NODE_FLAG_P(node, PM_CALL_NODE_FLAGS_VARIABLE_CALL); -} - -/** - * Returns whether or not this call is to the [] method in the index form without a block (as - * opposed to `foo.[]` and `foo[] { }`). - */ -static inline bool -pm_call_node_index_p(pm_call_node_t *node) { - return ( - (node->call_operator_loc.start == NULL) && - (node->message_loc.start != NULL) && - (node->message_loc.start[0] == '[') && - (node->message_loc.end[-1] == ']') && - (node->block == NULL || PM_NODE_TYPE_P(node->block, PM_BLOCK_ARGUMENT_NODE)) - ); -} - /** * Returns whether or not this call can be used on the left-hand side of an * operator assignment. @@ -2691,7 +3276,7 @@ pm_class_variable_write_node_create(pm_parser_t *parser, pm_class_variable_read_ }, .name = read_node->name, .name_loc = PM_LOCATION_NODE_VALUE((pm_node_t *) read_node), - .operator_loc = PM_OPTIONAL_LOCATION_TOKEN_VALUE(operator), + .operator_loc = PM_LOCATION_TOKEN_VALUE(operator), .value = value }; @@ -3229,6 +3814,15 @@ pm_double_parse(pm_parser_t *parser, const pm_token_t *token) { char *buffer = xmalloc(sizeof(char) * (length + 1)); memcpy((void *) buffer, token->start, length); + // Next, determine if we need to replace the decimal point because of + // locale-specific options, and then normalize them if we have to. + char decimal_point = *localeconv()->decimal_point; + if (decimal_point != '.') { + for (size_t index = 0; index < length; index++) { + if (buffer[index] == '.') buffer[index] = decimal_point; + } + } + // Next, handle underscores by removing them from the buffer. for (size_t index = 0; index < length; index++) { if (buffer[index] == '_') { @@ -3523,8 +4117,8 @@ pm_hash_pattern_node_node_list_create(pm_parser_t *parser, pm_node_list_t *eleme .closing_loc = PM_OPTIONAL_LOCATION_NOT_PROVIDED_VALUE }; - for (size_t index = 0; index < elements->size; index++) { - pm_node_t *element = elements->nodes[index]; + pm_node_t *element; + PM_NODE_LIST_FOREACH(elements, index, element) { pm_node_list_append(&node->elements, element); } @@ -3719,96 +4313,42 @@ pm_hash_node_create(pm_parser_t *parser, const pm_token_t *opening) { { .type = PM_HASH_NODE, .flags = PM_NODE_FLAG_STATIC_LITERAL, - .location = PM_LOCATION_TOKEN_VALUE(opening) - }, - .opening_loc = PM_LOCATION_TOKEN_VALUE(opening), - .closing_loc = PM_LOCATION_NULL_VALUE(parser), - .elements = { 0 } - }; - - return node; -} - -/** - * Append a new element to a hash node. - */ -static inline void -pm_hash_node_elements_append(pm_hash_node_t *hash, pm_node_t *element) { - pm_node_list_append(&hash->elements, element); - - bool static_literal = PM_NODE_TYPE_P(element, PM_ASSOC_NODE); - if (static_literal) { - pm_assoc_node_t *assoc = (pm_assoc_node_t *) element; - static_literal = !PM_NODE_TYPE_P(assoc->key, PM_ARRAY_NODE) && !PM_NODE_TYPE_P(assoc->key, PM_HASH_NODE) && !PM_NODE_TYPE_P(assoc->key, PM_RANGE_NODE); - static_literal = static_literal && PM_NODE_FLAG_P(assoc->key, PM_NODE_FLAG_STATIC_LITERAL); - static_literal = static_literal && PM_NODE_FLAG_P(assoc, PM_NODE_FLAG_STATIC_LITERAL); - } - - if (!static_literal) { - pm_node_flag_unset((pm_node_t *)hash, PM_NODE_FLAG_STATIC_LITERAL); - } -} - -static inline void -pm_hash_node_closing_loc_set(pm_hash_node_t *hash, pm_token_t *token) { - hash->base.location.end = token->end; - hash->closing_loc = PM_LOCATION_TOKEN_VALUE(token); -} - -/** - * Retrieve the value being written to the given node. - */ -static const pm_node_t * -pm_write_node_value(const pm_node_t *node) { - switch (PM_NODE_TYPE(node)) { - case PM_CLASS_VARIABLE_WRITE_NODE: - return ((const pm_class_variable_write_node_t *) node)->value; - case PM_CONSTANT_WRITE_NODE: - return ((const pm_constant_write_node_t * ) node)->value; - case PM_GLOBAL_VARIABLE_WRITE_NODE: - return ((const pm_global_variable_write_node_t *) node)->value; - case PM_INSTANCE_VARIABLE_WRITE_NODE: - return ((const pm_instance_variable_write_node_t *) node)->value; - case PM_LOCAL_VARIABLE_WRITE_NODE: - return ((const pm_local_variable_write_node_t *) node)->value; - case PM_MULTI_WRITE_NODE: - return ((const pm_multi_write_node_t *) node)->value; - case PM_PARENTHESES_NODE: { - const pm_parentheses_node_t *cast = (const pm_parentheses_node_t *) node; - if (cast->body != NULL) { - return pm_write_node_value(cast->body); - } - return NULL; - } - case PM_BEGIN_NODE: { - const pm_begin_node_t *cast = (const pm_begin_node_t *) node; - if (cast->statements != NULL) { - return pm_write_node_value((const pm_node_t *) cast->statements); - } - return NULL; - } - case PM_STATEMENTS_NODE: { - const pm_statements_node_t *cast = (const pm_statements_node_t *) node; - return pm_write_node_value(cast->body.nodes[cast->body.size - 1]); - } - default: - return NULL; - } + .location = PM_LOCATION_TOKEN_VALUE(opening) + }, + .opening_loc = PM_LOCATION_TOKEN_VALUE(opening), + .closing_loc = PM_LOCATION_NULL_VALUE(parser), + .elements = { 0 } + }; + + return node; } /** - * Check whether the predicate contains an assigment where the assigned value is a - * literal. If such an assignment is found, it generates a warning. + * Append a new element to a hash node. */ -static void -pm_predicate_check(pm_parser_t *parser, const pm_node_t *predicate) { - const pm_node_t *value = pm_write_node_value(predicate); +static inline void +pm_hash_node_elements_append(pm_hash_node_t *hash, pm_node_t *element) { + pm_node_list_append(&hash->elements, element); + + bool static_literal = PM_NODE_TYPE_P(element, PM_ASSOC_NODE); + if (static_literal) { + pm_assoc_node_t *assoc = (pm_assoc_node_t *) element; + static_literal = !PM_NODE_TYPE_P(assoc->key, PM_ARRAY_NODE) && !PM_NODE_TYPE_P(assoc->key, PM_HASH_NODE) && !PM_NODE_TYPE_P(assoc->key, PM_RANGE_NODE); + static_literal = static_literal && PM_NODE_FLAG_P(assoc->key, PM_NODE_FLAG_STATIC_LITERAL); + static_literal = static_literal && PM_NODE_FLAG_P(assoc, PM_NODE_FLAG_STATIC_LITERAL); + } - if ((value != NULL) && PM_NODE_FLAG_P(value, PM_NODE_FLAG_STATIC_LITERAL)) { - pm_parser_warn_token(parser, &parser->current, PM_WARN_EQUAL_IN_CONDITIONAL); + if (!static_literal) { + pm_node_flag_unset((pm_node_t *)hash, PM_NODE_FLAG_STATIC_LITERAL); } } +static inline void +pm_hash_node_closing_loc_set(pm_hash_node_t *hash, pm_token_t *token) { + hash->base.location.end = token->end; + hash->closing_loc = PM_LOCATION_TOKEN_VALUE(token); +} + /** * Allocate a new IfNode node. */ @@ -3821,8 +4361,7 @@ pm_if_node_create(pm_parser_t *parser, pm_node_t *consequent, const pm_token_t *end_keyword ) { - pm_conditional_predicate(parser, predicate); - pm_predicate_check(parser, predicate); + pm_conditional_predicate(parser, predicate, PM_CONDITIONAL_PREDICATE_TYPE_CONDITIONAL); pm_if_node_t *node = PM_ALLOC_NODE(parser, pm_if_node_t); const uint8_t *end; @@ -3861,8 +4400,7 @@ pm_if_node_create(pm_parser_t *parser, */ static pm_if_node_t * pm_if_node_modifier_create(pm_parser_t *parser, pm_node_t *statement, const pm_token_t *if_keyword, pm_node_t *predicate) { - pm_conditional_predicate(parser, predicate); - pm_predicate_check(parser, predicate); + pm_conditional_predicate(parser, predicate, PM_CONDITIONAL_PREDICATE_TYPE_CONDITIONAL); pm_if_node_t *node = PM_ALLOC_NODE(parser, pm_if_node_t); pm_statements_node_t *statements = pm_statements_node_create(parser); @@ -3894,8 +4432,7 @@ pm_if_node_modifier_create(pm_parser_t *parser, pm_node_t *statement, const pm_t static pm_if_node_t * pm_if_node_ternary_create(pm_parser_t *parser, pm_node_t *predicate, const pm_token_t *qmark, pm_node_t *true_expression, const pm_token_t *colon, pm_node_t *false_expression) { pm_assert_value_expression(parser, predicate); - pm_conditional_predicate(parser, predicate); - pm_predicate_check(parser, predicate); + pm_conditional_predicate(parser, predicate, PM_CONDITIONAL_PREDICATE_TYPE_CONDITIONAL); pm_statements_node_t *if_statements = pm_statements_node_create(parser); pm_statements_node_body_append(if_statements, true_expression); @@ -4244,6 +4781,7 @@ pm_interpolated_regular_expression_node_create(pm_parser_t *parser, const pm_tok *node = (pm_interpolated_regular_expression_node_t) { { .type = PM_INTERPOLATED_REGULAR_EXPRESSION_NODE, + .flags = PM_NODE_FLAG_STATIC_LITERAL, .location = { .start = opening->start, .end = NULL, @@ -4265,14 +4803,55 @@ pm_interpolated_regular_expression_node_append(pm_interpolated_regular_expressio if (node->base.location.end < part->location.end) { node->base.location.end = part->location.end; } + + if (PM_NODE_TYPE_P(part, PM_STRING_NODE)) { + pm_node_flag_set(part, PM_NODE_FLAG_STATIC_LITERAL | PM_STRING_FLAGS_FROZEN); + } + + if (!PM_NODE_FLAG_P(part, PM_NODE_FLAG_STATIC_LITERAL)) { + pm_node_flag_unset((pm_node_t *) node, PM_NODE_FLAG_STATIC_LITERAL); + } + pm_node_list_append(&node->parts, part); } static inline void -pm_interpolated_regular_expression_node_closing_set(pm_interpolated_regular_expression_node_t *node, const pm_token_t *closing) { +pm_interpolated_regular_expression_node_closing_set(pm_parser_t *parser, pm_interpolated_regular_expression_node_t *node, const pm_token_t *closing) { node->closing_loc = PM_LOCATION_TOKEN_VALUE(closing); node->base.location.end = closing->end; - pm_node_flag_set((pm_node_t *)node, pm_regular_expression_flags_create(closing)); + pm_node_flag_set((pm_node_t *)node, pm_regular_expression_flags_create(parser, closing)); +} + +/** + * Append a part to an InterpolatedStringNode node. + */ +static inline void +pm_interpolated_string_node_append(pm_parser_t *parser, pm_interpolated_string_node_t *node, pm_node_t *part) { + if (node->parts.size == 0 && node->opening_loc.start == NULL) { + node->base.location.start = part->location.start; + } + + if (PM_NODE_TYPE_P(part, PM_STRING_NODE)) { + pm_node_flag_set(part, PM_NODE_FLAG_STATIC_LITERAL | PM_STRING_FLAGS_FROZEN); + } + + if (!PM_NODE_FLAG_P(part, PM_NODE_FLAG_STATIC_LITERAL)) { + pm_node_flag_unset((pm_node_t *) node, PM_NODE_FLAG_STATIC_LITERAL | PM_INTERPOLATED_STRING_NODE_FLAGS_FROZEN | PM_INTERPOLATED_STRING_NODE_FLAGS_MUTABLE); + } + + pm_node_list_append(&node->parts, part); + node->base.location.end = MAX(node->base.location.end, part->location.end); + + if (PM_NODE_FLAG_P(node, PM_NODE_FLAG_STATIC_LITERAL)) { + switch (parser->frozen_string_literal) { + case PM_OPTIONS_FROZEN_STRING_LITERAL_DISABLED: + pm_node_flag_set((pm_node_t *) node, PM_INTERPOLATED_STRING_NODE_FLAGS_MUTABLE); + break; + case PM_OPTIONS_FROZEN_STRING_LITERAL_ENABLED: + pm_node_flag_set((pm_node_t *) node, PM_INTERPOLATED_STRING_NODE_FLAGS_FROZEN); + break; + } + } } /** @@ -4285,6 +4864,7 @@ pm_interpolated_string_node_create(pm_parser_t *parser, const pm_token_t *openin *node = (pm_interpolated_string_node_t) { { .type = PM_INTERPOLATED_STRING_NODE, + .flags = PM_NODE_FLAG_STATIC_LITERAL, .location = { .start = opening->start, .end = closing->end, @@ -4296,30 +4876,44 @@ pm_interpolated_string_node_create(pm_parser_t *parser, const pm_token_t *openin }; if (parts != NULL) { - node->parts = *parts; + pm_node_t *part; + PM_NODE_LIST_FOREACH(parts, index, part) { + pm_interpolated_string_node_append(parser, node, part); + } } return node; } /** - * Append a part to an InterpolatedStringNode node. + * Set the closing token of the given InterpolatedStringNode node. */ -static inline void -pm_interpolated_string_node_append(pm_interpolated_string_node_t *node, pm_node_t *part) { +static void +pm_interpolated_string_node_closing_set(pm_interpolated_string_node_t *node, const pm_token_t *closing) { + node->closing_loc = PM_OPTIONAL_LOCATION_TOKEN_VALUE(closing); + node->base.location.end = closing->end; +} + +static void +pm_interpolated_symbol_node_append(pm_interpolated_symbol_node_t *node, pm_node_t *part) { if (node->parts.size == 0 && node->opening_loc.start == NULL) { node->base.location.start = part->location.start; } + if (PM_NODE_TYPE_P(part, PM_STRING_NODE)) { + pm_node_flag_set(part, PM_NODE_FLAG_STATIC_LITERAL | PM_STRING_FLAGS_FROZEN); + } + + if (!PM_NODE_FLAG_P(part, PM_NODE_FLAG_STATIC_LITERAL)) { + pm_node_flag_unset((pm_node_t *) node, PM_NODE_FLAG_STATIC_LITERAL); + } + pm_node_list_append(&node->parts, part); - node->base.location.end = part->location.end; + node->base.location.end = MAX(node->base.location.end, part->location.end); } -/** - * Set the closing token of the given InterpolatedStringNode node. - */ static void -pm_interpolated_string_node_closing_set(pm_interpolated_string_node_t *node, const pm_token_t *closing) { +pm_interpolated_symbol_node_closing_loc_set(pm_interpolated_symbol_node_t *node, const pm_token_t *closing) { node->closing_loc = PM_OPTIONAL_LOCATION_TOKEN_VALUE(closing); node->base.location.end = closing->end; } @@ -4334,6 +4928,7 @@ pm_interpolated_symbol_node_create(pm_parser_t *parser, const pm_token_t *openin *node = (pm_interpolated_symbol_node_t) { { .type = PM_INTERPOLATED_SYMBOL_NODE, + .flags = PM_NODE_FLAG_STATIC_LITERAL, .location = { .start = opening->start, .end = closing->end, @@ -4345,22 +4940,15 @@ pm_interpolated_symbol_node_create(pm_parser_t *parser, const pm_token_t *openin }; if (parts != NULL) { - node->parts = *parts; + pm_node_t *part; + PM_NODE_LIST_FOREACH(parts, index, part) { + pm_interpolated_symbol_node_append(node, part); + } } return node; } -static inline void -pm_interpolated_symbol_node_append(pm_interpolated_symbol_node_t *node, pm_node_t *part) { - if (node->parts.size == 0 && node->opening_loc.start == NULL) { - node->base.location.start = part->location.start; - } - - pm_node_list_append(&node->parts, part); - node->base.location.end = part->location.end; -} - /** * Allocate a new InterpolatedXStringNode node. */ @@ -4386,6 +4974,10 @@ pm_interpolated_xstring_node_create(pm_parser_t *parser, const pm_token_t *openi static inline void pm_interpolated_xstring_node_append(pm_interpolated_x_string_node_t *node, pm_node_t *part) { + if (PM_NODE_TYPE_P(part, PM_STRING_NODE)) { + pm_node_flag_set(part, PM_NODE_FLAG_STATIC_LITERAL | PM_STRING_FLAGS_FROZEN); + } + pm_node_list_append(&node->parts, part); node->base.location.end = part->location.end; } @@ -4639,10 +5231,8 @@ pm_local_variable_or_write_node_create(pm_parser_t *parser, pm_node_t *target, c * Allocate a new LocalVariableReadNode node with constant_id. */ static pm_local_variable_read_node_t * -pm_local_variable_read_node_create_constant_id(pm_parser_t *parser, const pm_token_t *name, pm_constant_id_t name_id, uint32_t depth) { - if (parser->current_param_name == name_id) { - pm_parser_err_token(parser, name, PM_ERR_PARAMETER_CIRCULAR); - } +pm_local_variable_read_node_create_constant_id(pm_parser_t *parser, const pm_token_t *name, pm_constant_id_t name_id, uint32_t depth, bool missing) { + if (!missing) pm_locals_read(&pm_parser_scope_find(parser, depth)->locals, name_id); pm_local_variable_read_node_t *node = PM_ALLOC_NODE(parser, pm_local_variable_read_node_t); @@ -4659,12 +5249,22 @@ pm_local_variable_read_node_create_constant_id(pm_parser_t *parser, const pm_tok } /** - * Allocate a new LocalVariableReadNode node. + * Allocate and initialize a new LocalVariableReadNode node. */ static pm_local_variable_read_node_t * pm_local_variable_read_node_create(pm_parser_t *parser, const pm_token_t *name, uint32_t depth) { pm_constant_id_t name_id = pm_parser_constant_id_token(parser, name); - return pm_local_variable_read_node_create_constant_id(parser, name, name_id, depth); + return pm_local_variable_read_node_create_constant_id(parser, name, name_id, depth, false); +} + +/** + * Allocate and initialize a new LocalVariableReadNode node for a missing local + * variable. (This will only happen when there is a syntax error.) + */ +static pm_local_variable_read_node_t * +pm_local_variable_read_node_missing_create(pm_parser_t *parser, const pm_token_t *name, uint32_t depth) { + pm_constant_id_t name_id = pm_parser_constant_id_token(parser, name); + return pm_local_variable_read_node_create_constant_id(parser, name, name_id, depth, true); } /** @@ -4712,7 +5312,7 @@ pm_node_is_it(pm_parser_t *parser, pm_node_t *node) { // Check if it's a variable call pm_call_node_t *call_node = (pm_call_node_t *) node; - if (!pm_call_node_variable_call_p(call_node)) { + if (!PM_NODE_FLAG_P(call_node, PM_CALL_NODE_FLAGS_VARIABLE_CALL)) { return false; } @@ -4747,7 +5347,8 @@ pm_refute_numbered_parameter(pm_parser_t *parser, const uint8_t *start, const ui * name and depth. */ static pm_local_variable_target_node_t * -pm_local_variable_target_node_create_values(pm_parser_t *parser, const pm_location_t *location, pm_constant_id_t name, uint32_t depth) { +pm_local_variable_target_node_create(pm_parser_t *parser, const pm_location_t *location, pm_constant_id_t name, uint32_t depth) { + pm_refute_numbered_parameter(parser, location->start, location->end); pm_local_variable_target_node_t *node = PM_ALLOC_NODE(parser, pm_local_variable_target_node_t); *node = (pm_local_variable_target_node_t) { @@ -4762,36 +5363,6 @@ pm_local_variable_target_node_create_values(pm_parser_t *parser, const pm_locati return node; } -/** - * Allocate and initialize a new LocalVariableTargetNode node. - */ -static pm_local_variable_target_node_t * -pm_local_variable_target_node_create(pm_parser_t *parser, const pm_token_t *name) { - pm_refute_numbered_parameter(parser, name->start, name->end); - - return pm_local_variable_target_node_create_values( - parser, - &(pm_location_t) { .start = name->start, .end = name->end }, - pm_parser_constant_id_token(parser, name), - 0 - ); -} - -/** - * Allocate and initialize a new LocalVariableTargetNode node with the given depth. - */ -static pm_local_variable_target_node_t * -pm_local_variable_target_node_create_depth(pm_parser_t *parser, const pm_token_t *name, uint32_t depth) { - pm_refute_numbered_parameter(parser, name->start, name->end); - - return pm_local_variable_target_node_create_values( - parser, - &(pm_location_t) { .start = name->start, .end = name->end }, - pm_parser_constant_id_token(parser, name), - depth - ); -} - /** * Allocate and initialize a new MatchPredicateNode node. */ @@ -5074,6 +5645,52 @@ pm_numbered_parameters_node_create(pm_parser_t *parser, const pm_location_t *loc return node; } +/** + * The maximum numbered reference value is defined as the maximum value that an + * integer can hold minus 1 bit for CRuby instruction sequence operand tagging. + */ +#define NTH_REF_MAX ((uint32_t) (INT_MAX >> 1)) + +/** + * Parse the decimal number represented by the range of bytes. Returns + * 0 if the number fails to parse or if the number is greater than the maximum + * value representable by a numbered reference. This function assumes that the + * range of bytes has already been validated to contain only decimal digits. + */ +static uint32_t +pm_numbered_reference_read_node_number(pm_parser_t *parser, const pm_token_t *token) { + const uint8_t *start = token->start + 1; + const uint8_t *end = token->end; + + ptrdiff_t diff = end - start; + assert(diff > 0 && ((unsigned long) diff < SIZE_MAX)); + size_t length = (size_t) diff; + + char *digits = xcalloc(length + 1, sizeof(char)); + memcpy(digits, start, length); + digits[length] = '\0'; + + char *endptr; + errno = 0; + unsigned long value = strtoul(digits, &endptr, 10); + + if ((digits == endptr) || (*endptr != '\0') || (errno == ERANGE)) { + pm_parser_err(parser, start, end, PM_ERR_INVALID_NUMBER_DECIMAL); + value = 0; + } + + xfree(digits); + + if (value > NTH_REF_MAX) { + PM_PARSER_WARN_FORMAT(parser, start, end, PM_WARN_INVALID_NUMBERED_REFERENCE, (int) (length + 1), (const char *) token->start); + value = 0; + } + + return (uint32_t) value; +} + +#undef NTH_REF_MAX + /** * Allocate and initialize a new NthReferenceReadNode node. */ @@ -5087,7 +5704,7 @@ pm_numbered_reference_read_node_create(pm_parser_t *parser, const pm_token_t *na .type = PM_NUMBERED_REFERENCE_READ_NODE, .location = PM_LOCATION_TOKEN_VALUE(name), }, - .number = parse_decimal_number(parser, name->start + 1, name->end) + .number = pm_numbered_reference_read_node_number(parser, name) }; return node; @@ -5399,7 +6016,7 @@ pm_range_node_create(pm_parser_t *parser, pm_node_t *left, const pm_token_t *ope pm_range_node_t *node = PM_ALLOC_NODE(parser, pm_range_node_t); pm_node_flags_t flags = 0; - // Indicate that this node an exclusive range if the operator is `...`. + // Indicate that this node is an exclusive range if the operator is `...`. if (operator->type == PM_TOKEN_DOT_DOT_DOT || operator->type == PM_TOKEN_UDOT_DOT_DOT) { flags |= PM_RANGE_FLAGS_EXCLUDE_END; } @@ -5454,7 +6071,7 @@ pm_regular_expression_node_create_unescaped(pm_parser_t *parser, const pm_token_ *node = (pm_regular_expression_node_t) { { .type = PM_REGULAR_EXPRESSION_NODE, - .flags = pm_regular_expression_flags_create(closing) | PM_NODE_FLAG_STATIC_LITERAL, + .flags = pm_regular_expression_flags_create(parser, closing) | PM_NODE_FLAG_STATIC_LITERAL, .location = { .start = MIN(opening->start, closing->start), .end = MAX(opening->end, closing->end) @@ -5519,7 +6136,7 @@ pm_rescue_modifier_node_create(pm_parser_t *parser, pm_node_t *expression, const } /** - * Allocate and initiliaze a new RescueNode node. + * Allocate and initialize a new RescueNode node. */ static pm_rescue_node_t * pm_rescue_node_create(pm_parser_t *parser, const pm_token_t *keyword) { @@ -5657,6 +6274,25 @@ pm_self_node_create(pm_parser_t *parser, const pm_token_t *token) { return node; } +/** + * Allocate and initialize a new ShareableConstantNode node. + */ +static pm_shareable_constant_node_t * +pm_shareable_constant_node_create(pm_parser_t *parser, pm_node_t *write, pm_shareable_constant_value_t value) { + pm_shareable_constant_node_t *node = PM_ALLOC_NODE(parser, pm_shareable_constant_node_t); + + *node = (pm_shareable_constant_node_t) { + { + .type = PM_SHAREABLE_CONSTANT_NODE, + .flags = (pm_node_flags_t) value, + .location = PM_LOCATION_NODE_VALUE(write) + }, + .write = write + }; + + return node; +} + /** * Allocate a new SingletonClassNode node. */ @@ -5708,10 +6344,21 @@ pm_source_file_node_create(pm_parser_t *parser, const pm_token_t *file_keyword) pm_source_file_node_t *node = PM_ALLOC_NODE(parser, pm_source_file_node_t); assert(file_keyword->type == PM_TOKEN_KEYWORD___FILE__); + pm_node_flags_t flags = 0; + + switch (parser->frozen_string_literal) { + case PM_OPTIONS_FROZEN_STRING_LITERAL_DISABLED: + flags |= PM_STRING_FLAGS_MUTABLE; + break; + case PM_OPTIONS_FROZEN_STRING_LITERAL_ENABLED: + flags |= PM_NODE_FLAG_STATIC_LITERAL | PM_STRING_FLAGS_FROZEN; + break; + } + *node = (pm_source_file_node_t) { { .type = PM_SOURCE_FILE_NODE, - .flags = PM_NODE_FLAG_STATIC_LITERAL, + .flags = flags, .location = PM_LOCATION_TOKEN_VALUE(file_keyword), }, .filepath = parser->filepath @@ -5836,8 +6483,13 @@ pm_string_node_create_unescaped(pm_parser_t *parser, const pm_token_t *opening, pm_string_node_t *node = PM_ALLOC_NODE(parser, pm_string_node_t); pm_node_flags_t flags = 0; - if (parser->frozen_string_literal) { - flags = PM_NODE_FLAG_STATIC_LITERAL | PM_STRING_FLAGS_FROZEN; + switch (parser->frozen_string_literal) { + case PM_OPTIONS_FROZEN_STRING_LITERAL_DISABLED: + flags = PM_STRING_FLAGS_MUTABLE; + break; + case PM_OPTIONS_FROZEN_STRING_LITERAL_ENABLED: + flags = PM_NODE_FLAG_STATIC_LITERAL | PM_STRING_FLAGS_FROZEN; + break; } *node = (pm_string_node_t) { @@ -6236,8 +6888,13 @@ pm_symbol_node_to_string_node(pm_parser_t *parser, pm_symbol_node_t *node) { pm_string_node_t *new_node = PM_ALLOC_NODE(parser, pm_string_node_t); pm_node_flags_t flags = 0; - if (parser->frozen_string_literal) { - flags = PM_NODE_FLAG_STATIC_LITERAL | PM_STRING_FLAGS_FROZEN; + switch (parser->frozen_string_literal) { + case PM_OPTIONS_FROZEN_STRING_LITERAL_DISABLED: + flags = PM_STRING_FLAGS_MUTABLE; + break; + case PM_OPTIONS_FROZEN_STRING_LITERAL_ENABLED: + flags = PM_NODE_FLAG_STATIC_LITERAL | PM_STRING_FLAGS_FROZEN; + break; } *new_node = (pm_string_node_t) { @@ -6327,8 +6984,7 @@ pm_undef_node_append(pm_undef_node_t *node, pm_node_t *name) { */ static pm_unless_node_t * pm_unless_node_create(pm_parser_t *parser, const pm_token_t *keyword, pm_node_t *predicate, const pm_token_t *then_keyword, pm_statements_node_t *statements) { - pm_conditional_predicate(parser, predicate); - pm_predicate_check(parser, predicate); + pm_conditional_predicate(parser, predicate, PM_CONDITIONAL_PREDICATE_TYPE_CONDITIONAL); pm_unless_node_t *node = PM_ALLOC_NODE(parser, pm_unless_node_t); const uint8_t *end; @@ -6363,8 +7019,7 @@ pm_unless_node_create(pm_parser_t *parser, const pm_token_t *keyword, pm_node_t */ static pm_unless_node_t * pm_unless_node_modifier_create(pm_parser_t *parser, pm_node_t *statement, const pm_token_t *unless_keyword, pm_node_t *predicate) { - pm_conditional_predicate(parser, predicate); - pm_predicate_check(parser, predicate); + pm_conditional_predicate(parser, predicate, PM_CONDITIONAL_PREDICATE_TYPE_CONDITIONAL); pm_unless_node_t *node = PM_ALLOC_NODE(parser, pm_unless_node_t); pm_statements_node_t *statements = pm_statements_node_create(parser); @@ -6402,7 +7057,7 @@ pm_unless_node_end_keyword_loc_set(pm_unless_node_t *node, const pm_token_t *end static pm_until_node_t * pm_until_node_create(pm_parser_t *parser, const pm_token_t *keyword, const pm_token_t *closing, pm_node_t *predicate, pm_statements_node_t *statements, pm_node_flags_t flags) { pm_until_node_t *node = PM_ALLOC_NODE(parser, pm_until_node_t); - pm_predicate_check(parser, predicate); + pm_conditional_predicate(parser, predicate, PM_CONDITIONAL_PREDICATE_TYPE_CONDITIONAL); *node = (pm_until_node_t) { { @@ -6428,7 +7083,7 @@ pm_until_node_create(pm_parser_t *parser, const pm_token_t *keyword, const pm_to static pm_until_node_t * pm_until_node_modifier_create(pm_parser_t *parser, const pm_token_t *keyword, pm_node_t *predicate, pm_statements_node_t *statements, pm_node_flags_t flags) { pm_until_node_t *node = PM_ALLOC_NODE(parser, pm_until_node_t); - pm_predicate_check(parser, predicate); + pm_conditional_predicate(parser, predicate, PM_CONDITIONAL_PREDICATE_TYPE_CONDITIONAL); *node = (pm_until_node_t) { { @@ -6508,7 +7163,7 @@ pm_when_node_statements_set(pm_when_node_t *node, pm_statements_node_t *statemen static pm_while_node_t * pm_while_node_create(pm_parser_t *parser, const pm_token_t *keyword, const pm_token_t *closing, pm_node_t *predicate, pm_statements_node_t *statements, pm_node_flags_t flags) { pm_while_node_t *node = PM_ALLOC_NODE(parser, pm_while_node_t); - pm_predicate_check(parser, predicate); + pm_conditional_predicate(parser, predicate, PM_CONDITIONAL_PREDICATE_TYPE_CONDITIONAL); *node = (pm_while_node_t) { { @@ -6534,7 +7189,7 @@ pm_while_node_create(pm_parser_t *parser, const pm_token_t *keyword, const pm_to static pm_while_node_t * pm_while_node_modifier_create(pm_parser_t *parser, const pm_token_t *keyword, pm_node_t *predicate, pm_statements_node_t *statements, pm_node_flags_t flags) { pm_while_node_t *node = PM_ALLOC_NODE(parser, pm_while_node_t); - pm_predicate_check(parser, predicate); + pm_conditional_predicate(parser, predicate, PM_CONDITIONAL_PREDICATE_TYPE_CONDITIONAL); *node = (pm_while_node_t) { { @@ -6646,95 +7301,6 @@ pm_yield_node_create(pm_parser_t *parser, const pm_token_t *keyword, const pm_lo #undef PM_ALLOC_NODE -/******************************************************************************/ -/* Scope-related functions */ -/******************************************************************************/ - -/** - * Allocate and initialize a new scope. Push it onto the scope stack. - */ -static bool -pm_parser_scope_push(pm_parser_t *parser, bool closed) { - pm_scope_t *scope = (pm_scope_t *) xmalloc(sizeof(pm_scope_t)); - if (scope == NULL) return false; - - *scope = (pm_scope_t) { - .previous = parser->current_scope, - .locals = { 0 }, - .parameters = PM_SCOPE_PARAMETERS_NONE, - .numbered_parameters = PM_SCOPE_NUMBERED_PARAMETERS_NONE, - .closed = closed - }; - - parser->current_scope = scope; - return true; -} - -static void -pm_parser_scope_forwarding_param_check(pm_parser_t *parser, const pm_token_t * token, const uint8_t mask, pm_diagnostic_id_t diag) { - pm_scope_t *scope = parser->current_scope; - while (scope) { - if (scope->parameters & mask) { - if (!scope->closed) { - pm_parser_err_token(parser, token, diag); - return; - } - return; - } - if (scope->closed) break; - scope = scope->previous; - } - - pm_parser_err_token(parser, token, diag); -} - -static inline void -pm_parser_scope_forwarding_block_check(pm_parser_t *parser, const pm_token_t * token) { - pm_parser_scope_forwarding_param_check(parser, token, PM_SCOPE_PARAMETERS_FORWARDING_BLOCK, PM_ERR_ARGUMENT_NO_FORWARDING_AMP); -} - -static inline void -pm_parser_scope_forwarding_positionals_check(pm_parser_t *parser, const pm_token_t * token) { - pm_parser_scope_forwarding_param_check(parser, token, PM_SCOPE_PARAMETERS_FORWARDING_POSITIONALS, PM_ERR_ARGUMENT_NO_FORWARDING_STAR); -} - -static inline void -pm_parser_scope_forwarding_all_check(pm_parser_t *parser, const pm_token_t * token) { - pm_parser_scope_forwarding_param_check(parser, token, PM_SCOPE_PARAMETERS_FORWARDING_ALL, PM_ERR_ARGUMENT_NO_FORWARDING_ELLIPSES); -} - -static inline void -pm_parser_scope_forwarding_keywords_check(pm_parser_t *parser, const pm_token_t * token) { - pm_parser_scope_forwarding_param_check(parser, token, PM_SCOPE_PARAMETERS_FORWARDING_KEYWORDS, PM_ERR_EXPECT_EXPRESSION_AFTER_SPLAT_HASH); -} - -/** - * Save the current param name as the return value and set it to the given - * constant id. - */ -static inline pm_constant_id_t -pm_parser_current_param_name_set(pm_parser_t *parser, pm_constant_id_t current_param_name) { - pm_constant_id_t saved_param_name = parser->current_param_name; - parser->current_param_name = current_param_name; - return saved_param_name; -} - -/** - * Save the current param name as the return value and clear it. - */ -static inline pm_constant_id_t -pm_parser_current_param_name_unset(pm_parser_t *parser) { - return pm_parser_current_param_name_set(parser, PM_CONSTANT_ID_UNSET); -} - -/** - * Restore the current param name from the given value. - */ -static inline void -pm_parser_current_param_name_restore(pm_parser_t *parser, pm_constant_id_t saved_param_name) { - parser->current_param_name = saved_param_name; -} - /** * Check if any of the currently visible scopes contain a local variable * described by the given constant id. @@ -6745,7 +7311,7 @@ pm_parser_local_depth_constant_id(pm_parser_t *parser, pm_constant_id_t constant int depth = 0; while (scope != NULL) { - if (pm_constant_id_list_includes(&scope->locals, constant_id)) return depth; + if (pm_locals_find(&scope->locals, constant_id) != UINT32_MAX) return depth; if (scope->closed) break; scope = scope->previous; @@ -6769,28 +7335,26 @@ pm_parser_local_depth(pm_parser_t *parser, pm_token_t *token) { * Add a constant id to the local table of the current scope. */ static inline void -pm_parser_local_add(pm_parser_t *parser, pm_constant_id_t constant_id) { - if (!pm_constant_id_list_includes(&parser->current_scope->locals, constant_id)) { - pm_constant_id_list_append(&parser->current_scope->locals, constant_id); - } +pm_parser_local_add(pm_parser_t *parser, pm_constant_id_t constant_id, const uint8_t *start, const uint8_t *end, uint32_t reads) { + pm_locals_write(&parser->current_scope->locals, constant_id, start, end, reads); } /** * Add a local variable from a location to the current scope. */ static pm_constant_id_t -pm_parser_local_add_location(pm_parser_t *parser, const uint8_t *start, const uint8_t *end) { +pm_parser_local_add_location(pm_parser_t *parser, const uint8_t *start, const uint8_t *end, uint32_t reads) { pm_constant_id_t constant_id = pm_parser_constant_id_location(parser, start, end); - if (constant_id != 0) pm_parser_local_add(parser, constant_id); + if (constant_id != 0) pm_parser_local_add(parser, constant_id, start, end, reads); return constant_id; } /** * Add a local variable from a token to the current scope. */ -static inline void -pm_parser_local_add_token(pm_parser_t *parser, pm_token_t *token) { - pm_parser_local_add_location(parser, token->start, token->end); +static inline pm_constant_id_t +pm_parser_local_add_token(pm_parser_t *parser, pm_token_t *token, uint32_t reads) { + return pm_parser_local_add_location(parser, token->start, token->end, reads); } /** @@ -6799,7 +7363,7 @@ pm_parser_local_add_token(pm_parser_t *parser, pm_token_t *token) { static pm_constant_id_t pm_parser_local_add_owned(pm_parser_t *parser, uint8_t *start, size_t length) { pm_constant_id_t constant_id = pm_parser_constant_id_owned(parser, start, length); - if (constant_id != 0) pm_parser_local_add(parser, constant_id); + if (constant_id != 0) pm_parser_local_add(parser, constant_id, parser->start, parser->start, 1); return constant_id; } @@ -6809,7 +7373,7 @@ pm_parser_local_add_owned(pm_parser_t *parser, uint8_t *start, size_t length) { static pm_constant_id_t pm_parser_local_add_constant(pm_parser_t *parser, const char *start, size_t length) { pm_constant_id_t constant_id = pm_parser_constant_id_constant(parser, start, length); - if (constant_id != 0) pm_parser_local_add(parser, constant_id); + if (constant_id != 0) pm_parser_local_add(parser, constant_id, parser->start, parser->start, 1); return constant_id; } @@ -6831,9 +7395,9 @@ pm_local_variable_read_node_create_it(pm_parser_t *parser, const pm_token_t *nam parser->current_scope->parameters |= PM_SCOPE_PARAMETERS_IT; pm_constant_id_t name_id = pm_parser_constant_id_constant(parser, "0it", 3); - pm_parser_local_add(parser, name_id); + pm_parser_local_add(parser, name_id, name->start, name->end, 0); - return pm_local_variable_read_node_create_constant_id(parser, name, name_id, 0); + return pm_local_variable_read_node_create_constant_id(parser, name, name_id, 0, false); } /** @@ -6875,10 +7439,10 @@ pm_parser_parameter_name_check(pm_parser_t *parser, const pm_token_t *name) { // whether it's already in the current scope. pm_constant_id_t constant_id = pm_parser_constant_id_token(parser, name); - if (pm_constant_id_list_includes(&parser->current_scope->locals, constant_id)) { + if (pm_locals_find(&parser->current_scope->locals, constant_id) != UINT32_MAX) { // Add an error if the parameter doesn't start with _ and has been seen before if ((name->start < name->end) && (*name->start != '_')) { - pm_parser_err_token(parser, name, PM_ERR_PARAMETER_NAME_REPEAT); + pm_parser_err_token(parser, name, PM_ERR_PARAMETER_NAME_DUPLICATED); } return true; } @@ -6886,14 +7450,13 @@ pm_parser_parameter_name_check(pm_parser_t *parser, const pm_token_t *name) { } /** - * Pop the current scope off the scope stack. Note that we specifically do not - * free the associated constant list because we assume that we have already - * transferred ownership of the list to the AST somewhere. + * Pop the current scope off the scope stack. */ static void pm_parser_scope_pop(pm_parser_t *parser) { pm_scope_t *scope = parser->current_scope; parser->current_scope = scope->previous; + pm_locals_free(&scope->locals); xfree(scope); } @@ -6970,7 +7533,7 @@ peek(pm_parser_t *parser) { /** * If the character to be read matches the given value, then returns true and - * advanced the current pointer. + * advances the current pointer. */ static inline bool match(pm_parser_t *parser, uint8_t value) { @@ -7113,9 +7676,9 @@ parser_lex_magic_comment_encoding(pm_parser_t *parser) { static void parser_lex_magic_comment_frozen_string_literal_value(pm_parser_t *parser, const uint8_t *start, const uint8_t *end) { if ((start + 4 <= end) && pm_strncasecmp(start, (const uint8_t *) "true", 4) == 0) { - parser->frozen_string_literal = true; + parser->frozen_string_literal = PM_OPTIONS_FROZEN_STRING_LITERAL_ENABLED; } else if ((start + 5 <= end) && pm_strncasecmp(start, (const uint8_t *) "false", 5) == 0) { - parser->frozen_string_literal = false; + parser->frozen_string_literal = PM_OPTIONS_FROZEN_STRING_LITERAL_DISABLED; } } @@ -7257,19 +7820,43 @@ parser_lex_magic_comment(pm_parser_t *parser, bool semantic_token_seen) { // We only want to handle frozen string literal comments if it's before // any semantic tokens have been seen. - if (!semantic_token_seen) { - if (key_length == 21 && pm_strncasecmp(key_source, (const uint8_t *) "frozen_string_literal", 21) == 0) { + if (key_length == 21 && pm_strncasecmp(key_source, (const uint8_t *) "frozen_string_literal", 21) == 0) { + if (semantic_token_seen) { + pm_parser_warn_token(parser, &parser->current, PM_WARN_IGNORED_FROZEN_STRING_LITERAL); + } else { parser_lex_magic_comment_frozen_string_literal_value(parser, value_start, value_end); } } + // If we have hit a ractor pragma, attempt to lex that. + uint32_t value_length = (uint32_t) (value_end - value_start); + if (key_length == 24 && pm_strncasecmp(key_source, (const uint8_t *) "shareable_constant_value", 24) == 0) { + if (value_length == 4 && pm_strncasecmp(value_start, (const uint8_t *) "none", 4) == 0) { + pm_parser_scope_shareable_constant_set(parser, PM_SCOPE_SHAREABLE_CONSTANT_NONE); + } else if (value_length == 7 && pm_strncasecmp(value_start, (const uint8_t *) "literal", 7) == 0) { + pm_parser_scope_shareable_constant_set(parser, PM_SCOPE_SHAREABLE_CONSTANT_LITERAL); + } else if (value_length == 23 && pm_strncasecmp(value_start, (const uint8_t *) "experimental_everything", 23) == 0) { + pm_parser_scope_shareable_constant_set(parser, PM_SCOPE_SHAREABLE_CONSTANT_EXPERIMENTAL_EVERYTHING); + } else if (value_length == 17 && pm_strncasecmp(value_start, (const uint8_t *) "experimental_copy", 17) == 0) { + pm_parser_scope_shareable_constant_set(parser, PM_SCOPE_SHAREABLE_CONSTANT_EXPERIMENTAL_COPY); + } else { + PM_PARSER_WARN_TOKEN_FORMAT( + parser, + parser->current, + PM_WARN_INVALID_SHAREABLE_CONSTANT_VALUE, + (int) value_length, + (const char *) value_start + ); + } + } + // When we're done, we want to free the string in case we had to // allocate memory for it. pm_string_free(&key); // Allocate a new magic comment node to append to the parser's list. pm_magic_comment_t *magic_comment; - if ((magic_comment = (pm_magic_comment_t *) xcalloc(sizeof(pm_magic_comment_t), 1)) != NULL) { + if ((magic_comment = (pm_magic_comment_t *) xcalloc(1, sizeof(pm_magic_comment_t))) != NULL) { magic_comment->key_start = key_start; magic_comment->value_start = value_start; magic_comment->key_length = (uint32_t) key_length; @@ -7290,6 +7877,9 @@ context_terminator(pm_context_t context, pm_token_t *token) { switch (context) { case PM_CONTEXT_MAIN: case PM_CONTEXT_DEF_PARAMS: + case PM_CONTEXT_DEFINED: + case PM_CONTEXT_TERNARY: + case PM_CONTEXT_RESCUE_MODIFIER: return token->type == PM_TOKEN_EOF; case PM_CONTEXT_DEFAULT_PARAMS: return token->type == PM_TOKEN_COMMA || token->type == PM_TOKEN_PARENTHESIS_RIGHT; @@ -7307,8 +7897,13 @@ context_terminator(pm_context_t context, pm_token_t *token) { case PM_CONTEXT_UNTIL: case PM_CONTEXT_ELSE: case PM_CONTEXT_FOR: - case PM_CONTEXT_ENSURE: - case PM_CONTEXT_ENSURE_DEF: + case PM_CONTEXT_BEGIN_ENSURE: + case PM_CONTEXT_BLOCK_ENSURE: + case PM_CONTEXT_CLASS_ENSURE: + case PM_CONTEXT_DEF_ENSURE: + case PM_CONTEXT_LAMBDA_ENSURE: + case PM_CONTEXT_MODULE_ENSURE: + case PM_CONTEXT_SCLASS_ENSURE: return token->type == PM_TOKEN_KEYWORD_END; case PM_CONTEXT_FOR_INDEX: return token->type == PM_TOKEN_KEYWORD_IN; @@ -7328,11 +7923,21 @@ context_terminator(pm_context_t context, pm_token_t *token) { case PM_CONTEXT_PARENS: return token->type == PM_TOKEN_PARENTHESIS_RIGHT; case PM_CONTEXT_BEGIN: - case PM_CONTEXT_RESCUE: - case PM_CONTEXT_RESCUE_DEF: + case PM_CONTEXT_BEGIN_RESCUE: + case PM_CONTEXT_BLOCK_RESCUE: + case PM_CONTEXT_CLASS_RESCUE: + case PM_CONTEXT_DEF_RESCUE: + case PM_CONTEXT_LAMBDA_RESCUE: + case PM_CONTEXT_MODULE_RESCUE: + case PM_CONTEXT_SCLASS_RESCUE: return token->type == PM_TOKEN_KEYWORD_ENSURE || token->type == PM_TOKEN_KEYWORD_RESCUE || token->type == PM_TOKEN_KEYWORD_ELSE || token->type == PM_TOKEN_KEYWORD_END; - case PM_CONTEXT_RESCUE_ELSE: - case PM_CONTEXT_RESCUE_ELSE_DEF: + case PM_CONTEXT_BEGIN_ELSE: + case PM_CONTEXT_BLOCK_ELSE: + case PM_CONTEXT_CLASS_ELSE: + case PM_CONTEXT_DEF_ELSE: + case PM_CONTEXT_LAMBDA_ELSE: + case PM_CONTEXT_MODULE_ELSE: + case PM_CONTEXT_SCLASS_ELSE: return token->type == PM_TOKEN_KEYWORD_ENSURE || token->type == PM_TOKEN_KEYWORD_END; case PM_CONTEXT_LAMBDA_BRACES: return token->type == PM_TOKEN_BRACE_RIGHT; @@ -7405,13 +8010,22 @@ context_def_p(const pm_parser_t *parser) { switch (context_node->context) { case PM_CONTEXT_DEF: case PM_CONTEXT_DEF_PARAMS: - case PM_CONTEXT_ENSURE_DEF: - case PM_CONTEXT_RESCUE_DEF: - case PM_CONTEXT_RESCUE_ELSE_DEF: + case PM_CONTEXT_DEF_ENSURE: + case PM_CONTEXT_DEF_RESCUE: + case PM_CONTEXT_DEF_ELSE: return true; case PM_CONTEXT_CLASS: + case PM_CONTEXT_CLASS_ENSURE: + case PM_CONTEXT_CLASS_RESCUE: + case PM_CONTEXT_CLASS_ELSE: case PM_CONTEXT_MODULE: + case PM_CONTEXT_MODULE_ENSURE: + case PM_CONTEXT_MODULE_RESCUE: + case PM_CONTEXT_MODULE_ELSE: case PM_CONTEXT_SCLASS: + case PM_CONTEXT_SCLASS_ENSURE: + case PM_CONTEXT_SCLASS_RESCUE: + case PM_CONTEXT_SCLASS_ELSE: return false; default: context_node = context_node->prev; @@ -7440,11 +8054,24 @@ context_human(pm_context_t context) { case PM_CONTEXT_DEF: return "method definition"; case PM_CONTEXT_DEF_PARAMS: return "method parameters"; case PM_CONTEXT_DEFAULT_PARAMS: return "parameter default value"; - case PM_CONTEXT_ELSE: return "'else' clause"; + case PM_CONTEXT_DEFINED: return "'defined?' expression"; + case PM_CONTEXT_ELSE: + case PM_CONTEXT_BEGIN_ELSE: + case PM_CONTEXT_BLOCK_ELSE: + case PM_CONTEXT_CLASS_ELSE: + case PM_CONTEXT_DEF_ELSE: + case PM_CONTEXT_LAMBDA_ELSE: + case PM_CONTEXT_MODULE_ELSE: + case PM_CONTEXT_SCLASS_ELSE: return "'else' clause"; case PM_CONTEXT_ELSIF: return "'elsif' clause"; case PM_CONTEXT_EMBEXPR: return "embedded expression"; - case PM_CONTEXT_ENSURE: return "'ensure' clause"; - case PM_CONTEXT_ENSURE_DEF: return "'ensure' clause"; + case PM_CONTEXT_BEGIN_ENSURE: + case PM_CONTEXT_BLOCK_ENSURE: + case PM_CONTEXT_CLASS_ENSURE: + case PM_CONTEXT_DEF_ENSURE: + case PM_CONTEXT_LAMBDA_ENSURE: + case PM_CONTEXT_MODULE_ENSURE: + case PM_CONTEXT_SCLASS_ENSURE: return "'ensure' clause"; case PM_CONTEXT_FOR: return "for loop"; case PM_CONTEXT_FOR_INDEX: return "for loop index"; case PM_CONTEXT_IF: return "if statement"; @@ -7456,11 +8083,16 @@ context_human(pm_context_t context) { case PM_CONTEXT_POSTEXE: return "'END' block"; case PM_CONTEXT_PREDICATE: return "predicate"; case PM_CONTEXT_PREEXE: return "'BEGIN' block"; - case PM_CONTEXT_RESCUE_ELSE: return "'else' clause"; - case PM_CONTEXT_RESCUE_ELSE_DEF: return "'else' clause"; - case PM_CONTEXT_RESCUE: return "'rescue' clause"; - case PM_CONTEXT_RESCUE_DEF: return "'rescue' clause"; + case PM_CONTEXT_BEGIN_RESCUE: + case PM_CONTEXT_BLOCK_RESCUE: + case PM_CONTEXT_CLASS_RESCUE: + case PM_CONTEXT_DEF_RESCUE: + case PM_CONTEXT_LAMBDA_RESCUE: + case PM_CONTEXT_MODULE_RESCUE: + case PM_CONTEXT_RESCUE_MODIFIER: + case PM_CONTEXT_SCLASS_RESCUE: return "'rescue' clause"; case PM_CONTEXT_SCLASS: return "singleton class definition"; + case PM_CONTEXT_TERNARY: return "ternary expression"; case PM_CONTEXT_UNLESS: return "unless statement"; case PM_CONTEXT_UNTIL: return "until statement"; case PM_CONTEXT_WHILE: return "while statement"; @@ -7525,26 +8157,33 @@ lex_optional_float_suffix(pm_parser_t *parser, bool* seen_e) { parser->current.end += pm_strspn_decimal_number_validate(parser, parser->current.end); type = PM_TOKEN_FLOAT; } else { - // If we had a . and then something else, then it's not a float suffix on - // a number it's a method call or something else. + // If we had a . and then something else, then it's not a float + // suffix on a number it's a method call or something else. return type; } } // Here we're going to attempt to parse the optional exponent portion of a // float. If it's not there, it's okay and we'll just continue on. - if (match(parser, 'e') || match(parser, 'E')) { - (void) (match(parser, '+') || match(parser, '-')); - *seen_e = true; + if ((peek(parser) == 'e') || (peek(parser) == 'E')) { + if ((peek_offset(parser, 1) == '+') || (peek_offset(parser, 1) == '-')) { + parser->current.end += 2; - if (pm_char_is_decimal_digit(peek(parser))) { + if (pm_char_is_decimal_digit(peek(parser))) { + parser->current.end++; + parser->current.end += pm_strspn_decimal_number_validate(parser, parser->current.end); + } else { + pm_parser_err_current(parser, PM_ERR_INVALID_FLOAT_EXPONENT); + } + } else if (pm_char_is_decimal_digit(peek_offset(parser, 1))) { parser->current.end++; parser->current.end += pm_strspn_decimal_number_validate(parser, parser->current.end); - type = PM_TOKEN_FLOAT; } else { - pm_parser_err_current(parser, PM_ERR_INVALID_FLOAT_EXPONENT); - type = PM_TOKEN_FLOAT; + return type; } + + *seen_e = true; + type = PM_TOKEN_FLOAT; } return type; @@ -7695,8 +8334,7 @@ lex_numeric(pm_parser_t *parser) { static pm_token_type_t lex_global_variable(pm_parser_t *parser) { if (parser->current.end >= parser->end) { - pm_diagnostic_id_t diag_id = parser->version == PM_OPTIONS_VERSION_CRUBY_3_3_0 ? PM_ERR_INVALID_VARIABLE_GLOBAL_3_3_0 : PM_ERR_INVALID_VARIABLE_GLOBAL; - PM_PARSER_ERR_TOKEN_FORMAT_CONTENT(parser, parser->current, diag_id); + pm_parser_err_token(parser, &parser->current, PM_ERR_GLOBAL_VARIABLE_BARE); return PM_TOKEN_GLOBAL_VARIABLE; } @@ -7766,11 +8404,16 @@ lex_global_variable(pm_parser_t *parser) { do { parser->current.end += width; } while (parser->current.end < parser->end && (width = char_is_identifier(parser, parser->current.end)) > 0); + } else if (pm_char_is_whitespace(peek(parser))) { + // If we get here, then we have a $ followed by whitespace, + // which is not allowed. + pm_parser_err_token(parser, &parser->current, PM_ERR_GLOBAL_VARIABLE_BARE); } else { - // If we get here, then we have a $ followed by something that isn't - // recognized as a global variable. + // If we get here, then we have a $ followed by something that + // isn't recognized as a global variable. pm_diagnostic_id_t diag_id = parser->version == PM_OPTIONS_VERSION_CRUBY_3_3_0 ? PM_ERR_INVALID_VARIABLE_GLOBAL_3_3_0 : PM_ERR_INVALID_VARIABLE_GLOBAL; - PM_PARSER_ERR_TOKEN_FORMAT_CONTENT(parser, parser->current, diag_id); + size_t width = parser->encoding->char_width(parser->current.end, parser->end - parser->current.end); + PM_PARSER_ERR_TOKEN_FORMAT(parser, parser->current, diag_id, (int) ((parser->current.end + width) - parser->current.start), (const char *) parser->current.start); } return PM_TOKEN_GLOBAL_VARIABLE; @@ -7987,7 +8630,7 @@ lex_interpolation(pm_parser_t *parser, const uint8_t *pound) { return PM_TOKEN_STRING_CONTENT; } - // Now we'll check against the character the follows the #. If it constitutes + // Now we'll check against the character that follows the #. If it constitutes // valid interplation, we'll handle that, otherwise we'll return // PM_TOKEN_NOT_PROVIDED. switch (pound[1]) { @@ -8019,7 +8662,7 @@ lex_interpolation(pm_parser_t *parser, const uint8_t *pound) { return PM_TOKEN_EMBVAR; } - // If we didn't get an valid interpolation, then this is just regular + // If we didn't get a valid interpolation, then this is just regular // string content. This is like if we get "#@-". In this case the caller // should keep lexing. parser->current.end = pound + 1; @@ -8276,6 +8919,27 @@ escape_write_byte(pm_parser_t *parser, pm_buffer_t *buffer, pm_buffer_t *regular escape_write_byte_encoded(parser, buffer, byte); } +/** + * Warn about using a space or a tab character in an escape, as opposed to using + * \\s or \\t. Note that we can quite copy the source because the warning + * message replaces \\c with \\C. + */ +static void +escape_read_warn(pm_parser_t *parser, uint8_t flags, uint8_t flag, const char *type) { +#define FLAG(value) ((value & PM_ESCAPE_FLAG_CONTROL) ? "\\C-" : (value & PM_ESCAPE_FLAG_META) ? "\\M-" : "") + + PM_PARSER_WARN_TOKEN_FORMAT( + parser, + parser->current, + PM_WARN_INVALID_CHARACTER, + FLAG(flags), + FLAG(flag), + type + ); + +#undef FLAG +} + /** * Read the value of an escape into the buffer. */ @@ -8477,6 +9141,16 @@ escape_read(pm_parser_t *parser, pm_buffer_t *buffer, pm_buffer_t *regular_expre parser->current.end++; escape_read(parser, buffer, regular_expression_buffer, flags | PM_ESCAPE_FLAG_CONTROL); return; + case ' ': + parser->current.end++; + escape_read_warn(parser, flags, PM_ESCAPE_FLAG_CONTROL, "\\s"); + escape_write_byte(parser, buffer, regular_expression_buffer, flags, escape_byte(peeked, flags | PM_ESCAPE_FLAG_CONTROL)); + return; + case '\t': + parser->current.end++; + escape_read_warn(parser, flags, 0, "\\t"); + escape_write_byte(parser, buffer, regular_expression_buffer, flags, escape_byte(peeked, flags | PM_ESCAPE_FLAG_CONTROL)); + return; default: { if (!char_is_ascii_printable(peeked)) { pm_parser_err_current(parser, PM_ERR_ESCAPE_INVALID_CONTROL); @@ -8517,6 +9191,16 @@ escape_read(pm_parser_t *parser, pm_buffer_t *buffer, pm_buffer_t *regular_expre parser->current.end++; escape_read(parser, buffer, regular_expression_buffer, flags | PM_ESCAPE_FLAG_CONTROL); return; + case ' ': + parser->current.end++; + escape_read_warn(parser, flags, PM_ESCAPE_FLAG_CONTROL, "\\s"); + escape_write_byte(parser, buffer, regular_expression_buffer, flags, escape_byte(peeked, flags | PM_ESCAPE_FLAG_CONTROL)); + return; + case '\t': + parser->current.end++; + escape_read_warn(parser, flags, 0, "\\t"); + escape_write_byte(parser, buffer, regular_expression_buffer, flags, escape_byte(peeked, flags | PM_ESCAPE_FLAG_CONTROL)); + return; default: { if (!char_is_ascii_printable(peeked)) { pm_parser_err_current(parser, PM_ERR_ESCAPE_INVALID_CONTROL); @@ -8543,24 +9227,35 @@ escape_read(pm_parser_t *parser, pm_buffer_t *buffer, pm_buffer_t *regular_expre } uint8_t peeked = peek(parser); - if (peeked == '\\') { - if (flags & PM_ESCAPE_FLAG_META) { - pm_parser_err_current(parser, PM_ERR_ESCAPE_INVALID_META_REPEAT); + switch (peeked) { + case '\\': + if (flags & PM_ESCAPE_FLAG_META) { + pm_parser_err_current(parser, PM_ERR_ESCAPE_INVALID_META_REPEAT); + return; + } + parser->current.end++; + escape_read(parser, buffer, regular_expression_buffer, flags | PM_ESCAPE_FLAG_META); return; - } - parser->current.end++; - escape_read(parser, buffer, regular_expression_buffer, flags | PM_ESCAPE_FLAG_META); - return; - } + case ' ': + parser->current.end++; + escape_read_warn(parser, flags, PM_ESCAPE_FLAG_META, "\\s"); + escape_write_byte(parser, buffer, regular_expression_buffer, flags, escape_byte(peeked, flags | PM_ESCAPE_FLAG_META)); + return; + case '\t': + parser->current.end++; + escape_read_warn(parser, flags & ((uint8_t) ~PM_ESCAPE_FLAG_CONTROL), PM_ESCAPE_FLAG_META, "\\t"); + escape_write_byte(parser, buffer, regular_expression_buffer, flags, escape_byte(peeked, flags | PM_ESCAPE_FLAG_META)); + return; + default: + if (!char_is_ascii_printable(peeked)) { + pm_parser_err_current(parser, PM_ERR_ESCAPE_INVALID_META); + return; + } - if (!char_is_ascii_printable(peeked)) { - pm_parser_err_current(parser, PM_ERR_ESCAPE_INVALID_META); - return; + parser->current.end++; + escape_write_byte(parser, buffer, regular_expression_buffer, flags, escape_byte(peeked, flags | PM_ESCAPE_FLAG_META)); + return; } - - parser->current.end++; - escape_write_byte(parser, buffer, regular_expression_buffer, flags, escape_byte(peeked, flags | PM_ESCAPE_FLAG_META)); - return; } case '\r': { if (peek_offset(parser, 1) == '\n') { @@ -8671,7 +9366,7 @@ lex_at_variable(pm_parser_t *parser) { while (parser->current.end < parser->end && (width = char_is_identifier(parser, parser->current.end)) > 0) { parser->current.end += width; } - } else { + } else if (parser->current.end < parser->end && pm_char_is_decimal_digit(*parser->current.end)) { pm_diagnostic_id_t diag_id = (type == PM_TOKEN_CLASS_VARIABLE) ? PM_ERR_INCOMPLETE_VARIABLE_CLASS : PM_ERR_INCOMPLETE_VARIABLE_INSTANCE; if (parser->version == PM_OPTIONS_VERSION_CRUBY_3_3_0) { diag_id = (type == PM_TOKEN_CLASS_VARIABLE) ? PM_ERR_INCOMPLETE_VARIABLE_CLASS_3_3_0 : PM_ERR_INCOMPLETE_VARIABLE_INSTANCE_3_3_0; @@ -8679,6 +9374,9 @@ lex_at_variable(pm_parser_t *parser) { size_t width = parser->encoding->char_width(parser->current.end, parser->end - parser->current.end); PM_PARSER_ERR_TOKEN_FORMAT(parser, parser->current, diag_id, (int) ((parser->current.end + width) - parser->current.start), (const char *) parser->current.start); + } else { + pm_diagnostic_id_t diag_id = (type == PM_TOKEN_CLASS_VARIABLE) ? PM_ERR_CLASS_VARIABLE_BARE : PM_ERR_INSTANCE_VARIABLE_BARE; + pm_parser_err_token(parser, &parser->current, diag_id); } // If we're lexing an embedded variable, then we need to pop back into the @@ -8705,7 +9403,7 @@ parser_lex_callback(pm_parser_t *parser) { */ static inline pm_comment_t * parser_comment(pm_parser_t *parser, pm_comment_type_t type) { - pm_comment_t *comment = (pm_comment_t *) xcalloc(sizeof(pm_comment_t), 1); + pm_comment_t *comment = (pm_comment_t *) xcalloc(1, sizeof(pm_comment_t)); if (comment == NULL) return NULL; *comment = (pm_comment_t) { @@ -9166,6 +9864,7 @@ parser_lex(pm_parser_t *parser) { if (match_eol_offset(parser, 1)) { chomping = false; } else { + pm_parser_warn(parser, parser->current.end, parser->current.end + 1, PM_WARN_UNEXPECTED_CARRIAGE_RETURN); parser->current.end++; space_seen = true; } @@ -9339,7 +10038,7 @@ parser_lex(pm_parser_t *parser) { // we need to return the call operator. if (next_content[0] == '.') { // To match ripper, we need to emit an ignored newline even though - // its a real newline in the case that we have a beginless range + // it's a real newline in the case that we have a beginless range // on a subsequent line. if (peek_at(parser, next_content + 1) == '.') { if (!lexed_comment) parser_lex_ignored_newline(parser); @@ -9497,7 +10196,10 @@ parser_lex(pm_parser_t *parser) { pm_token_type_t type = PM_TOKEN_STAR_STAR; - if (lex_state_spcarg_p(parser, space_seen) || lex_state_beg_p(parser)) { + if (lex_state_spcarg_p(parser, space_seen)) { + pm_parser_warn_token(parser, &parser->current, PM_WARN_AMBIGUOUS_PREFIX_STAR_STAR); + type = PM_TOKEN_USTAR_STAR; + } else if (lex_state_beg_p(parser)) { type = PM_TOKEN_USTAR_STAR; } @@ -9796,7 +10498,10 @@ parser_lex(pm_parser_t *parser) { } pm_token_type_t type = PM_TOKEN_AMPERSAND; - if (lex_state_spcarg_p(parser, space_seen) || lex_state_beg_p(parser)) { + if (lex_state_spcarg_p(parser, space_seen)) { + pm_parser_warn_token(parser, &parser->current, PM_WARN_AMBIGUOUS_PREFIX_AMPERSAND); + type = PM_TOKEN_UAMPERSAND; + } else if (lex_state_beg_p(parser)) { type = PM_TOKEN_UAMPERSAND; } @@ -10183,7 +10888,7 @@ parser_lex(pm_parser_t *parser) { } default: // If we get to this point, then we have a % that is completely - // unparseable. In this case we'll just drop it from the parser + // unparsable. In this case we'll just drop it from the parser // and skip past it and hope that the next token is something // that we can parse. pm_parser_err_current(parser, PM_ERR_INVALID_PERCENT); @@ -10223,16 +10928,43 @@ parser_lex(pm_parser_t *parser) { // other options. We'll skip past it and return the next // token after adding an appropriate error message. if (!width) { - pm_diagnostic_id_t diag_id; if (*parser->current.start >= 0x80) { - diag_id = PM_ERR_INVALID_MULTIBYTE_CHARACTER; - } else if (char_is_ascii_printable(*parser->current.start) || (*parser->current.start == '\\')) { - diag_id = PM_ERR_INVALID_PRINTABLE_CHARACTER; + PM_PARSER_ERR_TOKEN_FORMAT(parser, parser->current, PM_ERR_INVALID_MULTIBYTE_CHARACTER, *parser->current.start); + } else if (*parser->current.start == '\\') { + switch (peek_at(parser, parser->current.start + 1)) { + case ' ': + parser->current.end++; + PM_PARSER_ERR_TOKEN_FORMAT(parser, parser->current, PM_ERR_UNEXPECTED_TOKEN_IGNORE, "escaped space"); + break; + case '\f': + parser->current.end++; + PM_PARSER_ERR_TOKEN_FORMAT(parser, parser->current, PM_ERR_UNEXPECTED_TOKEN_IGNORE, "escaped form feed"); + break; + case '\t': + parser->current.end++; + PM_PARSER_ERR_TOKEN_FORMAT(parser, parser->current, PM_ERR_UNEXPECTED_TOKEN_IGNORE, "escaped horizontal tab"); + break; + case '\v': + parser->current.end++; + PM_PARSER_ERR_TOKEN_FORMAT(parser, parser->current, PM_ERR_UNEXPECTED_TOKEN_IGNORE, "escaped vertical tab"); + break; + case '\r': + if (peek_at(parser, parser->current.start + 2) != '\n') { + parser->current.end++; + PM_PARSER_ERR_TOKEN_FORMAT(parser, parser->current, PM_ERR_UNEXPECTED_TOKEN_IGNORE, "escaped carriage return"); + break; + } + /* fallthrough */ + default: + PM_PARSER_ERR_TOKEN_FORMAT(parser, parser->current, PM_ERR_UNEXPECTED_TOKEN_IGNORE, "backslash"); + break; + } + } else if (char_is_ascii_printable(*parser->current.start)) { + PM_PARSER_ERR_TOKEN_FORMAT(parser, parser->current, PM_ERR_INVALID_PRINTABLE_CHARACTER, *parser->current.start); } else { - diag_id = PM_ERR_INVALID_CHARACTER; + PM_PARSER_ERR_TOKEN_FORMAT(parser, parser->current, PM_ERR_INVALID_CHARACTER, *parser->current.start); } - PM_PARSER_ERR_TOKEN_FORMAT(parser, parser->current, diag_id, *parser->current.start); goto lex_next_token; } @@ -10241,19 +10973,19 @@ parser_lex(pm_parser_t *parser) { pm_token_type_t type = lex_identifier(parser, previous_command_start); - // If we've hit a __END__ and it was at the start of the line or the - // start of the file and it is followed by either a \n or a \r\n, then - // this is the last token of the file. + // If we've hit a __END__ and it was at the start of the + // line or the start of the file and it is followed by + // either a \n or a \r\n, then this is the last token of the + // file. if ( ((parser->current.end - parser->current.start) == 7) && current_token_starts_line(parser) && (memcmp(parser->current.start, "__END__", 7) == 0) && (parser->current.end == parser->end || match_eol(parser)) - ) - { - // Since we know we're about to add an __END__ comment, we know we - // need at add all of the newlines to get the correct column - // information for it. + ) { + // Since we know we're about to add an __END__ comment, + // we know we need to add all of the newlines to get the + // correct column information for it. const uint8_t *cursor = parser->current.end; while ((cursor = next_newline(cursor, parser->end - cursor)) != NULL) { pm_newline_list_append(&parser->newline_list, cursor++); @@ -10346,12 +11078,6 @@ parser_lex(pm_parser_t *parser) { pm_token_buffer_t token_buffer = { 0 }; while (breakpoint != NULL) { - // If we hit a null byte, skip directly past it. - if (*breakpoint == '\0') { - breakpoint = pm_strpbrk(parser, breakpoint + 1, breakpoints, parser->end - (breakpoint + 1), true); - continue; - } - // If we hit whitespace, then we must have received content by // now, so we can return an element of the list. if (pm_char_is_whitespace(*breakpoint)) { @@ -10388,6 +11114,12 @@ parser_lex(pm_parser_t *parser) { LEX(PM_TOKEN_STRING_END); } + // If we hit a null byte, skip directly past it. + if (*breakpoint == '\0') { + breakpoint = pm_strpbrk(parser, breakpoint + 1, breakpoints, parser->end - (breakpoint + 1), true); + continue; + } + // If we hit escapes, then we need to treat the next token // literally. In this case we'll skip past the next character // and find the next breakpoint. @@ -10523,36 +11255,6 @@ parser_lex(pm_parser_t *parser) { pm_regexp_token_buffer_t token_buffer = { 0 }; while (breakpoint != NULL) { - // If we hit a null byte, skip directly past it. - if (*breakpoint == '\0') { - parser->current.end = breakpoint + 1; - breakpoint = pm_strpbrk(parser, parser->current.end, breakpoints, parser->end - parser->current.end, false); - continue; - } - - // If we've hit a newline, then we need to track that in the - // list of newlines. - if (*breakpoint == '\n') { - // For the special case of a newline-terminated regular expression, we will pass - // through this branch twice -- once with PM_TOKEN_REGEXP_BEGIN and then again - // with PM_TOKEN_STRING_CONTENT. Let's avoid tracking the newline twice, by - // tracking it only in the REGEXP_BEGIN case. - if ( - !(lex_mode->as.regexp.terminator == '\n' && parser->current.type != PM_TOKEN_REGEXP_BEGIN) - && parser->heredoc_end == NULL - ) { - pm_newline_list_append(&parser->newline_list, breakpoint); - } - - if (lex_mode->as.regexp.terminator != '\n') { - // If the terminator is not a newline, then we can set - // the next breakpoint and continue. - parser->current.end = breakpoint + 1; - breakpoint = pm_strpbrk(parser, parser->current.end, breakpoints, parser->end - parser->current.end, false); - continue; - } - } - // If we hit the terminator, we need to determine what kind of // token to return. if (*breakpoint == lex_mode->as.regexp.terminator) { @@ -10572,9 +11274,17 @@ parser_lex(pm_parser_t *parser) { LEX(PM_TOKEN_STRING_CONTENT); } + // Check here if we need to track the newline. + size_t eol_length = match_eol_at(parser, breakpoint); + if (eol_length) { + parser->current.end = breakpoint + eol_length; + pm_newline_list_append(&parser->newline_list, parser->current.end - 1); + } else { + parser->current.end = breakpoint + 1; + } + // Since we've hit the terminator of the regular expression, // we now need to parse the options. - parser->current.end = breakpoint + 1; parser->current.end += pm_strspn_regexp_option(parser->current.end, parser->end - parser->current.end); lex_mode_pop(parser); @@ -10582,114 +11292,152 @@ parser_lex(pm_parser_t *parser) { LEX(PM_TOKEN_REGEXP_END); } - // If we hit escapes, then we need to treat the next token - // literally. In this case we'll skip past the next character + // If we've hit the incrementor, then we need to skip past it // and find the next breakpoint. - if (*breakpoint == '\\') { + if (*breakpoint && *breakpoint == lex_mode->as.regexp.incrementor) { parser->current.end = breakpoint + 1; + breakpoint = pm_strpbrk(parser, parser->current.end, breakpoints, parser->end - parser->current.end, false); + lex_mode->as.regexp.nesting++; + continue; + } - // If we've hit the end of the file, then break out of the - // loop by setting the breakpoint to NULL. - if (parser->current.end == parser->end) { - breakpoint = NULL; - continue; - } + switch (*breakpoint) { + case '\0': + // If we hit a null byte, skip directly past it. + parser->current.end = breakpoint + 1; + breakpoint = pm_strpbrk(parser, parser->current.end, breakpoints, parser->end - parser->current.end, false); + break; + case '\r': + if (peek_at(parser, breakpoint + 1) != '\n') { + parser->current.end = breakpoint + 1; + breakpoint = pm_strpbrk(parser, parser->current.end, breakpoints, parser->end - parser->current.end, false); + break; + } - pm_regexp_token_buffer_escape(parser, &token_buffer); - uint8_t peeked = peek(parser); + breakpoint++; + parser->current.end = breakpoint; + pm_regexp_token_buffer_escape(parser, &token_buffer); + token_buffer.base.cursor = breakpoint; - switch (peeked) { - case '\r': - parser->current.end++; - if (peek(parser) != '\n') { - if (lex_mode->as.regexp.terminator != '\r') { - pm_token_buffer_push_byte(&token_buffer.base, '\\'); - } - pm_regexp_token_buffer_push_byte(&token_buffer, '\r'); - pm_token_buffer_push_byte(&token_buffer.base, '\r'); - break; - } /* fallthrough */ - case '\n': - if (parser->heredoc_end) { - // ... if we are on the same line as a heredoc, - // flush the heredoc and continue parsing after - // heredoc_end. - parser_flush_heredoc_end(parser); - pm_regexp_token_buffer_copy(parser, &token_buffer); - LEX(PM_TOKEN_STRING_CONTENT); - } else { - // ... else track the newline. - pm_newline_list_append(&parser->newline_list, parser->current.end); - } - - parser->current.end++; + case '\n': + // If we've hit a newline, then we need to track that in + // the list of newlines. + if (parser->heredoc_end == NULL) { + pm_newline_list_append(&parser->newline_list, breakpoint); + parser->current.end = breakpoint + 1; + breakpoint = pm_strpbrk(parser, parser->current.end, breakpoints, parser->end - parser->current.end, false); break; - case 'c': - case 'C': - case 'M': - case 'u': - case 'x': - escape_read(parser, &token_buffer.regexp_buffer, &token_buffer.base.buffer, PM_ESCAPE_FLAG_REGEXP); + } + + parser->current.end = breakpoint + 1; + parser_flush_heredoc_end(parser); + pm_regexp_token_buffer_flush(parser, &token_buffer); + LEX(PM_TOKEN_STRING_CONTENT); + case '\\': { + // If we hit escapes, then we need to treat the next + // token literally. In this case we'll skip past the + // next character and find the next breakpoint. + parser->current.end = breakpoint + 1; + + // If we've hit the end of the file, then break out of + // the loop by setting the breakpoint to NULL. + if (parser->current.end == parser->end) { + breakpoint = NULL; break; - default: - if (lex_mode->as.regexp.terminator == peeked) { - // Some characters when they are used as the - // terminator also receive an escape. They are - // enumerated here. - switch (peeked) { - case '$': case ')': case '*': case '+': - case '.': case '>': case '?': case ']': - case '^': case '|': case '}': + } + + pm_regexp_token_buffer_escape(parser, &token_buffer); + uint8_t peeked = peek(parser); + + switch (peeked) { + case '\r': + parser->current.end++; + if (peek(parser) != '\n') { + if (lex_mode->as.regexp.terminator != '\r') { pm_token_buffer_push_byte(&token_buffer.base, '\\'); - break; - default: - break; + } + pm_regexp_token_buffer_push_byte(&token_buffer, '\r'); + pm_token_buffer_push_byte(&token_buffer.base, '\r'); + break; + } + /* fallthrough */ + case '\n': + if (parser->heredoc_end) { + // ... if we are on the same line as a heredoc, + // flush the heredoc and continue parsing after + // heredoc_end. + parser_flush_heredoc_end(parser); + pm_regexp_token_buffer_copy(parser, &token_buffer); + LEX(PM_TOKEN_STRING_CONTENT); + } else { + // ... else track the newline. + pm_newline_list_append(&parser->newline_list, parser->current.end); } - pm_regexp_token_buffer_push_byte(&token_buffer, peeked); - pm_token_buffer_push_byte(&token_buffer.base, peeked); parser->current.end++; break; - } - - if (peeked < 0x80) pm_token_buffer_push_byte(&token_buffer.base, '\\'); - pm_regexp_token_buffer_push_escaped(&token_buffer, parser); - break; - } + case 'c': + case 'C': + case 'M': + case 'u': + case 'x': + escape_read(parser, &token_buffer.regexp_buffer, &token_buffer.base.buffer, PM_ESCAPE_FLAG_REGEXP); + break; + default: + if (lex_mode->as.regexp.terminator == peeked) { + // Some characters when they are used as the + // terminator also receive an escape. They are + // enumerated here. + switch (peeked) { + case '$': case ')': case '*': case '+': + case '.': case '>': case '?': case ']': + case '^': case '|': case '}': + pm_token_buffer_push_byte(&token_buffer.base, '\\'); + break; + default: + break; + } - token_buffer.base.cursor = parser->current.end; - breakpoint = pm_strpbrk(parser, parser->current.end, breakpoints, parser->end - parser->current.end, false); - continue; - } + pm_regexp_token_buffer_push_byte(&token_buffer, peeked); + pm_token_buffer_push_byte(&token_buffer.base, peeked); + parser->current.end++; + break; + } - // If we hit a #, then we will attempt to lex interpolation. - if (*breakpoint == '#') { - pm_token_type_t type = lex_interpolation(parser, breakpoint); + if (peeked < 0x80) pm_token_buffer_push_byte(&token_buffer.base, '\\'); + pm_regexp_token_buffer_push_escaped(&token_buffer, parser); + break; + } - if (type == PM_TOKEN_NOT_PROVIDED) { - // If we haven't returned at this point then we had - // something that looked like an interpolated class or - // instance variable like "#@" but wasn't actually. In - // this case we'll just skip to the next breakpoint. + token_buffer.base.cursor = parser->current.end; breakpoint = pm_strpbrk(parser, parser->current.end, breakpoints, parser->end - parser->current.end, false); - continue; + break; } + case '#': { + // If we hit a #, then we will attempt to lex + // interpolation. + pm_token_type_t type = lex_interpolation(parser, breakpoint); - if (type == PM_TOKEN_STRING_CONTENT) { - pm_regexp_token_buffer_flush(parser, &token_buffer); - } + if (type == PM_TOKEN_NOT_PROVIDED) { + // If we haven't returned at this point then we had + // something that looked like an interpolated class or + // instance variable like "#@" but wasn't actually. In + // this case we'll just skip to the next breakpoint. + breakpoint = pm_strpbrk(parser, parser->current.end, breakpoints, parser->end - parser->current.end, false); + break; + } - LEX(type); - } + if (type == PM_TOKEN_STRING_CONTENT) { + pm_regexp_token_buffer_flush(parser, &token_buffer); + } - // If we've hit the incrementor, then we need to skip past it - // and find the next breakpoint. - assert(*breakpoint == lex_mode->as.regexp.incrementor); - parser->current.end = breakpoint + 1; - breakpoint = pm_strpbrk(parser, parser->current.end, breakpoints, parser->end - parser->current.end, false); - lex_mode->as.regexp.nesting++; - continue; + LEX(type); + } + default: + assert(false && "unreachable"); + break; + } } if (parser->current.end > parser->current.start) { @@ -10777,26 +11525,9 @@ parser_lex(pm_parser_t *parser) { LEX(PM_TOKEN_LABEL_END); } - lex_state_set(parser, PM_LEX_STATE_END); - lex_mode_pop(parser); - LEX(PM_TOKEN_STRING_END); - } - - // When we hit a newline, we need to flush any potential heredocs. Note - // that this has to happen after we check for the terminator in case the - // terminator is a newline character. - if (*breakpoint == '\n') { - if (parser->heredoc_end == NULL) { - pm_newline_list_append(&parser->newline_list, breakpoint); - parser->current.end = breakpoint + 1; - breakpoint = pm_strpbrk(parser, parser->current.end, breakpoints, parser->end - parser->current.end, true); - continue; - } else { - parser->current.end = breakpoint + 1; - parser_flush_heredoc_end(parser); - pm_token_buffer_flush(parser, &token_buffer); - LEX(PM_TOKEN_STRING_CONTENT); - } + lex_state_set(parser, PM_LEX_STATE_END); + lex_mode_pop(parser); + LEX(PM_TOKEN_STRING_END); } switch (*breakpoint) { @@ -10805,6 +11536,37 @@ parser_lex(pm_parser_t *parser) { parser->current.end = breakpoint + 1; breakpoint = pm_strpbrk(parser, parser->current.end, breakpoints, parser->end - parser->current.end, true); break; + case '\r': + if (peek_at(parser, breakpoint + 1) != '\n') { + parser->current.end = breakpoint + 1; + breakpoint = pm_strpbrk(parser, parser->current.end, breakpoints, parser->end - parser->current.end, true); + break; + } + + // If we hit a \r\n sequence, then we need to treat it + // as a newline. + breakpoint++; + parser->current.end = breakpoint; + pm_token_buffer_escape(parser, &token_buffer); + token_buffer.cursor = breakpoint; + + /* fallthrough */ + case '\n': + // When we hit a newline, we need to flush any potential + // heredocs. Note that this has to happen after we check + // for the terminator in case the terminator is a + // newline character. + if (parser->heredoc_end == NULL) { + pm_newline_list_append(&parser->newline_list, breakpoint); + parser->current.end = breakpoint + 1; + breakpoint = pm_strpbrk(parser, parser->current.end, breakpoints, parser->end - parser->current.end, true); + break; + } + + parser->current.end = breakpoint + 1; + parser_flush_heredoc_end(parser); + pm_token_buffer_flush(parser, &token_buffer); + LEX(PM_TOKEN_STRING_CONTENT); case '\\': { // Here we hit escapes. parser->current.end = breakpoint + 1; @@ -11007,11 +11769,11 @@ parser_lex(pm_parser_t *parser) { // Otherwise we'll be parsing string content. These are the places // where we need to split up the content of the heredoc. We'll use // strpbrk to find the first of these characters. - uint8_t breakpoints[] = "\n\\#"; + uint8_t breakpoints[] = "\r\n\\#"; pm_heredoc_quote_t quote = lex_mode->as.heredoc.quote; if (quote == PM_HEREDOC_QUOTE_SINGLE) { - breakpoints[2] = '\0'; + breakpoints[3] = '\0'; } const uint8_t *breakpoint = pm_strpbrk(parser, parser->current.end, breakpoints, parser->end - parser->current.end, true); @@ -11025,6 +11787,21 @@ parser_lex(pm_parser_t *parser) { parser->current.end = breakpoint + 1; breakpoint = pm_strpbrk(parser, parser->current.end, breakpoints, parser->end - parser->current.end, true); break; + case '\r': + parser->current.end = breakpoint + 1; + + if (peek_at(parser, breakpoint + 1) != '\n') { + breakpoint = pm_strpbrk(parser, parser->current.end, breakpoints, parser->end - parser->current.end, true); + break; + } + + // If we hit a \r\n sequence, then we want to replace it + // with a single \n character in the final string. + breakpoint++; + pm_token_buffer_escape(parser, &token_buffer); + token_buffer.cursor = breakpoint; + + /* fallthrough */ case '\n': { if (parser->heredoc_end != NULL && (parser->heredoc_end > breakpoint)) { parser_flush_heredoc_end(parser); @@ -11103,7 +11880,7 @@ parser_lex(pm_parser_t *parser) { case '\\': { // If we hit an escape, then we need to skip past // however many characters the escape takes up. However - // it's important that if \n or \r\n are escaped that we + // it's important that if \n or \r\n are escaped, we // stop looping before the newline and not after the // newline so that we can still potentially find the // terminator of the heredoc. @@ -11315,7 +12092,7 @@ pm_binding_powers_t pm_binding_powers[PM_TOKEN_MAXIMUM] = { [PM_TOKEN_EQUAL_GREATER] = NON_ASSOCIATIVE(PM_BINDING_POWER_MATCH), [PM_TOKEN_KEYWORD_IN] = NON_ASSOCIATIVE(PM_BINDING_POWER_MATCH), - // &&= &= ^= = >>= <<= -= %= |= += /= *= **= + // &&= &= ^= = >>= <<= -= %= |= ||= += /= *= **= [PM_TOKEN_AMPERSAND_AMPERSAND_EQUAL] = BINDING_POWER_ASSIGNMENT, [PM_TOKEN_AMPERSAND_EQUAL] = BINDING_POWER_ASSIGNMENT, [PM_TOKEN_CARET_EQUAL] = BINDING_POWER_ASSIGNMENT, @@ -11560,7 +12337,8 @@ static pm_node_t * parse_expression(pm_parser_t *parser, pm_binding_power_t binding_power, bool accepts_command_call, pm_diagnostic_id_t diag_id); /** - * This is a wrapper of parse_expression, which also checks whether the resulting node is value expression. + * This is a wrapper of parse_expression, which also checks whether the + * resulting node is a value expression. */ static pm_node_t * parse_value_expression(pm_parser_t *parser, pm_binding_power_t binding_power, bool accepts_command_call, pm_diagnostic_id_t diag_id) { @@ -11584,7 +12362,6 @@ parse_value_expression(pm_parser_t *parser, pm_binding_power_t binding_power, bo * CRuby parsers that are generated would resolve this by using a lookahead and * potentially backtracking. We attempt to do this by just looking at the next * token and making a decision based on that. I am not sure if this is going to - * * work in all cases, it may need to be refactored later. But it appears to work * for now. */ @@ -11617,7 +12394,7 @@ token_begins_expression_p(pm_token_type_t type) { case PM_TOKEN_SEMICOLON: // The reason we need this short-circuit is because we're using the // binding powers table to tell us if the subsequent token could - // potentially be the start of an expression . If there _is_ a binding + // potentially be the start of an expression. If there _is_ a binding // power for one of these tokens, then we should remove it from this list // and let it be handled by the default case below. assert(pm_binding_powers[type].left == PM_BINDING_POWER_UNSET); @@ -11711,13 +12488,19 @@ parse_target(pm_parser_t *parser, pm_node_t *target) { assert(sizeof(pm_global_variable_target_node_t) == sizeof(pm_global_variable_read_node_t)); target->type = PM_GLOBAL_VARIABLE_TARGET_NODE; return target; - case PM_LOCAL_VARIABLE_READ_NODE: + case PM_LOCAL_VARIABLE_READ_NODE: { pm_refute_numbered_parameter(parser, target->location.start, target->location.end); + const pm_local_variable_read_node_t *cast = (const pm_local_variable_read_node_t *) target; + uint32_t name = cast->name; + uint32_t depth = cast->depth; + pm_locals_unread(&pm_parser_scope_find(parser, depth)->locals, name); + assert(sizeof(pm_local_variable_target_node_t) == sizeof(pm_local_variable_read_node_t)); target->type = PM_LOCAL_VARIABLE_TARGET_NODE; return target; + } case PM_INSTANCE_VARIABLE_READ_NODE: assert(sizeof(pm_instance_variable_target_node_t) == sizeof(pm_instance_variable_read_node_t)); target->type = PM_INSTANCE_VARIABLE_TARGET_NODE; @@ -11737,7 +12520,8 @@ parse_target(pm_parser_t *parser, pm_node_t *target) { pm_call_node_t *call = (pm_call_node_t *) target; // If we have no arguments to the call node and we need this to be a - // target then this is either a method call or a local variable write. + // target then this is either a method call or a local variable + // write. if ( (call->message_loc.start != NULL) && (call->message_loc.end[-1] != '!') && @@ -11756,20 +12540,12 @@ parse_target(pm_parser_t *parser, pm_node_t *target) { // When it was parsed in the prefix position, foo was seen as a // method call with no receiver and no arguments. Now we have an // =, so we know it's a local variable write. - const pm_location_t message = call->message_loc; + const pm_location_t message_loc = call->message_loc; - pm_parser_local_add_location(parser, message.start, message.end); + pm_constant_id_t name = pm_parser_local_add_location(parser, message_loc.start, message_loc.end, 0); pm_node_destroy(parser, target); - uint32_t depth = 0; - const pm_token_t name = { .type = PM_TOKEN_IDENTIFIER, .start = message.start, .end = message.end }; - target = (pm_node_t *) pm_local_variable_read_node_create(parser, &name, depth); - - assert(sizeof(pm_local_variable_target_node_t) == sizeof(pm_local_variable_read_node_t)); - target->type = PM_LOCAL_VARIABLE_TARGET_NODE; - - pm_refute_numbered_parameter(parser, message.start, message.end); - return target; + return (pm_node_t *) pm_local_variable_target_node_create(parser, &message_loc, name, 0); } if (*call->message_loc.start == '_' || parser->encoding->alnum_char(call->message_loc.start, call->message_loc.end - call->message_loc.start)) { @@ -11781,7 +12557,7 @@ parse_target(pm_parser_t *parser, pm_node_t *target) { // If there is no call operator and the message is "[]" then this is // an aref expression, and we can transform it into an aset // expression. - if (pm_call_node_index_p(call)) { + if (PM_NODE_FLAG_P(call, PM_CALL_NODE_FLAGS_INDEX)) { return (pm_node_t *) pm_index_target_node_create(parser, call); } } @@ -11815,6 +12591,21 @@ parse_target_validate(pm_parser_t *parser, pm_node_t *target) { return result; } +/** + * Potentially wrap a constant write node in a shareable constant node depending + * on the current state. + */ +static pm_node_t * +parse_shareable_constant_write(pm_parser_t *parser, pm_node_t *write) { + pm_shareable_constant_value_t shareable_constant = pm_parser_scope_shareable_constant_get(parser); + + if (shareable_constant != PM_SCOPE_SHAREABLE_CONSTANT_NONE) { + return (pm_node_t *) pm_shareable_constant_node_create(parser, write, shareable_constant); + } + + return write; +} + /** * Convert the given node into a valid write node. */ @@ -11829,15 +12620,17 @@ parse_write(pm_parser_t *parser, pm_node_t *target, pm_token_t *operator, pm_nod pm_node_destroy(parser, target); return (pm_node_t *) node; } - case PM_CONSTANT_PATH_NODE: - return (pm_node_t *) pm_constant_path_write_node_create(parser, (pm_constant_path_node_t *) target, operator, value); + case PM_CONSTANT_PATH_NODE: { + pm_node_t *node = (pm_node_t *) pm_constant_path_write_node_create(parser, (pm_constant_path_node_t *) target, operator, value); + return parse_shareable_constant_write(parser, node); + } case PM_CONSTANT_READ_NODE: { - pm_constant_write_node_t *node = pm_constant_write_node_create(parser, (pm_constant_read_node_t *) target, operator, value); + pm_node_t *node = (pm_node_t *) pm_constant_write_node_create(parser, (pm_constant_read_node_t *) target, operator, value); if (context_def_p(parser)) { - pm_parser_err_node(parser, (pm_node_t *) node, PM_ERR_WRITE_TARGET_IN_METHOD); + pm_parser_err_node(parser, node, PM_ERR_WRITE_TARGET_IN_METHOD); } pm_node_destroy(parser, target); - return (pm_node_t *) node; + return parse_shareable_constant_write(parser, node); } case PM_BACK_REFERENCE_READ_NODE: case PM_NUMBERED_REFERENCE_READ_NODE: @@ -11852,13 +12645,14 @@ parse_write(pm_parser_t *parser, pm_node_t *target, pm_token_t *operator, pm_nod pm_refute_numbered_parameter(parser, target->location.start, target->location.end); pm_local_variable_read_node_t *local_read = (pm_local_variable_read_node_t *) target; - pm_constant_id_t constant_id = local_read->name; + pm_constant_id_t name = local_read->name; uint32_t depth = local_read->depth; + pm_locals_unread(&pm_parser_scope_find(parser, depth)->locals, name); pm_location_t name_loc = target->location; pm_node_destroy(parser, target); - return (pm_node_t *) pm_local_variable_write_node_create(parser, constant_id, depth, value, &name_loc, operator); + return (pm_node_t *) pm_local_variable_write_node_create(parser, name, depth, value, &name_loc, operator); } case PM_INSTANCE_VARIABLE_READ_NODE: { pm_node_t *write_node = (pm_node_t *) pm_instance_variable_write_node_create(parser, (pm_instance_variable_read_node_t *) target, operator, value); @@ -11905,7 +12699,7 @@ parse_write(pm_parser_t *parser, pm_node_t *target, pm_token_t *operator, pm_nod // =, so we know it's a local variable write. const pm_location_t message = call->message_loc; - pm_parser_local_add_location(parser, message.start, message.end); + pm_parser_local_add_location(parser, message.start, message.end, 0); pm_node_destroy(parser, target); pm_constant_id_t constant_id = pm_parser_constant_id_location(parser, message.start, message.end); @@ -11941,7 +12735,7 @@ parse_write(pm_parser_t *parser, pm_node_t *target, pm_token_t *operator, pm_nod // If there is no call operator and the message is "[]" then this is // an aref expression, and we can transform it into an aset // expression. - if (pm_call_node_index_p(call)) { + if (PM_NODE_FLAG_P(call, PM_CALL_NODE_FLAGS_INDEX)) { if (call->arguments == NULL) { call->arguments = pm_arguments_node_create(parser); } @@ -12129,15 +12923,20 @@ pm_hash_key_static_literals_add(pm_parser_t *parser, pm_static_literals_t *liter const pm_node_t *duplicated = pm_static_literals_add(parser, literals, node); if (duplicated != NULL) { + pm_buffer_t buffer = { 0 }; + pm_static_literal_inspect(&buffer, parser, duplicated); + pm_diagnostic_list_append_format( &parser->warning_list, duplicated->location.start, duplicated->location.end, PM_WARN_DUPLICATED_HASH_KEY, - (int) (duplicated->location.end - duplicated->location.start), - duplicated->location.start, + (int) pm_buffer_length(&buffer), + pm_buffer_value(&buffer), pm_newline_list_line_column(&parser->newline_list, node->location.start, parser->start_line).line ); + + pm_buffer_free(&buffer); } } @@ -12159,7 +12958,7 @@ pm_when_clause_static_literals_add(pm_parser_t *parser, pm_static_literals_t *li } /** - * Parse all of the elements of a hash. returns true if a double splat was found. + * Parse all of the elements of a hash. Return true if a double splat was found. */ static bool parse_assocs(pm_parser_t *parser, pm_static_literals_t *literals, pm_node_t *node) { @@ -12427,7 +13226,7 @@ parse_arguments(pm_parser_t *parser, pm_arguments_t *arguments, bool accepts_for pm_static_literals_t literals = { 0 }; pm_hash_key_static_literals_add(parser, &literals, argument); - // Finish parsing the one we are part way through + // Finish parsing the one we are part way through. pm_node_t *value = parse_value_expression(parser, PM_BINDING_POWER_DEFINED, false, PM_ERR_HASH_VALUE); argument = (pm_node_t *) pm_assoc_node_create(parser, argument, &operator, value); @@ -12526,7 +13325,7 @@ parse_required_destructured_parameter(pm_parser_t *parser) { if (pm_parser_parameter_name_check(parser, &name)) { pm_node_flag_set_repeated_parameter(value); } - pm_parser_local_add_token(parser, &name); + pm_parser_local_add_token(parser, &name, 1); } param = (pm_node_t *) pm_splat_node_create(parser, &star, value); @@ -12538,7 +13337,7 @@ parse_required_destructured_parameter(pm_parser_t *parser) { if (pm_parser_parameter_name_check(parser, &name)) { pm_node_flag_set_repeated_parameter(param); } - pm_parser_local_add_token(parser, &name); + pm_parser_local_add_token(parser, &name, 1); } pm_multi_target_node_targets_append(parser, node, param); @@ -12597,7 +13396,7 @@ update_parameter_state(pm_parser_t *parser, pm_token_t *token, pm_parameters_ord if (state == PM_PARAMETERS_NO_CHANGE) return; // If we see another ordered argument after a optional argument - // we only continue parsing ordered arguments until we stop seeing ordered arguments + // we only continue parsing ordered arguments until we stop seeing ordered arguments. if (*current == PM_PARAMETERS_ORDER_OPTIONAL && state == PM_PARAMETERS_ORDER_NAMED) { *current = PM_PARAMETERS_ORDER_AFTER_OPTIONAL; return; @@ -12659,7 +13458,7 @@ parse_parameters( if (accept1(parser, PM_TOKEN_IDENTIFIER)) { name = parser->previous; repeated = pm_parser_parameter_name_check(parser, &name); - pm_parser_local_add_token(parser, &name); + pm_parser_local_add_token(parser, &name, 1); } else { name = not_provided(parser); parser->current_scope->parameters |= PM_SCOPE_PARAMETERS_FORWARDING_BLOCK; @@ -12741,22 +13540,30 @@ parse_parameters( pm_token_t name = parser->previous; bool repeated = pm_parser_parameter_name_check(parser, &name); - pm_parser_local_add_token(parser, &name); + pm_parser_local_add_token(parser, &name, 1); if (accept1(parser, PM_TOKEN_EQUAL)) { pm_token_t operator = parser->previous; context_push(parser, PM_CONTEXT_DEFAULT_PARAMS); - pm_constant_id_t saved_param_name = pm_parser_current_param_name_set(parser, pm_parser_constant_id_token(parser, &name)); - pm_node_t *value = parse_value_expression(parser, binding_power, false, PM_ERR_PARAMETER_NO_DEFAULT); + pm_constant_id_t name_id = pm_parser_constant_id_token(parser, &name); + uint32_t reads = pm_locals_reads(&parser->current_scope->locals, name_id); + pm_node_t *value = parse_value_expression(parser, binding_power, false, PM_ERR_PARAMETER_NO_DEFAULT); pm_optional_parameter_node_t *param = pm_optional_parameter_node_create(parser, &name, &operator, value); + if (repeated) { pm_node_flag_set_repeated_parameter((pm_node_t *)param); } pm_parameters_node_optionals_append(params, param); - pm_parser_current_param_name_restore(parser, saved_param_name); + // If the value of the parameter increased the number of + // reads of that parameter, then we need to warn that we + // have a circular definition. + if (pm_locals_reads(&parser->current_scope->locals, name_id) != reads) { + PM_PARSER_ERR_TOKEN_FORMAT_CONTENT(parser, name, PM_ERR_PARAMETER_CIRCULAR); + } + context_pop(parser); // If parsing the value of the parameter resulted in error recovery, @@ -12792,7 +13599,7 @@ parse_parameters( local.end -= 1; bool repeated = pm_parser_parameter_name_check(parser, &local); - pm_parser_local_add_token(parser, &local); + pm_parser_local_add_token(parser, &local, 1); switch (parser->current.type) { case PM_TOKEN_COMMA: @@ -12825,12 +13632,15 @@ parse_parameters( if (token_begins_expression_p(parser->current.type)) { context_push(parser, PM_CONTEXT_DEFAULT_PARAMS); - pm_constant_id_t saved_param_name = pm_parser_current_param_name_set(parser, pm_parser_constant_id_token(parser, &local)); + pm_constant_id_t name_id = pm_parser_constant_id_token(parser, &local); + uint32_t reads = pm_locals_reads(&parser->current_scope->locals, name_id); pm_node_t *value = parse_value_expression(parser, binding_power, false, PM_ERR_PARAMETER_NO_DEFAULT_KW); - pm_parser_current_param_name_restore(parser, saved_param_name); - context_pop(parser); + if (pm_locals_reads(&parser->current_scope->locals, name_id) != reads) { + PM_PARSER_ERR_TOKEN_FORMAT_CONTENT(parser, local, PM_ERR_PARAMETER_CIRCULAR); + } + context_pop(parser); param = (pm_node_t *) pm_optional_keyword_parameter_node_create(parser, &name, value); } else { @@ -12866,7 +13676,7 @@ parse_parameters( if (accept1(parser, PM_TOKEN_IDENTIFIER)) { name = parser->previous; repeated = pm_parser_parameter_name_check(parser, &name); - pm_parser_local_add_token(parser, &name); + pm_parser_local_add_token(parser, &name, 1); } else { name = not_provided(parser); parser->current_scope->parameters |= PM_SCOPE_PARAMETERS_FORWARDING_POSITIONALS; @@ -12902,7 +13712,7 @@ parse_parameters( if (accept1(parser, PM_TOKEN_IDENTIFIER)) { name = parser->previous; repeated = pm_parser_parameter_name_check(parser, &name); - pm_parser_local_add_token(parser, &name); + pm_parser_local_add_token(parser, &name, 1); } else { name = not_provided(parser); parser->current_scope->parameters |= PM_SCOPE_PARAMETERS_FORWARDING_KEYWORDS; @@ -12961,12 +13771,22 @@ parse_parameters( return params; } +typedef enum { + PM_RESCUES_BEGIN = 1, + PM_RESCUES_BLOCK, + PM_RESCUES_CLASS, + PM_RESCUES_DEF, + PM_RESCUES_LAMBDA, + PM_RESCUES_MODULE, + PM_RESCUES_SCLASS +} pm_rescues_type_t; + /** * Parse any number of rescue clauses. This will form a linked list of if * nodes pointing to each other from the top. */ static inline void -parse_rescues(pm_parser_t *parser, pm_begin_node_t *parent_node, bool def_p) { +parse_rescues(pm_parser_t *parser, pm_begin_node_t *parent_node, pm_rescues_type_t type) { pm_rescue_node_t *current = NULL; while (accept1(parser, PM_TOKEN_KEYWORD_RESCUE)) { @@ -13029,10 +13849,22 @@ parse_rescues(pm_parser_t *parser, pm_begin_node_t *parent_node, bool def_p) { if (!match3(parser, PM_TOKEN_KEYWORD_ELSE, PM_TOKEN_KEYWORD_ENSURE, PM_TOKEN_KEYWORD_END)) { pm_accepts_block_stack_push(parser, true); - pm_statements_node_t *statements = parse_statements(parser, def_p ? PM_CONTEXT_RESCUE_DEF : PM_CONTEXT_RESCUE); - if (statements) { - pm_rescue_node_statements_set(rescue, statements); + pm_context_t context; + + switch (type) { + case PM_RESCUES_BEGIN: context = PM_CONTEXT_BEGIN_RESCUE; break; + case PM_RESCUES_BLOCK: context = PM_CONTEXT_BLOCK_RESCUE; break; + case PM_RESCUES_CLASS: context = PM_CONTEXT_CLASS_RESCUE; break; + case PM_RESCUES_DEF: context = PM_CONTEXT_DEF_RESCUE; break; + case PM_RESCUES_LAMBDA: context = PM_CONTEXT_LAMBDA_RESCUE; break; + case PM_RESCUES_MODULE: context = PM_CONTEXT_MODULE_RESCUE; break; + case PM_RESCUES_SCLASS: context = PM_CONTEXT_SCLASS_RESCUE; break; + default: assert(false && "unreachable"); context = PM_CONTEXT_BEGIN_RESCUE; break; } + + pm_statements_node_t *statements = parse_statements(parser, context); + if (statements != NULL) pm_rescue_node_statements_set(rescue, statements); + pm_accepts_block_stack_pop(parser); accept2(parser, PM_TOKEN_NEWLINE, PM_TOKEN_SEMICOLON); } @@ -13048,7 +13880,7 @@ parse_rescues(pm_parser_t *parser, pm_begin_node_t *parent_node, bool def_p) { // The end node locations on rescue nodes will not be set correctly // since we won't know the end until we've found all consequent - // clauses. This sets the end location on all rescues once we know it + // clauses. This sets the end location on all rescues once we know it. if (current) { const uint8_t *end_to_set = current->base.location.end; current = parent_node->rescue_clause; @@ -13065,8 +13897,22 @@ parse_rescues(pm_parser_t *parser, pm_begin_node_t *parent_node, bool def_p) { pm_statements_node_t *else_statements = NULL; if (!match2(parser, PM_TOKEN_KEYWORD_END, PM_TOKEN_KEYWORD_ENSURE)) { pm_accepts_block_stack_push(parser, true); - else_statements = parse_statements(parser, def_p ? PM_CONTEXT_RESCUE_ELSE_DEF : PM_CONTEXT_RESCUE_ELSE); + pm_context_t context; + + switch (type) { + case PM_RESCUES_BEGIN: context = PM_CONTEXT_BEGIN_ELSE; break; + case PM_RESCUES_BLOCK: context = PM_CONTEXT_BLOCK_ELSE; break; + case PM_RESCUES_CLASS: context = PM_CONTEXT_CLASS_ELSE; break; + case PM_RESCUES_DEF: context = PM_CONTEXT_DEF_ELSE; break; + case PM_RESCUES_LAMBDA: context = PM_CONTEXT_LAMBDA_ELSE; break; + case PM_RESCUES_MODULE: context = PM_CONTEXT_MODULE_ELSE; break; + case PM_RESCUES_SCLASS: context = PM_CONTEXT_SCLASS_ELSE; break; + default: assert(false && "unreachable"); context = PM_CONTEXT_BEGIN_RESCUE; break; + } + + else_statements = parse_statements(parser, context); pm_accepts_block_stack_pop(parser); + accept2(parser, PM_TOKEN_NEWLINE, PM_TOKEN_SEMICOLON); } @@ -13081,8 +13927,22 @@ parse_rescues(pm_parser_t *parser, pm_begin_node_t *parent_node, bool def_p) { pm_statements_node_t *ensure_statements = NULL; if (!match1(parser, PM_TOKEN_KEYWORD_END)) { pm_accepts_block_stack_push(parser, true); - ensure_statements = parse_statements(parser, def_p ? PM_CONTEXT_ENSURE_DEF : PM_CONTEXT_ENSURE); + pm_context_t context; + + switch (type) { + case PM_RESCUES_BEGIN: context = PM_CONTEXT_BEGIN_ENSURE; break; + case PM_RESCUES_BLOCK: context = PM_CONTEXT_BLOCK_ENSURE; break; + case PM_RESCUES_CLASS: context = PM_CONTEXT_CLASS_ENSURE; break; + case PM_RESCUES_DEF: context = PM_CONTEXT_DEF_ENSURE; break; + case PM_RESCUES_LAMBDA: context = PM_CONTEXT_LAMBDA_ENSURE; break; + case PM_RESCUES_MODULE: context = PM_CONTEXT_MODULE_ENSURE; break; + case PM_RESCUES_SCLASS: context = PM_CONTEXT_SCLASS_ENSURE; break; + default: assert(false && "unreachable"); context = PM_CONTEXT_BEGIN_RESCUE; break; + } + + ensure_statements = parse_statements(parser, context); pm_accepts_block_stack_pop(parser); + accept2(parser, PM_TOKEN_NEWLINE, PM_TOKEN_SEMICOLON); } @@ -13098,13 +13958,19 @@ parse_rescues(pm_parser_t *parser, pm_begin_node_t *parent_node, bool def_p) { } } -static inline pm_begin_node_t * -parse_rescues_as_begin(pm_parser_t *parser, const uint8_t *start, pm_statements_node_t *statements, bool def_p) { - pm_token_t no_begin_token = not_provided(parser); - pm_begin_node_t *begin_node = pm_begin_node_create(parser, &no_begin_token, statements); - parse_rescues(parser, begin_node, def_p); - begin_node->base.location.start = start; - return begin_node; +/** + * Parse a set of rescue clauses with an implicit begin (for example when on a + * class, module, def, etc.). + */ +static pm_begin_node_t * +parse_rescues_implicit_begin(pm_parser_t *parser, const uint8_t *start, pm_statements_node_t *statements, pm_rescues_type_t type) { + pm_token_t begin_keyword = not_provided(parser); + pm_begin_node_t *node = pm_begin_node_create(parser, &begin_keyword, statements); + + parse_rescues(parser, node, type); + node->base.location.start = start; + + return node; } /** @@ -13129,18 +13995,42 @@ parse_block_parameters( } pm_block_parameters_node_t *block_parameters = pm_block_parameters_node_create(parser, parameters, opening); - if ((opening->type != PM_TOKEN_NOT_PROVIDED) && accept1(parser, PM_TOKEN_SEMICOLON)) { - do { - expect1(parser, PM_TOKEN_IDENTIFIER, PM_ERR_BLOCK_PARAM_LOCAL_VARIABLE); - bool repeated = pm_parser_parameter_name_check(parser, &parser->previous); - pm_parser_local_add_token(parser, &parser->previous); + if ((opening->type != PM_TOKEN_NOT_PROVIDED)) { + accept1(parser, PM_TOKEN_NEWLINE); - pm_block_local_variable_node_t *local = pm_block_local_variable_node_create(parser, &parser->previous); - if (repeated) { - pm_node_flag_set_repeated_parameter((pm_node_t *)local); - } - pm_block_parameters_node_append_local(block_parameters, local); - } while (accept1(parser, PM_TOKEN_COMMA)); + if (accept1(parser, PM_TOKEN_SEMICOLON)) { + do { + switch (parser->current.type) { + case PM_TOKEN_CONSTANT: + pm_parser_err_current(parser, PM_ERR_ARGUMENT_FORMAL_CONSTANT); + parser_lex(parser); + break; + case PM_TOKEN_INSTANCE_VARIABLE: + pm_parser_err_current(parser, PM_ERR_ARGUMENT_FORMAL_IVAR); + parser_lex(parser); + break; + case PM_TOKEN_GLOBAL_VARIABLE: + pm_parser_err_current(parser, PM_ERR_ARGUMENT_FORMAL_GLOBAL); + parser_lex(parser); + break; + case PM_TOKEN_CLASS_VARIABLE: + pm_parser_err_current(parser, PM_ERR_ARGUMENT_FORMAL_CLASS); + parser_lex(parser); + break; + default: + expect1(parser, PM_TOKEN_IDENTIFIER, PM_ERR_BLOCK_PARAM_LOCAL_VARIABLE); + break; + } + + bool repeated = pm_parser_parameter_name_check(parser, &parser->previous); + pm_parser_local_add_token(parser, &parser->previous, 1); + + pm_block_local_variable_node_t *local = pm_block_local_variable_node_create(parser, &parser->previous); + if (repeated) pm_node_flag_set_repeated_parameter((pm_node_t *) local); + + pm_block_parameters_node_append_local(block_parameters, local); + } while (accept1(parser, PM_TOKEN_COMMA)); + } } return block_parameters; @@ -13188,7 +14078,6 @@ parse_block(pm_parser_t *parser) { pm_token_t opening = parser->previous; accept1(parser, PM_TOKEN_NEWLINE); - pm_constant_id_t saved_param_name = pm_parser_current_param_name_unset(parser); pm_accepts_block_stack_push(parser, true); pm_parser_scope_push(parser, false); @@ -13232,19 +14121,19 @@ parse_block(pm_parser_t *parser) { if (match2(parser, PM_TOKEN_KEYWORD_RESCUE, PM_TOKEN_KEYWORD_ENSURE)) { assert(statements == NULL || PM_NODE_TYPE_P(statements, PM_STATEMENTS_NODE)); - statements = (pm_node_t *) parse_rescues_as_begin(parser, opening.start, (pm_statements_node_t *) statements, false); + statements = (pm_node_t *) parse_rescues_implicit_begin(parser, opening.start, (pm_statements_node_t *) statements, PM_RESCUES_BLOCK); } } expect1(parser, PM_TOKEN_KEYWORD_END, PM_ERR_BLOCK_TERM_END); } - pm_constant_id_list_t locals = parser->current_scope->locals; + pm_constant_id_list_t locals; + pm_locals_order(parser, &parser->current_scope->locals, &locals, pm_parser_scope_toplevel_p(parser)); pm_node_t *parameters = parse_blocklike_parameters(parser, (pm_node_t *) block_parameters, &opening, &parser->previous); pm_parser_scope_pop(parser); pm_accepts_block_stack_pop(parser); - pm_parser_current_param_name_restore(parser, saved_param_name); return pm_block_node_create(parser, &locals, &opening, parameters, statements, &parser->previous); } @@ -13329,6 +14218,160 @@ parse_arguments_list(pm_parser_t *parser, pm_arguments_t *arguments, bool accept return found; } +/** + * Check that the block exit (next, break, redo) is allowed in the current + * context. If it isn't, add an error to the parser. + */ +static void +parse_block_exit(pm_parser_t *parser, pm_node_t *node, const char *type) { + pm_context_node_t *context_node = parser->current_context; + bool through_expression = false; + + while (context_node != NULL) { + switch (context_node->context) { + case PM_CONTEXT_BLOCK_BRACES: + case PM_CONTEXT_BLOCK_KEYWORDS: + case PM_CONTEXT_BLOCK_ELSE: + case PM_CONTEXT_BLOCK_ENSURE: + case PM_CONTEXT_BLOCK_RESCUE: + case PM_CONTEXT_DEFINED: + case PM_CONTEXT_FOR: + case PM_CONTEXT_LAMBDA_BRACES: + case PM_CONTEXT_LAMBDA_DO_END: + case PM_CONTEXT_LAMBDA_ELSE: + case PM_CONTEXT_LAMBDA_ENSURE: + case PM_CONTEXT_LAMBDA_RESCUE: + case PM_CONTEXT_POSTEXE: + case PM_CONTEXT_UNTIL: + case PM_CONTEXT_WHILE: + // These are the good cases. We're allowed to have a block exit + // in these contexts. + return; + case PM_CONTEXT_DEF: + case PM_CONTEXT_DEF_PARAMS: + case PM_CONTEXT_DEF_ELSE: + case PM_CONTEXT_DEF_ENSURE: + case PM_CONTEXT_DEF_RESCUE: + case PM_CONTEXT_MAIN: + case PM_CONTEXT_PREEXE: + case PM_CONTEXT_SCLASS: + case PM_CONTEXT_SCLASS_ELSE: + case PM_CONTEXT_SCLASS_ENSURE: + case PM_CONTEXT_SCLASS_RESCUE: + // These are the bad cases. We're not allowed to have a block + // exit in these contexts. + + if (through_expression) { + // If we get here, then we're about to mark this block exit + // as invalid. However, it could later _become_ valid if we + // find a trailing while/until on the expression. In this + // case instead of adding the error here, we'll add the + // block exit to the list of exits for the expression, and + // the node parsing will handle validating it instead. + assert(parser->current_block_exits != NULL); + pm_node_list_append(parser->current_block_exits, node); + } else { + // Otherwise, if we haven't gone through an expression + // context, then this is just invalid and we'll add the + // error here. + PM_PARSER_ERR_NODE_FORMAT(parser, node, PM_ERR_INVALID_BLOCK_EXIT, type); + } + + return; + case PM_CONTEXT_NONE: + // This case should never happen. + assert(false && "unreachable"); + break; + case PM_CONTEXT_BEGIN: + case PM_CONTEXT_BEGIN_ELSE: + case PM_CONTEXT_BEGIN_ENSURE: + case PM_CONTEXT_BEGIN_RESCUE: + case PM_CONTEXT_CASE_IN: + case PM_CONTEXT_CASE_WHEN: + case PM_CONTEXT_CLASS: + case PM_CONTEXT_CLASS_ELSE: + case PM_CONTEXT_CLASS_ENSURE: + case PM_CONTEXT_CLASS_RESCUE: + case PM_CONTEXT_ELSE: + case PM_CONTEXT_ELSIF: + case PM_CONTEXT_IF: + case PM_CONTEXT_MODULE: + case PM_CONTEXT_MODULE_ELSE: + case PM_CONTEXT_MODULE_ENSURE: + case PM_CONTEXT_MODULE_RESCUE: + case PM_CONTEXT_PARENS: + case PM_CONTEXT_RESCUE_MODIFIER: + case PM_CONTEXT_TERNARY: + case PM_CONTEXT_UNLESS: + // If we got to an expression that could be modified by a + // trailing while/until, then we'll track that we have gotten + // here because we need to know it if this block exit is later + // marked as invalid. + through_expression = true; + break; + case PM_CONTEXT_EMBEXPR: + case PM_CONTEXT_DEFAULT_PARAMS: + case PM_CONTEXT_FOR_INDEX: + case PM_CONTEXT_PREDICATE: + // In these contexts we should continue walking up the list of + // contexts. + break; + } + + context_node = context_node->prev; + } +} + +/** + * When we hit an expression that could contain block exits, we need to stash + * the previous set and create a new one. + */ +static pm_node_list_t * +push_block_exits(pm_parser_t *parser, pm_node_list_t *current_block_exits) { + pm_node_list_t *previous_block_exits = parser->current_block_exits; + parser->current_block_exits = current_block_exits; + return previous_block_exits; +} + +/** + * Pop the current level of block exits from the parser, and add errors to the + * parser if any of them are deemed to be invalid. + */ +static void +pop_block_exits(pm_parser_t *parser, pm_node_list_t *previous_block_exits) { + if (match2(parser, PM_TOKEN_KEYWORD_WHILE_MODIFIER, PM_TOKEN_KEYWORD_UNTIL_MODIFIER)) { + // If we matched a trailing while/until, then all of the block exits in + // the contained list are valid. In this case we do not need to do + // anything. + } else if (previous_block_exits != NULL) { + // If we did not matching a trailing while/until, then all of the block + // exits contained in the list are invalid for this specific context. + // However, they could still become valid in a higher level context if + // there is another list above this one. In this case we'll push all of + // the block exits up to the previous list. + pm_node_list_concat(previous_block_exits, parser->current_block_exits); + } else { + // If we did not match a trailing while/until and this was the last + // chance to do so, then all of the block exits in the list are invalid + // and we need to add an error for each of them. + pm_node_t *block_exit; + PM_NODE_LIST_FOREACH(parser->current_block_exits, index, block_exit) { + const char *type; + + switch (PM_NODE_TYPE(block_exit)) { + case PM_BREAK_NODE: type = "break"; break; + case PM_NEXT_NODE: type = "next"; break; + case PM_REDO_NODE: type = "redo"; break; + default: assert(false && "unreachable"); type = ""; break; + } + + PM_PARSER_ERR_NODE_FORMAT(parser, block_exit, PM_ERR_INVALID_BLOCK_EXIT, type); + } + } + + parser->current_block_exits = previous_block_exits; +} + static inline pm_node_t * parse_predicate(pm_parser_t *parser, pm_binding_power_t binding_power, pm_context_t context, pm_token_t *then_keyword) { context_push(parser, PM_CONTEXT_PREDICATE); @@ -13353,6 +14396,9 @@ parse_predicate(pm_parser_t *parser, pm_binding_power_t binding_power, pm_contex static inline pm_node_t * parse_conditional(pm_parser_t *parser, pm_context_t context) { + pm_node_list_t current_block_exits = { 0 }; + pm_node_list_t *previous_block_exits = push_block_exits(parser, ¤t_block_exits); + pm_token_t keyword = parser->previous; pm_token_t then_keyword = not_provided(parser); @@ -13432,7 +14478,8 @@ parse_conditional(pm_parser_t *parser, pm_context_t context) { break; } } else { - // We should specialize this error message to refer to 'if' or 'unless' explicitly. + // We should specialize this error message to refer to 'if' or 'unless' + // explicitly. expect1(parser, PM_TOKEN_KEYWORD_END, PM_ERR_CONDITIONAL_TERM); } @@ -13469,6 +14516,9 @@ parse_conditional(pm_parser_t *parser, pm_context_t context) { break; } + pop_block_exits(parser, previous_block_exits); + pm_node_list_free(¤t_block_exits); + return parent; } @@ -13773,14 +14823,12 @@ parse_symbol(pm_parser_t *parser, pm_lex_mode_t *lex_mode, pm_lex_state_t next_s return (pm_node_t *) pm_string_node_to_symbol_node(parser, (pm_string_node_t *) part, &opening, &parser->previous); } - // Create a node_list first. We'll use this to check if it should be an - // InterpolatedSymbolNode or a SymbolNode. - pm_node_list_t node_list = { 0 }; - if (part) pm_node_list_append(&node_list, part); + pm_interpolated_symbol_node_t *symbol = pm_interpolated_symbol_node_create(parser, &opening, NULL, &opening); + if (part) pm_interpolated_symbol_node_append(symbol, part); while (!match2(parser, PM_TOKEN_STRING_END, PM_TOKEN_EOF)) { if ((part = parse_string_part(parser)) != NULL) { - pm_node_list_append(&node_list, part); + pm_interpolated_symbol_node_append(symbol, part); } } @@ -13791,7 +14839,8 @@ parse_symbol(pm_parser_t *parser, pm_lex_mode_t *lex_mode, pm_lex_state_t next_s expect1(parser, PM_TOKEN_STRING_END, PM_ERR_SYMBOL_TERM_INTERPOLATED); } - return (pm_node_t *) pm_interpolated_symbol_node_create(parser, &opening, &node_list, &parser->previous); + pm_interpolated_symbol_node_closing_loc_set(symbol, &parser->previous); + return (pm_node_t *) symbol; } pm_token_t content; @@ -13812,14 +14861,14 @@ parse_symbol(pm_parser_t *parser, pm_lex_mode_t *lex_mode, pm_lex_state_t next_s // In this case, the best way we have to represent this is as an // interpolated string node, so that's what we'll do here. if (match1(parser, PM_TOKEN_STRING_CONTENT)) { - pm_node_list_t parts = { 0 }; + pm_interpolated_symbol_node_t *symbol = pm_interpolated_symbol_node_create(parser, &opening, NULL, &opening); pm_token_t bounds = not_provided(parser); pm_node_t *part = (pm_node_t *) pm_string_node_create_unescaped(parser, &bounds, &content, &bounds, &unescaped); - pm_node_list_append(&parts, part); + pm_interpolated_symbol_node_append(symbol, part); part = (pm_node_t *) pm_string_node_create_unescaped(parser, &bounds, &parser->current, &bounds, &parser->current_string); - pm_node_list_append(&parts, part); + pm_interpolated_symbol_node_append(symbol, part); if (next_state != PM_LEX_STATE_NONE) { lex_state_set(parser, next_state); @@ -13827,7 +14876,9 @@ parse_symbol(pm_parser_t *parser, pm_lex_mode_t *lex_mode, pm_lex_state_t next_s parser_lex(parser); expect1(parser, PM_TOKEN_STRING_END, PM_ERR_SYMBOL_TERM_DYNAMIC); - return (pm_node_t *) pm_interpolated_symbol_node_create(parser, &opening, &parts, &parser->previous); + + pm_interpolated_symbol_node_closing_loc_set(symbol, &parser->previous); + return (pm_node_t *) symbol; } } else { content = (pm_token_t) { .type = PM_TOKEN_STRING_CONTENT, .start = parser->previous.end, .end = parser->previous.end }; @@ -14003,7 +15054,7 @@ parse_variable(pm_parser_t *parser) { // Finally we can create the local variable read node. pm_constant_id_t name_id = pm_parser_local_add_constant(parser, pm_numbered_parameter_names[numbered_parameters - 1], 2); - return pm_local_variable_read_node_create_constant_id(parser, &parser->previous, name_id, 0); + return pm_local_variable_read_node_create_constant_id(parser, &parser->previous, name_id, 0, false); } } @@ -14099,7 +15150,7 @@ parse_heredoc_dedent_string(pm_string_t *string, size_t common_whitespace) { static void parse_heredoc_dedent(pm_parser_t *parser, pm_node_list_t *nodes, size_t common_whitespace) { // The next node should be dedented if it's the first node in the list or if - // if follows a string node. + // it follows a string node. bool dedent_next = true; // Iterate over all nodes, and trim whitespace accordingly. We're going to @@ -14107,9 +15158,8 @@ parse_heredoc_dedent(pm_parser_t *parser, pm_node_list_t *nodes, size_t common_w // the whitespace from a node, then we'll drop it from the list entirely. size_t write_index = 0; - for (size_t read_index = 0; read_index < nodes->size; read_index++) { - pm_node_t *node = nodes->nodes[read_index]; - + pm_node_t *node; + PM_NODE_LIST_FOREACH(nodes, read_index, node) { // We're not manipulating child nodes that aren't strings. In this case // we'll skip past it and indicate that the subsequent node should not // be dedented. @@ -14138,13 +15188,30 @@ parse_heredoc_dedent(pm_parser_t *parser, pm_node_list_t *nodes, size_t common_w } static pm_node_t * -parse_pattern(pm_parser_t *parser, bool top_pattern, pm_diagnostic_id_t diag_id); +parse_pattern(pm_parser_t *parser, pm_constant_id_list_t *captures, bool top_pattern, pm_diagnostic_id_t diag_id); + +/** + * Add the newly created local to the list of captures for this pattern matching + * expression. If it is duplicated from a previous local, then we'll need to add + * an error to the parser. + */ +static void +parse_pattern_capture(pm_parser_t *parser, pm_constant_id_list_t *captures, pm_constant_id_t capture, const pm_location_t *location) { + // Skip this capture if it starts with an underscore. + if (*location->start == '_') return; + + if (pm_constant_id_list_includes(captures, capture)) { + pm_parser_err(parser, location->start, location->end, PM_ERR_PATTERN_CAPTURE_DUPLICATE); + } else { + pm_constant_id_list_append(captures, capture); + } +} /** * Accept any number of constants joined by :: delimiters. */ static pm_node_t * -parse_pattern_constant_path(pm_parser_t *parser, pm_node_t *node) { +parse_pattern_constant_path(pm_parser_t *parser, pm_constant_id_list_t *captures, pm_node_t *node) { // Now, if there are any :: operators that follow, parse them as constant // path nodes. while (accept1(parser, PM_TOKEN_COLON_COLON)) { @@ -14152,7 +15219,7 @@ parse_pattern_constant_path(pm_parser_t *parser, pm_node_t *node) { expect1(parser, PM_TOKEN_CONSTANT, PM_ERR_CONSTANT_PATH_COLON_COLON_CONSTANT); pm_node_t *child = (pm_node_t *) pm_constant_read_node_create(parser, &parser->previous); - node = (pm_node_t *)pm_constant_path_node_create(parser, node, &delimiter, child); + node = (pm_node_t *) pm_constant_path_node_create(parser, node, &delimiter, child); } // If there is a [ or ( that follows, then this is part of a larger pattern @@ -14171,7 +15238,7 @@ parse_pattern_constant_path(pm_parser_t *parser, pm_node_t *node) { accept1(parser, PM_TOKEN_NEWLINE); if (!accept1(parser, PM_TOKEN_BRACKET_RIGHT)) { - inner = parse_pattern(parser, true, PM_ERR_PATTERN_EXPRESSION_AFTER_BRACKET); + inner = parse_pattern(parser, captures, true, PM_ERR_PATTERN_EXPRESSION_AFTER_BRACKET); accept1(parser, PM_TOKEN_NEWLINE); expect1(parser, PM_TOKEN_BRACKET_RIGHT, PM_ERR_PATTERN_TERM_BRACKET); } @@ -14183,7 +15250,7 @@ parse_pattern_constant_path(pm_parser_t *parser, pm_node_t *node) { accept1(parser, PM_TOKEN_NEWLINE); if (!accept1(parser, PM_TOKEN_PARENTHESIS_RIGHT)) { - inner = parse_pattern(parser, true, PM_ERR_PATTERN_EXPRESSION_AFTER_PAREN); + inner = parse_pattern(parser, captures, true, PM_ERR_PATTERN_EXPRESSION_AFTER_PAREN); accept1(parser, PM_TOKEN_NEWLINE); expect1(parser, PM_TOKEN_PARENTHESIS_RIGHT, PM_ERR_PATTERN_TERM_PAREN); } @@ -14266,18 +15333,30 @@ parse_pattern_constant_path(pm_parser_t *parser, pm_node_t *node) { * Parse a rest pattern. */ static pm_splat_node_t * -parse_pattern_rest(pm_parser_t *parser) { +parse_pattern_rest(pm_parser_t *parser, pm_constant_id_list_t *captures) { assert(parser->previous.type == PM_TOKEN_USTAR); pm_token_t operator = parser->previous; pm_node_t *name = NULL; // Rest patterns don't necessarily have a name associated with them. So we - // will check for that here. If they do, then we'll add it to the local table - // since this pattern will cause it to become a local variable. + // will check for that here. If they do, then we'll add it to the local + // table since this pattern will cause it to become a local variable. if (accept1(parser, PM_TOKEN_IDENTIFIER)) { pm_token_t identifier = parser->previous; - pm_parser_local_add_token(parser, &identifier); - name = (pm_node_t *) pm_local_variable_target_node_create(parser, &identifier); + pm_constant_id_t constant_id = pm_parser_constant_id_token(parser, &identifier); + + int depth; + if ((depth = pm_parser_local_depth_constant_id(parser, constant_id)) == -1) { + pm_parser_local_add(parser, constant_id, identifier.start, identifier.end, 0); + } + + parse_pattern_capture(parser, captures, constant_id, &PM_LOCATION_TOKEN_VALUE(&identifier)); + name = (pm_node_t *) pm_local_variable_target_node_create( + parser, + &PM_LOCATION_TOKEN_VALUE(&identifier), + constant_id, + (uint32_t) (depth == -1 ? 0 : depth) + ); } // Finally we can return the created node. @@ -14288,7 +15367,7 @@ parse_pattern_rest(pm_parser_t *parser) { * Parse a keyword rest node. */ static pm_node_t * -parse_pattern_keyword_rest(pm_parser_t *parser) { +parse_pattern_keyword_rest(pm_parser_t *parser, pm_constant_id_list_t *captures) { assert(parser->current.type == PM_TOKEN_USTAR_STAR); parser_lex(parser); @@ -14300,8 +15379,20 @@ parse_pattern_keyword_rest(pm_parser_t *parser) { } if (accept1(parser, PM_TOKEN_IDENTIFIER)) { - pm_parser_local_add_token(parser, &parser->previous); - value = (pm_node_t *) pm_local_variable_target_node_create(parser, &parser->previous); + pm_constant_id_t constant_id = pm_parser_constant_id_token(parser, &parser->previous); + + int depth; + if ((depth = pm_parser_local_depth_constant_id(parser, constant_id)) == -1) { + pm_parser_local_add(parser, constant_id, parser->previous.start, parser->previous.end, 0); + } + + parse_pattern_capture(parser, captures, constant_id, &PM_LOCATION_TOKEN_VALUE(&parser->previous)); + value = (pm_node_t *) pm_local_variable_target_node_create( + parser, + &PM_LOCATION_TOKEN_VALUE(&parser->previous), + constant_id, + (uint32_t) (depth == -1 ? 0 : depth) + ); } return (pm_node_t *) pm_assoc_splat_node_create(parser, value, &operator); @@ -14312,30 +15403,44 @@ parse_pattern_keyword_rest(pm_parser_t *parser) { * value. This will use an implicit local variable target. */ static pm_node_t * -parse_pattern_hash_implicit_value(pm_parser_t *parser, pm_symbol_node_t *key) { +parse_pattern_hash_implicit_value(pm_parser_t *parser, pm_constant_id_list_t *captures, pm_symbol_node_t *key) { const pm_location_t *value_loc = &((pm_symbol_node_t *) key)->value_loc; - pm_constant_id_t name = pm_parser_constant_id_location(parser, value_loc->start, value_loc->end); + pm_constant_id_t constant_id = pm_parser_constant_id_location(parser, value_loc->start, value_loc->end); - int current_depth = pm_parser_local_depth_constant_id(parser, name); - uint32_t depth; - - if (current_depth == -1) { - pm_parser_local_add_location(parser, value_loc->start, value_loc->end); - depth = 0; - } else { - depth = (uint32_t) current_depth; + int depth; + if ((depth = pm_parser_local_depth_constant_id(parser, constant_id)) == -1) { + pm_parser_local_add(parser, constant_id, value_loc->start, value_loc->end, 0); } - pm_local_variable_target_node_t *target = pm_local_variable_target_node_create_values(parser, value_loc, name, depth); + parse_pattern_capture(parser, captures, constant_id, value_loc); + pm_local_variable_target_node_t *target = pm_local_variable_target_node_create( + parser, + value_loc, + constant_id, + (uint32_t) (depth == -1 ? 0 : depth) + ); + return (pm_node_t *) pm_implicit_node_create(parser, (pm_node_t *) target); } +/** + * Add a node to the list of keys for a hash pattern, and if it is a duplicate + * then add an error to the parser. + */ +static void +parse_pattern_hash_key(pm_parser_t *parser, pm_static_literals_t *keys, pm_node_t *node) { + if (pm_static_literals_add(parser, keys, node) != NULL) { + pm_parser_err_node(parser, node, PM_ERR_PATTERN_HASH_KEY_DUPLICATE); + } +} + /** * Parse a hash pattern. */ static pm_hash_pattern_node_t * -parse_pattern_hash(pm_parser_t *parser, pm_node_t *first_node) { +parse_pattern_hash(pm_parser_t *parser, pm_constant_id_list_t *captures, pm_node_t *first_node) { pm_node_list_t assocs = { 0 }; + pm_static_literals_t keys = { 0 }; pm_node_t *rest = NULL; switch (PM_NODE_TYPE(first_node)) { @@ -14345,16 +15450,17 @@ parse_pattern_hash(pm_parser_t *parser, pm_node_t *first_node) { break; case PM_SYMBOL_NODE: { if (pm_symbol_node_label_p(first_node)) { + parse_pattern_hash_key(parser, &keys, first_node); pm_node_t *value; - if (!match7(parser, PM_TOKEN_COMMA, PM_TOKEN_KEYWORD_THEN, PM_TOKEN_BRACE_RIGHT, PM_TOKEN_BRACKET_RIGHT, PM_TOKEN_PARENTHESIS_RIGHT, PM_TOKEN_NEWLINE, PM_TOKEN_SEMICOLON)) { - // Here we have a value for the first assoc in the list, so - // we will parse it now. - value = parse_pattern(parser, false, PM_ERR_PATTERN_EXPRESSION_AFTER_KEY); - } else { + if (match7(parser, PM_TOKEN_COMMA, PM_TOKEN_KEYWORD_THEN, PM_TOKEN_BRACE_RIGHT, PM_TOKEN_BRACKET_RIGHT, PM_TOKEN_PARENTHESIS_RIGHT, PM_TOKEN_NEWLINE, PM_TOKEN_SEMICOLON)) { // Otherwise, we will create an implicit local variable // target for the value. - value = parse_pattern_hash_implicit_value(parser, (pm_symbol_node_t *) first_node); + value = parse_pattern_hash_implicit_value(parser, captures, (pm_symbol_node_t *) first_node); + } else { + // Here we have a value for the first assoc in the list, so + // we will parse it now. + value = parse_pattern(parser, captures, false, PM_ERR_PATTERN_EXPRESSION_AFTER_KEY); } pm_token_t operator = not_provided(parser); @@ -14388,7 +15494,7 @@ parse_pattern_hash(pm_parser_t *parser, pm_node_t *first_node) { } if (match1(parser, PM_TOKEN_USTAR_STAR)) { - pm_node_t *assoc = parse_pattern_keyword_rest(parser); + pm_node_t *assoc = parse_pattern_keyword_rest(parser, captures); if (rest == NULL) { rest = assoc; @@ -14399,14 +15505,14 @@ parse_pattern_hash(pm_parser_t *parser, pm_node_t *first_node) { } else { expect1(parser, PM_TOKEN_LABEL, PM_ERR_PATTERN_LABEL_AFTER_COMMA); pm_node_t *key = (pm_node_t *) pm_symbol_node_label_create(parser, &parser->previous); + + parse_pattern_hash_key(parser, &keys, key); pm_node_t *value = NULL; - if (!match7(parser, PM_TOKEN_COMMA, PM_TOKEN_KEYWORD_THEN, PM_TOKEN_BRACE_RIGHT, PM_TOKEN_BRACKET_RIGHT, PM_TOKEN_PARENTHESIS_RIGHT, PM_TOKEN_NEWLINE, PM_TOKEN_SEMICOLON)) { - value = parse_pattern(parser, false, PM_ERR_PATTERN_EXPRESSION_AFTER_KEY); + if (match7(parser, PM_TOKEN_COMMA, PM_TOKEN_KEYWORD_THEN, PM_TOKEN_BRACE_RIGHT, PM_TOKEN_BRACKET_RIGHT, PM_TOKEN_PARENTHESIS_RIGHT, PM_TOKEN_NEWLINE, PM_TOKEN_SEMICOLON)) { + value = parse_pattern_hash_implicit_value(parser, captures, (pm_symbol_node_t *) key); } else { - const pm_location_t *value_loc = &((pm_symbol_node_t *) key)->value_loc; - pm_parser_local_add_location(parser, value_loc->start, value_loc->end); - value = parse_pattern_hash_implicit_value(parser, (pm_symbol_node_t *) key); + value = parse_pattern(parser, captures, false, PM_ERR_PATTERN_EXPRESSION_AFTER_KEY); } pm_token_t operator = not_provided(parser); @@ -14423,6 +15529,7 @@ parse_pattern_hash(pm_parser_t *parser, pm_node_t *first_node) { pm_hash_pattern_node_t *node = pm_hash_pattern_node_node_list_create(parser, &assocs, rest); xfree(assocs.nodes); + pm_static_literals_free(&keys); return node; } @@ -14430,18 +15537,25 @@ parse_pattern_hash(pm_parser_t *parser, pm_node_t *first_node) { * Parse a pattern expression primitive. */ static pm_node_t * -parse_pattern_primitive(pm_parser_t *parser, pm_diagnostic_id_t diag_id) { +parse_pattern_primitive(pm_parser_t *parser, pm_constant_id_list_t *captures, pm_diagnostic_id_t diag_id) { switch (parser->current.type) { case PM_TOKEN_IDENTIFIER: case PM_TOKEN_METHOD_NAME: { parser_lex(parser); - pm_token_t name = parser->previous; - int depth = pm_parser_local_depth(parser, &name); - if (depth < 0) { - depth = 0; - pm_parser_local_add_token(parser, &name); + pm_constant_id_t constant_id = pm_parser_constant_id_token(parser, &parser->previous); + + int depth; + if ((depth = pm_parser_local_depth_constant_id(parser, constant_id)) == -1) { + pm_parser_local_add(parser, constant_id, parser->previous.start, parser->previous.end, 0); } - return (pm_node_t *) pm_local_variable_target_node_create_depth(parser, &name, (uint32_t) depth); + + parse_pattern_capture(parser, captures, constant_id, &PM_LOCATION_TOKEN_VALUE(&parser->previous)); + return (pm_node_t *) pm_local_variable_target_node_create( + parser, + &PM_LOCATION_TOKEN_VALUE(&parser->previous), + constant_id, + (uint32_t) (depth == -1 ? 0 : depth) + ); } case PM_TOKEN_BRACKET_LEFT_ARRAY: { pm_token_t opening = parser->current; @@ -14450,15 +15564,14 @@ parse_pattern_primitive(pm_parser_t *parser, pm_diagnostic_id_t diag_id) { if (accept1(parser, PM_TOKEN_BRACKET_RIGHT)) { // If we have an empty array pattern, then we'll just return a new // array pattern node. - return (pm_node_t *)pm_array_pattern_node_empty_create(parser, &opening, &parser->previous); + return (pm_node_t *) pm_array_pattern_node_empty_create(parser, &opening, &parser->previous); } // Otherwise, we'll parse the inner pattern, then deal with it depending // on the type it returns. - pm_node_t *inner = parse_pattern(parser, true, PM_ERR_PATTERN_EXPRESSION_AFTER_BRACKET); + pm_node_t *inner = parse_pattern(parser, captures, true, PM_ERR_PATTERN_EXPRESSION_AFTER_BRACKET); accept1(parser, PM_TOKEN_NEWLINE); - expect1(parser, PM_TOKEN_BRACKET_RIGHT, PM_ERR_PATTERN_TERM_BRACKET); pm_token_t closing = parser->previous; @@ -14520,7 +15633,7 @@ parse_pattern_primitive(pm_parser_t *parser, pm_diagnostic_id_t diag_id) { first_node = (pm_node_t *) pm_symbol_node_label_create(parser, &parser->previous); break; case PM_TOKEN_USTAR_STAR: - first_node = parse_pattern_keyword_rest(parser); + first_node = parse_pattern_keyword_rest(parser, captures); break; case PM_TOKEN_STRING_BEGIN: first_node = parse_expression(parser, PM_BINDING_POWER_MAX, false, PM_ERR_PATTERN_HASH_KEY); @@ -14534,7 +15647,7 @@ parse_pattern_primitive(pm_parser_t *parser, pm_diagnostic_id_t diag_id) { } } - node = parse_pattern_hash(parser, first_node); + node = parse_pattern_hash(parser, captures, first_node); accept1(parser, PM_TOKEN_NEWLINE); expect1(parser, PM_TOKEN_BRACE_RIGHT, PM_ERR_PATTERN_TERM_BRACE); @@ -14614,7 +15727,7 @@ parse_pattern_primitive(pm_parser_t *parser, pm_diagnostic_id_t diag_id) { variable = (pm_node_t *) read; } else { PM_PARSER_ERR_TOKEN_FORMAT_CONTENT(parser, parser->previous, PM_ERR_NO_LOCAL_VARIABLE); - variable = (pm_node_t *) pm_local_variable_read_node_create(parser, &parser->previous, 0); + variable = (pm_node_t *) pm_local_variable_read_node_missing_create(parser, &parser->previous, 0); } } @@ -14681,14 +15794,14 @@ parse_pattern_primitive(pm_parser_t *parser, pm_diagnostic_id_t diag_id) { pm_node_t *child = (pm_node_t *) pm_constant_read_node_create(parser, &parser->previous); pm_constant_path_node_t *node = pm_constant_path_node_create(parser, NULL, &delimiter, child); - return parse_pattern_constant_path(parser, (pm_node_t *)node); + return parse_pattern_constant_path(parser, captures, (pm_node_t *) node); } case PM_TOKEN_CONSTANT: { pm_token_t constant = parser->current; parser_lex(parser); pm_node_t *node = (pm_node_t *) pm_constant_read_node_create(parser, &constant); - return parse_pattern_constant_path(parser, node); + return parse_pattern_constant_path(parser, captures, node); } default: pm_parser_err_current(parser, diag_id); @@ -14701,7 +15814,7 @@ parse_pattern_primitive(pm_parser_t *parser, pm_diagnostic_id_t diag_id) { * assignment. */ static pm_node_t * -parse_pattern_primitives(pm_parser_t *parser, pm_diagnostic_id_t diag_id) { +parse_pattern_primitives(pm_parser_t *parser, pm_constant_id_list_t *captures, pm_diagnostic_id_t diag_id) { pm_node_t *node = NULL; do { @@ -14718,23 +15831,29 @@ parse_pattern_primitives(pm_parser_t *parser, pm_diagnostic_id_t diag_id) { case PM_TOKEN_UDOT_DOT_DOT: case PM_CASE_PRIMITIVE: { if (node == NULL) { - node = parse_pattern_primitive(parser, diag_id); + node = parse_pattern_primitive(parser, captures, diag_id); } else { - pm_node_t *right = parse_pattern_primitive(parser, PM_ERR_PATTERN_EXPRESSION_AFTER_PIPE); + pm_node_t *right = parse_pattern_primitive(parser, captures, PM_ERR_PATTERN_EXPRESSION_AFTER_PIPE); node = (pm_node_t *) pm_alternation_pattern_node_create(parser, node, right, &operator); } break; } case PM_TOKEN_PARENTHESIS_LEFT: { + pm_token_t opening = parser->current; parser_lex(parser); - if (node != NULL) { - pm_node_destroy(parser, node); - } - node = parse_pattern(parser, false, PM_ERR_PATTERN_EXPRESSION_AFTER_PAREN); + pm_node_t *body = parse_pattern(parser, captures, false, PM_ERR_PATTERN_EXPRESSION_AFTER_PAREN); accept1(parser, PM_TOKEN_NEWLINE); expect1(parser, PM_TOKEN_PARENTHESIS_RIGHT, PM_ERR_PATTERN_TERM_PAREN); + pm_node_t *right = (pm_node_t *) pm_parentheses_node_create(parser, &opening, body, &parser->previous); + + if (node == NULL) { + node = right; + } else { + node = (pm_node_t *) pm_alternation_pattern_node_create(parser, node, right, &operator); + } + break; } default: { @@ -14756,16 +15875,23 @@ parse_pattern_primitives(pm_parser_t *parser, pm_diagnostic_id_t diag_id) { // In this case we should create an assignment node. while (accept1(parser, PM_TOKEN_EQUAL_GREATER)) { pm_token_t operator = parser->previous; - expect1(parser, PM_TOKEN_IDENTIFIER, PM_ERR_PATTERN_IDENT_AFTER_HROCKET); - pm_token_t identifier = parser->previous; - int depth = pm_parser_local_depth(parser, &identifier); - if (depth < 0) { - depth = 0; - pm_parser_local_add_token(parser, &identifier); + + pm_constant_id_t constant_id = pm_parser_constant_id_token(parser, &parser->previous); + int depth; + + if ((depth = pm_parser_local_depth_constant_id(parser, constant_id)) == -1) { + pm_parser_local_add(parser, constant_id, parser->previous.start, parser->previous.end, 0); } - pm_node_t *target = (pm_node_t *) pm_local_variable_target_node_create_depth(parser, &identifier, (uint32_t) depth); + parse_pattern_capture(parser, captures, constant_id, &PM_LOCATION_TOKEN_VALUE(&parser->previous)); + pm_node_t *target = (pm_node_t *) pm_local_variable_target_node_create( + parser, + &PM_LOCATION_TOKEN_VALUE(&parser->previous), + constant_id, + (uint32_t) (depth == -1 ? 0 : depth) + ); + node = (pm_node_t *) pm_capture_pattern_node_create(parser, node, target, &operator); } @@ -14776,7 +15902,7 @@ parse_pattern_primitives(pm_parser_t *parser, pm_diagnostic_id_t diag_id) { * Parse a pattern matching expression. */ static pm_node_t * -parse_pattern(pm_parser_t *parser, bool top_pattern, pm_diagnostic_id_t diag_id) { +parse_pattern(pm_parser_t *parser, pm_constant_id_list_t *captures, bool top_pattern, pm_diagnostic_id_t diag_id) { pm_node_t *node = NULL; bool leading_rest = false; @@ -14786,30 +15912,30 @@ parse_pattern(pm_parser_t *parser, bool top_pattern, pm_diagnostic_id_t diag_id) case PM_TOKEN_LABEL: { parser_lex(parser); pm_node_t *key = (pm_node_t *) pm_symbol_node_label_create(parser, &parser->previous); - return (pm_node_t *) parse_pattern_hash(parser, key); + return (pm_node_t *) parse_pattern_hash(parser, captures, key); } case PM_TOKEN_USTAR_STAR: { - node = parse_pattern_keyword_rest(parser); - return (pm_node_t *) parse_pattern_hash(parser, node); + node = parse_pattern_keyword_rest(parser, captures); + return (pm_node_t *) parse_pattern_hash(parser, captures, node); } case PM_TOKEN_USTAR: { if (top_pattern) { parser_lex(parser); - node = (pm_node_t *) parse_pattern_rest(parser); + node = (pm_node_t *) parse_pattern_rest(parser, captures); leading_rest = true; break; } } /* fallthrough */ default: - node = parse_pattern_primitives(parser, diag_id); + node = parse_pattern_primitives(parser, captures, diag_id); break; } // If we got a dynamic label symbol, then we need to treat it like the // beginning of a hash pattern. if (pm_symbol_node_label_p(node)) { - return (pm_node_t *) parse_pattern_hash(parser, node); + return (pm_node_t *) parse_pattern_hash(parser, captures, node); } if (top_pattern && match1(parser, PM_TOKEN_COMMA)) { @@ -14829,7 +15955,7 @@ parse_pattern(pm_parser_t *parser, bool top_pattern, pm_diagnostic_id_t diag_id) } if (accept1(parser, PM_TOKEN_USTAR)) { - node = (pm_node_t *) parse_pattern_rest(parser); + node = (pm_node_t *) parse_pattern_rest(parser, captures); // If we have already parsed a splat pattern, then this is an error. We // will continue to parse the rest of the patterns, but we will indicate @@ -14840,7 +15966,7 @@ parse_pattern(pm_parser_t *parser, bool top_pattern, pm_diagnostic_id_t diag_id) trailing_rest = true; } else { - node = parse_pattern_primitives(parser, PM_ERR_PATTERN_EXPRESSION_AFTER_COMMA); + node = parse_pattern_primitives(parser, captures, PM_ERR_PATTERN_EXPRESSION_AFTER_COMMA); } pm_node_list_append(&nodes, node); @@ -14901,7 +16027,7 @@ parse_negative_numeric(pm_node_t *node) { } /** - * Returns a string content token at a particular location that is empty. + * Return a string content token at a particular location that is empty. */ static pm_token_t parse_strings_empty_content(const uint8_t *location) { @@ -14989,6 +16115,8 @@ parse_strings(pm_parser_t *parser, pm_node_t *current) { expect1(parser, PM_TOKEN_STRING_END, PM_ERR_STRING_LITERAL_EOF); node = (pm_node_t *) pm_interpolated_string_node_create(parser, &opening, &parts, &parser->previous); + + pm_node_list_free(&parts); } else if (accept1(parser, PM_TOKEN_LABEL_END) && !state_is_arg_labeled) { node = (pm_node_t *) pm_symbol_node_create_unescaped(parser, &opening, &content, &parser->previous, &unescaped, parse_symbol_encoding(parser, &unescaped)); } else if (match1(parser, PM_TOKEN_EOF)) { @@ -15043,6 +16171,8 @@ parse_strings(pm_parser_t *parser, pm_node_t *current) { expect1(parser, PM_TOKEN_STRING_END, PM_ERR_STRING_INTERPOLATED_TERM); node = (pm_node_t *) pm_interpolated_string_node_create(parser, &opening, &parts, &parser->previous); } + + pm_node_list_free(&parts); } } else { // If we get here, then the first part of the string is not plain @@ -15066,6 +16196,8 @@ parse_strings(pm_parser_t *parser, pm_node_t *current) { expect1(parser, PM_TOKEN_STRING_END, PM_ERR_STRING_INTERPOLATED_TERM); node = (pm_node_t *) pm_interpolated_string_node_create(parser, &opening, &parts, &parser->previous); } + + pm_node_list_free(&parts); } if (current == NULL) { @@ -15094,11 +16226,11 @@ parse_strings(pm_parser_t *parser, pm_node_t *current) { pm_token_t bounds = not_provided(parser); pm_interpolated_string_node_t *container = pm_interpolated_string_node_create(parser, &bounds, NULL, &bounds); - pm_interpolated_string_node_append(container, current); + pm_interpolated_string_node_append(parser, container, current); current = (pm_node_t *) container; } - pm_interpolated_string_node_append((pm_interpolated_string_node_t *) current, node); + pm_interpolated_string_node_append(parser, (pm_interpolated_string_node_t *) current, node); } } @@ -15122,9 +16254,176 @@ pm_parser_err_prefix(pm_parser_t *parser, pm_diagnostic_id_t diag_id) { PM_PARSER_ERR_TOKEN_FORMAT(parser, parser->previous, diag_id, human, parser->previous.start[0]); break; } - default: - pm_parser_err_previous(parser, diag_id); - break; + default: + pm_parser_err_previous(parser, diag_id); + break; + } +} + +/** + * Ensures that the current retry token is valid in the current context. + */ +static void +parse_retry(pm_parser_t *parser, const pm_node_t *node) { + pm_context_node_t *context_node = parser->current_context; + + while (context_node != NULL) { + switch (context_node->context) { + case PM_CONTEXT_BEGIN_RESCUE: + case PM_CONTEXT_BLOCK_RESCUE: + case PM_CONTEXT_CLASS_RESCUE: + case PM_CONTEXT_DEF_RESCUE: + case PM_CONTEXT_LAMBDA_RESCUE: + case PM_CONTEXT_MODULE_RESCUE: + case PM_CONTEXT_SCLASS_RESCUE: + case PM_CONTEXT_DEFINED: + case PM_CONTEXT_RESCUE_MODIFIER: + // These are the good cases. We're allowed to have a retry here. + return; + case PM_CONTEXT_CLASS: + case PM_CONTEXT_DEF: + case PM_CONTEXT_DEF_PARAMS: + case PM_CONTEXT_MAIN: + case PM_CONTEXT_MODULE: + case PM_CONTEXT_PREEXE: + case PM_CONTEXT_SCLASS: + // These are the bad cases. We're not allowed to have a retry in + // these contexts. + pm_parser_err_node(parser, node, PM_ERR_INVALID_RETRY_WITHOUT_RESCUE); + return; + case PM_CONTEXT_BEGIN_ELSE: + case PM_CONTEXT_BLOCK_ELSE: + case PM_CONTEXT_CLASS_ELSE: + case PM_CONTEXT_DEF_ELSE: + case PM_CONTEXT_LAMBDA_ELSE: + case PM_CONTEXT_MODULE_ELSE: + case PM_CONTEXT_SCLASS_ELSE: + // These are also bad cases, but with a more specific error + // message indicating the else. + pm_parser_err_node(parser, node, PM_ERR_INVALID_RETRY_AFTER_ELSE); + return; + case PM_CONTEXT_BEGIN_ENSURE: + case PM_CONTEXT_BLOCK_ENSURE: + case PM_CONTEXT_CLASS_ENSURE: + case PM_CONTEXT_DEF_ENSURE: + case PM_CONTEXT_LAMBDA_ENSURE: + case PM_CONTEXT_MODULE_ENSURE: + case PM_CONTEXT_SCLASS_ENSURE: + // These are also bad cases, but with a more specific error + // message indicating the ensure. + pm_parser_err_node(parser, node, PM_ERR_INVALID_RETRY_AFTER_ENSURE); + return; + case PM_CONTEXT_NONE: + // This case should never happen. + assert(false && "unreachable"); + break; + case PM_CONTEXT_BEGIN: + case PM_CONTEXT_BLOCK_BRACES: + case PM_CONTEXT_BLOCK_KEYWORDS: + case PM_CONTEXT_CASE_IN: + case PM_CONTEXT_CASE_WHEN: + case PM_CONTEXT_DEFAULT_PARAMS: + case PM_CONTEXT_ELSE: + case PM_CONTEXT_ELSIF: + case PM_CONTEXT_EMBEXPR: + case PM_CONTEXT_FOR_INDEX: + case PM_CONTEXT_FOR: + case PM_CONTEXT_IF: + case PM_CONTEXT_LAMBDA_BRACES: + case PM_CONTEXT_LAMBDA_DO_END: + case PM_CONTEXT_PARENS: + case PM_CONTEXT_POSTEXE: + case PM_CONTEXT_PREDICATE: + case PM_CONTEXT_TERNARY: + case PM_CONTEXT_UNLESS: + case PM_CONTEXT_UNTIL: + case PM_CONTEXT_WHILE: + // In these contexts we should continue walking up the list of + // contexts. + break; + } + + context_node = context_node->prev; + } +} + +/** + * Ensures that the current yield token is valid in the current context. + */ +static void +parse_yield(pm_parser_t *parser, const pm_node_t *node) { + pm_context_node_t *context_node = parser->current_context; + + while (context_node != NULL) { + switch (context_node->context) { + case PM_CONTEXT_DEF: + case PM_CONTEXT_DEF_PARAMS: + case PM_CONTEXT_DEFINED: + case PM_CONTEXT_DEF_ENSURE: + case PM_CONTEXT_DEF_RESCUE: + case PM_CONTEXT_DEF_ELSE: + // These are the good cases. We're allowed to have a block exit + // in these contexts. + return; + case PM_CONTEXT_CLASS: + case PM_CONTEXT_CLASS_ENSURE: + case PM_CONTEXT_CLASS_RESCUE: + case PM_CONTEXT_CLASS_ELSE: + case PM_CONTEXT_MAIN: + case PM_CONTEXT_MODULE: + case PM_CONTEXT_MODULE_ENSURE: + case PM_CONTEXT_MODULE_RESCUE: + case PM_CONTEXT_MODULE_ELSE: + case PM_CONTEXT_SCLASS: + case PM_CONTEXT_SCLASS_RESCUE: + case PM_CONTEXT_SCLASS_ENSURE: + case PM_CONTEXT_SCLASS_ELSE: + // These are the bad cases. We're not allowed to have a retry in + // these contexts. + pm_parser_err_node(parser, node, PM_ERR_INVALID_YIELD); + return; + case PM_CONTEXT_NONE: + // This case should never happen. + assert(false && "unreachable"); + break; + case PM_CONTEXT_BEGIN: + case PM_CONTEXT_BEGIN_ELSE: + case PM_CONTEXT_BEGIN_ENSURE: + case PM_CONTEXT_BEGIN_RESCUE: + case PM_CONTEXT_BLOCK_BRACES: + case PM_CONTEXT_BLOCK_KEYWORDS: + case PM_CONTEXT_BLOCK_ELSE: + case PM_CONTEXT_BLOCK_ENSURE: + case PM_CONTEXT_BLOCK_RESCUE: + case PM_CONTEXT_CASE_IN: + case PM_CONTEXT_CASE_WHEN: + case PM_CONTEXT_DEFAULT_PARAMS: + case PM_CONTEXT_ELSE: + case PM_CONTEXT_ELSIF: + case PM_CONTEXT_EMBEXPR: + case PM_CONTEXT_FOR_INDEX: + case PM_CONTEXT_FOR: + case PM_CONTEXT_IF: + case PM_CONTEXT_LAMBDA_BRACES: + case PM_CONTEXT_LAMBDA_DO_END: + case PM_CONTEXT_LAMBDA_ELSE: + case PM_CONTEXT_LAMBDA_ENSURE: + case PM_CONTEXT_LAMBDA_RESCUE: + case PM_CONTEXT_PARENS: + case PM_CONTEXT_POSTEXE: + case PM_CONTEXT_PREDICATE: + case PM_CONTEXT_PREEXE: + case PM_CONTEXT_RESCUE_MODIFIER: + case PM_CONTEXT_TERNARY: + case PM_CONTEXT_UNLESS: + case PM_CONTEXT_UNTIL: + case PM_CONTEXT_WHILE: + // In these contexts we should continue walking up the list of + // contexts. + break; + } + + context_node = context_node->prev; } } @@ -15231,6 +16530,10 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b case PM_TOKEN_PARENTHESIS_LEFT: case PM_TOKEN_PARENTHESIS_LEFT_PARENTHESES: { pm_token_t opening = parser->current; + + pm_node_list_t current_block_exits = { 0 }; + pm_node_list_t *previous_block_exits = push_block_exits(parser, ¤t_block_exits); + parser_lex(parser); while (accept2(parser, PM_TOKEN_SEMICOLON, PM_TOKEN_NEWLINE)); @@ -15238,6 +16541,10 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b // we have an empty parentheses node, and we can immediately return. if (match2(parser, PM_TOKEN_PARENTHESIS_RIGHT, PM_TOKEN_EOF)) { expect1(parser, PM_TOKEN_PARENTHESIS_RIGHT, PM_ERR_EXPECT_RPAREN); + + pop_block_exits(parser, previous_block_exits); + pm_node_list_free(¤t_block_exits); + return (pm_node_t *) pm_parentheses_node_create(parser, &opening, NULL, &parser->previous); } @@ -15263,9 +16570,13 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b if (opening.type == PM_TOKEN_PARENTHESIS_LEFT_PARENTHESES) { lex_state_set(parser, PM_LEX_STATE_ENDARG); } + parser_lex(parser); pm_accepts_block_stack_pop(parser); + pop_block_exits(parser, previous_block_exits); + pm_node_list_free(¤t_block_exits); + if (PM_NODE_TYPE_P(statement, PM_MULTI_TARGET_NODE) || PM_NODE_TYPE_P(statement, PM_SPLAT_NODE)) { // If we have a single statement and are ending on a right // parenthesis, then we need to check if this is possibly a @@ -15353,6 +16664,9 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b pm_accepts_block_stack_pop(parser); expect1(parser, PM_TOKEN_PARENTHESIS_RIGHT, PM_ERR_EXPECT_RPAREN); + pop_block_exits(parser, previous_block_exits); + pm_node_list_free(¤t_block_exits); + return (pm_node_t *) pm_parentheses_node_create(parser, &opening, (pm_node_t *) statements, &parser->previous); } case PM_TOKEN_BRACE_LEFT: { @@ -15674,6 +16988,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b node = (pm_node_t *) cast; } else { pm_interpolated_string_node_t *cast = pm_interpolated_string_node_create(parser, &opening, &parts, &opening); + pm_node_list_free(&parts); lex_mode_pop(parser); expect1(parser, PM_TOKEN_HEREDOC_END, PM_ERR_HEREDOC_TERM); @@ -15759,7 +17074,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b case PM_GLOBAL_VARIABLE_READ_NODE: { if (PM_NODE_TYPE_P(old_name, PM_BACK_REFERENCE_READ_NODE) || PM_NODE_TYPE_P(old_name, PM_NUMBERED_REFERENCE_READ_NODE) || PM_NODE_TYPE_P(old_name, PM_GLOBAL_VARIABLE_READ_NODE)) { if (PM_NODE_TYPE_P(old_name, PM_NUMBERED_REFERENCE_READ_NODE)) { - pm_parser_err_node(parser, old_name, PM_ERR_ALIAS_ARGUMENT); + pm_parser_err_node(parser, old_name, PM_ERR_ALIAS_ARGUMENT_NUMBERED_REFERENCE); } } else { pm_parser_err_node(parser, old_name, PM_ERR_ALIAS_ARGUMENT); @@ -15783,6 +17098,9 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b pm_token_t case_keyword = parser->previous; pm_node_t *predicate = NULL; + pm_node_list_t current_block_exits = { 0 }; + pm_node_list_t *previous_block_exits = push_block_exits(parser, ¤t_block_exits); + if (accept2(parser, PM_TOKEN_NEWLINE, PM_TOKEN_SEMICOLON)) { while (accept2(parser, PM_TOKEN_NEWLINE, PM_TOKEN_SEMICOLON)); predicate = NULL; @@ -15796,12 +17114,15 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b } if (accept1(parser, PM_TOKEN_KEYWORD_END)) { + pop_block_exits(parser, previous_block_exits); + pm_node_list_free(¤t_block_exits); + pm_parser_err_token(parser, &case_keyword, PM_ERR_CASE_MISSING_CONDITIONS); return (pm_node_t *) pm_case_node_create(parser, &case_keyword, predicate, &parser->previous); } - // At this point we can create a case node, though we don't yet know if it - // is a case-in or case-when node. + // At this point we can create a case node, though we don't yet know + // if it is a case-in or case-when node. pm_token_t end_keyword = not_provided(parser); pm_node_t *node; @@ -15879,8 +17200,9 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b pm_parser_err_token(parser, &case_keyword, PM_ERR_CASE_MATCH_MISSING_PREDICATE); } - // At this point we expect that we're parsing a case-in node. We will - // continue to parse the in nodes until we hit the end of the list. + // At this point we expect that we're parsing a case-in node. We + // will continue to parse the in nodes until we hit the end of + // the list. while (match1(parser, PM_TOKEN_KEYWORD_IN)) { bool previous_pattern_matching_newlines = parser->pattern_matching_newlines; parser->pattern_matching_newlines = true; @@ -15890,11 +17212,16 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b parser_lex(parser); pm_token_t in_keyword = parser->previous; - pm_node_t *pattern = parse_pattern(parser, true, PM_ERR_PATTERN_EXPRESSION_AFTER_IN); + + pm_constant_id_list_t captures = { 0 }; + pm_node_t *pattern = parse_pattern(parser, &captures, true, PM_ERR_PATTERN_EXPRESSION_AFTER_IN); + parser->pattern_matching_newlines = previous_pattern_matching_newlines; + pm_constant_id_list_free(&captures); - // Since we're in the top-level of the case-in node we need to check - // for guard clauses in the form of `if` or `unless` statements. + // Since we're in the top-level of the case-in node we need + // to check for guard clauses in the form of `if` or + // `unless` statements. if (accept1(parser, PM_TOKEN_KEYWORD_IF_MODIFIER)) { pm_token_t keyword = parser->previous; pm_node_t *predicate = parse_value_expression(parser, PM_BINDING_POWER_COMPOSITION, true, PM_ERR_CONDITIONAL_IF_PREDICATE); @@ -15905,9 +17232,9 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b pattern = (pm_node_t *) pm_unless_node_modifier_create(parser, pattern, &keyword, predicate); } - // Now we need to check for the terminator of the in node's pattern. - // It can be a newline or semicolon optionally followed by a `then` - // keyword. + // Now we need to check for the terminator of the in node's + // pattern. It can be a newline or semicolon optionally + // followed by a `then` keyword. pm_token_t then_keyword; if (accept2(parser, PM_TOKEN_NEWLINE, PM_TOKEN_SEMICOLON)) { if (accept1(parser, PM_TOKEN_KEYWORD_THEN)) { @@ -15920,8 +17247,8 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b then_keyword = parser->previous; } - // Now we can actually parse the statements associated with the in - // node. + // Now we can actually parse the statements associated with + // the in node. pm_statements_node_t *statements; if (match3(parser, PM_TOKEN_KEYWORD_IN, PM_TOKEN_KEYWORD_ELSE, PM_TOKEN_KEYWORD_END)) { statements = NULL; @@ -15929,8 +17256,8 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b statements = parse_statements(parser, PM_CONTEXT_CASE_IN); } - // Now that we have the full pattern and statements, we can create the - // node and attach it to the case node. + // Now that we have the full pattern and statements, we can + // create the node and attach it to the case node. pm_node_t *condition = (pm_node_t *) pm_in_node_create(parser, pattern, statements, &in_keyword, &then_keyword); pm_case_match_node_condition_append(case_node, condition); } @@ -15969,6 +17296,9 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b pm_case_match_node_end_keyword_loc_set((pm_case_match_node_t *) node, &parser->previous); } + pop_block_exits(parser, previous_block_exits); + pm_node_list_free(¤t_block_exits); + return node; } case PM_TOKEN_KEYWORD_BEGIN: { @@ -15976,6 +17306,9 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b pm_token_t begin_keyword = parser->previous; accept2(parser, PM_TOKEN_NEWLINE, PM_TOKEN_SEMICOLON); + + pm_node_list_t current_block_exits = { 0 }; + pm_node_list_t *previous_block_exits = push_block_exits(parser, ¤t_block_exits); pm_statements_node_t *begin_statements = NULL; if (!match3(parser, PM_TOKEN_KEYWORD_RESCUE, PM_TOKEN_KEYWORD_ENSURE, PM_TOKEN_KEYWORD_END)) { @@ -15986,7 +17319,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b } pm_begin_node_t *begin_node = pm_begin_node_create(parser, &begin_keyword, begin_statements); - parse_rescues(parser, begin_node, false); + parse_rescues(parser, begin_node, PM_RESCUES_BEGIN); expect1(parser, PM_TOKEN_KEYWORD_END, PM_ERR_BEGIN_TERM); begin_node->base.location.end = parser->previous.end; @@ -15996,6 +17329,9 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b pm_parser_err_node(parser, (pm_node_t *) begin_node->else_clause, PM_ERR_BEGIN_LONELY_ELSE); } + pop_block_exits(parser, previous_block_exits); + pm_node_list_free(¤t_block_exits); + return (pm_node_t *) begin_node; } case PM_TOKEN_KEYWORD_BEGIN_UPCASE: { @@ -16037,16 +17373,22 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b } switch (keyword.type) { - case PM_TOKEN_KEYWORD_BREAK: - return (pm_node_t *) pm_break_node_create(parser, &keyword, arguments.arguments); - case PM_TOKEN_KEYWORD_NEXT: - return (pm_node_t *) pm_next_node_create(parser, &keyword, arguments.arguments); + case PM_TOKEN_KEYWORD_BREAK: { + pm_node_t *node = (pm_node_t *) pm_break_node_create(parser, &keyword, arguments.arguments); + if (!parser->parsing_eval) parse_block_exit(parser, node, "break"); + return node; + } + case PM_TOKEN_KEYWORD_NEXT: { + pm_node_t *node = (pm_node_t *) pm_next_node_create(parser, &keyword, arguments.arguments); + if (!parser->parsing_eval) parse_block_exit(parser, node, "next"); + return node; + } case PM_TOKEN_KEYWORD_RETURN: { if ( (parser->current_context->context == PM_CONTEXT_CLASS) || (parser->current_context->context == PM_CONTEXT_MODULE) ) { - pm_parser_err_current(parser, PM_ERR_RETURN_INVALID); + pm_parser_err_previous(parser, PM_ERR_RETURN_INVALID); } return (pm_node_t *) pm_return_node_create(parser, &keyword, arguments.arguments); } @@ -16079,7 +17421,10 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b pm_arguments_t arguments = { 0 }; parse_arguments_list(parser, &arguments, false, accepts_command_call); - return (pm_node_t *) pm_yield_node_create(parser, &keyword, &arguments.opening_loc, arguments.arguments, &arguments.closing_loc); + pm_node_t *node = (pm_node_t *) pm_yield_node_create(parser, &keyword, &arguments.opening_loc, arguments.arguments, &arguments.closing_loc); + if (!parser->parsing_eval) parse_yield(parser, node); + + return node; } case PM_TOKEN_KEYWORD_CLASS: { parser_lex(parser); @@ -16090,7 +17435,6 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b pm_token_t operator = parser->previous; pm_node_t *expression = parse_value_expression(parser, PM_BINDING_POWER_NOT, true, PM_ERR_EXPECT_EXPRESSION_AFTER_LESS_LESS); - pm_constant_id_t saved_param_name = pm_parser_current_param_name_unset(parser); pm_parser_scope_push(parser, true); accept2(parser, PM_TOKEN_NEWLINE, PM_TOKEN_SEMICOLON); @@ -16103,19 +17447,23 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b if (match2(parser, PM_TOKEN_KEYWORD_RESCUE, PM_TOKEN_KEYWORD_ENSURE)) { assert(statements == NULL || PM_NODE_TYPE_P(statements, PM_STATEMENTS_NODE)); - statements = (pm_node_t *) parse_rescues_as_begin(parser, class_keyword.start, (pm_statements_node_t *) statements, false); + statements = (pm_node_t *) parse_rescues_implicit_begin(parser, class_keyword.start, (pm_statements_node_t *) statements, PM_RESCUES_SCLASS); } expect1(parser, PM_TOKEN_KEYWORD_END, PM_ERR_CLASS_TERM); - pm_constant_id_list_t locals = parser->current_scope->locals; + + pm_constant_id_list_t locals; + pm_locals_order(parser, &parser->current_scope->locals, &locals, false); pm_parser_scope_pop(parser); pm_do_loop_stack_pop(parser); - pm_parser_current_param_name_restore(parser, saved_param_name); return (pm_node_t *) pm_singleton_class_node_create(parser, &locals, &class_keyword, &operator, expression, statements, &parser->previous); } + pm_node_list_t current_block_exits = { 0 }; + pm_node_list_t *previous_block_exits = push_block_exits(parser, ¤t_block_exits); + pm_node_t *constant_path = parse_expression(parser, PM_BINDING_POWER_INDEX, false, PM_ERR_CLASS_NAME); pm_token_t name = parser->previous; if (name.type != PM_TOKEN_CONSTANT) { @@ -16138,7 +17486,6 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b superclass = NULL; } - pm_constant_id_t saved_param_name = pm_parser_current_param_name_unset(parser); pm_parser_scope_push(parser, true); if (inheritance_operator.type != PM_TOKEN_NOT_PROVIDED) { @@ -16156,7 +17503,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b if (match2(parser, PM_TOKEN_KEYWORD_RESCUE, PM_TOKEN_KEYWORD_ENSURE)) { assert(statements == NULL || PM_NODE_TYPE_P(statements, PM_STATEMENTS_NODE)); - statements = (pm_node_t *) parse_rescues_as_begin(parser, class_keyword.start, (pm_statements_node_t *) statements, false); + statements = (pm_node_t *) parse_rescues_implicit_begin(parser, class_keyword.start, (pm_statements_node_t *) statements, PM_RESCUES_CLASS); } expect1(parser, PM_TOKEN_KEYWORD_END, PM_ERR_CLASS_TERM); @@ -16165,16 +17512,19 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b pm_parser_err_token(parser, &class_keyword, PM_ERR_CLASS_IN_METHOD); } - pm_constant_id_list_t locals = parser->current_scope->locals; + pm_constant_id_list_t locals; + pm_locals_order(parser, &parser->current_scope->locals, &locals, false); pm_parser_scope_pop(parser); pm_do_loop_stack_pop(parser); - pm_parser_current_param_name_restore(parser, saved_param_name); if (!PM_NODE_TYPE_P(constant_path, PM_CONSTANT_PATH_NODE) && !(PM_NODE_TYPE_P(constant_path, PM_CONSTANT_READ_NODE))) { pm_parser_err_node(parser, constant_path, PM_ERR_CLASS_NAME); } + pop_block_exits(parser, previous_block_exits); + pm_node_list_free(¤t_block_exits); + return (pm_node_t *) pm_class_node_create(parser, &locals, &class_keyword, constant_path, &name, &inheritance_operator, superclass, statements, &parser->previous); } case PM_TOKEN_KEYWORD_DEF: { @@ -16188,13 +17538,10 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b // correctly. It must be pushed before lexing the first param, so it // is here. context_push(parser, PM_CONTEXT_DEF_PARAMS); - pm_constant_id_t saved_param_name; - parser_lex(parser); switch (parser->current.type) { case PM_CASE_OPERATOR: - saved_param_name = pm_parser_current_param_name_unset(parser); pm_parser_scope_push(parser, true); lex_state_set(parser, PM_LEX_STATE_ENDFN); parser_lex(parser); @@ -16208,7 +17555,6 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b receiver = parse_variable_call(parser); receiver = pm_node_check_it(parser, receiver); - saved_param_name = pm_parser_current_param_name_unset(parser); pm_parser_scope_push(parser, true); lex_state_set(parser, PM_LEX_STATE_FNAME); parser_lex(parser); @@ -16216,7 +17562,6 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b operator = parser->previous; name = parse_method_definition_name(parser); } else { - saved_param_name = pm_parser_current_param_name_unset(parser); pm_refute_numbered_parameter(parser, parser->previous.start, parser->previous.end); pm_parser_scope_push(parser, true); @@ -16236,7 +17581,6 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b case PM_TOKEN_KEYWORD___FILE__: case PM_TOKEN_KEYWORD___LINE__: case PM_TOKEN_KEYWORD___ENCODING__: { - saved_param_name = pm_parser_current_param_name_unset(parser); pm_parser_scope_push(parser, true); parser_lex(parser); @@ -16311,18 +17655,14 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b operator = parser->previous; receiver = (pm_node_t *) pm_parentheses_node_create(parser, &lparen, expression, &rparen); - saved_param_name = pm_parser_current_param_name_unset(parser); - pm_parser_scope_push(parser, true); - // To push `PM_CONTEXT_DEF_PARAMS` again is for the same reason as described the above. + pm_parser_scope_push(parser, true); context_push(parser, PM_CONTEXT_DEF_PARAMS); name = parse_method_definition_name(parser); break; } default: - saved_param_name = pm_parser_current_param_name_unset(parser); pm_parser_scope_push(parser, true); - name = parse_method_definition_name(parser); break; } @@ -16393,10 +17733,13 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b pm_node_t *statement = parse_expression(parser, PM_BINDING_POWER_DEFINED + 1, binding_power < PM_BINDING_POWER_COMPOSITION, PM_ERR_DEF_ENDLESS); if (accept1(parser, PM_TOKEN_KEYWORD_RESCUE_MODIFIER)) { + context_push(parser, PM_CONTEXT_RESCUE_MODIFIER); + pm_token_t rescue_keyword = parser->previous; pm_node_t *value = parse_expression(parser, binding_power, false, PM_ERR_RESCUE_MODIFIER_VALUE); - pm_rescue_modifier_node_t *rescue_node = pm_rescue_modifier_node_create(parser, statement, &rescue_keyword, value); - statement = (pm_node_t *)rescue_node; + context_pop(parser); + + statement = (pm_node_t *) pm_rescue_modifier_node_create(parser, statement, &rescue_keyword, value); } pm_statements_node_body_append((pm_statements_node_t *) statements, statement); @@ -16425,7 +17768,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b if (match2(parser, PM_TOKEN_KEYWORD_RESCUE, PM_TOKEN_KEYWORD_ENSURE)) { assert(statements == NULL || PM_NODE_TYPE_P(statements, PM_STATEMENTS_NODE)); - statements = (pm_node_t *) parse_rescues_as_begin(parser, def_keyword.start, (pm_statements_node_t *) statements, true); + statements = (pm_node_t *) parse_rescues_implicit_begin(parser, def_keyword.start, (pm_statements_node_t *) statements, PM_RESCUES_DEF); } pm_accepts_block_stack_pop(parser); @@ -16434,10 +17777,9 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b end_keyword = parser->previous; } - pm_constant_id_list_t locals = parser->current_scope->locals; - + pm_constant_id_list_t locals; + pm_locals_order(parser, &parser->current_scope->locals, &locals, false); pm_parser_scope_pop(parser); - pm_parser_current_param_name_restore(parser, saved_param_name); /** * If the final character is @. As is the case when defining @@ -16469,6 +17811,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b pm_token_t lparen; pm_token_t rparen; pm_node_t *expression; + context_push(parser, PM_CONTEXT_DEFINED); if (accept1(parser, PM_TOKEN_PARENTHESIS_LEFT)) { lparen = parser->previous; @@ -16487,6 +17830,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b expression = parse_expression(parser, PM_BINDING_POWER_DEFINED, false, PM_ERR_DEFINED_EXPRESSION); } + context_pop(parser); return (pm_node_t *) pm_defined_node_create( parser, &lparen, @@ -16627,8 +17971,6 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b arguments.closing_loc = PM_LOCATION_TOKEN_VALUE(&parser->previous); } else { receiver = parse_expression(parser, PM_BINDING_POWER_COMPOSITION, true, PM_ERR_NOT_EXPRESSION); - pm_conditional_predicate(parser, receiver); - pm_predicate_check(parser, receiver); if (!parser->recovering) { accept1(parser, PM_TOKEN_NEWLINE); @@ -16638,8 +17980,6 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b } } else { receiver = parse_expression(parser, PM_BINDING_POWER_NOT, true, PM_ERR_NOT_EXPRESSION); - pm_conditional_predicate(parser, receiver); - pm_predicate_check(parser, receiver); } return (pm_node_t *) pm_call_node_not_create(parser, receiver, &message, &arguments); @@ -16648,15 +17988,21 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b parser_lex(parser); return parse_conditional(parser, PM_CONTEXT_UNLESS); case PM_TOKEN_KEYWORD_MODULE: { - parser_lex(parser); + pm_node_list_t current_block_exits = { 0 }; + pm_node_list_t *previous_block_exits = push_block_exits(parser, ¤t_block_exits); + parser_lex(parser); pm_token_t module_keyword = parser->previous; + pm_node_t *constant_path = parse_expression(parser, PM_BINDING_POWER_INDEX, false, PM_ERR_MODULE_NAME); pm_token_t name; // If we can recover from a syntax error that occurred while parsing // the name of the module, then we'll handle that here. if (PM_NODE_TYPE_P(constant_path, PM_MISSING_NODE)) { + pop_block_exits(parser, previous_block_exits); + pm_node_list_free(¤t_block_exits); + pm_token_t missing = (pm_token_t) { .type = PM_TOKEN_MISSING, .start = parser->previous.end, .end = parser->previous.end }; return (pm_node_t *) pm_module_node_create(parser, NULL, &module_keyword, constant_path, &missing, NULL, &missing); } @@ -16678,9 +18024,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b pm_parser_err_token(parser, &name, PM_ERR_MODULE_NAME); } - pm_constant_id_t saved_param_name = pm_parser_current_param_name_unset(parser); pm_parser_scope_push(parser, true); - accept2(parser, PM_TOKEN_SEMICOLON, PM_TOKEN_NEWLINE); pm_node_t *statements = NULL; @@ -16692,30 +18036,43 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b if (match2(parser, PM_TOKEN_KEYWORD_RESCUE, PM_TOKEN_KEYWORD_ENSURE)) { assert(statements == NULL || PM_NODE_TYPE_P(statements, PM_STATEMENTS_NODE)); - statements = (pm_node_t *) parse_rescues_as_begin(parser, module_keyword.start, (pm_statements_node_t *) statements, false); + statements = (pm_node_t *) parse_rescues_implicit_begin(parser, module_keyword.start, (pm_statements_node_t *) statements, PM_RESCUES_MODULE); } - pm_constant_id_list_t locals = parser->current_scope->locals; - pm_parser_scope_pop(parser); - pm_parser_current_param_name_restore(parser, saved_param_name); + pm_constant_id_list_t locals; + pm_locals_order(parser, &parser->current_scope->locals, &locals, false); + pm_parser_scope_pop(parser); expect1(parser, PM_TOKEN_KEYWORD_END, PM_ERR_MODULE_TERM); if (context_def_p(parser)) { pm_parser_err_token(parser, &module_keyword, PM_ERR_MODULE_IN_METHOD); } + pop_block_exits(parser, previous_block_exits); + pm_node_list_free(¤t_block_exits); + return (pm_node_t *) pm_module_node_create(parser, &locals, &module_keyword, constant_path, &name, statements, &parser->previous); } case PM_TOKEN_KEYWORD_NIL: parser_lex(parser); return (pm_node_t *) pm_nil_node_create(parser, &parser->previous); - case PM_TOKEN_KEYWORD_REDO: + case PM_TOKEN_KEYWORD_REDO: { parser_lex(parser); - return (pm_node_t *) pm_redo_node_create(parser, &parser->previous); - case PM_TOKEN_KEYWORD_RETRY: + + pm_node_t *node = (pm_node_t *) pm_redo_node_create(parser, &parser->previous); + if (!parser->parsing_eval) parse_block_exit(parser, node, "redo"); + + return node; + } + case PM_TOKEN_KEYWORD_RETRY: { parser_lex(parser); - return (pm_node_t *) pm_retry_node_create(parser, &parser->previous); + + pm_node_t *node = (pm_node_t *) pm_retry_node_create(parser, &parser->previous); + parse_retry(parser, node); + + return node; + } case PM_TOKEN_KEYWORD_SELF: parser_lex(parser); return (pm_node_t *) pm_self_node_create(parser, &parser->previous); @@ -17036,15 +18393,15 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b // If we hit string content and the current node is // an interpolated string, then we need to append // the string content to the list of child nodes. - pm_interpolated_string_node_append((pm_interpolated_string_node_t *) current, string); + pm_interpolated_string_node_append(parser, (pm_interpolated_string_node_t *) current, string); } else if (PM_NODE_TYPE_P(current, PM_STRING_NODE)) { // If we hit string content and the current node is // a string node, then we need to convert the // current node into an interpolated string and add // the string content to the list of child nodes. pm_interpolated_string_node_t *interpolated = pm_interpolated_string_node_create(parser, &opening, NULL, &closing); - pm_interpolated_string_node_append(interpolated, current); - pm_interpolated_string_node_append(interpolated, string); + pm_interpolated_string_node_append(parser, interpolated, current); + pm_interpolated_string_node_append(parser, interpolated, string); current = (pm_node_t *) interpolated; } else { assert(false && "unreachable"); @@ -17069,7 +18426,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b pm_token_t opening = not_provided(parser); pm_token_t closing = not_provided(parser); pm_interpolated_string_node_t *interpolated = pm_interpolated_string_node_create(parser, &opening, NULL, &closing); - pm_interpolated_string_node_append(interpolated, current); + pm_interpolated_string_node_append(parser, interpolated, current); current = (pm_node_t *) interpolated; } else { // If we hit an embedded variable and the current @@ -17078,7 +18435,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b } pm_node_t *part = parse_string_part(parser); - pm_interpolated_string_node_append((pm_interpolated_string_node_t *) current, part); + pm_interpolated_string_node_append(parser, (pm_interpolated_string_node_t *) current, part); break; } case PM_TOKEN_EMBEXPR_BEGIN: { @@ -17098,7 +18455,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b pm_token_t opening = not_provided(parser); pm_token_t closing = not_provided(parser); pm_interpolated_string_node_t *interpolated = pm_interpolated_string_node_create(parser, &opening, NULL, &closing); - pm_interpolated_string_node_append(interpolated, current); + pm_interpolated_string_node_append(parser, interpolated, current); current = (pm_node_t *) interpolated; } else if (PM_NODE_TYPE_P(current, PM_INTERPOLATED_STRING_NODE)) { // If we hit an embedded expression and the current @@ -17109,7 +18466,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b } pm_node_t *part = parse_string_part(parser); - pm_interpolated_string_node_append((pm_interpolated_string_node_t *) current, part); + pm_interpolated_string_node_append(parser, (pm_interpolated_string_node_t *) current, part); break; } default: @@ -17210,7 +18567,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b expect1(parser, PM_TOKEN_REGEXP_END, PM_ERR_REGEXP_TERM); } - pm_interpolated_regular_expression_node_closing_set(interpolated, &closing); + pm_interpolated_regular_expression_node_closing_set(parser, interpolated, &closing); return (pm_node_t *) interpolated; } case PM_TOKEN_BACKTICK: @@ -17323,8 +18680,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b pm_node_t *receiver = parse_expression(parser, pm_binding_powers[parser->previous.type].right, binding_power < PM_BINDING_POWER_MATCH, PM_ERR_UNARY_RECEIVER); pm_call_node_t *node = pm_call_node_unary_create(parser, &operator, receiver, "!"); - pm_conditional_predicate(parser, receiver); - pm_predicate_check(parser, receiver); + pm_conditional_predicate(parser, receiver, PM_CONDITIONAL_PREDICATE_TYPE_NOT); return (pm_node_t *) node; } case PM_TOKEN_TILDE: { @@ -17354,7 +18710,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b if (accept1(parser, PM_TOKEN_STAR_STAR)) { pm_token_t exponent_operator = parser->previous; pm_node_t *exponent = parse_expression(parser, pm_binding_powers[exponent_operator.type].right, false, PM_ERR_EXPECT_ARGUMENT); - node = (pm_node_t *) pm_call_node_binary_create(parser, node, &exponent_operator, exponent); + node = (pm_node_t *) pm_call_node_binary_create(parser, node, &exponent_operator, exponent, 0); node = (pm_node_t *) pm_call_node_unary_create(parser, &operator, node, "-@"); } else { switch (PM_NODE_TYPE(node)) { @@ -17380,7 +18736,6 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b parser_lex(parser); pm_token_t operator = parser->previous; - pm_constant_id_t saved_param_name = pm_parser_current_param_name_unset(parser); pm_parser_scope_push(parser, false); pm_block_parameters_node_t *block_parameters; @@ -17444,18 +18799,18 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b if (match2(parser, PM_TOKEN_KEYWORD_RESCUE, PM_TOKEN_KEYWORD_ENSURE)) { assert(body == NULL || PM_NODE_TYPE_P(body, PM_STATEMENTS_NODE)); - body = (pm_node_t *) parse_rescues_as_begin(parser, opening.start, (pm_statements_node_t *) body, false); + body = (pm_node_t *) parse_rescues_implicit_begin(parser, opening.start, (pm_statements_node_t *) body, PM_RESCUES_LAMBDA); } expect1(parser, PM_TOKEN_KEYWORD_END, PM_ERR_LAMBDA_TERM_END); } - pm_constant_id_list_t locals = parser->current_scope->locals; + pm_constant_id_list_t locals; + pm_locals_order(parser, &parser->current_scope->locals, &locals, pm_parser_scope_toplevel_p(parser)); pm_node_t *parameters = parse_blocklike_parameters(parser, (pm_node_t *) block_parameters, &operator, &parser->previous); pm_parser_scope_pop(parser); pm_accepts_block_stack_pop(parser); - pm_parser_current_param_name_restore(parser, saved_param_name); return (pm_node_t *) pm_lambda_node_create(parser, &locals, &operator, &opening, &parser->previous, parameters, body); } @@ -17507,15 +18862,29 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b } } -static inline pm_node_t * +/** + * Parse a value that is going to be written to some kind of variable or method + * call. We need to handle this separately because the rescue modifier is + * permitted on the end of the these expressions, which is a deviation from its + * normal binding power. + * + * Note that this will only be called after an operator write, as in &&=, ||=, + * or any of the binary operators that can be written to a variable. + */ +static pm_node_t * parse_assignment_value(pm_parser_t *parser, pm_binding_power_t previous_binding_power, pm_binding_power_t binding_power, bool accepts_command_call, pm_diagnostic_id_t diag_id) { pm_node_t *value = parse_value_expression(parser, binding_power, previous_binding_power == PM_BINDING_POWER_ASSIGNMENT ? accepts_command_call : previous_binding_power < PM_BINDING_POWER_MATCH, diag_id); - // Contradicting binding powers, the right-hand-side value of rthe assignment allows the `rescue` modifier. + // Contradicting binding powers, the right-hand-side value of the assignment + // allows the `rescue` modifier. if (match1(parser, PM_TOKEN_KEYWORD_RESCUE_MODIFIER)) { + context_push(parser, PM_CONTEXT_RESCUE_MODIFIER); + pm_token_t rescue = parser->current; parser_lex(parser); + pm_node_t *right = parse_expression(parser, binding_power, false, PM_ERR_RESCUE_MODIFIER_VALUE); + context_pop(parser); return (pm_node_t *) pm_rescue_modifier_node_create(parser, value, &rescue, right); } @@ -17523,14 +18892,63 @@ parse_assignment_value(pm_parser_t *parser, pm_binding_power_t previous_binding_ return value; } +/** + * When a local variable write node is the value being written in a different + * write, the local variable is considered "used". + */ +static void +parse_assignment_value_local(pm_parser_t *parser, const pm_node_t *node) { + switch (PM_NODE_TYPE(node)) { + case PM_BEGIN_NODE: { + const pm_begin_node_t *cast = (const pm_begin_node_t *) node; + if (cast->statements != NULL) parse_assignment_value_local(parser, (const pm_node_t *) cast->statements); + break; + } + case PM_LOCAL_VARIABLE_WRITE_NODE: { + const pm_local_variable_write_node_t *cast = (const pm_local_variable_write_node_t *) node; + pm_locals_read(&pm_parser_scope_find(parser, cast->depth)->locals, cast->name); + break; + } + case PM_PARENTHESES_NODE: { + const pm_parentheses_node_t *cast = (const pm_parentheses_node_t *) node; + if (cast->body != NULL) parse_assignment_value_local(parser, cast->body); + break; + } + case PM_STATEMENTS_NODE: { + const pm_statements_node_t *cast = (const pm_statements_node_t *) node; + const pm_node_t *statement; + + PM_NODE_LIST_FOREACH(&cast->body, index, statement) { + parse_assignment_value_local(parser, statement); + } + break; + } + default: + break; + } +} -static inline pm_node_t * +/** + * Parse the value (or values, through an implicit array) that is going to be + * written to some kind of variable or method call. We need to handle this + * separately because the rescue modifier is permitted on the end of the these + * expressions, which is a deviation from its normal binding power. + * + * Additionally, if the value is a local variable write node (e.g., a = a = 1), + * the "a" is marked as being used so the parser should not warn on it. + * + * Note that this will only be called after an = operator, as that is the only + * operator that allows multiple values after it. + */ +static pm_node_t * parse_assignment_values(pm_parser_t *parser, pm_binding_power_t previous_binding_power, pm_binding_power_t binding_power, bool accepts_command_call, pm_diagnostic_id_t diag_id) { pm_node_t *value = parse_starred_expression(parser, binding_power, previous_binding_power == PM_BINDING_POWER_ASSIGNMENT ? accepts_command_call : previous_binding_power < PM_BINDING_POWER_MATCH, diag_id); - bool single_value = true; + parse_assignment_value_local(parser, value); + bool single_value = true; if (previous_binding_power == PM_BINDING_POWER_STATEMENT && (PM_NODE_TYPE_P(value, PM_SPLAT_NODE) || match1(parser, PM_TOKEN_COMMA))) { single_value = false; + pm_token_t opening = not_provided(parser); pm_array_node_t *array = pm_array_node_create(parser, &opening); @@ -17539,14 +18957,19 @@ parse_assignment_values(pm_parser_t *parser, pm_binding_power_t previous_binding while (accept1(parser, PM_TOKEN_COMMA)) { pm_node_t *element = parse_starred_expression(parser, binding_power, false, PM_ERR_ARRAY_ELEMENT); + pm_array_node_elements_append(array, element); if (PM_NODE_TYPE_P(element, PM_MISSING_NODE)) break; + + parse_assignment_value_local(parser, element); } } // Contradicting binding powers, the right-hand-side value of the assignment // allows the `rescue` modifier. if ((single_value || (binding_power == (PM_BINDING_POWER_MULTI_ASSIGNMENT + 1))) && match1(parser, PM_TOKEN_KEYWORD_RESCUE_MODIFIER)) { + context_push(parser, PM_CONTEXT_RESCUE_MODIFIER); + pm_token_t rescue = parser->current; parser_lex(parser); @@ -17562,6 +18985,7 @@ parse_assignment_values(pm_parser_t *parser, pm_binding_power_t previous_binding } pm_node_t *right = parse_expression(parser, binding_power, accepts_command_call_inner, PM_ERR_RESCUE_MODIFIER_VALUE); + context_pop(parser); return (pm_node_t *) pm_rescue_modifier_node_create(parser, value, &rescue, right); } @@ -17570,7 +18994,7 @@ parse_assignment_values(pm_parser_t *parser, pm_binding_power_t previous_binding } /** - * Ensures a call node that is about to become a call operator node does not + * Ensure a call node that is about to become a call operator node does not * have arguments or a block attached. If it does, then we'll need to add an * error message and destroy the arguments/block. Ideally we would keep the node * around so that consumers would still have access to it, but we don't have a @@ -17591,18 +19015,32 @@ parse_call_operator_write(pm_parser_t *parser, pm_call_node_t *call_node, const } } +/** + * Returns true if the name of the capture group is a valid local variable that + * can be written to. + */ static bool -name_is_identifier(pm_parser_t *parser, const uint8_t *source, size_t length) { +parse_regular_expression_named_capture(pm_parser_t *parser, const uint8_t *source, size_t length) { if (length == 0) { return false; } + // First ensure that it starts with a valid identifier starting character. size_t width = char_is_identifier_start(parser, source); if (!width) { return false; } - uint8_t *cursor = ((uint8_t *)source) + width; + // Next, ensure that it's not an uppercase character. + if (parser->encoding_changed) { + if (parser->encoding->isupper_char(source, (ptrdiff_t) length)) return false; + } else { + if (pm_encoding_utf_8_isupper_char(source, (ptrdiff_t) length)) return false; + } + + // Next, iterate through all of the bytes of the string to ensure that they + // are all valid identifier characters. + const uint8_t *cursor = source + width; while (cursor < source + length && (width = char_is_identifier(parser, cursor))) { cursor += width; } @@ -17621,7 +19059,7 @@ parse_regular_expression_named_captures(pm_parser_t *parser, const pm_string_t * if (pm_regexp_named_capture_group_names(pm_string_source(content), pm_string_length(content), &named_captures, parser->encoding_changed, parser->encoding) && (named_captures.length > 0)) { // Since we should not create a MatchWriteNode when all capture names - // are invalid, creating a MatchWriteNode is delayed here. + // are invalid, creating a MatchWriteNode is delaid here. pm_match_write_node_t *match = NULL; pm_constant_id_list_t names = { 0 }; @@ -17636,14 +19074,13 @@ parse_regular_expression_named_captures(pm_parser_t *parser, const pm_string_t * // If the name of the capture group isn't a valid identifier, we do // not add it to the local table. - if (!name_is_identifier(parser, source, length)) continue; + if (!parse_regular_expression_named_capture(parser, source, length)) continue; if (content->type == PM_STRING_SHARED) { // If the unescaped string is a slice of the source, then we can // copy the names directly. The pointers will line up. location = (pm_location_t) { .start = source, .end = source + length }; name = pm_parser_constant_id_location(parser, location.start, location.end); - pm_refute_numbered_parameter(parser, source, source + length); } else { // Otherwise, the name is a slice of the malloc-ed owned string, // in which case we need to copy it out into a new string. @@ -17654,11 +19091,6 @@ parse_regular_expression_named_captures(pm_parser_t *parser, const pm_string_t * memcpy(memory, source, length); name = pm_parser_constant_id_owned(parser, (uint8_t *) memory, length); - - if (pm_token_is_numbered_parameter(source, source + length)) { - const pm_location_t *location = &call->receiver->location; - PM_PARSER_ERR_LOCATION_FORMAT(parser, location, PM_ERR_PARAMETER_NUMBERED_RESERVED, location->start); - } } if (name != 0) { @@ -17667,19 +19099,22 @@ parse_regular_expression_named_captures(pm_parser_t *parser, const pm_string_t * if (pm_constant_id_list_includes(&names, name)) continue; pm_constant_id_list_append(&names, name); - // Here we lazily create the MatchWriteNode since we know we're - // about to add a target. - if (match == NULL) match = pm_match_write_node_create(parser, call); - - // First, find the depth of the local that is being assigned. int depth; if ((depth = pm_parser_local_depth_constant_id(parser, name)) == -1) { - pm_parser_local_add(parser, name); + // If the identifier is not already a local, then we'll add + // it to the local table unless it's a keyword. + if (pm_local_is_keyword((const char *) source, length)) continue; + + pm_parser_local_add(parser, name, location.start, location.end, 0); } + // Here we lazily create the MatchWriteNode since we know we're + // about to add a target. + if (match == NULL) match = pm_match_write_node_create(parser, call); + // Next, create the local variable target and add it to the // list of targets for the match. - pm_node_t *target = (pm_node_t *) pm_local_variable_target_node_create_values(parser, &location, name, depth == -1 ? 0 : (uint32_t) depth); + pm_node_t *target = (pm_node_t *) pm_local_variable_target_node_create(parser, &location, name, depth == -1 ? 0 : (uint32_t) depth); pm_node_list_append(&match->targets, target); } } @@ -17712,8 +19147,8 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t // local variable write. This _must_ happen before the value // is parsed because it could be referenced in the value. pm_call_node_t *call_node = (pm_call_node_t *) node; - if (pm_call_node_variable_call_p(call_node)) { - pm_parser_local_add_location(parser, call_node->message_loc.start, call_node->message_loc.end); + if (PM_NODE_FLAG_P(call_node, PM_CALL_NODE_FLAGS_VARIABLE_CALL)) { + pm_parser_local_add_location(parser, call_node->message_loc.start, call_node->message_loc.end, 0); } } /* fallthrough */ @@ -17768,16 +19203,18 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t parser_lex(parser); pm_node_t *value = parse_assignment_value(parser, previous_binding_power, binding_power, accepts_command_call, PM_ERR_EXPECT_EXPRESSION_AFTER_AMPAMPEQ); - return (pm_node_t *) pm_constant_path_and_write_node_create(parser, (pm_constant_path_node_t *) node, &token, value); + pm_node_t *write = (pm_node_t *) pm_constant_path_and_write_node_create(parser, (pm_constant_path_node_t *) node, &token, value); + + return parse_shareable_constant_write(parser, write); } case PM_CONSTANT_READ_NODE: { parser_lex(parser); pm_node_t *value = parse_assignment_value(parser, previous_binding_power, binding_power, accepts_command_call, PM_ERR_EXPECT_EXPRESSION_AFTER_AMPAMPEQ); - pm_node_t *result = (pm_node_t *) pm_constant_and_write_node_create(parser, (pm_constant_read_node_t *) node, &token, value); + pm_node_t *write = (pm_node_t *) pm_constant_and_write_node_create(parser, (pm_constant_read_node_t *) node, &token, value); pm_node_destroy(parser, node); - return result; + return parse_shareable_constant_write(parser, write); } case PM_INSTANCE_VARIABLE_READ_NODE: { parser_lex(parser); @@ -17805,11 +19242,11 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t // If we have a vcall (a method with no arguments and no // receiver that could have been a local variable) then we // will transform it into a local variable write. - if (pm_call_node_variable_call_p(cast)) { + if (PM_NODE_FLAG_P(cast, PM_CALL_NODE_FLAGS_VARIABLE_CALL)) { pm_location_t *message_loc = &cast->message_loc; pm_refute_numbered_parameter(parser, message_loc->start, message_loc->end); - pm_constant_id_t constant_id = pm_parser_local_add_location(parser, message_loc->start, message_loc->end); + pm_constant_id_t constant_id = pm_parser_local_add_location(parser, message_loc->start, message_loc->end, 1); pm_node_t *value = parse_assignment_value(parser, previous_binding_power, binding_power, accepts_command_call, PM_ERR_EXPECT_EXPRESSION_AFTER_AMPAMPEQ); pm_node_t *result = (pm_node_t *) pm_local_variable_and_write_node_create(parser, (pm_node_t *) cast, &token, value, constant_id, 0); @@ -17820,7 +19257,7 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t // If there is no call operator and the message is "[]" then // this is an aref expression, and we can transform it into // an aset expression. - if (pm_call_node_index_p(cast)) { + if (PM_NODE_FLAG_P(cast, PM_CALL_NODE_FLAGS_INDEX)) { pm_node_t *value = parse_assignment_value(parser, previous_binding_power, binding_power, accepts_command_call, PM_ERR_EXPECT_EXPRESSION_AFTER_AMPAMPEQ); return (pm_node_t *) pm_index_and_write_node_create(parser, cast, &token, value); } @@ -17879,16 +19316,18 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t parser_lex(parser); pm_node_t *value = parse_assignment_value(parser, previous_binding_power, binding_power, accepts_command_call, PM_ERR_EXPECT_EXPRESSION_AFTER_PIPEPIPEEQ); - return (pm_node_t *) pm_constant_path_or_write_node_create(parser, (pm_constant_path_node_t *) node, &token, value); + pm_node_t *write = (pm_node_t *) pm_constant_path_or_write_node_create(parser, (pm_constant_path_node_t *) node, &token, value); + + return parse_shareable_constant_write(parser, write); } case PM_CONSTANT_READ_NODE: { parser_lex(parser); pm_node_t *value = parse_assignment_value(parser, previous_binding_power, binding_power, accepts_command_call, PM_ERR_EXPECT_EXPRESSION_AFTER_PIPEPIPEEQ); - pm_node_t *result = (pm_node_t *) pm_constant_or_write_node_create(parser, (pm_constant_read_node_t *) node, &token, value); + pm_node_t *write = (pm_node_t *) pm_constant_or_write_node_create(parser, (pm_constant_read_node_t *) node, &token, value); pm_node_destroy(parser, node); - return result; + return parse_shareable_constant_write(parser, write); } case PM_INSTANCE_VARIABLE_READ_NODE: { parser_lex(parser); @@ -17916,11 +19355,11 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t // If we have a vcall (a method with no arguments and no // receiver that could have been a local variable) then we // will transform it into a local variable write. - if (pm_call_node_variable_call_p(cast)) { + if (PM_NODE_FLAG_P(cast, PM_CALL_NODE_FLAGS_VARIABLE_CALL)) { pm_location_t *message_loc = &cast->message_loc; pm_refute_numbered_parameter(parser, message_loc->start, message_loc->end); - pm_constant_id_t constant_id = pm_parser_local_add_location(parser, message_loc->start, message_loc->end); + pm_constant_id_t constant_id = pm_parser_local_add_location(parser, message_loc->start, message_loc->end, 1); pm_node_t *value = parse_assignment_value(parser, previous_binding_power, binding_power, accepts_command_call, PM_ERR_EXPECT_EXPRESSION_AFTER_PIPEPIPEEQ); pm_node_t *result = (pm_node_t *) pm_local_variable_or_write_node_create(parser, (pm_node_t *) cast, &token, value, constant_id, 0); @@ -17931,7 +19370,7 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t // If there is no call operator and the message is "[]" then // this is an aref expression, and we can transform it into // an aset expression. - if (pm_call_node_index_p(cast)) { + if (PM_NODE_FLAG_P(cast, PM_CALL_NODE_FLAGS_INDEX)) { pm_node_t *value = parse_assignment_value(parser, previous_binding_power, binding_power, accepts_command_call, PM_ERR_EXPECT_EXPRESSION_AFTER_PIPEPIPEEQ); return (pm_node_t *) pm_index_or_write_node_create(parser, cast, &token, value); } @@ -18000,16 +19439,18 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t parser_lex(parser); pm_node_t *value = parse_assignment_value(parser, previous_binding_power, binding_power, accepts_command_call, PM_ERR_EXPECT_EXPRESSION_AFTER_OPERATOR); - return (pm_node_t *) pm_constant_path_operator_write_node_create(parser, (pm_constant_path_node_t *) node, &token, value); + pm_node_t *write = (pm_node_t *) pm_constant_path_operator_write_node_create(parser, (pm_constant_path_node_t *) node, &token, value); + + return parse_shareable_constant_write(parser, write); } case PM_CONSTANT_READ_NODE: { parser_lex(parser); pm_node_t *value = parse_assignment_value(parser, previous_binding_power, binding_power, accepts_command_call, PM_ERR_EXPECT_EXPRESSION_AFTER_OPERATOR); - pm_node_t *result = (pm_node_t *) pm_constant_operator_write_node_create(parser, (pm_constant_read_node_t *) node, &token, value); + pm_node_t *write = (pm_node_t *) pm_constant_operator_write_node_create(parser, (pm_constant_read_node_t *) node, &token, value); pm_node_destroy(parser, node); - return result; + return parse_shareable_constant_write(parser, write); } case PM_INSTANCE_VARIABLE_READ_NODE: { parser_lex(parser); @@ -18037,11 +19478,11 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t // If we have a vcall (a method with no arguments and no // receiver that could have been a local variable) then we // will transform it into a local variable write. - if (pm_call_node_variable_call_p(cast)) { + if (PM_NODE_FLAG_P(cast, PM_CALL_NODE_FLAGS_VARIABLE_CALL)) { pm_location_t *message_loc = &cast->message_loc; pm_refute_numbered_parameter(parser, message_loc->start, message_loc->end); - pm_constant_id_t constant_id = pm_parser_local_add_location(parser, message_loc->start, message_loc->end); + pm_constant_id_t constant_id = pm_parser_local_add_location(parser, message_loc->start, message_loc->end, 1); pm_node_t *value = parse_assignment_value(parser, previous_binding_power, binding_power, accepts_command_call, PM_ERR_EXPECT_EXPRESSION_AFTER_OPERATOR); pm_node_t *result = (pm_node_t *) pm_local_variable_operator_write_node_create(parser, (pm_node_t *) cast, &token, value, constant_id, 0); @@ -18052,7 +19493,7 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t // If there is no call operator and the message is "[]" then // this is an aref expression, and we can transform it into // an aset expression. - if (pm_call_node_index_p(cast)) { + if (PM_NODE_FLAG_P(cast, PM_CALL_NODE_FLAGS_INDEX)) { pm_node_t *value = parse_assignment_value(parser, previous_binding_power, binding_power, accepts_command_call, PM_ERR_EXPECT_EXPRESSION_AFTER_OPERATOR); return (pm_node_t *) pm_index_operator_write_node_create(parser, cast, &token, value); } @@ -18109,7 +19550,7 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t pm_node_t *argument = parse_expression(parser, binding_power, false, PM_ERR_EXPECT_EXPRESSION_AFTER_OPERATOR); // By default, we're going to create a call node and then return it. - pm_call_node_t *call = pm_call_node_binary_create(parser, node, &token, argument); + pm_call_node_t *call = pm_call_node_binary_create(parser, node, &token, argument, 0); pm_node_t *result = (pm_node_t *) call; // If the receiver of this =~ is a regular expression node, then we @@ -18125,9 +19566,8 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t bool interpolated = false; size_t total_length = 0; - for (size_t index = 0; index < parts->size; index++) { - pm_node_t *part = parts->nodes[index]; - + pm_node_t *part; + PM_NODE_LIST_FOREACH(parts, index, part) { if (PM_NODE_TYPE_P(part, PM_STRING_NODE)) { total_length += pm_string_length(&((pm_string_node_t *) part)->unescaped); } else { @@ -18141,8 +19581,8 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t if (!memory) abort(); uint8_t *cursor = memory; - for (size_t index = 0; index < parts->size; index++) { - pm_string_t *unescaped = &((pm_string_node_t *) parts->nodes[index])->unescaped; + PM_NODE_LIST_FOREACH(parts, index, part) { + pm_string_t *unescaped = &((pm_string_node_t *) part)->unescaped; size_t length = pm_string_length(unescaped); memcpy(cursor, pm_string_source(unescaped), length); @@ -18174,10 +19614,6 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t case PM_TOKEN_EQUAL_EQUAL: case PM_TOKEN_EQUAL_EQUAL_EQUAL: case PM_TOKEN_LESS_EQUAL_GREATER: - case PM_TOKEN_GREATER: - case PM_TOKEN_GREATER_EQUAL: - case PM_TOKEN_LESS: - case PM_TOKEN_LESS_EQUAL: case PM_TOKEN_CARET: case PM_TOKEN_PIPE: case PM_TOKEN_AMPERSAND: @@ -18190,9 +19626,20 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t case PM_TOKEN_STAR: case PM_TOKEN_STAR_STAR: { parser_lex(parser); + pm_node_t *argument = parse_expression(parser, binding_power, false, PM_ERR_EXPECT_EXPRESSION_AFTER_OPERATOR); + return (pm_node_t *) pm_call_node_binary_create(parser, node, &token, argument, 0); + } + case PM_TOKEN_GREATER: + case PM_TOKEN_GREATER_EQUAL: + case PM_TOKEN_LESS: + case PM_TOKEN_LESS_EQUAL: { + if (PM_NODE_TYPE_P(node, PM_CALL_NODE) && PM_NODE_FLAG_P(node, PM_CALL_NODE_FLAGS_COMPARISON)) { + PM_PARSER_WARN_TOKEN_FORMAT_CONTENT(parser, parser->current, PM_WARN_COMPARISON_AFTER_COMPARISON); + } + parser_lex(parser); pm_node_t *argument = parse_expression(parser, binding_power, false, PM_ERR_EXPECT_EXPRESSION_AFTER_OPERATOR); - return (pm_node_t *) pm_call_node_binary_create(parser, node, &token, argument); + return (pm_node_t *) pm_call_node_binary_create(parser, node, &token, argument, PM_CALL_NODE_FLAGS_COMPARISON); } case PM_TOKEN_AMPERSAND_DOT: case PM_TOKEN_DOT: { @@ -18280,8 +19727,13 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t return (pm_node_t *) pm_while_node_modifier_create(parser, &token, predicate, statements, PM_NODE_TYPE_P(node, PM_BEGIN_NODE) ? PM_LOOP_FLAGS_BEGIN_MODIFIER : 0); } case PM_TOKEN_QUESTION_MARK: { + context_push(parser, PM_CONTEXT_TERNARY); + pm_node_list_t current_block_exits = { 0 }; + pm_node_list_t *previous_block_exits = push_block_exits(parser, ¤t_block_exits); + pm_token_t qmark = parser->current; parser_lex(parser); + pm_node_t *true_expression = parse_expression(parser, PM_BINDING_POWER_DEFINED, false, PM_ERR_TERNARY_EXPRESSION_TRUE); if (parser->recovering) { @@ -18294,6 +19746,10 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t pm_token_t colon = (pm_token_t) { .type = PM_TOKEN_MISSING, .start = parser->previous.end, .end = parser->previous.end }; pm_node_t *false_expression = (pm_node_t *) pm_missing_node_create(parser, colon.start, colon.end); + context_pop(parser); + pop_block_exits(parser, previous_block_exits); + pm_node_list_free(¤t_block_exits); + return (pm_node_t *) pm_if_node_ternary_create(parser, node, &qmark, true_expression, &colon, false_expression); } @@ -18303,6 +19759,10 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t pm_token_t colon = parser->previous; pm_node_t *false_expression = parse_expression(parser, PM_BINDING_POWER_DEFINED, false, PM_ERR_TERNARY_EXPRESSION_FALSE); + context_pop(parser); + pop_block_exits(parser, previous_block_exits); + pm_node_list_free(¤t_block_exits); + return (pm_node_t *) pm_if_node_ternary_create(parser, node, &qmark, true_expression, &colon, false_expression); } case PM_TOKEN_COLON_COLON: { @@ -18378,9 +19838,12 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t } } case PM_TOKEN_KEYWORD_RESCUE_MODIFIER: { + context_push(parser, PM_CONTEXT_RESCUE_MODIFIER); parser_lex(parser); accept1(parser, PM_TOKEN_NEWLINE); + pm_node_t *value = parse_expression(parser, binding_power, true, PM_ERR_RESCUE_MODIFIER_VALUE); + context_pop(parser); return (pm_node_t *) pm_rescue_modifier_node_create(parser, node, &token, value); } @@ -18438,11 +19901,13 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t pm_token_t operator = parser->current; parser->command_start = false; lex_state_set(parser, PM_LEX_STATE_BEG | PM_LEX_STATE_LABEL); - parser_lex(parser); - pm_node_t *pattern = parse_pattern(parser, true, PM_ERR_PATTERN_EXPRESSION_AFTER_IN); + pm_constant_id_list_t captures = { 0 }; + pm_node_t *pattern = parse_pattern(parser, &captures, true, PM_ERR_PATTERN_EXPRESSION_AFTER_IN); + parser->pattern_matching_newlines = previous_pattern_matching_newlines; + pm_constant_id_list_free(&captures); return (pm_node_t *) pm_match_predicate_node_create(parser, node, pattern, &operator); } @@ -18453,11 +19918,13 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t pm_token_t operator = parser->current; parser->command_start = false; lex_state_set(parser, PM_LEX_STATE_BEG | PM_LEX_STATE_LABEL); - parser_lex(parser); - pm_node_t *pattern = parse_pattern(parser, true, PM_ERR_PATTERN_EXPRESSION_AFTER_HROCKET); + pm_constant_id_list_t captures = { 0 }; + pm_node_t *pattern = parse_pattern(parser, &captures, true, PM_ERR_PATTERN_EXPRESSION_AFTER_HROCKET); + parser->pattern_matching_newlines = previous_pattern_matching_newlines; + pm_constant_id_list_free(&captures); return (pm_node_t *) pm_match_required_node_create(parser, node, pattern, &operator); } @@ -18498,8 +19965,8 @@ parse_expression(pm_parser_t *parser, pm_binding_power_t binding_power, bool acc case PM_RANGE_NODE: // Range operators are non-associative, so that it does not // associate with other range operators (i.e. `..1..` should be - // rejected.) For this reason, we check such a case for unary ranges - // here, and if so, it returns the node immediately, + // rejected). For this reason, we check such a case for unary ranges + // here, and if so, it returns the node immediately. if ((((pm_range_node_t *) node)->left == NULL) && pm_binding_powers[parser->current.type].left >= PM_BINDING_POWER_RANGE) { return node; } @@ -18517,6 +19984,7 @@ parse_expression(pm_parser_t *parser, pm_binding_power_t binding_power, bool acc current_binding_powers.binary ) { node = parse_expression_infix(parser, node, binding_power, current_binding_powers.right, accepts_command_call); + if (current_binding_powers.nonassoc) { bool endless_range_p = PM_NODE_TYPE_P(node, PM_RANGE_NODE) && ((pm_range_node_t *) node)->right == NULL; pm_binding_power_t left = endless_range_p ? PM_BINDING_POWER_TERM : current_binding_powers.left; @@ -18530,6 +19998,7 @@ parse_expression(pm_parser_t *parser, pm_binding_power_t binding_power, bool acc break; } } + if (accepts_command_call) { // A command-style method call is only accepted on method chains. // Thus, we check whether the parsed node can continue method chains. @@ -18590,7 +20059,7 @@ parse_expression(pm_parser_t *parser, pm_binding_power_t binding_power, bool acc */ static pm_statements_node_t * wrap_statements(pm_parser_t *parser, pm_statements_node_t *statements) { - if (parser->command_line & PM_OPTIONS_COMMAND_LINE_P) { + if (PM_PARSER_COMMAND_LINE_OPTION_P(parser)) { pm_arguments_node_t *arguments = pm_arguments_node_create(parser); pm_arguments_node_arguments_append( arguments, @@ -18604,8 +20073,8 @@ wrap_statements(pm_parser_t *parser, pm_statements_node_t *statements) { )); } - if (parser->command_line & PM_OPTIONS_COMMAND_LINE_N) { - if (parser->command_line & PM_OPTIONS_COMMAND_LINE_A) { + if (PM_PARSER_COMMAND_LINE_OPTION_N(parser)) { + if (PM_PARSER_COMMAND_LINE_OPTION_A(parser)) { pm_arguments_node_t *arguments = pm_arguments_node_create(parser); pm_arguments_node_arguments_append( arguments, @@ -18630,7 +20099,7 @@ wrap_statements(pm_parser_t *parser, pm_statements_node_t *statements) { (pm_node_t *) pm_global_variable_read_node_synthesized_create(parser, pm_parser_constant_id_constant(parser, "$/", 2)) ); - if (parser->command_line & PM_OPTIONS_COMMAND_LINE_L) { + if (PM_PARSER_COMMAND_LINE_OPTION_L(parser)) { pm_keyword_hash_node_t *keywords = pm_keyword_hash_node_create(parser); pm_keyword_hash_node_elements_append(keywords, (pm_node_t *) pm_assoc_node_create( parser, @@ -18672,7 +20141,9 @@ parse_program(pm_parser_t *parser) { if (!statements) { statements = pm_statements_node_create(parser); } - pm_constant_id_list_t locals = parser->current_scope->locals; + + pm_constant_id_list_t locals; + pm_locals_order(parser, &parser->current_scope->locals, &locals, true); pm_parser_scope_pop(parser); // If this is an empty file, then we're still going to parse all of the @@ -18695,6 +20166,38 @@ parse_program(pm_parser_t *parser) { /* External functions */ /******************************************************************************/ +/** + * A vendored version of strnstr that is used to find a substring within a + * string with a given length. This function is used to search for the Ruby + * engine name within a shebang when the -x option is passed to Ruby. + * + * The only modification that we made here is that we don't do NULL byte checks + * because we know the little parameter will not have a NULL byte and we allow + * the big parameter to have them. + */ +static const char * +pm_strnstr(const char *big, const char *little, size_t big_length) { + size_t little_length = strlen(little); + + for (const char *big_end = big + big_length; big < big_end; big++) { + if (*big == *little && memcmp(big, little, little_length) == 0) return big; + } + + return NULL; +} + +/** + * Potentially warn the user if the shebang that has been found to include + * "ruby" has a carriage return at the end, as that can cause problems on some + * platforms. + */ +static void +pm_parser_warn_shebang_carriage_return(pm_parser_t *parser, const uint8_t *start, size_t length) { + if (length > 2 && start[length - 1] == '\n' && start[length - 2] == '\r') { + pm_parser_warn(parser, start, start + length, PM_WARN_SHEBANG_CARRIAGE_RETURN); + } +} + /** * Initialize a parser with the given start and end pointers. */ @@ -18739,14 +20242,15 @@ pm_parser_init(pm_parser_t *parser, const uint8_t *source, size_t size, const pm .start_line = 1, .explicit_encoding = NULL, .command_line = 0, + .parsing_eval = false, .command_start = true, .recovering = false, .encoding_changed = false, .pattern_matching_newlines = false, .in_keyword_arg = false, - .current_param_name = 0, + .current_block_exits = NULL, .semantic_token_seen = false, - .frozen_string_literal = false, + .frozen_string_literal = PM_OPTIONS_FROZEN_STRING_LITERAL_UNSET, .current_regular_expression_ascii_only = false }; @@ -18789,9 +20293,7 @@ pm_parser_init(pm_parser_t *parser, const uint8_t *source, size_t size, const pm } // frozen_string_literal option - if (options->frozen_string_literal) { - parser->frozen_string_literal = true; - } + parser->frozen_string_literal = options->frozen_string_literal; // command_line option parser->command_line = options->command_line; @@ -18800,6 +20302,8 @@ pm_parser_init(pm_parser_t *parser, const uint8_t *source, size_t size, const pm parser->version = options->version; // scopes option + parser->parsing_eval = options->scopes_count > 0; + for (size_t scope_index = 0; scope_index < options->scopes_count; scope_index++) { const pm_options_scope_t *scope = pm_options_scope_get(options, scope_index); pm_parser_scope_push(parser, scope_index == 0); @@ -18829,16 +20333,81 @@ pm_parser_init(pm_parser_t *parser, const uint8_t *source, size_t size, const pm if (size >= 3 && source[0] == 0xef && source[1] == 0xbb && source[2] == 0xbf) { parser->current.end += 3; parser->encoding_comment_start += 3; + + if (parser->encoding != PM_ENCODING_UTF_8_ENTRY) { + parser->encoding = PM_ENCODING_UTF_8_ENTRY; + if (parser->encoding_changed_callback != NULL) parser->encoding_changed_callback(parser); + } } + // If the -x command line flag is set, or the first shebang of the file does + // not include "ruby", then we'll search for a shebang that does include + // "ruby" and start parsing from there. + bool search_shebang = PM_PARSER_COMMAND_LINE_OPTION_X(parser); + // If the first two bytes of the source are a shebang, then we'll indicate // that the encoding comment is at the end of the shebang. if (peek(parser) == '#' && peek_offset(parser, 1) == '!') { - const uint8_t *encoding_comment_start = next_newline(source, (ptrdiff_t) size); - if (encoding_comment_start) { - parser->encoding_comment_start = encoding_comment_start + 1; + const uint8_t *newline = next_newline(parser->start, parser->end - parser->start); + size_t length = (size_t) ((newline != NULL ? newline : parser->end) - parser->start); + + if (pm_strnstr((const char *) parser->start, "ruby", length) != NULL) { + pm_parser_warn_shebang_carriage_return(parser, parser->start, length); + if (newline != NULL) parser->encoding_comment_start = newline + 1; + search_shebang = false; + } else { + search_shebang = true; } } + + // Here we're going to find the first shebang that includes "ruby" and start + // parsing from there. + if (search_shebang) { + // If a shebang that includes "ruby" is not found, then we're going to a + // a load error to the list of errors on the parser. + bool found_shebang = false; + + // This is going to point to the start of each line as we check it. + // We'll maintain a moving window looking at each line at they come. + const uint8_t *cursor = parser->start; + + // The newline pointer points to the end of the current line that we're + // considering. If it is NULL, then we're at the end of the file. + const uint8_t *newline = next_newline(cursor, parser->end - cursor); + + while (newline != NULL) { + pm_newline_list_append(&parser->newline_list, newline); + + cursor = newline + 1; + newline = next_newline(cursor, parser->end - cursor); + + size_t length = (size_t) ((newline != NULL ? newline : parser->end) - cursor); + if (length > 2 && cursor[0] == '#' && cursor[1] == '!') { + if (parser->newline_list.size == 1) { + pm_parser_warn_shebang_carriage_return(parser, cursor, length); + } + + if (pm_strnstr((const char *) cursor, "ruby", length) != NULL) { + found_shebang = true; + parser->encoding_comment_start = newline + 1; + break; + } + } + } + + if (found_shebang) { + parser->previous = (pm_token_t) { .type = PM_TOKEN_EOF, .start = cursor, .end = cursor }; + parser->current = (pm_token_t) { .type = PM_TOKEN_EOF, .start = cursor, .end = cursor }; + } else { + pm_parser_err(parser, parser->start, parser->start, PM_ERR_SCRIPT_NOT_FOUND); + pm_newline_list_clear(&parser->newline_list); + } + } + + // The encoding comment can start after any amount of inline whitespace, so + // here we'll advance it to the first non-inline-whitespace character so + // that it is ready for future comparisons. + parser->encoding_comment_start += pm_strspn_inline_whitespace(parser->encoding_comment_start, parser->end - parser->encoding_comment_start); } /** @@ -18898,7 +20467,6 @@ pm_parser_free(pm_parser_t *parser) { // assumed that ownership has transferred to the AST. However if we have // scopes while we're freeing the parser, it's likely they came from // eval scopes and we need to free them explicitly here. - pm_constant_id_list_free(&parser->current_scope->locals); pm_parser_scope_pop(parser); } @@ -19008,6 +20576,41 @@ pm_parse_stream(pm_parser_t *parser, pm_buffer_t *buffer, void *stream, pm_parse return node; } +/** + * Parse the source and return true if it parses without errors or warnings. + */ +PRISM_EXPORTED_FUNCTION bool +pm_parse_success_p(const uint8_t *source, size_t size, const char *data) { + pm_options_t options = { 0 }; + pm_options_read(&options, data); + + pm_parser_t parser; + pm_parser_init(&parser, source, size, &options); + + pm_node_t *node = pm_parse(&parser); + pm_node_destroy(&parser, node); + + bool result = parser.error_list.size == 0 && parser.warning_list.size == 0; + pm_parser_free(&parser); + pm_options_free(&options); + + return result; +} + +#undef PM_CASE_KEYWORD +#undef PM_CASE_OPERATOR +#undef PM_CASE_WRITABLE +#undef PM_STRING_EMPTY +#undef PM_LOCATION_NODE_BASE_VALUE +#undef PM_LOCATION_NODE_VALUE +#undef PM_LOCATION_NULL_VALUE +#undef PM_LOCATION_TOKEN_VALUE + +// We optionally support serializing to a binary string. For systems that don't +// want or need this functionality, it can be turned off with the +// PRISM_EXCLUDE_SERIALIZATION define. +#ifndef PRISM_EXCLUDE_SERIALIZATION + static inline void pm_serialize_header(pm_buffer_t *buffer) { pm_buffer_append_string(buffer, "PRISM", 5); @@ -19094,14 +20697,7 @@ pm_serialize_parse_comments(pm_buffer_t *buffer, const uint8_t *source, size_t s pm_options_free(&options); } -#undef PM_CASE_KEYWORD -#undef PM_CASE_OPERATOR -#undef PM_CASE_WRITABLE -#undef PM_STRING_EMPTY -#undef PM_LOCATION_NODE_BASE_VALUE -#undef PM_LOCATION_NODE_VALUE -#undef PM_LOCATION_NULL_VALUE -#undef PM_LOCATION_TOKEN_VALUE +#endif /** An error that is going to be formatted into the output. */ typedef struct { @@ -19138,7 +20734,7 @@ typedef struct { #define PM_COLOR_GRAY "\033[38;5;102m" #define PM_COLOR_RED "\033[1;31m" -#define PM_COLOR_RESET "\033[0m" +#define PM_COLOR_RESET "\033[m" static inline pm_error_t * pm_parser_errors_format_sort(const pm_parser_t *parser, const pm_list_t *error_list, const pm_newline_list_t *newline_list) { @@ -19194,7 +20790,11 @@ pm_parser_errors_format_sort(const pm_parser_t *parser, const pm_list_t *error_l static inline void pm_parser_errors_format_line(const pm_parser_t *parser, const pm_newline_list_t *newline_list, const char *number_prefix, int32_t line, pm_buffer_t *buffer) { - size_t index = (size_t) (line - parser->start_line); + int32_t line_delta = line - parser->start_line; + assert(line_delta >= 0); + + size_t index = (size_t) line_delta; + assert(index < newline_list->size); const uint8_t *start = &parser->start[newline_list->offsets[index]]; const uint8_t *end; @@ -19217,8 +20817,7 @@ pm_parser_errors_format_line(const pm_parser_t *parser, const pm_newline_list_t * Format the errors on the parser into the given buffer. */ PRISM_EXPORTED_FUNCTION void -pm_parser_errors_format(const pm_parser_t *parser, pm_buffer_t *buffer, bool colorize) { - const pm_list_t *error_list = &parser->error_list; +pm_parser_errors_format(const pm_parser_t *parser, const pm_list_t *error_list, pm_buffer_t *buffer, bool colorize, bool inline_messages) { assert(error_list->size != 0); // First, we're going to sort all of the errors by line number using an @@ -19231,9 +20830,17 @@ pm_parser_errors_format(const pm_parser_t *parser, pm_buffer_t *buffer, bool col // Now we're going to determine how we're going to format line numbers and // blank lines based on the maximum number of digits in the line numbers - // that are going to be displayed. + // that are going to be displaid. pm_error_format_t error_format; - int32_t max_line_number = errors[error_list->size - 1].line - start_line; + int32_t first_line_number = errors[0].line; + int32_t last_line_number = errors[error_list->size - 1].line; + + // If we have a maximum line number that is negative, then we're going to + // use the absolute value for comparison but multiple by 10 to additionally + // have a column for the negative sign. + if (first_line_number < 0) first_line_number = (-first_line_number) * 10; + if (last_line_number < 0) last_line_number = (-last_line_number) * 10; + int32_t max_line_number = first_line_number > last_line_number ? first_line_number : last_line_number; if (max_line_number < 10) { if (colorize) { @@ -19315,14 +20922,14 @@ pm_parser_errors_format(const pm_parser_t *parser, pm_buffer_t *buffer, bool col // the source before the error to give some context. We'll be careful not to // display the same line twice in case the errors are close enough in the // source. - int32_t last_line = 0; + int32_t last_line = parser->start_line - 1; const pm_encoding_t *encoding = parser->encoding; for (size_t index = 0; index < error_list->size; index++) { pm_error_t *error = &errors[index]; // Here we determine how many lines of padding of the source to display, - // based on the difference from the last line that was displayed. + // based on the difference from the last line that was displaid. if (error->line - last_line > 1) { if (error->line - last_line > 2) { if ((index != 0) && (error->line - last_line > 3)) { @@ -19341,51 +20948,60 @@ pm_parser_errors_format(const pm_parser_t *parser, pm_buffer_t *buffer, bool col // the line that has the error in it. if ((index == 0) || (error->line != last_line)) { if (colorize) { - pm_buffer_append_string(buffer, PM_COLOR_RED "> " PM_COLOR_RESET, 13); + pm_buffer_append_string(buffer, PM_COLOR_RED "> " PM_COLOR_RESET, 12); } else { pm_buffer_append_string(buffer, "> ", 2); } pm_parser_errors_format_line(parser, newline_list, error_format.number_prefix, error->line, buffer); } + const uint8_t *start = &parser->start[newline_list->offsets[error->line - start_line]]; + if (start == parser->end) pm_buffer_append_byte(buffer, '\n'); + // Now we'll display the actual error message. We'll do this by first // putting the prefix to the line, then a bunch of blank spaces // depending on the column, then as many carets as we need to display // the width of the error, then the error message itself. // // Note that this doesn't take into account the width of the actual - // character when displayed in the terminal. For some east-asian + // character when displaid in the terminal. For some east-asian // languages or emoji, this means it can be thrown off pretty badly. We // will need to solve this eventually. pm_buffer_append_string(buffer, " ", 2); pm_buffer_append_string(buffer, error_format.blank_prefix, error_format.blank_prefix_length); size_t column = 0; - const uint8_t *start = &parser->start[newline_list->offsets[error->line - start_line]]; - while (column < error->column_end) { if (column < error->column_start) { pm_buffer_append_byte(buffer, ' '); - } else if (colorize) { - pm_buffer_append_string(buffer, PM_COLOR_RED "^" PM_COLOR_RESET, 12); } else { - pm_buffer_append_byte(buffer, '^'); + const uint8_t caret = column == error->column_start ? '^' : '~'; + + if (colorize) { + pm_buffer_append_string(buffer, PM_COLOR_RED, 7); + pm_buffer_append_byte(buffer, caret); + pm_buffer_append_string(buffer, PM_COLOR_RESET, 3); + } else { + pm_buffer_append_byte(buffer, caret); + } } size_t char_width = encoding->char_width(start + column, parser->end - (start + column)); column += (char_width == 0 ? 1 : char_width); } - pm_buffer_append_byte(buffer, ' '); + if (inline_messages) { + pm_buffer_append_byte(buffer, ' '); + const char *message = error->error->message; + pm_buffer_append_string(buffer, message, strlen(message)); + } - const char *message = error->error->message; - pm_buffer_append_string(buffer, message, strlen(message)); pm_buffer_append_byte(buffer, '\n'); // Here we determine how many lines of padding to display after the // error, depending on where the next error is in source. last_line = error->line; - int32_t next_line = (index == error_list->size - 1) ? ((int32_t) newline_list->size) : errors[index + 1].line; + int32_t next_line = (index == error_list->size - 1) ? (((int32_t) newline_list->size) + parser->start_line) : errors[index + 1].line; if (next_line - last_line > 1) { pm_buffer_append_string(buffer, " ", 2); diff --git a/prism/prism.h b/prism/prism.h index 5e3919f40b8c94..59067c3021a70a 100644 --- a/prism/prism.h +++ b/prism/prism.h @@ -26,6 +26,7 @@ #include #include +#include #include #include #include @@ -98,6 +99,11 @@ typedef char * (pm_parse_stream_fgets_t)(char *string, int size, void *stream); */ PRISM_EXPORTED_FUNCTION pm_node_t * pm_parse_stream(pm_parser_t *parser, pm_buffer_t *buffer, void *stream, pm_parse_stream_fgets_t *fgets, const pm_options_t *options); +// We optionally support serializing to a binary string. For systems that don't +// want or need this functionality, it can be turned off with the +// PRISM_EXCLUDE_SERIALIZATION define. +#ifndef PRISM_EXCLUDE_SERIALIZATION + /** * Parse and serialize the AST represented by the source that is read out of the * given stream into to the given buffer. @@ -185,6 +191,8 @@ PRISM_EXPORTED_FUNCTION void pm_serialize_lex(pm_buffer_t *buffer, const uint8_t */ PRISM_EXPORTED_FUNCTION void pm_serialize_parse_lex(pm_buffer_t *buffer, const uint8_t *source, size_t size, const char *data); +#endif + /** * Parse the source and return true if it parses without errors or warnings. * @@ -215,10 +223,16 @@ const char * pm_token_type_human(pm_token_type_t token_type); * Format the errors on the parser into the given buffer. * * @param parser The parser to format the errors for. + * @param error_list The list of errors to format. * @param buffer The buffer to format the errors into. * @param colorize Whether or not to colorize the errors with ANSI escape sequences. + * @param inline_messages Whether or not to inline the messages with the source. */ -PRISM_EXPORTED_FUNCTION void pm_parser_errors_format(const pm_parser_t *parser, pm_buffer_t *buffer, bool colorize); +PRISM_EXPORTED_FUNCTION void pm_parser_errors_format(const pm_parser_t *parser, const pm_list_t *error_list, pm_buffer_t *buffer, bool colorize, bool inline_messages); + +// We optionally support dumping to JSON. For systems that don't want or need +// this functionality, it can be turned off with the PRISM_EXCLUDE_JSON define. +#ifndef PRISM_EXCLUDE_JSON /** * Dump JSON to the given buffer. @@ -229,6 +243,8 @@ PRISM_EXPORTED_FUNCTION void pm_parser_errors_format(const pm_parser_t *parser, */ PRISM_EXPORTED_FUNCTION void pm_dump_json(pm_buffer_t *buffer, const pm_parser_t *parser, const pm_node_t *node); +#endif + /** * @mainpage * diff --git a/prism/static_literals.c b/prism/static_literals.c index 81231692f6688a..469bdfd5ea2bd4 100644 --- a/prism/static_literals.c +++ b/prism/static_literals.c @@ -95,13 +95,17 @@ node_hash(const pm_parser_t *parser, const pm_node_t *node) { // Strings hash their value and mix in their flags so that different // encodings are not considered equal. const pm_string_t *value = &((const pm_string_node_t *) node)->unescaped; - return murmur_hash(pm_string_source(value), pm_string_length(value) * sizeof(uint8_t)) ^ murmur_scramble((uint32_t) node->flags); + + pm_node_flags_t flags = node->flags; + flags &= (PM_STRING_FLAGS_FORCED_BINARY_ENCODING | PM_STRING_FLAGS_FORCED_UTF8_ENCODING); + + return murmur_hash(pm_string_source(value), pm_string_length(value) * sizeof(uint8_t)) ^ murmur_scramble((uint32_t) flags); } case PM_SOURCE_FILE_NODE: { // Source files hash their value and mix in their flags so that // different encodings are not considered equal. const pm_string_t *value = &((const pm_source_file_node_t *) node)->filepath; - return murmur_hash(pm_string_source(value), pm_string_length(value) * sizeof(uint8_t)) ^ murmur_scramble((uint32_t) node->flags); + return murmur_hash(pm_string_source(value), pm_string_length(value) * sizeof(uint8_t)); } case PM_REGULAR_EXPRESSION_NODE: { // Regular expressions hash their value and mix in their flags so @@ -316,8 +320,6 @@ pm_compare_regular_expression_nodes(PRISM_ATTRIBUTE_UNUSED const pm_parser_t *pa */ pm_node_t * pm_static_literals_add(const pm_parser_t *parser, pm_static_literals_t *literals, pm_node_t *node) { - if (!PM_NODE_FLAG_P(node, PM_NODE_FLAG_STATIC_LITERAL)) return NULL; - switch (PM_NODE_TYPE(node)) { case PM_INTEGER_NODE: case PM_SOURCE_LINE_NODE: @@ -371,3 +373,180 @@ pm_static_literals_free(pm_static_literals_t *literals) { pm_node_hash_free(&literals->regexp_nodes); pm_node_hash_free(&literals->symbol_nodes); } + +/** + * A helper to determine if the given node is a static literal that is positive. + * This is used for formatting imaginary nodes. + */ +static bool +pm_static_literal_positive_p(const pm_node_t *node) { + switch (PM_NODE_TYPE(node)) { + case PM_FLOAT_NODE: + return ((const pm_float_node_t *) node)->value > 0; + case PM_INTEGER_NODE: + return !((const pm_integer_node_t *) node)->value.negative; + case PM_RATIONAL_NODE: + return pm_static_literal_positive_p(((const pm_rational_node_t *) node)->numeric); + case PM_IMAGINARY_NODE: + return pm_static_literal_positive_p(((const pm_imaginary_node_t *) node)->numeric); + default: + assert(false && "unreachable"); + return false; + } +} + +/** + * Inspect a rational node that wraps a float node. This is going to be a + * poor-man's version of the Ruby `Rational#to_s` method, because we're not + * going to try to reduce the rational by finding the GCD. We'll leave that for + * a future improvement. + */ +static void +pm_rational_inspect(pm_buffer_t *buffer, pm_rational_node_t *node) { + const uint8_t *start = node->base.location.start; + const uint8_t *end = node->base.location.end - 1; // r + + while (start < end && *start == '0') start++; // 0.1 -> .1 + while (end > start && end[-1] == '0') end--; // 1.0 -> 1. + size_t length = (size_t) (end - start); + + const uint8_t *point = memchr(start, '.', length); + assert(point && "should have a decimal point"); + + uint8_t *digits = malloc(length - 1); + if (digits == NULL) return; + + memcpy(digits, start, (unsigned long) (point - start)); + memcpy(digits + (point - start), point + 1, (unsigned long) (end - point - 1)); + + pm_integer_t numerator = { 0 }; + pm_integer_parse(&numerator, PM_INTEGER_BASE_DECIMAL, digits, digits + length - 1); + + pm_buffer_append_byte(buffer, '('); + pm_integer_string(buffer, &numerator); + pm_buffer_append_string(buffer, "/1", 2); + for (size_t index = 0; index < (size_t) (end - point - 1); index++) pm_buffer_append_byte(buffer, '0'); + pm_buffer_append_byte(buffer, ')'); + + pm_integer_free(&numerator); + free(digits); +} + +/** + * Create a string-based representation of the given static literal. + */ +PRISM_EXPORTED_FUNCTION void +pm_static_literal_inspect(pm_buffer_t *buffer, const pm_parser_t *parser, const pm_node_t *node) { + switch (PM_NODE_TYPE(node)) { + case PM_FALSE_NODE: + pm_buffer_append_string(buffer, "false", 5); + break; + case PM_FLOAT_NODE: { + const double value = ((const pm_float_node_t *) node)->value; + + if (isinf(value)) { + if (*node->location.start == '-') { + pm_buffer_append_byte(buffer, '-'); + } + pm_buffer_append_string(buffer, "Infinity", 8); + } else if (value == 0.0) { + if (*node->location.start == '-') { + pm_buffer_append_byte(buffer, '-'); + } + pm_buffer_append_string(buffer, "0.0", 3); + } else { + pm_buffer_append_format(buffer, "%g", value); + + // %g will not insert a .0 for 1e100 (we'll get back 1e+100). So + // we check for the decimal point and add it in here if it's not + // present. + if (pm_buffer_index(buffer, '.') == SIZE_MAX) { + size_t exponent_index = pm_buffer_index(buffer, 'e'); + size_t index = exponent_index == SIZE_MAX ? pm_buffer_length(buffer) : exponent_index; + pm_buffer_insert(buffer, index, ".0", 2); + } + } + + break; + } + case PM_IMAGINARY_NODE: { + const pm_node_t *numeric = ((const pm_imaginary_node_t *) node)->numeric; + pm_buffer_append_string(buffer, "(0", 2); + if (pm_static_literal_positive_p(numeric)) pm_buffer_append_byte(buffer, '+'); + pm_static_literal_inspect(buffer, parser, numeric); + if (PM_NODE_TYPE_P(numeric, PM_RATIONAL_NODE)) pm_buffer_append_byte(buffer, '*'); + pm_buffer_append_string(buffer, "i)", 2); + break; + } + case PM_INTEGER_NODE: + pm_integer_string(buffer, &((const pm_integer_node_t *) node)->value); + break; + case PM_NIL_NODE: + pm_buffer_append_string(buffer, "nil", 3); + break; + case PM_RATIONAL_NODE: { + const pm_node_t *numeric = ((const pm_rational_node_t *) node)->numeric; + + switch (PM_NODE_TYPE(numeric)) { + case PM_INTEGER_NODE: + pm_buffer_append_byte(buffer, '('); + pm_static_literal_inspect(buffer, parser, numeric); + pm_buffer_append_string(buffer, "/1)", 3); + break; + case PM_FLOAT_NODE: + pm_rational_inspect(buffer, (pm_rational_node_t *) node); + break; + default: + assert(false && "unreachable"); + break; + } + + break; + } + case PM_REGULAR_EXPRESSION_NODE: { + const pm_string_t *unescaped = &((const pm_regular_expression_node_t *) node)->unescaped; + pm_buffer_append_byte(buffer, '/'); + pm_buffer_append_source(buffer, pm_string_source(unescaped), pm_string_length(unescaped), PM_BUFFER_ESCAPING_RUBY); + pm_buffer_append_byte(buffer, '/'); + + if (PM_NODE_FLAG_P(node, PM_REGULAR_EXPRESSION_FLAGS_MULTI_LINE)) pm_buffer_append_string(buffer, "m", 1); + if (PM_NODE_FLAG_P(node, PM_REGULAR_EXPRESSION_FLAGS_IGNORE_CASE)) pm_buffer_append_string(buffer, "i", 1); + if (PM_NODE_FLAG_P(node, PM_REGULAR_EXPRESSION_FLAGS_EXTENDED)) pm_buffer_append_string(buffer, "x", 1); + if (PM_NODE_FLAG_P(node, PM_REGULAR_EXPRESSION_FLAGS_ASCII_8BIT)) pm_buffer_append_string(buffer, "n", 1); + + break; + } + case PM_SOURCE_ENCODING_NODE: + pm_buffer_append_format(buffer, "#", parser->encoding->name); + break; + case PM_SOURCE_FILE_NODE: { + const pm_string_t *filepath = &((const pm_source_file_node_t *) node)->filepath; + pm_buffer_append_byte(buffer, '"'); + pm_buffer_append_source(buffer, pm_string_source(filepath), pm_string_length(filepath), PM_BUFFER_ESCAPING_RUBY); + pm_buffer_append_byte(buffer, '"'); + break; + } + case PM_SOURCE_LINE_NODE: + pm_buffer_append_format(buffer, "%d", pm_newline_list_line_column(&parser->newline_list, node->location.start, parser->start_line).line); + break; + case PM_STRING_NODE: { + const pm_string_t *unescaped = &((const pm_string_node_t *) node)->unescaped; + pm_buffer_append_byte(buffer, '"'); + pm_buffer_append_source(buffer, pm_string_source(unescaped), pm_string_length(unescaped), PM_BUFFER_ESCAPING_RUBY); + pm_buffer_append_byte(buffer, '"'); + break; + } + case PM_SYMBOL_NODE: { + const pm_string_t *unescaped = &((const pm_symbol_node_t *) node)->unescaped; + pm_buffer_append_byte(buffer, ':'); + pm_buffer_append_source(buffer, pm_string_source(unescaped), pm_string_length(unescaped), PM_BUFFER_ESCAPING_RUBY); + break; + } + case PM_TRUE_NODE: + pm_buffer_append_string(buffer, "true", 4); + break; + default: + assert(false && "unreachable"); + break; + } +} diff --git a/prism/static_literals.h b/prism/static_literals.h index 2a3d815fa9ef86..dd1f2e7f84c37b 100644 --- a/prism/static_literals.h +++ b/prism/static_literals.h @@ -106,4 +106,13 @@ pm_node_t * pm_static_literals_add(const pm_parser_t *parser, pm_static_literals */ void pm_static_literals_free(pm_static_literals_t *literals); +/** + * Create a string-based representation of the given static literal. + * + * @param buffer The buffer to write the string to. + * @param parser The parser that created the node. + * @param node The node to create a string representation of. + */ +PRISM_EXPORTED_FUNCTION void pm_static_literal_inspect(pm_buffer_t *buffer, const pm_parser_t *parser, const pm_node_t *node); + #endif diff --git a/prism/templates/include/prism/diagnostic.h.erb b/prism/templates/include/prism/diagnostic.h.erb index 53a700d409397d..07bbc8fae79264 100644 --- a/prism/templates/include/prism/diagnostic.h.erb +++ b/prism/templates/include/prism/diagnostic.h.erb @@ -66,11 +66,14 @@ typedef struct { * The levels of errors generated during parsing. */ typedef enum { - /** For errors that cannot be recovered from. */ - PM_ERROR_LEVEL_FATAL = 0, + /** For errors that should raise a syntax error. */ + PM_ERROR_LEVEL_SYNTAX = 0, /** For errors that should raise an argument error. */ - PM_ERROR_LEVEL_ARGUMENT = 1 + PM_ERROR_LEVEL_ARGUMENT = 1, + + /** For errors that should raise a load error. */ + PM_ERROR_LEVEL_LOAD = 2 } pm_error_level_t; /** diff --git a/prism/templates/lib/prism/serialize.rb.erb b/prism/templates/lib/prism/serialize.rb.erb index 88d61ec4c0930c..36d5d5432df696 100644 --- a/prism/templates/lib/prism/serialize.rb.erb +++ b/prism/templates/lib/prism/serialize.rb.erb @@ -1,5 +1,5 @@ require "stringio" -require "prism/polyfill/string" +require_relative "polyfill/string" module Prism # A module responsible for deserializing parse results. @@ -10,7 +10,7 @@ module Prism # The minor version of prism that we are expecting to find in the serialized # strings. - MINOR_VERSION = 24 + MINOR_VERSION = 25 # The patch version of prism that we are expecting to find in the serialized # strings. @@ -131,8 +131,8 @@ module Prism comments = load_comments magic_comments = Array.new(load_varuint) { MagicComment.new(load_location_object, load_location_object) } data_loc = load_optional_location_object - errors = Array.new(load_varuint) { ParseError.new(DIAGNOSTIC_TYPES[io.getbyte], load_embedded_string, load_location_object, load_error_level) } - warnings = Array.new(load_varuint) { ParseWarning.new(DIAGNOSTIC_TYPES[io.getbyte], load_embedded_string, load_location_object, load_warning_level) } + errors = Array.new(load_varuint) { ParseError.new(DIAGNOSTIC_TYPES[load_varuint], load_embedded_string, load_location_object, load_error_level) } + warnings = Array.new(load_varuint) { ParseWarning.new(DIAGNOSTIC_TYPES[load_varuint], load_embedded_string, load_location_object, load_warning_level) } [comments, magic_comments, data_loc, errors, warnings] end @@ -296,9 +296,11 @@ module Prism case level when 0 - :fatal + :syntax when 1 :argument + when 2 + :load else raise "Unknown level: #{level}" end diff --git a/prism/templates/src/diagnostic.c.erb b/prism/templates/src/diagnostic.c.erb index 64e1a1d742d57d..a3aa7902879466 100644 --- a/prism/templates/src/diagnostic.c.erb +++ b/prism/templates/src/diagnostic.c.erb @@ -65,8 +65,9 @@ typedef struct { * * For errors, they are: * - * * `PM_ERROR_LEVEL_FATAL` - The default level for errors. + * * `PM_ERROR_LEVEL_SYNTAX` - Errors that should raise SyntaxError. * * `PM_ERROR_LEVEL_ARGUMENT` - Errors that should raise ArgumentError. + * * `PM_ERROR_LEVEL_LOAD` - Errors that should raise LoadError. * * For warnings, they are: * @@ -75,254 +76,283 @@ typedef struct { */ static const pm_diagnostic_data_t diagnostic_messages[PM_DIAGNOSTIC_ID_MAX] = { // Special error that can be replaced - [PM_ERR_CANNOT_PARSE_EXPRESSION] = { "cannot parse the expression", PM_ERROR_LEVEL_FATAL }, + [PM_ERR_CANNOT_PARSE_EXPRESSION] = { "cannot parse the expression", PM_ERROR_LEVEL_SYNTAX }, // Errors that should raise argument errors [PM_ERR_INVALID_ENCODING_MAGIC_COMMENT] = { "unknown or invalid encoding in the magic comment", PM_ERROR_LEVEL_ARGUMENT }, + // Errors that should raise load errors + [PM_ERR_SCRIPT_NOT_FOUND] = { "no Ruby script found in input", PM_ERROR_LEVEL_LOAD }, + // Errors that should raise syntax errors - [PM_ERR_ALIAS_ARGUMENT] = { "invalid argument being passed to `alias`; expected a bare word, symbol, constant, or global variable", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_AMPAMPEQ_MULTI_ASSIGN] = { "unexpected `&&=` in a multiple assignment", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_ARGUMENT_AFTER_BLOCK] = { "unexpected argument after a block argument", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_ARGUMENT_AFTER_FORWARDING_ELLIPSES] = { "unexpected argument after `...`", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_ARGUMENT_BARE_HASH] = { "unexpected bare hash argument", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_ARGUMENT_BLOCK_FORWARDING] = { "both a block argument and a forwarding argument; only one block is allowed", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_ARGUMENT_BLOCK_MULTI] = { "multiple block arguments; only one block is allowed", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_ARGUMENT_FORMAL_CLASS] = { "invalid formal argument; formal argument cannot be a class variable", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_ARGUMENT_FORMAL_CONSTANT] = { "invalid formal argument; formal argument cannot be a constant", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_ARGUMENT_FORMAL_GLOBAL] = { "invalid formal argument; formal argument cannot be a global variable", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_ARGUMENT_FORMAL_IVAR] = { "invalid formal argument; formal argument cannot be an instance variable", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_ARGUMENT_FORWARDING_UNBOUND] = { "unexpected `...` in an non-parenthesized call", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_ARGUMENT_IN] = { "unexpected `in` keyword in arguments", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_ARGUMENT_NO_FORWARDING_AMP] = { "unexpected `&` when the parent method is not forwarding", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_ARGUMENT_NO_FORWARDING_ELLIPSES] = { "unexpected `...` when the parent method is not forwarding", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_ARGUMENT_NO_FORWARDING_STAR] = { "unexpected `*` when the parent method is not forwarding", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_ARGUMENT_SPLAT_AFTER_ASSOC_SPLAT] = { "unexpected `*` splat argument after a `**` keyword splat argument", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_ARGUMENT_SPLAT_AFTER_SPLAT] = { "unexpected `*` splat argument after a `*` splat argument", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_ARGUMENT_TERM_PAREN] = { "expected a `)` to close the arguments", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_ARGUMENT_UNEXPECTED_BLOCK] = { "unexpected `{` after a method call without parenthesis", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_ARRAY_ELEMENT] = { "expected an element for the array", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_ARRAY_EXPRESSION] = { "expected an expression for the array element", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_ARRAY_EXPRESSION_AFTER_STAR] = { "expected an expression after `*` in the array", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_ARRAY_SEPARATOR] = { "expected a `,` separator for the array elements", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_ARRAY_TERM] = { "expected a `]` to close the array", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_BEGIN_LONELY_ELSE] = { "unexpected `else` in `begin` block; a `rescue` clause must precede `else`", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_BEGIN_TERM] = { "expected an `end` to close the `begin` statement", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_BEGIN_UPCASE_BRACE] = { "expected a `{` after `BEGIN`", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_BEGIN_UPCASE_TERM] = { "expected a `}` to close the `BEGIN` statement", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_BEGIN_UPCASE_TOPLEVEL] = { "BEGIN is permitted only at toplevel", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_BLOCK_PARAM_LOCAL_VARIABLE] = { "expected a local variable name in the block parameters", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_BLOCK_PARAM_PIPE_TERM] = { "expected the block parameters to end with `|`", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_BLOCK_TERM_BRACE] = { "expected a block beginning with `{` to end with `}`", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_BLOCK_TERM_END] = { "expected a block beginning with `do` to end with `end`", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_CANNOT_PARSE_STRING_PART] = { "cannot parse the string part", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_CASE_EXPRESSION_AFTER_CASE] = { "expected an expression after `case`", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_CASE_EXPRESSION_AFTER_WHEN] = { "expected an expression after `when`", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_CASE_MATCH_MISSING_PREDICATE] = { "expected a predicate for a case matching statement", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_CASE_MISSING_CONDITIONS] = { "expected a `when` or `in` clause after `case`", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_CASE_TERM] = { "expected an `end` to close the `case` statement", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_CLASS_IN_METHOD] = { "unexpected class definition in a method definition", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_CLASS_NAME] = { "expected a constant name after `class`", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_CLASS_SUPERCLASS] = { "expected a superclass after `<`", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_CLASS_TERM] = { "expected an `end` to close the `class` statement", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_CLASS_UNEXPECTED_END] = { "unexpected `end`, expecting ';' or '\\n'", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_CONDITIONAL_ELSIF_PREDICATE] = { "expected a predicate expression for the `elsif` statement", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_CONDITIONAL_IF_PREDICATE] = { "expected a predicate expression for the `if` statement", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_CONDITIONAL_PREDICATE_TERM] = { "expected `then` or `;` or '\\n'", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_CONDITIONAL_TERM] = { "expected an `end` to close the conditional clause", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_CONDITIONAL_TERM_ELSE] = { "expected an `end` to close the `else` clause", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_CONDITIONAL_UNLESS_PREDICATE] = { "expected a predicate expression for the `unless` statement", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_CONDITIONAL_UNTIL_PREDICATE] = { "expected a predicate expression for the `until` statement", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_CONDITIONAL_WHILE_PREDICATE] = { "expected a predicate expression for the `while` statement", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_CONSTANT_PATH_COLON_COLON_CONSTANT] = { "expected a constant after the `::` operator", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_DEF_ENDLESS] = { "could not parse the endless method body", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_DEF_ENDLESS_SETTER] = { "invalid method name; a setter method cannot be defined in an endless method definition", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_DEF_NAME] = { "expected a method name", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_DEF_NAME_AFTER_RECEIVER] = { "expected a method name after the receiver", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_DEF_PARAMS_TERM] = { "expected a delimiter to close the parameters", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_DEF_PARAMS_TERM_PAREN] = { "expected a `)` to close the parameters", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_DEF_RECEIVER] = { "expected a receiver for the method definition", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_DEF_RECEIVER_TERM] = { "expected a `.` or `::` after the receiver in a method definition", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_DEF_TERM] = { "expected an `end` to close the `def` statement", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_DEFINED_EXPRESSION] = { "expected an expression after `defined?`", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_EMBDOC_TERM] = { "could not find a terminator for the embedded document", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_EMBEXPR_END] = { "expected a `}` to close the embedded expression", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_EMBVAR_INVALID] = { "invalid embedded variable", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_END_UPCASE_BRACE] = { "expected a `{` after `END`", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_END_UPCASE_TERM] = { "expected a `}` to close the `END` statement", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_ESCAPE_INVALID_CONTROL] = { "invalid control escape sequence", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_ESCAPE_INVALID_CONTROL_REPEAT] = { "invalid control escape sequence; control cannot be repeated", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_ESCAPE_INVALID_HEXADECIMAL] = { "invalid hexadecimal escape sequence", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_ESCAPE_INVALID_META] = { "invalid meta escape sequence", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_ESCAPE_INVALID_META_REPEAT] = { "invalid meta escape sequence; meta cannot be repeated", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_ESCAPE_INVALID_UNICODE] = { "invalid Unicode escape sequence", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_ESCAPE_INVALID_UNICODE_CM_FLAGS] = { "invalid Unicode escape sequence; Unicode cannot be combined with control or meta flags", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_ESCAPE_INVALID_UNICODE_LITERAL] = { "invalid Unicode escape sequence; multiple codepoints are not allowed in a character literal", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_ESCAPE_INVALID_UNICODE_LONG] = { "invalid Unicode escape sequence; maximum length is 6 digits", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_ESCAPE_INVALID_UNICODE_TERM] = { "invalid Unicode escape sequence; needs closing `}`", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_EXPECT_ARGUMENT] = { "expected an argument", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_EXPECT_EOL_AFTER_STATEMENT] = { "unexpected %s, expecting end-of-input", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_EXPECT_EXPRESSION_AFTER_AMPAMPEQ] = { "expected an expression after `&&=`", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_EXPECT_EXPRESSION_AFTER_PIPEPIPEEQ] = { "expected an expression after `||=`", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_EXPECT_EXPRESSION_AFTER_COMMA] = { "expected an expression after `,`", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_EXPECT_EXPRESSION_AFTER_EQUAL] = { "expected an expression after `=`", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_EXPECT_EXPRESSION_AFTER_LESS_LESS] = { "expected an expression after `<<`", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_EXPECT_EXPRESSION_AFTER_LPAREN] = { "expected an expression after `(`", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_EXPECT_EXPRESSION_AFTER_OPERATOR] = { "expected an expression after the operator", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_EXPECT_EXPRESSION_AFTER_SPLAT] = { "expected an expression after `*` splat in an argument", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_EXPECT_EXPRESSION_AFTER_SPLAT_HASH] = { "expected an expression after `**` in a hash", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_EXPECT_EXPRESSION_AFTER_STAR] = { "expected an expression after `*`", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_EXPECT_IDENT_REQ_PARAMETER] = { "expected an identifier for the required parameter", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_EXPECT_LPAREN_REQ_PARAMETER] = { "expected a `(` to start a required parameter", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_EXPECT_RBRACKET] = { "expected a matching `]`", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_EXPECT_RPAREN] = { "expected a matching `)`", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_EXPECT_RPAREN_AFTER_MULTI] = { "expected a `)` after multiple assignment", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_EXPECT_RPAREN_REQ_PARAMETER] = { "expected a `)` to end a required parameter", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_EXPECT_STRING_CONTENT] = { "expected string content after opening string delimiter", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_EXPECT_WHEN_DELIMITER] = { "expected a delimiter after the predicates of a `when` clause", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_EXPRESSION_BARE_HASH] = { "unexpected bare hash in expression", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_FLOAT_PARSE] = { "could not parse the float '%.*s'", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_FOR_COLLECTION] = { "expected a collection after the `in` in a `for` statement", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_FOR_INDEX] = { "expected an index after `for`", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_FOR_IN] = { "expected an `in` after the index in a `for` statement", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_FOR_TERM] = { "expected an `end` to close the `for` loop", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_HASH_EXPRESSION_AFTER_LABEL] = { "expected an expression after the label in a hash", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_HASH_KEY] = { "unexpected %s, expecting '}' or a key in the hash literal", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_HASH_ROCKET] = { "expected a `=>` between the hash key and value", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_HASH_TERM] = { "expected a `}` to close the hash literal", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_HASH_VALUE] = { "expected a value in the hash literal", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_HEREDOC_TERM] = { "could not find a terminator for the heredoc", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_INCOMPLETE_QUESTION_MARK] = { "incomplete expression at `?`", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_INCOMPLETE_VARIABLE_CLASS_3_3_0] = { "`%.*s' is not allowed as a class variable name", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_INCOMPLETE_VARIABLE_CLASS] = { "'%.*s' is not allowed as a class variable name", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_INCOMPLETE_VARIABLE_INSTANCE_3_3_0] = { "`%.*s' is not allowed as an instance variable name", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_INCOMPLETE_VARIABLE_INSTANCE] = { "'%.*s' is not allowed as an instance variable name", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_INVALID_FLOAT_EXPONENT] = { "invalid exponent", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_INVALID_NUMBER_BINARY] = { "invalid binary number", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_INVALID_NUMBER_DECIMAL] = { "invalid decimal number", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_INVALID_NUMBER_HEXADECIMAL] = { "invalid hexadecimal number", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_INVALID_NUMBER_OCTAL] = { "invalid octal number", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_INVALID_NUMBER_UNDERSCORE] = { "invalid underscore placement in number", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_INVALID_CHARACTER] = { "invalid character 0x%X", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_INVALID_MULTIBYTE_CHAR] = { "invalid multibyte char (%s)", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_INVALID_MULTIBYTE_CHARACTER] = { "invalid multibyte character 0x%X", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_INVALID_MULTIBYTE_ESCAPE] = { "invalid multibyte escape: /%.*s/", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_INVALID_PRINTABLE_CHARACTER] = { "invalid character `%c`", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_INVALID_PERCENT] = { "invalid `%` token", PM_ERROR_LEVEL_FATAL }, // TODO WHAT? - [PM_ERR_INVALID_VARIABLE_GLOBAL_3_3_0] = { "`%.*s' is not allowed as a global variable name", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_INVALID_VARIABLE_GLOBAL] = { "'%.*s' is not allowed as a global variable name", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_IT_NOT_ALLOWED_NUMBERED] = { "`it` is not allowed when an numbered parameter is defined", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_IT_NOT_ALLOWED_ORDINARY] = { "`it` is not allowed when an ordinary parameter is defined", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_LAMBDA_OPEN] = { "expected a `do` keyword or a `{` to open the lambda block", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_LAMBDA_TERM_BRACE] = { "expected a lambda block beginning with `{` to end with `}`", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_LAMBDA_TERM_END] = { "expected a lambda block beginning with `do` to end with `end`", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_LIST_I_LOWER_ELEMENT] = { "expected a symbol in a `%i` list", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_LIST_I_LOWER_TERM] = { "expected a closing delimiter for the `%i` list", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_LIST_I_UPPER_ELEMENT] = { "expected a symbol in a `%I` list", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_LIST_I_UPPER_TERM] = { "expected a closing delimiter for the `%I` list", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_LIST_W_LOWER_ELEMENT] = { "expected a string in a `%w` list", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_LIST_W_LOWER_TERM] = { "expected a closing delimiter for the `%w` list", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_LIST_W_UPPER_ELEMENT] = { "expected a string in a `%W` list", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_LIST_W_UPPER_TERM] = { "expected a closing delimiter for the `%W` list", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_MALLOC_FAILED] = { "failed to allocate memory", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_MIXED_ENCODING] = { "UTF-8 mixed within %s source", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_MODULE_IN_METHOD] = { "unexpected module definition in a method definition", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_MODULE_NAME] = { "expected a constant name after `module`", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_MODULE_TERM] = { "expected an `end` to close the `module` statement", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_MULTI_ASSIGN_MULTI_SPLATS] = { "multiple splats in multiple assignment", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_MULTI_ASSIGN_UNEXPECTED_REST] = { "unexpected '%.*s' resulting in multiple splats in multiple assignment", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_NOT_EXPRESSION] = { "expected an expression after `not`", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_NO_LOCAL_VARIABLE] = { "%.*s: no such local variable", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_NUMBER_LITERAL_UNDERSCORE] = { "number literal ending with a `_`", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_NUMBERED_PARAMETER_IT] = { "numbered parameters are not allowed when an 'it' parameter is defined", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_NUMBERED_PARAMETER_ORDINARY] = { "numbered parameters are not allowed when an ordinary parameter is defined", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_NUMBERED_PARAMETER_OUTER_SCOPE] = { "numbered parameter is already used in outer scope", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_OPERATOR_MULTI_ASSIGN] = { "unexpected operator for a multiple assignment", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_OPERATOR_WRITE_ARGUMENTS] = { "unexpected operator after a call with arguments", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_OPERATOR_WRITE_BLOCK] = { "unexpected operator after a call with a block", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_PARAMETER_ASSOC_SPLAT_MULTI] = { "unexpected multiple `**` splat parameters", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_PARAMETER_BLOCK_MULTI] = { "multiple block parameters; only one block is allowed", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_PARAMETER_CIRCULAR] = { "parameter default value references itself", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_PARAMETER_METHOD_NAME] = { "unexpected name for a parameter", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_PARAMETER_NAME_REPEAT] = { "repeated parameter name", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_PARAMETER_NO_DEFAULT] = { "expected a default value for the parameter", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_PARAMETER_NO_DEFAULT_KW] = { "expected a default value for the keyword parameter", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_PARAMETER_NUMBERED_RESERVED] = { "%.2s is reserved for numbered parameters", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_PARAMETER_ORDER] = { "unexpected parameter order", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_PARAMETER_SPLAT_MULTI] = { "unexpected multiple `*` splat parameters", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_PARAMETER_STAR] = { "unexpected parameter `*`", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_PARAMETER_UNEXPECTED_FWD] = { "unexpected `...` in parameters", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_PARAMETER_WILD_LOOSE_COMMA] = { "unexpected `,` in parameters", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_PATTERN_EXPRESSION_AFTER_BRACKET] = { "expected a pattern expression after the `[` operator", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_PATTERN_EXPRESSION_AFTER_COMMA] = { "expected a pattern expression after `,`", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_PATTERN_EXPRESSION_AFTER_HROCKET] = { "expected a pattern expression after `=>`", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_PATTERN_EXPRESSION_AFTER_IN] = { "expected a pattern expression after the `in` keyword", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_PATTERN_EXPRESSION_AFTER_KEY] = { "expected a pattern expression after the key", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_PATTERN_EXPRESSION_AFTER_PAREN] = { "expected a pattern expression after the `(` operator", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_PATTERN_EXPRESSION_AFTER_PIN] = { "expected a pattern expression after the `^` pin operator", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_PATTERN_EXPRESSION_AFTER_PIPE] = { "expected a pattern expression after the `|` operator", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_PATTERN_EXPRESSION_AFTER_RANGE] = { "expected a pattern expression after the range operator", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_PATTERN_EXPRESSION_AFTER_REST] = { "unexpected pattern expression after the `**` expression", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_PATTERN_HASH_KEY] = { "expected a key in the hash pattern", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_PATTERN_HASH_KEY_LABEL] = { "expected a label as the key in the hash pattern", PM_ERROR_LEVEL_FATAL }, // TODO // THIS // AND // ABOVE // IS WEIRD - [PM_ERR_PATTERN_IDENT_AFTER_HROCKET] = { "expected an identifier after the `=>` operator", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_PATTERN_LABEL_AFTER_COMMA] = { "expected a label after the `,` in the hash pattern", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_PATTERN_REST] = { "unexpected rest pattern", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_PATTERN_TERM_BRACE] = { "expected a `}` to close the pattern expression", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_PATTERN_TERM_BRACKET] = { "expected a `]` to close the pattern expression", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_PATTERN_TERM_PAREN] = { "expected a `)` to close the pattern expression", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_PIPEPIPEEQ_MULTI_ASSIGN] = { "unexpected `||=` in a multiple assignment", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_REGEXP_ENCODING_OPTION_MISMATCH] = { "regexp encoding option '%c' differs from source encoding '%s'", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_REGEXP_INCOMPAT_CHAR_ENCODING] = { "incompatible character encoding: /%.*s/", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_REGEXP_NON_ESCAPED_MBC] = { "/.../n has a non escaped non ASCII character in non ASCII-8BIT script: /%.*s/", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_REGEXP_INVALID_UNICODE_RANGE] = { "invalid Unicode range: /%.*s/", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_REGEXP_TERM] = { "expected a closing delimiter for the regular expression", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_REGEXP_UTF8_CHAR_NON_UTF8_REGEXP] = { "UTF-8 character in non UTF-8 regexp: /%s/", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_RESCUE_EXPRESSION] = { "expected a rescued expression", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_RESCUE_MODIFIER_VALUE] = { "expected a value after the `rescue` modifier", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_RESCUE_TERM] = { "expected a closing delimiter for the `rescue` clause", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_RESCUE_VARIABLE] = { "expected an exception variable after `=>` in a rescue statement", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_RETURN_INVALID] = { "invalid `return` in a class or module body", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_SINGLETON_FOR_LITERALS] = { "cannot define singleton method for literals", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_STATEMENT_ALIAS] = { "unexpected an `alias` at a non-statement position", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_STATEMENT_POSTEXE_END] = { "unexpected an `END` at a non-statement position", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_STATEMENT_PREEXE_BEGIN] = { "unexpected a `BEGIN` at a non-statement position", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_STATEMENT_UNDEF] = { "unexpected an `undef` at a non-statement position", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_STRING_CONCATENATION] = { "expected a string for concatenation", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_STRING_INTERPOLATED_TERM] = { "expected a closing delimiter for the interpolated string", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_STRING_LITERAL_EOF] = { "unterminated string meets end of file", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_STRING_LITERAL_TERM] = { "unexpected %s, expected a string literal terminator", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_SYMBOL_INVALID] = { "invalid symbol", PM_ERROR_LEVEL_FATAL }, // TODO expected symbol? prism.c ~9719 - [PM_ERR_SYMBOL_TERM_DYNAMIC] = { "expected a closing delimiter for the dynamic symbol", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_SYMBOL_TERM_INTERPOLATED] = { "expected a closing delimiter for the interpolated symbol", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_TERNARY_COLON] = { "expected a `:` after the true expression of a ternary operator", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_TERNARY_EXPRESSION_FALSE] = { "expected an expression after `:` in the ternary operator", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_TERNARY_EXPRESSION_TRUE] = { "expected an expression after `?` in the ternary operator", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_UNDEF_ARGUMENT] = { "invalid argument being passed to `undef`; expected a bare word, constant, or symbol argument", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_UNARY_RECEIVER] = { "unexpected %s, expected a receiver for unary `%c`", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_UNEXPECTED_TOKEN_CLOSE_CONTEXT] = { "unexpected %s, assuming it is closing the parent %s", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_UNEXPECTED_TOKEN_IGNORE] = { "unexpected %s, ignoring it", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_UNTIL_TERM] = { "expected an `end` to close the `until` statement", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_VOID_EXPRESSION] = { "unexpected void value expression", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_WHILE_TERM] = { "expected an `end` to close the `while` statement", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_WRITE_TARGET_IN_METHOD] = { "dynamic constant assignment", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_WRITE_TARGET_READONLY] = { "Can't set variable %.*s", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_WRITE_TARGET_UNEXPECTED] = { "unexpected write target", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_XSTRING_TERM] = { "expected a closing delimiter for the `%x` or backtick string", PM_ERROR_LEVEL_FATAL }, + [PM_ERR_ALIAS_ARGUMENT] = { "invalid argument being passed to `alias`; expected a bare word, symbol, constant, or global variable", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_ALIAS_ARGUMENT_NUMBERED_REFERENCE] = { "invalid argument being passed to `alias`; can't make alias for the number variables", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_AMPAMPEQ_MULTI_ASSIGN] = { "unexpected `&&=` in a multiple assignment", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_ARGUMENT_AFTER_BLOCK] = { "unexpected argument after a block argument", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_ARGUMENT_AFTER_FORWARDING_ELLIPSES] = { "unexpected argument after `...`", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_ARGUMENT_BARE_HASH] = { "unexpected bare hash argument", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_ARGUMENT_BLOCK_FORWARDING] = { "both a block argument and a forwarding argument; only one block is allowed", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_ARGUMENT_BLOCK_MULTI] = { "both block arg and actual block given; only one block is allowed", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_ARGUMENT_FORMAL_CLASS] = { "invalid formal argument; formal argument cannot be a class variable", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_ARGUMENT_FORMAL_CONSTANT] = { "invalid formal argument; formal argument cannot be a constant", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_ARGUMENT_FORMAL_GLOBAL] = { "invalid formal argument; formal argument cannot be a global variable", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_ARGUMENT_FORMAL_IVAR] = { "invalid formal argument; formal argument cannot be an instance variable", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_ARGUMENT_FORWARDING_UNBOUND] = { "unexpected `...` in an non-parenthesized call", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_ARGUMENT_IN] = { "unexpected `in` keyword in arguments", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_ARGUMENT_NO_FORWARDING_AMP] = { "unexpected `&` when the parent method is not forwarding", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_ARGUMENT_NO_FORWARDING_ELLIPSES] = { "unexpected ... when the parent method is not forwarding", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_ARGUMENT_NO_FORWARDING_STAR] = { "unexpected `*`; no anonymous rest parameter", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_ARGUMENT_NO_FORWARDING_STAR_STAR] = { "unexpected `**`; no anonymous keyword rest parameter", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_ARGUMENT_SPLAT_AFTER_ASSOC_SPLAT] = { "unexpected `*` splat argument after a `**` keyword splat argument", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_ARGUMENT_SPLAT_AFTER_SPLAT] = { "unexpected `*` splat argument after a `*` splat argument", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_ARGUMENT_TERM_PAREN] = { "expected a `)` to close the arguments", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_ARGUMENT_UNEXPECTED_BLOCK] = { "unexpected `{` after a method call without parenthesis", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_ARRAY_ELEMENT] = { "expected an element for the array", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_ARRAY_EXPRESSION] = { "expected an expression for the array element", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_ARRAY_EXPRESSION_AFTER_STAR] = { "expected an expression after `*` in the array", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_ARRAY_SEPARATOR] = { "expected a `,` separator for the array elements", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_ARRAY_TERM] = { "expected a `]` to close the array", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_BEGIN_LONELY_ELSE] = { "unexpected `else` in `begin` block; else without rescue is useless", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_BEGIN_TERM] = { "expected an `end` to close the `begin` statement", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_BEGIN_UPCASE_BRACE] = { "expected a `{` after `BEGIN`", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_BEGIN_UPCASE_TERM] = { "expected a `}` to close the `BEGIN` statement", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_BEGIN_UPCASE_TOPLEVEL] = { "BEGIN is permitted only at toplevel", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_BLOCK_PARAM_LOCAL_VARIABLE] = { "expected a local variable name in the block parameters", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_BLOCK_PARAM_PIPE_TERM] = { "expected the block parameters to end with `|`", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_BLOCK_TERM_BRACE] = { "expected a block beginning with `{` to end with `}`", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_BLOCK_TERM_END] = { "expected a block beginning with `do` to end with `end`", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_CANNOT_PARSE_STRING_PART] = { "cannot parse the string part", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_CASE_EXPRESSION_AFTER_CASE] = { "expected an expression after `case`", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_CASE_EXPRESSION_AFTER_WHEN] = { "expected an expression after `when`", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_CASE_MATCH_MISSING_PREDICATE] = { "expected a predicate for a case matching statement", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_CASE_MISSING_CONDITIONS] = { "expected a `when` or `in` clause after `case`", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_CASE_TERM] = { "expected an `end` to close the `case` statement", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_CLASS_IN_METHOD] = { "unexpected class definition in method body", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_CLASS_NAME] = { "unexpected constant path after `class`; class/module name must be CONSTANT", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_CLASS_SUPERCLASS] = { "expected a superclass after `<`", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_CLASS_TERM] = { "expected an `end` to close the `class` statement", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_CLASS_UNEXPECTED_END] = { "unexpected `end`, expecting ';' or '\\n'", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_CLASS_VARIABLE_BARE] = { "'@@' without identifiers is not allowed as a class variable name", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_CONDITIONAL_ELSIF_PREDICATE] = { "expected a predicate expression for the `elsif` statement", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_CONDITIONAL_IF_PREDICATE] = { "expected a predicate expression for the `if` statement", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_CONDITIONAL_PREDICATE_TERM] = { "expected `then` or `;` or '\\n'", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_CONDITIONAL_TERM] = { "expected an `end` to close the conditional clause", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_CONDITIONAL_TERM_ELSE] = { "expected an `end` to close the `else` clause", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_CONDITIONAL_UNLESS_PREDICATE] = { "expected a predicate expression for the `unless` statement", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_CONDITIONAL_UNTIL_PREDICATE] = { "expected a predicate expression for the `until` statement", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_CONDITIONAL_WHILE_PREDICATE] = { "expected a predicate expression for the `while` statement", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_CONSTANT_PATH_COLON_COLON_CONSTANT] = { "expected a constant after the `::` operator", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_DEF_ENDLESS] = { "could not parse the endless method body", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_DEF_ENDLESS_SETTER] = { "invalid method name; a setter method cannot be defined in an endless method definition", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_DEF_NAME] = { "expected a method name", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_DEF_NAME_AFTER_RECEIVER] = { "expected a method name after the receiver", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_DEF_PARAMS_TERM] = { "expected a delimiter to close the parameters", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_DEF_PARAMS_TERM_PAREN] = { "expected a `)` to close the parameters", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_DEF_RECEIVER] = { "expected a receiver for the method definition", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_DEF_RECEIVER_TERM] = { "expected a `.` or `::` after the receiver in a method definition", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_DEF_TERM] = { "expected an `end` to close the `def` statement", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_DEFINED_EXPRESSION] = { "expected an expression after `defined?`", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_EMBDOC_TERM] = { "could not find a terminator for the embedded document", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_EMBEXPR_END] = { "expected a `}` to close the embedded expression", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_EMBVAR_INVALID] = { "invalid embedded variable", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_END_UPCASE_BRACE] = { "expected a `{` after `END`", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_END_UPCASE_TERM] = { "expected a `}` to close the `END` statement", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_ESCAPE_INVALID_CONTROL] = { "invalid control escape sequence", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_ESCAPE_INVALID_CONTROL_REPEAT] = { "invalid control escape sequence; control cannot be repeated", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_ESCAPE_INVALID_HEXADECIMAL] = { "invalid hexadecimal escape sequence", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_ESCAPE_INVALID_META] = { "invalid meta escape sequence", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_ESCAPE_INVALID_META_REPEAT] = { "invalid meta escape sequence; meta cannot be repeated", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_ESCAPE_INVALID_UNICODE] = { "invalid Unicode escape sequence", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_ESCAPE_INVALID_UNICODE_CM_FLAGS] = { "invalid Unicode escape sequence; Unicode cannot be combined with control or meta flags", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_ESCAPE_INVALID_UNICODE_LITERAL] = { "invalid Unicode escape sequence; multiple codepoints are not allowed in a character literal", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_ESCAPE_INVALID_UNICODE_LONG] = { "invalid Unicode escape sequence; maximum length is 6 digits", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_ESCAPE_INVALID_UNICODE_TERM] = { "invalid Unicode escape sequence; needs closing `}`", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_EXPECT_ARGUMENT] = { "expected an argument", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_EXPECT_EOL_AFTER_STATEMENT] = { "unexpected %s, expecting end-of-input", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_EXPECT_EXPRESSION_AFTER_AMPAMPEQ] = { "expected an expression after `&&=`", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_EXPECT_EXPRESSION_AFTER_PIPEPIPEEQ] = { "expected an expression after `||=`", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_EXPECT_EXPRESSION_AFTER_COMMA] = { "expected an expression after `,`", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_EXPECT_EXPRESSION_AFTER_EQUAL] = { "expected an expression after `=`", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_EXPECT_EXPRESSION_AFTER_LESS_LESS] = { "expected an expression after `<<`", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_EXPECT_EXPRESSION_AFTER_LPAREN] = { "expected an expression after `(`", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_EXPECT_EXPRESSION_AFTER_OPERATOR] = { "expected an expression after the operator", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_EXPECT_EXPRESSION_AFTER_SPLAT] = { "expected an expression after `*` splat in an argument", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_EXPECT_EXPRESSION_AFTER_SPLAT_HASH] = { "expected an expression after `**` in a hash", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_EXPECT_EXPRESSION_AFTER_STAR] = { "expected an expression after `*`", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_EXPECT_IDENT_REQ_PARAMETER] = { "expected an identifier for the required parameter", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_EXPECT_LPAREN_REQ_PARAMETER] = { "expected a `(` to start a required parameter", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_EXPECT_RBRACKET] = { "expected a matching `]`", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_EXPECT_RPAREN] = { "expected a matching `)`", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_EXPECT_RPAREN_AFTER_MULTI] = { "expected a `)` after multiple assignment", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_EXPECT_RPAREN_REQ_PARAMETER] = { "expected a `)` to end a required parameter", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_EXPECT_STRING_CONTENT] = { "expected string content after opening string delimiter", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_EXPECT_WHEN_DELIMITER] = { "expected a delimiter after the predicates of a `when` clause", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_EXPRESSION_BARE_HASH] = { "unexpected bare hash in expression", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_FLOAT_PARSE] = { "could not parse the float '%.*s'", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_FOR_COLLECTION] = { "expected a collection after the `in` in a `for` statement", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_FOR_INDEX] = { "expected an index after `for`", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_FOR_IN] = { "expected an `in` after the index in a `for` statement", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_FOR_TERM] = { "expected an `end` to close the `for` loop", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_GLOBAL_VARIABLE_BARE] = { "'$' without identifiers is not allowed as a global variable name", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_HASH_EXPRESSION_AFTER_LABEL] = { "expected an expression after the label in a hash", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_HASH_KEY] = { "unexpected %s, expecting '}' or a key in the hash literal", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_HASH_ROCKET] = { "expected a `=>` between the hash key and value", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_HASH_TERM] = { "expected a `}` to close the hash literal", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_HASH_VALUE] = { "expected a value in the hash literal", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_HEREDOC_TERM] = { "could not find a terminator for the heredoc", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_INCOMPLETE_QUESTION_MARK] = { "incomplete expression at `?`", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_INCOMPLETE_VARIABLE_CLASS_3_3_0] = { "`%.*s' is not allowed as a class variable name", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_INCOMPLETE_VARIABLE_CLASS] = { "'%.*s' is not allowed as a class variable name", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_INCOMPLETE_VARIABLE_INSTANCE_3_3_0] = { "`%.*s' is not allowed as an instance variable name", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_INCOMPLETE_VARIABLE_INSTANCE] = { "'%.*s' is not allowed as an instance variable name", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_INSTANCE_VARIABLE_BARE] = { "'@' without identifiers is not allowed as an instance variable name", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_INVALID_BLOCK_EXIT] = { "Invalid %s", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_INVALID_FLOAT_EXPONENT] = { "invalid exponent", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_INVALID_NUMBER_BINARY] = { "invalid binary number", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_INVALID_NUMBER_DECIMAL] = { "invalid decimal number", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_INVALID_NUMBER_HEXADECIMAL] = { "invalid hexadecimal number", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_INVALID_NUMBER_OCTAL] = { "invalid octal number", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_INVALID_NUMBER_UNDERSCORE] = { "invalid underscore placement in number", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_INVALID_CHARACTER] = { "invalid character 0x%X", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_INVALID_MULTIBYTE_CHAR] = { "invalid multibyte char (%s)", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_INVALID_MULTIBYTE_CHARACTER] = { "invalid multibyte character 0x%X", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_INVALID_MULTIBYTE_ESCAPE] = { "invalid multibyte escape: /%.*s/", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_INVALID_PRINTABLE_CHARACTER] = { "invalid character `%c`", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_INVALID_PERCENT] = { "invalid `%` token", PM_ERROR_LEVEL_SYNTAX }, // TODO WHAT? + [PM_ERR_INVALID_RETRY_AFTER_ELSE] = { "Invalid retry after else", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_INVALID_RETRY_AFTER_ENSURE] = { "Invalid retry after ensure", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_INVALID_RETRY_WITHOUT_RESCUE] = { "Invalid retry without rescue", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_INVALID_VARIABLE_GLOBAL_3_3_0] = { "`%.*s' is not allowed as a global variable name", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_INVALID_VARIABLE_GLOBAL] = { "'%.*s' is not allowed as a global variable name", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_INVALID_YIELD] = { "Invalid yield", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_IT_NOT_ALLOWED_NUMBERED] = { "`it` is not allowed when an numbered parameter is defined", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_IT_NOT_ALLOWED_ORDINARY] = { "`it` is not allowed when an ordinary parameter is defined", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_LAMBDA_OPEN] = { "expected a `do` keyword or a `{` to open the lambda block", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_LAMBDA_TERM_BRACE] = { "expected a lambda block beginning with `{` to end with `}`", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_LAMBDA_TERM_END] = { "expected a lambda block beginning with `do` to end with `end`", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_LIST_I_LOWER_ELEMENT] = { "expected a symbol in a `%i` list", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_LIST_I_LOWER_TERM] = { "expected a closing delimiter for the `%i` list", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_LIST_I_UPPER_ELEMENT] = { "expected a symbol in a `%I` list", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_LIST_I_UPPER_TERM] = { "expected a closing delimiter for the `%I` list", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_LIST_W_LOWER_ELEMENT] = { "expected a string in a `%w` list", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_LIST_W_LOWER_TERM] = { "expected a closing delimiter for the `%w` list", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_LIST_W_UPPER_ELEMENT] = { "expected a string in a `%W` list", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_LIST_W_UPPER_TERM] = { "expected a closing delimiter for the `%W` list", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_MALLOC_FAILED] = { "failed to allocate memory", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_MIXED_ENCODING] = { "UTF-8 mixed within %s source", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_MODULE_IN_METHOD] = { "unexpected module definition in method body", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_MODULE_NAME] = { "unexpected constant path after `module`; class/module name must be CONSTANT", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_MODULE_TERM] = { "expected an `end` to close the `module` statement", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_MULTI_ASSIGN_MULTI_SPLATS] = { "multiple splats in multiple assignment", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_MULTI_ASSIGN_UNEXPECTED_REST] = { "unexpected '%.*s' resulting in multiple splats in multiple assignment", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_NOT_EXPRESSION] = { "expected an expression after `not`", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_NO_LOCAL_VARIABLE] = { "%.*s: no such local variable", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_NUMBER_LITERAL_UNDERSCORE] = { "number literal ending with a `_`", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_NUMBERED_PARAMETER_IT] = { "numbered parameters are not allowed when an 'it' parameter is defined", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_NUMBERED_PARAMETER_ORDINARY] = { "numbered parameters are not allowed when an ordinary parameter is defined", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_NUMBERED_PARAMETER_OUTER_SCOPE] = { "numbered parameter is already used in outer scope", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_OPERATOR_MULTI_ASSIGN] = { "unexpected operator for a multiple assignment", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_OPERATOR_WRITE_ARGUMENTS] = { "unexpected operator after a call with arguments", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_OPERATOR_WRITE_BLOCK] = { "unexpected operator after a call with a block", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_PARAMETER_ASSOC_SPLAT_MULTI] = { "unexpected multiple `**` splat parameters", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_PARAMETER_BLOCK_MULTI] = { "multiple block parameters; only one block is allowed", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_PARAMETER_CIRCULAR] = { "circular argument reference - %.*s", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_PARAMETER_METHOD_NAME] = { "unexpected name for a parameter", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_PARAMETER_NAME_DUPLICATED] = { "duplicated argument name", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_PARAMETER_NO_DEFAULT] = { "expected a default value for the parameter", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_PARAMETER_NO_DEFAULT_KW] = { "expected a default value for the keyword parameter", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_PARAMETER_NUMBERED_RESERVED] = { "%.2s is reserved for numbered parameters", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_PARAMETER_ORDER] = { "unexpected parameter order", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_PARAMETER_SPLAT_MULTI] = { "unexpected multiple `*` splat parameters", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_PARAMETER_STAR] = { "unexpected parameter `*`", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_PARAMETER_UNEXPECTED_FWD] = { "unexpected `...` in parameters", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_PARAMETER_WILD_LOOSE_COMMA] = { "unexpected `,` in parameters", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_PATTERN_CAPTURE_DUPLICATE] = { "duplicated variable name", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_PATTERN_EXPRESSION_AFTER_BRACKET] = { "expected a pattern expression after the `[` operator", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_PATTERN_EXPRESSION_AFTER_COMMA] = { "expected a pattern expression after `,`", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_PATTERN_EXPRESSION_AFTER_HROCKET] = { "expected a pattern expression after `=>`", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_PATTERN_EXPRESSION_AFTER_IN] = { "expected a pattern expression after the `in` keyword", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_PATTERN_EXPRESSION_AFTER_KEY] = { "expected a pattern expression after the key", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_PATTERN_EXPRESSION_AFTER_PAREN] = { "expected a pattern expression after the `(` operator", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_PATTERN_EXPRESSION_AFTER_PIN] = { "expected a pattern expression after the `^` pin operator", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_PATTERN_EXPRESSION_AFTER_PIPE] = { "expected a pattern expression after the `|` operator", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_PATTERN_EXPRESSION_AFTER_RANGE] = { "expected a pattern expression after the range operator", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_PATTERN_EXPRESSION_AFTER_REST] = { "unexpected pattern expression after the `**` expression", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_PATTERN_HASH_KEY] = { "expected a key in the hash pattern", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_PATTERN_HASH_KEY_DUPLICATE] = { "duplicated key name", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_PATTERN_HASH_KEY_LABEL] = { "expected a label as the key in the hash pattern", PM_ERROR_LEVEL_SYNTAX }, // TODO // THIS // AND // ABOVE // IS WEIRD + [PM_ERR_PATTERN_IDENT_AFTER_HROCKET] = { "expected an identifier after the `=>` operator", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_PATTERN_LABEL_AFTER_COMMA] = { "expected a label after the `,` in the hash pattern", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_PATTERN_REST] = { "unexpected rest pattern", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_PATTERN_TERM_BRACE] = { "expected a `}` to close the pattern expression", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_PATTERN_TERM_BRACKET] = { "expected a `]` to close the pattern expression", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_PATTERN_TERM_PAREN] = { "expected a `)` to close the pattern expression", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_PIPEPIPEEQ_MULTI_ASSIGN] = { "unexpected `||=` in a multiple assignment", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_REGEXP_ENCODING_OPTION_MISMATCH] = { "regexp encoding option '%c' differs from source encoding '%s'", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_REGEXP_INCOMPAT_CHAR_ENCODING] = { "incompatible character encoding: /%.*s/", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_REGEXP_NON_ESCAPED_MBC] = { "/.../n has a non escaped non ASCII character in non ASCII-8BIT script: /%.*s/", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_REGEXP_INVALID_UNICODE_RANGE] = { "invalid Unicode range: /%.*s/", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_REGEXP_UNKNOWN_OPTIONS] = { "unknown regexp %s: %.*s", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_REGEXP_TERM] = { "expected a closing delimiter for the regular expression", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_REGEXP_UTF8_CHAR_NON_UTF8_REGEXP] = { "UTF-8 character in non UTF-8 regexp: /%s/", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_RESCUE_EXPRESSION] = { "expected a rescued expression", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_RESCUE_MODIFIER_VALUE] = { "expected a value after the `rescue` modifier", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_RESCUE_TERM] = { "expected a closing delimiter for the `rescue` clause", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_RESCUE_VARIABLE] = { "expected an exception variable after `=>` in a rescue statement", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_RETURN_INVALID] = { "Invalid return in class/module body", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_SINGLETON_FOR_LITERALS] = { "cannot define singleton method for literals", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_STATEMENT_ALIAS] = { "unexpected an `alias` at a non-statement position", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_STATEMENT_POSTEXE_END] = { "unexpected an `END` at a non-statement position", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_STATEMENT_PREEXE_BEGIN] = { "unexpected a `BEGIN` at a non-statement position", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_STATEMENT_UNDEF] = { "unexpected an `undef` at a non-statement position", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_STRING_CONCATENATION] = { "expected a string for concatenation", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_STRING_INTERPOLATED_TERM] = { "expected a closing delimiter for the interpolated string", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_STRING_LITERAL_EOF] = { "unterminated string meets end of file", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_STRING_LITERAL_TERM] = { "unexpected %s, expected a string literal terminator", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_SYMBOL_INVALID] = { "invalid symbol", PM_ERROR_LEVEL_SYNTAX }, // TODO expected symbol? prism.c ~9719 + [PM_ERR_SYMBOL_TERM_DYNAMIC] = { "expected a closing delimiter for the dynamic symbol", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_SYMBOL_TERM_INTERPOLATED] = { "expected a closing delimiter for the interpolated symbol", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_TERNARY_COLON] = { "expected a `:` after the true expression of a ternary operator", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_TERNARY_EXPRESSION_FALSE] = { "expected an expression after `:` in the ternary operator", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_TERNARY_EXPRESSION_TRUE] = { "expected an expression after `?` in the ternary operator", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_UNDEF_ARGUMENT] = { "invalid argument being passed to `undef`; expected a bare word, constant, or symbol argument", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_UNARY_RECEIVER] = { "unexpected %s, expected a receiver for unary `%c`", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_UNEXPECTED_TOKEN_CLOSE_CONTEXT] = { "unexpected %s, assuming it is closing the parent %s", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_UNEXPECTED_TOKEN_IGNORE] = { "unexpected %s, ignoring it", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_UNTIL_TERM] = { "expected an `end` to close the `until` statement", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_VOID_EXPRESSION] = { "unexpected void value expression", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_WHILE_TERM] = { "expected an `end` to close the `while` statement", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_WRITE_TARGET_IN_METHOD] = { "dynamic constant assignment", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_WRITE_TARGET_READONLY] = { "Can't set variable %.*s", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_WRITE_TARGET_UNEXPECTED] = { "unexpected write target", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_XSTRING_TERM] = { "expected a closing delimiter for the `%x` or backtick string", PM_ERROR_LEVEL_SYNTAX }, // Warnings [PM_WARN_AMBIGUOUS_FIRST_ARGUMENT_MINUS] = { "ambiguous first argument; put parentheses or a space even after `-` operator", PM_WARNING_LEVEL_VERBOSE }, [PM_WARN_AMBIGUOUS_FIRST_ARGUMENT_PLUS] = { "ambiguous first argument; put parentheses or a space even after `+` operator", PM_WARNING_LEVEL_VERBOSE }, + [PM_WARN_AMBIGUOUS_PREFIX_AMPERSAND] = { "ambiguous `&` has been interpreted as an argument prefix", PM_WARNING_LEVEL_VERBOSE }, [PM_WARN_AMBIGUOUS_PREFIX_STAR] = { "ambiguous `*` has been interpreted as an argument prefix", PM_WARNING_LEVEL_VERBOSE }, + [PM_WARN_AMBIGUOUS_PREFIX_STAR_STAR] = { "ambiguous `**` has been interpreted as an argument prefix", PM_WARNING_LEVEL_VERBOSE }, [PM_WARN_AMBIGUOUS_SLASH] = { "ambiguous `/`; wrap regexp in parentheses or add a space after `/` operator", PM_WARNING_LEVEL_VERBOSE }, + [PM_WARN_COMPARISON_AFTER_COMPARISON] = { "comparison '%.*s' after comparison", PM_WARNING_LEVEL_VERBOSE }, [PM_WARN_DOT_DOT_DOT_EOL] = { "... at EOL, should be parenthesized?", PM_WARNING_LEVEL_DEFAULT }, [PM_WARN_DUPLICATED_HASH_KEY] = { "key %.*s is duplicated and overwritten on line %" PRIi32, PM_WARNING_LEVEL_DEFAULT }, [PM_WARN_DUPLICATED_WHEN_CLAUSE] = { "duplicated 'when' clause with line %" PRIi32 " is ignored", PM_WARNING_LEVEL_VERBOSE }, - [PM_WARN_EQUAL_IN_CONDITIONAL] = { "found `= literal' in conditional, should be ==", PM_WARNING_LEVEL_DEFAULT }, + [PM_WARN_EQUAL_IN_CONDITIONAL_3_3_0] = { "found `= literal' in conditional, should be ==", PM_WARNING_LEVEL_DEFAULT }, + [PM_WARN_EQUAL_IN_CONDITIONAL] = { "found '= literal' in conditional, should be ==", PM_WARNING_LEVEL_DEFAULT }, [PM_WARN_END_IN_METHOD] = { "END in method; use at_exit", PM_WARNING_LEVEL_DEFAULT }, [PM_WARN_FLOAT_OUT_OF_RANGE] = { "Float %.*s%s out of range", PM_WARNING_LEVEL_VERBOSE }, + [PM_WARN_IGNORED_FROZEN_STRING_LITERAL] = { "'frozen_string_literal' is ignored after any tokens", PM_WARNING_LEVEL_VERBOSE }, [PM_WARN_INTEGER_IN_FLIP_FLOP] = { "integer literal in flip-flop", PM_WARNING_LEVEL_DEFAULT }, - [PM_WARN_KEYWORD_EOL] = { "`%.*s` at the end of line without an expression", PM_WARNING_LEVEL_VERBOSE } + [PM_WARN_INVALID_CHARACTER] = { "invalid character syntax; use %s%s%s", PM_WARNING_LEVEL_DEFAULT }, + [PM_WARN_INVALID_SHAREABLE_CONSTANT_VALUE] = { "invalid value for shareable_constant_value: %.*s", PM_WARNING_LEVEL_VERBOSE }, + [PM_WARN_INVALID_NUMBERED_REFERENCE] = { "'%.*s' is too big for a number variable, always nil", PM_WARNING_LEVEL_DEFAULT }, + [PM_WARN_KEYWORD_EOL] = { "`%.*s` at the end of line without an expression", PM_WARNING_LEVEL_VERBOSE }, + [PM_WARN_LITERAL_IN_CONDITION_DEFAULT] = { "%sliteral in %s", PM_WARNING_LEVEL_DEFAULT }, + [PM_WARN_LITERAL_IN_CONDITION_VERBOSE] = { "%sliteral in %s", PM_WARNING_LEVEL_VERBOSE }, + [PM_WARN_SHEBANG_CARRIAGE_RETURN] = { "shebang line ending with \\r may cause problems", PM_WARNING_LEVEL_DEFAULT }, + [PM_WARN_UNEXPECTED_CARRIAGE_RETURN] = { "encountered \\r in middle of line, treated as a mere space", PM_WARNING_LEVEL_DEFAULT }, + [PM_WARN_UNUSED_LOCAL_VARIABLE] = { "assigned but unused variable - %.*s", PM_WARNING_LEVEL_VERBOSE } }; /** @@ -365,7 +395,7 @@ pm_diagnostic_level(pm_diagnostic_id_t diag_id) { */ bool pm_diagnostic_list_append(pm_list_t *list, const uint8_t *start, const uint8_t *end, pm_diagnostic_id_t diag_id) { - pm_diagnostic_t *diagnostic = (pm_diagnostic_t *) xcalloc(sizeof(pm_diagnostic_t), 1); + pm_diagnostic_t *diagnostic = (pm_diagnostic_t *) xcalloc(1, sizeof(pm_diagnostic_t)); if (diagnostic == NULL) return false; *diagnostic = (pm_diagnostic_t) { @@ -397,7 +427,7 @@ pm_diagnostic_list_append_format(pm_list_t *list, const uint8_t *start, const ui return false; } - pm_diagnostic_t *diagnostic = (pm_diagnostic_t *) xcalloc(sizeof(pm_diagnostic_t), 1); + pm_diagnostic_t *diagnostic = (pm_diagnostic_t *) xcalloc(1, sizeof(pm_diagnostic_t)); if (diagnostic == NULL) { return false; } diff --git a/prism/templates/src/node.c.erb b/prism/templates/src/node.c.erb index 699fc00725a8d0..e1c35f5a456f04 100644 --- a/prism/templates/src/node.c.erb +++ b/prism/templates/src/node.c.erb @@ -9,11 +9,9 @@ pm_node_memsize_node(pm_node_t *node, pm_memsize_t *memsize); */ static size_t pm_node_list_memsize(pm_node_list_t *node_list, pm_memsize_t *memsize) { - size_t size = sizeof(pm_node_list_t) + (node_list->capacity * sizeof(pm_node_t *)); - for (size_t index = 0; index < node_list->size; index++) { - pm_node_memsize_node(node_list->nodes[index], memsize); - } - return size; + pm_node_t *node; + PM_NODE_LIST_FOREACH(node_list, index, node) pm_node_memsize_node(node, memsize); + return sizeof(pm_node_list_t) + (node_list->capacity * sizeof(pm_node_t *)); } /** @@ -22,13 +20,36 @@ pm_node_list_memsize(pm_node_list_t *node_list, pm_memsize_t *memsize) { * the list to be twice as large as it was before. If the reallocation fails, * this function returns false, otherwise it returns true. */ -bool -pm_node_list_grow(pm_node_list_t *list) { - if (list->size == list->capacity) { - list->capacity = list->capacity == 0 ? 4 : list->capacity * 2; - list->nodes = (pm_node_t **) xrealloc(list->nodes, sizeof(pm_node_t *) * list->capacity); - return list->nodes != NULL; +static bool +pm_node_list_grow(pm_node_list_t *list, size_t size) { + size_t requested_size = list->size + size; + + // If the requested size caused overflow, return false. + if (requested_size < list->size) return false; + + // If the requested size is within the existing capacity, return true. + if (requested_size < list->capacity) return true; + + // Otherwise, reallocate the list to be twice as large as it was before. + size_t next_capacity = list->capacity == 0 ? 4 : list->capacity * 2; + + // If multiplying by 2 caused overflow, return false. + if (next_capacity < list->capacity) return false; + + // If we didn't get enough by doubling, keep doubling until we do. + while (requested_size > next_capacity) { + size_t double_capacity = next_capacity * 2; + + // Ensure we didn't overflow by multiplying by 2. + if (double_capacity < next_capacity) return false; + next_capacity = double_capacity; } + + pm_node_t **nodes = (pm_node_t **) xrealloc(list->nodes, sizeof(pm_node_t *) * next_capacity); + if (nodes == NULL) return false; + + list->nodes = nodes; + list->capacity = next_capacity; return true; } @@ -37,7 +58,7 @@ pm_node_list_grow(pm_node_list_t *list) { */ void pm_node_list_append(pm_node_list_t *list, pm_node_t *node) { - if (pm_node_list_grow(list)) { + if (pm_node_list_grow(list, 1)) { list->nodes[list->size++] = node; } } @@ -47,13 +68,24 @@ pm_node_list_append(pm_node_list_t *list, pm_node_t *node) { */ void pm_node_list_prepend(pm_node_list_t *list, pm_node_t *node) { - if (pm_node_list_grow(list)) { + if (pm_node_list_grow(list, 1)) { memmove(list->nodes + 1, list->nodes, list->size * sizeof(pm_node_t *)); list->nodes[0] = node; list->size++; } } +/** + * Concatenate the given node list onto the end of the other node list. + */ +void +pm_node_list_concat(pm_node_list_t *list, pm_node_list_t *other) { + if (other->size > 0 && pm_node_list_grow(list, other->size)) { + memcpy(list->nodes + list->size, other->nodes, other->size * sizeof(pm_node_t *)); + list->size += other->size; + } +} + /** * Free the internal memory associated with the given node list. */ @@ -73,10 +105,8 @@ pm_node_destroy(pm_parser_t *parser, pm_node_t *node); */ static void pm_node_list_destroy(pm_parser_t *parser, pm_node_list_t *list) { - for (size_t index = 0; index < list->size; index++) { - pm_node_destroy(parser, list->nodes[index]); - } - + pm_node_t *node; + PM_NODE_LIST_FOREACH(list, index, node) pm_node_destroy(parser, node); pm_node_list_free(list); } @@ -247,6 +277,10 @@ pm_visit_child_nodes(const pm_node_t *node, bool (*visitor)(const pm_node_t *nod } } +// We optionally support dumping to JSON. For systems that don't want or need +// this functionality, it can be turned off with the PRISM_EXCLUDE_JSON define. +#ifndef PRISM_EXCLUDE_JSON + static void pm_dump_json_constant(pm_buffer_t *buffer, const pm_parser_t *parser, pm_constant_id_t constant_id) { const pm_constant_t *constant = pm_constant_pool_id_to_constant(&parser->constant_pool, constant_id); @@ -360,3 +394,5 @@ pm_dump_json(pm_buffer_t *buffer, const pm_parser_t *parser, const pm_node_t *no break; } } + +#endif diff --git a/prism/templates/src/prettyprint.c.erb b/prism/templates/src/prettyprint.c.erb index ef3385d1a66b95..27f44cd996f68a 100644 --- a/prism/templates/src/prettyprint.c.erb +++ b/prism/templates/src/prettyprint.c.erb @@ -1,6 +1,15 @@ <%# encoding: ASCII -%> #include "prism/prettyprint.h" +// We optionally support pretty printing nodes. For systems that don't want or +// need this functionality, it can be turned off with the +// PRISM_EXCLUDE_PRETTYPRINT define. +#ifdef PRISM_EXCLUDE_PRETTYPRINT + +void pm_prettyprint(void) {} + +#else + static inline void prettyprint_location(pm_buffer_t *output_buffer, const pm_parser_t *parser, const pm_location_t *location) { pm_line_column_t start = pm_newline_list_line_column(&parser->newline_list, location->start, parser->start_line); @@ -154,3 +163,5 @@ pm_prettyprint(pm_buffer_t *output_buffer, const pm_parser_t *parser, const pm_n prettyprint_node(output_buffer, parser, node, &prefix_buffer); pm_buffer_free(&prefix_buffer); } + +#endif diff --git a/prism/templates/src/serialize.c.erb b/prism/templates/src/serialize.c.erb index 27fde37f698601..97101e36d52d91 100644 --- a/prism/templates/src/serialize.c.erb +++ b/prism/templates/src/serialize.c.erb @@ -1,5 +1,10 @@ #include "prism.h" +// We optionally support serializing to a binary string. For systems that don't +// want or need this functionality, it can be turned off with the +// PRISM_EXCLUDE_SERIALIZATION define. +#ifndef PRISM_EXCLUDE_SERIALIZATION + #include static inline uint32_t @@ -215,7 +220,7 @@ pm_serialize_data_loc(const pm_parser_t *parser, pm_buffer_t *buffer) { static void pm_serialize_diagnostic(pm_parser_t *parser, pm_diagnostic_t *diagnostic, pm_buffer_t *buffer) { // serialize the type - pm_buffer_append_byte(buffer, (uint8_t) diagnostic->diag_id); + pm_buffer_append_varuint(buffer, (uint32_t) diagnostic->diag_id); // serialize message size_t message_length = strlen(diagnostic->message); @@ -394,23 +399,4 @@ pm_serialize_parse_lex(pm_buffer_t *buffer, const uint8_t *source, size_t size, pm_options_free(&options); } -/** - * Parse the source and return true if it parses without errors or warnings. - */ -PRISM_EXPORTED_FUNCTION bool -pm_parse_success_p(const uint8_t *source, size_t size, const char *data) { - pm_options_t options = { 0 }; - pm_options_read(&options, data); - - pm_parser_t parser; - pm_parser_init(&parser, source, size, &options); - - pm_node_t *node = pm_parse(&parser); - pm_node_destroy(&parser, node); - - bool result = parser.error_list.size == 0 && parser.warning_list.size == 0; - pm_parser_free(&parser); - pm_options_free(&options); - - return result; -} +#endif diff --git a/prism/util/pm_buffer.c b/prism/util/pm_buffer.c index 87f79ddd2c2198..1e79e46718c222 100644 --- a/prism/util/pm_buffer.c +++ b/prism/util/pm_buffer.c @@ -283,6 +283,31 @@ pm_buffer_rstrip(pm_buffer_t *buffer) { } } +/** + * Checks if the buffer includes the given value. + */ +size_t +pm_buffer_index(const pm_buffer_t *buffer, char value) { + const char *first = memchr(buffer->value, value, buffer->length); + return (first == NULL) ? SIZE_MAX : (size_t) (first - buffer->value); +} + +/** + * Insert the given string into the buffer at the given index. + */ +void +pm_buffer_insert(pm_buffer_t *buffer, size_t index, const char *value, size_t length) { + assert(index <= buffer->length); + + if (index == buffer->length) { + pm_buffer_append_string(buffer, value, length); + } else { + pm_buffer_append_zeroes(buffer, length); + memmove(buffer->value + index + length, buffer->value + index, buffer->length - length - index); + memcpy(buffer->value + index, value, length); + } +} + /** * Free the memory associated with the buffer. */ diff --git a/prism/util/pm_buffer.h b/prism/util/pm_buffer.h index d8ec8180e799bb..58f7b506cfd304 100644 --- a/prism/util/pm_buffer.h +++ b/prism/util/pm_buffer.h @@ -188,6 +188,26 @@ void pm_buffer_clear(pm_buffer_t *buffer); */ void pm_buffer_rstrip(pm_buffer_t *buffer); +/** + * Checks if the buffer includes the given value. + * + * @param buffer The buffer to check. + * @param value The value to check for. + * @returns The index of the first occurrence of the value in the buffer, or + * SIZE_MAX if the value is not found. + */ +size_t pm_buffer_index(const pm_buffer_t *buffer, char value); + +/** + * Insert the given string into the buffer at the given index. + * + * @param buffer The buffer to insert into. + * @param index The index to insert at. + * @param value The string to insert. + * @param length The length of the string to insert. + */ +void pm_buffer_insert(pm_buffer_t *buffer, size_t index, const char *value, size_t length); + /** * Free the memory associated with the buffer. * diff --git a/prism/util/pm_char.c b/prism/util/pm_char.c index 13eddbba481c9d..dce19abd1b102f 100644 --- a/prism/util/pm_char.c +++ b/prism/util/pm_char.c @@ -19,10 +19,10 @@ static const uint8_t pm_byte_table[256] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 1x 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 2x 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 3x - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 4x - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 5x - 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 4, 4, // 6x - 0, 0, 0, 4, 0, 4, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, // 7x + 0, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, // 4x + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 0, 0, 0, 0, 0, // 5x + 0, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, // 6x + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 0, 0, 0, 0, 0, // 7x 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 8x 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 9x 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // Ax diff --git a/prism/util/pm_constant_pool.c b/prism/util/pm_constant_pool.c index 741a036cf70383..2a3203f4c46c82 100644 --- a/prism/util/pm_constant_pool.c +++ b/prism/util/pm_constant_pool.c @@ -10,6 +10,18 @@ pm_constant_id_list_init(pm_constant_id_list_t *list) { list->capacity = 0; } +/** + * Initialize a list of constant ids with a given capacity. + */ +void +pm_constant_id_list_init_capacity(pm_constant_id_list_t *list, size_t capacity) { + list->ids = xcalloc(capacity, sizeof(pm_constant_id_t)); + if (list->ids == NULL) abort(); + + list->size = 0; + list->capacity = capacity; +} + /** * Append a constant id to a list of constant ids. Returns false if any * potential reallocations fail. @@ -26,6 +38,18 @@ pm_constant_id_list_append(pm_constant_id_list_t *list, pm_constant_id_t id) { return true; } +/** + * Insert a constant id into a list of constant ids at the specified index. + */ +void +pm_constant_id_list_insert(pm_constant_id_list_t *list, size_t index, pm_constant_id_t id) { + assert(index < list->capacity); + assert(list->ids[index] == PM_CONSTANT_ID_UNSET); + + list->ids[index] = id; + list->size++; +} + /** * Checks if the current constant id list includes the given constant id. */ diff --git a/prism/util/pm_constant_pool.h b/prism/util/pm_constant_pool.h index 19e406396e5c7f..0fe16858a0dea7 100644 --- a/prism/util/pm_constant_pool.h +++ b/prism/util/pm_constant_pool.h @@ -51,6 +51,14 @@ typedef struct { */ void pm_constant_id_list_init(pm_constant_id_list_t *list); +/** + * Initialize a list of constant ids with a given capacity. + * + * @param list The list to initialize. + * @param capacity The initial capacity of the list. + */ +void pm_constant_id_list_init_capacity(pm_constant_id_list_t *list, size_t capacity); + /** * Append a constant id to a list of constant ids. Returns false if any * potential reallocations fail. @@ -61,6 +69,15 @@ void pm_constant_id_list_init(pm_constant_id_list_t *list); */ bool pm_constant_id_list_append(pm_constant_id_list_t *list, pm_constant_id_t id); +/** + * Insert a constant id into a list of constant ids at the specified index. + * + * @param list The list to insert into. + * @param index The index at which to insert. + * @param id The id to insert. + */ +void pm_constant_id_list_insert(pm_constant_id_list_t *list, size_t index, pm_constant_id_t id); + /** * Checks if the current constant id list includes the given constant id. * diff --git a/prism/util/pm_integer.c b/prism/util/pm_integer.c index 160a78920c0086..8f8b75474df33a 100644 --- a/prism/util/pm_integer.c +++ b/prism/util/pm_integer.c @@ -223,6 +223,10 @@ karatsuba_multiply(pm_integer_t *destination, pm_integer_t *left, pm_integer_t * /** * The values of a hexadecimal digit, where the index is the ASCII character. + * Note that there's an odd exception here where _ is mapped to 0. This is + * because it's possible for us to end up trying to parse a number that has + * already had an error attached to it, and we want to provide _something_ to + * the user. */ static const int8_t pm_integer_parse_digit_values[256] = { // 0 1 2 3 4 5 6 7 8 9 A B C D E F @@ -231,7 +235,7 @@ static const int8_t pm_integer_parse_digit_values[256] = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 2x 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, // 3x -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 4x - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 5x + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, // 5x -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 6x -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 7x -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 8x diff --git a/prism/util/pm_newline_list.c b/prism/util/pm_newline_list.c index f9dff4c1666187..ce07ce8c8e4644 100644 --- a/prism/util/pm_newline_list.c +++ b/prism/util/pm_newline_list.c @@ -19,6 +19,14 @@ pm_newline_list_init(pm_newline_list_t *list, const uint8_t *start, size_t capac return true; } +/** + * Clear out the newlines that have been appended to the list. + */ +void +pm_newline_list_clear(pm_newline_list_t *list) { + list->size = 1; +} + /** * Append a new offset to the newline list. Returns true if the reallocation of * the offsets succeeds (if one was necessary), otherwise returns false. diff --git a/prism/util/pm_newline_list.h b/prism/util/pm_newline_list.h index 612ee35d3f8fd6..7ae9b6b3da0ac5 100644 --- a/prism/util/pm_newline_list.h +++ b/prism/util/pm_newline_list.h @@ -61,6 +61,14 @@ typedef struct { */ bool pm_newline_list_init(pm_newline_list_t *list, const uint8_t *start, size_t capacity); +/** + * Clear out the newlines that have been appended to the list. + * + * @param list The list to clear. + */ +void +pm_newline_list_clear(pm_newline_list_t *list); + /** * Append a new offset to the newline list. Returns true if the reallocation of * the offsets succeeds (if one was necessary), otherwise returns false. diff --git a/prism/version.h b/prism/version.h index 237796815f95a7..c96fe6882f16a7 100644 --- a/prism/version.h +++ b/prism/version.h @@ -14,7 +14,7 @@ /** * The minor version of the Prism library as an int. */ -#define PRISM_VERSION_MINOR 24 +#define PRISM_VERSION_MINOR 25 /** * The patch version of the Prism library as an int. @@ -24,6 +24,6 @@ /** * The version of the Prism library as a constant string. */ -#define PRISM_VERSION "0.24.0" +#define PRISM_VERSION "0.25.0" #endif diff --git a/prism_compile.c b/prism_compile.c index 8b65ae7a7af6db..2c50c19c1dfb26 100644 --- a/prism_compile.c +++ b/prism_compile.c @@ -16,15 +16,15 @@ #define PUSH_INSN1(seq, location, insn, op1) \ ADD_ELEM((seq), (LINK_ELEMENT *) new_insn_body(iseq, (int) (location).line, (int) (location).column, BIN(insn), 1, (VALUE)(op1))) -#define PUSH_INSNL(seq, location, insn, label) \ - (PUSH_INSN1(seq, location, insn, label), LABEL_REF(label)) - #define PUSH_INSN2(seq, location, insn, op1, op2) \ ADD_ELEM((seq), (LINK_ELEMENT *) new_insn_body(iseq, (int) (location).line, (int) (location).column, BIN(insn), 2, (VALUE)(op1), (VALUE)(op2))) #define PUSH_INSN3(seq, location, insn, op1, op2, op3) \ ADD_ELEM((seq), (LINK_ELEMENT *) new_insn_body(iseq, (int) (location).line, (int) (location).column, BIN(insn), 3, (VALUE)(op1), (VALUE)(op2), (VALUE)(op3))) +#define PUSH_INSNL(seq, location, insn, label) \ + (PUSH_INSN1(seq, location, insn, label), LABEL_REF(label)) + #define PUSH_LABEL(seq, label) \ ADD_ELEM((seq), (LINK_ELEMENT *) (label)) @@ -37,8 +37,58 @@ #define PUSH_SEND_WITH_FLAG(seq, location, id, argc, flag) \ PUSH_SEND_R((seq), location, (id), (argc), NULL, (VALUE)(flag), NULL) +#define PUSH_SEND_WITH_BLOCK(seq, location, id, argc, block) \ + PUSH_SEND_R((seq), location, (id), (argc), (block), (VALUE)INT2FIX(0), NULL) + +#define PUSH_CALL(seq, location, id, argc) \ + PUSH_SEND_R((seq), location, (id), (argc), NULL, (VALUE)INT2FIX(VM_CALL_FCALL), NULL) + +#define PUSH_CALL_WITH_BLOCK(seq, location, id, argc, block) \ + PUSH_SEND_R((seq), location, (id), (argc), (block), (VALUE)INT2FIX(VM_CALL_FCALL), NULL) + #define PUSH_TRACE(seq, event) \ - ADD_ELEM((seq), (LINK_ELEMENT *) new_trace_body(iseq, (event), 0)) + ADD_ELEM((seq), (LINK_ELEMENT *) new_trace_body(iseq, (event), 0)) + +#define PUSH_CATCH_ENTRY(type, ls, le, iseqv, lc) \ + ADD_CATCH_ENTRY((type), (ls), (le), (iseqv), (lc)) + +#define PUSH_SEQ(seq1, seq2) \ + APPEND_LIST((seq1), (seq2)) + +/******************************************************************************/ +/* These functions compile getlocal/setlocal instructions but operate on */ +/* prism locations instead of NODEs. */ +/******************************************************************************/ + +static void +pm_iseq_add_getlocal(rb_iseq_t *iseq, LINK_ANCHOR *const seq, int line_no, int column, int idx, int level) +{ + if (iseq_local_block_param_p(iseq, idx, level)) { + ADD_ELEM(seq, (LINK_ELEMENT *) new_insn_body(iseq, line_no, column, BIN(getblockparam), 2, INT2FIX((idx) + VM_ENV_DATA_SIZE - 1), INT2FIX(level))); + } + else { + ADD_ELEM(seq, (LINK_ELEMENT *) new_insn_body(iseq, line_no, column, BIN(getlocal), 2, INT2FIX((idx) + VM_ENV_DATA_SIZE - 1), INT2FIX(level))); + } + if (level > 0) access_outer_variables(iseq, level, iseq_lvar_id(iseq, idx, level), Qfalse); +} + +static void +pm_iseq_add_setlocal(rb_iseq_t *iseq, LINK_ANCHOR *const seq, int line_no, int column, int idx, int level) +{ + if (iseq_local_block_param_p(iseq, idx, level)) { + ADD_ELEM(seq, (LINK_ELEMENT *) new_insn_body(iseq, line_no, column, BIN(setblockparam), 2, INT2FIX((idx) + VM_ENV_DATA_SIZE - 1), INT2FIX(level))); + } + else { + ADD_ELEM(seq, (LINK_ELEMENT *) new_insn_body(iseq, line_no, column, BIN(setlocal), 2, INT2FIX((idx) + VM_ENV_DATA_SIZE - 1), INT2FIX(level))); + } + if (level > 0) access_outer_variables(iseq, level, iseq_lvar_id(iseq, idx, level), Qtrue); +} + +#define PUSH_GETLOCAL(seq, location, idx, level) \ + pm_iseq_add_getlocal(iseq, (seq), (int) (location).line, (int) (location).column, (idx), (level)) + +#define PUSH_SETLOCAL(seq, location, idx, level) \ + pm_iseq_add_setlocal(iseq, (seq), (int) (location).line, (int) (location).column, (idx), (level)) /******************************************************************************/ /* These are helper macros for the compiler. */ @@ -68,54 +118,30 @@ #define PM_COMPILE_NOT_POPPED(node) \ pm_compile_node(iseq, (node), ret, false, scope_node) -#define PM_POP \ - ADD_INSN(ret, &dummy_line_node, pop) - -#define PM_POP_IF_POPPED \ - if (popped) PM_POP - -#define PM_POP_UNLESS_POPPED \ - if (!popped) PM_POP - -#define PM_DUP \ - ADD_INSN(ret, &dummy_line_node, dup) - -#define PM_DUP_UNLESS_POPPED \ - if (!popped) PM_DUP - -#define PM_PUTSELF \ - ADD_INSN(ret, &dummy_line_node, putself) - -#define PM_PUTNIL \ - ADD_INSN(ret, &dummy_line_node, putnil) - -#define PM_PUTNIL_UNLESS_POPPED \ - if (!popped) PM_PUTNIL - -#define PM_SWAP \ - ADD_INSN(ret, &dummy_line_node, swap) - -#define PM_SWAP_UNLESS_POPPED \ - if (!popped) PM_SWAP - -#define PM_NOP \ - ADD_INSN(ret, &dummy_line_node, nop) - #define PM_SPECIAL_CONSTANT_FLAG ((pm_constant_id_t)(1 << 31)) #define PM_CONSTANT_AND ((pm_constant_id_t)(idAnd | PM_SPECIAL_CONSTANT_FLAG)) #define PM_CONSTANT_DOT3 ((pm_constant_id_t)(idDot3 | PM_SPECIAL_CONSTANT_FLAG)) #define PM_CONSTANT_MULT ((pm_constant_id_t)(idMULT | PM_SPECIAL_CONSTANT_FLAG)) #define PM_CONSTANT_POW ((pm_constant_id_t)(idPow | PM_SPECIAL_CONSTANT_FLAG)) -static int -pm_location_line_number(const pm_parser_t *parser, const pm_location_t *location) { - return (int) pm_newline_list_line_column(&parser->newline_list, location->start, parser->start_line).line; -} +#define PM_NODE_START_LINE_COLUMN(parser, node) \ + pm_newline_list_line_column(&(parser)->newline_list, ((const pm_node_t *) (node))->location.start, (parser)->start_line) + +#define PM_NODE_END_LINE_COLUMN(parser, node) \ + pm_newline_list_line_column(&(parser)->newline_list, ((const pm_node_t *) (node))->location.end, (parser)->start_line) + +#define PM_LOCATION_LINE_COLUMN(parser, location) \ + pm_newline_list_line_column(&(parser)->newline_list, (location)->start, (parser)->start_line) static int pm_node_line_number(const pm_parser_t *parser, const pm_node_t *node) { - return (int) pm_newline_list_line_column(&parser->newline_list, node->location.start, parser->start_line).line; + return (int) PM_NODE_START_LINE_COLUMN(parser, node).line; +} + +static int +pm_location_line_number(const pm_parser_t *parser, const pm_location_t *location) { + return (int) PM_LOCATION_LINE_COLUMN(parser, location).line; } /** @@ -129,7 +155,8 @@ parse_integer(const pm_integer_node_t *node) if (integer->values == NULL) { result = UINT2NUM(integer->value); - } else { + } + else { VALUE string = rb_str_new(NULL, integer->length * 8); unsigned char *bytes = (unsigned char *) RSTRING_PTR(string); @@ -210,7 +237,7 @@ parse_rational(const pm_rational_node_t *node) * then convert into an imaginary with rb_complex_raw. */ static VALUE -parse_imaginary(pm_imaginary_node_t *node) +parse_imaginary(const pm_imaginary_node_t *node) { VALUE imaginary_part; switch (PM_NODE_TYPE(node->numeric)) { @@ -251,15 +278,46 @@ parse_string_encoded(const pm_scope_node_t *scope_node, const pm_node_t *node, c if (node->flags & PM_ENCODING_FLAGS_FORCED_BINARY_ENCODING) { encoding = rb_ascii8bit_encoding(); - } else if (node->flags & PM_ENCODING_FLAGS_FORCED_UTF8_ENCODING) { + } + else if (node->flags & PM_ENCODING_FLAGS_FORCED_UTF8_ENCODING) { encoding = rb_utf8_encoding(); - } else { + } + else { encoding = scope_node->encoding; } return rb_enc_str_new((const char *) pm_string_source(string), pm_string_length(string), encoding); } +static inline VALUE +parse_static_literal_string(rb_iseq_t *iseq, const pm_scope_node_t *scope_node, const pm_node_t *node, const pm_string_t *string) +{ + rb_encoding *encoding; + + if (node->flags & PM_ENCODING_FLAGS_FORCED_BINARY_ENCODING) { + encoding = rb_ascii8bit_encoding(); + } + else if (node->flags & PM_ENCODING_FLAGS_FORCED_UTF8_ENCODING) { + encoding = rb_utf8_encoding(); + } + else { + encoding = scope_node->encoding; + } + + VALUE value = rb_enc_interned_str((const char *) pm_string_source(string), pm_string_length(string), encoding); + rb_enc_str_coderange(value); + + if (ISEQ_COMPILE_DATA(iseq)->option->debug_frozen_string_literal || RTEST(ruby_debug)) { + int line_number = pm_node_line_number(scope_node->parser, node); + VALUE debug_info = rb_ary_new_from_args(2, rb_iseq_path(iseq), INT2FIX(line_number)); + value = rb_str_dup(value); + rb_ivar_set(value, id_debug_created_info, rb_obj_freeze(debug_info)); + rb_str_freeze(value); + } + + return value; +} + static inline ID parse_string_symbol(const pm_scope_node_t *scope_node, const pm_symbol_node_t *symbol) { @@ -281,104 +339,290 @@ parse_string_symbol(const pm_scope_node_t *scope_node, const pm_symbol_node_t *s } static int -pm_optimizable_range_item_p(pm_node_t *node) +pm_optimizable_range_item_p(const pm_node_t *node) { return (!node || PM_NODE_TYPE_P(node, PM_INTEGER_NODE) || PM_NODE_TYPE_P(node, PM_NIL_NODE)); } +static void pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, bool popped, pm_scope_node_t *scope_node); + +static int +pm_interpolated_node_compile(rb_iseq_t *iseq, const pm_node_list_t *parts, const pm_line_column_t *node_location, LINK_ANCHOR *const ret, bool popped, pm_scope_node_t *scope_node) +{ + int stack_size = 0; + size_t parts_size = parts->size; + bool interpolated = false; + + if (parts_size > 0) { + VALUE current_string = Qnil; + + for (size_t index = 0; index < parts_size; index++) { + const pm_node_t *part = parts->nodes[index]; + + if (PM_NODE_TYPE_P(part, PM_STRING_NODE)) { + const pm_string_node_t *string_node = (const pm_string_node_t *) part; + VALUE string_value = parse_string_encoded(scope_node, (const pm_node_t *) string_node, &string_node->unescaped); + + if (RTEST(current_string)) { + current_string = rb_str_concat(current_string, string_value); + } + else { + current_string = string_value; + } + } + else { + interpolated = true; + + if ( + PM_NODE_TYPE_P(part, PM_EMBEDDED_STATEMENTS_NODE) && + ((const pm_embedded_statements_node_t *) part)->statements != NULL && + ((const pm_embedded_statements_node_t *) part)->statements->body.size == 1 && + PM_NODE_TYPE_P(((const pm_embedded_statements_node_t *) part)->statements->body.nodes[0], PM_STRING_NODE) + ) { + const pm_string_node_t *string_node = (const pm_string_node_t *) ((const pm_embedded_statements_node_t *) part)->statements->body.nodes[0]; + VALUE string_value = parse_string_encoded(scope_node, (const pm_node_t *) string_node, &string_node->unescaped); + + if (RTEST(current_string)) { + current_string = rb_str_concat(current_string, string_value); + } + else { + current_string = string_value; + } + } + else { + if (!RTEST(current_string)) { + current_string = rb_enc_str_new(NULL, 0, scope_node->encoding); + } + + PUSH_INSN1(ret, *node_location, putobject, rb_fstring(current_string)); + PM_COMPILE_NOT_POPPED(part); + PUSH_INSN(ret, *node_location, dup); + PUSH_INSN1(ret, *node_location, objtostring, new_callinfo(iseq, idTo_s, 0, VM_CALL_FCALL | VM_CALL_ARGS_SIMPLE , NULL, FALSE)); + PUSH_INSN(ret, *node_location, anytostring); + + current_string = Qnil; + stack_size += 2; + } + } + } + + if (RTEST(current_string)) { + current_string = rb_fstring(current_string); + + if (stack_size == 0 && interpolated) { + PUSH_INSN1(ret, *node_location, putstring, current_string); + } + else { + PUSH_INSN1(ret, *node_location, putobject, current_string); + } + + current_string = Qnil; + stack_size++; + } + } + else { + PUSH_INSN(ret, *node_location, putnil); + } + + return stack_size; +} + +static VALUE +pm_static_literal_concat(const pm_node_list_t *nodes, const pm_scope_node_t *scope_node, bool top) +{ + VALUE current = Qnil; + + for (size_t index = 0; index < nodes->size; index++) { + const pm_node_t *part = nodes->nodes[index]; + VALUE string; + + switch (PM_NODE_TYPE(part)) { + case PM_STRING_NODE: + string = parse_string_encoded(scope_node, part, &((const pm_string_node_t *) part)->unescaped); + break; + case PM_INTERPOLATED_STRING_NODE: + string = pm_static_literal_concat(&((const pm_interpolated_string_node_t *) part)->parts, scope_node, false); + break; + default: + RUBY_ASSERT(false && "unexpected node type in pm_static_literal_concat"); + return Qnil; + } + + if (current != Qnil) { + current = rb_str_concat(current, string); + } + else { + current = string; + } + } + + return top ? rb_fstring(current) : current; +} + #define RE_OPTION_ENCODING_SHIFT 8 +#define RE_OPTION_ENCODING(encoding) (((encoding) & 0xFF) << RE_OPTION_ENCODING_SHIFT) +#define ARG_ENCODING_NONE 32 +#define ARG_ENCODING_FIXED 16 +#define ENC_ASCII8BIT 1 +#define ENC_EUC_JP 2 +#define ENC_Windows_31J 3 +#define ENC_UTF8 4 /** * Check the prism flags of a regular expression-like node and return the flags * that are expected by the CRuby VM. */ static int -pm_reg_flags(const pm_node_t *node) { +parse_regexp_flags(const pm_node_t *node) +{ int flags = 0; - int dummy = 0; // Check "no encoding" first so that flags don't get clobbered // We're calling `rb_char_to_option_kcode` in this case so that // we don't need to have access to `ARG_ENCODING_NONE` - if (node->flags & PM_REGULAR_EXPRESSION_FLAGS_ASCII_8BIT) { - rb_char_to_option_kcode('n', &flags, &dummy); + if (PM_NODE_FLAG_P(node, PM_REGULAR_EXPRESSION_FLAGS_ASCII_8BIT)) { + flags |= ARG_ENCODING_NONE; } - if (node->flags & PM_REGULAR_EXPRESSION_FLAGS_EUC_JP) { - rb_char_to_option_kcode('e', &flags, &dummy); - flags |= ('e' << RE_OPTION_ENCODING_SHIFT); + if (PM_NODE_FLAG_P(node, PM_REGULAR_EXPRESSION_FLAGS_EUC_JP)) { + flags |= (ARG_ENCODING_FIXED | RE_OPTION_ENCODING(ENC_EUC_JP)); } - if (node->flags & PM_REGULAR_EXPRESSION_FLAGS_WINDOWS_31J) { - rb_char_to_option_kcode('s', &flags, &dummy); - flags |= ('s' << RE_OPTION_ENCODING_SHIFT); + if (PM_NODE_FLAG_P(node, PM_REGULAR_EXPRESSION_FLAGS_WINDOWS_31J)) { + flags |= (ARG_ENCODING_FIXED | RE_OPTION_ENCODING(ENC_Windows_31J)); } - if (node->flags & PM_REGULAR_EXPRESSION_FLAGS_UTF_8) { - rb_char_to_option_kcode('u', &flags, &dummy); - flags |= ('u' << RE_OPTION_ENCODING_SHIFT); + if (PM_NODE_FLAG_P(node, PM_REGULAR_EXPRESSION_FLAGS_UTF_8)) { + flags |= (ARG_ENCODING_FIXED | RE_OPTION_ENCODING(ENC_UTF8)); } - if (node->flags & PM_REGULAR_EXPRESSION_FLAGS_IGNORE_CASE) { + if (PM_NODE_FLAG_P(node, PM_REGULAR_EXPRESSION_FLAGS_IGNORE_CASE)) { flags |= ONIG_OPTION_IGNORECASE; } - if (node->flags & PM_REGULAR_EXPRESSION_FLAGS_MULTI_LINE) { + if (PM_NODE_FLAG_P(node, PM_REGULAR_EXPRESSION_FLAGS_MULTI_LINE)) { flags |= ONIG_OPTION_MULTILINE; } - if (node->flags & PM_REGULAR_EXPRESSION_FLAGS_EXTENDED) { + if (PM_NODE_FLAG_P(node, PM_REGULAR_EXPRESSION_FLAGS_EXTENDED)) { flags |= ONIG_OPTION_EXTEND; } return flags; } +#undef RE_OPTION_ENCODING_SHIFT +#undef RE_OPTION_ENCODING +#undef ARG_ENCODING_FIXED +#undef ARG_ENCODING_NONE +#undef ENC_ASCII8BIT +#undef ENC_EUC_JP +#undef ENC_Windows_31J +#undef ENC_UTF8 + static rb_encoding * -pm_reg_enc(const pm_scope_node_t *scope_node, const pm_regular_expression_node_t *node) +parse_regexp_encoding(const pm_scope_node_t *scope_node, const pm_node_t *node) { - if (node->base.flags & PM_REGULAR_EXPRESSION_FLAGS_ASCII_8BIT) { + if (PM_NODE_FLAG_P(node, PM_REGULAR_EXPRESSION_FLAGS_ASCII_8BIT)) { return rb_ascii8bit_encoding(); } - - if (node->base.flags & PM_REGULAR_EXPRESSION_FLAGS_EUC_JP) { + else if (PM_NODE_FLAG_P(node, PM_REGULAR_EXPRESSION_FLAGS_UTF_8)) { + return rb_utf8_encoding(); + } + else if (PM_NODE_FLAG_P(node, PM_REGULAR_EXPRESSION_FLAGS_EUC_JP)) { return rb_enc_get_from_index(ENCINDEX_EUC_JP); } - - if (node->base.flags & PM_REGULAR_EXPRESSION_FLAGS_WINDOWS_31J) { + else if (PM_NODE_FLAG_P(node, PM_REGULAR_EXPRESSION_FLAGS_WINDOWS_31J)) { return rb_enc_get_from_index(ENCINDEX_Windows_31J); } + else { + return scope_node->encoding; + } +} - if (node->base.flags & PM_REGULAR_EXPRESSION_FLAGS_UTF_8) { - return rb_utf8_encoding(); +/** Raise an error corresponding to the invalid regular expression. */ +static VALUE +parse_regexp_error(rb_iseq_t *iseq, int32_t line_number, const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + VALUE error = rb_syntax_error_append(Qnil, rb_iseq_path(iseq), line_number, -1, NULL, "%" PRIsVALUE, args); + va_end(args); + rb_exc_raise(error); +} + +static VALUE +parse_regexp(rb_iseq_t *iseq, const pm_scope_node_t *scope_node, const pm_node_t *node, VALUE string) +{ + VALUE errinfo = rb_errinfo(); + + int32_t line_number = pm_node_line_number(scope_node->parser, node); + VALUE regexp = rb_reg_compile(string, parse_regexp_flags(node), (const char *) pm_string_source(&scope_node->parser->filepath), line_number); + + if (NIL_P(regexp)) { + VALUE message = rb_attr_get(rb_errinfo(), idMesg); + rb_set_errinfo(errinfo); + + parse_regexp_error(iseq, line_number, "%" PRIsVALUE, message); + return Qnil; } - return scope_node->encoding; + rb_obj_freeze(regexp); + return regexp; } -/** - * Certain nodes can be compiled literally, which can lead to further - * optimizations. These nodes will all have the PM_NODE_FLAG_STATIC_LITERAL flag - * set. - */ -static inline bool -pm_static_literal_p(const pm_node_t *node) +static inline VALUE +parse_regexp_literal(rb_iseq_t *iseq, const pm_scope_node_t *scope_node, const pm_node_t *node, const pm_string_t *unescaped) { - return node->flags & PM_NODE_FLAG_STATIC_LITERAL; + VALUE string = rb_enc_str_new((const char *) pm_string_source(unescaped), pm_string_length(unescaped), parse_regexp_encoding(scope_node, node)); + return parse_regexp(iseq, scope_node, node, string); } -static VALUE -pm_new_regex(const pm_scope_node_t *scope_node, const pm_regular_expression_node_t *node) +static inline VALUE +parse_regexp_concat(rb_iseq_t *iseq, const pm_scope_node_t *scope_node, const pm_node_t *node, const pm_node_list_t *parts) { - VALUE regex_str = parse_string(scope_node, &node->unescaped); - rb_encoding *enc = pm_reg_enc(scope_node, node); + VALUE string = pm_static_literal_concat(parts, scope_node, false); + rb_enc_associate(string, parse_regexp_encoding(scope_node, node)); + return parse_regexp(iseq, scope_node, node, string); +} - VALUE regex = rb_enc_reg_new(RSTRING_PTR(regex_str), RSTRING_LEN(regex_str), enc, pm_reg_flags((const pm_node_t *) node)); - RB_GC_GUARD(regex_str); +static void +pm_compile_regexp_dynamic(rb_iseq_t *iseq, const pm_node_t *node, const pm_node_list_t *parts, const pm_line_column_t *node_location, LINK_ANCHOR *const ret, bool popped, pm_scope_node_t *scope_node) +{ + int length = pm_interpolated_node_compile(iseq, parts, node_location, ret, popped, scope_node); + PUSH_INSN2(ret, *node_location, toregexp, INT2FIX(parse_regexp_flags(node) & 0xFF), INT2FIX(length)); +} - rb_obj_freeze(regex); +static VALUE +pm_source_file_value(const pm_source_file_node_t *node, const pm_scope_node_t *scope_node) +{ + const pm_string_t *filepath = &node->filepath; + size_t length = pm_string_length(filepath); + + if (length > 0) { + rb_encoding *filepath_encoding = scope_node->filepath_encoding != NULL ? scope_node->filepath_encoding : rb_utf8_encoding(); + return rb_enc_interned_str((const char *) pm_string_source(filepath), length, filepath_encoding); + } + else { + return rb_fstring_lit(""); + } +} - return regex; +/** + * Return a static literal string, optionally with attached debugging + * information. + */ +static VALUE +pm_static_literal_string(rb_iseq_t *iseq, VALUE string, int line_number) +{ + if (ISEQ_COMPILE_DATA(iseq)->option->debug_frozen_string_literal || RTEST(ruby_debug)) { + VALUE debug_info = rb_ary_new_from_args(2, rb_iseq_path(iseq), INT2FIX(line_number)); + rb_ivar_set(string, id_debug_created_info, rb_obj_freeze(debug_info)); + return rb_str_freeze(string); + } + else { + return rb_fstring(string); + } } /** @@ -386,21 +630,21 @@ pm_new_regex(const pm_scope_node_t *scope_node, const pm_regular_expression_node * value described by the given node. For example, an array node with all static * literal values can be compiled into a literal array. */ -static inline VALUE -pm_static_literal_value(const pm_node_t *node, const pm_scope_node_t *scope_node) +static VALUE +pm_static_literal_value(rb_iseq_t *iseq, const pm_node_t *node, const pm_scope_node_t *scope_node) { // Every node that comes into this function should already be marked as // static literal. If it's not, then we have a bug somewhere. - RUBY_ASSERT(pm_static_literal_p(node)); + RUBY_ASSERT(PM_NODE_FLAG_P(node, PM_NODE_FLAG_STATIC_LITERAL)); switch (PM_NODE_TYPE(node)) { case PM_ARRAY_NODE: { - pm_array_node_t *cast = (pm_array_node_t *) node; - pm_node_list_t *elements = &cast->elements; + const pm_array_node_t *cast = (const pm_array_node_t *) node; + const pm_node_list_t *elements = &cast->elements; VALUE value = rb_ary_hidden_new(elements->size); for (size_t index = 0; index < elements->size; index++) { - rb_ary_push(value, pm_static_literal_value(elements->nodes[index], scope_node)); + rb_ary_push(value, pm_static_literal_value(iseq, elements->nodes[index], scope_node)); } OBJ_FREEZE(value); @@ -411,14 +655,14 @@ pm_static_literal_value(const pm_node_t *node, const pm_scope_node_t *scope_node case PM_FLOAT_NODE: return parse_float((const pm_float_node_t *) node); case PM_HASH_NODE: { - pm_hash_node_t *cast = (pm_hash_node_t *) node; - pm_node_list_t *elements = &cast->elements; + const pm_hash_node_t *cast = (const pm_hash_node_t *) node; + const pm_node_list_t *elements = &cast->elements; VALUE array = rb_ary_hidden_new(elements->size * 2); for (size_t index = 0; index < elements->size; index++) { RUBY_ASSERT(PM_NODE_TYPE_P(elements->nodes[index], PM_ASSOC_NODE)); - pm_assoc_node_t *cast = (pm_assoc_node_t *) elements->nodes[index]; - VALUE pair[2] = { pm_static_literal_value(cast->key, scope_node), pm_static_literal_value(cast->value, scope_node) }; + const pm_assoc_node_t *cast = (const pm_assoc_node_t *) elements->nodes[index]; + VALUE pair[2] = { pm_static_literal_value(iseq, cast->key, scope_node), pm_static_literal_value(iseq, cast->value, scope_node) }; rb_ary_cat(array, pair, 2); } @@ -430,25 +674,52 @@ pm_static_literal_value(const pm_node_t *node, const pm_scope_node_t *scope_node return value; } case PM_IMAGINARY_NODE: - return parse_imaginary((pm_imaginary_node_t *) node); + return parse_imaginary((const pm_imaginary_node_t *) node); case PM_INTEGER_NODE: return parse_integer((const pm_integer_node_t *) node); + case PM_INTERPOLATED_MATCH_LAST_LINE_NODE: { + const pm_interpolated_match_last_line_node_t *cast = (const pm_interpolated_match_last_line_node_t *) node; + return parse_regexp_concat(iseq, scope_node, (const pm_node_t *) cast, &cast->parts); + } + case PM_INTERPOLATED_REGULAR_EXPRESSION_NODE: { + const pm_interpolated_regular_expression_node_t *cast = (const pm_interpolated_regular_expression_node_t *) node; + return parse_regexp_concat(iseq, scope_node, (const pm_node_t *) cast, &cast->parts); + } + case PM_INTERPOLATED_STRING_NODE: { + VALUE string = pm_static_literal_concat(&((const pm_interpolated_string_node_t *) node)->parts, scope_node, false); + int line_number = pm_node_line_number(scope_node->parser, node); + return pm_static_literal_string(iseq, string, line_number); + } + case PM_INTERPOLATED_SYMBOL_NODE: { + const pm_interpolated_symbol_node_t *cast = (const pm_interpolated_symbol_node_t *) node; + VALUE string = pm_static_literal_concat(&cast->parts, scope_node, true); + + return ID2SYM(rb_intern_str(string)); + } + case PM_MATCH_LAST_LINE_NODE: { + const pm_match_last_line_node_t *cast = (const pm_match_last_line_node_t *) node; + return parse_regexp_literal(iseq, scope_node, (const pm_node_t *) cast, &cast->unescaped); + } case PM_NIL_NODE: return Qnil; case PM_RATIONAL_NODE: return parse_rational((const pm_rational_node_t *) node); - case PM_REGULAR_EXPRESSION_NODE: - return pm_new_regex(scope_node, (const pm_regular_expression_node_t *) node); + case PM_REGULAR_EXPRESSION_NODE: { + const pm_regular_expression_node_t *cast = (const pm_regular_expression_node_t *) node; + return parse_regexp_literal(iseq, scope_node, (const pm_node_t *) cast, &cast->unescaped); + } case PM_SOURCE_ENCODING_NODE: return rb_enc_from_encoding(scope_node->encoding); case PM_SOURCE_FILE_NODE: { - pm_source_file_node_t *cast = (pm_source_file_node_t *)node; - return cast->filepath.length ? parse_string(scope_node, &cast->filepath) : rb_fstring_lit(""); + const pm_source_file_node_t *cast = (const pm_source_file_node_t *) node; + return pm_source_file_value(cast, scope_node); } case PM_SOURCE_LINE_NODE: return INT2FIX(pm_node_line_number(scope_node->parser, node)); - case PM_STRING_NODE: - return parse_string_encoded(scope_node, node, &((pm_string_node_t *)node)->unescaped); + case PM_STRING_NODE: { + const pm_string_node_t *cast = (const pm_string_node_t *) node; + return parse_static_literal_string(iseq, scope_node, node, &cast->unescaped); + } case PM_SYMBOL_NODE: return ID2SYM(parse_string_symbol(scope_node, (const pm_symbol_node_t *) node)); case PM_TRUE_NODE: @@ -459,50 +730,6 @@ pm_static_literal_value(const pm_node_t *node, const pm_scope_node_t *scope_node } } -/** - * Currently, the ADD_INSN family of macros expects a NODE as the second - * parameter. It uses this node to determine the line number and the node ID for - * the instruction. - * - * Because prism does not use the NODE struct (or have node IDs for that matter) - * we need to generate a dummy node to pass to these macros. We also need to use - * the line number from the node to generate labels. - * - * We use this struct to store the dummy node and the line number together so - * that we can use it while we're compiling code. - * - * In the future, we'll need to eventually remove this dependency and figure out - * a more permanent solution. For the line numbers, this shouldn't be too much - * of a problem, we can redefine the ADD_INSN family of macros. For the node ID, - * we can probably replace it directly with the column information since we have - * that at the time that we're generating instructions. In theory this could - * make node ID unnecessary. - */ -typedef struct { - NODE node; - int lineno; -} pm_line_node_t; - -/** - * The function generates a dummy node and stores the line number after it looks - * it up for the given scope and node. (The scope in this case is just used - * because it holds a reference to the parser, which holds a reference to the - * newline list that we need to look up the line numbers.) - */ -static void -pm_line_node(pm_line_node_t *line_node, const pm_scope_node_t *scope_node, const pm_node_t *node) -{ - // First, clear out the pointer. - memset(line_node, 0, sizeof(pm_line_node_t)); - - // Next, use the line number for the dummy node. - int lineno = pm_node_line_number(scope_node->parser, node); - - nd_set_line(&line_node->node, lineno); - nd_set_node_id(&line_node->node, lineno); - line_node->lineno = lineno; -} - static void pm_compile_branch_condition(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const pm_node_t *cond, LABEL *then_label, LABEL *else_label, bool popped, pm_scope_node_t *scope_node); @@ -510,46 +737,43 @@ pm_compile_branch_condition(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const pm_no static void pm_compile_logical(rb_iseq_t *iseq, LINK_ANCHOR *const ret, pm_node_t *cond, LABEL *then_label, LABEL *else_label, bool popped, pm_scope_node_t *scope_node) { - int lineno = pm_node_line_number(scope_node->parser, cond); - NODE dummy_line_node = generate_dummy_line_node(lineno, lineno); + const pm_line_column_t location = PM_NODE_START_LINE_COLUMN(scope_node->parser, cond); DECL_ANCHOR(seq); INIT_ANCHOR(seq); - LABEL *label = NEW_LABEL(lineno); + + LABEL *label = NEW_LABEL(location.line); if (!then_label) then_label = label; else if (!else_label) else_label = label; pm_compile_branch_condition(iseq, seq, cond, then_label, else_label, popped, scope_node); if (LIST_INSN_SIZE_ONE(seq)) { - INSN *insn = (INSN *)ELEM_FIRST_INSN(FIRST_ELEMENT(seq)); - if (insn->insn_id == BIN(jump) && (LABEL *)(insn->operands[0]) == label) - return; + INSN *insn = (INSN *) ELEM_FIRST_INSN(FIRST_ELEMENT(seq)); + if (insn->insn_id == BIN(jump) && (LABEL *)(insn->operands[0]) == label) return; } + if (!label->refcnt) { - if (popped) { - PM_PUTNIL; - } + if (popped) PUSH_INSN(ret, location, putnil); } else { - ADD_LABEL(seq, label); + PUSH_LABEL(seq, label); } - ADD_SEQ(ret, seq); + + PUSH_SEQ(ret, seq); return; } -static void pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, bool popped, pm_scope_node_t *scope_node); - static void pm_compile_flip_flop_bound(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, bool popped, pm_scope_node_t *scope_node) { - NODE dummy_line_node = generate_dummy_line_node(ISEQ_BODY(iseq)->location.first_lineno, -1); + const pm_line_column_t location = { .line = ISEQ_BODY(iseq)->location.first_lineno, .column = -1 }; if (PM_NODE_TYPE_P(node, PM_INTEGER_NODE)) { PM_COMPILE_NOT_POPPED(node); - ADD_INSN1(ret, &dummy_line_node, getglobal, ID2SYM(rb_intern("$."))); - ADD_SEND(ret, &dummy_line_node, idEq, INT2FIX(1)); - PM_POP_IF_POPPED; + PUSH_INSN1(ret, location, getglobal, ID2SYM(rb_intern("$."))); + PUSH_SEND(ret, location, idEq, INT2FIX(1)); + if (popped) PUSH_INSN(ret, location, pop); } else { PM_COMPILE(node); @@ -559,71 +783,71 @@ pm_compile_flip_flop_bound(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR * static void pm_compile_flip_flop(const pm_flip_flop_node_t *flip_flop_node, LABEL *else_label, LABEL *then_label, rb_iseq_t *iseq, const int lineno, LINK_ANCHOR *const ret, bool popped, pm_scope_node_t *scope_node) { - NODE dummy_line_node = generate_dummy_line_node(ISEQ_BODY(iseq)->location.first_lineno, -1); - LABEL *lend = NEW_LABEL(lineno); + const pm_line_column_t location = { .line = ISEQ_BODY(iseq)->location.first_lineno, .column = -1 }; + LABEL *lend = NEW_LABEL(location.line); int again = !(flip_flop_node->base.flags & PM_RANGE_FLAGS_EXCLUDE_END); rb_num_t count = ISEQ_FLIP_CNT_INCREMENT(ISEQ_BODY(iseq)->local_iseq) + VM_SVAR_FLIPFLOP_START; VALUE key = INT2FIX(count); - ADD_INSN2(ret, &dummy_line_node, getspecial, key, INT2FIX(0)); - ADD_INSNL(ret, &dummy_line_node, branchif, lend); + PUSH_INSN2(ret, location, getspecial, key, INT2FIX(0)); + PUSH_INSNL(ret, location, branchif, lend); if (flip_flop_node->left) { pm_compile_flip_flop_bound(iseq, flip_flop_node->left, ret, popped, scope_node); } else { - PM_PUTNIL; + PUSH_INSN(ret, location, putnil); } - ADD_INSNL(ret, &dummy_line_node, branchunless, else_label); - ADD_INSN1(ret, &dummy_line_node, putobject, Qtrue); - ADD_INSN1(ret, &dummy_line_node, setspecial, key); + PUSH_INSNL(ret, location, branchunless, else_label); + PUSH_INSN1(ret, location, putobject, Qtrue); + PUSH_INSN1(ret, location, setspecial, key); if (!again) { - ADD_INSNL(ret, &dummy_line_node, jump, then_label); + PUSH_INSNL(ret, location, jump, then_label); } - ADD_LABEL(ret, lend); + PUSH_LABEL(ret, lend); if (flip_flop_node->right) { pm_compile_flip_flop_bound(iseq, flip_flop_node->right, ret, popped, scope_node); } else { - PM_PUTNIL; + PUSH_INSN(ret, location, putnil); } - ADD_INSNL(ret, &dummy_line_node, branchunless, then_label); - ADD_INSN1(ret, &dummy_line_node, putobject, Qfalse); - ADD_INSN1(ret, &dummy_line_node, setspecial, key); - ADD_INSNL(ret, &dummy_line_node, jump, then_label); + PUSH_INSNL(ret, location, branchunless, then_label); + PUSH_INSN1(ret, location, putobject, Qfalse); + PUSH_INSN1(ret, location, setspecial, key); + PUSH_INSNL(ret, location, jump, then_label); } -void pm_compile_defined_expr(rb_iseq_t *iseq, const pm_node_t *defined_node, LINK_ANCHOR *const ret, bool popped, pm_scope_node_t *scope_node, NODE dummy_line_node, int lineno, bool in_condition); +static void pm_compile_defined_expr(rb_iseq_t *iseq, const pm_node_t *node, const pm_line_column_t *node_location, LINK_ANCHOR *const ret, bool popped, pm_scope_node_t *scope_node, bool in_condition); static void -pm_compile_branch_condition(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const pm_node_t *cond, - LABEL *then_label, LABEL *else_label, bool popped, pm_scope_node_t *scope_node) +pm_compile_branch_condition(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const pm_node_t *cond, LABEL *then_label, LABEL *else_label, bool popped, pm_scope_node_t *scope_node) { - int lineno = pm_node_line_number(scope_node->parser, cond); - NODE dummy_line_node = generate_dummy_line_node(lineno, lineno); + const pm_line_column_t location = PM_NODE_START_LINE_COLUMN(scope_node->parser, cond); again: switch (PM_NODE_TYPE(cond)) { case PM_AND_NODE: { - pm_and_node_t *and_node = (pm_and_node_t *)cond; - pm_compile_logical(iseq, ret, and_node->left, NULL, else_label, popped, scope_node); - cond = and_node->right; + const pm_and_node_t *cast = (const pm_and_node_t *) cond; + pm_compile_logical(iseq, ret, cast->left, NULL, else_label, popped, scope_node); + + cond = cast->right; goto again; } case PM_OR_NODE: { - pm_or_node_t *or_node = (pm_or_node_t *)cond; - pm_compile_logical(iseq, ret, or_node->left, then_label, NULL, popped, scope_node); - cond = or_node->right; + const pm_or_node_t *cast = (const pm_or_node_t *) cond; + pm_compile_logical(iseq, ret, cast->left, then_label, NULL, popped, scope_node); + + cond = cast->right; goto again; } case PM_FALSE_NODE: case PM_NIL_NODE: - ADD_INSNL(ret, &dummy_line_node, jump, else_label); + PUSH_INSNL(ret, location, jump, else_label); return; case PM_FLOAT_NODE: case PM_IMAGINARY_NODE: @@ -634,14 +858,14 @@ pm_compile_branch_condition(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const pm_no case PM_STRING_NODE: case PM_SYMBOL_NODE: case PM_TRUE_NODE: - ADD_INSNL(ret, &dummy_line_node, jump, then_label); + PUSH_INSNL(ret, location, jump, then_label); return; case PM_FLIP_FLOP_NODE: - pm_compile_flip_flop((const pm_flip_flop_node_t *) cond, else_label, then_label, iseq, lineno, ret, popped, scope_node); + pm_compile_flip_flop((const pm_flip_flop_node_t *) cond, else_label, then_label, iseq, location.line, ret, popped, scope_node); return; case PM_DEFINED_NODE: { - pm_defined_node_t *defined_node = (pm_defined_node_t *)cond; - pm_compile_defined_expr(iseq, defined_node->value, ret, popped, scope_node, dummy_line_node, lineno, true); + const pm_defined_node_t *cast = (const pm_defined_node_t *) cond; + pm_compile_defined_expr(iseq, cast->value, &location, ret, popped, scope_node, true); break; } default: { @@ -649,9 +873,9 @@ pm_compile_branch_condition(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const pm_no break; } } - ADD_INSNL(ret, &dummy_line_node, branchunless, else_label); - ADD_INSNL(ret, &dummy_line_node, jump, then_label); - return; + + PUSH_INSNL(ret, location, branchunless, else_label); + PUSH_INSNL(ret, location, jump, then_label); } /** @@ -686,7 +910,7 @@ pm_compile_conditional(rb_iseq_t *iseq, const pm_line_column_t *line_column, con if (!popped) PUSH_INSN(then_seq, location, pop); } - ADD_SEQ(ret, then_seq); + PUSH_SEQ(ret, then_seq); } if (else_label->refcnt) { @@ -702,7 +926,7 @@ pm_compile_conditional(rb_iseq_t *iseq, const pm_line_column_t *line_column, con PUSH_INSN(else_seq, location, putnil); } - ADD_SEQ(ret, else_seq); + PUSH_SEQ(ret, else_seq); } if (end_label) { @@ -757,7 +981,8 @@ pm_compile_loop(rb_iseq_t *iseq, const pm_line_column_t *line_column, pm_node_fl PUSH_LABEL(ret, next_label); if (type == PM_WHILE_NODE) { pm_compile_branch_condition(iseq, ret, predicate, redo_label, end_label, popped, scope_node); - } else if (type == PM_UNTIL_NODE) { + } + else if (type == PM_UNTIL_NODE) { pm_compile_branch_condition(iseq, ret, predicate, end_label, redo_label, popped, scope_node); } @@ -768,9 +993,9 @@ pm_compile_loop(rb_iseq_t *iseq, const pm_line_column_t *line_column, pm_node_fl PUSH_LABEL(ret, break_label); if (popped) PUSH_INSN(ret, location, pop); - ADD_CATCH_ENTRY(CATCH_TYPE_BREAK, redo_label, break_label, NULL, break_label); - ADD_CATCH_ENTRY(CATCH_TYPE_NEXT, redo_label, break_label, NULL, next_catch_label); - ADD_CATCH_ENTRY(CATCH_TYPE_REDO, redo_label, break_label, NULL, ISEQ_COMPILE_DATA(iseq)->redo_label); + PUSH_CATCH_ENTRY(CATCH_TYPE_BREAK, redo_label, break_label, NULL, break_label); + PUSH_CATCH_ENTRY(CATCH_TYPE_NEXT, redo_label, break_label, NULL, next_catch_label); + PUSH_CATCH_ENTRY(CATCH_TYPE_REDO, redo_label, break_label, NULL, ISEQ_COMPILE_DATA(iseq)->redo_label); ISEQ_COMPILE_DATA(iseq)->start_label = prev_start_label; ISEQ_COMPILE_DATA(iseq)->end_label = prev_end_label; @@ -778,87 +1003,6 @@ pm_compile_loop(rb_iseq_t *iseq, const pm_line_column_t *line_column, pm_node_fl return; } -static int -pm_interpolated_node_compile(pm_node_list_t *parts, rb_iseq_t *iseq, NODE dummy_line_node, LINK_ANCHOR *const ret, bool popped, pm_scope_node_t *scope_node) -{ - int number_of_items_pushed = 0; - size_t parts_size = parts->size; - - if (parts_size > 0) { - VALUE current_string = Qnil; - - for (size_t index = 0; index < parts_size; index++) { - const pm_node_t *part = parts->nodes[index]; - - if (PM_NODE_TYPE_P(part, PM_STRING_NODE)) { - const pm_string_node_t *string_node = (const pm_string_node_t *)part; - VALUE string_value = parse_string_encoded(scope_node, (pm_node_t *)string_node, &string_node->unescaped); - - if (RTEST(current_string)) { - current_string = rb_str_concat(current_string, string_value); - } - else { - current_string = string_value; - } - } - else if (PM_NODE_TYPE_P(part, PM_EMBEDDED_STATEMENTS_NODE) && - ((const pm_embedded_statements_node_t *) part)->statements != NULL && - ((const pm_embedded_statements_node_t *) part)->statements->body.size == 1 && - PM_NODE_TYPE_P(((const pm_embedded_statements_node_t *) part)->statements->body.nodes[0], PM_STRING_NODE)) { - const pm_string_node_t *string_node = (const pm_string_node_t *) ((const pm_embedded_statements_node_t *) part)->statements->body.nodes[0]; - VALUE string_value = parse_string_encoded(scope_node, (pm_node_t *)string_node, &string_node->unescaped); - - if (RTEST(current_string)) { - current_string = rb_str_concat(current_string, string_value); - } - else { - current_string = string_value; - } - } - else { - if (!RTEST(current_string)) { - current_string = rb_enc_str_new(NULL, 0, scope_node->encoding); - } - - if (ISEQ_COMPILE_DATA(iseq)->option->frozen_string_literal) { - ADD_INSN1(ret, &dummy_line_node, putobject, rb_str_freeze(current_string)); - } - else { - ADD_INSN1(ret, &dummy_line_node, putstring, rb_str_freeze(current_string)); - } - - current_string = Qnil; - number_of_items_pushed++; - - PM_COMPILE_NOT_POPPED(part); - PM_DUP; - ADD_INSN1(ret, &dummy_line_node, objtostring, new_callinfo(iseq, idTo_s, 0, VM_CALL_FCALL | VM_CALL_ARGS_SIMPLE , NULL, FALSE)); - ADD_INSN(ret, &dummy_line_node, anytostring); - - number_of_items_pushed++; - } - } - - if (RTEST(current_string)) { - current_string = rb_fstring(current_string); - - if (ISEQ_COMPILE_DATA(iseq)->option->frozen_string_literal) { - ADD_INSN1(ret, &dummy_line_node, putobject, current_string); - } - else { - ADD_INSN1(ret, &dummy_line_node, putstring, current_string); - } - - current_string = Qnil; - number_of_items_pushed++; - } - } - else { - PM_PUTNIL; - } - return number_of_items_pushed; -} - // This recurses through scopes and finds the local index at any scope level // It also takes a pointer to depth, and increments depth appropriately // according to the depth of the local. @@ -878,7 +1022,8 @@ pm_lookup_local_index(rb_iseq_t *iseq, const pm_scope_node_t *scope_node, pm_con if (scope_node->previous) { scope_node = scope_node->previous; - } else { + } + else { // We have recursed up all scope nodes // and have not found the local yet rb_bug("Local with constant_id %u does not exist", (unsigned int) constant_id); @@ -920,10 +1065,11 @@ pm_new_child_iseq(rb_iseq_t *iseq, pm_scope_node_t *node, VALUE name, const rb_i } static int -pm_compile_class_path(LINK_ANCHOR *const ret, rb_iseq_t *iseq, const pm_node_t *constant_path_node, const NODE *line_node, bool popped, pm_scope_node_t *scope_node) +pm_compile_class_path(rb_iseq_t *iseq, const pm_node_t *node, const pm_line_column_t *node_location, LINK_ANCHOR *const ret, bool popped, pm_scope_node_t *scope_node) { - if (PM_NODE_TYPE_P(constant_path_node, PM_CONSTANT_PATH_NODE)) { - pm_node_t *parent = ((pm_constant_path_node_t *)constant_path_node)->parent; + if (PM_NODE_TYPE_P(node, PM_CONSTANT_PATH_NODE)) { + const pm_node_t *parent = ((const pm_constant_path_node_t *) node)->parent; + if (parent) { /* Bar::Foo */ PM_COMPILE(parent); @@ -931,14 +1077,13 @@ pm_compile_class_path(LINK_ANCHOR *const ret, rb_iseq_t *iseq, const pm_node_t * } else { /* toplevel class ::Foo */ - ADD_INSN1(ret, line_node, putobject, rb_cObject); + PUSH_INSN1(ret, *node_location, putobject, rb_cObject); return VM_DEFINECLASS_FLAG_SCOPED; } } else { /* class at cbase Foo */ - ADD_INSN1(ret, line_node, putspecialobject, - INT2FIX(VM_SPECIAL_OBJECT_CONST_BASE)); + PUSH_INSN1(ret, *node_location, putspecialobject, INT2FIX(VM_SPECIAL_OBJECT_CONST_BASE)); return 0; } } @@ -948,12 +1093,11 @@ pm_compile_class_path(LINK_ANCHOR *const ret, rb_iseq_t *iseq, const pm_node_t * * method calls that are followed by a ||= or &&= operator. */ static void -pm_compile_call_and_or_write_node(bool and_node, pm_node_t *receiver, pm_node_t *value, pm_constant_id_t write_name, pm_constant_id_t read_name, bool safe_nav, LINK_ANCHOR *const ret, rb_iseq_t *iseq, int lineno, bool popped, pm_scope_node_t *scope_node) +pm_compile_call_and_or_write_node(rb_iseq_t *iseq, bool and_node, const pm_node_t *receiver, const pm_node_t *value, pm_constant_id_t write_name, pm_constant_id_t read_name, bool safe_nav, const pm_line_column_t *node_location, LINK_ANCHOR *const ret, bool popped, pm_scope_node_t *scope_node) { - NODE dummy_line_node = generate_dummy_line_node(lineno, lineno); - - LABEL *lfin = NEW_LABEL(lineno); - LABEL *lcfin = NEW_LABEL(lineno); + const pm_line_column_t location = *node_location; + LABEL *lfin = NEW_LABEL(location.line); + LABEL *lcfin = NEW_LABEL(location.line); LABEL *lskip = NULL; int flag = PM_NODE_TYPE_P(receiver, PM_SELF_NODE) ? VM_CALL_FCALL : 0; @@ -961,42 +1105,42 @@ pm_compile_call_and_or_write_node(bool and_node, pm_node_t *receiver, pm_node_t PM_COMPILE_NOT_POPPED(receiver); if (safe_nav) { - lskip = NEW_LABEL(lineno); - PM_DUP; - ADD_INSNL(ret, &dummy_line_node, branchnil, lskip); + lskip = NEW_LABEL(location.line); + PUSH_INSN(ret, location, dup); + PUSH_INSNL(ret, location, branchnil, lskip); } - PM_DUP; - ADD_SEND_WITH_FLAG(ret, &dummy_line_node, id_read_name, INT2FIX(0), INT2FIX(flag)); - PM_DUP_UNLESS_POPPED; + PUSH_INSN(ret, location, dup); + PUSH_SEND_WITH_FLAG(ret, location, id_read_name, INT2FIX(0), INT2FIX(flag)); + if (!popped) PUSH_INSN(ret, location, dup); if (and_node) { - ADD_INSNL(ret, &dummy_line_node, branchunless, lcfin); + PUSH_INSNL(ret, location, branchunless, lcfin); } else { - ADD_INSNL(ret, &dummy_line_node, branchif, lcfin); + PUSH_INSNL(ret, location, branchif, lcfin); } - PM_POP_UNLESS_POPPED; + if (!popped) PUSH_INSN(ret, location, pop); PM_COMPILE_NOT_POPPED(value); if (!popped) { - PM_SWAP; - ADD_INSN1(ret, &dummy_line_node, topn, INT2FIX(1)); + PUSH_INSN(ret, location, swap); + PUSH_INSN1(ret, location, topn, INT2FIX(1)); } ID id_write_name = pm_constant_id_lookup(scope_node, write_name); - ADD_SEND_WITH_FLAG(ret, &dummy_line_node, id_write_name, INT2FIX(1), INT2FIX(flag)); - ADD_INSNL(ret, &dummy_line_node, jump, lfin); + PUSH_SEND_WITH_FLAG(ret, location, id_write_name, INT2FIX(1), INT2FIX(flag)); + PUSH_INSNL(ret, location, jump, lfin); - ADD_LABEL(ret, lcfin); - if (!popped) PM_SWAP; + PUSH_LABEL(ret, lcfin); + if (!popped) PUSH_INSN(ret, location, swap); - ADD_LABEL(ret, lfin); + PUSH_LABEL(ret, lfin); - if (lskip && popped) ADD_LABEL(ret, lskip); - PM_POP; - if (lskip && !popped) ADD_LABEL(ret, lskip); + if (lskip && popped) PUSH_LABEL(ret, lskip); + PUSH_INSN(ret, location, pop); + if (lskip && !popped) PUSH_LABEL(ret, lskip); } /** @@ -1005,14 +1149,14 @@ pm_compile_call_and_or_write_node(bool and_node, pm_node_t *receiver, pm_node_t * contents of the hash are not popped. */ static void -pm_compile_hash_elements(const pm_node_list_t *elements, int lineno, rb_iseq_t *iseq, LINK_ANCHOR *const ret, pm_scope_node_t *scope_node) +pm_compile_hash_elements(rb_iseq_t *iseq, const pm_node_t *node, const pm_node_list_t *elements, LINK_ANCHOR *const ret, pm_scope_node_t *scope_node) { - NODE dummy_line_node = generate_dummy_line_node(lineno, lineno); + const pm_line_column_t location = PM_NODE_START_LINE_COLUMN(scope_node->parser, node); - // If this element is not popped, then we need to create the - // hash on the stack. Neighboring plain assoc nodes should be - // grouped together (either by newhash or hash merge). Double - // splat nodes should be merged using the mege_kwd method call. + // If this element is not popped, then we need to create the hash on the + // stack. Neighboring plain assoc nodes should be grouped together (either + // by newhash or hash merge). Double splat nodes should be merged using the + // merge_kwd method call. int assoc_length = 0; bool made_hash = false; @@ -1033,20 +1177,20 @@ pm_compile_hash_elements(const pm_node_list_t *elements, int lineno, rb_iseq_t * // merge the current elements into the existing hash. if (assoc_length > 0) { if (!made_hash) { - ADD_INSN1(ret, &dummy_line_node, newhash, INT2FIX(assoc_length * 2)); - ADD_INSN1(ret, &dummy_line_node, putspecialobject, INT2FIX(VM_SPECIAL_OBJECT_VMCORE)); - PM_SWAP; + PUSH_INSN1(ret, location, newhash, INT2FIX(assoc_length * 2)); + PUSH_INSN1(ret, location, putspecialobject, INT2FIX(VM_SPECIAL_OBJECT_VMCORE)); + PUSH_INSN(ret, location, swap); made_hash = true; } else { // Here we are merging plain assoc nodes into the hash on // the stack. - ADD_SEND(ret, &dummy_line_node, id_core_hash_merge_ptr, INT2FIX(assoc_length * 2 + 1)); + PUSH_SEND(ret, location, id_core_hash_merge_ptr, INT2FIX(assoc_length * 2 + 1)); // Since we already have a hash on the stack, we need to set // up the method call for the next merge that will occur. - ADD_INSN1(ret, &dummy_line_node, putspecialobject, INT2FIX(VM_SPECIAL_OBJECT_VMCORE)); - PM_SWAP; + PUSH_INSN1(ret, location, putspecialobject, INT2FIX(VM_SPECIAL_OBJECT_VMCORE)); + PUSH_INSN(ret, location, swap); } assoc_length = 0; @@ -1055,22 +1199,22 @@ pm_compile_hash_elements(const pm_node_list_t *elements, int lineno, rb_iseq_t * // If this is the first time we've seen a splat, then we need to // create a hash that we can merge into. if (!made_hash) { - ADD_INSN1(ret, &dummy_line_node, putspecialobject, INT2FIX(VM_SPECIAL_OBJECT_VMCORE)); - ADD_INSN1(ret, &dummy_line_node, newhash, INT2FIX(0)); + PUSH_INSN1(ret, location, putspecialobject, INT2FIX(VM_SPECIAL_OBJECT_VMCORE)); + PUSH_INSN1(ret, location, newhash, INT2FIX(0)); made_hash = true; } // Now compile the splat node itself and merge it into the hash. PM_COMPILE_NOT_POPPED(element); - ADD_SEND(ret, &dummy_line_node, id_core_hash_merge_kwd, INT2FIX(2)); + PUSH_SEND(ret, location, id_core_hash_merge_kwd, INT2FIX(2)); // We know that any subsequent elements will need to be merged in // using one of the special core methods. So here we will put the // receiver of the merge and then swap it with the hash that is // going to be the first argument. if (index != elements->size - 1) { - ADD_INSN1(ret, &dummy_line_node, putspecialobject, INT2FIX(VM_SPECIAL_OBJECT_VMCORE)); - PM_SWAP; + PUSH_INSN1(ret, location, putspecialobject, INT2FIX(VM_SPECIAL_OBJECT_VMCORE)); + PUSH_INSN(ret, location, swap); } break; @@ -1085,19 +1229,21 @@ pm_compile_hash_elements(const pm_node_list_t *elements, int lineno, rb_iseq_t * // If we haven't already made the hash, then this means we only saw // plain assoc nodes. In this case, we can just create the hash // directly. - ADD_INSN1(ret, &dummy_line_node, newhash, INT2FIX(assoc_length * 2)); + PUSH_INSN1(ret, location, newhash, INT2FIX(assoc_length * 2)); } else if (assoc_length > 0) { // If we have already made the hash, then we need to merge the remaining // assoc nodes into the hash on the stack. - ADD_SEND(ret, &dummy_line_node, id_core_hash_merge_ptr, INT2FIX(assoc_length * 2 + 1)); + PUSH_SEND(ret, location, id_core_hash_merge_ptr, INT2FIX(assoc_length * 2 + 1)); } } // This is details. Users should call pm_setup_args() instead. static int -pm_setup_args_core(const pm_arguments_node_t *arguments_node, const pm_node_t *block, int *flags, const bool has_regular_blockarg, struct rb_callinfo_kwarg **kw_arg, rb_iseq_t *iseq, LINK_ANCHOR *const ret, pm_scope_node_t *scope_node, NODE dummy_line_node) +pm_setup_args_core(const pm_arguments_node_t *arguments_node, const pm_node_t *block, int *flags, const bool has_regular_blockarg, struct rb_callinfo_kwarg **kw_arg, rb_iseq_t *iseq, LINK_ANCHOR *const ret, pm_scope_node_t *scope_node, const pm_line_column_t *node_location) { + const pm_line_column_t location = *node_location; + int orig_argc = 0; bool has_splat = false; bool has_keyword_splat = false; @@ -1108,78 +1254,119 @@ pm_setup_args_core(const pm_arguments_node_t *arguments_node, const pm_node_t *b } } else { - pm_node_list_t arguments_node_list = arguments_node->arguments; - - has_keyword_splat = (arguments_node->base.flags & PM_ARGUMENTS_NODE_FLAGS_CONTAINS_KEYWORD_SPLAT); + const pm_node_list_t *arguments = &arguments_node->arguments; + has_keyword_splat = PM_NODE_FLAG_P(arguments_node, PM_ARGUMENTS_NODE_FLAGS_CONTAINS_KEYWORD_SPLAT); // We count the number of elements post the splat node that are not keyword elements to // eventually pass as an argument to newarray int post_splat_counter = 0; + const pm_node_t *argument; - for (size_t index = 0; index < arguments_node_list.size; index++) { - pm_node_t *argument = arguments_node_list.nodes[index]; - + PM_NODE_LIST_FOREACH(arguments, index, argument) { switch (PM_NODE_TYPE(argument)) { // A keyword hash node contains all keyword arguments as AssocNodes and AssocSplatNodes case PM_KEYWORD_HASH_NODE: { - pm_keyword_hash_node_t *keyword_arg = (pm_keyword_hash_node_t *)argument; + const pm_keyword_hash_node_t *keyword_arg = (const pm_keyword_hash_node_t *) argument; + const pm_node_list_t *elements = &keyword_arg->elements; if (has_keyword_splat || has_splat) { *flags |= VM_CALL_KW_SPLAT; has_keyword_splat = true; - pm_compile_hash_elements(&keyword_arg->elements, nd_line(&dummy_line_node), iseq, ret, scope_node); + pm_compile_hash_elements(iseq, argument, elements, ret, scope_node); } else { - size_t len = keyword_arg->elements.size; - - // We need to first figure out if all elements of the KeywordHashNode are AssocNodes - // with symbol keys. + // We need to first figure out if all elements of the + // KeywordHashNode are AssocNodes with symbol keys. if (PM_NODE_FLAG_P(keyword_arg, PM_KEYWORD_HASH_NODE_FLAGS_SYMBOL_KEYS)) { - // If they are all symbol keys then we can pass them as keyword arguments. - *kw_arg = rb_xmalloc_mul_add(len, sizeof(VALUE), sizeof(struct rb_callinfo_kwarg)); + // If they are all symbol keys then we can pass them as + // keyword arguments. The first thing we need to do is + // deduplicate. We'll do this using the combination of a + // Ruby hash and a Ruby array. + VALUE stored_indices = rb_hash_new(); + VALUE keyword_indices = rb_ary_new_capa(elements->size); + + size_t size = 0; + for (size_t element_index = 0; element_index < elements->size; element_index++) { + const pm_assoc_node_t *assoc = (const pm_assoc_node_t *) elements->nodes[element_index]; + + // Retrieve the stored index from the hash for this + // keyword. + VALUE keyword = pm_static_literal_value(iseq, assoc->key, scope_node); + VALUE stored_index = rb_hash_aref(stored_indices, keyword); + + // If this keyword was already seen in the hash, + // then mark the array at that index as false and + // decrement the keyword size. + if (!NIL_P(stored_index)) { + rb_ary_store(keyword_indices, NUM2LONG(stored_index), Qfalse); + size--; + } + + // Store (and possibly overwrite) the index for this + // keyword in the hash, mark the array at that index + // as true, and increment the keyword size. + rb_hash_aset(stored_indices, keyword, ULONG2NUM(element_index)); + rb_ary_store(keyword_indices, (long) element_index, Qtrue); + size++; + } + + *kw_arg = rb_xmalloc_mul_add(size, sizeof(VALUE), sizeof(struct rb_callinfo_kwarg)); *flags |= VM_CALL_KWARG; + VALUE *keywords = (*kw_arg)->keywords; (*kw_arg)->references = 0; - (*kw_arg)->keyword_len = (int)len; + (*kw_arg)->keyword_len = (int) size; - for (size_t i = 0; i < len; i++) { - pm_assoc_node_t *assoc = (pm_assoc_node_t *)keyword_arg->elements.nodes[i]; - pm_node_t *key = assoc->key; - keywords[i] = pm_static_literal_value(key, scope_node); - PM_COMPILE_NOT_POPPED(assoc->value); + size_t keyword_index = 0; + for (size_t element_index = 0; element_index < elements->size; element_index++) { + const pm_assoc_node_t *assoc = (const pm_assoc_node_t *) elements->nodes[element_index]; + bool popped = true; + + if (rb_ary_entry(keyword_indices, (long) element_index) == Qtrue) { + keywords[keyword_index++] = pm_static_literal_value(iseq, assoc->key, scope_node); + popped = false; + } + + PM_COMPILE(assoc->value); } - } else { - // If they aren't all symbol keys then we need to construct a new hash - // and pass that as an argument. + + RUBY_ASSERT(keyword_index == size); + } + else { + // If they aren't all symbol keys then we need to + // construct a new hash and pass that as an argument. orig_argc++; *flags |= VM_CALL_KW_SPLAT; - if (len > 1) { - // A new hash will be created for the keyword arguments in this case, - // so mark the method as passing mutable keyword splat. + + size_t size = elements->size; + if (size > 1) { + // A new hash will be created for the keyword + // arguments in this case, so mark the method as + // passing mutable keyword splat. *flags |= VM_CALL_KW_SPLAT_MUT; } - for (size_t i = 0; i < len; i++) { - pm_assoc_node_t *assoc = (pm_assoc_node_t *)keyword_arg->elements.nodes[i]; + for (size_t element_index = 0; element_index < size; element_index++) { + const pm_assoc_node_t *assoc = (const pm_assoc_node_t *) elements->nodes[element_index]; PM_COMPILE_NOT_POPPED(assoc->key); PM_COMPILE_NOT_POPPED(assoc->value); } - ADD_INSN1(ret, &dummy_line_node, newhash, INT2FIX(len * 2)); + PUSH_INSN1(ret, location, newhash, INT2FIX(size * 2)); } } break; } case PM_SPLAT_NODE: { *flags |= VM_CALL_ARGS_SPLAT; - pm_splat_node_t *splat_node = (pm_splat_node_t *)argument; + const pm_splat_node_t *splat_node = (const pm_splat_node_t *) argument; if (splat_node->expression) { PM_COMPILE_NOT_POPPED(splat_node->expression); } else { pm_local_index_t index = pm_lookup_local_index(iseq, scope_node, PM_CONSTANT_MULT, 0); - ADD_GETLOCAL(ret, &dummy_line_node, index.index, index.level); + PUSH_GETLOCAL(ret, location, index.index, index.level); } bool first_splat = !has_splat; @@ -1190,8 +1377,8 @@ pm_setup_args_core(const pm_arguments_node_t *arguments_node, const pm_node_t *b // // foo(a, *b, c) // ^^ - if (index + 1 < arguments_node_list.size || has_regular_blockarg) { - ADD_INSN1(ret, &dummy_line_node, splatarray, Qtrue); + if (index + 1 < arguments->size || has_regular_blockarg) { + PUSH_INSN1(ret, location, splatarray, Qtrue); *flags |= VM_CALL_ARGS_SPLAT_MUT; } // If this is the first spalt array seen and it's the last @@ -1200,7 +1387,7 @@ pm_setup_args_core(const pm_arguments_node_t *arguments_node, const pm_node_t *b // foo(a, *b) // ^^ else { - ADD_INSN1(ret, &dummy_line_node, splatarray, Qfalse); + PUSH_INSN1(ret, location, splatarray, Qfalse); } } else { @@ -1210,8 +1397,8 @@ pm_setup_args_core(const pm_arguments_node_t *arguments_node, const pm_node_t *b // // foo(a, *b, *c) // ^^ - ADD_INSN1(ret, &dummy_line_node, splatarray, Qfalse); - ADD_INSN(ret, &dummy_line_node, concatarray); + PUSH_INSN1(ret, location, splatarray, Qfalse); + PUSH_INSN(ret, location, concatarray); } has_splat = true; @@ -1229,17 +1416,17 @@ pm_setup_args_core(const pm_arguments_node_t *arguments_node, const pm_node_t *b // // Push the * pm_local_index_t mult_local = pm_lookup_local_index(iseq, scope_node, PM_CONSTANT_MULT, 0); - ADD_GETLOCAL(ret, &dummy_line_node, mult_local.index, mult_local.level); - ADD_INSN1(ret, &dummy_line_node, splatarray, Qtrue); + PUSH_GETLOCAL(ret, location, mult_local.index, mult_local.level); + PUSH_INSN1(ret, location, splatarray, Qtrue); // Push the ** pm_local_index_t pow_local = pm_lookup_local_index(iseq, scope_node, PM_CONSTANT_POW, 0); - ADD_GETLOCAL(ret, &dummy_line_node, pow_local.index, pow_local.level); + PUSH_GETLOCAL(ret, location, pow_local.index, pow_local.level); // Push the & pm_local_index_t and_local = pm_lookup_local_index(iseq, scope_node, PM_CONSTANT_AND, 0); - ADD_INSN2(ret, &dummy_line_node, getblockparamproxy, INT2FIX(and_local.index + VM_ENV_DATA_SIZE - 1), INT2FIX(and_local.level)); - ADD_INSN(ret, &dummy_line_node, splatkw); + PUSH_INSN2(ret, location, getblockparamproxy, INT2FIX(and_local.index + VM_ENV_DATA_SIZE - 1), INT2FIX(and_local.level)); + PUSH_INSN(ret, location, splatkw); break; } @@ -1265,23 +1452,23 @@ pm_setup_args_core(const pm_arguments_node_t *arguments_node, const pm_node_t *b // If the next node is NULL (we have hit the end): // // foo(*a, b) - if (index == arguments_node_list.size - 1) { + if (index == arguments->size - 1) { RUBY_ASSERT(post_splat_counter > 0); - ADD_INSN1(ret, &dummy_line_node, pushtoarray, INT2FIX(post_splat_counter)); + PUSH_INSN1(ret, location, pushtoarray, INT2FIX(post_splat_counter)); } else { - pm_node_t *next_arg = arguments_node_list.nodes[index + 1]; + pm_node_t *next_arg = arguments->nodes[index + 1]; switch (PM_NODE_TYPE(next_arg)) { // A keyword hash node contains all keyword arguments as AssocNodes and AssocSplatNodes case PM_KEYWORD_HASH_NODE: { - ADD_INSN1(ret, &dummy_line_node, newarray, INT2FIX(post_splat_counter)); - ADD_INSN(ret, &dummy_line_node, concatarray); + PUSH_INSN1(ret, location, newarray, INT2FIX(post_splat_counter)); + PUSH_INSN(ret, location, concatarray); break; } case PM_SPLAT_NODE: { - ADD_INSN1(ret, &dummy_line_node, newarray, INT2FIX(post_splat_counter)); - ADD_INSN(ret, &dummy_line_node, concatarray); + PUSH_INSN1(ret, location, newarray, INT2FIX(post_splat_counter)); + PUSH_INSN(ret, location, concatarray); break; } default: @@ -1297,20 +1484,14 @@ pm_setup_args_core(const pm_arguments_node_t *arguments_node, const pm_node_t *b } } - if (has_splat) { - orig_argc++; - } - - if (has_keyword_splat) { - orig_argc++; - } - + if (has_splat) orig_argc++; + if (has_keyword_splat) orig_argc++; return orig_argc; } // Compile the argument parts of a call static int -pm_setup_args(const pm_arguments_node_t *arguments_node, const pm_node_t *block, int *flags, struct rb_callinfo_kwarg **kw_arg, rb_iseq_t *iseq, LINK_ANCHOR *const ret, pm_scope_node_t *scope_node, NODE dummy_line_node) +pm_setup_args(const pm_arguments_node_t *arguments_node, const pm_node_t *block, int *flags, struct rb_callinfo_kwarg **kw_arg, rb_iseq_t *iseq, LINK_ANCHOR *const ret, pm_scope_node_t *scope_node, const pm_line_column_t *node_location) { if (block && PM_NODE_TYPE_P(block, PM_BLOCK_ARGUMENT_NODE)) { // We compile the `&block_arg` expression first and stitch it later @@ -1326,7 +1507,7 @@ pm_setup_args(const pm_arguments_node_t *arguments_node, const pm_node_t *block, if (LIST_INSN_SIZE_ONE(block_arg)) { LINK_ELEMENT *elem = FIRST_ELEMENT(block_arg); if (IS_INSN(elem)) { - INSN *iobj = (INSN *)elem; + INSN *iobj = (INSN *) elem; if (iobj->insn_id == BIN(getblockparam)) { iobj->insn_id = BIN(getblockparamproxy); } @@ -1337,11 +1518,12 @@ pm_setup_args(const pm_arguments_node_t *arguments_node, const pm_node_t *block, } } - int argc = pm_setup_args_core(arguments_node, block, flags, regular_block_arg, kw_arg, iseq, ret, scope_node, dummy_line_node); - ADD_SEQ(ret, block_arg); + int argc = pm_setup_args_core(arguments_node, block, flags, regular_block_arg, kw_arg, iseq, ret, scope_node, node_location); + PUSH_SEQ(ret, block_arg); return argc; } - return pm_setup_args_core(arguments_node, block, flags, false, kw_arg, iseq, ret, scope_node, dummy_line_node); + + return pm_setup_args_core(arguments_node, block, flags, false, kw_arg, iseq, ret, scope_node, node_location); } /** @@ -1355,30 +1537,26 @@ pm_setup_args(const pm_arguments_node_t *arguments_node, const pm_node_t *block, * and then calling the []= method with the result of the operator method. */ static void -pm_compile_index_operator_write_node(pm_scope_node_t *scope_node, const pm_index_operator_write_node_t *node, rb_iseq_t *iseq, LINK_ANCHOR *const ret, bool popped) +pm_compile_index_operator_write_node(rb_iseq_t *iseq, const pm_index_operator_write_node_t *node, const pm_line_column_t *node_location, LINK_ANCHOR *const ret, bool popped, pm_scope_node_t *scope_node) { - int lineno = pm_node_line_number(scope_node->parser, (const pm_node_t *) node); - const NODE dummy_line_node = generate_dummy_line_node(lineno, lineno); - - if (!popped) { - PM_PUTNIL; - } + const pm_line_column_t location = *node_location; + if (!popped) PUSH_INSN(ret, location, putnil); PM_COMPILE_NOT_POPPED(node->receiver); int boff = (node->block == NULL ? 0 : 1); int flag = PM_NODE_TYPE_P(node->receiver, PM_SELF_NODE) ? VM_CALL_FCALL : 0; struct rb_callinfo_kwarg *keywords = NULL; - int argc = pm_setup_args(node->arguments, node->block, &flag, &keywords, iseq, ret, scope_node, dummy_line_node); + int argc = pm_setup_args(node->arguments, node->block, &flag, &keywords, iseq, ret, scope_node, node_location); if ((argc > 0 || boff) && (flag & VM_CALL_KW_SPLAT)) { if (boff) { - ADD_INSN(ret, &dummy_line_node, splatkw); + PUSH_INSN(ret, location, splatkw); } else { - ADD_INSN(ret, &dummy_line_node, dup); - ADD_INSN(ret, &dummy_line_node, splatkw); - ADD_INSN(ret, &dummy_line_node, pop); + PUSH_INSN(ret, location, dup); + PUSH_INSN(ret, location, splatkw); + PUSH_INSN(ret, location, pop); } } @@ -1390,76 +1568,77 @@ pm_compile_index_operator_write_node(pm_scope_node_t *scope_node, const pm_index dup_argn += keyword_len; } - ADD_INSN1(ret, &dummy_line_node, dupn, INT2FIX(dup_argn)); - ADD_SEND_R(ret, &dummy_line_node, idAREF, INT2FIX(argc), NULL, INT2FIX(flag & ~(VM_CALL_ARGS_SPLAT_MUT | VM_CALL_KW_SPLAT_MUT)), keywords); - + PUSH_INSN1(ret, location, dupn, INT2FIX(dup_argn)); + PUSH_SEND_R(ret, location, idAREF, INT2FIX(argc), NULL, INT2FIX(flag & ~(VM_CALL_ARGS_SPLAT_MUT | VM_CALL_KW_SPLAT_MUT)), keywords); PM_COMPILE_NOT_POPPED(node->value); ID id_operator = pm_constant_id_lookup(scope_node, node->operator); - ADD_SEND(ret, &dummy_line_node, id_operator, INT2FIX(1)); + PUSH_SEND(ret, location, id_operator, INT2FIX(1)); if (!popped) { - ADD_INSN1(ret, &dummy_line_node, setn, INT2FIX(dup_argn + 1)); + PUSH_INSN1(ret, location, setn, INT2FIX(dup_argn + 1)); } if (flag & VM_CALL_ARGS_SPLAT) { if (flag & VM_CALL_KW_SPLAT) { - ADD_INSN1(ret, &dummy_line_node, topn, INT2FIX(2 + boff)); + PUSH_INSN1(ret, location, topn, INT2FIX(2 + boff)); if (!(flag & VM_CALL_ARGS_SPLAT_MUT)) { - ADD_INSN1(ret, &dummy_line_node, splatarray, Qtrue); + PUSH_INSN1(ret, location, splatarray, Qtrue); flag |= VM_CALL_ARGS_SPLAT_MUT; } - ADD_INSN(ret, &dummy_line_node, swap); - ADD_INSN1(ret, &dummy_line_node, pushtoarray, INT2FIX(1)); - ADD_INSN1(ret, &dummy_line_node, setn, INT2FIX(2 + boff)); - ADD_INSN(ret, &dummy_line_node, pop); + PUSH_INSN(ret, location, swap); + PUSH_INSN1(ret, location, pushtoarray, INT2FIX(1)); + PUSH_INSN1(ret, location, setn, INT2FIX(2 + boff)); + PUSH_INSN(ret, location, pop); } else { if (boff > 0) { - ADD_INSN1(ret, &dummy_line_node, dupn, INT2FIX(3)); - ADD_INSN(ret, &dummy_line_node, swap); - ADD_INSN(ret, &dummy_line_node, pop); + PUSH_INSN1(ret, location, dupn, INT2FIX(3)); + PUSH_INSN(ret, location, swap); + PUSH_INSN(ret, location, pop); } if (!(flag & VM_CALL_ARGS_SPLAT_MUT)) { - ADD_INSN(ret, &dummy_line_node, swap); - ADD_INSN1(ret, &dummy_line_node, splatarray, Qtrue); - ADD_INSN(ret, &dummy_line_node, swap); + PUSH_INSN(ret, location, swap); + PUSH_INSN1(ret, location, splatarray, Qtrue); + PUSH_INSN(ret, location, swap); flag |= VM_CALL_ARGS_SPLAT_MUT; } - ADD_INSN1(ret, &dummy_line_node, pushtoarray, INT2FIX(1)); + PUSH_INSN1(ret, location, pushtoarray, INT2FIX(1)); if (boff > 0) { - ADD_INSN1(ret, &dummy_line_node, setn, INT2FIX(3)); - PM_POP; - PM_POP; + PUSH_INSN1(ret, location, setn, INT2FIX(3)); + PUSH_INSN(ret, location, pop); + PUSH_INSN(ret, location, pop); } } - ADD_SEND_R(ret, &dummy_line_node, idASET, INT2FIX(argc), NULL, INT2FIX(flag), keywords); + + PUSH_SEND_R(ret, location, idASET, INT2FIX(argc), NULL, INT2FIX(flag), keywords); } else if (flag & VM_CALL_KW_SPLAT) { if (boff > 0) { - ADD_INSN1(ret, &dummy_line_node, topn, INT2FIX(2)); - ADD_INSN(ret, &dummy_line_node, swap); - ADD_INSN1(ret, &dummy_line_node, setn, INT2FIX(3)); - PM_POP; + PUSH_INSN1(ret, location, topn, INT2FIX(2)); + PUSH_INSN(ret, location, swap); + PUSH_INSN1(ret, location, setn, INT2FIX(3)); + PUSH_INSN(ret, location, pop); } - ADD_INSN(ret, &dummy_line_node, swap); - ADD_SEND_R(ret, &dummy_line_node, idASET, INT2FIX(argc + 1), NULL, INT2FIX(flag), keywords); + PUSH_INSN(ret, location, swap); + PUSH_SEND_R(ret, location, idASET, INT2FIX(argc + 1), NULL, INT2FIX(flag), keywords); } else if (keyword_len) { - ADD_INSN(ret, &dummy_line_node, dup); - ADD_INSN1(ret, &dummy_line_node, opt_reverse, INT2FIX(keyword_len + boff + 2)); - ADD_INSN1(ret, &dummy_line_node, opt_reverse, INT2FIX(keyword_len + boff + 1)); - ADD_INSN(ret, &dummy_line_node, pop); - ADD_SEND_R(ret, &dummy_line_node, idASET, INT2FIX(argc + 1), NULL, INT2FIX(flag), keywords); + PUSH_INSN(ret, location, dup); + PUSH_INSN1(ret, location, opt_reverse, INT2FIX(keyword_len + boff + 2)); + PUSH_INSN1(ret, location, opt_reverse, INT2FIX(keyword_len + boff + 1)); + PUSH_INSN(ret, location, pop); + PUSH_SEND_R(ret, location, idASET, INT2FIX(argc + 1), NULL, INT2FIX(flag), keywords); } else { if (boff > 0) { - PM_SWAP; + PUSH_INSN(ret, location, swap); } - ADD_SEND_R(ret, &dummy_line_node, idASET, INT2FIX(argc + 1), NULL, INT2FIX(flag), keywords); + PUSH_SEND_R(ret, location, idASET, INT2FIX(argc + 1), NULL, INT2FIX(flag), keywords); } - PM_POP; + + PUSH_INSN(ret, location, pop); } /** @@ -1475,30 +1654,25 @@ pm_compile_index_operator_write_node(pm_scope_node_t *scope_node, const pm_index * []= method. */ static void -pm_compile_index_control_flow_write_node(pm_scope_node_t *scope_node, const pm_node_t *node, const pm_node_t *receiver, const pm_arguments_node_t *arguments, const pm_node_t *block, const pm_node_t *value, rb_iseq_t *iseq, LINK_ANCHOR *const ret, bool popped) +pm_compile_index_control_flow_write_node(rb_iseq_t *iseq, const pm_node_t *node, const pm_node_t *receiver, const pm_arguments_node_t *arguments, const pm_node_t *block, const pm_node_t *value, const pm_line_column_t *node_location, LINK_ANCHOR *const ret, bool popped, pm_scope_node_t *scope_node) { - int lineno = pm_node_line_number(scope_node->parser, node); - const NODE dummy_line_node = generate_dummy_line_node(lineno, lineno); - - if (!popped) { - PM_PUTNIL; - } - + const pm_line_column_t location = *node_location; + if (!popped) PUSH_INSN(ret, location, putnil); PM_COMPILE_NOT_POPPED(receiver); int boff = (block == NULL ? 0 : 1); int flag = PM_NODE_TYPE_P(receiver, PM_SELF_NODE) ? VM_CALL_FCALL : 0; struct rb_callinfo_kwarg *keywords = NULL; - int argc = pm_setup_args(arguments, block, &flag, &keywords, iseq, ret, scope_node, dummy_line_node); + int argc = pm_setup_args(arguments, block, &flag, &keywords, iseq, ret, scope_node, node_location); if ((argc > 0 || boff) && (flag & VM_CALL_KW_SPLAT)) { if (boff) { - ADD_INSN(ret, &dummy_line_node, splatkw); + PUSH_INSN(ret, location, splatkw); } else { - ADD_INSN(ret, &dummy_line_node, dup); - ADD_INSN(ret, &dummy_line_node, splatkw); - ADD_INSN(ret, &dummy_line_node, pop); + PUSH_INSN(ret, location, dup); + PUSH_INSN(ret, location, splatkw); + PUSH_INSN(ret, location, pop); } } @@ -1510,91 +1684,93 @@ pm_compile_index_control_flow_write_node(pm_scope_node_t *scope_node, const pm_n dup_argn += keyword_len; } - ADD_INSN1(ret, &dummy_line_node, dupn, INT2FIX(dup_argn)); - ADD_SEND_R(ret, &dummy_line_node, idAREF, INT2FIX(argc), NULL, INT2FIX(flag & ~(VM_CALL_ARGS_SPLAT_MUT | VM_CALL_KW_SPLAT_MUT)), keywords); + PUSH_INSN1(ret, location, dupn, INT2FIX(dup_argn)); + PUSH_SEND_R(ret, location, idAREF, INT2FIX(argc), NULL, INT2FIX(flag & ~(VM_CALL_ARGS_SPLAT_MUT | VM_CALL_KW_SPLAT_MUT)), keywords); - LABEL *label = NEW_LABEL(lineno); - LABEL *lfin = NEW_LABEL(lineno); + LABEL *label = NEW_LABEL(location.line); + LABEL *lfin = NEW_LABEL(location.line); - ADD_INSN(ret, &dummy_line_node, dup); + PUSH_INSN(ret, location, dup); if (PM_NODE_TYPE_P(node, PM_INDEX_AND_WRITE_NODE)) { - ADD_INSNL(ret, &dummy_line_node, branchunless, label); + PUSH_INSNL(ret, location, branchunless, label); } else { - ADD_INSNL(ret, &dummy_line_node, branchif, label); + PUSH_INSNL(ret, location, branchif, label); } - PM_POP; + PUSH_INSN(ret, location, pop); PM_COMPILE_NOT_POPPED(value); if (!popped) { - ADD_INSN1(ret, &dummy_line_node, setn, INT2FIX(dup_argn + 1)); + PUSH_INSN1(ret, location, setn, INT2FIX(dup_argn + 1)); } if (flag & VM_CALL_ARGS_SPLAT) { if (flag & VM_CALL_KW_SPLAT) { - ADD_INSN1(ret, &dummy_line_node, topn, INT2FIX(2 + boff)); + PUSH_INSN1(ret, location, topn, INT2FIX(2 + boff)); if (!(flag & VM_CALL_ARGS_SPLAT_MUT)) { - ADD_INSN1(ret, &dummy_line_node, splatarray, Qtrue); + PUSH_INSN1(ret, location, splatarray, Qtrue); flag |= VM_CALL_ARGS_SPLAT_MUT; } - ADD_INSN(ret, &dummy_line_node, swap); - ADD_INSN1(ret, &dummy_line_node, pushtoarray, INT2FIX(1)); - ADD_INSN1(ret, &dummy_line_node, setn, INT2FIX(2 + boff)); - ADD_INSN(ret, &dummy_line_node, pop); + PUSH_INSN(ret, location, swap); + PUSH_INSN1(ret, location, pushtoarray, INT2FIX(1)); + PUSH_INSN1(ret, location, setn, INT2FIX(2 + boff)); + PUSH_INSN(ret, location, pop); } else { if (boff > 0) { - ADD_INSN1(ret, &dummy_line_node, dupn, INT2FIX(3)); - ADD_INSN(ret, &dummy_line_node, swap); - ADD_INSN(ret, &dummy_line_node, pop); + PUSH_INSN1(ret, location, dupn, INT2FIX(3)); + PUSH_INSN(ret, location, swap); + PUSH_INSN(ret, location, pop); } if (!(flag & VM_CALL_ARGS_SPLAT_MUT)) { - ADD_INSN(ret, &dummy_line_node, swap); - ADD_INSN1(ret, &dummy_line_node, splatarray, Qtrue); - ADD_INSN(ret, &dummy_line_node, swap); + PUSH_INSN(ret, location, swap); + PUSH_INSN1(ret, location, splatarray, Qtrue); + PUSH_INSN(ret, location, swap); flag |= VM_CALL_ARGS_SPLAT_MUT; } - ADD_INSN1(ret, &dummy_line_node, pushtoarray, INT2FIX(1)); + PUSH_INSN1(ret, location, pushtoarray, INT2FIX(1)); if (boff > 0) { - ADD_INSN1(ret, &dummy_line_node, setn, INT2FIX(3)); - PM_POP; - PM_POP; + PUSH_INSN1(ret, location, setn, INT2FIX(3)); + PUSH_INSN(ret, location, pop); + PUSH_INSN(ret, location, pop); } } - ADD_SEND_R(ret, &dummy_line_node, idASET, INT2FIX(argc), NULL, INT2FIX(flag), keywords); + + PUSH_SEND_R(ret, location, idASET, INT2FIX(argc), NULL, INT2FIX(flag), keywords); } else if (flag & VM_CALL_KW_SPLAT) { if (boff > 0) { - ADD_INSN1(ret, &dummy_line_node, topn, INT2FIX(2)); - ADD_INSN(ret, &dummy_line_node, swap); - ADD_INSN1(ret, &dummy_line_node, setn, INT2FIX(3)); - PM_POP; + PUSH_INSN1(ret, location, topn, INT2FIX(2)); + PUSH_INSN(ret, location, swap); + PUSH_INSN1(ret, location, setn, INT2FIX(3)); + PUSH_INSN(ret, location, pop); } - ADD_INSN(ret, &dummy_line_node, swap); - ADD_SEND_R(ret, &dummy_line_node, idASET, INT2FIX(argc + 1), NULL, INT2FIX(flag), keywords); + PUSH_INSN(ret, location, swap); + PUSH_SEND_R(ret, location, idASET, INT2FIX(argc + 1), NULL, INT2FIX(flag), keywords); } else if (keyword_len) { - ADD_INSN1(ret, &dummy_line_node, opt_reverse, INT2FIX(keyword_len + boff + 1)); - ADD_INSN1(ret, &dummy_line_node, opt_reverse, INT2FIX(keyword_len + boff + 0)); - ADD_SEND_R(ret, &dummy_line_node, idASET, INT2FIX(argc + 1), NULL, INT2FIX(flag), keywords); + PUSH_INSN1(ret, location, opt_reverse, INT2FIX(keyword_len + boff + 1)); + PUSH_INSN1(ret, location, opt_reverse, INT2FIX(keyword_len + boff + 0)); + PUSH_SEND_R(ret, location, idASET, INT2FIX(argc + 1), NULL, INT2FIX(flag), keywords); } else { if (boff > 0) { - PM_SWAP; + PUSH_INSN(ret, location, swap); } - ADD_SEND_R(ret, &dummy_line_node, idASET, INT2FIX(argc + 1), NULL, INT2FIX(flag), keywords); + PUSH_SEND_R(ret, location, idASET, INT2FIX(argc + 1), NULL, INT2FIX(flag), keywords); } - PM_POP; - ADD_INSNL(ret, &dummy_line_node, jump, lfin); - ADD_LABEL(ret, label); + + PUSH_INSN(ret, location, pop); + PUSH_INSNL(ret, location, jump, lfin); + PUSH_LABEL(ret, label); if (!popped) { - ADD_INSN1(ret, &dummy_line_node, setn, INT2FIX(dup_argn + 1)); + PUSH_INSN1(ret, location, setn, INT2FIX(dup_argn + 1)); } - ADD_INSN1(ret, &dummy_line_node, adjuststack, INT2FIX(dup_argn + 1)); - ADD_LABEL(ret, lfin); + PUSH_INSN1(ret, location, adjuststack, INT2FIX(dup_argn + 1)); + PUSH_LABEL(ret, lfin); } // When we compile a pattern matching expression, we use the stack as a scratch @@ -1621,26 +1797,24 @@ static int pm_compile_pattern(rb_iseq_t *iseq, pm_scope_node_t *scope_node, cons static int pm_compile_pattern_generic_error(rb_iseq_t *iseq, pm_scope_node_t *scope_node, const pm_node_t *node, LINK_ANCHOR *const ret, VALUE message, unsigned int base_index) { - pm_line_node_t line; - pm_line_node(&line, scope_node, node); - - LABEL *match_succeeded_label = NEW_LABEL(line.lineno); + const pm_line_column_t location = PM_NODE_START_LINE_COLUMN(scope_node->parser, node); + LABEL *match_succeeded_label = NEW_LABEL(location.line); - ADD_INSN(ret, &line.node, dup); - ADD_INSNL(ret, &line.node, branchif, match_succeeded_label); + PUSH_INSN(ret, location, dup); + PUSH_INSNL(ret, location, branchif, match_succeeded_label); - ADD_INSN1(ret, &line.node, putspecialobject, INT2FIX(VM_SPECIAL_OBJECT_VMCORE)); - ADD_INSN1(ret, &line.node, putobject, message); - ADD_INSN1(ret, &line.node, topn, INT2FIX(3)); - ADD_SEND(ret, &line.node, id_core_sprintf, INT2FIX(2)); - ADD_INSN1(ret, &line.node, setn, INT2FIX(base_index + PM_PATTERN_BASE_INDEX_OFFSET_ERROR_STRING + 1)); + PUSH_INSN1(ret, location, putspecialobject, INT2FIX(VM_SPECIAL_OBJECT_VMCORE)); + PUSH_INSN1(ret, location, putobject, message); + PUSH_INSN1(ret, location, topn, INT2FIX(3)); + PUSH_SEND(ret, location, id_core_sprintf, INT2FIX(2)); + PUSH_INSN1(ret, location, setn, INT2FIX(base_index + PM_PATTERN_BASE_INDEX_OFFSET_ERROR_STRING + 1)); - ADD_INSN1(ret, &line.node, putobject, Qfalse); - ADD_INSN1(ret, &line.node, setn, INT2FIX(base_index + PM_PATTERN_BASE_INDEX_OFFSET_KEY_ERROR_P + 2)); + PUSH_INSN1(ret, location, putobject, Qfalse); + PUSH_INSN1(ret, location, setn, INT2FIX(base_index + PM_PATTERN_BASE_INDEX_OFFSET_KEY_ERROR_P + 2)); - ADD_INSN(ret, &line.node, pop); - ADD_INSN(ret, &line.node, pop); - ADD_LABEL(ret, match_succeeded_label); + PUSH_INSN(ret, location, pop); + PUSH_INSN(ret, location, pop); + PUSH_LABEL(ret, match_succeeded_label); return COMPILE_OK; } @@ -1653,29 +1827,27 @@ pm_compile_pattern_generic_error(rb_iseq_t *iseq, pm_scope_node_t *scope_node, c static int pm_compile_pattern_length_error(rb_iseq_t *iseq, pm_scope_node_t *scope_node, const pm_node_t *node, LINK_ANCHOR *const ret, VALUE message, VALUE length, unsigned int base_index) { - pm_line_node_t line; - pm_line_node(&line, scope_node, node); + const pm_line_column_t location = PM_NODE_START_LINE_COLUMN(scope_node->parser, node); + LABEL *match_succeeded_label = NEW_LABEL(location.line); - LABEL *match_succeeded_label = NEW_LABEL(line.lineno); + PUSH_INSN(ret, location, dup); + PUSH_INSNL(ret, location, branchif, match_succeeded_label); - ADD_INSN(ret, &line.node, dup); - ADD_INSNL(ret, &line.node, branchif, match_succeeded_label); + PUSH_INSN1(ret, location, putspecialobject, INT2FIX(VM_SPECIAL_OBJECT_VMCORE)); + PUSH_INSN1(ret, location, putobject, message); + PUSH_INSN1(ret, location, topn, INT2FIX(3)); + PUSH_INSN(ret, location, dup); + PUSH_SEND(ret, location, idLength, INT2FIX(0)); + PUSH_INSN1(ret, location, putobject, length); + PUSH_SEND(ret, location, id_core_sprintf, INT2FIX(4)); + PUSH_INSN1(ret, location, setn, INT2FIX(base_index + PM_PATTERN_BASE_INDEX_OFFSET_ERROR_STRING + 1)); - ADD_INSN1(ret, &line.node, putspecialobject, INT2FIX(VM_SPECIAL_OBJECT_VMCORE)); - ADD_INSN1(ret, &line.node, putobject, message); - ADD_INSN1(ret, &line.node, topn, INT2FIX(3)); - ADD_INSN(ret, &line.node, dup); - ADD_SEND(ret, &line.node, idLength, INT2FIX(0)); - ADD_INSN1(ret, &line.node, putobject, length); - ADD_SEND(ret, &line.node, id_core_sprintf, INT2FIX(4)); - ADD_INSN1(ret, &line.node, setn, INT2FIX(base_index + PM_PATTERN_BASE_INDEX_OFFSET_ERROR_STRING + 1)); + PUSH_INSN1(ret, location, putobject, Qfalse); + PUSH_INSN1(ret, location, setn, INT2FIX(base_index + PM_PATTERN_BASE_INDEX_OFFSET_KEY_ERROR_P + 2)); - ADD_INSN1(ret, &line.node, putobject, Qfalse); - ADD_INSN1(ret, &line.node, setn, INT2FIX(base_index + PM_PATTERN_BASE_INDEX_OFFSET_KEY_ERROR_P + 2)); - - ADD_INSN(ret, &line.node, pop); - ADD_INSN(ret, &line.node, pop); - ADD_LABEL(ret, match_succeeded_label); + PUSH_INSN(ret, location, pop); + PUSH_INSN(ret, location, pop); + PUSH_LABEL(ret, match_succeeded_label); return COMPILE_OK; } @@ -1688,29 +1860,27 @@ pm_compile_pattern_length_error(rb_iseq_t *iseq, pm_scope_node_t *scope_node, co static int pm_compile_pattern_eqq_error(rb_iseq_t *iseq, pm_scope_node_t *scope_node, const pm_node_t *node, LINK_ANCHOR *const ret, unsigned int base_index) { - pm_line_node_t line; - pm_line_node(&line, scope_node, node); - - LABEL *match_succeeded_label = NEW_LABEL(line.lineno); - - ADD_INSN(ret, &line.node, dup); - ADD_INSNL(ret, &line.node, branchif, match_succeeded_label); - - ADD_INSN1(ret, &line.node, putspecialobject, INT2FIX(VM_SPECIAL_OBJECT_VMCORE)); - ADD_INSN1(ret, &line.node, putobject, rb_fstring_lit("%p === %p does not return true")); - ADD_INSN1(ret, &line.node, topn, INT2FIX(3)); - ADD_INSN1(ret, &line.node, topn, INT2FIX(5)); - ADD_SEND(ret, &line.node, id_core_sprintf, INT2FIX(3)); - ADD_INSN1(ret, &line.node, setn, INT2FIX(base_index + PM_PATTERN_BASE_INDEX_OFFSET_ERROR_STRING + 1)); - ADD_INSN1(ret, &line.node, putobject, Qfalse); - ADD_INSN1(ret, &line.node, setn, INT2FIX(base_index + PM_PATTERN_BASE_INDEX_OFFSET_KEY_ERROR_P + 2)); - ADD_INSN(ret, &line.node, pop); - ADD_INSN(ret, &line.node, pop); - - ADD_LABEL(ret, match_succeeded_label); - ADD_INSN1(ret, &line.node, setn, INT2FIX(2)); - ADD_INSN(ret, &line.node, pop); - ADD_INSN(ret, &line.node, pop); + const pm_line_column_t location = PM_NODE_START_LINE_COLUMN(scope_node->parser, node); + LABEL *match_succeeded_label = NEW_LABEL(location.line); + + PUSH_INSN(ret, location, dup); + PUSH_INSNL(ret, location, branchif, match_succeeded_label); + + PUSH_INSN1(ret, location, putspecialobject, INT2FIX(VM_SPECIAL_OBJECT_VMCORE)); + PUSH_INSN1(ret, location, putobject, rb_fstring_lit("%p === %p does not return true")); + PUSH_INSN1(ret, location, topn, INT2FIX(3)); + PUSH_INSN1(ret, location, topn, INT2FIX(5)); + PUSH_SEND(ret, location, id_core_sprintf, INT2FIX(3)); + PUSH_INSN1(ret, location, setn, INT2FIX(base_index + PM_PATTERN_BASE_INDEX_OFFSET_ERROR_STRING + 1)); + PUSH_INSN1(ret, location, putobject, Qfalse); + PUSH_INSN1(ret, location, setn, INT2FIX(base_index + PM_PATTERN_BASE_INDEX_OFFSET_KEY_ERROR_P + 2)); + PUSH_INSN(ret, location, pop); + PUSH_INSN(ret, location, pop); + + PUSH_LABEL(ret, match_succeeded_label); + PUSH_INSN1(ret, location, setn, INT2FIX(2)); + PUSH_INSN(ret, location, pop); + PUSH_INSN(ret, location, pop); return COMPILE_OK; } @@ -1724,9 +1894,9 @@ pm_compile_pattern_eqq_error(rb_iseq_t *iseq, pm_scope_node_t *scope_node, const static int pm_compile_pattern_match(rb_iseq_t *iseq, pm_scope_node_t *scope_node, const pm_node_t *node, LINK_ANCHOR *const ret, LABEL *unmatched_label, bool in_single_pattern, bool in_alternation_pattern, bool use_deconstructed_cache, unsigned int base_index) { - LABEL *matched_label = NEW_LABEL(nd_line(node)); + LABEL *matched_label = NEW_LABEL(pm_node_line_number(scope_node->parser, node)); CHECK(pm_compile_pattern(iseq, scope_node, node, ret, matched_label, unmatched_label, in_single_pattern, in_alternation_pattern, use_deconstructed_cache, base_index)); - ADD_LABEL(ret, matched_label); + PUSH_LABEL(ret, matched_label); return COMPILE_OK; } @@ -1738,47 +1908,47 @@ pm_compile_pattern_match(rb_iseq_t *iseq, pm_scope_node_t *scope_node, const pm_ static int pm_compile_pattern_deconstruct(rb_iseq_t *iseq, pm_scope_node_t *scope_node, const pm_node_t *node, LINK_ANCHOR *const ret, LABEL *deconstruct_label, LABEL *match_failed_label, LABEL *deconstructed_label, LABEL *type_error_label, bool in_single_pattern, bool use_deconstructed_cache, unsigned int base_index) { - pm_line_node_t line; - pm_line_node(&line, scope_node, node); + const pm_line_column_t location = PM_NODE_START_LINE_COLUMN(scope_node->parser, node); if (use_deconstructed_cache) { - ADD_INSN1(ret, &line.node, topn, INT2FIX(base_index + PM_PATTERN_BASE_INDEX_OFFSET_DECONSTRUCTED_CACHE)); - ADD_INSNL(ret, &line.node, branchnil, deconstruct_label); + PUSH_INSN1(ret, location, topn, INT2FIX(base_index + PM_PATTERN_BASE_INDEX_OFFSET_DECONSTRUCTED_CACHE)); + PUSH_INSNL(ret, location, branchnil, deconstruct_label); - ADD_INSN1(ret, &line.node, topn, INT2FIX(base_index + PM_PATTERN_BASE_INDEX_OFFSET_DECONSTRUCTED_CACHE)); - ADD_INSNL(ret, &line.node, branchunless, match_failed_label); + PUSH_INSN1(ret, location, topn, INT2FIX(base_index + PM_PATTERN_BASE_INDEX_OFFSET_DECONSTRUCTED_CACHE)); + PUSH_INSNL(ret, location, branchunless, match_failed_label); - ADD_INSN(ret, &line.node, pop); - ADD_INSN1(ret, &line.node, topn, INT2FIX(base_index + PM_PATTERN_BASE_INDEX_OFFSET_DECONSTRUCTED_CACHE - 1)); - ADD_INSNL(ret, &line.node, jump, deconstructed_label); - } else { - ADD_INSNL(ret, &line.node, jump, deconstruct_label); + PUSH_INSN(ret, location, pop); + PUSH_INSN1(ret, location, topn, INT2FIX(base_index + PM_PATTERN_BASE_INDEX_OFFSET_DECONSTRUCTED_CACHE - 1)); + PUSH_INSNL(ret, location, jump, deconstructed_label); + } + else { + PUSH_INSNL(ret, location, jump, deconstruct_label); } - ADD_LABEL(ret, deconstruct_label); - ADD_INSN(ret, &line.node, dup); - ADD_INSN1(ret, &line.node, putobject, ID2SYM(rb_intern("deconstruct"))); - ADD_SEND(ret, &line.node, idRespond_to, INT2FIX(1)); + PUSH_LABEL(ret, deconstruct_label); + PUSH_INSN(ret, location, dup); + PUSH_INSN1(ret, location, putobject, ID2SYM(rb_intern("deconstruct"))); + PUSH_SEND(ret, location, idRespond_to, INT2FIX(1)); if (use_deconstructed_cache) { - ADD_INSN1(ret, &line.node, setn, INT2FIX(base_index + PM_PATTERN_BASE_INDEX_OFFSET_DECONSTRUCTED_CACHE + 1)); + PUSH_INSN1(ret, location, setn, INT2FIX(base_index + PM_PATTERN_BASE_INDEX_OFFSET_DECONSTRUCTED_CACHE + 1)); } if (in_single_pattern) { CHECK(pm_compile_pattern_generic_error(iseq, scope_node, node, ret, rb_fstring_lit("%p does not respond to #deconstruct"), base_index + 1)); } - ADD_INSNL(ret, &line.node, branchunless, match_failed_label); - ADD_SEND(ret, &line.node, rb_intern("deconstruct"), INT2FIX(0)); + PUSH_INSNL(ret, location, branchunless, match_failed_label); + PUSH_SEND(ret, location, rb_intern("deconstruct"), INT2FIX(0)); if (use_deconstructed_cache) { - ADD_INSN1(ret, &line.node, setn, INT2FIX(base_index + PM_PATTERN_BASE_INDEX_OFFSET_DECONSTRUCTED_CACHE)); + PUSH_INSN1(ret, location, setn, INT2FIX(base_index + PM_PATTERN_BASE_INDEX_OFFSET_DECONSTRUCTED_CACHE)); } - ADD_INSN(ret, &line.node, dup); - ADD_INSN1(ret, &line.node, checktype, INT2FIX(T_ARRAY)); - ADD_INSNL(ret, &line.node, branchunless, type_error_label); - ADD_LABEL(ret, deconstructed_label); + PUSH_INSN(ret, location, dup); + PUSH_INSN1(ret, location, checktype, INT2FIX(T_ARRAY)); + PUSH_INSNL(ret, location, branchunless, type_error_label); + PUSH_LABEL(ret, deconstructed_label); return COMPILE_OK; } @@ -1790,20 +1960,19 @@ pm_compile_pattern_deconstruct(rb_iseq_t *iseq, pm_scope_node_t *scope_node, con static int pm_compile_pattern_constant(rb_iseq_t *iseq, pm_scope_node_t *scope_node, const pm_node_t *node, LINK_ANCHOR *const ret, LABEL *match_failed_label, bool in_single_pattern, unsigned int base_index) { - pm_line_node_t line; - pm_line_node(&line, scope_node, node); + const pm_line_column_t location = PM_NODE_START_LINE_COLUMN(scope_node->parser, node); - ADD_INSN(ret, &line.node, dup); + PUSH_INSN(ret, location, dup); PM_COMPILE_NOT_POPPED(node); if (in_single_pattern) { - ADD_INSN1(ret, &line.node, dupn, INT2FIX(2)); + PUSH_INSN1(ret, location, dupn, INT2FIX(2)); } - ADD_INSN1(ret, &line.node, checkmatch, INT2FIX(VM_CHECKMATCH_TYPE_CASE)); + PUSH_INSN1(ret, location, checkmatch, INT2FIX(VM_CHECKMATCH_TYPE_CASE)); if (in_single_pattern) { CHECK(pm_compile_pattern_eqq_error(iseq, scope_node, node, ret, base_index + 3)); } - ADD_INSNL(ret, &line.node, branchunless, match_failed_label); + PUSH_INSNL(ret, location, branchunless, match_failed_label); return COMPILE_OK; } @@ -1814,11 +1983,9 @@ pm_compile_pattern_constant(rb_iseq_t *iseq, pm_scope_node_t *scope_node, const static void pm_compile_pattern_error_handler(rb_iseq_t *iseq, const pm_scope_node_t *scope_node, const pm_node_t *node, LINK_ANCHOR *const ret, LABEL *done_label, bool popped) { - pm_line_node_t line; - pm_line_node(&line, scope_node, node); - - LABEL *key_error_label = NEW_LABEL(line.lineno); - LABEL *cleanup_label = NEW_LABEL(line.lineno); + const pm_line_column_t location = PM_NODE_START_LINE_COLUMN(scope_node->parser, node); + LABEL *key_error_label = NEW_LABEL(location.line); + LABEL *cleanup_label = NEW_LABEL(location.line); struct rb_callinfo_kwarg *kw_arg = rb_xmalloc_mul_add(2, sizeof(VALUE), sizeof(struct rb_callinfo_kwarg)); kw_arg->references = 0; @@ -1826,37 +1993,37 @@ pm_compile_pattern_error_handler(rb_iseq_t *iseq, const pm_scope_node_t *scope_n kw_arg->keywords[0] = ID2SYM(rb_intern("matchee")); kw_arg->keywords[1] = ID2SYM(rb_intern("key")); - ADD_INSN1(ret, &line.node, putspecialobject, INT2FIX(VM_SPECIAL_OBJECT_VMCORE)); - ADD_INSN1(ret, &line.node, topn, INT2FIX(PM_PATTERN_BASE_INDEX_OFFSET_KEY_ERROR_P + 2)); - ADD_INSNL(ret, &line.node, branchif, key_error_label); - - ADD_INSN1(ret, &line.node, putobject, rb_eNoMatchingPatternError); - ADD_INSN1(ret, &line.node, putspecialobject, INT2FIX(VM_SPECIAL_OBJECT_VMCORE)); - ADD_INSN1(ret, &line.node, putobject, rb_fstring_lit("%p: %s")); - ADD_INSN1(ret, &line.node, topn, INT2FIX(4)); - ADD_INSN1(ret, &line.node, topn, INT2FIX(PM_PATTERN_BASE_INDEX_OFFSET_ERROR_STRING + 6)); - ADD_SEND(ret, &line.node, id_core_sprintf, INT2FIX(3)); - ADD_SEND(ret, &line.node, id_core_raise, INT2FIX(2)); - ADD_INSNL(ret, &line.node, jump, cleanup_label); - - ADD_LABEL(ret, key_error_label); - ADD_INSN1(ret, &line.node, putobject, rb_eNoMatchingPatternKeyError); - ADD_INSN1(ret, &line.node, putspecialobject, INT2FIX(VM_SPECIAL_OBJECT_VMCORE)); - ADD_INSN1(ret, &line.node, putobject, rb_fstring_lit("%p: %s")); - ADD_INSN1(ret, &line.node, topn, INT2FIX(4)); - ADD_INSN1(ret, &line.node, topn, INT2FIX(PM_PATTERN_BASE_INDEX_OFFSET_ERROR_STRING + 6)); - ADD_SEND(ret, &line.node, id_core_sprintf, INT2FIX(3)); - ADD_INSN1(ret, &line.node, topn, INT2FIX(PM_PATTERN_BASE_INDEX_OFFSET_KEY_ERROR_MATCHEE + 4)); - ADD_INSN1(ret, &line.node, topn, INT2FIX(PM_PATTERN_BASE_INDEX_OFFSET_KEY_ERROR_KEY + 5)); - ADD_SEND_R(ret, &line.node, rb_intern("new"), INT2FIX(1), NULL, INT2FIX(VM_CALL_KWARG), kw_arg); - ADD_SEND(ret, &line.node, id_core_raise, INT2FIX(1)); - ADD_LABEL(ret, cleanup_label); - - ADD_INSN1(ret, &line.node, adjuststack, INT2FIX(7)); - if (!popped) ADD_INSN(ret, &line.node, putnil); - ADD_INSNL(ret, &line.node, jump, done_label); - ADD_INSN1(ret, &line.node, dupn, INT2FIX(5)); - if (popped) ADD_INSN(ret, &line.node, putnil); + PUSH_INSN1(ret, location, putspecialobject, INT2FIX(VM_SPECIAL_OBJECT_VMCORE)); + PUSH_INSN1(ret, location, topn, INT2FIX(PM_PATTERN_BASE_INDEX_OFFSET_KEY_ERROR_P + 2)); + PUSH_INSNL(ret, location, branchif, key_error_label); + + PUSH_INSN1(ret, location, putobject, rb_eNoMatchingPatternError); + PUSH_INSN1(ret, location, putspecialobject, INT2FIX(VM_SPECIAL_OBJECT_VMCORE)); + PUSH_INSN1(ret, location, putobject, rb_fstring_lit("%p: %s")); + PUSH_INSN1(ret, location, topn, INT2FIX(4)); + PUSH_INSN1(ret, location, topn, INT2FIX(PM_PATTERN_BASE_INDEX_OFFSET_ERROR_STRING + 6)); + PUSH_SEND(ret, location, id_core_sprintf, INT2FIX(3)); + PUSH_SEND(ret, location, id_core_raise, INT2FIX(2)); + PUSH_INSNL(ret, location, jump, cleanup_label); + + PUSH_LABEL(ret, key_error_label); + PUSH_INSN1(ret, location, putobject, rb_eNoMatchingPatternKeyError); + PUSH_INSN1(ret, location, putspecialobject, INT2FIX(VM_SPECIAL_OBJECT_VMCORE)); + PUSH_INSN1(ret, location, putobject, rb_fstring_lit("%p: %s")); + PUSH_INSN1(ret, location, topn, INT2FIX(4)); + PUSH_INSN1(ret, location, topn, INT2FIX(PM_PATTERN_BASE_INDEX_OFFSET_ERROR_STRING + 6)); + PUSH_SEND(ret, location, id_core_sprintf, INT2FIX(3)); + PUSH_INSN1(ret, location, topn, INT2FIX(PM_PATTERN_BASE_INDEX_OFFSET_KEY_ERROR_MATCHEE + 4)); + PUSH_INSN1(ret, location, topn, INT2FIX(PM_PATTERN_BASE_INDEX_OFFSET_KEY_ERROR_KEY + 5)); + PUSH_SEND_R(ret, location, rb_intern("new"), INT2FIX(1), NULL, INT2FIX(VM_CALL_KWARG), kw_arg); + PUSH_SEND(ret, location, id_core_raise, INT2FIX(1)); + PUSH_LABEL(ret, cleanup_label); + + PUSH_INSN1(ret, location, adjuststack, INT2FIX(7)); + if (!popped) PUSH_INSN(ret, location, putnil); + PUSH_INSNL(ret, location, jump, done_label); + PUSH_INSN1(ret, location, dupn, INT2FIX(5)); + if (popped) PUSH_INSN(ret, location, putnil); } /** @@ -1865,8 +2032,7 @@ pm_compile_pattern_error_handler(rb_iseq_t *iseq, const pm_scope_node_t *scope_n static int pm_compile_pattern(rb_iseq_t *iseq, pm_scope_node_t *scope_node, const pm_node_t *node, LINK_ANCHOR *const ret, LABEL *matched_label, LABEL *unmatched_label, bool in_single_pattern, bool in_alternation_pattern, bool use_deconstructed_cache, unsigned int base_index) { - pm_line_node_t line; - pm_line_node(&line, scope_node, node); + const pm_line_column_t location = PM_NODE_START_LINE_COLUMN(scope_node->parser, node); switch (PM_NODE_TYPE(node)) { case PM_ARRAY_PATTERN_NODE: { @@ -1892,14 +2058,14 @@ pm_compile_pattern(rb_iseq_t *iseq, pm_scope_node_t *scope_node, const pm_node_t use_rest_size = (rest_named || (!rest_named && posts_size > 0)); } - LABEL *match_failed_label = NEW_LABEL(line.lineno); - LABEL *type_error_label = NEW_LABEL(line.lineno); - LABEL *deconstruct_label = NEW_LABEL(line.lineno); - LABEL *deconstructed_label = NEW_LABEL(line.lineno); + LABEL *match_failed_label = NEW_LABEL(location.line); + LABEL *type_error_label = NEW_LABEL(location.line); + LABEL *deconstruct_label = NEW_LABEL(location.line); + LABEL *deconstructed_label = NEW_LABEL(location.line); if (use_rest_size) { - ADD_INSN1(ret, &line.node, putobject, INT2FIX(0)); - ADD_INSN(ret, &line.node, swap); + PUSH_INSN1(ret, location, putobject, INT2FIX(0)); + PUSH_INSN(ret, location, swap); base_index++; } @@ -1909,81 +2075,82 @@ pm_compile_pattern(rb_iseq_t *iseq, pm_scope_node_t *scope_node, const pm_node_t CHECK(pm_compile_pattern_deconstruct(iseq, scope_node, node, ret, deconstruct_label, match_failed_label, deconstructed_label, type_error_label, in_single_pattern, use_deconstructed_cache, base_index)); - ADD_INSN(ret, &line.node, dup); - ADD_SEND(ret, &line.node, idLength, INT2FIX(0)); - ADD_INSN1(ret, &line.node, putobject, INT2FIX(minimum_size)); - ADD_SEND(ret, &line.node, cast->rest == NULL ? idEq : idGE, INT2FIX(1)); + PUSH_INSN(ret, location, dup); + PUSH_SEND(ret, location, idLength, INT2FIX(0)); + PUSH_INSN1(ret, location, putobject, INT2FIX(minimum_size)); + PUSH_SEND(ret, location, cast->rest == NULL ? idEq : idGE, INT2FIX(1)); if (in_single_pattern) { VALUE message = cast->rest == NULL ? rb_fstring_lit("%p length mismatch (given %p, expected %p)") : rb_fstring_lit("%p length mismatch (given %p, expected %p+)"); CHECK(pm_compile_pattern_length_error(iseq, scope_node, node, ret, message, INT2FIX(minimum_size), base_index + 1)); } - ADD_INSNL(ret, &line.node, branchunless, match_failed_label); + PUSH_INSNL(ret, location, branchunless, match_failed_label); for (size_t index = 0; index < requireds_size; index++) { const pm_node_t *required = cast->requireds.nodes[index]; - ADD_INSN(ret, &line.node, dup); - ADD_INSN1(ret, &line.node, putobject, INT2FIX(index)); - ADD_SEND(ret, &line.node, idAREF, INT2FIX(1)); + PUSH_INSN(ret, location, dup); + PUSH_INSN1(ret, location, putobject, INT2FIX(index)); + PUSH_SEND(ret, location, idAREF, INT2FIX(1)); CHECK(pm_compile_pattern_match(iseq, scope_node, required, ret, match_failed_label, in_single_pattern, in_alternation_pattern, false, base_index + 1)); } if (cast->rest != NULL) { if (rest_named) { - ADD_INSN(ret, &line.node, dup); - ADD_INSN1(ret, &line.node, putobject, INT2FIX(requireds_size)); - ADD_INSN1(ret, &line.node, topn, INT2FIX(1)); - ADD_SEND(ret, &line.node, idLength, INT2FIX(0)); - ADD_INSN1(ret, &line.node, putobject, INT2FIX(minimum_size)); - ADD_SEND(ret, &line.node, idMINUS, INT2FIX(1)); - ADD_INSN1(ret, &line.node, setn, INT2FIX(4)); - ADD_SEND(ret, &line.node, idAREF, INT2FIX(2)); + PUSH_INSN(ret, location, dup); + PUSH_INSN1(ret, location, putobject, INT2FIX(requireds_size)); + PUSH_INSN1(ret, location, topn, INT2FIX(1)); + PUSH_SEND(ret, location, idLength, INT2FIX(0)); + PUSH_INSN1(ret, location, putobject, INT2FIX(minimum_size)); + PUSH_SEND(ret, location, idMINUS, INT2FIX(1)); + PUSH_INSN1(ret, location, setn, INT2FIX(4)); + PUSH_SEND(ret, location, idAREF, INT2FIX(2)); CHECK(pm_compile_pattern_match(iseq, scope_node, ((const pm_splat_node_t *) cast->rest)->expression, ret, match_failed_label, in_single_pattern, in_alternation_pattern, false, base_index + 1)); - } else if (posts_size > 0) { - ADD_INSN(ret, &line.node, dup); - ADD_SEND(ret, &line.node, idLength, INT2FIX(0)); - ADD_INSN1(ret, &line.node, putobject, INT2FIX(minimum_size)); - ADD_SEND(ret, &line.node, idMINUS, INT2FIX(1)); - ADD_INSN1(ret, &line.node, setn, INT2FIX(2)); - ADD_INSN(ret, &line.node, pop); + } + else if (posts_size > 0) { + PUSH_INSN(ret, location, dup); + PUSH_SEND(ret, location, idLength, INT2FIX(0)); + PUSH_INSN1(ret, location, putobject, INT2FIX(minimum_size)); + PUSH_SEND(ret, location, idMINUS, INT2FIX(1)); + PUSH_INSN1(ret, location, setn, INT2FIX(2)); + PUSH_INSN(ret, location, pop); } } for (size_t index = 0; index < posts_size; index++) { const pm_node_t *post = cast->posts.nodes[index]; - ADD_INSN(ret, &line.node, dup); + PUSH_INSN(ret, location, dup); - ADD_INSN1(ret, &line.node, putobject, INT2FIX(requireds_size + index)); - ADD_INSN1(ret, &line.node, topn, INT2FIX(3)); - ADD_SEND(ret, &line.node, idPLUS, INT2FIX(1)); - ADD_SEND(ret, &line.node, idAREF, INT2FIX(1)); + PUSH_INSN1(ret, location, putobject, INT2FIX(requireds_size + index)); + PUSH_INSN1(ret, location, topn, INT2FIX(3)); + PUSH_SEND(ret, location, idPLUS, INT2FIX(1)); + PUSH_SEND(ret, location, idAREF, INT2FIX(1)); CHECK(pm_compile_pattern_match(iseq, scope_node, post, ret, match_failed_label, in_single_pattern, in_alternation_pattern, false, base_index + 1)); } - ADD_INSN(ret, &line.node, pop); + PUSH_INSN(ret, location, pop); if (use_rest_size) { - ADD_INSN(ret, &line.node, pop); + PUSH_INSN(ret, location, pop); } - ADD_INSNL(ret, &line.node, jump, matched_label); - ADD_INSN(ret, &line.node, putnil); + PUSH_INSNL(ret, location, jump, matched_label); + PUSH_INSN(ret, location, putnil); if (use_rest_size) { - ADD_INSN(ret, &line.node, putnil); + PUSH_INSN(ret, location, putnil); } - ADD_LABEL(ret, type_error_label); - ADD_INSN1(ret, &line.node, putspecialobject, INT2FIX(VM_SPECIAL_OBJECT_VMCORE)); - ADD_INSN1(ret, &line.node, putobject, rb_eTypeError); - ADD_INSN1(ret, &line.node, putobject, rb_fstring_lit("deconstruct must return Array")); - ADD_SEND(ret, &line.node, id_core_raise, INT2FIX(2)); - ADD_INSN(ret, &line.node, pop); + PUSH_LABEL(ret, type_error_label); + PUSH_INSN1(ret, location, putspecialobject, INT2FIX(VM_SPECIAL_OBJECT_VMCORE)); + PUSH_INSN1(ret, location, putobject, rb_eTypeError); + PUSH_INSN1(ret, location, putobject, rb_fstring_lit("deconstruct must return Array")); + PUSH_SEND(ret, location, id_core_raise, INT2FIX(2)); + PUSH_INSN(ret, location, pop); - ADD_LABEL(ret, match_failed_label); - ADD_INSN(ret, &line.node, pop); + PUSH_LABEL(ret, match_failed_label); + PUSH_INSN(ret, location, pop); if (use_rest_size) { - ADD_INSN(ret, &line.node, pop); + PUSH_INSN(ret, location, pop); } - ADD_INSNL(ret, &line.node, jump, unmatched_label); + PUSH_INSNL(ret, location, jump, unmatched_label); break; } case PM_FIND_PATTERN_NODE: { @@ -1998,10 +2165,10 @@ pm_compile_pattern(rb_iseq_t *iseq, pm_scope_node_t *scope_node, const pm_node_t const pm_find_pattern_node_t *cast = (const pm_find_pattern_node_t *) node; const size_t size = cast->requireds.size; - LABEL *match_failed_label = NEW_LABEL(line.lineno); - LABEL *type_error_label = NEW_LABEL(line.lineno); - LABEL *deconstruct_label = NEW_LABEL(line.lineno); - LABEL *deconstructed_label = NEW_LABEL(line.lineno); + LABEL *match_failed_label = NEW_LABEL(location.line); + LABEL *type_error_label = NEW_LABEL(location.line); + LABEL *deconstruct_label = NEW_LABEL(location.line); + LABEL *deconstructed_label = NEW_LABEL(location.line); if (cast->constant) { CHECK(pm_compile_pattern_constant(iseq, scope_node, cast->constant, ret, match_failed_label, in_single_pattern, base_index)); @@ -2009,45 +2176,45 @@ pm_compile_pattern(rb_iseq_t *iseq, pm_scope_node_t *scope_node, const pm_node_t CHECK(pm_compile_pattern_deconstruct(iseq, scope_node, node, ret, deconstruct_label, match_failed_label, deconstructed_label, type_error_label, in_single_pattern, use_deconstructed_cache, base_index)); - ADD_INSN(ret, &line.node, dup); - ADD_SEND(ret, &line.node, idLength, INT2FIX(0)); - ADD_INSN1(ret, &line.node, putobject, INT2FIX(size)); - ADD_SEND(ret, &line.node, idGE, INT2FIX(1)); + PUSH_INSN(ret, location, dup); + PUSH_SEND(ret, location, idLength, INT2FIX(0)); + PUSH_INSN1(ret, location, putobject, INT2FIX(size)); + PUSH_SEND(ret, location, idGE, INT2FIX(1)); if (in_single_pattern) { CHECK(pm_compile_pattern_length_error(iseq, scope_node, node, ret, rb_fstring_lit("%p length mismatch (given %p, expected %p+)"), INT2FIX(size), base_index + 1)); } - ADD_INSNL(ret, &line.node, branchunless, match_failed_label); + PUSH_INSNL(ret, location, branchunless, match_failed_label); { - LABEL *while_begin_label = NEW_LABEL(line.lineno); - LABEL *next_loop_label = NEW_LABEL(line.lineno); - LABEL *find_succeeded_label = NEW_LABEL(line.lineno); - LABEL *find_failed_label = NEW_LABEL(line.lineno); + LABEL *while_begin_label = NEW_LABEL(location.line); + LABEL *next_loop_label = NEW_LABEL(location.line); + LABEL *find_succeeded_label = NEW_LABEL(location.line); + LABEL *find_failed_label = NEW_LABEL(location.line); - ADD_INSN(ret, &line.node, dup); - ADD_SEND(ret, &line.node, idLength, INT2FIX(0)); + PUSH_INSN(ret, location, dup); + PUSH_SEND(ret, location, idLength, INT2FIX(0)); - ADD_INSN(ret, &line.node, dup); - ADD_INSN1(ret, &line.node, putobject, INT2FIX(size)); - ADD_SEND(ret, &line.node, idMINUS, INT2FIX(1)); - ADD_INSN1(ret, &line.node, putobject, INT2FIX(0)); - ADD_LABEL(ret, while_begin_label); + PUSH_INSN(ret, location, dup); + PUSH_INSN1(ret, location, putobject, INT2FIX(size)); + PUSH_SEND(ret, location, idMINUS, INT2FIX(1)); + PUSH_INSN1(ret, location, putobject, INT2FIX(0)); + PUSH_LABEL(ret, while_begin_label); - ADD_INSN(ret, &line.node, dup); - ADD_INSN1(ret, &line.node, topn, INT2FIX(2)); - ADD_SEND(ret, &line.node, idLE, INT2FIX(1)); - ADD_INSNL(ret, &line.node, branchunless, find_failed_label); + PUSH_INSN(ret, location, dup); + PUSH_INSN1(ret, location, topn, INT2FIX(2)); + PUSH_SEND(ret, location, idLE, INT2FIX(1)); + PUSH_INSNL(ret, location, branchunless, find_failed_label); for (size_t index = 0; index < size; index++) { - ADD_INSN1(ret, &line.node, topn, INT2FIX(3)); - ADD_INSN1(ret, &line.node, topn, INT2FIX(1)); + PUSH_INSN1(ret, location, topn, INT2FIX(3)); + PUSH_INSN1(ret, location, topn, INT2FIX(1)); if (index != 0) { - ADD_INSN1(ret, &line.node, putobject, INT2FIX(index)); - ADD_SEND(ret, &line.node, idPLUS, INT2FIX(1)); + PUSH_INSN1(ret, location, putobject, INT2FIX(index)); + PUSH_SEND(ret, location, idPLUS, INT2FIX(1)); } - ADD_SEND(ret, &line.node, idAREF, INT2FIX(1)); + PUSH_SEND(ret, location, idAREF, INT2FIX(1)); CHECK(pm_compile_pattern_match(iseq, scope_node, cast->requireds.nodes[index], ret, next_loop_label, in_single_pattern, in_alternation_pattern, false, base_index + 4)); } @@ -2055,10 +2222,10 @@ pm_compile_pattern(rb_iseq_t *iseq, pm_scope_node_t *scope_node, const pm_node_t const pm_splat_node_t *left = (const pm_splat_node_t *) cast->left; if (left->expression != NULL) { - ADD_INSN1(ret, &line.node, topn, INT2FIX(3)); - ADD_INSN1(ret, &line.node, putobject, INT2FIX(0)); - ADD_INSN1(ret, &line.node, topn, INT2FIX(2)); - ADD_SEND(ret, &line.node, idAREF, INT2FIX(2)); + PUSH_INSN1(ret, location, topn, INT2FIX(3)); + PUSH_INSN1(ret, location, putobject, INT2FIX(0)); + PUSH_INSN1(ret, location, topn, INT2FIX(2)); + PUSH_SEND(ret, location, idAREF, INT2FIX(2)); CHECK(pm_compile_pattern_match(iseq, scope_node, left->expression, ret, find_failed_label, in_single_pattern, in_alternation_pattern, false, base_index + 4)); } @@ -2066,58 +2233,58 @@ pm_compile_pattern(rb_iseq_t *iseq, pm_scope_node_t *scope_node, const pm_node_t const pm_splat_node_t *right = (const pm_splat_node_t *) cast->right; if (right->expression != NULL) { - ADD_INSN1(ret, &line.node, topn, INT2FIX(3)); - ADD_INSN1(ret, &line.node, topn, INT2FIX(1)); - ADD_INSN1(ret, &line.node, putobject, INT2FIX(size)); - ADD_SEND(ret, &line.node, idPLUS, INT2FIX(1)); - ADD_INSN1(ret, &line.node, topn, INT2FIX(3)); - ADD_SEND(ret, &line.node, idAREF, INT2FIX(2)); + PUSH_INSN1(ret, location, topn, INT2FIX(3)); + PUSH_INSN1(ret, location, topn, INT2FIX(1)); + PUSH_INSN1(ret, location, putobject, INT2FIX(size)); + PUSH_SEND(ret, location, idPLUS, INT2FIX(1)); + PUSH_INSN1(ret, location, topn, INT2FIX(3)); + PUSH_SEND(ret, location, idAREF, INT2FIX(2)); pm_compile_pattern_match(iseq, scope_node, right->expression, ret, find_failed_label, in_single_pattern, in_alternation_pattern, false, base_index + 4); } - ADD_INSNL(ret, &line.node, jump, find_succeeded_label); + PUSH_INSNL(ret, location, jump, find_succeeded_label); - ADD_LABEL(ret, next_loop_label); - ADD_INSN1(ret, &line.node, putobject, INT2FIX(1)); - ADD_SEND(ret, &line.node, idPLUS, INT2FIX(1)); - ADD_INSNL(ret, &line.node, jump, while_begin_label); + PUSH_LABEL(ret, next_loop_label); + PUSH_INSN1(ret, location, putobject, INT2FIX(1)); + PUSH_SEND(ret, location, idPLUS, INT2FIX(1)); + PUSH_INSNL(ret, location, jump, while_begin_label); - ADD_LABEL(ret, find_failed_label); - ADD_INSN1(ret, &line.node, adjuststack, INT2FIX(3)); + PUSH_LABEL(ret, find_failed_label); + PUSH_INSN1(ret, location, adjuststack, INT2FIX(3)); if (in_single_pattern) { - ADD_INSN1(ret, &line.node, putspecialobject, INT2FIX(VM_SPECIAL_OBJECT_VMCORE)); - ADD_INSN1(ret, &line.node, putobject, rb_fstring_lit("%p does not match to find pattern")); - ADD_INSN1(ret, &line.node, topn, INT2FIX(2)); - ADD_SEND(ret, &line.node, id_core_sprintf, INT2FIX(2)); - ADD_INSN1(ret, &line.node, setn, INT2FIX(base_index + PM_PATTERN_BASE_INDEX_OFFSET_ERROR_STRING + 1)); + PUSH_INSN1(ret, location, putspecialobject, INT2FIX(VM_SPECIAL_OBJECT_VMCORE)); + PUSH_INSN1(ret, location, putobject, rb_fstring_lit("%p does not match to find pattern")); + PUSH_INSN1(ret, location, topn, INT2FIX(2)); + PUSH_SEND(ret, location, id_core_sprintf, INT2FIX(2)); + PUSH_INSN1(ret, location, setn, INT2FIX(base_index + PM_PATTERN_BASE_INDEX_OFFSET_ERROR_STRING + 1)); - ADD_INSN1(ret, &line.node, putobject, Qfalse); - ADD_INSN1(ret, &line.node, setn, INT2FIX(base_index + PM_PATTERN_BASE_INDEX_OFFSET_KEY_ERROR_P + 2)); + PUSH_INSN1(ret, location, putobject, Qfalse); + PUSH_INSN1(ret, location, setn, INT2FIX(base_index + PM_PATTERN_BASE_INDEX_OFFSET_KEY_ERROR_P + 2)); - ADD_INSN(ret, &line.node, pop); - ADD_INSN(ret, &line.node, pop); + PUSH_INSN(ret, location, pop); + PUSH_INSN(ret, location, pop); } - ADD_INSNL(ret, &line.node, jump, match_failed_label); - ADD_INSN1(ret, &line.node, dupn, INT2FIX(3)); + PUSH_INSNL(ret, location, jump, match_failed_label); + PUSH_INSN1(ret, location, dupn, INT2FIX(3)); - ADD_LABEL(ret, find_succeeded_label); - ADD_INSN1(ret, &line.node, adjuststack, INT2FIX(3)); + PUSH_LABEL(ret, find_succeeded_label); + PUSH_INSN1(ret, location, adjuststack, INT2FIX(3)); } - ADD_INSN(ret, &line.node, pop); - ADD_INSNL(ret, &line.node, jump, matched_label); - ADD_INSN(ret, &line.node, putnil); + PUSH_INSN(ret, location, pop); + PUSH_INSNL(ret, location, jump, matched_label); + PUSH_INSN(ret, location, putnil); - ADD_LABEL(ret, type_error_label); - ADD_INSN1(ret, &line.node, putspecialobject, INT2FIX(VM_SPECIAL_OBJECT_VMCORE)); - ADD_INSN1(ret, &line.node, putobject, rb_eTypeError); - ADD_INSN1(ret, &line.node, putobject, rb_fstring_lit("deconstruct must return Array")); - ADD_SEND(ret, &line.node, id_core_raise, INT2FIX(2)); - ADD_INSN(ret, &line.node, pop); + PUSH_LABEL(ret, type_error_label); + PUSH_INSN1(ret, location, putspecialobject, INT2FIX(VM_SPECIAL_OBJECT_VMCORE)); + PUSH_INSN1(ret, location, putobject, rb_eTypeError); + PUSH_INSN1(ret, location, putobject, rb_fstring_lit("deconstruct must return Array")); + PUSH_SEND(ret, location, id_core_raise, INT2FIX(2)); + PUSH_INSN(ret, location, pop); - ADD_LABEL(ret, match_failed_label); - ADD_INSN(ret, &line.node, pop); - ADD_INSNL(ret, &line.node, jump, unmatched_label); + PUSH_LABEL(ret, match_failed_label); + PUSH_INSN(ret, location, pop); + PUSH_INSNL(ret, location, jump, unmatched_label); break; } @@ -2136,8 +2303,8 @@ pm_compile_pattern(rb_iseq_t *iseq, pm_scope_node_t *scope_node, const pm_node_t bool has_rest = cast->rest != NULL && !(PM_NODE_TYPE_P(cast->rest, PM_ASSOC_SPLAT_NODE) && ((const pm_assoc_splat_node_t *) cast->rest)->value == NULL); bool has_keys = cast->elements.size > 0 || cast->rest != NULL; - LABEL *match_failed_label = NEW_LABEL(line.lineno); - LABEL *type_error_label = NEW_LABEL(line.lineno); + LABEL *match_failed_label = NEW_LABEL(location.line); + LABEL *type_error_label = NEW_LABEL(location.line); VALUE keys = Qnil; if (has_keys && !has_rest) { @@ -2159,28 +2326,29 @@ pm_compile_pattern(rb_iseq_t *iseq, pm_scope_node_t *scope_node, const pm_node_t CHECK(pm_compile_pattern_constant(iseq, scope_node, cast->constant, ret, match_failed_label, in_single_pattern, base_index)); } - ADD_INSN(ret, &line.node, dup); - ADD_INSN1(ret, &line.node, putobject, ID2SYM(rb_intern("deconstruct_keys"))); - ADD_SEND(ret, &line.node, idRespond_to, INT2FIX(1)); + PUSH_INSN(ret, location, dup); + PUSH_INSN1(ret, location, putobject, ID2SYM(rb_intern("deconstruct_keys"))); + PUSH_SEND(ret, location, idRespond_to, INT2FIX(1)); if (in_single_pattern) { CHECK(pm_compile_pattern_generic_error(iseq, scope_node, node, ret, rb_fstring_lit("%p does not respond to #deconstruct_keys"), base_index + 1)); } - ADD_INSNL(ret, &line.node, branchunless, match_failed_label); + PUSH_INSNL(ret, location, branchunless, match_failed_label); if (NIL_P(keys)) { - ADD_INSN(ret, &line.node, putnil); - } else { - ADD_INSN1(ret, &line.node, duparray, keys); + PUSH_INSN(ret, location, putnil); + } + else { + PUSH_INSN1(ret, location, duparray, keys); RB_OBJ_WRITTEN(iseq, Qundef, rb_obj_hide(keys)); } - ADD_SEND(ret, &line.node, rb_intern("deconstruct_keys"), INT2FIX(1)); + PUSH_SEND(ret, location, rb_intern("deconstruct_keys"), INT2FIX(1)); - ADD_INSN(ret, &line.node, dup); - ADD_INSN1(ret, &line.node, checktype, INT2FIX(T_HASH)); - ADD_INSNL(ret, &line.node, branchunless, type_error_label); + PUSH_INSN(ret, location, dup); + PUSH_INSN1(ret, location, checktype, INT2FIX(T_HASH)); + PUSH_INSNL(ret, location, branchunless, type_error_label); if (has_rest) { - ADD_SEND(ret, &line.node, rb_intern("dup"), INT2FIX(0)); + PUSH_SEND(ret, location, rb_intern("dup"), INT2FIX(0)); } if (has_keys) { @@ -2196,33 +2364,33 @@ pm_compile_pattern(rb_iseq_t *iseq, pm_scope_node_t *scope_node, const pm_node_t RUBY_ASSERT(PM_NODE_TYPE_P(key, PM_SYMBOL_NODE)); VALUE symbol = ID2SYM(parse_string_symbol(scope_node, (const pm_symbol_node_t *) key)); - ADD_INSN(ret, &line.node, dup); - ADD_INSN1(ret, &line.node, putobject, symbol); - ADD_SEND(ret, &line.node, rb_intern("key?"), INT2FIX(1)); + PUSH_INSN(ret, location, dup); + PUSH_INSN1(ret, location, putobject, symbol); + PUSH_SEND(ret, location, rb_intern("key?"), INT2FIX(1)); if (in_single_pattern) { - LABEL *match_succeeded_label = NEW_LABEL(line.lineno); - - ADD_INSN(ret, &line.node, dup); - ADD_INSNL(ret, &line.node, branchif, match_succeeded_label); - - ADD_INSN1(ret, &line.node, putobject, rb_str_freeze(rb_sprintf("key not found: %+"PRIsVALUE, symbol))); - ADD_INSN1(ret, &line.node, setn, INT2FIX(base_index + PM_PATTERN_BASE_INDEX_OFFSET_ERROR_STRING + 2)); - ADD_INSN1(ret, &line.node, putobject, Qtrue); - ADD_INSN1(ret, &line.node, setn, INT2FIX(base_index + PM_PATTERN_BASE_INDEX_OFFSET_KEY_ERROR_P + 3)); - ADD_INSN1(ret, &line.node, topn, INT2FIX(3)); - ADD_INSN1(ret, &line.node, setn, INT2FIX(base_index + PM_PATTERN_BASE_INDEX_OFFSET_KEY_ERROR_MATCHEE + 4)); - ADD_INSN1(ret, &line.node, putobject, symbol); - ADD_INSN1(ret, &line.node, setn, INT2FIX(base_index + PM_PATTERN_BASE_INDEX_OFFSET_KEY_ERROR_KEY + 5)); - - ADD_INSN1(ret, &line.node, adjuststack, INT2FIX(4)); - ADD_LABEL(ret, match_succeeded_label); + LABEL *match_succeeded_label = NEW_LABEL(location.line); + + PUSH_INSN(ret, location, dup); + PUSH_INSNL(ret, location, branchif, match_succeeded_label); + + PUSH_INSN1(ret, location, putobject, rb_str_freeze(rb_sprintf("key not found: %+"PRIsVALUE, symbol))); + PUSH_INSN1(ret, location, setn, INT2FIX(base_index + PM_PATTERN_BASE_INDEX_OFFSET_ERROR_STRING + 2)); + PUSH_INSN1(ret, location, putobject, Qtrue); + PUSH_INSN1(ret, location, setn, INT2FIX(base_index + PM_PATTERN_BASE_INDEX_OFFSET_KEY_ERROR_P + 3)); + PUSH_INSN1(ret, location, topn, INT2FIX(3)); + PUSH_INSN1(ret, location, setn, INT2FIX(base_index + PM_PATTERN_BASE_INDEX_OFFSET_KEY_ERROR_MATCHEE + 4)); + PUSH_INSN1(ret, location, putobject, symbol); + PUSH_INSN1(ret, location, setn, INT2FIX(base_index + PM_PATTERN_BASE_INDEX_OFFSET_KEY_ERROR_KEY + 5)); + + PUSH_INSN1(ret, location, adjuststack, INT2FIX(4)); + PUSH_LABEL(ret, match_succeeded_label); } - ADD_INSNL(ret, &line.node, branchunless, match_failed_label); - ADD_INSN(match_values, &line.node, dup); - ADD_INSN1(match_values, &line.node, putobject, symbol); - ADD_SEND(match_values, &line.node, has_rest ? rb_intern("delete") : idAREF, INT2FIX(1)); + PUSH_INSNL(ret, location, branchunless, match_failed_label); + PUSH_INSN(match_values, location, dup); + PUSH_INSN1(match_values, location, putobject, symbol); + PUSH_SEND(match_values, location, has_rest ? rb_intern("delete") : idAREF, INT2FIX(1)); const pm_node_t *value = assoc->value; if (PM_NODE_TYPE_P(value, PM_IMPLICIT_NODE)) { @@ -2232,30 +2400,31 @@ pm_compile_pattern(rb_iseq_t *iseq, pm_scope_node_t *scope_node, const pm_node_t CHECK(pm_compile_pattern_match(iseq, scope_node, value, match_values, match_failed_label, in_single_pattern, in_alternation_pattern, false, base_index + 1)); } - ADD_SEQ(ret, match_values); - } else { - ADD_INSN(ret, &line.node, dup); - ADD_SEND(ret, &line.node, idEmptyP, INT2FIX(0)); + PUSH_SEQ(ret, match_values); + } + else { + PUSH_INSN(ret, location, dup); + PUSH_SEND(ret, location, idEmptyP, INT2FIX(0)); if (in_single_pattern) { CHECK(pm_compile_pattern_generic_error(iseq, scope_node, node, ret, rb_fstring_lit("%p is not empty"), base_index + 1)); } - ADD_INSNL(ret, &line.node, branchunless, match_failed_label); + PUSH_INSNL(ret, location, branchunless, match_failed_label); } if (has_rest) { switch (PM_NODE_TYPE(cast->rest)) { case PM_NO_KEYWORDS_PARAMETER_NODE: { - ADD_INSN(ret, &line.node, dup); - ADD_SEND(ret, &line.node, idEmptyP, INT2FIX(0)); + PUSH_INSN(ret, location, dup); + PUSH_SEND(ret, location, idEmptyP, INT2FIX(0)); if (in_single_pattern) { pm_compile_pattern_generic_error(iseq, scope_node, node, ret, rb_fstring_lit("rest of %p is not empty"), base_index + 1); } - ADD_INSNL(ret, &line.node, branchunless, match_failed_label); + PUSH_INSNL(ret, location, branchunless, match_failed_label); break; } case PM_ASSOC_SPLAT_NODE: { const pm_assoc_splat_node_t *splat = (const pm_assoc_splat_node_t *) cast->rest; - ADD_INSN(ret, &line.node, dup); + PUSH_INSN(ret, location, dup); pm_compile_pattern_match(iseq, scope_node, splat->value, ret, match_failed_label, in_single_pattern, in_alternation_pattern, false, base_index + 1); break; } @@ -2265,20 +2434,20 @@ pm_compile_pattern(rb_iseq_t *iseq, pm_scope_node_t *scope_node, const pm_node_t } } - ADD_INSN(ret, &line.node, pop); - ADD_INSNL(ret, &line.node, jump, matched_label); - ADD_INSN(ret, &line.node, putnil); - - ADD_LABEL(ret, type_error_label); - ADD_INSN1(ret, &line.node, putspecialobject, INT2FIX(VM_SPECIAL_OBJECT_VMCORE)); - ADD_INSN1(ret, &line.node, putobject, rb_eTypeError); - ADD_INSN1(ret, &line.node, putobject, rb_fstring_lit("deconstruct_keys must return Hash")); - ADD_SEND(ret, &line.node, id_core_raise, INT2FIX(2)); - ADD_INSN(ret, &line.node, pop); + PUSH_INSN(ret, location, pop); + PUSH_INSNL(ret, location, jump, matched_label); + PUSH_INSN(ret, location, putnil); - ADD_LABEL(ret, match_failed_label); - ADD_INSN(ret, &line.node, pop); - ADD_INSNL(ret, &line.node, jump, unmatched_label); + PUSH_LABEL(ret, type_error_label); + PUSH_INSN1(ret, location, putspecialobject, INT2FIX(VM_SPECIAL_OBJECT_VMCORE)); + PUSH_INSN1(ret, location, putobject, rb_eTypeError); + PUSH_INSN1(ret, location, putobject, rb_fstring_lit("deconstruct_keys must return Hash")); + PUSH_SEND(ret, location, id_core_raise, INT2FIX(2)); + PUSH_INSN(ret, location, pop); + + PUSH_LABEL(ret, match_failed_label); + PUSH_INSN(ret, location, pop); + PUSH_INSNL(ret, location, jump, unmatched_label); break; } case PM_CAPTURE_PATTERN_NODE: { @@ -2293,16 +2462,16 @@ pm_compile_pattern(rb_iseq_t *iseq, pm_scope_node_t *scope_node, const pm_node_t // against) and a target (the place to write the variable into). const pm_capture_pattern_node_t *cast = (const pm_capture_pattern_node_t *) node; - LABEL *match_failed_label = NEW_LABEL(line.lineno); + LABEL *match_failed_label = NEW_LABEL(location.line); - ADD_INSN(ret, &line.node, dup); + PUSH_INSN(ret, location, dup); CHECK(pm_compile_pattern_match(iseq, scope_node, cast->value, ret, match_failed_label, in_single_pattern, in_alternation_pattern, use_deconstructed_cache, base_index + 1)); CHECK(pm_compile_pattern(iseq, scope_node, cast->target, ret, matched_label, match_failed_label, in_single_pattern, in_alternation_pattern, false, base_index)); - ADD_INSN(ret, &line.node, putnil); + PUSH_INSN(ret, location, putnil); - ADD_LABEL(ret, match_failed_label); - ADD_INSN(ret, &line.node, pop); - ADD_INSNL(ret, &line.node, jump, unmatched_label); + PUSH_LABEL(ret, match_failed_label); + PUSH_INSN(ret, location, pop); + PUSH_INSNL(ret, location, jump, unmatched_label); break; } @@ -2310,7 +2479,7 @@ pm_compile_pattern(rb_iseq_t *iseq, pm_scope_node_t *scope_node, const pm_node_t // Local variables can be targeted by placing identifiers in the place // of a pattern. For example, foo in bar. This results in the value // being matched being written to that local variable. - pm_local_variable_target_node_t *cast = (pm_local_variable_target_node_t *) node; + const pm_local_variable_target_node_t *cast = (const pm_local_variable_target_node_t *) node; pm_local_index_t index = pm_lookup_local_index(iseq, scope_node, cast->name, cast->depth); // If this local variable is being written from within an alternation @@ -2327,43 +2496,49 @@ pm_compile_pattern(rb_iseq_t *iseq, pm_scope_node_t *scope_node, const pm_node_t } } - ADD_SETLOCAL(ret, &line.node, index.index, index.level); - ADD_INSNL(ret, &line.node, jump, matched_label); + PUSH_SETLOCAL(ret, location, index.index, index.level); + PUSH_INSNL(ret, location, jump, matched_label); break; } case PM_ALTERNATION_PATTERN_NODE: { // Alternation patterns allow you to specify multiple patterns in a // single expression using the | operator. - pm_alternation_pattern_node_t *cast = (pm_alternation_pattern_node_t *) node; + const pm_alternation_pattern_node_t *cast = (const pm_alternation_pattern_node_t *) node; - LABEL *matched_left_label = NEW_LABEL(line.lineno); - LABEL *unmatched_left_label = NEW_LABEL(line.lineno); + LABEL *matched_left_label = NEW_LABEL(location.line); + LABEL *unmatched_left_label = NEW_LABEL(location.line); // First, we're going to attempt to match against the left pattern. If // that pattern matches, then we'll skip matching the right pattern. - ADD_INSN(ret, &line.node, dup); + PUSH_INSN(ret, location, dup); CHECK(pm_compile_pattern(iseq, scope_node, cast->left, ret, matched_left_label, unmatched_left_label, in_single_pattern, true, true, base_index + 1)); // If we get here, then we matched on the left pattern. In this case we // should pop out the duplicate value that we preemptively added to // match against the right pattern and then jump to the match label. - ADD_LABEL(ret, matched_left_label); - ADD_INSN(ret, &line.node, pop); - ADD_INSNL(ret, &line.node, jump, matched_label); - ADD_INSN(ret, &line.node, putnil); + PUSH_LABEL(ret, matched_left_label); + PUSH_INSN(ret, location, pop); + PUSH_INSNL(ret, location, jump, matched_label); + PUSH_INSN(ret, location, putnil); // If we get here, then we didn't match on the left pattern. In this // case we attempt to match against the right pattern. - ADD_LABEL(ret, unmatched_left_label); + PUSH_LABEL(ret, unmatched_left_label); CHECK(pm_compile_pattern(iseq, scope_node, cast->right, ret, matched_label, unmatched_label, in_single_pattern, true, true, base_index)); break; } + case PM_PARENTHESES_NODE: + // Parentheses are allowed to wrap expressions in pattern matching and + // they do nothing since they can only wrap individual expressions and + // not groups. In this case we'll recurse back into this same function + // with the body of the parentheses. + return pm_compile_pattern(iseq, scope_node, ((const pm_parentheses_node_t *) node)->body, ret, matched_label, unmatched_label, in_single_pattern, in_alternation_pattern, use_deconstructed_cache, base_index); case PM_PINNED_EXPRESSION_NODE: // Pinned expressions are a way to match against the value of an // expression that should be evaluated at runtime. This looks like: // foo in ^(bar). To compile these, we compile the expression as if it // were a literal value by falling through to the literal case. - node = ((pm_pinned_expression_node_t *) node)->expression; + node = ((const pm_pinned_expression_node_t *) node)->expression; /* fallthrough */ case PM_ARRAY_NODE: case PM_CLASS_VARIABLE_READ_NODE: @@ -2398,17 +2573,17 @@ pm_compile_pattern(rb_iseq_t *iseq, pm_scope_node_t *scope_node, const pm_node_t // VM-level === operator. PM_COMPILE_NOT_POPPED(node); if (in_single_pattern) { - ADD_INSN1(ret, &line.node, dupn, INT2FIX(2)); + PUSH_INSN1(ret, location, dupn, INT2FIX(2)); } - ADD_INSN1(ret, &line.node, checkmatch, INT2FIX(VM_CHECKMATCH_TYPE_CASE)); + PUSH_INSN1(ret, location, checkmatch, INT2FIX(VM_CHECKMATCH_TYPE_CASE)); if (in_single_pattern) { pm_compile_pattern_eqq_error(iseq, scope_node, node, ret, base_index + 2); } - ADD_INSNL(ret, &line.node, branchif, matched_label); - ADD_INSNL(ret, &line.node, jump, unmatched_label); + PUSH_INSNL(ret, location, branchif, matched_label); + PUSH_INSNL(ret, location, jump, unmatched_label); break; } case PM_PINNED_VARIABLE_NODE: { @@ -2416,7 +2591,7 @@ pm_compile_pattern(rb_iseq_t *iseq, pm_scope_node_t *scope_node, const pm_node_t // without it looking like you're trying to write to the variable. This // looks like: foo in ^@bar. To compile these, we compile the variable // that they hold. - pm_pinned_variable_node_t *cast = (pm_pinned_variable_node_t *) node; + const pm_pinned_variable_node_t *cast = (const pm_pinned_variable_node_t *) node; CHECK(pm_compile_pattern(iseq, scope_node, cast->variable, ret, matched_label, unmatched_label, in_single_pattern, in_alternation_pattern, true, base_index)); break; } @@ -2442,7 +2617,8 @@ pm_compile_pattern(rb_iseq_t *iseq, pm_scope_node_t *scope_node, const pm_node_t RUBY_ASSERT(cast->statements != NULL && cast->statements->body.size == 1); statement = cast->statements->body.nodes[0]; - } else { + } + else { const pm_unless_node_t *cast = (const pm_unless_node_t *) node; predicate = cast->predicate; @@ -2454,33 +2630,35 @@ pm_compile_pattern(rb_iseq_t *iseq, pm_scope_node_t *scope_node, const pm_node_t PM_COMPILE_NOT_POPPED(predicate); if (in_single_pattern) { - LABEL *match_succeeded_label = NEW_LABEL(line.lineno); + LABEL *match_succeeded_label = NEW_LABEL(location.line); - ADD_INSN(ret, &line.node, dup); + PUSH_INSN(ret, location, dup); if (PM_NODE_TYPE_P(node, PM_IF_NODE)) { - ADD_INSNL(ret, &line.node, branchif, match_succeeded_label); - } else { - ADD_INSNL(ret, &line.node, branchunless, match_succeeded_label); + PUSH_INSNL(ret, location, branchif, match_succeeded_label); + } + else { + PUSH_INSNL(ret, location, branchunless, match_succeeded_label); } - ADD_INSN1(ret, &line.node, putobject, rb_fstring_lit("guard clause does not return true")); - ADD_INSN1(ret, &line.node, setn, INT2FIX(base_index + PM_PATTERN_BASE_INDEX_OFFSET_ERROR_STRING + 1)); - ADD_INSN1(ret, &line.node, putobject, Qfalse); - ADD_INSN1(ret, &line.node, setn, INT2FIX(base_index + PM_PATTERN_BASE_INDEX_OFFSET_KEY_ERROR_P + 2)); + PUSH_INSN1(ret, location, putobject, rb_fstring_lit("guard clause does not return true")); + PUSH_INSN1(ret, location, setn, INT2FIX(base_index + PM_PATTERN_BASE_INDEX_OFFSET_ERROR_STRING + 1)); + PUSH_INSN1(ret, location, putobject, Qfalse); + PUSH_INSN1(ret, location, setn, INT2FIX(base_index + PM_PATTERN_BASE_INDEX_OFFSET_KEY_ERROR_P + 2)); - ADD_INSN(ret, &line.node, pop); - ADD_INSN(ret, &line.node, pop); + PUSH_INSN(ret, location, pop); + PUSH_INSN(ret, location, pop); - ADD_LABEL(ret, match_succeeded_label); + PUSH_LABEL(ret, match_succeeded_label); } if (PM_NODE_TYPE_P(node, PM_IF_NODE)) { - ADD_INSNL(ret, &line.node, branchunless, unmatched_label); - } else { - ADD_INSNL(ret, &line.node, branchif, unmatched_label); + PUSH_INSNL(ret, location, branchunless, unmatched_label); + } + else { + PUSH_INSNL(ret, location, branchif, unmatched_label); } - ADD_INSNL(ret, &line.node, jump, matched_label); + PUSH_INSNL(ret, location, jump, matched_label); break; } default: @@ -2513,51 +2691,59 @@ pm_scope_node_init(const pm_node_t *node, pm_scope_node_t *scope, pm_scope_node_ scope->base.location.end = node->location.end; scope->previous = previous; - scope->ast_node = (pm_node_t *)node; + scope->ast_node = (pm_node_t *) node; if (previous) { scope->parser = previous->parser; scope->encoding = previous->encoding; + scope->filepath_encoding = previous->filepath_encoding; scope->constants = previous->constants; } switch (PM_NODE_TYPE(node)) { case PM_BLOCK_NODE: { - pm_block_node_t *cast = (pm_block_node_t *) node; + const pm_block_node_t *cast = (const pm_block_node_t *) node; scope->body = cast->body; scope->locals = cast->locals; scope->parameters = cast->parameters; break; } case PM_CLASS_NODE: { - pm_class_node_t *cast = (pm_class_node_t *) node; + const pm_class_node_t *cast = (const pm_class_node_t *) node; scope->body = cast->body; scope->locals = cast->locals; break; } case PM_DEF_NODE: { - pm_def_node_t *cast = (pm_def_node_t *) node; - scope->parameters = (pm_node_t *)cast->parameters; + const pm_def_node_t *cast = (const pm_def_node_t *) node; + scope->parameters = (pm_node_t *) cast->parameters; scope->body = cast->body; scope->locals = cast->locals; break; } case PM_ENSURE_NODE: { - scope->body = (pm_node_t *)node; + const pm_ensure_node_t *cast = (const pm_ensure_node_t *) node; + scope->body = (pm_node_t *) node; + + if (cast->statements != NULL) { + scope->base.location.start = cast->statements->base.location.start; + scope->base.location.end = cast->statements->base.location.end; + } + break; } case PM_FOR_NODE: { - pm_for_node_t *cast = (pm_for_node_t *)node; - scope->body = (pm_node_t *)cast->statements; + const pm_for_node_t *cast = (const pm_for_node_t *) node; + scope->body = (pm_node_t *) cast->statements; break; } case PM_INTERPOLATED_REGULAR_EXPRESSION_NODE: { RUBY_ASSERT(node->flags & PM_REGULAR_EXPRESSION_FLAGS_ONCE); - scope->body = (pm_node_t *)node; + scope->body = (pm_node_t *) node; break; } case PM_LAMBDA_NODE: { - pm_lambda_node_t *cast = (pm_lambda_node_t *) node; + const pm_lambda_node_t *cast = (const pm_lambda_node_t *) node; scope->parameters = cast->parameters; scope->body = cast->body; scope->locals = cast->locals; @@ -2571,77 +2757,168 @@ pm_scope_node_init(const pm_node_t *node, pm_scope_node_t *scope, pm_scope_node_ break; } case PM_MODULE_NODE: { - pm_module_node_t *cast = (pm_module_node_t *) node; + const pm_module_node_t *cast = (const pm_module_node_t *) node; scope->body = cast->body; scope->locals = cast->locals; break; } case PM_POST_EXECUTION_NODE: { - pm_post_execution_node_t *cast = (pm_post_execution_node_t *) node; + const pm_post_execution_node_t *cast = (const pm_post_execution_node_t *) node; scope->body = (pm_node_t *) cast->statements; break; } case PM_PROGRAM_NODE: { - pm_program_node_t *cast = (pm_program_node_t *) node; + const pm_program_node_t *cast = (const pm_program_node_t *) node; scope->body = (pm_node_t *) cast->statements; scope->locals = cast->locals; break; } case PM_RESCUE_NODE: { - pm_rescue_node_t *cast = (pm_rescue_node_t *)node; - scope->body = (pm_node_t *)cast->statements; + const pm_rescue_node_t *cast = (const pm_rescue_node_t *) node; + scope->body = (pm_node_t *) cast->statements; break; } case PM_RESCUE_MODIFIER_NODE: { - pm_rescue_modifier_node_t *cast = (pm_rescue_modifier_node_t *)node; - scope->body = (pm_node_t *)cast->rescue_expression; + const pm_rescue_modifier_node_t *cast = (const pm_rescue_modifier_node_t *) node; + scope->body = (pm_node_t *) cast->rescue_expression; break; } case PM_SINGLETON_CLASS_NODE: { - pm_singleton_class_node_t *cast = (pm_singleton_class_node_t *) node; + const pm_singleton_class_node_t *cast = (const pm_singleton_class_node_t *) node; scope->body = cast->body; scope->locals = cast->locals; break; } case PM_STATEMENTS_NODE: { - pm_statements_node_t *cast = (pm_statements_node_t *) node; - scope->body = (pm_node_t *)cast; + const pm_statements_node_t *cast = (const pm_statements_node_t *) node; + scope->body = (pm_node_t *) cast; break; } default: rb_bug("unreachable"); break; } -} +} + +void +pm_scope_node_destroy(pm_scope_node_t *scope_node) +{ + if (scope_node->index_lookup_table) { + st_free_table(scope_node->index_lookup_table); + } +} + +static void +pm_compile_call(rb_iseq_t *iseq, const pm_call_node_t *call_node, LINK_ANCHOR *const ret, bool popped, pm_scope_node_t *scope_node, ID method_id, LABEL *start) +{ + const pm_location_t *message_loc = &call_node->message_loc; + if (message_loc->start == NULL) message_loc = &call_node->base.location; + + const pm_line_column_t location = PM_LOCATION_LINE_COLUMN(scope_node->parser, message_loc); + LABEL *else_label = NEW_LABEL(location.line); + LABEL *end_label = NEW_LABEL(location.line); + + if (PM_NODE_FLAG_P(call_node, PM_CALL_NODE_FLAGS_SAFE_NAVIGATION)) { + PUSH_INSN(ret, location, dup); + PUSH_INSNL(ret, location, branchnil, else_label); + } + + int flags = 0; + struct rb_callinfo_kwarg *kw_arg = NULL; + + int orig_argc = pm_setup_args(call_node->arguments, call_node->block, &flags, &kw_arg, iseq, ret, scope_node, &location); + const rb_iseq_t *block_iseq = NULL; + + if (call_node->block != NULL && PM_NODE_TYPE_P(call_node->block, PM_BLOCK_NODE)) { + // Scope associated with the block + pm_scope_node_t next_scope_node; + pm_scope_node_init(call_node->block, &next_scope_node, scope_node); + + block_iseq = NEW_CHILD_ISEQ(&next_scope_node, make_name_for_block(iseq), ISEQ_TYPE_BLOCK, pm_node_line_number(scope_node->parser, call_node->block)); + pm_scope_node_destroy(&next_scope_node); + + if (ISEQ_BODY(block_iseq)->catch_table) { + PUSH_CATCH_ENTRY(CATCH_TYPE_BREAK, start, end_label, block_iseq, end_label); + } + ISEQ_COMPILE_DATA(iseq)->current_block = block_iseq; + } + else { + if (PM_NODE_FLAG_P(call_node, PM_CALL_NODE_FLAGS_VARIABLE_CALL)) { + flags |= VM_CALL_VCALL; + } + + if (!flags) { + flags |= VM_CALL_ARGS_SIMPLE; + } + } + + if (PM_NODE_FLAG_P(call_node, PM_CALL_NODE_FLAGS_IGNORE_VISIBILITY)) { + flags |= VM_CALL_FCALL; + } + + if (!popped && PM_NODE_FLAG_P(call_node, PM_CALL_NODE_FLAGS_ATTRIBUTE_WRITE)) { + if (flags & VM_CALL_ARGS_BLOCKARG) { + PUSH_INSN1(ret, location, topn, INT2FIX(1)); + if (flags & VM_CALL_ARGS_SPLAT) { + PUSH_INSN1(ret, location, putobject, INT2FIX(-1)); + PUSH_SEND_WITH_FLAG(ret, location, idAREF, INT2FIX(1), INT2FIX(0)); + } + PUSH_INSN1(ret, location, setn, INT2FIX(orig_argc + 3)); + PUSH_INSN(ret, location, pop); + } + else if (flags & VM_CALL_ARGS_SPLAT) { + PUSH_INSN(ret, location, dup); + PUSH_INSN1(ret, location, putobject, INT2FIX(-1)); + PUSH_SEND_WITH_FLAG(ret, location, idAREF, INT2FIX(1), INT2FIX(0)); + PUSH_INSN1(ret, location, setn, INT2FIX(orig_argc + 2)); + PUSH_INSN(ret, location, pop); + } + else { + PUSH_INSN1(ret, location, setn, INT2FIX(orig_argc + 1)); + } + } -void -pm_scope_node_destroy(pm_scope_node_t *scope_node) -{ - if (scope_node->index_lookup_table) { - st_free_table(scope_node->index_lookup_table); + if ((flags & VM_CALL_KW_SPLAT) && (flags & VM_CALL_ARGS_BLOCKARG) && !(flags & VM_CALL_KW_SPLAT_MUT)) { + PUSH_INSN(ret, location, splatkw); } -} -static void pm_compile_call(rb_iseq_t *iseq, const pm_call_node_t *call_node, LINK_ANCHOR *const ret, bool popped, pm_scope_node_t *scope_node, ID method_id, LABEL *start); + PUSH_SEND_R(ret, location, method_id, INT2FIX(orig_argc), block_iseq, INT2FIX(flags), kw_arg); -void -pm_compile_defined_expr0(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, bool popped, pm_scope_node_t *scope_node, NODE dummy_line_node, int lineno, bool in_condition, LABEL **lfinish, bool explicit_receiver) + if (PM_NODE_FLAG_P(call_node, PM_CALL_NODE_FLAGS_SAFE_NAVIGATION)) { + PUSH_INSNL(ret, location, jump, end_label); + PUSH_LABEL(ret, else_label); + } + + if (PM_NODE_FLAG_P(call_node, PM_CALL_NODE_FLAGS_SAFE_NAVIGATION) || (block_iseq && ISEQ_BODY(block_iseq)->catch_table)) { + PUSH_LABEL(ret, end_label); + } + + if (PM_NODE_FLAG_P(call_node, PM_CALL_NODE_FLAGS_ATTRIBUTE_WRITE) && !popped) { + PUSH_INSN(ret, location, pop); + } + + if (popped) PUSH_INSN(ret, location, pop); +} + +static void +pm_compile_defined_expr0(rb_iseq_t *iseq, const pm_node_t *node, const pm_line_column_t *node_location, LINK_ANCHOR *const ret, bool popped, pm_scope_node_t *scope_node, bool in_condition, LABEL **lfinish, bool explicit_receiver) { // in_condition is the same as compile.c's needstr enum defined_type dtype = DEFINED_NOT_DEFINED; + const pm_line_column_t location = *node_location; switch (PM_NODE_TYPE(node)) { case PM_ARGUMENTS_NODE: { - const pm_arguments_node_t *cast = (pm_arguments_node_t *) node; + const pm_arguments_node_t *cast = (const pm_arguments_node_t *) node; const pm_node_list_t *arguments = &cast->arguments; for (size_t idx = 0; idx < arguments->size; idx++) { const pm_node_t *argument = arguments->nodes[idx]; - pm_compile_defined_expr0(iseq, argument, ret, popped, scope_node, dummy_line_node, lineno, in_condition, lfinish, explicit_receiver); + pm_compile_defined_expr0(iseq, argument, node_location, ret, popped, scope_node, in_condition, lfinish, explicit_receiver); if (!lfinish[1]) { - lfinish[1] = NEW_LABEL(lineno); + lfinish[1] = NEW_LABEL(location.line); } - ADD_INSNL(ret, &dummy_line_node, branchunless, lfinish[1]); + PUSH_INSNL(ret, location, branchunless, lfinish[1]); } dtype = DEFINED_TRUE; break; @@ -2659,7 +2936,7 @@ pm_compile_defined_expr0(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *co else if (PM_NODE_TYPE_P(cast->body, PM_STATEMENTS_NODE) && ((const pm_statements_node_t *) cast->body)->body.size == 1) { // If we have a parentheses node that is wrapping a single statement // then we want to recurse down to that statement and compile it. - pm_compile_defined_expr0(iseq, ((const pm_statements_node_t *) cast->body)->body.nodes[0], ret, popped, scope_node, dummy_line_node, lineno, in_condition, lfinish, explicit_receiver); + pm_compile_defined_expr0(iseq, ((const pm_statements_node_t *) cast->body)->body.nodes[0], node_location, ret, popped, scope_node, in_condition, lfinish, explicit_receiver); return; } else { @@ -2680,17 +2957,17 @@ pm_compile_defined_expr0(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *co dtype = DEFINED_FALSE; break; case PM_ARRAY_NODE: { - pm_array_node_t *cast = (pm_array_node_t *) node; + const pm_array_node_t *cast = (const pm_array_node_t *) node; if (!PM_NODE_FLAG_P(cast, PM_ARRAY_NODE_FLAGS_CONTAINS_SPLAT)) { for (size_t index = 0; index < cast->elements.size; index++) { - pm_compile_defined_expr0(iseq, cast->elements.nodes[index], ret, popped, scope_node, dummy_line_node, lineno, true, lfinish, false); + pm_compile_defined_expr0(iseq, cast->elements.nodes[index], node_location, ret, popped, scope_node, true, lfinish, false); if (!lfinish[1]) { - lfinish[1] = NEW_LABEL(lineno); + lfinish[1] = NEW_LABEL(location.line); } - ADD_INSNL(ret, &dummy_line_node, branchunless, lfinish[1]); + PUSH_INSNL(ret, location, branchunless, lfinish[1]); } } } @@ -2741,133 +3018,127 @@ pm_compile_defined_expr0(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *co case PM_LOCAL_VARIABLE_READ_NODE: dtype = DEFINED_LVAR; break; + #define PUSH_VAL(type) (in_condition ? Qtrue : rb_iseq_defined_string(type)) + case PM_INSTANCE_VARIABLE_READ_NODE: { - pm_instance_variable_read_node_t *instance_variable_read_node = (pm_instance_variable_read_node_t *)node; - ID id = pm_constant_id_lookup(scope_node, instance_variable_read_node->name); - ADD_INSN3(ret, &dummy_line_node, definedivar, - ID2SYM(id), get_ivar_ic_value(iseq, id), PUSH_VAL(DEFINED_IVAR)); + const pm_instance_variable_read_node_t *cast = (const pm_instance_variable_read_node_t *) node; + + ID name = pm_constant_id_lookup(scope_node, cast->name); + PUSH_INSN3(ret, location, definedivar, ID2SYM(name), get_ivar_ic_value(iseq, name), PUSH_VAL(DEFINED_IVAR)); + return; } case PM_BACK_REFERENCE_READ_NODE: { - char *char_ptr = (char *)(node->location.start) + 1; + const char *char_ptr = (const char *) (node->location.start + 1); ID backref_val = INT2FIX(rb_intern2(char_ptr, 1)) << 1 | 1; - PM_PUTNIL; - ADD_INSN3(ret, &dummy_line_node, defined, INT2FIX(DEFINED_REF), - backref_val, - PUSH_VAL(DEFINED_GVAR)); + PUSH_INSN(ret, location, putnil); + PUSH_INSN3(ret, location, defined, INT2FIX(DEFINED_REF), backref_val, PUSH_VAL(DEFINED_GVAR)); return; } case PM_NUMBERED_REFERENCE_READ_NODE: { - uint32_t reference_number = ((pm_numbered_reference_read_node_t *)node)->number; + uint32_t reference_number = ((const pm_numbered_reference_read_node_t *) node)->number; - PM_PUTNIL; - ADD_INSN3(ret, &dummy_line_node, defined, INT2FIX(DEFINED_REF), - INT2FIX(reference_number << 1), - PUSH_VAL(DEFINED_GVAR)); + PUSH_INSN(ret, location, putnil); + PUSH_INSN3(ret, location, defined, INT2FIX(DEFINED_REF), INT2FIX(reference_number << 1), PUSH_VAL(DEFINED_GVAR)); return; } case PM_GLOBAL_VARIABLE_READ_NODE: { - pm_global_variable_read_node_t *glabal_variable_read_node = (pm_global_variable_read_node_t *)node; - PM_PUTNIL; - ADD_INSN3(ret, &dummy_line_node, defined, INT2FIX(DEFINED_GVAR), - ID2SYM(pm_constant_id_lookup(scope_node, glabal_variable_read_node->name)), PUSH_VAL(DEFINED_GVAR)); + const pm_global_variable_read_node_t *cast = (const pm_global_variable_read_node_t *) node; + VALUE name = ID2SYM(pm_constant_id_lookup(scope_node, cast->name)); + + PUSH_INSN(ret, location, putnil); + PUSH_INSN3(ret, location, defined, INT2FIX(DEFINED_GVAR), name, PUSH_VAL(DEFINED_GVAR)); + return; } case PM_CLASS_VARIABLE_READ_NODE: { - pm_class_variable_read_node_t *class_variable_read_node = (pm_class_variable_read_node_t *)node; - PM_PUTNIL; - ADD_INSN3(ret, &dummy_line_node, defined, INT2FIX(DEFINED_CVAR), - ID2SYM(pm_constant_id_lookup(scope_node, class_variable_read_node->name)), PUSH_VAL(DEFINED_CVAR)); + const pm_class_variable_read_node_t *cast = (const pm_class_variable_read_node_t *) node; + VALUE name = ID2SYM(pm_constant_id_lookup(scope_node, cast->name)); + + PUSH_INSN(ret, location, putnil); + PUSH_INSN3(ret, location, defined, INT2FIX(DEFINED_CVAR), name, PUSH_VAL(DEFINED_CVAR)); return; } case PM_CONSTANT_READ_NODE: { - pm_constant_read_node_t *constant_node = (pm_constant_read_node_t *)node; - PM_PUTNIL; - ADD_INSN3(ret, &dummy_line_node, defined, INT2FIX(DEFINED_CONST), - ID2SYM(pm_constant_id_lookup(scope_node, constant_node->name)), PUSH_VAL(DEFINED_CONST)); + const pm_constant_read_node_t *cast = (const pm_constant_read_node_t *) node; + VALUE name = ID2SYM(pm_constant_id_lookup(scope_node, cast->name)); + + PUSH_INSN(ret, location, putnil); + PUSH_INSN3(ret, location, defined, INT2FIX(DEFINED_CONST), name, PUSH_VAL(DEFINED_CONST)); + return; } case PM_CONSTANT_PATH_NODE: { - pm_constant_path_node_t *constant_path_node = ((pm_constant_path_node_t *)node); - if (constant_path_node->parent) { - if (!lfinish[1]) { - lfinish[1] = NEW_LABEL(lineno); - } - pm_compile_defined_expr0(iseq, constant_path_node->parent, ret, popped, scope_node, dummy_line_node, lineno, true, lfinish, false); - ADD_INSNL(ret, &dummy_line_node, branchunless, lfinish[1]); - PM_COMPILE(constant_path_node->parent); + const pm_constant_path_node_t *cast = (const pm_constant_path_node_t *) node; + VALUE name = ID2SYM(pm_constant_id_lookup(scope_node, ((const pm_constant_read_node_t *) cast->child)->name)); + + if (cast->parent != NULL) { + if (!lfinish[1]) lfinish[1] = NEW_LABEL(location.line); + pm_compile_defined_expr0(iseq, cast->parent, node_location, ret, popped, scope_node, true, lfinish, false); + + PUSH_INSNL(ret, location, branchunless, lfinish[1]); + PM_COMPILE(cast->parent); } else { - ADD_INSN1(ret, &dummy_line_node, putobject, rb_cObject); + PUSH_INSN1(ret, location, putobject, rb_cObject); } - ADD_INSN3(ret, &dummy_line_node, defined, INT2FIX(DEFINED_CONST_FROM), - ID2SYM(pm_constant_id_lookup(scope_node, ((pm_constant_read_node_t *)constant_path_node->child)->name)), PUSH_VAL(DEFINED_CONST)); + + PUSH_INSN3(ret, location, defined, INT2FIX(DEFINED_CONST_FROM), name, PUSH_VAL(DEFINED_CONST)); return; } - case PM_CALL_NODE: { - pm_call_node_t *call_node = ((pm_call_node_t *)node); - ID method_id = pm_constant_id_lookup(scope_node, call_node->name); + const pm_call_node_t *cast = ((const pm_call_node_t *) node); + ID method_id = pm_constant_id_lookup(scope_node, cast->name); - if (call_node->receiver || call_node->arguments) { - if (!lfinish[1]) { - lfinish[1] = NEW_LABEL(lineno); - } - if (!lfinish[2]) { - lfinish[2] = NEW_LABEL(lineno); - } + if (cast->receiver || cast->arguments) { + if (!lfinish[1]) lfinish[1] = NEW_LABEL(location.line); + if (!lfinish[2]) lfinish[2] = NEW_LABEL(location.line); } - if (call_node->arguments) { - pm_compile_defined_expr0(iseq, (const pm_node_t *)call_node->arguments, ret, popped, scope_node, dummy_line_node, lineno, true, lfinish, false); - ADD_INSNL(ret, &dummy_line_node, branchunless, lfinish[1]); + if (cast->arguments) { + pm_compile_defined_expr0(iseq, (const pm_node_t *) cast->arguments, node_location, ret, popped, scope_node, true, lfinish, false); + PUSH_INSNL(ret, location, branchunless, lfinish[1]); } - if (call_node->receiver) { - pm_compile_defined_expr0(iseq, call_node->receiver, ret, popped, scope_node, dummy_line_node, lineno, true, lfinish, true); - if (PM_NODE_TYPE_P(call_node->receiver, PM_CALL_NODE)) { - ADD_INSNL(ret, &dummy_line_node, branchunless, lfinish[2]); + if (cast->receiver) { + pm_compile_defined_expr0(iseq, cast->receiver, node_location, ret, popped, scope_node, true, lfinish, true); - const pm_call_node_t *receiver = (const pm_call_node_t *)call_node->receiver; + if (PM_NODE_TYPE_P(cast->receiver, PM_CALL_NODE)) { + PUSH_INSNL(ret, location, branchunless, lfinish[2]); + + const pm_call_node_t *receiver = (const pm_call_node_t *) cast->receiver; ID method_id = pm_constant_id_lookup(scope_node, receiver->name); pm_compile_call(iseq, receiver, ret, popped, scope_node, method_id, NULL); } else { - ADD_INSNL(ret, &dummy_line_node, branchunless, lfinish[1]); - PM_COMPILE(call_node->receiver); - } - - if (explicit_receiver) { - PM_DUP; + PUSH_INSNL(ret, location, branchunless, lfinish[1]); + PM_COMPILE(cast->receiver); } - ADD_INSN3(ret, &dummy_line_node, defined, INT2FIX(DEFINED_METHOD), rb_id2sym(method_id), PUSH_VAL(DEFINED_METHOD)); + if (explicit_receiver) PUSH_INSN(ret, location, dup); + PUSH_INSN3(ret, location, defined, INT2FIX(DEFINED_METHOD), rb_id2sym(method_id), PUSH_VAL(DEFINED_METHOD)); } else { - PM_PUTSELF; - if (explicit_receiver) { - PM_DUP; - } - ADD_INSN3(ret, &dummy_line_node, defined, INT2FIX(DEFINED_FUNC), rb_id2sym(method_id), PUSH_VAL(DEFINED_METHOD)); + PUSH_INSN(ret, location, putself); + if (explicit_receiver) PUSH_INSN(ret, location, dup); + PUSH_INSN3(ret, location, defined, INT2FIX(DEFINED_FUNC), rb_id2sym(method_id), PUSH_VAL(DEFINED_METHOD)); } + return; } - case PM_YIELD_NODE: - PM_PUTNIL; - ADD_INSN3(ret, &dummy_line_node, defined, INT2FIX(DEFINED_YIELD), 0, - PUSH_VAL(DEFINED_YIELD)); + PUSH_INSN(ret, location, putnil); + PUSH_INSN3(ret, location, defined, INT2FIX(DEFINED_YIELD), 0, PUSH_VAL(DEFINED_YIELD)); return; case PM_SUPER_NODE: case PM_FORWARDING_SUPER_NODE: - PM_PUTNIL; - ADD_INSN3(ret, &dummy_line_node, defined, INT2FIX(DEFINED_ZSUPER), 0, - PUSH_VAL(DEFINED_ZSUPER)); + PUSH_INSN(ret, location, putnil); + PUSH_INSN3(ret, location, defined, INT2FIX(DEFINED_ZSUPER), 0, PUSH_VAL(DEFINED_ZSUPER)); return; case PM_CALL_AND_WRITE_NODE: case PM_CALL_OPERATOR_WRITE_NODE: @@ -2915,158 +3186,66 @@ pm_compile_defined_expr0(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *co } RUBY_ASSERT(dtype != DEFINED_NOT_DEFINED); - - ADD_INSN1(ret, &dummy_line_node, putobject, PUSH_VAL(dtype)); + PUSH_INSN1(ret, location, putobject, PUSH_VAL(dtype)); #undef PUSH_VAL } static void -pm_defined_expr(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, bool popped, pm_scope_node_t *scope_node, NODE dummy_line_node, int lineno, bool in_condition, LABEL **lfinish, bool explicit_receiver) +pm_defined_expr(rb_iseq_t *iseq, const pm_node_t *node, const pm_line_column_t *node_location, LINK_ANCHOR *const ret, bool popped, pm_scope_node_t *scope_node, bool in_condition, LABEL **lfinish, bool explicit_receiver) { LINK_ELEMENT *lcur = ret->last; - - pm_compile_defined_expr0(iseq, node, ret, popped, scope_node, dummy_line_node, lineno, in_condition, lfinish, false); + pm_compile_defined_expr0(iseq, node, node_location, ret, popped, scope_node, in_condition, lfinish, false); if (lfinish[1]) { - LABEL *lstart = NEW_LABEL(lineno); - LABEL *lend = NEW_LABEL(lineno); + LABEL *lstart = NEW_LABEL(node_location->line); + LABEL *lend = NEW_LABEL(node_location->line); struct rb_iseq_new_with_callback_callback_func *ifunc = rb_iseq_new_with_callback_new_callback(build_defined_rescue_iseq, NULL); - const rb_iseq_t *rescue = new_child_iseq_with_callback(iseq, ifunc, - rb_str_concat(rb_str_new2("defined guard in "), - ISEQ_BODY(iseq)->location.label), - iseq, ISEQ_TYPE_RESCUE, 0); + const rb_iseq_t *rescue = new_child_iseq_with_callback( + iseq, + ifunc, + rb_str_concat(rb_str_new2("defined guard in "), ISEQ_BODY(iseq)->location.label), + iseq, + ISEQ_TYPE_RESCUE, + 0 + ); lstart->rescued = LABEL_RESCUE_BEG; lend->rescued = LABEL_RESCUE_END; APPEND_LABEL(ret, lcur, lstart); - ADD_LABEL(ret, lend); - ADD_CATCH_ENTRY(CATCH_TYPE_RESCUE, lstart, lend, rescue, lfinish[1]); + PUSH_LABEL(ret, lend); + PUSH_CATCH_ENTRY(CATCH_TYPE_RESCUE, lstart, lend, rescue, lfinish[1]); } } -void -pm_compile_defined_expr(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, bool popped, pm_scope_node_t *scope_node, NODE dummy_line_node, int lineno, bool in_condition) +static void +pm_compile_defined_expr(rb_iseq_t *iseq, const pm_node_t *node, const pm_line_column_t *node_location, LINK_ANCHOR *const ret, bool popped, pm_scope_node_t *scope_node, bool in_condition) { LABEL *lfinish[3]; LINK_ELEMENT *last = ret->last; - lfinish[0] = NEW_LABEL(lineno); + lfinish[0] = NEW_LABEL(node_location->line); lfinish[1] = 0; lfinish[2] = 0; if (!popped) { - pm_defined_expr(iseq, node, ret, popped, scope_node, dummy_line_node, lineno, in_condition, lfinish, false); + pm_defined_expr(iseq, node, node_location, ret, popped, scope_node, in_condition, lfinish, false); } if (lfinish[1]) { - ELEM_INSERT_NEXT(last, &new_insn_body(iseq, nd_line(&dummy_line_node), nd_node_id(&dummy_line_node), BIN(putnil), 0)->link); - PM_SWAP; - if (lfinish[2]) { - ADD_LABEL(ret, lfinish[2]); - } - PM_POP; - ADD_LABEL(ret, lfinish[1]); - - } - ADD_LABEL(ret, lfinish[0]); -} - -static void -pm_compile_call(rb_iseq_t *iseq, const pm_call_node_t *call_node, LINK_ANCHOR *const ret, bool popped, pm_scope_node_t *scope_node, ID method_id, LABEL *start) -{ - const pm_location_t *message_loc = &call_node->message_loc; - if (message_loc->start == NULL) message_loc = &call_node->base.location; - - int lineno = pm_location_line_number(scope_node->parser, message_loc); - NODE dummy_line_node = generate_dummy_line_node(lineno, lineno); - - LABEL *else_label = NEW_LABEL(lineno); - LABEL *end_label = NEW_LABEL(lineno); - - if (PM_NODE_FLAG_P(call_node, PM_CALL_NODE_FLAGS_SAFE_NAVIGATION)) { - PM_DUP; - ADD_INSNL(ret, &dummy_line_node, branchnil, else_label); - } - - int flags = 0; - struct rb_callinfo_kwarg *kw_arg = NULL; - - int orig_argc = pm_setup_args(call_node->arguments, call_node->block, &flags, &kw_arg, iseq, ret, scope_node, dummy_line_node); - const rb_iseq_t *block_iseq = NULL; - - if (call_node->block != NULL && PM_NODE_TYPE_P(call_node->block, PM_BLOCK_NODE)) { - // Scope associated with the block - pm_scope_node_t next_scope_node; - pm_scope_node_init(call_node->block, &next_scope_node, scope_node); - - block_iseq = NEW_CHILD_ISEQ(&next_scope_node, make_name_for_block(iseq), ISEQ_TYPE_BLOCK, pm_node_line_number(scope_node->parser, call_node->block)); - pm_scope_node_destroy(&next_scope_node); - - if (ISEQ_BODY(block_iseq)->catch_table) { - ADD_CATCH_ENTRY(CATCH_TYPE_BREAK, start, end_label, block_iseq, end_label); - } - ISEQ_COMPILE_DATA(iseq)->current_block = block_iseq; - } - else { - if (PM_NODE_FLAG_P(call_node, PM_CALL_NODE_FLAGS_VARIABLE_CALL)) { - flags |= VM_CALL_VCALL; - } - - if (!flags) { - flags |= VM_CALL_ARGS_SIMPLE; - } - } - - if (PM_NODE_FLAG_P(call_node, PM_CALL_NODE_FLAGS_IGNORE_VISIBILITY)) { - flags |= VM_CALL_FCALL; - } - - if (!popped && PM_NODE_FLAG_P(call_node, PM_CALL_NODE_FLAGS_ATTRIBUTE_WRITE)) { - if (flags & VM_CALL_ARGS_BLOCKARG) { - ADD_INSN1(ret, &dummy_line_node, topn, INT2FIX(1)); - if (flags & VM_CALL_ARGS_SPLAT) { - ADD_INSN1(ret, &dummy_line_node, putobject, INT2FIX(-1)); - ADD_SEND_WITH_FLAG(ret, &dummy_line_node, idAREF, INT2FIX(1), INT2FIX(0)); - } - ADD_INSN1(ret, &dummy_line_node, setn, INT2FIX(orig_argc + 3)); - PM_POP; - } - else if (flags & VM_CALL_ARGS_SPLAT) { - ADD_INSN(ret, &dummy_line_node, dup); - ADD_INSN1(ret, &dummy_line_node, putobject, INT2FIX(-1)); - ADD_SEND_WITH_FLAG(ret, &dummy_line_node, idAREF, INT2FIX(1), INT2FIX(0)); - ADD_INSN1(ret, &dummy_line_node, setn, INT2FIX(orig_argc + 2)); - PM_POP; - } - else { - ADD_INSN1(ret, &dummy_line_node, setn, INT2FIX(orig_argc + 1)); - } - } - - if ((flags & VM_CALL_KW_SPLAT) && (flags & VM_CALL_ARGS_BLOCKARG) && !(flags & VM_CALL_KW_SPLAT_MUT)) { - ADD_INSN(ret, &dummy_line_node, splatkw); - } - - ADD_SEND_R(ret, &dummy_line_node, method_id, INT2FIX(orig_argc), block_iseq, INT2FIX(flags), kw_arg); - - if (PM_NODE_FLAG_P(call_node, PM_CALL_NODE_FLAGS_SAFE_NAVIGATION)) { - ADD_INSNL(ret, &dummy_line_node, jump, end_label); - ADD_LABEL(ret, else_label); - } + ELEM_INSERT_NEXT(last, &new_insn_body(iseq, node_location->line, node_location->column, BIN(putnil), 0)->link); + PUSH_INSN(ret, *node_location, swap); - if (PM_NODE_FLAG_P(call_node, PM_CALL_NODE_FLAGS_SAFE_NAVIGATION) || (block_iseq && ISEQ_BODY(block_iseq)->catch_table)) { - ADD_LABEL(ret, end_label); - } + if (lfinish[2]) PUSH_LABEL(ret, lfinish[2]); + PUSH_INSN(ret, *node_location, pop); + PUSH_LABEL(ret, lfinish[1]); - if (PM_NODE_FLAG_P(call_node, PM_CALL_NODE_FLAGS_ATTRIBUTE_WRITE)) { - PM_POP_UNLESS_POPPED; } - PM_POP_IF_POPPED; + PUSH_LABEL(ret, lfinish[0]); } // This is exactly the same as add_ensure_iseq, except it compiled @@ -3092,11 +3271,11 @@ pm_add_ensure_iseq(LINK_ANCHOR *const ret, rb_iseq_t *iseq, int is_return, pm_sc add_ensure_range(iseq, enlp->erange, lstart, lend); ISEQ_COMPILE_DATA(iseq)->ensure_node_stack = enlp->prev; - ADD_LABEL(ensure_part, lstart); + PUSH_LABEL(ensure_part, lstart); bool popped = true; - PM_COMPILE_INTO_ANCHOR(ensure_part, (pm_node_t *)enlp->ensure_node); - ADD_LABEL(ensure_part, lend); - ADD_SEQ(ensure, ensure_part); + PM_COMPILE_INTO_ANCHOR(ensure_part, (const pm_node_t *) enlp->ensure_node); + PUSH_LABEL(ensure_part, lend); + PUSH_SEQ(ensure, ensure_part); } else { if (!is_return) { @@ -3106,7 +3285,7 @@ pm_add_ensure_iseq(LINK_ANCHOR *const ret, rb_iseq_t *iseq, int is_return, pm_sc enlp = enlp->prev; } ISEQ_COMPILE_DATA(iseq)->ensure_node_stack = prev_enlp; - ADD_SEQ(ret, ensure); + PUSH_SEQ(ret, ensure); } struct pm_local_table_insert_ctx { @@ -3119,8 +3298,8 @@ static int pm_local_table_insert_func(st_data_t *key, st_data_t *value, st_data_t arg, int existing) { if (!existing) { - pm_constant_id_t constant_id = (pm_constant_id_t)*key; - struct pm_local_table_insert_ctx * ctx = (struct pm_local_table_insert_ctx *)arg; + pm_constant_id_t constant_id = (pm_constant_id_t) *key; + struct pm_local_table_insert_ctx * ctx = (struct pm_local_table_insert_ctx *) arg; pm_scope_node_t *scope_node = ctx->scope_node; rb_ast_id_table_t *local_table_for_iseq = ctx->local_table_for_iseq; @@ -3180,7 +3359,8 @@ pm_compile_destructured_param_locals(const pm_multi_target_node_t *node, st_tabl pm_insert_local_index(((const pm_required_parameter_node_t *) left)->name, local_index, index_lookup_table, local_table_for_iseq, scope_node); local_index++; } - } else { + } + else { RUBY_ASSERT(PM_NODE_TYPE_P(left, PM_MULTI_TARGET_NODE)); local_index = pm_compile_destructured_param_locals((const pm_multi_target_node_t *) left, index_lookup_table, local_table_for_iseq, scope_node, local_index); } @@ -3202,7 +3382,8 @@ pm_compile_destructured_param_locals(const pm_multi_target_node_t *node, st_tabl if (PM_NODE_TYPE_P(right, PM_REQUIRED_PARAMETER_NODE)) { pm_insert_local_index(((const pm_required_parameter_node_t *) right)->name, local_index, index_lookup_table, local_table_for_iseq, scope_node); local_index++; - } else { + } + else { RUBY_ASSERT(PM_NODE_TYPE_P(right, PM_MULTI_TARGET_NODE)); local_index = pm_compile_destructured_param_locals((const pm_multi_target_node_t *) right, index_lookup_table, local_table_for_iseq, scope_node, local_index); } @@ -3218,11 +3399,9 @@ pm_compile_destructured_param_locals(const pm_multi_target_node_t *node, st_tabl static inline void pm_compile_destructured_param_write(rb_iseq_t *iseq, const pm_required_parameter_node_t *node, LINK_ANCHOR *const ret, const pm_scope_node_t *scope_node) { - int lineno = pm_node_line_number(scope_node->parser, (const pm_node_t *) node); - NODE dummy_line_node = generate_dummy_line_node(lineno, lineno); - + const pm_line_column_t location = PM_NODE_START_LINE_COLUMN(scope_node->parser, node); pm_local_index_t index = pm_lookup_local_index(iseq, scope_node, node->name, 0); - ADD_SETLOCAL(ret, &dummy_line_node, index.index, index.level); + PUSH_SETLOCAL(ret, location, index.index, index.level); } /** @@ -3236,21 +3415,20 @@ pm_compile_destructured_param_write(rb_iseq_t *iseq, const pm_required_parameter static void pm_compile_destructured_param_writes(rb_iseq_t *iseq, const pm_multi_target_node_t *node, LINK_ANCHOR *const ret, const pm_scope_node_t *scope_node) { - int lineno = pm_node_line_number(scope_node->parser, (const pm_node_t *) node); - NODE dummy_line_node = generate_dummy_line_node(lineno, lineno); - - bool has_rest = (node->rest && PM_NODE_TYPE_P(node->rest, PM_SPLAT_NODE) && (((pm_splat_node_t *) node->rest)->expression) != NULL); + const pm_line_column_t location = PM_NODE_START_LINE_COLUMN(scope_node->parser, node); + bool has_rest = (node->rest && PM_NODE_TYPE_P(node->rest, PM_SPLAT_NODE) && (((const pm_splat_node_t *) node->rest)->expression) != NULL); bool has_rights = node->rights.size > 0; int flag = (has_rest || has_rights) ? 1 : 0; - ADD_INSN2(ret, &dummy_line_node, expandarray, INT2FIX(node->lefts.size), INT2FIX(flag)); + PUSH_INSN2(ret, location, expandarray, INT2FIX(node->lefts.size), INT2FIX(flag)); for (size_t index = 0; index < node->lefts.size; index++) { const pm_node_t *left = node->lefts.nodes[index]; if (PM_NODE_TYPE_P(left, PM_REQUIRED_PARAMETER_NODE)) { pm_compile_destructured_param_write(iseq, (const pm_required_parameter_node_t *) left, ret, scope_node); - } else { + } + else { RUBY_ASSERT(PM_NODE_TYPE_P(left, PM_MULTI_TARGET_NODE)); pm_compile_destructured_param_writes(iseq, (const pm_multi_target_node_t *) left, ret, scope_node); } @@ -3258,10 +3436,10 @@ pm_compile_destructured_param_writes(rb_iseq_t *iseq, const pm_multi_target_node if (has_rest) { if (has_rights) { - ADD_INSN2(ret, &dummy_line_node, expandarray, INT2FIX(node->rights.size), INT2FIX(3)); + PUSH_INSN2(ret, location, expandarray, INT2FIX(node->rights.size), INT2FIX(3)); } - const pm_node_t *rest = ((pm_splat_node_t *) node->rest)->expression; + const pm_node_t *rest = ((const pm_splat_node_t *) node->rest)->expression; RUBY_ASSERT(PM_NODE_TYPE_P(rest, PM_REQUIRED_PARAMETER_NODE)); pm_compile_destructured_param_write(iseq, (const pm_required_parameter_node_t *) rest, ret, scope_node); @@ -3269,7 +3447,7 @@ pm_compile_destructured_param_writes(rb_iseq_t *iseq, const pm_multi_target_node if (has_rights) { if (!has_rest) { - ADD_INSN2(ret, &dummy_line_node, expandarray, INT2FIX(node->rights.size), INT2FIX(2)); + PUSH_INSN2(ret, location, expandarray, INT2FIX(node->rights.size), INT2FIX(2)); } for (size_t index = 0; index < node->rights.size; index++) { @@ -3277,7 +3455,8 @@ pm_compile_destructured_param_writes(rb_iseq_t *iseq, const pm_multi_target_node if (PM_NODE_TYPE_P(right, PM_REQUIRED_PARAMETER_NODE)) { pm_compile_destructured_param_write(iseq, (const pm_required_parameter_node_t *) right, ret, scope_node); - } else { + } + else { RUBY_ASSERT(PM_NODE_TYPE_P(right, PM_MULTI_TARGET_NODE)); pm_compile_destructured_param_writes(iseq, (const pm_multi_target_node_t *) right, ret, scope_node); } @@ -3346,7 +3525,8 @@ pm_multi_target_state_push(pm_multi_target_state_t *state, INSN *topn, size_t st if (state->head == NULL) { state->head = node; state->tail = node; - } else { + } + else { state->tail->next = node; state->tail = node; } @@ -3432,8 +3612,7 @@ pm_compile_multi_target_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR static void pm_compile_target_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const parents, LINK_ANCHOR *const writes, LINK_ANCHOR *const cleanup, pm_scope_node_t *scope_node, pm_multi_target_state_t *state) { - int lineno = pm_node_line_number(scope_node->parser, node); - NODE dummy_line_node = generate_dummy_line_node(lineno, lineno); + const pm_line_column_t location = PM_NODE_START_LINE_COLUMN(scope_node->parser, node); switch (PM_NODE_TYPE(node)) { case PM_LOCAL_VARIABLE_TARGET_NODE: { @@ -3442,10 +3621,10 @@ pm_compile_target_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *cons // // for i in []; end // - pm_local_variable_target_node_t *cast = (pm_local_variable_target_node_t *) node; + const pm_local_variable_target_node_t *cast = (const pm_local_variable_target_node_t *) node; pm_local_index_t index = pm_lookup_local_index(iseq, scope_node, cast->name, cast->depth); - ADD_SETLOCAL(writes, &dummy_line_node, index.index, index.level); + PUSH_SETLOCAL(writes, location, index.index, index.level); break; } case PM_CLASS_VARIABLE_TARGET_NODE: { @@ -3454,10 +3633,10 @@ pm_compile_target_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *cons // // for @@i in []; end // - pm_class_variable_target_node_t *cast = (pm_class_variable_target_node_t *) node; + const pm_class_variable_target_node_t *cast = (const pm_class_variable_target_node_t *) node; ID name = pm_constant_id_lookup(scope_node, cast->name); - ADD_INSN2(writes, &dummy_line_node, setclassvariable, ID2SYM(name), get_cvar_ic_value(iseq, name)); + PUSH_INSN2(writes, location, setclassvariable, ID2SYM(name), get_cvar_ic_value(iseq, name)); break; } case PM_CONSTANT_TARGET_NODE: { @@ -3466,11 +3645,11 @@ pm_compile_target_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *cons // // for I in []; end // - pm_constant_target_node_t *cast = (pm_constant_target_node_t *) node; + const pm_constant_target_node_t *cast = (const pm_constant_target_node_t *) node; ID name = pm_constant_id_lookup(scope_node, cast->name); - ADD_INSN1(writes, &dummy_line_node, putspecialobject, INT2FIX(VM_SPECIAL_OBJECT_CONST_BASE)); - ADD_INSN1(writes, &dummy_line_node, setconstant, ID2SYM(name)); + PUSH_INSN1(writes, location, putspecialobject, INT2FIX(VM_SPECIAL_OBJECT_CONST_BASE)); + PUSH_INSN1(writes, location, setconstant, ID2SYM(name)); break; } case PM_GLOBAL_VARIABLE_TARGET_NODE: { @@ -3479,10 +3658,10 @@ pm_compile_target_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *cons // // for $i in []; end // - pm_global_variable_target_node_t *cast = (pm_global_variable_target_node_t *) node; + const pm_global_variable_target_node_t *cast = (const pm_global_variable_target_node_t *) node; ID name = pm_constant_id_lookup(scope_node, cast->name); - ADD_INSN1(writes, &dummy_line_node, setglobal, ID2SYM(name)); + PUSH_INSN1(writes, location, setglobal, ID2SYM(name)); break; } case PM_INSTANCE_VARIABLE_TARGET_NODE: { @@ -3491,10 +3670,10 @@ pm_compile_target_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *cons // // for @i in []; end // - pm_instance_variable_target_node_t *cast = (pm_instance_variable_target_node_t *) node; + const pm_instance_variable_target_node_t *cast = (const pm_instance_variable_target_node_t *) node; ID name = pm_constant_id_lookup(scope_node, cast->name); - ADD_INSN2(writes, &dummy_line_node, setinstancevariable, ID2SYM(name), get_ivar_ic_value(iseq, name)); + PUSH_INSN2(writes, location, setinstancevariable, ID2SYM(name), get_ivar_ic_value(iseq, name)); break; } case PM_CONSTANT_PATH_TARGET_NODE: { @@ -3511,21 +3690,23 @@ pm_compile_target_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *cons if (cast->parent != NULL) { pm_compile_node(iseq, cast->parent, parents, false, scope_node); - } else { - ADD_INSN1(parents, &dummy_line_node, putobject, rb_cObject); + } + else { + PUSH_INSN1(parents, location, putobject, rb_cObject); } if (state == NULL) { - ADD_INSN(writes, &dummy_line_node, swap); - } else { - ADD_INSN1(writes, &dummy_line_node, topn, INT2FIX(1)); + PUSH_INSN(writes, location, swap); + } + else { + PUSH_INSN1(writes, location, topn, INT2FIX(1)); pm_multi_target_state_push(state, (INSN *) LAST_ELEMENT(writes), 1); } - ADD_INSN1(writes, &dummy_line_node, setconstant, ID2SYM(name)); + PUSH_INSN1(writes, location, setconstant, ID2SYM(name)); if (state != NULL) { - ADD_INSN(cleanup, &dummy_line_node, pop); + PUSH_INSN(cleanup, location, pop); } break; @@ -3545,19 +3726,19 @@ pm_compile_target_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *cons pm_compile_node(iseq, cast->receiver, parents, false, scope_node); if (state != NULL) { - ADD_INSN1(writes, &dummy_line_node, topn, INT2FIX(1)); + PUSH_INSN1(writes, location, topn, INT2FIX(1)); pm_multi_target_state_push(state, (INSN *) LAST_ELEMENT(writes), 1); - ADD_INSN(writes, &dummy_line_node, swap); + PUSH_INSN(writes, location, swap); } int flags = VM_CALL_ARGS_SIMPLE; if (PM_NODE_FLAG_P(cast, PM_CALL_NODE_FLAGS_IGNORE_VISIBILITY)) flags |= VM_CALL_FCALL; - ADD_SEND_WITH_FLAG(writes, &dummy_line_node, method_id, INT2FIX(1), INT2FIX(flags)); - ADD_INSN(writes, &dummy_line_node, pop); + PUSH_SEND_WITH_FLAG(writes, location, method_id, INT2FIX(1), INT2FIX(flags)); + PUSH_INSN(writes, location, pop); if (state != NULL) { - ADD_INSN(cleanup, &dummy_line_node, pop); + PUSH_INSN(cleanup, location, pop); } break; @@ -3578,19 +3759,20 @@ pm_compile_target_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *cons int flags = 0; struct rb_callinfo_kwarg *kwargs = NULL; - int argc = pm_setup_args(cast->arguments, cast->block, &flags, &kwargs, iseq, parents, scope_node, dummy_line_node); + int argc = pm_setup_args(cast->arguments, cast->block, &flags, &kwargs, iseq, parents, scope_node, &location); if (state != NULL) { - ADD_INSN1(writes, &dummy_line_node, topn, INT2FIX(argc + 1)); + PUSH_INSN1(writes, location, topn, INT2FIX(argc + 1)); pm_multi_target_state_push(state, (INSN *) LAST_ELEMENT(writes), argc + 1); if (argc == 0) { - ADD_INSN(writes, &dummy_line_node, swap); - } else { + PUSH_INSN(writes, location, swap); + } + else { for (int index = 0; index < argc; index++) { - ADD_INSN1(writes, &dummy_line_node, topn, INT2FIX(argc + 1)); + PUSH_INSN1(writes, location, topn, INT2FIX(argc + 1)); } - ADD_INSN1(writes, &dummy_line_node, topn, INT2FIX(argc + 1)); + PUSH_INSN1(writes, location, topn, INT2FIX(argc + 1)); } } @@ -3601,20 +3783,20 @@ pm_compile_target_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *cons int ci_argc = argc + 1; if (flags & VM_CALL_ARGS_SPLAT) { ci_argc--; - ADD_INSN1(writes, &dummy_line_node, newarray, INT2FIX(1)); - ADD_INSN(writes, &dummy_line_node, concatarray); + PUSH_INSN1(writes, location, newarray, INT2FIX(1)); + PUSH_INSN(writes, location, concatarray); } - ADD_SEND_R(writes, &dummy_line_node, idASET, INT2NUM(ci_argc), NULL, INT2FIX(flags), kwargs); - ADD_INSN(writes, &dummy_line_node, pop); + PUSH_SEND_R(writes, location, idASET, INT2NUM(ci_argc), NULL, INT2FIX(flags), kwargs); + PUSH_INSN(writes, location, pop); if (state != NULL) { if (argc != 0) { - ADD_INSN(writes, &dummy_line_node, pop); + PUSH_INSN(writes, location, pop); } for (int index = 0; index < argc + 1; index++) { - ADD_INSN(cleanup, &dummy_line_node, pop); + PUSH_INSN(cleanup, location, pop); } } @@ -3646,23 +3828,21 @@ pm_compile_target_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *cons static size_t pm_compile_multi_target_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const parents, LINK_ANCHOR *const writes, LINK_ANCHOR *const cleanup, pm_scope_node_t *scope_node, pm_multi_target_state_t *state) { - int lineno = pm_node_line_number(scope_node->parser, node); - NODE dummy_line_node = generate_dummy_line_node(lineno, lineno); - + const pm_line_column_t location = PM_NODE_START_LINE_COLUMN(scope_node->parser, node); const pm_node_list_t *lefts; const pm_node_t *rest; const pm_node_list_t *rights; switch (PM_NODE_TYPE(node)) { case PM_MULTI_TARGET_NODE: { - pm_multi_target_node_t *cast = (pm_multi_target_node_t *) node; + const pm_multi_target_node_t *cast = (const pm_multi_target_node_t *) node; lefts = &cast->lefts; rest = cast->rest; rights = &cast->rights; break; } case PM_MULTI_WRITE_NODE: { - pm_multi_write_node_t *cast = (pm_multi_write_node_t *) node; + const pm_multi_write_node_t *cast = (const pm_multi_write_node_t *) node; lefts = &cast->lefts; rest = cast->rest; rights = &cast->rights; @@ -3673,13 +3853,13 @@ pm_compile_multi_target_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR break; } - bool has_rest = (rest != NULL) && PM_NODE_TYPE_P(rest, PM_SPLAT_NODE) && ((pm_splat_node_t *) rest)->expression != NULL; + bool has_rest = (rest != NULL) && PM_NODE_TYPE_P(rest, PM_SPLAT_NODE) && ((const pm_splat_node_t *) rest)->expression != NULL; bool has_posts = rights->size > 0; // The first instruction in the writes sequence is going to spread the // top value of the stack onto the number of values that we're going to // write. - ADD_INSN2(writes, &dummy_line_node, expandarray, INT2FIX(lefts->size), INT2FIX((has_rest || has_posts) ? 1 : 0)); + PUSH_INSN2(writes, location, expandarray, INT2FIX(lefts->size), INT2FIX((has_rest || has_posts) ? 1 : 0)); // We need to keep track of some additional state information as we're // going through the targets because we will need to revisit them once @@ -3697,11 +3877,11 @@ pm_compile_multi_target_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR // Next, we'll compile the rest target if there is one. if (has_rest) { - const pm_node_t *target = ((pm_splat_node_t *) rest)->expression; + const pm_node_t *target = ((const pm_splat_node_t *) rest)->expression; target_state.position = 1 + rights->size + base_position; if (has_posts) { - ADD_INSN2(writes, &dummy_line_node, expandarray, INT2FIX(rights->size), INT2FIX(3)); + PUSH_INSN2(writes, location, expandarray, INT2FIX(rights->size), INT2FIX(3)); } pm_compile_target_node(iseq, target, parents, writes, cleanup, scope_node, &target_state); @@ -3710,7 +3890,7 @@ pm_compile_multi_target_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR // Finally, we'll compile the trailing targets. if (has_posts) { if (!has_rest && rest != NULL) { - ADD_INSN2(writes, &dummy_line_node, expandarray, INT2FIX(rights->size), INT2FIX(2)); + PUSH_INSN2(writes, location, expandarray, INT2FIX(rights->size), INT2FIX(2)); } for (size_t index = 0; index < rights->size; index++) { @@ -3737,14 +3917,13 @@ pm_compile_multi_target_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR static void pm_compile_for_node_index(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, pm_scope_node_t *scope_node) { - int lineno = pm_node_line_number(scope_node->parser, node); - NODE dummy_line_node = generate_dummy_line_node(lineno, lineno); + const pm_line_column_t location = PM_NODE_START_LINE_COLUMN(scope_node->parser, node); switch (PM_NODE_TYPE(node)) { case PM_LOCAL_VARIABLE_TARGET_NODE: { // For local variables, all we have to do is retrieve the value and then // compile the index node. - ADD_GETLOCAL(ret, &dummy_line_node, 1, 0); + PUSH_GETLOCAL(ret, location, 1, 0); pm_compile_target_node(iseq, node, ret, ret, ret, scope_node, NULL); break; } @@ -3768,11 +3947,11 @@ pm_compile_for_node_index(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *c state.position = 1; pm_compile_target_node(iseq, node, ret, writes, cleanup, scope_node, &state); - ADD_GETLOCAL(ret, &dummy_line_node, 1, 0); - ADD_INSN2(ret, &dummy_line_node, expandarray, INT2FIX(1), INT2FIX(0)); + PUSH_GETLOCAL(ret, location, 1, 0); + PUSH_INSN2(ret, location, expandarray, INT2FIX(1), INT2FIX(0)); - ADD_SEQ(ret, writes); - ADD_SEQ(ret, cleanup); + PUSH_SEQ(ret, writes); + PUSH_SEQ(ret, cleanup); pm_multi_target_state_update(&state); break; @@ -3786,8 +3965,8 @@ pm_compile_for_node_index(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *c pm_compile_target_node(iseq, node, ret, writes, cleanup, scope_node, NULL); - LABEL *not_single = NEW_LABEL(lineno); - LABEL *not_ary = NEW_LABEL(lineno); + LABEL *not_single = NEW_LABEL(location.line); + LABEL *not_ary = NEW_LABEL(location.line); // When there are multiple targets, we'll do a bunch of work to convert // the value into an array before we expand it. Effectively we're trying @@ -3795,28 +3974,28 @@ pm_compile_for_node_index(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *c // // (args.length == 1 && Array.try_convert(args[0])) || args // - ADD_GETLOCAL(ret, &dummy_line_node, 1, 0); - ADD_INSN(ret, &dummy_line_node, dup); - ADD_CALL(ret, &dummy_line_node, idLength, INT2FIX(0)); - ADD_INSN1(ret, &dummy_line_node, putobject, INT2FIX(1)); - ADD_CALL(ret, &dummy_line_node, idEq, INT2FIX(1)); - ADD_INSNL(ret, &dummy_line_node, branchunless, not_single); - ADD_INSN(ret, &dummy_line_node, dup); - ADD_INSN1(ret, &dummy_line_node, putobject, INT2FIX(0)); - ADD_CALL(ret, &dummy_line_node, idAREF, INT2FIX(1)); - ADD_INSN1(ret, &dummy_line_node, putobject, rb_cArray); - PM_SWAP; - ADD_CALL(ret, &dummy_line_node, rb_intern("try_convert"), INT2FIX(1)); - ADD_INSN(ret, &dummy_line_node, dup); - ADD_INSNL(ret, &dummy_line_node, branchunless, not_ary); - PM_SWAP; - - ADD_LABEL(ret, not_ary); - PM_POP; - - ADD_LABEL(ret, not_single); - ADD_SEQ(ret, writes); - ADD_SEQ(ret, cleanup); + PUSH_GETLOCAL(ret, location, 1, 0); + PUSH_INSN(ret, location, dup); + PUSH_CALL(ret, location, idLength, INT2FIX(0)); + PUSH_INSN1(ret, location, putobject, INT2FIX(1)); + PUSH_CALL(ret, location, idEq, INT2FIX(1)); + PUSH_INSNL(ret, location, branchunless, not_single); + PUSH_INSN(ret, location, dup); + PUSH_INSN1(ret, location, putobject, INT2FIX(0)); + PUSH_CALL(ret, location, idAREF, INT2FIX(1)); + PUSH_INSN1(ret, location, putobject, rb_cArray); + PUSH_INSN(ret, location, swap); + PUSH_CALL(ret, location, rb_intern("try_convert"), INT2FIX(1)); + PUSH_INSN(ret, location, dup); + PUSH_INSNL(ret, location, branchunless, not_ary); + PUSH_INSN(ret, location, swap); + + PUSH_LABEL(ret, not_ary); + PUSH_INSN(ret, location, pop); + + PUSH_LABEL(ret, not_single); + PUSH_SEQ(ret, writes); + PUSH_SEQ(ret, cleanup); break; } default: @@ -3826,63 +4005,66 @@ pm_compile_for_node_index(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *c } static void -pm_compile_rescue(rb_iseq_t *iseq, pm_begin_node_t *begin_node, LINK_ANCHOR *const ret, int lineno, bool popped, pm_scope_node_t *scope_node) +pm_compile_rescue(rb_iseq_t *iseq, const pm_begin_node_t *cast, const pm_line_column_t *node_location, LINK_ANCHOR *const ret, bool popped, pm_scope_node_t *scope_node) { - NODE dummy_line_node = generate_dummy_line_node(lineno, lineno); const pm_parser_t *parser = scope_node->parser; - LABEL *lstart = NEW_LABEL(lineno); - LABEL *lend = NEW_LABEL(lineno); - LABEL *lcont = NEW_LABEL(lineno); + + LABEL *lstart = NEW_LABEL(node_location->line); + LABEL *lend = NEW_LABEL(node_location->line); + LABEL *lcont = NEW_LABEL(node_location->line); pm_scope_node_t rescue_scope_node; - pm_scope_node_init((pm_node_t *) begin_node->rescue_clause, &rescue_scope_node, scope_node); + pm_scope_node_init((const pm_node_t *) cast->rescue_clause, &rescue_scope_node, scope_node); rb_iseq_t *rescue_iseq = NEW_CHILD_ISEQ( &rescue_scope_node, rb_str_concat(rb_str_new2("rescue in "), ISEQ_BODY(iseq)->location.label), ISEQ_TYPE_RESCUE, - pm_node_line_number(parser, (const pm_node_t *) begin_node->rescue_clause) + pm_node_line_number(parser, (const pm_node_t *) cast->rescue_clause) ); pm_scope_node_destroy(&rescue_scope_node); lstart->rescued = LABEL_RESCUE_BEG; lend->rescued = LABEL_RESCUE_END; - ADD_LABEL(ret, lstart); + PUSH_LABEL(ret, lstart); + bool prev_in_rescue = ISEQ_COMPILE_DATA(iseq)->in_rescue; ISEQ_COMPILE_DATA(iseq)->in_rescue = true; - if (begin_node->statements) { - PM_COMPILE_NOT_POPPED((pm_node_t *)begin_node->statements); + + if (cast->statements != NULL) { + PM_COMPILE_NOT_POPPED((const pm_node_t *) cast->statements); } else { - PM_PUTNIL; + PUSH_INSN(ret, *node_location, putnil); } - ISEQ_COMPILE_DATA(iseq)->in_rescue = prev_in_rescue; - ADD_LABEL(ret, lend); + ISEQ_COMPILE_DATA(iseq)->in_rescue = prev_in_rescue; + PUSH_LABEL(ret, lend); - if (begin_node->else_clause) { - PM_POP_UNLESS_POPPED; - PM_COMPILE((pm_node_t *)begin_node->else_clause); + if (cast->else_clause != NULL) { + if (!popped) PUSH_INSN(ret, *node_location, pop); + PM_COMPILE((const pm_node_t *) cast->else_clause); } - PM_NOP; - ADD_LABEL(ret, lcont); + PUSH_INSN(ret, *node_location, nop); + PUSH_LABEL(ret, lcont); - PM_POP_IF_POPPED; - ADD_CATCH_ENTRY(CATCH_TYPE_RESCUE, lstart, lend, rescue_iseq, lcont); - ADD_CATCH_ENTRY(CATCH_TYPE_RETRY, lend, lcont, NULL, lstart); + if (popped) PUSH_INSN(ret, *node_location, pop); + PUSH_CATCH_ENTRY(CATCH_TYPE_RESCUE, lstart, lend, rescue_iseq, lcont); + PUSH_CATCH_ENTRY(CATCH_TYPE_RETRY, lend, lcont, NULL, lstart); } static void -pm_compile_ensure(rb_iseq_t *iseq, pm_begin_node_t *begin_node, LINK_ANCHOR *const ret, int lineno, bool popped, pm_scope_node_t *scope_node) +pm_compile_ensure(rb_iseq_t *iseq, const pm_begin_node_t *cast, const pm_line_column_t *node_location, LINK_ANCHOR *const ret, bool popped, pm_scope_node_t *scope_node) { - NODE dummy_line_node = generate_dummy_line_node(lineno, lineno); const pm_parser_t *parser = scope_node->parser; + const pm_statements_node_t *statements = cast->ensure_clause->statements; + const pm_line_column_t location = statements != NULL ? PM_NODE_START_LINE_COLUMN(parser, statements) : *node_location; - LABEL *estart = NEW_LABEL(lineno); - LABEL *eend = NEW_LABEL(lineno); - LABEL *econt = NEW_LABEL(lineno); + LABEL *estart = NEW_LABEL(location.line); + LABEL *eend = NEW_LABEL(location.line); + LABEL *econt = NEW_LABEL(location.line); struct ensure_range er; struct iseq_compile_data_ensure_node_stack enl; @@ -3891,52 +4073,50 @@ pm_compile_ensure(rb_iseq_t *iseq, pm_begin_node_t *begin_node, LINK_ANCHOR *con er.begin = estart; er.end = eend; er.next = 0; - push_ensure_entry(iseq, &enl, &er, (void *)begin_node->ensure_clause); + push_ensure_entry(iseq, &enl, &er, (void *) cast->ensure_clause); - ADD_LABEL(ret, estart); - if (begin_node->rescue_clause) { - pm_compile_rescue(iseq, begin_node, ret, lineno, popped, scope_node); + PUSH_LABEL(ret, estart); + if (cast->rescue_clause) { + pm_compile_rescue(iseq, cast, &location, ret, popped, scope_node); } else { - if (begin_node->statements) { - PM_COMPILE((pm_node_t *)begin_node->statements); + if (cast->statements) { + PM_COMPILE((const pm_node_t *) cast->statements); } - else { - PM_PUTNIL_UNLESS_POPPED; + else if (!popped) { + PUSH_INSN(ret, *node_location, putnil); } } - ADD_LABEL(ret, eend); - ADD_LABEL(ret, econt); + PUSH_LABEL(ret, eend); + PUSH_LABEL(ret, econt); pm_scope_node_t next_scope_node; - pm_scope_node_init((pm_node_t *)begin_node->ensure_clause, &next_scope_node, scope_node); + pm_scope_node_init((const pm_node_t *) cast->ensure_clause, &next_scope_node, scope_node); rb_iseq_t *child_iseq = NEW_CHILD_ISEQ( &next_scope_node, rb_str_concat(rb_str_new2("ensure in "), ISEQ_BODY(iseq)->location.label), ISEQ_TYPE_ENSURE, - pm_node_line_number(parser, (const pm_node_t *) begin_node->ensure_clause) + location.line ); pm_scope_node_destroy(&next_scope_node); - ISEQ_COMPILE_DATA(iseq)->current_block = child_iseq; erange = ISEQ_COMPILE_DATA(iseq)->ensure_node_stack->erange; if (estart->link.next != &eend->link) { while (erange) { - ADD_CATCH_ENTRY(CATCH_TYPE_ENSURE, erange->begin, erange->end, child_iseq, econt); + PUSH_CATCH_ENTRY(CATCH_TYPE_ENSURE, erange->begin, erange->end, child_iseq, econt); erange = erange->next; } } ISEQ_COMPILE_DATA(iseq)->ensure_node_stack = enl.prev; // Compile the ensure entry - pm_statements_node_t *statements = begin_node->ensure_clause->statements; - if (statements) { - PM_COMPILE((pm_node_t *)statements); - PM_POP_UNLESS_POPPED; + if (statements != NULL) { + PM_COMPILE((const pm_node_t *) statements); + if (!popped) PUSH_INSN(ret, *node_location, pop); } } @@ -3971,7 +4151,7 @@ pm_opt_aref_with_p(const rb_iseq_t *iseq, const pm_call_node_t *node) ((const pm_arguments_node_t *) node->arguments)->arguments.size == 1 && PM_NODE_TYPE_P(((const pm_arguments_node_t *) node->arguments)->arguments.nodes[0], PM_STRING_NODE) && node->block == NULL && - !ISEQ_COMPILE_DATA(iseq)->option->frozen_string_literal && + !PM_NODE_FLAG_P(((const pm_arguments_node_t *) node->arguments)->arguments.nodes[0], PM_STRING_FLAGS_FROZEN) && ISEQ_COMPILE_DATA(iseq)->option->specialized_instruction ); } @@ -3990,7 +4170,7 @@ pm_opt_aset_with_p(const rb_iseq_t *iseq, const pm_call_node_t *node) ((const pm_arguments_node_t *) node->arguments)->arguments.size == 2 && PM_NODE_TYPE_P(((const pm_arguments_node_t *) node->arguments)->arguments.nodes[0], PM_STRING_NODE) && node->block == NULL && - !ISEQ_COMPILE_DATA(iseq)->option->frozen_string_literal && + !PM_NODE_FLAG_P(((const pm_arguments_node_t *) node->arguments)->arguments.nodes[0], PM_STRING_FLAGS_FROZEN) && ISEQ_COMPILE_DATA(iseq)->option->specialized_instruction ); } @@ -4002,18 +4182,17 @@ pm_opt_aset_with_p(const rb_iseq_t *iseq, const pm_call_node_t *node) static void pm_compile_constant_read(rb_iseq_t *iseq, VALUE name, const pm_location_t *name_loc, LINK_ANCHOR *const ret, const pm_scope_node_t *scope_node) { - int lineno = pm_location_line_number(scope_node->parser, name_loc); - NODE dummy_line_node = generate_dummy_line_node(lineno, lineno); + const pm_line_column_t location = PM_LOCATION_LINE_COLUMN(scope_node->parser, name_loc); if (ISEQ_COMPILE_DATA(iseq)->option->inline_const_cache) { ISEQ_BODY(iseq)->ic_size++; VALUE segments = rb_ary_new_from_args(1, name); - ADD_INSN1(ret, &dummy_line_node, opt_getconstant_path, segments); + PUSH_INSN1(ret, location, opt_getconstant_path, segments); } else { - PM_PUTNIL; - ADD_INSN1(ret, &dummy_line_node, putobject, Qtrue); - ADD_INSN1(ret, &dummy_line_node, getconstant, name); + PUSH_INSN(ret, location, putnil); + PUSH_INSN1(ret, location, putobject, Qtrue); + PUSH_INSN1(ret, location, getconstant, name); } } @@ -4062,16 +4241,15 @@ pm_constant_path_parts(const pm_node_t *node, const pm_scope_node_t *scope_node) static void pm_compile_constant_path(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const prefix, LINK_ANCHOR *const body, bool popped, pm_scope_node_t *scope_node) { - int lineno = pm_node_line_number(scope_node->parser, node); - NODE dummy_line_node = generate_dummy_line_node(lineno, lineno); + const pm_line_column_t location = PM_NODE_START_LINE_COLUMN(scope_node->parser, node); switch (PM_NODE_TYPE(node)) { case PM_CONSTANT_READ_NODE: { const pm_constant_read_node_t *cast = (const pm_constant_read_node_t *) node; VALUE name = ID2SYM(pm_constant_id_lookup(scope_node, cast->name)); - ADD_INSN1(body, &dummy_line_node, putobject, Qtrue); - ADD_INSN1(body, &dummy_line_node, getconstant, name); + PUSH_INSN1(body, location, putobject, Qtrue); + PUSH_INSN1(body, location, getconstant, name); break; } case PM_CONSTANT_PATH_NODE: { @@ -4079,15 +4257,15 @@ pm_compile_constant_path(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *co VALUE name = ID2SYM(pm_constant_id_lookup(scope_node, ((const pm_constant_read_node_t *) cast->child)->name)); if (cast->parent == NULL) { - ADD_INSN(body, &dummy_line_node, pop); - ADD_INSN1(body, &dummy_line_node, putobject, rb_cObject); - ADD_INSN1(body, &dummy_line_node, putobject, Qtrue); - ADD_INSN1(body, &dummy_line_node, getconstant, name); + PUSH_INSN(body, location, pop); + PUSH_INSN1(body, location, putobject, rb_cObject); + PUSH_INSN1(body, location, putobject, Qtrue); + PUSH_INSN1(body, location, getconstant, name); } else { - pm_compile_constant_path(iseq, cast->parent, prefix, body, popped, scope_node); - ADD_INSN1(body, &dummy_line_node, putobject, Qfalse); - ADD_INSN1(body, &dummy_line_node, getconstant, name); + pm_compile_constant_path(iseq, cast->parent, prefix, body, false, scope_node); + PUSH_INSN1(body, location, putobject, Qfalse); + PUSH_INSN1(body, location, getconstant, name); } break; } @@ -4114,24 +4292,33 @@ pm_compile_constant_path(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *co * optimization entirely. */ static VALUE -pm_compile_case_node_dispatch(VALUE dispatch, const pm_node_t *node, LABEL *label, const pm_scope_node_t *scope_node) +pm_compile_case_node_dispatch(rb_iseq_t *iseq, VALUE dispatch, const pm_node_t *node, LABEL *label, const pm_scope_node_t *scope_node) { VALUE key = Qundef; switch (PM_NODE_TYPE(node)) { + case PM_FLOAT_NODE: { + key = pm_static_literal_value(iseq, node, scope_node); + double intptr; + + if (modf(RFLOAT_VALUE(key), &intptr) == 0.0) { + key = (FIXABLE(intptr) ? LONG2FIX((long) intptr) : rb_dbl2big(intptr)); + } + + break; + } case PM_FALSE_NODE: - case PM_FLOAT_NODE: case PM_INTEGER_NODE: case PM_NIL_NODE: case PM_SOURCE_FILE_NODE: case PM_SOURCE_LINE_NODE: case PM_SYMBOL_NODE: case PM_TRUE_NODE: - key = pm_static_literal_value(node, scope_node); + key = pm_static_literal_value(iseq, node, scope_node); break; case PM_STRING_NODE: { const pm_string_node_t *cast = (const pm_string_node_t *) node; - key = rb_fstring(parse_string_encoded(scope_node, node, &cast->unescaped)); + key = parse_static_literal_string(iseq, scope_node, node, &cast->unescaped); break; } default: @@ -4159,7 +4346,7 @@ static void pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, bool popped, pm_scope_node_t *scope_node) { const pm_parser_t *parser = scope_node->parser; - const pm_line_column_t location = pm_newline_list_line_column(&parser->newline_list, node->location.start, parser->start_line); + const pm_line_column_t location = PM_NODE_START_LINE_COLUMN(parser, node); int lineno = (int) location.line; if (PM_NODE_FLAG_P(node, PM_NODE_FLAG_NEWLINE) && ISEQ_COMPILE_DATA(iseq)->last_line != lineno) { @@ -4172,8 +4359,6 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, PUSH_TRACE(ret, event); } - NODE dummy_line_node = generate_dummy_line_node(lineno, lineno); - switch (PM_NODE_TYPE(node)) { case PM_ALIAS_GLOBAL_VARIABLE_NODE: { // alias $foo $bar @@ -4233,7 +4418,7 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, if (PM_NODE_TYPE(node) == PM_ARGUMENTS_NODE) { // break foo // ^^^ - const pm_arguments_node_t *cast = (const pm_arguments_node_t *)node; + const pm_arguments_node_t *cast = (const pm_arguments_node_t *) node; elements = &cast->arguments; // If we are only returning a single element through one of the jump @@ -4252,13 +4437,13 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, // If every node in the array is static, then we can compile the entire // array now instead of later. - if (pm_static_literal_p(node)) { + if (PM_NODE_FLAG_P(node, PM_NODE_FLAG_STATIC_LITERAL)) { // We're only going to compile this node if it's not popped. If it // is popped, then we know we don't need to do anything since it's // statically known. if (!popped) { if (elements->size) { - VALUE value = pm_static_literal_value(node, scope_node); + VALUE value = pm_static_literal_value(iseq, node, scope_node); PUSH_INSN1(ret, location, duparray, value); } else { @@ -4307,7 +4492,7 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, } else { pm_local_index_t index = pm_lookup_local_index(iseq, scope_node, PM_CONSTANT_MULT, 0); - ADD_GETLOCAL(ret, &dummy_line_node, index.index, index.level); + PUSH_GETLOCAL(ret, location, index.index, index.level); } if (index > 0) { @@ -4326,7 +4511,7 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, else if (PM_NODE_TYPE_P(element, PM_KEYWORD_HASH_NODE)) { new_array_size++; has_kw_splat = true; - pm_compile_hash_elements(&((const pm_keyword_hash_node_t *) element)->elements, lineno, iseq, ret, scope_node); + pm_compile_hash_elements(iseq, element, &((const pm_keyword_hash_node_t *) element)->elements, ret, scope_node); } else { new_array_size++; @@ -4375,78 +4560,92 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, } else if (!popped) { pm_local_index_t index = pm_lookup_local_index(iseq, scope_node, PM_CONSTANT_POW, 0); - ADD_GETLOCAL(ret, &dummy_line_node, index.index, index.level); + PUSH_GETLOCAL(ret, location, index.index, index.level); } return; } case PM_BACK_REFERENCE_READ_NODE: { + // $+ + // ^^ if (!popped) { // Since a back reference is `$`, ruby represents the ID as the // an rb_intern on the value after the `$`. char *char_ptr = (char *)(node->location.start) + 1; ID backref_val = INT2FIX(rb_intern2(char_ptr, 1)) << 1 | 1; - ADD_INSN2(ret, &dummy_line_node, getspecial, INT2FIX(1), backref_val); + PUSH_INSN2(ret, location, getspecial, INT2FIX(1), backref_val); } return; } case PM_BEGIN_NODE: { - pm_begin_node_t *begin_node = (pm_begin_node_t *) node; + // begin end + // ^^^^^^^^^ + const pm_begin_node_t *cast = (const pm_begin_node_t *) node; - if (begin_node->ensure_clause) { + if (cast->ensure_clause) { // Compiling the ensure clause will compile the rescue clause (if - // there is one), which will compile the begin statements - pm_compile_ensure(iseq, begin_node, ret, lineno, popped, scope_node); + // there is one), which will compile the begin statements. + pm_compile_ensure(iseq, cast, &location, ret, popped, scope_node); } - else if (begin_node->rescue_clause) { - // Compiling rescue will compile begin statements (if applicable) - pm_compile_rescue(iseq, begin_node, ret, lineno, popped, scope_node); + else if (cast->rescue_clause) { + // Compiling rescue will compile begin statements (if applicable). + pm_compile_rescue(iseq, cast, &location, ret, popped, scope_node); } else { - // If there is neither ensure or rescue, the just compile statements - if (begin_node->statements) { - PM_COMPILE((pm_node_t *)begin_node->statements); + // If there is neither ensure or rescue, the just compile the + // statements. + if (cast->statements != NULL) { + PM_COMPILE((const pm_node_t *) cast->statements); } - else { - PM_PUTNIL_UNLESS_POPPED; + else if (!popped) { + PUSH_INSN(ret, location, putnil); } } return; } case PM_BLOCK_ARGUMENT_NODE: { - pm_block_argument_node_t *cast = (pm_block_argument_node_t *) node; + // foo(&bar) + // ^^^^ + const pm_block_argument_node_t *cast = (const pm_block_argument_node_t *) node; - if (cast->expression) { + if (cast->expression != NULL) { PM_COMPILE(cast->expression); } else { // If there's no expression, this must be block forwarding. pm_local_index_t local_index = pm_lookup_local_index(iseq, scope_node, PM_CONSTANT_AND, 0); - ADD_INSN2(ret, &dummy_line_node, getblockparamproxy, INT2FIX(local_index.index + VM_ENV_DATA_SIZE - 1), INT2FIX(local_index.level)); + PUSH_INSN2(ret, location, getblockparamproxy, INT2FIX(local_index.index + VM_ENV_DATA_SIZE - 1), INT2FIX(local_index.level)); } return; } case PM_BREAK_NODE: { - pm_break_node_t *break_node = (pm_break_node_t *) node; + // break + // ^^^^^ + // + // break foo + // ^^^^^^^^^ + const pm_break_node_t *cast = (const pm_break_node_t *) node; unsigned long throw_flag = 0; + if (ISEQ_COMPILE_DATA(iseq)->redo_label != 0 && can_add_ensure_iseq(iseq)) { /* while/until */ LABEL *splabel = NEW_LABEL(0); - ADD_LABEL(ret, splabel); - ADD_ADJUST(ret, &dummy_line_node, ISEQ_COMPILE_DATA(iseq)->redo_label); - if (break_node->arguments) { - PM_COMPILE_NOT_POPPED((pm_node_t *)break_node->arguments); + PUSH_LABEL(ret, splabel); + PUSH_ADJUST(ret, location, ISEQ_COMPILE_DATA(iseq)->redo_label); + + if (cast->arguments != NULL) { + PM_COMPILE_NOT_POPPED((const pm_node_t *) cast->arguments); } else { - PM_PUTNIL; + PUSH_INSN(ret, location, putnil); } pm_add_ensure_iseq(ret, iseq, 0, scope_node); - ADD_INSNL(ret, &dummy_line_node, jump, ISEQ_COMPILE_DATA(iseq)->end_label); - ADD_ADJUST_RESTORE(ret, splabel); - - PM_PUTNIL_UNLESS_POPPED; - } else { + PUSH_INSNL(ret, location, jump, ISEQ_COMPILE_DATA(iseq)->end_label); + PUSH_ADJUST_RESTORE(ret, splabel); + if (!popped) PUSH_INSN(ret, location, putnil); + } + else { const rb_iseq_t *ip = iseq; while (ip) { @@ -4471,111 +4670,128 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, } /* escape from block */ - if (break_node->arguments) { - PM_COMPILE_NOT_POPPED((pm_node_t *)break_node->arguments); + if (cast->arguments != NULL) { + PM_COMPILE_NOT_POPPED((const pm_node_t *) cast->arguments); } else { - PM_PUTNIL; + PUSH_INSN(ret, location, putnil); } - ADD_INSN1(ret, &dummy_line_node, throw, INT2FIX(throw_flag | TAG_BREAK)); - PM_POP_IF_POPPED; + PUSH_INSN1(ret, location, throw, INT2FIX(throw_flag | TAG_BREAK)); + if (popped) PUSH_INSN(ret, location, pop); return; } + COMPILE_ERROR(ERROR_ARGS "Invalid break"); rb_bug("Invalid break"); } return; } case PM_CALL_NODE: { - const pm_call_node_t *call_node = (const pm_call_node_t *) node; - LABEL *start = NEW_LABEL(lineno); + // foo + // ^^^ + // + // foo.bar + // ^^^^^^^ + // + // foo.bar() {} + // ^^^^^^^^^^^^ + const pm_call_node_t *cast = (const pm_call_node_t *) node; + LABEL *start = NEW_LABEL(location.line); - if (call_node->block) { - ADD_LABEL(ret, start); + if (cast->block) { + PUSH_LABEL(ret, start); } - ID method_id = pm_constant_id_lookup(scope_node, call_node->name); + ID method_id = pm_constant_id_lookup(scope_node, cast->name); switch (method_id) { case idUMinus: { - if (pm_opt_str_freeze_p(iseq, call_node)) { - VALUE value = rb_fstring(parse_string_encoded(scope_node, call_node->receiver, &((const pm_string_node_t * )call_node->receiver)->unescaped)); - ADD_INSN2(ret, &dummy_line_node, opt_str_uminus, value, new_callinfo(iseq, idUMinus, 0, 0, NULL, FALSE)); + if (pm_opt_str_freeze_p(iseq, cast)) { + VALUE value = parse_static_literal_string(iseq, scope_node, cast->receiver, &((const pm_string_node_t * ) cast->receiver)->unescaped); + PUSH_INSN2(ret, location, opt_str_uminus, value, new_callinfo(iseq, idUMinus, 0, 0, NULL, FALSE)); return; } break; } case idFreeze: { - if (pm_opt_str_freeze_p(iseq, call_node)) { - VALUE value = rb_fstring(parse_string_encoded(scope_node, call_node->receiver, &((const pm_string_node_t * )call_node->receiver)->unescaped)); - ADD_INSN2(ret, &dummy_line_node, opt_str_freeze, value, new_callinfo(iseq, idFreeze, 0, 0, NULL, FALSE)); + if (pm_opt_str_freeze_p(iseq, cast)) { + VALUE value = parse_static_literal_string(iseq, scope_node, cast->receiver, &((const pm_string_node_t * ) cast->receiver)->unescaped); + PUSH_INSN2(ret, location, opt_str_freeze, value, new_callinfo(iseq, idFreeze, 0, 0, NULL, FALSE)); return; } break; } case idAREF: { - if (pm_opt_aref_with_p(iseq, call_node)) { - const pm_string_node_t *string = (const pm_string_node_t *) ((const pm_arguments_node_t *) call_node->arguments)->arguments.nodes[0]; - VALUE value = rb_fstring(parse_string_encoded(scope_node, (const pm_node_t *) string, &string->unescaped)); + if (pm_opt_aref_with_p(iseq, cast)) { + const pm_string_node_t *string = (const pm_string_node_t *) ((const pm_arguments_node_t *) cast->arguments)->arguments.nodes[0]; + VALUE value = parse_static_literal_string(iseq, scope_node, (const pm_node_t *) string, &string->unescaped); - PM_COMPILE_NOT_POPPED(call_node->receiver); - ADD_INSN2(ret, &dummy_line_node, opt_aref_with, value, new_callinfo(iseq, idAREF, 1, 0, NULL, FALSE)); + PM_COMPILE_NOT_POPPED(cast->receiver); + PUSH_INSN2(ret, location, opt_aref_with, value, new_callinfo(iseq, idAREF, 1, 0, NULL, FALSE)); if (popped) { - ADD_INSN(ret, &dummy_line_node, pop); + PUSH_INSN(ret, location, pop); } + return; } break; } case idASET: { - if (pm_opt_aset_with_p(iseq, call_node)) { - const pm_string_node_t *string = (const pm_string_node_t *) ((const pm_arguments_node_t *) call_node->arguments)->arguments.nodes[0]; - VALUE value = rb_fstring(parse_string_encoded(scope_node, (const pm_node_t *) string, &string->unescaped)); + if (pm_opt_aset_with_p(iseq, cast)) { + const pm_string_node_t *string = (const pm_string_node_t *) ((const pm_arguments_node_t *) cast->arguments)->arguments.nodes[0]; + VALUE value = parse_static_literal_string(iseq, scope_node, (const pm_node_t *) string, &string->unescaped); - PM_COMPILE_NOT_POPPED(call_node->receiver); - PM_COMPILE_NOT_POPPED(((const pm_arguments_node_t *) call_node->arguments)->arguments.nodes[1]); + PM_COMPILE_NOT_POPPED(cast->receiver); + PM_COMPILE_NOT_POPPED(((const pm_arguments_node_t *) cast->arguments)->arguments.nodes[1]); if (!popped) { - ADD_INSN(ret, &dummy_line_node, swap); - ADD_INSN1(ret, &dummy_line_node, topn, INT2FIX(1)); + PUSH_INSN(ret, location, swap); + PUSH_INSN1(ret, location, topn, INT2FIX(1)); } - ADD_INSN2(ret, &dummy_line_node, opt_aset_with, value, new_callinfo(iseq, idASET, 2, 0, NULL, FALSE)); - ADD_INSN(ret, &dummy_line_node, pop); + PUSH_INSN2(ret, location, opt_aset_with, value, new_callinfo(iseq, idASET, 2, 0, NULL, FALSE)); + PUSH_INSN(ret, location, pop); return; } break; } } - if (PM_NODE_FLAG_P(call_node, PM_CALL_NODE_FLAGS_ATTRIBUTE_WRITE) && !popped) { - PM_PUTNIL; + if (PM_NODE_FLAG_P(cast, PM_CALL_NODE_FLAGS_ATTRIBUTE_WRITE) && !popped) { + PUSH_INSN(ret, location, putnil); } - if (call_node->receiver == NULL) { - PM_PUTSELF; + if (cast->receiver == NULL) { + PUSH_INSN(ret, location, putself); } else { - PM_COMPILE_NOT_POPPED(call_node->receiver); + PM_COMPILE_NOT_POPPED(cast->receiver); } - pm_compile_call(iseq, call_node, ret, popped, scope_node, method_id, start); + pm_compile_call(iseq, cast, ret, popped, scope_node, method_id, start); return; } case PM_CALL_AND_WRITE_NODE: { - pm_call_and_write_node_t *cast = (pm_call_and_write_node_t*) node; - pm_compile_call_and_or_write_node(true, cast->receiver, cast->value, cast->write_name, cast->read_name, PM_NODE_FLAG_P(cast, PM_CALL_NODE_FLAGS_SAFE_NAVIGATION), ret, iseq, lineno, popped, scope_node); + // foo.bar &&= baz + // ^^^^^^^^^^^^^^^ + const pm_call_and_write_node_t *cast = (const pm_call_and_write_node_t *) node; + pm_compile_call_and_or_write_node(iseq, true, cast->receiver, cast->value, cast->write_name, cast->read_name, PM_NODE_FLAG_P(cast, PM_CALL_NODE_FLAGS_SAFE_NAVIGATION), &location, ret, popped, scope_node); return; } case PM_CALL_OR_WRITE_NODE: { - pm_call_or_write_node_t *cast = (pm_call_or_write_node_t*) node; - pm_compile_call_and_or_write_node(false, cast->receiver, cast->value, cast->write_name, cast->read_name, PM_NODE_FLAG_P(cast, PM_CALL_NODE_FLAGS_SAFE_NAVIGATION), ret, iseq, lineno, popped, scope_node); + // foo.bar ||= baz + // ^^^^^^^^^^^^^^^ + const pm_call_or_write_node_t *cast = (const pm_call_or_write_node_t *) node; + pm_compile_call_and_or_write_node(iseq, false, cast->receiver, cast->value, cast->write_name, cast->read_name, PM_NODE_FLAG_P(cast, PM_CALL_NODE_FLAGS_SAFE_NAVIGATION), &location, ret, popped, scope_node); return; } case PM_CALL_OPERATOR_WRITE_NODE: { + // foo.bar += baz + // ^^^^^^^^^^^^^^^ + // // Call operator writes occur when you have a call node on the left-hand // side of a write operator that is not `=`. As an example, // `foo.bar *= 1`. This breaks down to caching the receiver on the @@ -4593,31 +4809,31 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, LABEL *safe_label = NULL; if (PM_NODE_FLAG_P(cast, PM_CALL_NODE_FLAGS_SAFE_NAVIGATION)) { - safe_label = NEW_LABEL(lineno); - PM_DUP; - ADD_INSNL(ret, &dummy_line_node, branchnil, safe_label); + safe_label = NEW_LABEL(location.line); + PUSH_INSN(ret, location, dup); + PUSH_INSNL(ret, location, branchnil, safe_label); } - PM_DUP; + PUSH_INSN(ret, location, dup); ID id_read_name = pm_constant_id_lookup(scope_node, cast->read_name); - ADD_SEND_WITH_FLAG(ret, &dummy_line_node, id_read_name, INT2FIX(0), INT2FIX(flag)); + PUSH_SEND_WITH_FLAG(ret, location, id_read_name, INT2FIX(0), INT2FIX(flag)); PM_COMPILE_NOT_POPPED(cast->value); ID id_operator = pm_constant_id_lookup(scope_node, cast->operator); - ADD_SEND(ret, &dummy_line_node, id_operator, INT2FIX(1)); + PUSH_SEND(ret, location, id_operator, INT2FIX(1)); if (!popped) { - PM_SWAP; - ADD_INSN1(ret, &dummy_line_node, topn, INT2FIX(1)); + PUSH_INSN(ret, location, swap); + PUSH_INSN1(ret, location, topn, INT2FIX(1)); } ID id_write_name = pm_constant_id_lookup(scope_node, cast->write_name); - ADD_SEND_WITH_FLAG(ret, &dummy_line_node, id_write_name, INT2FIX(1), INT2FIX(flag)); + PUSH_SEND_WITH_FLAG(ret, location, id_write_name, INT2FIX(1), INT2FIX(flag)); - if (safe_label != NULL && popped) ADD_LABEL(ret, safe_label); - PM_POP; - if (safe_label != NULL && !popped) ADD_LABEL(ret, safe_label); + if (safe_label != NULL && popped) PUSH_LABEL(ret, safe_label); + PUSH_INSN(ret, location, pop); + if (safe_label != NULL && !popped) PUSH_LABEL(ret, safe_label); return; } @@ -4641,7 +4857,7 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, // This is the label where all of the when clauses will jump to if they // have matched and are done executing their bodies. - LABEL *end_label = NEW_LABEL(lineno); + LABEL *end_label = NEW_LABEL(location.line); // If we have a predicate on this case statement, then it's going to // compare all of the various when clauses to the predicate. If we @@ -4657,15 +4873,15 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, int clause_lineno = pm_node_line_number(parser, (const pm_node_t *) clause); LABEL *label = NEW_LABEL(clause_lineno); - ADD_LABEL(body_seq, label); + PUSH_LABEL(body_seq, label); if (clause->statements != NULL) { pm_compile_node(iseq, (const pm_node_t *) clause->statements, body_seq, popped, scope_node); } else if (!popped) { - ADD_INSN(body_seq, &dummy_line_node, putnil); + PUSH_INSN(body_seq, location, putnil); } - ADD_INSNL(body_seq, &dummy_line_node, jump, end_label); + PUSH_INSNL(body_seq, location, jump, end_label); // Compile each of the conditions for the when clause into the // cond_seq. Each one should have a unique condition and should @@ -4674,15 +4890,15 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, const pm_node_t *condition = conditions->nodes[condition_index]; if (PM_NODE_TYPE_P(condition, PM_SPLAT_NODE)) { - ADD_INSN(cond_seq, &dummy_line_node, putnil); + PUSH_INSN(cond_seq, location, putnil); pm_compile_node(iseq, condition, cond_seq, false, scope_node); - ADD_INSN1(cond_seq, &dummy_line_node, checkmatch, INT2FIX(VM_CHECKMATCH_TYPE_WHEN | VM_CHECKMATCH_ARRAY)); - ADD_INSNL(cond_seq, &dummy_line_node, branchif, label); + PUSH_INSN1(cond_seq, location, checkmatch, INT2FIX(VM_CHECKMATCH_TYPE_WHEN | VM_CHECKMATCH_ARRAY)); + PUSH_INSNL(cond_seq, location, branchif, label); } else { LABEL *next_label = NEW_LABEL(pm_node_line_number(parser, condition)); pm_compile_branch_condition(iseq, cond_seq, condition, label, next_label, false, scope_node); - ADD_LABEL(cond_seq, next_label); + PUSH_LABEL(cond_seq, next_label); } } } @@ -4692,18 +4908,18 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, pm_compile_node(iseq, (const pm_node_t *) cast->consequent, cond_seq, popped, scope_node); } else if (!popped) { - ADD_INSN(cond_seq, &dummy_line_node, putnil); + PUSH_INSN(cond_seq, location, putnil); } // Finally, jump to the end label if none of the other conditions // have matched. - ADD_INSNL(cond_seq, &dummy_line_node, jump, end_label); - ADD_SEQ(ret, cond_seq); + PUSH_INSNL(cond_seq, location, jump, end_label); + PUSH_SEQ(ret, cond_seq); } else { // This is the label where everything will fall into if none of the // conditions matched. - LABEL *else_label = NEW_LABEL(lineno); + LABEL *else_label = NEW_LABEL(location.line); // It's possible for us to speed up the case node by using a // dispatch hash. This is a hash that maps the conditions of the @@ -4728,7 +4944,7 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, const pm_when_node_t *clause = (const pm_when_node_t *) conditions->nodes[clause_index]; const pm_node_list_t *conditions = &clause->conditions; - LABEL *label = NEW_LABEL(lineno); + LABEL *label = NEW_LABEL(location.line); // Compile each of the conditions for the when clause into the // cond_seq. Each one should have a unique comparison that then @@ -4740,46 +4956,46 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, // we're going to try to compile the condition into the // dispatch hash. if (dispatch != Qundef) { - dispatch = pm_compile_case_node_dispatch(dispatch, condition, label, scope_node); + dispatch = pm_compile_case_node_dispatch(iseq, dispatch, condition, label, scope_node); } if (PM_NODE_TYPE_P(condition, PM_SPLAT_NODE)) { - ADD_INSN(cond_seq, &dummy_line_node, dup); + PUSH_INSN(cond_seq, location, dup); pm_compile_node(iseq, condition, cond_seq, false, scope_node); - ADD_INSN1(cond_seq, &dummy_line_node, checkmatch, INT2FIX(VM_CHECKMATCH_TYPE_CASE | VM_CHECKMATCH_ARRAY)); + PUSH_INSN1(cond_seq, location, checkmatch, INT2FIX(VM_CHECKMATCH_TYPE_CASE | VM_CHECKMATCH_ARRAY)); } else { if (PM_NODE_TYPE_P(condition, PM_STRING_NODE)) { const pm_string_node_t *string = (const pm_string_node_t *) condition; - VALUE value = rb_fstring(parse_string_encoded(scope_node, (const pm_node_t *) string, &string->unescaped)); - ADD_INSN1(cond_seq, &dummy_line_node, putobject, value); + VALUE value = parse_static_literal_string(iseq, scope_node, condition, &string->unescaped); + PUSH_INSN1(cond_seq, location, putobject, value); } else { pm_compile_node(iseq, condition, cond_seq, false, scope_node); } - ADD_INSN1(cond_seq, &dummy_line_node, topn, INT2FIX(1)); - ADD_SEND_WITH_FLAG(cond_seq, &dummy_line_node, idEqq, INT2NUM(1), INT2FIX(VM_CALL_FCALL | VM_CALL_ARGS_SIMPLE)); + PUSH_INSN1(cond_seq, location, topn, INT2FIX(1)); + PUSH_SEND_WITH_FLAG(cond_seq, location, idEqq, INT2NUM(1), INT2FIX(VM_CALL_FCALL | VM_CALL_ARGS_SIMPLE)); } - ADD_INSNL(cond_seq, &dummy_line_node, branchif, label); + PUSH_INSNL(cond_seq, location, branchif, label); } // Now, add the label to the body and compile the body of the // when clause. This involves popping the predicate, compiling // the statements to be executed, and then compiling a jump to // the end of the case node. - ADD_LABEL(body_seq, label); - ADD_INSN(body_seq, &dummy_line_node, pop); + PUSH_LABEL(body_seq, label); + PUSH_INSN(body_seq, location, pop); if (clause->statements != NULL) { pm_compile_node(iseq, (const pm_node_t *) clause->statements, body_seq, popped, scope_node); } else if (!popped) { - ADD_INSN(body_seq, &dummy_line_node, putnil); + PUSH_INSN(body_seq, location, putnil); } - ADD_INSNL(body_seq, &dummy_line_node, jump, end_label); + PUSH_INSNL(body_seq, location, jump, end_label); } // Now that we have compiled the conditions and the bodies of the @@ -4791,34 +5007,37 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, // If we have a dispatch hash, then we'll use it here to create the // optimization. if (dispatch != Qundef) { - PM_DUP; - ADD_INSN2(ret, &dummy_line_node, opt_case_dispatch, dispatch, else_label); + PUSH_INSN(ret, location, dup); + PUSH_INSN2(ret, location, opt_case_dispatch, dispatch, else_label); LABEL_REF(else_label); } - ADD_SEQ(ret, cond_seq); + PUSH_SEQ(ret, cond_seq); // Compile either the explicit else clause or an implicit else // clause. - ADD_LABEL(ret, else_label); - PM_POP; + PUSH_LABEL(ret, else_label); + PUSH_INSN(ret, location, pop); if (cast->consequent != NULL) { PM_COMPILE((const pm_node_t *) cast->consequent); } else if (!popped) { - PM_PUTNIL; + PUSH_INSN(ret, location, putnil); } - ADD_INSNL(ret, &dummy_line_node, jump, end_label); + PUSH_INSNL(ret, location, jump, end_label); } - ADD_SEQ(ret, body_seq); - ADD_LABEL(ret, end_label); + PUSH_SEQ(ret, body_seq); + PUSH_LABEL(ret, end_label); return; } case PM_CASE_MATCH_NODE: { + // case foo; in bar; end + // ^^^^^^^^^^^^^^^^^^^^^ + // // If you use the `case` keyword to create a case match node, it will // match against all of the `in` clauses until it finds one that // matches. If it doesn't find one, it can optionally fall back to an @@ -4840,12 +5059,12 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, // This label is used to indicate the end of the entire node. It is // jumped to after the entire stack is cleaned up. - LABEL *end_label = NEW_LABEL(lineno); + LABEL *end_label = NEW_LABEL(location.line); // This label is used as the fallback for the case match. If no match is // found, then we jump to this label. This is either an `else` clause or // an error handler. - LABEL *else_label = NEW_LABEL(lineno); + LABEL *else_label = NEW_LABEL(location.line); // We're going to use this to uniquely identify each branch so that we // can track coverage information. @@ -4860,14 +5079,14 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, // First, we're going to push a bunch of stuff onto the stack that is // going to serve as our scratch space. if (in_single_pattern) { - ADD_INSN(ret, &dummy_line_node, putnil); // key error key - ADD_INSN(ret, &dummy_line_node, putnil); // key error matchee - ADD_INSN1(ret, &dummy_line_node, putobject, Qfalse); // key error? - ADD_INSN(ret, &dummy_line_node, putnil); // error string + PUSH_INSN(ret, location, putnil); // key error key + PUSH_INSN(ret, location, putnil); // key error matchee + PUSH_INSN1(ret, location, putobject, Qfalse); // key error? + PUSH_INSN(ret, location, putnil); // error string } // Now we're going to compile the value to match against. - ADD_INSN(ret, &dummy_line_node, putnil); // deconstruct cache + PUSH_INSN(ret, location, putnil); // deconstruct cache PM_COMPILE_NOT_POPPED(cast->predicate); // Next, we'll loop through every in clause and compile its body into @@ -4879,20 +5098,16 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, RUBY_ASSERT(PM_NODE_TYPE_P(condition, PM_IN_NODE)); const pm_in_node_t *in_node = (const pm_in_node_t *) condition; - - pm_line_node_t in_line; - pm_line_node(&in_line, scope_node, (const pm_node_t *) in_node); - - pm_line_node_t pattern_line; - pm_line_node(&pattern_line, scope_node, (const pm_node_t *) in_node->pattern); + const pm_line_column_t in_location = PM_NODE_START_LINE_COLUMN(parser, in_node); + const pm_line_column_t pattern_location = PM_NODE_START_LINE_COLUMN(parser, in_node->pattern); if (branch_id) { - ADD_INSN(body_seq, &in_line.node, putnil); + PUSH_INSN(body_seq, in_location, putnil); } - LABEL *body_label = NEW_LABEL(in_line.lineno); - ADD_LABEL(body_seq, body_label); - ADD_INSN1(body_seq, &in_line.node, adjuststack, INT2FIX(in_single_pattern ? 6 : 2)); + LABEL *body_label = NEW_LABEL(in_location.line); + PUSH_LABEL(body_seq, body_label); + PUSH_INSN1(body_seq, in_location, adjuststack, INT2FIX(in_single_pattern ? 6 : 2)); // TODO: We need to come back to this and enable trace branch // coverage. At the moment we can't call this function because it @@ -4902,16 +5117,17 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, branch_id++; if (in_node->statements != NULL) { PM_COMPILE_INTO_ANCHOR(body_seq, (const pm_node_t *) in_node->statements); - } else if (!popped) { - ADD_INSN(body_seq, &in_line.node, putnil); + } + else if (!popped) { + PUSH_INSN(body_seq, in_location, putnil); } - ADD_INSNL(body_seq, &in_line.node, jump, end_label); - LABEL *next_pattern_label = NEW_LABEL(pattern_line.lineno); + PUSH_INSNL(body_seq, in_location, jump, end_label); + LABEL *next_pattern_label = NEW_LABEL(pattern_location.line); - ADD_INSN(cond_seq, &pattern_line.node, dup); + PUSH_INSN(cond_seq, pattern_location, dup); pm_compile_pattern(iseq, scope_node, in_node->pattern, cond_seq, body_label, next_pattern_label, in_single_pattern, false, true, 2); - ADD_LABEL(cond_seq, next_pattern_label); + PUSH_LABEL(cond_seq, next_pattern_label); LABEL_UNREMOVABLE(next_pattern_label); } @@ -4921,192 +5137,182 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, // error). const pm_else_node_t *else_node = (const pm_else_node_t *) cast->consequent; - ADD_LABEL(cond_seq, else_label); - ADD_INSN(cond_seq, &dummy_line_node, pop); - ADD_INSN(cond_seq, &dummy_line_node, pop); + PUSH_LABEL(cond_seq, else_label); + PUSH_INSN(cond_seq, location, pop); + PUSH_INSN(cond_seq, location, pop); // TODO: trace branch coverage // add_trace_branch_coverage(iseq, cond_seq, cast->consequent, branch_id, "else", branches); if (else_node->statements != NULL) { PM_COMPILE_INTO_ANCHOR(cond_seq, (const pm_node_t *) else_node->statements); - } else if (!popped) { - ADD_INSN(cond_seq, &dummy_line_node, putnil); } - - ADD_INSNL(cond_seq, &dummy_line_node, jump, end_label); - ADD_INSN(cond_seq, &dummy_line_node, putnil); - if (popped) { - ADD_INSN(cond_seq, &dummy_line_node, putnil); + else if (!popped) { + PUSH_INSN(cond_seq, location, putnil); } - } else { + + PUSH_INSNL(cond_seq, location, jump, end_label); + PUSH_INSN(cond_seq, location, putnil); + if (popped) PUSH_INSN(cond_seq, location, putnil); + } + else { // Otherwise, if we do not have an `else` clause, we will compile in // the code to handle raising an appropriate error. - ADD_LABEL(cond_seq, else_label); + PUSH_LABEL(cond_seq, else_label); // TODO: trace branch coverage // add_trace_branch_coverage(iseq, cond_seq, orig_node, branch_id, "else", branches); if (in_single_pattern) { pm_compile_pattern_error_handler(iseq, scope_node, node, cond_seq, end_label, popped); - } else { - ADD_INSN1(cond_seq, &dummy_line_node, putspecialobject, INT2FIX(VM_SPECIAL_OBJECT_VMCORE)); - ADD_INSN1(cond_seq, &dummy_line_node, putobject, rb_eNoMatchingPatternError); - ADD_INSN1(cond_seq, &dummy_line_node, topn, INT2FIX(2)); - ADD_SEND(cond_seq, &dummy_line_node, id_core_raise, INT2FIX(2)); + } + else { + PUSH_INSN1(cond_seq, location, putspecialobject, INT2FIX(VM_SPECIAL_OBJECT_VMCORE)); + PUSH_INSN1(cond_seq, location, putobject, rb_eNoMatchingPatternError); + PUSH_INSN1(cond_seq, location, topn, INT2FIX(2)); + PUSH_SEND(cond_seq, location, id_core_raise, INT2FIX(2)); - ADD_INSN1(cond_seq, &dummy_line_node, adjuststack, INT2FIX(3)); - if (!popped) ADD_INSN(cond_seq, &dummy_line_node, putnil); - ADD_INSNL(cond_seq, &dummy_line_node, jump, end_label); - ADD_INSN1(cond_seq, &dummy_line_node, dupn, INT2FIX(1)); - if (popped) ADD_INSN(cond_seq, &dummy_line_node, putnil); + PUSH_INSN1(cond_seq, location, adjuststack, INT2FIX(3)); + if (!popped) PUSH_INSN(cond_seq, location, putnil); + PUSH_INSNL(cond_seq, location, jump, end_label); + PUSH_INSN1(cond_seq, location, dupn, INT2FIX(1)); + if (popped) PUSH_INSN(cond_seq, location, putnil); } } // At the end of all of this compilation, we will add the code for the // conditions first, then the various bodies, then mark the end of the // entire sequence with the end label. - ADD_SEQ(ret, cond_seq); - ADD_SEQ(ret, body_seq); - ADD_LABEL(ret, end_label); + PUSH_SEQ(ret, cond_seq); + PUSH_SEQ(ret, body_seq); + PUSH_LABEL(ret, end_label); return; } case PM_CLASS_NODE: { - pm_class_node_t *class_node = (pm_class_node_t *)node; - - ID class_id = pm_constant_id_lookup(scope_node, class_node->name); + // class Foo; end + // ^^^^^^^^^^^^^^ + const pm_class_node_t *cast = (const pm_class_node_t *) node; + ID class_id = pm_constant_id_lookup(scope_node, cast->name); VALUE class_name = rb_str_freeze(rb_sprintf("", rb_id2str(class_id))); pm_scope_node_t next_scope_node; - pm_scope_node_init((pm_node_t *)class_node, &next_scope_node, scope_node); - const rb_iseq_t *class_iseq = NEW_CHILD_ISEQ(&next_scope_node, class_name, ISEQ_TYPE_CLASS, lineno); + pm_scope_node_init((const pm_node_t *) cast, &next_scope_node, scope_node); + + const rb_iseq_t *class_iseq = NEW_CHILD_ISEQ(&next_scope_node, class_name, ISEQ_TYPE_CLASS, location.line); pm_scope_node_destroy(&next_scope_node); // TODO: Once we merge constant path nodes correctly, fix this flag const int flags = VM_DEFINECLASS_TYPE_CLASS | - (class_node->superclass ? VM_DEFINECLASS_FLAG_HAS_SUPERCLASS : 0) | - pm_compile_class_path(ret, iseq, class_node->constant_path, &dummy_line_node, false, scope_node); + (cast->superclass ? VM_DEFINECLASS_FLAG_HAS_SUPERCLASS : 0) | + pm_compile_class_path(iseq, cast->constant_path, &location, ret, false, scope_node); - if (class_node->superclass) { - PM_COMPILE_NOT_POPPED(class_node->superclass); + if (cast->superclass) { + PM_COMPILE_NOT_POPPED(cast->superclass); } else { - PM_PUTNIL; + PUSH_INSN(ret, location, putnil); } - ADD_INSN3(ret, &dummy_line_node, defineclass, ID2SYM(class_id), class_iseq, INT2FIX(flags)); + PUSH_INSN3(ret, location, defineclass, ID2SYM(class_id), class_iseq, INT2FIX(flags)); RB_OBJ_WRITTEN(iseq, Qundef, (VALUE)class_iseq); - PM_POP_IF_POPPED; + if (popped) PUSH_INSN(ret, location, pop); return; } case PM_CLASS_VARIABLE_AND_WRITE_NODE: { - pm_class_variable_and_write_node_t *class_variable_and_write_node = (pm_class_variable_and_write_node_t*) node; - - LABEL *end_label = NEW_LABEL(lineno); - - ID class_variable_name_id = pm_constant_id_lookup(scope_node, class_variable_and_write_node->name); - VALUE class_variable_name_val = ID2SYM(class_variable_name_id); - - ADD_INSN2(ret, &dummy_line_node, getclassvariable, - class_variable_name_val, - get_cvar_ic_value(iseq, class_variable_name_id)); - - PM_DUP_UNLESS_POPPED; + // @@foo &&= bar + // ^^^^^^^^^^^^^ + const pm_class_variable_and_write_node_t *cast = (const pm_class_variable_and_write_node_t *) node; + LABEL *end_label = NEW_LABEL(location.line); - ADD_INSNL(ret, &dummy_line_node, branchunless, end_label); + ID name_id = pm_constant_id_lookup(scope_node, cast->name); + VALUE name = ID2SYM(name_id); - PM_POP_UNLESS_POPPED; + PUSH_INSN2(ret, location, getclassvariable, name, get_cvar_ic_value(iseq, name_id)); + if (!popped) PUSH_INSN(ret, location, dup); - PM_COMPILE_NOT_POPPED(class_variable_and_write_node->value); + PUSH_INSNL(ret, location, branchunless, end_label); + if (!popped) PUSH_INSN(ret, location, pop); - PM_DUP_UNLESS_POPPED; + PM_COMPILE_NOT_POPPED(cast->value); + if (!popped) PUSH_INSN(ret, location, dup); - ADD_INSN2(ret, &dummy_line_node, setclassvariable, - class_variable_name_val, - get_cvar_ic_value(iseq, class_variable_name_id)); - ADD_LABEL(ret, end_label); + PUSH_INSN2(ret, location, setclassvariable, name, get_cvar_ic_value(iseq, name_id)); + PUSH_LABEL(ret, end_label); return; } case PM_CLASS_VARIABLE_OPERATOR_WRITE_NODE: { - pm_class_variable_operator_write_node_t *class_variable_operator_write_node = (pm_class_variable_operator_write_node_t*) node; - - ID class_variable_name_id = pm_constant_id_lookup(scope_node, class_variable_operator_write_node->name); - VALUE class_variable_name_val = ID2SYM(class_variable_name_id); + // @@foo += bar + // ^^^^^^^^^^^^ + const pm_class_variable_operator_write_node_t *cast = (const pm_class_variable_operator_write_node_t *) node; - ADD_INSN2(ret, &dummy_line_node, getclassvariable, - class_variable_name_val, - get_cvar_ic_value(iseq, class_variable_name_id)); + ID name_id = pm_constant_id_lookup(scope_node, cast->name); + VALUE name = ID2SYM(name_id); - PM_COMPILE_NOT_POPPED(class_variable_operator_write_node->value); - ID method_id = pm_constant_id_lookup(scope_node, class_variable_operator_write_node->operator); + PUSH_INSN2(ret, location, getclassvariable, name, get_cvar_ic_value(iseq, name_id)); + PM_COMPILE_NOT_POPPED(cast->value); + ID method_id = pm_constant_id_lookup(scope_node, cast->operator); int flags = VM_CALL_ARGS_SIMPLE; - ADD_SEND_WITH_FLAG(ret, &dummy_line_node, method_id, INT2NUM(1), INT2FIX(flags)); - - PM_DUP_UNLESS_POPPED; + PUSH_SEND_WITH_FLAG(ret, location, method_id, INT2NUM(1), INT2FIX(flags)); - ADD_INSN2(ret, &dummy_line_node, setclassvariable, - class_variable_name_val, - get_cvar_ic_value(iseq, class_variable_name_id)); + if (!popped) PUSH_INSN(ret, location, dup); + PUSH_INSN2(ret, location, setclassvariable, name, get_cvar_ic_value(iseq, name_id)); return; } case PM_CLASS_VARIABLE_OR_WRITE_NODE: { - pm_class_variable_or_write_node_t *class_variable_or_write_node = (pm_class_variable_or_write_node_t*) node; - - LABEL *end_label = NEW_LABEL(lineno); - LABEL *start_label = NEW_LABEL(lineno); - - ADD_INSN(ret, &dummy_line_node, putnil); - ADD_INSN3(ret, &dummy_line_node, defined, INT2FIX(DEFINED_CVAR), - ID2SYM(pm_constant_id_lookup(scope_node, class_variable_or_write_node->name)), Qtrue); - - ADD_INSNL(ret, &dummy_line_node, branchunless, start_label); - - ID class_variable_name_id = pm_constant_id_lookup(scope_node, class_variable_or_write_node->name); - VALUE class_variable_name_val = ID2SYM(class_variable_name_id); - - ADD_INSN2(ret, &dummy_line_node, getclassvariable, - class_variable_name_val, - get_cvar_ic_value(iseq, class_variable_name_id)); + // @@foo ||= bar + // ^^^^^^^^^^^^^ + const pm_class_variable_or_write_node_t *cast = (const pm_class_variable_or_write_node_t *) node; + LABEL *end_label = NEW_LABEL(location.line); + LABEL *start_label = NEW_LABEL(location.line); - PM_DUP_UNLESS_POPPED; + ID name_id = pm_constant_id_lookup(scope_node, cast->name); + VALUE name = ID2SYM(name_id); - ADD_INSNL(ret, &dummy_line_node, branchif, end_label); + PUSH_INSN(ret, location, putnil); + PUSH_INSN3(ret, location, defined, INT2FIX(DEFINED_CVAR), name, Qtrue); + PUSH_INSNL(ret, location, branchunless, start_label); - PM_POP_UNLESS_POPPED; - ADD_LABEL(ret, start_label); + PUSH_INSN2(ret, location, getclassvariable, name, get_cvar_ic_value(iseq, name_id)); + if (!popped) PUSH_INSN(ret, location, dup); - PM_COMPILE_NOT_POPPED(class_variable_or_write_node->value); + PUSH_INSNL(ret, location, branchif, end_label); + if (!popped) PUSH_INSN(ret, location, pop); - PM_DUP_UNLESS_POPPED; + PUSH_LABEL(ret, start_label); + PM_COMPILE_NOT_POPPED(cast->value); + if (!popped) PUSH_INSN(ret, location, dup); - ADD_INSN2(ret, &dummy_line_node, setclassvariable, - class_variable_name_val, - get_cvar_ic_value(iseq, class_variable_name_id)); - ADD_LABEL(ret, end_label); + PUSH_INSN2(ret, location, setclassvariable, name, get_cvar_ic_value(iseq, name_id)); + PUSH_LABEL(ret, end_label); return; } case PM_CLASS_VARIABLE_READ_NODE: { + // @@foo + // ^^^^^ if (!popped) { - pm_class_variable_read_node_t *class_variable_read_node = (pm_class_variable_read_node_t *) node; - ID cvar_name = pm_constant_id_lookup(scope_node, class_variable_read_node->name); - ADD_INSN2(ret, &dummy_line_node, getclassvariable, ID2SYM(cvar_name), get_cvar_ic_value(iseq, cvar_name)); + const pm_class_variable_read_node_t *cast = (const pm_class_variable_read_node_t *) node; + ID name = pm_constant_id_lookup(scope_node, cast->name); + PUSH_INSN2(ret, location, getclassvariable, ID2SYM(name), get_cvar_ic_value(iseq, name)); } return; } case PM_CLASS_VARIABLE_WRITE_NODE: { - pm_class_variable_write_node_t *write_node = (pm_class_variable_write_node_t *) node; - PM_COMPILE_NOT_POPPED(write_node->value); - PM_DUP_UNLESS_POPPED; + // @@foo = 1 + // ^^^^^^^^^ + const pm_class_variable_write_node_t *cast = (const pm_class_variable_write_node_t *) node; + PM_COMPILE_NOT_POPPED(cast->value); + if (!popped) PUSH_INSN(ret, location, dup); + + ID name = pm_constant_id_lookup(scope_node, cast->name); + PUSH_INSN2(ret, location, setclassvariable, ID2SYM(name), get_cvar_ic_value(iseq, name)); - ID cvar_name = pm_constant_id_lookup(scope_node, write_node->name); - ADD_INSN2(ret, &dummy_line_node, setclassvariable, ID2SYM(cvar_name), get_cvar_ic_value(iseq, cvar_name)); return; } case PM_CONSTANT_PATH_NODE: { @@ -5116,12 +5322,9 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, if (ISEQ_COMPILE_DATA(iseq)->option->inline_const_cache && ((parts = pm_constant_path_parts(node, scope_node)) != Qnil)) { ISEQ_BODY(iseq)->ic_size++; - ADD_INSN1(ret, &dummy_line_node, opt_getconstant_path, parts); + PUSH_INSN1(ret, location, opt_getconstant_path, parts); } else { - int lineno = pm_node_line_number(parser, node); - NODE dummy_line_node = generate_dummy_line_node(lineno, lineno); - DECL_ANCHOR(prefix); INIT_ANCHOR(prefix); @@ -5130,59 +5333,58 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, pm_compile_constant_path(iseq, node, prefix, body, popped, scope_node); if (LIST_INSN_SIZE_ZERO(prefix)) { - ADD_INSN(ret, &dummy_line_node, putnil); + PUSH_INSN(ret, location, putnil); } else { - ADD_SEQ(ret, prefix); + PUSH_SEQ(ret, prefix); } - ADD_SEQ(ret, body); + PUSH_SEQ(ret, body); } - PM_POP_IF_POPPED; + if (popped) PUSH_INSN(ret, location, pop); return; } case PM_CONSTANT_PATH_AND_WRITE_NODE: { // Foo::Bar &&= baz // ^^^^^^^^^^^^^^^^ - const pm_constant_path_and_write_node_t *cast = (const pm_constant_path_and_write_node_t*) node; + const pm_constant_path_and_write_node_t *cast = (const pm_constant_path_and_write_node_t *) node; const pm_constant_path_node_t *target = cast->target; const pm_constant_read_node_t *child = (const pm_constant_read_node_t *) target->child; VALUE name = ID2SYM(pm_constant_id_lookup(scope_node, child->name)); - - LABEL *lfin = NEW_LABEL(lineno); + LABEL *lfin = NEW_LABEL(location.line); if (target->parent) { PM_COMPILE_NOT_POPPED(target->parent); } else { - ADD_INSN1(ret, &dummy_line_node, putobject, rb_cObject); + PUSH_INSN1(ret, location, putobject, rb_cObject); } - PM_DUP; - ADD_INSN1(ret, &dummy_line_node, putobject, Qtrue); - ADD_INSN1(ret, &dummy_line_node, getconstant, name); + PUSH_INSN(ret, location, dup); + PUSH_INSN1(ret, location, putobject, Qtrue); + PUSH_INSN1(ret, location, getconstant, name); - PM_DUP_UNLESS_POPPED; - ADD_INSNL(ret, &dummy_line_node, branchunless, lfin); + if (!popped) PUSH_INSN(ret, location, dup); + PUSH_INSNL(ret, location, branchunless, lfin); - PM_POP_UNLESS_POPPED; + if (!popped) PUSH_INSN(ret, location, pop); PM_COMPILE_NOT_POPPED(cast->value); if (popped) { - ADD_INSN1(ret, &dummy_line_node, topn, INT2FIX(1)); + PUSH_INSN1(ret, location, topn, INT2FIX(1)); } else { - ADD_INSN1(ret, &dummy_line_node, dupn, INT2FIX(2)); - PM_SWAP; + PUSH_INSN1(ret, location, dupn, INT2FIX(2)); + PUSH_INSN(ret, location, swap); } - ADD_INSN1(ret, &dummy_line_node, setconstant, name); - ADD_LABEL(ret, lfin); + PUSH_INSN1(ret, location, setconstant, name); + PUSH_LABEL(ret, lfin); - PM_SWAP_UNLESS_POPPED; - PM_POP; + if (!popped) PUSH_INSN(ret, location, swap); + PUSH_INSN(ret, location, pop); return; } @@ -5195,44 +5397,44 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, const pm_constant_read_node_t *child = (const pm_constant_read_node_t *) target->child; VALUE name = ID2SYM(pm_constant_id_lookup(scope_node, child->name)); - LABEL *lassign = NEW_LABEL(lineno); - LABEL *lfin = NEW_LABEL(lineno); + LABEL *lassign = NEW_LABEL(location.line); + LABEL *lfin = NEW_LABEL(location.line); if (target->parent) { PM_COMPILE_NOT_POPPED(target->parent); } else { - ADD_INSN1(ret, &dummy_line_node, putobject, rb_cObject); + PUSH_INSN1(ret, location, putobject, rb_cObject); } - PM_DUP; - ADD_INSN3(ret, &dummy_line_node, defined, INT2FIX(DEFINED_CONST_FROM), name, Qtrue); - ADD_INSNL(ret, &dummy_line_node, branchunless, lassign); + PUSH_INSN(ret, location, dup); + PUSH_INSN3(ret, location, defined, INT2FIX(DEFINED_CONST_FROM), name, Qtrue); + PUSH_INSNL(ret, location, branchunless, lassign); - PM_DUP; - ADD_INSN1(ret, &dummy_line_node, putobject, Qtrue); - ADD_INSN1(ret, &dummy_line_node, getconstant, name); + PUSH_INSN(ret, location, dup); + PUSH_INSN1(ret, location, putobject, Qtrue); + PUSH_INSN1(ret, location, getconstant, name); - PM_DUP_UNLESS_POPPED; - ADD_INSNL(ret, &dummy_line_node, branchif, lfin); + if (!popped) PUSH_INSN(ret, location, dup); + PUSH_INSNL(ret, location, branchif, lfin); - PM_POP_UNLESS_POPPED; - ADD_LABEL(ret, lassign); + if (!popped) PUSH_INSN(ret, location, pop); + PUSH_LABEL(ret, lassign); PM_COMPILE_NOT_POPPED(cast->value); if (popped) { - ADD_INSN1(ret, &dummy_line_node, topn, INT2FIX(1)); + PUSH_INSN1(ret, location, topn, INT2FIX(1)); } else { - ADD_INSN1(ret, &dummy_line_node, dupn, INT2FIX(2)); - PM_SWAP; + PUSH_INSN1(ret, location, dupn, INT2FIX(2)); + PUSH_INSN(ret, location, swap); } - ADD_INSN1(ret, &dummy_line_node, setconstant, name); - ADD_LABEL(ret, lfin); + PUSH_INSN1(ret, location, setconstant, name); + PUSH_LABEL(ret, lfin); - PM_SWAP_UNLESS_POPPED; - PM_POP; + if (!popped) PUSH_INSN(ret, location, swap); + PUSH_INSN(ret, location, pop); return; } @@ -5250,23 +5452,23 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, PM_COMPILE_NOT_POPPED(target->parent); } else { - ADD_INSN1(ret, &dummy_line_node, putobject, rb_cObject); + PUSH_INSN1(ret, location, putobject, rb_cObject); } - PM_DUP; - ADD_INSN1(ret, &dummy_line_node, putobject, Qtrue); - ADD_INSN1(ret, &dummy_line_node, getconstant, name); + PUSH_INSN(ret, location, dup); + PUSH_INSN1(ret, location, putobject, Qtrue); + PUSH_INSN1(ret, location, getconstant, name); PM_COMPILE_NOT_POPPED(cast->value); - ADD_CALL(ret, &dummy_line_node, method_id, INT2FIX(1)); - PM_SWAP; + PUSH_CALL(ret, location, method_id, INT2FIX(1)); + PUSH_INSN(ret, location, swap); if (!popped) { - ADD_INSN1(ret, &dummy_line_node, topn, INT2FIX(1)); - PM_SWAP; + PUSH_INSN1(ret, location, topn, INT2FIX(1)); + PUSH_INSN(ret, location, swap); } - ADD_INSN1(ret, &dummy_line_node, setconstant, name); + PUSH_INSN1(ret, location, setconstant, name); return; } case PM_CONSTANT_PATH_WRITE_NODE: { @@ -5279,21 +5481,22 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, VALUE name = ID2SYM(pm_constant_id_lookup(scope_node, child->name)); if (target->parent) { - PM_COMPILE_NOT_POPPED((pm_node_t *) target->parent); + PM_COMPILE_NOT_POPPED((const pm_node_t *) target->parent); } else { - ADD_INSN1(ret, &dummy_line_node, putobject, rb_cObject); + PUSH_INSN1(ret, location, putobject, rb_cObject); } PM_COMPILE_NOT_POPPED(cast->value); if (!popped) { - PM_SWAP; - ADD_INSN1(ret, &dummy_line_node, topn, INT2FIX(1)); + PUSH_INSN(ret, location, swap); + PUSH_INSN1(ret, location, topn, INT2FIX(1)); } - PM_SWAP; - ADD_INSN1(ret, &dummy_line_node, setconstant, name); + PUSH_INSN(ret, location, swap); + PUSH_INSN1(ret, location, setconstant, name); + return; } case PM_CONSTANT_READ_NODE: { @@ -5303,8 +5506,8 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, VALUE name = ID2SYM(pm_constant_id_lookup(scope_node, cast->name)); pm_compile_constant_read(iseq, name, &cast->base.location, ret, scope_node); + if (popped) PUSH_INSN(ret, location, pop); - PM_POP_IF_POPPED; return; } case PM_CONSTANT_AND_WRITE_NODE: { @@ -5312,20 +5515,20 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, // ^^^^^^^^^^^ const pm_constant_and_write_node_t *cast = (const pm_constant_and_write_node_t *) node; VALUE name = ID2SYM(pm_constant_id_lookup(scope_node, cast->name)); - LABEL *end_label = NEW_LABEL(lineno); + LABEL *end_label = NEW_LABEL(location.line); pm_compile_constant_read(iseq, name, &cast->name_loc, ret, scope_node); + if (!popped) PUSH_INSN(ret, location, dup); - PM_DUP_UNLESS_POPPED; - ADD_INSNL(ret, &dummy_line_node, branchunless, end_label); - PM_POP_UNLESS_POPPED; + PUSH_INSNL(ret, location, branchunless, end_label); + if (!popped) PUSH_INSN(ret, location, pop); PM_COMPILE_NOT_POPPED(cast->value); - PM_DUP_UNLESS_POPPED; + if (!popped) PUSH_INSN(ret, location, dup); - ADD_INSN1(ret, &dummy_line_node, putspecialobject, INT2FIX(VM_SPECIAL_OBJECT_CONST_BASE)); - ADD_INSN1(ret, &dummy_line_node, setconstant, name); - ADD_LABEL(ret, end_label); + PUSH_INSN1(ret, location, putspecialobject, INT2FIX(VM_SPECIAL_OBJECT_CONST_BASE)); + PUSH_INSN1(ret, location, setconstant, name); + PUSH_LABEL(ret, end_label); return; } @@ -5334,26 +5537,26 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, // ^^^^^^^^^^^ const pm_constant_or_write_node_t *cast = (const pm_constant_or_write_node_t *) node; VALUE name = ID2SYM(pm_constant_id_lookup(scope_node, cast->name)); - LABEL *set_label = NEW_LABEL(lineno); - LABEL *end_label = NEW_LABEL(lineno); + LABEL *set_label = NEW_LABEL(location.line); + LABEL *end_label = NEW_LABEL(location.line); - PM_PUTNIL; - ADD_INSN3(ret, &dummy_line_node, defined, INT2FIX(DEFINED_CONST), name, Qtrue); - ADD_INSNL(ret, &dummy_line_node, branchunless, set_label); + PUSH_INSN(ret, location, putnil); + PUSH_INSN3(ret, location, defined, INT2FIX(DEFINED_CONST), name, Qtrue); + PUSH_INSNL(ret, location, branchunless, set_label); pm_compile_constant_read(iseq, name, &cast->name_loc, ret, scope_node); + if (!popped) PUSH_INSN(ret, location, dup); - PM_DUP_UNLESS_POPPED; - ADD_INSNL(ret, &dummy_line_node, branchif, end_label); - PM_POP_UNLESS_POPPED; + PUSH_INSNL(ret, location, branchif, end_label); + if (!popped) PUSH_INSN(ret, location, pop); - ADD_LABEL(ret, set_label); + PUSH_LABEL(ret, set_label); PM_COMPILE_NOT_POPPED(cast->value); + if (!popped) PUSH_INSN(ret, location, dup); - PM_DUP_UNLESS_POPPED; - ADD_INSN1(ret, &dummy_line_node, putspecialobject, INT2FIX(VM_SPECIAL_OBJECT_CONST_BASE)); - ADD_INSN1(ret, &dummy_line_node, setconstant, name); - ADD_LABEL(ret, end_label); + PUSH_INSN1(ret, location, putspecialobject, INT2FIX(VM_SPECIAL_OBJECT_CONST_BASE)); + PUSH_INSN1(ret, location, setconstant, name); + PUSH_LABEL(ret, end_label); return; } @@ -5365,13 +5568,13 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, ID method_id = pm_constant_id_lookup(scope_node, cast->operator); pm_compile_constant_read(iseq, name, &cast->name_loc, ret, scope_node); - PM_COMPILE_NOT_POPPED(cast->value); - ADD_SEND_WITH_FLAG(ret, &dummy_line_node, method_id, INT2NUM(1), INT2FIX(VM_CALL_ARGS_SIMPLE)); - PM_DUP_UNLESS_POPPED; - ADD_INSN1(ret, &dummy_line_node, putspecialobject, INT2FIX(VM_SPECIAL_OBJECT_CONST_BASE)); - ADD_INSN1(ret, &dummy_line_node, setconstant, name); + PUSH_SEND_WITH_FLAG(ret, location, method_id, INT2NUM(1), INT2FIX(VM_CALL_ARGS_SIMPLE)); + if (!popped) PUSH_INSN(ret, location, dup); + + PUSH_INSN1(ret, location, putspecialobject, INT2FIX(VM_SPECIAL_OBJECT_CONST_BASE)); + PUSH_INSN1(ret, location, setconstant, name); return; } @@ -5382,129 +5585,158 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, VALUE name = ID2SYM(pm_constant_id_lookup(scope_node, cast->name)); PM_COMPILE_NOT_POPPED(cast->value); - PM_DUP_UNLESS_POPPED; + if (!popped) PUSH_INSN(ret, location, dup); - ADD_INSN1(ret, &dummy_line_node, putspecialobject, INT2FIX(VM_SPECIAL_OBJECT_CONST_BASE)); - ADD_INSN1(ret, &dummy_line_node, setconstant, name); + PUSH_INSN1(ret, location, putspecialobject, INT2FIX(VM_SPECIAL_OBJECT_CONST_BASE)); + PUSH_INSN1(ret, location, setconstant, name); return; } case PM_DEF_NODE: { - pm_def_node_t *def_node = (pm_def_node_t *) node; - ID method_name = pm_constant_id_lookup(scope_node, def_node->name); + // def foo; end + // ^^^^^^^^^^^^ + // + // def self.foo; end + // ^^^^^^^^^^^^^^^^^ + const pm_def_node_t *cast = (const pm_def_node_t *) node; + ID method_name = pm_constant_id_lookup(scope_node, cast->name); pm_scope_node_t next_scope_node; - pm_scope_node_init((pm_node_t *)def_node, &next_scope_node, scope_node); - rb_iseq_t *method_iseq = NEW_ISEQ(&next_scope_node, rb_id2str(method_name), ISEQ_TYPE_METHOD, lineno); + pm_scope_node_init((const pm_node_t *) cast, &next_scope_node, scope_node); + + rb_iseq_t *method_iseq = NEW_ISEQ(&next_scope_node, rb_id2str(method_name), ISEQ_TYPE_METHOD, location.line); pm_scope_node_destroy(&next_scope_node); - if (def_node->receiver) { - PM_COMPILE_NOT_POPPED(def_node->receiver); - ADD_INSN2(ret, &dummy_line_node, definesmethod, ID2SYM(method_name), method_iseq); + if (cast->receiver) { + PM_COMPILE_NOT_POPPED(cast->receiver); + PUSH_INSN2(ret, location, definesmethod, ID2SYM(method_name), method_iseq); } else { - ADD_INSN2(ret, &dummy_line_node, definemethod, ID2SYM(method_name), method_iseq); + PUSH_INSN2(ret, location, definemethod, ID2SYM(method_name), method_iseq); } - RB_OBJ_WRITTEN(iseq, Qundef, (VALUE)method_iseq); + RB_OBJ_WRITTEN(iseq, Qundef, (VALUE) method_iseq); if (!popped) { - ADD_INSN1(ret, &dummy_line_node, putobject, ID2SYM(method_name)); + PUSH_INSN1(ret, location, putobject, ID2SYM(method_name)); } + return; } case PM_DEFINED_NODE: { - pm_defined_node_t *defined_node = (pm_defined_node_t *)node; - pm_compile_defined_expr(iseq, defined_node->value, ret, popped, scope_node, dummy_line_node, lineno, false); + // defined?(a) + // ^^^^^^^^^^^ + const pm_defined_node_t *cast = (const pm_defined_node_t *) node; + pm_compile_defined_expr(iseq, cast->value, &location, ret, popped, scope_node, false); return; } case PM_EMBEDDED_STATEMENTS_NODE: { - pm_embedded_statements_node_t *embedded_statements_node = (pm_embedded_statements_node_t *)node; + // "foo #{bar}" + // ^^^^^^ + const pm_embedded_statements_node_t *cast = (const pm_embedded_statements_node_t *) node; - if (embedded_statements_node->statements) { - PM_COMPILE((pm_node_t *) (embedded_statements_node->statements)); + if (cast->statements != NULL) { + PM_COMPILE((const pm_node_t *) (cast->statements)); } else { - PM_PUTNIL; + PUSH_INSN(ret, location, putnil); } - PM_POP_IF_POPPED; - // TODO: Concatenate the strings that exist here + if (popped) PUSH_INSN(ret, location, pop); return; } case PM_EMBEDDED_VARIABLE_NODE: { - pm_embedded_variable_node_t *embedded_node = (pm_embedded_variable_node_t *)node; - PM_COMPILE(embedded_node->variable); + // "foo #@bar" + // ^^^^^ + const pm_embedded_variable_node_t *cast = (const pm_embedded_variable_node_t *) node; + PM_COMPILE(cast->variable); return; } - case PM_FALSE_NODE: + case PM_FALSE_NODE: { + // false + // ^^^^^ if (!popped) { - ADD_INSN1(ret, &dummy_line_node, putobject, Qfalse); + PUSH_INSN1(ret, location, putobject, Qfalse); } return; + } case PM_ENSURE_NODE: { - pm_ensure_node_t *ensure_node = (pm_ensure_node_t *)node; + const pm_ensure_node_t *cast = (const pm_ensure_node_t *) node; + + if (cast->statements != NULL) { + LABEL *start = NEW_LABEL(location.line); + LABEL *end = NEW_LABEL(location.line); + PUSH_LABEL(ret, start); - LABEL *start = NEW_LABEL(lineno); - LABEL *end = NEW_LABEL(lineno); - ADD_LABEL(ret, start); - if (ensure_node->statements) { LABEL *prev_end_label = ISEQ_COMPILE_DATA(iseq)->end_label; ISEQ_COMPILE_DATA(iseq)->end_label = end; - PM_COMPILE((pm_node_t *)ensure_node->statements); + + PM_COMPILE((const pm_node_t *) cast->statements); ISEQ_COMPILE_DATA(iseq)->end_label = prev_end_label; + PUSH_LABEL(ret, end); } - ADD_LABEL(ret, end); + return; } case PM_ELSE_NODE: { - pm_else_node_t *cast = (pm_else_node_t *)node; - if (cast->statements) { - PM_COMPILE((pm_node_t *)cast->statements); - } - else { - PM_PUTNIL_UNLESS_POPPED; - } - return; + // if foo then bar else baz end + // ^^^^^^^^^^^^ + const pm_else_node_t *cast = (const pm_else_node_t *) node; + + if (cast->statements != NULL) { + PM_COMPILE((const pm_node_t *) cast->statements); + } + else if (!popped) { + PUSH_INSN(ret, location, putnil); + } + + return; } case PM_FLIP_FLOP_NODE: { - pm_flip_flop_node_t *flip_flop_node = (pm_flip_flop_node_t *)node; + // if foo .. bar; end + // ^^^^^^^^^^ + const pm_flip_flop_node_t *cast = (const pm_flip_flop_node_t *) node; - LABEL *final_label = NEW_LABEL(lineno); - LABEL *then_label = NEW_LABEL(lineno); - LABEL *else_label = NEW_LABEL(lineno); + LABEL *final_label = NEW_LABEL(location.line); + LABEL *then_label = NEW_LABEL(location.line); + LABEL *else_label = NEW_LABEL(location.line); - pm_compile_flip_flop(flip_flop_node, else_label, then_label, iseq, lineno, ret, popped, scope_node); + pm_compile_flip_flop(cast, else_label, then_label, iseq, location.line, ret, popped, scope_node); + + PUSH_LABEL(ret, then_label); + PUSH_INSN1(ret, location, putobject, Qtrue); + PUSH_INSNL(ret, location, jump, final_label); + PUSH_LABEL(ret, else_label); + PUSH_INSN1(ret, location, putobject, Qfalse); + PUSH_LABEL(ret, final_label); - ADD_LABEL(ret, then_label); - ADD_INSN1(ret, &dummy_line_node, putobject, Qtrue); - ADD_INSNL(ret, &dummy_line_node, jump, final_label); - ADD_LABEL(ret, else_label); - ADD_INSN1(ret, &dummy_line_node, putobject, Qfalse); - ADD_LABEL(ret, final_label); return; } case PM_FLOAT_NODE: { + // 1.0 + // ^^^ if (!popped) { - ADD_INSN1(ret, &dummy_line_node, putobject, parse_float((const pm_float_node_t *) node)); + PUSH_INSN1(ret, location, putobject, parse_float((const pm_float_node_t *) node)); } return; } case PM_FOR_NODE: { - pm_for_node_t *cast = (pm_for_node_t *) node; + // for foo in bar do end + // ^^^^^^^^^^^^^^^^^^^^^ + const pm_for_node_t *cast = (const pm_for_node_t *) node; - LABEL *retry_label = NEW_LABEL(lineno); - LABEL *retry_end_l = NEW_LABEL(lineno); + LABEL *retry_label = NEW_LABEL(location.line); + LABEL *retry_end_l = NEW_LABEL(location.line); // First, compile the collection that we're going to be iterating over. - ADD_LABEL(ret, retry_label); + PUSH_LABEL(ret, retry_label); PM_COMPILE_NOT_POPPED(cast->collection); // Next, create the new scope that is going to contain the block that // will be passed to the each method. pm_scope_node_t next_scope_node; - pm_scope_node_init((pm_node_t *) cast, &next_scope_node, scope_node); + pm_scope_node_init((const pm_node_t *) cast, &next_scope_node, scope_node); - const rb_iseq_t *child_iseq = NEW_CHILD_ISEQ(&next_scope_node, make_name_for_block(iseq), ISEQ_TYPE_BLOCK, lineno); + const rb_iseq_t *child_iseq = NEW_CHILD_ISEQ(&next_scope_node, make_name_for_block(iseq), ISEQ_TYPE_BLOCK, location.line); pm_scope_node_destroy(&next_scope_node); const rb_iseq_t *prev_block = ISEQ_COMPILE_DATA(iseq)->current_block; @@ -5512,7 +5744,7 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, // Now, create the method call to each that will be used to iterate over // the collection, and pass the newly created iseq as the block. - ADD_SEND_WITH_BLOCK(ret, &dummy_line_node, idEach, INT2FIX(0), child_iseq); + PUSH_SEND_WITH_BLOCK(ret, location, idEach, INT2FIX(0), child_iseq); // We need to put the label "retry_end_l" immediately after the last // "send" instruction. This because vm_throw checks if the break cont is @@ -5541,9 +5773,9 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, } } - PM_POP_IF_POPPED; + if (popped) PUSH_INSN(ret, location, pop); ISEQ_COMPILE_DATA(iseq)->current_block = prev_block; - ADD_CATCH_ENTRY(CATCH_TYPE_BREAK, retry_label, retry_end_l, child_iseq, retry_end_l); + PUSH_CATCH_ENTRY(CATCH_TYPE_BREAK, retry_label, retry_end_l, child_iseq, retry_end_l); return; } case PM_FORWARDING_ARGUMENTS_NODE: { @@ -5551,25 +5783,44 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, return; } case PM_FORWARDING_SUPER_NODE: { - pm_forwarding_super_node_t *forwarding_super_node = (pm_forwarding_super_node_t *) node; + // super + // ^^^^^ + // + // super {} + // ^^^^^^^^ + const pm_forwarding_super_node_t *cast = (const pm_forwarding_super_node_t *) node; const rb_iseq_t *block = NULL; - PM_PUTSELF; + + const rb_iseq_t *previous_block = NULL; + LABEL *retry_label = NULL; + LABEL *retry_end_l = NULL; + + if (cast->block != NULL) { + previous_block = ISEQ_COMPILE_DATA(iseq)->current_block; + ISEQ_COMPILE_DATA(iseq)->current_block = NULL; + + retry_label = NEW_LABEL(location.line); + retry_end_l = NEW_LABEL(location.line); + + PUSH_LABEL(ret, retry_label); + } + + PUSH_INSN(ret, location, putself); int flag = VM_CALL_ZSUPER | VM_CALL_SUPER | VM_CALL_FCALL; - if (forwarding_super_node->block) { + if (cast->block != NULL) { pm_scope_node_t next_scope_node; - pm_scope_node_init((pm_node_t *)forwarding_super_node->block, &next_scope_node, scope_node); - block = NEW_CHILD_ISEQ(&next_scope_node, make_name_for_block(iseq), ISEQ_TYPE_BLOCK, lineno); - pm_scope_node_destroy(&next_scope_node); + pm_scope_node_init((const pm_node_t *) cast->block, &next_scope_node, scope_node); - RB_OBJ_WRITTEN(iseq, Qundef, (VALUE)block); + ISEQ_COMPILE_DATA(iseq)->current_block = block = NEW_CHILD_ISEQ(&next_scope_node, make_name_for_block(iseq), ISEQ_TYPE_BLOCK, location.line); + pm_scope_node_destroy(&next_scope_node); + RB_OBJ_WRITTEN(iseq, Qundef, (VALUE) block); } DECL_ANCHOR(args); INIT_ANCHOR(args); struct rb_iseq_constant_body *const body = ISEQ_BODY(iseq); - const rb_iseq_t *local_iseq = body->local_iseq; const struct rb_iseq_constant_body *const local_body = ISEQ_BODY(local_iseq); @@ -5580,7 +5831,7 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, /* required arguments */ for (int i = 0; i < local_body->param.lead_num; i++) { int idx = local_body->local_table_size - i; - ADD_GETLOCAL(args, &dummy_line_node, idx, depth); + PUSH_GETLOCAL(args, location, idx, depth); } argc += local_body->param.lead_num; } @@ -5589,7 +5840,7 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, /* optional arguments */ for (int j = 0; j < local_body->param.opt_num; j++) { int idx = local_body->local_table_size - (argc + j); - ADD_GETLOCAL(args, &dummy_line_node, idx, depth); + PUSH_GETLOCAL(args, location, idx, depth); } argc += local_body->param.opt_num; } @@ -5597,8 +5848,8 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, if (local_body->param.flags.has_rest) { /* rest argument */ int idx = local_body->local_table_size - local_body->param.rest_start; - ADD_GETLOCAL(args, &dummy_line_node, idx, depth); - ADD_INSN1(args, &dummy_line_node, splatarray, Qfalse); + PUSH_GETLOCAL(args, location, idx, depth); + PUSH_INSN1(args, location, splatarray, Qfalse); argc = local_body->param.rest_start + 1; flag |= VM_CALL_ARGS_SPLAT; @@ -5612,13 +5863,13 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, int j = 0; for (; j < post_len; j++) { int idx = local_body->local_table_size - (post_start + j); - ADD_GETLOCAL(args, &dummy_line_node, idx, depth); + PUSH_GETLOCAL(args, location, idx, depth); } if (local_body->param.flags.has_rest) { // argc remains unchanged from rest branch - ADD_INSN1(args, &dummy_line_node, newarray, INT2FIX(j)); - ADD_INSN (args, &dummy_line_node, concatarray); + PUSH_INSN1(args, location, newarray, INT2FIX(j)); + PUSH_INSN(args, location, concatarray); } else { argc = post_len + post_start; @@ -5630,140 +5881,154 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, int local_size = local_body->local_table_size; argc++; - ADD_INSN1(args, &dummy_line_node, putspecialobject, INT2FIX(VM_SPECIAL_OBJECT_VMCORE)); + PUSH_INSN1(args, location, putspecialobject, INT2FIX(VM_SPECIAL_OBJECT_VMCORE)); if (local_body->param.flags.has_kwrest) { int idx = local_body->local_table_size - local_keyword->rest_start; - ADD_GETLOCAL(args, &dummy_line_node, idx, depth); + PUSH_GETLOCAL(args, location, idx, depth); RUBY_ASSERT(local_keyword->num > 0); - ADD_SEND(args, &dummy_line_node, rb_intern("dup"), INT2FIX(0)); + PUSH_SEND(args, location, rb_intern("dup"), INT2FIX(0)); } else { - ADD_INSN1(args, &dummy_line_node, newhash, INT2FIX(0)); + PUSH_INSN1(args, location, newhash, INT2FIX(0)); } int i = 0; for (; i < local_keyword->num; ++i) { ID id = local_keyword->table[i]; int idx = local_size - get_local_var_idx(local_iseq, id); - ADD_INSN1(args, &dummy_line_node, putobject, ID2SYM(id)); - ADD_GETLOCAL(args, &dummy_line_node, idx, depth); + PUSH_INSN1(args, location, putobject, ID2SYM(id)); + PUSH_GETLOCAL(args, location, idx, depth); } - ADD_SEND(args, &dummy_line_node, id_core_hash_merge_ptr, INT2FIX(i * 2 + 1)); + + PUSH_SEND(args, location, id_core_hash_merge_ptr, INT2FIX(i * 2 + 1)); flag |= VM_CALL_KW_SPLAT| VM_CALL_KW_SPLAT_MUT; } else if (local_body->param.flags.has_kwrest) { int idx = local_body->local_table_size - local_keyword->rest_start; - ADD_GETLOCAL(args, &dummy_line_node, idx, depth); + PUSH_GETLOCAL(args, location, idx, depth); argc++; flag |= VM_CALL_KW_SPLAT; } - ADD_SEQ(ret, args); - ADD_INSN2(ret, &dummy_line_node, invokesuper, new_callinfo(iseq, 0, argc, flag, NULL, block != NULL), block); - PM_POP_IF_POPPED; + PUSH_SEQ(ret, args); + PUSH_INSN2(ret, location, invokesuper, new_callinfo(iseq, 0, argc, flag, NULL, block != NULL), block); + + if (cast->block != NULL) { + PUSH_LABEL(ret, retry_end_l); + PUSH_CATCH_ENTRY(CATCH_TYPE_BREAK, retry_label, retry_end_l, block, retry_end_l); + ISEQ_COMPILE_DATA(iseq)->current_block = previous_block; + } + + if (popped) PUSH_INSN(ret, location, pop); return; } case PM_GLOBAL_VARIABLE_AND_WRITE_NODE: { - pm_global_variable_and_write_node_t *global_variable_and_write_node = (pm_global_variable_and_write_node_t*) node; - - LABEL *end_label = NEW_LABEL(lineno); - - VALUE global_variable_name = ID2SYM(pm_constant_id_lookup(scope_node, global_variable_and_write_node->name)); - - ADD_INSN1(ret, &dummy_line_node, getglobal, global_variable_name); - - PM_DUP_UNLESS_POPPED; - - ADD_INSNL(ret, &dummy_line_node, branchunless, end_label); + // $foo &&= bar + // ^^^^^^^^^^^^ + const pm_global_variable_and_write_node_t *cast = (const pm_global_variable_and_write_node_t *) node; + LABEL *end_label = NEW_LABEL(location.line); - PM_POP_UNLESS_POPPED; + VALUE name = ID2SYM(pm_constant_id_lookup(scope_node, cast->name)); + PUSH_INSN1(ret, location, getglobal, name); + if (!popped) PUSH_INSN(ret, location, dup); - PM_COMPILE_NOT_POPPED(global_variable_and_write_node->value); + PUSH_INSNL(ret, location, branchunless, end_label); + if (!popped) PUSH_INSN(ret, location, pop); - PM_DUP_UNLESS_POPPED; + PM_COMPILE_NOT_POPPED(cast->value); + if (!popped) PUSH_INSN(ret, location, dup); - ADD_INSN1(ret, &dummy_line_node, setglobal, global_variable_name); - ADD_LABEL(ret, end_label); + PUSH_INSN1(ret, location, setglobal, name); + PUSH_LABEL(ret, end_label); return; } case PM_GLOBAL_VARIABLE_OPERATOR_WRITE_NODE: { - pm_global_variable_operator_write_node_t *global_variable_operator_write_node = (pm_global_variable_operator_write_node_t*) node; - - VALUE global_variable_name = ID2SYM(pm_constant_id_lookup(scope_node, global_variable_operator_write_node->name)); - ADD_INSN1(ret, &dummy_line_node, getglobal, global_variable_name); + // $foo += bar + // ^^^^^^^^^^^ + const pm_global_variable_operator_write_node_t *cast = (const pm_global_variable_operator_write_node_t *) node; - PM_COMPILE_NOT_POPPED(global_variable_operator_write_node->value); - ID method_id = pm_constant_id_lookup(scope_node, global_variable_operator_write_node->operator); + VALUE name = ID2SYM(pm_constant_id_lookup(scope_node, cast->name)); + PUSH_INSN1(ret, location, getglobal, name); + PM_COMPILE_NOT_POPPED(cast->value); + ID method_id = pm_constant_id_lookup(scope_node, cast->operator); int flags = VM_CALL_ARGS_SIMPLE; - ADD_SEND_WITH_FLAG(ret, &dummy_line_node, method_id, INT2NUM(1), INT2FIX(flags)); + PUSH_SEND_WITH_FLAG(ret, location, method_id, INT2NUM(1), INT2FIX(flags)); - PM_DUP_UNLESS_POPPED; - - ADD_INSN1(ret, &dummy_line_node, setglobal, global_variable_name); + if (!popped) PUSH_INSN(ret, location, dup); + PUSH_INSN1(ret, location, setglobal, name); return; } case PM_GLOBAL_VARIABLE_OR_WRITE_NODE: { - pm_global_variable_or_write_node_t *global_variable_or_write_node = (pm_global_variable_or_write_node_t*) node; - - LABEL *set_label= NEW_LABEL(lineno); - LABEL *end_label = NEW_LABEL(lineno); - - PM_PUTNIL; - VALUE global_variable_name = ID2SYM(pm_constant_id_lookup(scope_node, global_variable_or_write_node->name)); - - ADD_INSN3(ret, &dummy_line_node, defined, INT2FIX(DEFINED_GVAR), global_variable_name, Qtrue); - - ADD_INSNL(ret, &dummy_line_node, branchunless, set_label); - - ADD_INSN1(ret, &dummy_line_node, getglobal, global_variable_name); + // $foo ||= bar + // ^^^^^^^^^^^^ + const pm_global_variable_or_write_node_t *cast = (const pm_global_variable_or_write_node_t *) node; + LABEL *set_label = NEW_LABEL(location.line); + LABEL *end_label = NEW_LABEL(location.line); - PM_DUP_UNLESS_POPPED; + PUSH_INSN(ret, location, putnil); + VALUE name = ID2SYM(pm_constant_id_lookup(scope_node, cast->name)); - ADD_INSNL(ret, &dummy_line_node, branchif, end_label); + PUSH_INSN3(ret, location, defined, INT2FIX(DEFINED_GVAR), name, Qtrue); + PUSH_INSNL(ret, location, branchunless, set_label); - PM_POP_UNLESS_POPPED; + PUSH_INSN1(ret, location, getglobal, name); + if (!popped) PUSH_INSN(ret, location, dup); - ADD_LABEL(ret, set_label); - PM_COMPILE_NOT_POPPED(global_variable_or_write_node->value); + PUSH_INSNL(ret, location, branchif, end_label); + if (!popped) PUSH_INSN(ret, location, pop); - PM_DUP_UNLESS_POPPED; + PUSH_LABEL(ret, set_label); + PM_COMPILE_NOT_POPPED(cast->value); + if (!popped) PUSH_INSN(ret, location, dup); - ADD_INSN1(ret, &dummy_line_node, setglobal, global_variable_name); - ADD_LABEL(ret, end_label); + PUSH_INSN1(ret, location, setglobal, name); + PUSH_LABEL(ret, end_label); return; } case PM_GLOBAL_VARIABLE_READ_NODE: { - pm_global_variable_read_node_t *global_variable_read_node = (pm_global_variable_read_node_t *)node; - VALUE global_variable_name = ID2SYM(pm_constant_id_lookup(scope_node, global_variable_read_node->name)); - ADD_INSN1(ret, &dummy_line_node, getglobal, global_variable_name); - PM_POP_IF_POPPED; + // $foo + // ^^^^ + const pm_global_variable_read_node_t *cast = (const pm_global_variable_read_node_t *) node; + VALUE name = ID2SYM(pm_constant_id_lookup(scope_node, cast->name)); + + PUSH_INSN1(ret, location, getglobal, name); + if (popped) PUSH_INSN(ret, location, pop); + return; } case PM_GLOBAL_VARIABLE_WRITE_NODE: { - pm_global_variable_write_node_t *write_node = (pm_global_variable_write_node_t *) node; - PM_COMPILE_NOT_POPPED(write_node->value); - PM_DUP_UNLESS_POPPED; - ID ivar_name = pm_constant_id_lookup(scope_node, write_node->name); - ADD_INSN1(ret, &dummy_line_node, setglobal, ID2SYM(ivar_name)); + // $foo = 1 + // ^^^^^^^^ + const pm_global_variable_write_node_t *cast = (const pm_global_variable_write_node_t *) node; + PM_COMPILE_NOT_POPPED(cast->value); + if (!popped) PUSH_INSN(ret, location, dup); + + ID name = pm_constant_id_lookup(scope_node, cast->name); + PUSH_INSN1(ret, location, setglobal, ID2SYM(name)); + return; } case PM_HASH_NODE: { + // {} + // ^^ + // // If every node in the hash is static, then we can compile the entire // hash now instead of later. - if (pm_static_literal_p(node)) { + if (PM_NODE_FLAG_P(node, PM_NODE_FLAG_STATIC_LITERAL)) { // We're only going to compile this node if it's not popped. If it // is popped, then we know we don't need to do anything since it's // statically known. if (!popped) { - VALUE value = pm_static_literal_value(node, scope_node); - ADD_INSN1(ret, &dummy_line_node, duphash, value); + VALUE value = pm_static_literal_value(iseq, node, scope_node); + PUSH_INSN1(ret, location, duphash, value); RB_OBJ_WRITTEN(iseq, Qundef, value); } - } else { + } + else { // Here since we know there are possible side-effects inside the // hash contents, we're going to build it entirely at runtime. We'll // do this by pushing all of the key-value pairs onto the stack and @@ -5784,7 +6049,7 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, } } else { - pm_compile_hash_elements(elements, lineno, iseq, ret, scope_node); + pm_compile_hash_elements(iseq, node, elements, ret, scope_node); } } @@ -5804,8 +6069,10 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, return; } case PM_IMAGINARY_NODE: { + // 1i + // ^^ if (!popped) { - ADD_INSN1(ret, &dummy_line_node, putobject, parse_imaginary((pm_imaginary_node_t *)node)); + PUSH_INSN1(ret, location, putobject, parse_imaginary((const pm_imaginary_node_t *) node)); } return; } @@ -5818,7 +6085,7 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, // In this case a method call/local variable read is implied by virtue // of the missing value. To compile these nodes, we simply compile the // value that is implied, which is helpfully supplied by the parser. - pm_implicit_node_t *cast = (pm_implicit_node_t *)node; + const pm_implicit_node_t *cast = (const pm_implicit_node_t *) node; PM_COMPILE(cast->value); return; } @@ -5828,209 +6095,263 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, rb_bug("Should not ever enter an in node directly"); return; } - case PM_INDEX_OPERATOR_WRITE_NODE: - pm_compile_index_operator_write_node(scope_node, (const pm_index_operator_write_node_t *) node, iseq, ret, popped); + case PM_INDEX_OPERATOR_WRITE_NODE: { + // foo[bar] += baz + // ^^^^^^^^^^^^^^^ + const pm_index_operator_write_node_t *cast = (const pm_index_operator_write_node_t *) node; + pm_compile_index_operator_write_node(iseq, cast, &location, ret, popped, scope_node); return; + } case PM_INDEX_AND_WRITE_NODE: { + // foo[bar] &&= baz + // ^^^^^^^^^^^^^^^^ const pm_index_and_write_node_t *cast = (const pm_index_and_write_node_t *) node; - pm_compile_index_control_flow_write_node(scope_node, node, cast->receiver, cast->arguments, cast->block, cast->value, iseq, ret, popped); + pm_compile_index_control_flow_write_node(iseq, node, cast->receiver, cast->arguments, cast->block, cast->value, &location, ret, popped, scope_node); return; } case PM_INDEX_OR_WRITE_NODE: { + // foo[bar] ||= baz + // ^^^^^^^^^^^^^^^^ const pm_index_or_write_node_t *cast = (const pm_index_or_write_node_t *) node; - pm_compile_index_control_flow_write_node(scope_node, node, cast->receiver, cast->arguments, cast->block, cast->value, iseq, ret, popped); + pm_compile_index_control_flow_write_node(iseq, node, cast->receiver, cast->arguments, cast->block, cast->value, &location, ret, popped, scope_node); return; } case PM_INSTANCE_VARIABLE_AND_WRITE_NODE: { - pm_instance_variable_and_write_node_t *instance_variable_and_write_node = (pm_instance_variable_and_write_node_t*) node; + // @foo &&= bar + // ^^^^^^^^^^^^ + const pm_instance_variable_and_write_node_t *cast = (const pm_instance_variable_and_write_node_t *) node; + LABEL *end_label = NEW_LABEL(location.line); - LABEL *end_label = NEW_LABEL(lineno); - ID instance_variable_name_id = pm_constant_id_lookup(scope_node, instance_variable_and_write_node->name); - VALUE instance_variable_name_val = ID2SYM(instance_variable_name_id); + ID name_id = pm_constant_id_lookup(scope_node, cast->name); + VALUE name = ID2SYM(name_id); - ADD_INSN2(ret, &dummy_line_node, getinstancevariable, instance_variable_name_val, get_ivar_ic_value(iseq, instance_variable_name_id)); - PM_DUP_UNLESS_POPPED; + PUSH_INSN2(ret, location, getinstancevariable, name, get_ivar_ic_value(iseq, name_id)); + if (!popped) PUSH_INSN(ret, location, dup); - ADD_INSNL(ret, &dummy_line_node, branchunless, end_label); - PM_POP_UNLESS_POPPED; + PUSH_INSNL(ret, location, branchunless, end_label); + if (!popped) PUSH_INSN(ret, location, pop); - PM_COMPILE_NOT_POPPED(instance_variable_and_write_node->value); - PM_DUP_UNLESS_POPPED; + PM_COMPILE_NOT_POPPED(cast->value); + if (!popped) PUSH_INSN(ret, location, dup); - ADD_INSN2(ret, &dummy_line_node, setinstancevariable, instance_variable_name_val, get_ivar_ic_value(iseq, instance_variable_name_id)); - ADD_LABEL(ret, end_label); + PUSH_INSN2(ret, location, setinstancevariable, name, get_ivar_ic_value(iseq, name_id)); + PUSH_LABEL(ret, end_label); return; } case PM_INSTANCE_VARIABLE_OPERATOR_WRITE_NODE: { - pm_instance_variable_operator_write_node_t *instance_variable_operator_write_node = (pm_instance_variable_operator_write_node_t*) node; - - ID instance_variable_name_id = pm_constant_id_lookup(scope_node, instance_variable_operator_write_node->name); - VALUE instance_variable_name_val = ID2SYM(instance_variable_name_id); + // @foo += bar + // ^^^^^^^^^^^ + const pm_instance_variable_operator_write_node_t *cast = (const pm_instance_variable_operator_write_node_t *) node; - ADD_INSN2(ret, &dummy_line_node, getinstancevariable, - instance_variable_name_val, - get_ivar_ic_value(iseq, instance_variable_name_id)); + ID name_id = pm_constant_id_lookup(scope_node, cast->name); + VALUE name = ID2SYM(name_id); - PM_COMPILE_NOT_POPPED(instance_variable_operator_write_node->value); - ID method_id = pm_constant_id_lookup(scope_node, instance_variable_operator_write_node->operator); + PUSH_INSN2(ret, location, getinstancevariable, name, get_ivar_ic_value(iseq, name_id)); + PM_COMPILE_NOT_POPPED(cast->value); + ID method_id = pm_constant_id_lookup(scope_node, cast->operator); int flags = VM_CALL_ARGS_SIMPLE; - ADD_SEND_WITH_FLAG(ret, &dummy_line_node, method_id, INT2NUM(1), INT2FIX(flags)); + PUSH_SEND_WITH_FLAG(ret, location, method_id, INT2NUM(1), INT2FIX(flags)); - PM_DUP_UNLESS_POPPED; - - ADD_INSN2(ret, &dummy_line_node, setinstancevariable, - instance_variable_name_val, - get_ivar_ic_value(iseq, instance_variable_name_id)); + if (!popped) PUSH_INSN(ret, location, dup); + PUSH_INSN2(ret, location, setinstancevariable, name, get_ivar_ic_value(iseq, name_id)); return; } case PM_INSTANCE_VARIABLE_OR_WRITE_NODE: { - pm_instance_variable_or_write_node_t *instance_variable_or_write_node = (pm_instance_variable_or_write_node_t*) node; - - LABEL *end_label = NEW_LABEL(lineno); + // @foo ||= bar + // ^^^^^^^^^^^^ + const pm_instance_variable_or_write_node_t *cast = (const pm_instance_variable_or_write_node_t *) node; + LABEL *end_label = NEW_LABEL(location.line); - ID instance_variable_name_id = pm_constant_id_lookup(scope_node, instance_variable_or_write_node->name); - VALUE instance_variable_name_val = ID2SYM(instance_variable_name_id); + ID name_id = pm_constant_id_lookup(scope_node, cast->name); + VALUE name = ID2SYM(name_id); - ADD_INSN2(ret, &dummy_line_node, getinstancevariable, instance_variable_name_val, get_ivar_ic_value(iseq, instance_variable_name_id)); - PM_DUP_UNLESS_POPPED; + PUSH_INSN2(ret, location, getinstancevariable, name, get_ivar_ic_value(iseq, name_id)); + if (!popped) PUSH_INSN(ret, location, dup); - ADD_INSNL(ret, &dummy_line_node, branchif, end_label); - PM_POP_UNLESS_POPPED; + PUSH_INSNL(ret, location, branchif, end_label); + if (!popped) PUSH_INSN(ret, location, pop); - PM_COMPILE_NOT_POPPED(instance_variable_or_write_node->value); - PM_DUP_UNLESS_POPPED; + PM_COMPILE_NOT_POPPED(cast->value); + if (!popped) PUSH_INSN(ret, location, dup); - ADD_INSN2(ret, &dummy_line_node, setinstancevariable, instance_variable_name_val, get_ivar_ic_value(iseq, instance_variable_name_id)); - ADD_LABEL(ret, end_label); + PUSH_INSN2(ret, location, setinstancevariable, name, get_ivar_ic_value(iseq, name_id)); + PUSH_LABEL(ret, end_label); return; } case PM_INSTANCE_VARIABLE_READ_NODE: { + // @foo + // ^^^^ if (!popped) { - pm_instance_variable_read_node_t *instance_variable_read_node = (pm_instance_variable_read_node_t *) node; - ID ivar_name = pm_constant_id_lookup(scope_node, instance_variable_read_node->name); - ADD_INSN2(ret, &dummy_line_node, getinstancevariable, ID2SYM(ivar_name), get_ivar_ic_value(iseq, ivar_name)); + const pm_instance_variable_read_node_t *cast = (const pm_instance_variable_read_node_t *) node; + ID name = pm_constant_id_lookup(scope_node, cast->name); + PUSH_INSN2(ret, location, getinstancevariable, ID2SYM(name), get_ivar_ic_value(iseq, name)); } return; } case PM_INSTANCE_VARIABLE_WRITE_NODE: { - pm_instance_variable_write_node_t *write_node = (pm_instance_variable_write_node_t *) node; - PM_COMPILE_NOT_POPPED(write_node->value); + // @foo = 1 + // ^^^^^^^^ + const pm_instance_variable_write_node_t *cast = (const pm_instance_variable_write_node_t *) node; + PM_COMPILE_NOT_POPPED(cast->value); + if (!popped) PUSH_INSN(ret, location, dup); - PM_DUP_UNLESS_POPPED; + ID name = pm_constant_id_lookup(scope_node, cast->name); + PUSH_INSN2(ret, location, setinstancevariable, ID2SYM(name), get_ivar_ic_value(iseq, name)); - ID ivar_name = pm_constant_id_lookup(scope_node, write_node->name); - ADD_INSN2(ret, &dummy_line_node, setinstancevariable, - ID2SYM(ivar_name), - get_ivar_ic_value(iseq, ivar_name)); return; } case PM_INTEGER_NODE: { + // 1 + // ^ if (!popped) { - ADD_INSN1(ret, &dummy_line_node, putobject, parse_integer((const pm_integer_node_t *) node)); + PUSH_INSN1(ret, location, putobject, parse_integer((const pm_integer_node_t *) node)); } return; } case PM_INTERPOLATED_MATCH_LAST_LINE_NODE: { - pm_interpolated_match_last_line_node_t *cast = (pm_interpolated_match_last_line_node_t *) node; - - int parts_size = (int)cast->parts.size; - - pm_interpolated_node_compile(&cast->parts, iseq, dummy_line_node, ret, popped, scope_node); - - ADD_INSN2(ret, &dummy_line_node, toregexp, INT2FIX(pm_reg_flags(node)), INT2FIX(parts_size)); + // if /foo #{bar}/ then end + // ^^^^^^^^^^^^ + if (PM_NODE_FLAG_P(node, PM_NODE_FLAG_STATIC_LITERAL)) { + if (!popped) { + VALUE regexp = pm_static_literal_value(iseq, node, scope_node); + PUSH_INSN1(ret, location, putobject, regexp); + } + } + else { + pm_compile_regexp_dynamic(iseq, node, &((const pm_interpolated_match_last_line_node_t *) node)->parts, &location, ret, popped, scope_node); + } - ADD_INSN1(ret, &dummy_line_node, getglobal, rb_id2sym(idLASTLINE)); - ADD_SEND(ret, &dummy_line_node, idEqTilde, INT2NUM(1)); - PM_POP_IF_POPPED; + PUSH_INSN1(ret, location, getglobal, rb_id2sym(idLASTLINE)); + PUSH_SEND(ret, location, idEqTilde, INT2NUM(1)); + if (popped) PUSH_INSN(ret, location, pop); return; } case PM_INTERPOLATED_REGULAR_EXPRESSION_NODE: { - if (node->flags & PM_REGULAR_EXPRESSION_FLAGS_ONCE) { + // /foo #{bar}/ + // ^^^^^^^^^^^^ + if (PM_NODE_FLAG_P(node, PM_REGULAR_EXPRESSION_FLAGS_ONCE)) { const rb_iseq_t *prevblock = ISEQ_COMPILE_DATA(iseq)->current_block; const rb_iseq_t *block_iseq = NULL; - int ic_index = ISEQ_BODY(iseq)->ise_size++; + int ise_index = ISEQ_BODY(iseq)->ise_size++; pm_scope_node_t next_scope_node; - pm_scope_node_init((pm_node_t*)node, &next_scope_node, scope_node); - block_iseq = NEW_CHILD_ISEQ(&next_scope_node, make_name_for_block(iseq), ISEQ_TYPE_BLOCK, lineno); + pm_scope_node_init(node, &next_scope_node, scope_node); + + block_iseq = NEW_CHILD_ISEQ(&next_scope_node, make_name_for_block(iseq), ISEQ_TYPE_BLOCK, location.line); pm_scope_node_destroy(&next_scope_node); ISEQ_COMPILE_DATA(iseq)->current_block = block_iseq; - - ADD_INSN2(ret, &dummy_line_node, once, block_iseq, INT2FIX(ic_index)); - + PUSH_INSN2(ret, location, once, block_iseq, INT2FIX(ise_index)); ISEQ_COMPILE_DATA(iseq)->current_block = prevblock; + + if (popped) PUSH_INSN(ret, location, pop); return; } - pm_interpolated_regular_expression_node_t *cast = (pm_interpolated_regular_expression_node_t *) node; - - int parts_size = pm_interpolated_node_compile(&cast->parts, iseq, dummy_line_node, ret, popped, scope_node); + if (PM_NODE_FLAG_P(node, PM_NODE_FLAG_STATIC_LITERAL)) { + if (!popped) { + VALUE regexp = pm_static_literal_value(iseq, node, scope_node); + PUSH_INSN1(ret, location, putobject, regexp); + } + } + else { + pm_compile_regexp_dynamic(iseq, node, &((const pm_interpolated_regular_expression_node_t *) node)->parts, &location, ret, popped, scope_node); + if (popped) PUSH_INSN(ret, location, pop); + } - ADD_INSN2(ret, &dummy_line_node, toregexp, INT2FIX(pm_reg_flags(node)), INT2FIX(parts_size)); - PM_POP_IF_POPPED; return; } case PM_INTERPOLATED_STRING_NODE: { - pm_interpolated_string_node_t *interp_string_node = (pm_interpolated_string_node_t *) node; - int number_of_items_pushed = pm_interpolated_node_compile(&interp_string_node->parts, iseq, dummy_line_node, ret, popped, scope_node); + // "foo #{bar}" + // ^^^^^^^^^^^^ + if (PM_NODE_FLAG_P(node, PM_NODE_FLAG_STATIC_LITERAL)) { + if (!popped) { + VALUE string = pm_static_literal_value(iseq, node, scope_node); - if (number_of_items_pushed > 1) { - ADD_INSN1(ret, &dummy_line_node, concatstrings, INT2FIX(number_of_items_pushed)); + if (PM_NODE_FLAG_P(node, PM_INTERPOLATED_STRING_NODE_FLAGS_FROZEN)) { + PUSH_INSN1(ret, location, putobject, string); + } + else if (PM_NODE_FLAG_P(node, PM_INTERPOLATED_STRING_NODE_FLAGS_MUTABLE)) { + PUSH_INSN1(ret, location, putstring, string); + } + else { + PUSH_INSN1(ret, location, putchilledstring, string); + } + } + } + else { + const pm_interpolated_string_node_t *cast = (const pm_interpolated_string_node_t *) node; + int length = pm_interpolated_node_compile(iseq, &cast->parts, &location, ret, popped, scope_node); + if (length > 1) PUSH_INSN1(ret, location, concatstrings, INT2FIX(length)); + if (popped) PUSH_INSN(ret, location, pop); } - PM_POP_IF_POPPED; return; } case PM_INTERPOLATED_SYMBOL_NODE: { - pm_interpolated_symbol_node_t *interp_symbol_node = (pm_interpolated_symbol_node_t *) node; - int number_of_items_pushed = pm_interpolated_node_compile(&interp_symbol_node->parts, iseq, dummy_line_node, ret, popped, scope_node); - - if (number_of_items_pushed > 1) { - ADD_INSN1(ret, &dummy_line_node, concatstrings, INT2FIX(number_of_items_pushed)); - } + // :"foo #{bar}" + // ^^^^^^^^^^^^^ + const pm_interpolated_symbol_node_t *cast = (const pm_interpolated_symbol_node_t *) node; - if (!popped) { - ADD_INSN(ret, &dummy_line_node, intern); + if (PM_NODE_FLAG_P(node, PM_NODE_FLAG_STATIC_LITERAL)) { + if (!popped) { + VALUE symbol = pm_static_literal_value(iseq, node, scope_node); + PUSH_INSN1(ret, location, putobject, symbol); + } } else { - PM_POP; + int length = pm_interpolated_node_compile(iseq, &cast->parts, &location, ret, popped, scope_node); + if (length > 1) { + PUSH_INSN1(ret, location, concatstrings, INT2FIX(length)); + } + + if (!popped) { + PUSH_INSN(ret, location, intern); + } + else { + PUSH_INSN(ret, location, pop); + } } return; } case PM_INTERPOLATED_X_STRING_NODE: { - pm_interpolated_x_string_node_t *interp_x_string_node = (pm_interpolated_x_string_node_t *) node; - PM_PUTSELF; - int number_of_items_pushed = pm_interpolated_node_compile(&interp_x_string_node->parts, iseq, dummy_line_node, ret, false, scope_node); + // `foo #{bar}` + // ^^^^^^^^^^^^ + const pm_interpolated_x_string_node_t *cast = (const pm_interpolated_x_string_node_t *) node; - if (number_of_items_pushed > 1) { - ADD_INSN1(ret, &dummy_line_node, concatstrings, INT2FIX(number_of_items_pushed)); - } + PUSH_INSN(ret, location, putself); + + int length = pm_interpolated_node_compile(iseq, &cast->parts, &location, ret, false, scope_node); + if (length > 1) PUSH_INSN1(ret, location, concatstrings, INT2FIX(length)); + + PUSH_SEND_WITH_FLAG(ret, location, idBackquote, INT2NUM(1), INT2FIX(VM_CALL_FCALL | VM_CALL_ARGS_SIMPLE)); + if (popped) PUSH_INSN(ret, location, pop); - ADD_SEND_WITH_FLAG(ret, &dummy_line_node, idBackquote, INT2NUM(1), INT2FIX(VM_CALL_FCALL | VM_CALL_ARGS_SIMPLE)); - PM_POP_IF_POPPED; return; } case PM_KEYWORD_HASH_NODE: { - pm_keyword_hash_node_t *keyword_hash_node = (pm_keyword_hash_node_t *) node; - pm_node_list_t elements = keyword_hash_node->elements; + // foo(bar: baz) + // ^^^^^^^^ + const pm_keyword_hash_node_t *cast = (const pm_keyword_hash_node_t *) node; + const pm_node_list_t *elements = &cast->elements; - for (size_t index = 0; index < elements.size; index++) { - PM_COMPILE(elements.nodes[index]); + const pm_node_t *element; + PM_NODE_LIST_FOREACH(elements, index, element) { + PM_COMPILE(element); } - if (!popped) { - ADD_INSN1(ret, &dummy_line_node, newhash, INT2FIX(elements.size * 2)); - } + if (!popped) PUSH_INSN1(ret, location, newhash, INT2FIX(elements->size * 2)); return; } case PM_LAMBDA_NODE: { + // -> {} + // ^^^^^ const pm_lambda_node_t *cast = (const pm_lambda_node_t *) node; pm_scope_node_t next_scope_node; @@ -6041,163 +6362,158 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, pm_scope_node_destroy(&next_scope_node); VALUE argc = INT2FIX(0); + PUSH_INSN1(ret, location, putspecialobject, INT2FIX(VM_SPECIAL_OBJECT_VMCORE)); + PUSH_CALL_WITH_BLOCK(ret, location, idLambda, argc, block); + RB_OBJ_WRITTEN(iseq, Qundef, (VALUE) block); - ADD_INSN1(ret, &dummy_line_node, putspecialobject, INT2FIX(VM_SPECIAL_OBJECT_VMCORE)); - ADD_CALL_WITH_BLOCK(ret, &dummy_line_node, idLambda, argc, block); - RB_OBJ_WRITTEN(iseq, Qundef, (VALUE)block); - - PM_POP_IF_POPPED; - return; - } - case PM_LOCAL_VARIABLE_AND_WRITE_NODE: { - pm_local_variable_and_write_node_t *local_variable_and_write_node = (pm_local_variable_and_write_node_t*) node; - - LABEL *end_label = NEW_LABEL(lineno); - - pm_constant_id_t constant_id = local_variable_and_write_node->name; - pm_local_index_t local_index = pm_lookup_local_index(iseq, scope_node, constant_id, local_variable_and_write_node->depth); - ADD_GETLOCAL(ret, &dummy_line_node, local_index.index, local_index.level); - - PM_DUP_UNLESS_POPPED; - - ADD_INSNL(ret, &dummy_line_node, branchunless, end_label); + if (popped) PUSH_INSN(ret, location, pop); + return; + } + case PM_LOCAL_VARIABLE_AND_WRITE_NODE: { + // foo &&= bar + // ^^^^^^^^^^^ + const pm_local_variable_and_write_node_t *cast = (const pm_local_variable_and_write_node_t *) node; + LABEL *end_label = NEW_LABEL(location.line); - PM_POP_UNLESS_POPPED; + pm_local_index_t local_index = pm_lookup_local_index(iseq, scope_node, cast->name, cast->depth); + PUSH_GETLOCAL(ret, location, local_index.index, local_index.level); + if (!popped) PUSH_INSN(ret, location, dup); - PM_COMPILE_NOT_POPPED(local_variable_and_write_node->value); + PUSH_INSNL(ret, location, branchunless, end_label); + if (!popped) PUSH_INSN(ret, location, pop); - PM_DUP_UNLESS_POPPED; + PM_COMPILE_NOT_POPPED(cast->value); + if (!popped) PUSH_INSN(ret, location, dup); - ADD_SETLOCAL(ret, &dummy_line_node, local_index.index, local_index.level); - ADD_LABEL(ret, end_label); + PUSH_SETLOCAL(ret, location, local_index.index, local_index.level); + PUSH_LABEL(ret, end_label); return; } case PM_LOCAL_VARIABLE_OPERATOR_WRITE_NODE: { - pm_local_variable_operator_write_node_t *local_variable_operator_write_node = (pm_local_variable_operator_write_node_t*) node; - - pm_constant_id_t constant_id = local_variable_operator_write_node->name; - pm_local_index_t local_index = pm_lookup_local_index(iseq, scope_node, constant_id, local_variable_operator_write_node->depth); - - ADD_GETLOCAL(ret, &dummy_line_node, local_index.index, local_index.level); + // foo += bar + // ^^^^^^^^^^ + const pm_local_variable_operator_write_node_t *cast = (const pm_local_variable_operator_write_node_t *) node; - PM_COMPILE_NOT_POPPED(local_variable_operator_write_node->value); - ID method_id = pm_constant_id_lookup(scope_node, local_variable_operator_write_node->operator); + pm_local_index_t local_index = pm_lookup_local_index(iseq, scope_node, cast->name, cast->depth); + PUSH_GETLOCAL(ret, location, local_index.index, local_index.level); - int flags = VM_CALL_ARGS_SIMPLE | VM_CALL_FCALL | VM_CALL_VCALL; - ADD_SEND_WITH_FLAG(ret, &dummy_line_node, method_id, INT2NUM(1), INT2FIX(flags)); + PM_COMPILE_NOT_POPPED(cast->value); - PM_DUP_UNLESS_POPPED; + ID method_id = pm_constant_id_lookup(scope_node, cast->operator); + PUSH_SEND_WITH_FLAG(ret, location, method_id, INT2NUM(1), INT2FIX(VM_CALL_ARGS_SIMPLE)); - ADD_SETLOCAL(ret, &dummy_line_node, local_index.index, local_index.level); + if (!popped) PUSH_INSN(ret, location, dup); + PUSH_SETLOCAL(ret, location, local_index.index, local_index.level); return; } case PM_LOCAL_VARIABLE_OR_WRITE_NODE: { - pm_local_variable_or_write_node_t *local_variable_or_write_node = (pm_local_variable_or_write_node_t*) node; - - LABEL *set_label= NEW_LABEL(lineno); - LABEL *end_label = NEW_LABEL(lineno); - - ADD_INSN1(ret, &dummy_line_node, putobject, Qtrue); - ADD_INSNL(ret, &dummy_line_node, branchunless, set_label); - - pm_constant_id_t constant_id = local_variable_or_write_node->name; - pm_local_index_t local_index = pm_lookup_local_index(iseq, scope_node, constant_id, local_variable_or_write_node->depth); - ADD_GETLOCAL(ret, &dummy_line_node, local_index.index, local_index.level); + // foo ||= bar + // ^^^^^^^^^^^ + const pm_local_variable_or_write_node_t *cast = (const pm_local_variable_or_write_node_t *) node; - PM_DUP_UNLESS_POPPED; + LABEL *set_label = NEW_LABEL(location.line); + LABEL *end_label = NEW_LABEL(location.line); - ADD_INSNL(ret, &dummy_line_node, branchif, end_label); + PUSH_INSN1(ret, location, putobject, Qtrue); + PUSH_INSNL(ret, location, branchunless, set_label); - PM_POP_UNLESS_POPPED; + pm_local_index_t local_index = pm_lookup_local_index(iseq, scope_node, cast->name, cast->depth); + PUSH_GETLOCAL(ret, location, local_index.index, local_index.level); + if (!popped) PUSH_INSN(ret, location, dup); - ADD_LABEL(ret, set_label); - PM_COMPILE_NOT_POPPED(local_variable_or_write_node->value); + PUSH_INSNL(ret, location, branchif, end_label); + if (!popped) PUSH_INSN(ret, location, pop); - PM_DUP_UNLESS_POPPED; + PUSH_LABEL(ret, set_label); + PM_COMPILE_NOT_POPPED(cast->value); + if (!popped) PUSH_INSN(ret, location, dup); - ADD_SETLOCAL(ret, &dummy_line_node, local_index.index, local_index.level); - ADD_LABEL(ret, end_label); + PUSH_SETLOCAL(ret, location, local_index.index, local_index.level); + PUSH_LABEL(ret, end_label); return; } case PM_LOCAL_VARIABLE_READ_NODE: { - pm_local_variable_read_node_t *local_read_node = (pm_local_variable_read_node_t *) node; + // foo + // ^^^ + const pm_local_variable_read_node_t *cast = (const pm_local_variable_read_node_t *) node; if (!popped) { - pm_local_index_t index = pm_lookup_local_index(iseq, scope_node, local_read_node->name, local_read_node->depth); - ADD_GETLOCAL(ret, &dummy_line_node, index.index, index.level); + pm_local_index_t index = pm_lookup_local_index(iseq, scope_node, cast->name, cast->depth); + PUSH_GETLOCAL(ret, location, index.index, index.level); } + return; } case PM_LOCAL_VARIABLE_WRITE_NODE: { - pm_local_variable_write_node_t *local_write_node = (pm_local_variable_write_node_t *) node; - PM_COMPILE_NOT_POPPED(local_write_node->value); - - PM_DUP_UNLESS_POPPED; - - pm_constant_id_t constant_id = local_write_node->name; - - pm_local_index_t index = pm_lookup_local_index(iseq, scope_node, constant_id, local_write_node->depth); + // foo = 1 + // ^^^^^^^ + const pm_local_variable_write_node_t *cast = (const pm_local_variable_write_node_t *) node; + PM_COMPILE_NOT_POPPED(cast->value); + if (!popped) PUSH_INSN(ret, location, dup); - ADD_SETLOCAL(ret, &dummy_line_node, index.index, index.level); + pm_local_index_t index = pm_lookup_local_index(iseq, scope_node, cast->name, cast->depth); + PUSH_SETLOCAL(ret, location, index.index, index.level); return; } case PM_MATCH_LAST_LINE_NODE: { - if (!popped) { - pm_match_last_line_node_t *cast = (pm_match_last_line_node_t *) node; + // if /foo/ then end + // ^^^^^ + VALUE regexp = pm_static_literal_value(iseq, node, scope_node); - VALUE regex_str = parse_string(scope_node, &cast->unescaped); - VALUE regex = rb_reg_new(RSTRING_PTR(regex_str), RSTRING_LEN(regex_str), pm_reg_flags(node)); - RB_GC_GUARD(regex_str); - - ADD_INSN1(ret, &dummy_line_node, putobject, regex); - ADD_INSN2(ret, &dummy_line_node, getspecial, INT2FIX(0), INT2FIX(0)); - ADD_SEND(ret, &dummy_line_node, idEqTilde, INT2NUM(1)); - } + PUSH_INSN1(ret, location, putobject, regexp); + PUSH_INSN2(ret, location, getspecial, INT2FIX(0), INT2FIX(0)); + PUSH_SEND(ret, location, idEqTilde, INT2NUM(1)); + if (popped) PUSH_INSN(ret, location, pop); return; } case PM_MATCH_PREDICATE_NODE: { - pm_match_predicate_node_t *cast = (pm_match_predicate_node_t *) node; + // foo in bar + // ^^^^^^^^^^ + const pm_match_predicate_node_t *cast = (const pm_match_predicate_node_t *) node; // First, allocate some stack space for the cached return value of any // calls to #deconstruct. - PM_PUTNIL; + PUSH_INSN(ret, location, putnil); // Next, compile the expression that we're going to match against. PM_COMPILE_NOT_POPPED(cast->value); - PM_DUP; + PUSH_INSN(ret, location, dup); // Now compile the pattern that is going to be used to match against the // expression. - LABEL *matched_label = NEW_LABEL(lineno); - LABEL *unmatched_label = NEW_LABEL(lineno); - LABEL *done_label = NEW_LABEL(lineno); + LABEL *matched_label = NEW_LABEL(location.line); + LABEL *unmatched_label = NEW_LABEL(location.line); + LABEL *done_label = NEW_LABEL(location.line); pm_compile_pattern(iseq, scope_node, cast->pattern, ret, matched_label, unmatched_label, false, false, true, 2); // If the pattern did not match, then compile the necessary instructions // to handle pushing false onto the stack, then jump to the end. - ADD_LABEL(ret, unmatched_label); - PM_POP; - PM_POP; + PUSH_LABEL(ret, unmatched_label); + PUSH_INSN(ret, location, pop); + PUSH_INSN(ret, location, pop); - if (!popped) ADD_INSN1(ret, &dummy_line_node, putobject, Qfalse); - ADD_INSNL(ret, &dummy_line_node, jump, done_label); - PM_PUTNIL; + if (!popped) PUSH_INSN1(ret, location, putobject, Qfalse); + PUSH_INSNL(ret, location, jump, done_label); + PUSH_INSN(ret, location, putnil); // If the pattern did match, then compile the necessary instructions to // handle pushing true onto the stack, then jump to the end. - ADD_LABEL(ret, matched_label); - ADD_INSN1(ret, &dummy_line_node, adjuststack, INT2FIX(2)); - if (!popped) ADD_INSN1(ret, &dummy_line_node, putobject, Qtrue); - ADD_INSNL(ret, &dummy_line_node, jump, done_label); + PUSH_LABEL(ret, matched_label); + PUSH_INSN1(ret, location, adjuststack, INT2FIX(2)); + if (!popped) PUSH_INSN1(ret, location, putobject, Qtrue); + PUSH_INSNL(ret, location, jump, done_label); - ADD_LABEL(ret, done_label); + PUSH_LABEL(ret, done_label); return; } case PM_MATCH_REQUIRED_NODE: { + // foo => bar + // ^^^^^^^^^^ + // // A match required node represents pattern matching against a single // pattern using the => operator. For example, // @@ -6208,17 +6524,17 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, // immediately raise an error. const pm_match_required_node_t *cast = (const pm_match_required_node_t *) node; - LABEL *matched_label = NEW_LABEL(lineno); - LABEL *unmatched_label = NEW_LABEL(lineno); - LABEL *done_label = NEW_LABEL(lineno); + LABEL *matched_label = NEW_LABEL(location.line); + LABEL *unmatched_label = NEW_LABEL(location.line); + LABEL *done_label = NEW_LABEL(location.line); // First, we're going to push a bunch of stuff onto the stack that is // going to serve as our scratch space. - ADD_INSN(ret, &dummy_line_node, putnil); // key error key - ADD_INSN(ret, &dummy_line_node, putnil); // key error matchee - ADD_INSN1(ret, &dummy_line_node, putobject, Qfalse); // key error? - ADD_INSN(ret, &dummy_line_node, putnil); // error string - ADD_INSN(ret, &dummy_line_node, putnil); // deconstruct cache + PUSH_INSN(ret, location, putnil); // key error key + PUSH_INSN(ret, location, putnil); // key error matchee + PUSH_INSN1(ret, location, putobject, Qfalse); // key error? + PUSH_INSN(ret, location, putnil); // error string + PUSH_INSN(ret, location, putnil); // deconstruct cache // Next we're going to compile the value expression such that it's on // the stack. @@ -6226,7 +6542,7 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, // Here we'll dup it so that it can be used for comparison, but also be // used for error handling. - ADD_INSN(ret, &dummy_line_node, dup); + PUSH_INSN(ret, location, dup); // Next we'll compile the pattern. We indicate to the pm_compile_pattern // function that this is the only pattern that will be matched against @@ -6238,98 +6554,97 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, // If the pattern did not match the value, then we're going to compile // in our error handler code. This will determine which error to raise // and raise it. - ADD_LABEL(ret, unmatched_label); + PUSH_LABEL(ret, unmatched_label); pm_compile_pattern_error_handler(iseq, scope_node, node, ret, done_label, popped); // If the pattern did match, we'll clean up the values we've pushed onto // the stack and then push nil onto the stack if it's not popped. - ADD_LABEL(ret, matched_label); - ADD_INSN1(ret, &dummy_line_node, adjuststack, INT2FIX(6)); - if (!popped) ADD_INSN(ret, &dummy_line_node, putnil); - ADD_INSNL(ret, &dummy_line_node, jump, done_label); + PUSH_LABEL(ret, matched_label); + PUSH_INSN1(ret, location, adjuststack, INT2FIX(6)); + if (!popped) PUSH_INSN(ret, location, putnil); + PUSH_INSNL(ret, location, jump, done_label); - ADD_LABEL(ret, done_label); + PUSH_LABEL(ret, done_label); return; } case PM_MATCH_WRITE_NODE: { + // /(?foo)/ =~ bar + // ^^^^^^^^^^^^^^^^^^^^ + // // Match write nodes are specialized call nodes that have a regular // expression with valid named capture groups on the left, the =~ // operator, and some value on the right. The nodes themselves simply // wrap the call with the local variable targets that will be written // when the call is executed. - pm_match_write_node_t *cast = (pm_match_write_node_t *) node; - LABEL *fail_label = NEW_LABEL(lineno); - LABEL *end_label = NEW_LABEL(lineno); + const pm_match_write_node_t *cast = (const pm_match_write_node_t *) node; + LABEL *fail_label = NEW_LABEL(location.line); + LABEL *end_label = NEW_LABEL(location.line); // First, we'll compile the call so that all of its instructions are // present. Then we'll compile all of the local variable targets. - PM_COMPILE_NOT_POPPED((pm_node_t *) cast->call); + PM_COMPILE_NOT_POPPED((const pm_node_t *) cast->call); // Now, check if the match was successful. If it was, then we'll // continue on and assign local variables. Otherwise we'll skip over the // assignment code. - ADD_INSN1(ret, &dummy_line_node, getglobal, rb_id2sym(idBACKREF)); - PM_DUP; - ADD_INSNL(ret, &dummy_line_node, branchunless, fail_label); + PUSH_INSN1(ret, location, getglobal, rb_id2sym(idBACKREF)); + PUSH_INSN(ret, location, dup); + PUSH_INSNL(ret, location, branchunless, fail_label); // If there's only a single local variable target, we can skip some of // the bookkeeping, so we'll put a special branch here. size_t targets_count = cast->targets.size; if (targets_count == 1) { - pm_node_t *target = cast->targets.nodes[0]; + const pm_node_t *target = cast->targets.nodes[0]; RUBY_ASSERT(PM_NODE_TYPE_P(target, PM_LOCAL_VARIABLE_TARGET_NODE)); - pm_local_variable_target_node_t *local_target = (pm_local_variable_target_node_t *) target; + const pm_local_variable_target_node_t *local_target = (const pm_local_variable_target_node_t *) target; pm_local_index_t index = pm_lookup_local_index(iseq, scope_node, local_target->name, local_target->depth); - ADD_INSN1(ret, &dummy_line_node, putobject, rb_id2sym(pm_constant_id_lookup(scope_node, local_target->name))); - ADD_SEND(ret, &dummy_line_node, idAREF, INT2FIX(1)); - ADD_LABEL(ret, fail_label); - ADD_SETLOCAL(ret, &dummy_line_node, index.index, index.level); - PM_POP_IF_POPPED; + PUSH_INSN1(ret, location, putobject, rb_id2sym(pm_constant_id_lookup(scope_node, local_target->name))); + PUSH_SEND(ret, location, idAREF, INT2FIX(1)); + PUSH_LABEL(ret, fail_label); + PUSH_SETLOCAL(ret, location, index.index, index.level); + if (popped) PUSH_INSN(ret, location, pop); return; } + DECL_ANCHOR(fail_anchor); + INIT_ANCHOR(fail_anchor); + // Otherwise there is more than one local variable target, so we'll need // to do some bookkeeping. for (size_t targets_index = 0; targets_index < targets_count; targets_index++) { - pm_node_t *target = cast->targets.nodes[targets_index]; + const pm_node_t *target = cast->targets.nodes[targets_index]; RUBY_ASSERT(PM_NODE_TYPE_P(target, PM_LOCAL_VARIABLE_TARGET_NODE)); - pm_local_variable_target_node_t *local_target = (pm_local_variable_target_node_t *) target; + const pm_local_variable_target_node_t *local_target = (const pm_local_variable_target_node_t *) target; pm_local_index_t index = pm_lookup_local_index(iseq, scope_node, local_target->name, local_target->depth); if (((size_t) targets_index) < (targets_count - 1)) { - PM_DUP; + PUSH_INSN(ret, location, dup); } - ADD_INSN1(ret, &dummy_line_node, putobject, rb_id2sym(pm_constant_id_lookup(scope_node, local_target->name))); - ADD_SEND(ret, &dummy_line_node, idAREF, INT2FIX(1)); - ADD_SETLOCAL(ret, &dummy_line_node, index.index, index.level); + PUSH_INSN1(ret, location, putobject, rb_id2sym(pm_constant_id_lookup(scope_node, local_target->name))); + PUSH_SEND(ret, location, idAREF, INT2FIX(1)); + PUSH_SETLOCAL(ret, location, index.index, index.level); + + PUSH_INSN(fail_anchor, location, putnil); + PUSH_SETLOCAL(fail_anchor, location, index.index, index.level); } // Since we matched successfully, now we'll jump to the end. - ADD_INSNL(ret, &dummy_line_node, jump, end_label); + PUSH_INSNL(ret, location, jump, end_label); // In the case that the match failed, we'll loop through each local // variable target and set all of them to `nil`. - ADD_LABEL(ret, fail_label); - PM_POP; - - for (size_t targets_index = 0; targets_index < targets_count; targets_index++) { - pm_node_t *target = cast->targets.nodes[targets_index]; - RUBY_ASSERT(PM_NODE_TYPE_P(target, PM_LOCAL_VARIABLE_TARGET_NODE)); - - pm_local_variable_target_node_t *local_target = (pm_local_variable_target_node_t *) target; - pm_local_index_t index = pm_lookup_local_index(iseq, scope_node, local_target->name, local_target->depth); - - PM_PUTNIL; - ADD_SETLOCAL(ret, &dummy_line_node, index.index, index.level); - } + PUSH_LABEL(ret, fail_label); + PUSH_INSN(ret, location, pop); + PUSH_SEQ(ret, fail_anchor); // Finally, we can push the end label for either case. - ADD_LABEL(ret, end_label); - PM_POP_IF_POPPED; + PUSH_LABEL(ret, end_label); + if (popped) PUSH_INSN(ret, location, pop); return; } case PM_MISSING_NODE: { @@ -6337,34 +6652,40 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, return; } case PM_MODULE_NODE: { - pm_module_node_t *module_node = (pm_module_node_t *)node; + // module Foo; end + // ^^^^^^^^^^^^^^^ + const pm_module_node_t *cast = (const pm_module_node_t *) node; - ID module_id = pm_constant_id_lookup(scope_node, module_node->name); + ID module_id = pm_constant_id_lookup(scope_node, cast->name); VALUE module_name = rb_str_freeze(rb_sprintf("", rb_id2str(module_id))); pm_scope_node_t next_scope_node; - pm_scope_node_init((pm_node_t *)module_node, &next_scope_node, scope_node); - const rb_iseq_t *module_iseq = NEW_CHILD_ISEQ(&next_scope_node, module_name, ISEQ_TYPE_CLASS, lineno); - pm_scope_node_destroy(&next_scope_node); + pm_scope_node_init((const pm_node_t *) cast, &next_scope_node, scope_node); - const int flags = VM_DEFINECLASS_TYPE_MODULE | - pm_compile_class_path(ret, iseq, module_node->constant_path, &dummy_line_node, false, scope_node); + const rb_iseq_t *module_iseq = NEW_CHILD_ISEQ(&next_scope_node, module_name, ISEQ_TYPE_CLASS, location.line); + pm_scope_node_destroy(&next_scope_node); - PM_PUTNIL; - ADD_INSN3(ret, &dummy_line_node, defineclass, ID2SYM(module_id), module_iseq, INT2FIX(flags)); - RB_OBJ_WRITTEN(iseq, Qundef, (VALUE)module_iseq); + const int flags = VM_DEFINECLASS_TYPE_MODULE | pm_compile_class_path(iseq, cast->constant_path, &location, ret, false, scope_node); + PUSH_INSN(ret, location, putnil); + PUSH_INSN3(ret, location, defineclass, ID2SYM(module_id), module_iseq, INT2FIX(flags)); + RB_OBJ_WRITTEN(iseq, Qundef, (VALUE) module_iseq); - PM_POP_IF_POPPED; + if (popped) PUSH_INSN(ret, location, pop); return; } case PM_REQUIRED_PARAMETER_NODE: { - pm_required_parameter_node_t *required_parameter_node = (pm_required_parameter_node_t *)node; - pm_local_index_t index = pm_lookup_local_index(iseq, scope_node, required_parameter_node->name, 0); + // def foo(bar); end + // ^^^ + const pm_required_parameter_node_t *cast = (const pm_required_parameter_node_t *) node; + pm_local_index_t index = pm_lookup_local_index(iseq, scope_node, cast->name, 0); - ADD_SETLOCAL(ret, &dummy_line_node, index.index, index.level); + PUSH_SETLOCAL(ret, location, index.index, index.level); return; } case PM_MULTI_WRITE_NODE: { + // foo, bar = baz + // ^^^^^^^^^^^^^^ + // // A multi write node represents writing to multiple values using an = // operator. Importantly these nodes are only parsed when the left-hand // side of the operator has multiple targets. The right-hand side of the @@ -6383,65 +6704,68 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, size_t stack_size = pm_compile_multi_target_node(iseq, node, ret, writes, cleanup, scope_node, &state); PM_COMPILE_NOT_POPPED(cast->value); - PM_DUP_UNLESS_POPPED; + if (!popped) PUSH_INSN(ret, location, dup); - ADD_SEQ(ret, writes); + PUSH_SEQ(ret, writes); if (!popped && stack_size >= 1) { // Make sure the value on the right-hand side of the = operator is // being returned before we pop the parent expressions. - ADD_INSN1(ret, &dummy_line_node, setn, INT2FIX(stack_size)); + PUSH_INSN1(ret, location, setn, INT2FIX(stack_size)); } - ADD_SEQ(ret, cleanup); - + PUSH_SEQ(ret, cleanup); return; } case PM_NEXT_NODE: { - pm_next_node_t *next_node = (pm_next_node_t *) node; + // next + // ^^^^ + // + // next foo + // ^^^^^^^^ + const pm_next_node_t *cast = (const pm_next_node_t *) node; if (ISEQ_COMPILE_DATA(iseq)->redo_label != 0 && can_add_ensure_iseq(iseq)) { LABEL *splabel = NEW_LABEL(0); + PUSH_LABEL(ret, splabel); - ADD_LABEL(ret, splabel); - - if (next_node->arguments) { - PM_COMPILE_NOT_POPPED((pm_node_t *)next_node->arguments); + if (cast->arguments) { + PM_COMPILE_NOT_POPPED((const pm_node_t *) cast->arguments); } else { - PM_PUTNIL; + PUSH_INSN(ret, location, putnil); } pm_add_ensure_iseq(ret, iseq, 0, scope_node); - ADD_ADJUST(ret, &dummy_line_node, ISEQ_COMPILE_DATA(iseq)->redo_label); - ADD_INSNL(ret, &dummy_line_node, jump, ISEQ_COMPILE_DATA(iseq)->start_label); + PUSH_ADJUST(ret, location, ISEQ_COMPILE_DATA(iseq)->redo_label); + PUSH_INSNL(ret, location, jump, ISEQ_COMPILE_DATA(iseq)->start_label); - ADD_ADJUST_RESTORE(ret, splabel); - PM_PUTNIL_UNLESS_POPPED; + PUSH_ADJUST_RESTORE(ret, splabel); + if (!popped) PUSH_INSN(ret, location, putnil); } else if (ISEQ_COMPILE_DATA(iseq)->end_label && can_add_ensure_iseq(iseq)) { LABEL *splabel = NEW_LABEL(0); - ADD_LABEL(ret, splabel); - ADD_ADJUST(ret, &dummy_line_node, ISEQ_COMPILE_DATA(iseq)->start_label); + PUSH_LABEL(ret, splabel); + PUSH_ADJUST(ret, location, ISEQ_COMPILE_DATA(iseq)->start_label); - if (next_node->arguments) { - PM_COMPILE_NOT_POPPED((pm_node_t *)next_node->arguments); + if (cast->arguments != NULL) { + PM_COMPILE_NOT_POPPED((const pm_node_t *) cast->arguments); } else { - PM_PUTNIL; + PUSH_INSN(ret, location, putnil); } pm_add_ensure_iseq(ret, iseq, 0, scope_node); - ADD_INSNL(ret, &dummy_line_node, jump, ISEQ_COMPILE_DATA(iseq)->end_label); - ADD_ADJUST_RESTORE(ret, splabel); + PUSH_INSNL(ret, location, jump, ISEQ_COMPILE_DATA(iseq)->end_label); + PUSH_ADJUST_RESTORE(ret, splabel); splabel->unremovable = FALSE; - PM_PUTNIL_UNLESS_POPPED; + if (!popped) PUSH_INSN(ret, location, putnil); } else { const rb_iseq_t *ip = iseq; - unsigned long throw_flag = 0; + while (ip) { if (!ISEQ_COMPILE_DATA(ip)) { ip = 0; @@ -6464,36 +6788,53 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, ip = ISEQ_BODY(ip)->parent_iseq; } if (ip != 0) { - if (next_node->arguments) { - PM_COMPILE_NOT_POPPED((pm_node_t *)next_node->arguments); + if (cast->arguments) { + PM_COMPILE_NOT_POPPED((const pm_node_t *) cast->arguments); } else { - PM_PUTNIL; + PUSH_INSN(ret, location, putnil); } - ADD_INSN1(ret, &dummy_line_node, throw, INT2FIX(throw_flag | TAG_NEXT)); - PM_POP_IF_POPPED; + PUSH_INSN1(ret, location, throw, INT2FIX(throw_flag | TAG_NEXT)); + if (popped) PUSH_INSN(ret, location, pop); } else { - rb_raise(rb_eArgError, "Invalid next"); + COMPILE_ERROR(ERROR_ARGS "Invalid next"); return; } } return; } - case PM_NIL_NODE: - PM_PUTNIL_UNLESS_POPPED; + case PM_NIL_NODE: { + // nil + // ^^^ + if (!popped) { + PUSH_INSN(ret, location, putnil); + } + return; + } case PM_NO_KEYWORDS_PARAMETER_NODE: { + // def foo(**nil); end + // ^^^^^ ISEQ_BODY(iseq)->param.flags.accepts_no_kwarg = TRUE; return; } case PM_NUMBERED_REFERENCE_READ_NODE: { + // $1 + // ^^ if (!popped) { - uint32_t reference_number = ((pm_numbered_reference_read_node_t *)node)->number; - ADD_INSN2(ret, &dummy_line_node, getspecial, INT2FIX(1), INT2FIX(reference_number << 1)); + uint32_t reference_number = ((const pm_numbered_reference_read_node_t *) node)->number; + + if (reference_number > 0) { + PUSH_INSN2(ret, location, getspecial, INT2FIX(1), INT2FIX(reference_number << 1)); + } + else { + PUSH_INSN(ret, location, putnil); + } } + return; } case PM_OR_NODE: { @@ -6520,7 +6861,7 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, PM_COMPILE_NOT_POPPED(cast->value); pm_local_index_t index = pm_lookup_local_index(iseq, scope_node, cast->name, 0); - ADD_SETLOCAL(ret, &dummy_line_node, index.index, index.level); + PUSH_SETLOCAL(ret, location, index.index, index.level); return; } @@ -6534,7 +6875,8 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, if (cast->body != NULL) { PM_COMPILE(cast->body); - } else if (!popped) { + } + else if (!popped) { PUSH_INSN(ret, location, putnil); } @@ -6545,27 +6887,38 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, // ^^^^^^^^ const pm_pre_execution_node_t *cast = (const pm_pre_execution_node_t *) node; - DECL_ANCHOR(pre_ex); - INIT_ANCHOR(pre_ex); + LINK_ANCHOR *outer_pre = scope_node->pre_execution_anchor; + RUBY_ASSERT(outer_pre != NULL); + + // BEGIN{} nodes can be nested, so here we're going to do the same thing + // that we did for the top-level compilation where we create two + // anchors and then join them in the correct order into the resulting + // anchor. + DECL_ANCHOR(inner_pre); + INIT_ANCHOR(inner_pre); + scope_node->pre_execution_anchor = inner_pre; + + DECL_ANCHOR(inner_body); + INIT_ANCHOR(inner_body); if (cast->statements != NULL) { const pm_node_list_t *body = &cast->statements->body; + for (size_t index = 0; index < body->size; index++) { - pm_compile_node(iseq, body->nodes[index], pre_ex, true, scope_node); + pm_compile_node(iseq, body->nodes[index], inner_body, true, scope_node); } } if (!popped) { - PUSH_INSN(pre_ex, location, putnil); + PUSH_INSN(inner_body, location, putnil); } - pre_ex->last->next = ret->anchor.next; - ret->anchor.next = pre_ex->anchor.next; - ret->anchor.next->prev = pre_ex->anchor.next; - - if (ret->last == (LINK_ELEMENT *)ret) { - ret->last = pre_ex->last; - } + // Now that everything has been compiled, join both anchors together + // into the correct outer pre execution anchor, and reset the value so + // that subsequent BEGIN{} nodes can be compiled correctly. + PUSH_SEQ(outer_pre, inner_pre); + PUSH_SEQ(outer_pre, inner_body); + scope_node->pre_execution_anchor = outer_pre; return; } @@ -6614,13 +6967,15 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, else { if (cast->left == NULL) { PUSH_INSN(ret, location, putnil); - } else { + } + else { PM_COMPILE(cast->left); } if (cast->right == NULL) { PUSH_INSN(ret, location, putnil); - } else { + } + else { PM_COMPILE(cast->right); } @@ -6702,8 +7057,8 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, // /foo/ // ^^^^^ if (!popped) { - VALUE regex = pm_static_literal_value(node, scope_node); - PUSH_INSN1(ret, location, putobject, regex); + VALUE regexp = pm_static_literal_value(iseq, node, scope_node); + PUSH_INSN1(ret, location, putobject, regexp); } return; } @@ -6728,7 +7083,7 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, if (exceptions->size > 0) { for (size_t index = 0; index < exceptions->size; index++) { - ADD_GETLOCAL(ret, &dummy_line_node, LVAR_ERRINFO, 0); + PUSH_GETLOCAL(ret, location, LVAR_ERRINFO, 0); PM_COMPILE(exceptions->nodes[index]); int checkmatch_flags = VM_CHECKMATCH_TYPE_RESCUE; if (PM_NODE_TYPE_P(exceptions->nodes[index], PM_SPLAT_NODE)) { @@ -6737,8 +7092,9 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, PUSH_INSN1(ret, location, checkmatch, INT2FIX(checkmatch_flags)); PUSH_INSNL(ret, location, branchif, exception_match_label); } - } else { - ADD_GETLOCAL(ret, &dummy_line_node, LVAR_ERRINFO, 0); + } + else { + PUSH_GETLOCAL(ret, location, LVAR_ERRINFO, 0); PUSH_INSN1(ret, location, putobject, rb_eStandardError); PUSH_INSN1(ret, location, checkmatch, INT2FIX(VM_CHECKMATCH_TYPE_RESCUE)); PUSH_INSNL(ret, location, branchif, exception_match_label); @@ -6765,10 +7121,10 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, INIT_ANCHOR(cleanup); pm_compile_target_node(iseq, cast->reference, ret, writes, cleanup, scope_node, NULL); - ADD_GETLOCAL(ret, &dummy_line_node, LVAR_ERRINFO, 0); + PUSH_GETLOCAL(ret, location, LVAR_ERRINFO, 0); - ADD_SEQ(ret, writes); - ADD_SEQ(ret, cleanup); + PUSH_SEQ(ret, writes); + PUSH_SEQ(ret, cleanup); } // If we have statements to execute, we'll compile them here. Otherwise @@ -6781,11 +7137,12 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, LABEL *prev_end = ISEQ_COMPILE_DATA(iseq)->end_label; ISEQ_COMPILE_DATA(iseq)->end_label = NULL; - PM_COMPILE((pm_node_t *) cast->statements); + PM_COMPILE((const pm_node_t *) cast->statements); // Now restore the end_label ISEQ_COMPILE_DATA(iseq)->end_label = prev_end; - } else { + } + else { PUSH_INSN(ret, location, putnil); } @@ -6797,9 +7154,10 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, // subsequent instruction returning the raised error. PUSH_LABEL(ret, rescue_end_label); if (cast->consequent) { - PM_COMPILE((pm_node_t *) cast->consequent); - } else { - ADD_GETLOCAL(ret, &dummy_line_node, 1, 0); + PM_COMPILE((const pm_node_t *) cast->consequent); + } + else { + PUSH_GETLOCAL(ret, location, 1, 0); } return; @@ -6834,8 +7192,8 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, PUSH_LABEL(ret, lcont); if (popped) PUSH_INSN(ret, location, pop); - ADD_CATCH_ENTRY(CATCH_TYPE_RESCUE, lstart, lend, rescue_iseq, lcont); - ADD_CATCH_ENTRY(CATCH_TYPE_RETRY, lend, lcont, NULL, lstart); + PUSH_CATCH_ENTRY(CATCH_TYPE_RESCUE, lstart, lend, rescue_iseq, lcont); + PUSH_CATCH_ENTRY(CATCH_TYPE_RETRY, lend, lcont, NULL, lstart); return; } case PM_RETURN_NODE: { @@ -6905,14 +7263,15 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, PUSH_INSN(ret, location, putnil); PUSH_INSN1(ret, location, throw, INT2FIX(TAG_RETRY)); if (popped) PUSH_INSN(ret, location, pop); - } else { + } + else { COMPILE_ERROR(ERROR_ARGS "Invalid retry"); return; } return; } case PM_SCOPE_NODE: { - pm_scope_node_t *scope_node = (pm_scope_node_t *)node; + pm_scope_node_t *scope_node = (pm_scope_node_t *) node; pm_constant_id_list_t *locals = &scope_node->locals; pm_parameters_node_t *parameters_node = NULL; @@ -6925,12 +7284,17 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, struct rb_iseq_constant_body *body = ISEQ_BODY(iseq); + if (PM_NODE_TYPE_P(scope_node->ast_node, PM_CLASS_NODE)) { + ADD_TRACE(ret, RUBY_EVENT_CLASS); + } + if (scope_node->parameters) { switch (PM_NODE_TYPE(scope_node->parameters)) { case PM_BLOCK_PARAMETERS_NODE: { - pm_block_parameters_node_t *block_parameters_node = (pm_block_parameters_node_t *)scope_node->parameters; - parameters_node = block_parameters_node->parameters; - block_locals = &block_parameters_node->locals; + pm_block_parameters_node_t *cast = (pm_block_parameters_node_t *) scope_node->parameters; + parameters_node = cast->parameters; + block_locals = &cast->locals; + if (parameters_node) { if (parameters_node->rest && PM_NODE_TYPE_P(parameters_node->rest, PM_IMPLICIT_REST_NODE)) { trailing_comma = true; @@ -6943,11 +7307,14 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, break; } case PM_NUMBERED_PARAMETERS_NODE: { - body->param.lead_num = ((pm_numbered_parameters_node_t *) scope_node->parameters)->maximum; + uint32_t maximum = ((const pm_numbered_parameters_node_t *) scope_node->parameters)->maximum; + body->param.lead_num = maximum; + body->param.flags.ambiguous_param0 = maximum == 1; break; } case PM_IT_PARAMETERS_NODE: body->param.lead_num = 1; + body->param.flags.ambiguous_param0 = true; break; default: rb_bug("Unexpected node type for parameters: %s", pm_node_type_to_str(PM_NODE_TYPE(node))); @@ -7025,7 +7392,7 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, if (parameters_node) { if (parameters_node->rest) { if (!(PM_NODE_TYPE_P(parameters_node->rest, PM_IMPLICIT_REST_NODE))) { - if (!((pm_rest_parameter_node_t *)parameters_node->rest)->name || PM_NODE_FLAG_P(parameters_node->rest, PM_PARAMETER_FLAGS_REPEATED_PARAMETER)) { + if (!((const pm_rest_parameter_node_t *) parameters_node->rest)->name || PM_NODE_FLAG_P(parameters_node->rest, PM_PARAMETER_FLAGS_REPEATED_PARAMETER)) { table_size++; } } @@ -7042,7 +7409,7 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, table_size += 4; } else { - pm_keyword_rest_parameter_node_t * kw_rest = (pm_keyword_rest_parameter_node_t *)parameters_node->keyword_rest; + const pm_keyword_rest_parameter_node_t *kw_rest = (const pm_keyword_rest_parameter_node_t *) parameters_node->keyword_rest; // If it's anonymous or repeated, then we need to allocate stack space if (!kw_rest->name || PM_NODE_FLAG_P(kw_rest, PM_PARAMETER_FLAGS_REPEATED_PARAMETER)) { @@ -7074,7 +7441,7 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, } if (parameters_node && parameters_node->block) { - pm_block_parameter_node_t * block_node = (pm_block_parameter_node_t *)parameters_node->block; + const pm_block_parameter_node_t *block_node = (const pm_block_parameter_node_t *) parameters_node->block; if (PM_NODE_FLAG_P(block_node, PM_PARAMETER_FLAGS_REPEATED_PARAMETER) || !block_node->name) { table_size++; @@ -7131,7 +7498,7 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, // def foo(a, (b, *c, d), e = 1, *f, g, (h, *i, j), k:, l: 1, **m, &n) // ^ case PM_REQUIRED_PARAMETER_NODE: { - pm_required_parameter_node_t * param = (pm_required_parameter_node_t *)required; + const pm_required_parameter_node_t *param = (const pm_required_parameter_node_t *) required; if (PM_NODE_FLAG_P(required, PM_PARAMETER_FLAGS_REPEATED_PARAMETER)) { ID local = pm_constant_id_lookup(scope_node, param->name); @@ -7161,7 +7528,7 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, for (size_t i = 0; i < optionals_list->size; i++, local_index++) { pm_node_t * node = optionals_list->nodes[i]; - pm_constant_id_t name = ((pm_optional_parameter_node_t *)node)->name; + pm_constant_id_t name = ((const pm_optional_parameter_node_t *) node)->name; if (PM_NODE_FLAG_P(node, PM_PARAMETER_FLAGS_REPEATED_PARAMETER)) { ID local = pm_constant_id_lookup(scope_node, name); @@ -7184,7 +7551,7 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, body->param.flags.has_rest = true; RUBY_ASSERT(body->param.rest_start != -1); - pm_constant_id_t name = ((pm_rest_parameter_node_t *) parameters_node->rest)->name; + pm_constant_id_t name = ((const pm_rest_parameter_node_t *) parameters_node->rest)->name; if (name) { // def foo(a, (b, *c, d), e = 1, *f, g, (h, *i, j), k:, l: 1, **m, &n) @@ -7273,7 +7640,7 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, // def foo(a, (b, *c, d), e = 1, *f, g, (h, *i, j), k:, l: 1, **m, &n) // ^^ if (PM_NODE_TYPE_P(keyword_parameter_node, PM_REQUIRED_KEYWORD_PARAMETER_NODE)) { - name = ((pm_required_keyword_parameter_node_t *)keyword_parameter_node)->name; + name = ((const pm_required_keyword_parameter_node_t *) keyword_parameter_node)->name; keyword->required_num++; ID local = pm_constant_id_lookup(scope_node, name); @@ -7295,17 +7662,13 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, // def foo(a, (b, *c, d), e = 1, *f, g, (h, *i, j), k:, l: 1, **m, &n) // ^^^^ if (PM_NODE_TYPE_P(keyword_parameter_node, PM_OPTIONAL_KEYWORD_PARAMETER_NODE)) { - pm_optional_keyword_parameter_node_t *cast = ((pm_optional_keyword_parameter_node_t *)keyword_parameter_node); + const pm_optional_keyword_parameter_node_t *cast = ((const pm_optional_keyword_parameter_node_t *) keyword_parameter_node); pm_node_t *value = cast->value; name = cast->name; - if (pm_static_literal_p(value) && - !(PM_NODE_TYPE_P(value, PM_ARRAY_NODE) || - PM_NODE_TYPE_P(value, PM_HASH_NODE) || - PM_NODE_TYPE_P(value, PM_RANGE_NODE))) { - - rb_ary_push(default_values, pm_static_literal_value(value, scope_node)); + if (PM_NODE_FLAG_P(value, PM_NODE_FLAG_STATIC_LITERAL) && !(PM_NODE_TYPE_P(value, PM_ARRAY_NODE) || PM_NODE_TYPE_P(value, PM_HASH_NODE) || PM_NODE_TYPE_P(value, PM_RANGE_NODE))) { + rb_ary_push(default_values, pm_static_literal_value(iseq, value, scope_node)); } else { rb_ary_push(default_values, complex_mark); @@ -7430,7 +7793,7 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, body->param.block_start = local_index; body->param.flags.has_block = true; - pm_constant_id_t name = ((pm_block_parameter_node_t *) parameters_node->block)->name; + pm_constant_id_t name = ((const pm_block_parameter_node_t *) parameters_node->block)->name; if (name) { if (PM_NODE_FLAG_P(parameters_node->block, PM_PARAMETER_FLAGS_REPEATED_PARAMETER)) { @@ -7492,7 +7855,8 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, if (PM_NODE_TYPE_P(scope_node->ast_node, PM_FOR_NODE)) { if (PM_NODE_TYPE_P(((const pm_for_node_t *) scope_node->ast_node)->index, PM_LOCAL_VARIABLE_TARGET_NODE)) { body->param.lead_num++; - } else { + } + else { body->param.rest_start = local_index; body->param.flags.has_rest = true; } @@ -7504,7 +7868,7 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, // Fill in any NumberedParameters, if they exist if (scope_node->parameters && PM_NODE_TYPE_P(scope_node->parameters, PM_NUMBERED_PARAMETERS_NODE)) { - int maximum = ((pm_numbered_parameters_node_t *)scope_node->parameters)->maximum; + int maximum = ((const pm_numbered_parameters_node_t *) scope_node->parameters)->maximum; RUBY_ASSERT(0 < maximum && maximum <= 9); for (int i = 0; i < maximum; i++, local_index++) { const uint8_t param_name[] = { '_', '1' + i }; @@ -7538,7 +7902,7 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, // ^ if (block_locals && block_locals->size) { for (size_t i = 0; i < block_locals->size; i++, local_index++) { - pm_constant_id_t constant_id = ((pm_block_local_variable_node_t *)block_locals->nodes[i])->name; + pm_constant_id_t constant_id = ((const pm_block_local_variable_node_t *) block_locals->nodes[i])->name; pm_insert_local_index(constant_id, local_index, index_lookup_table, local_table_for_iseq, scope_node); } } @@ -7575,7 +7939,7 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, //********STEP 5************ // Goal: compile anything that needed to be compiled if (optionals_list && optionals_list->size) { - LABEL **opt_table = (LABEL **)ALLOC_N(VALUE, optionals_list->size + 1); + LABEL **opt_table = (LABEL **) ALLOC_N(VALUE, optionals_list->size + 1); LABEL *label; // TODO: Should we make an api for NEW_LABEL where you can pass @@ -7585,7 +7949,7 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, for (size_t i = 0; i < optionals_list->size; i++) { label = NEW_LABEL(lineno); opt_table[i] = label; - ADD_LABEL(ret, label); + PUSH_LABEL(ret, label); pm_node_t *optional_node = optionals_list->nodes[i]; PM_COMPILE_NOT_POPPED(optional_node); } @@ -7593,9 +7957,9 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, // Set the last label label = NEW_LABEL(lineno); opt_table[optionals_list->size] = label; - ADD_LABEL(ret, label); + PUSH_LABEL(ret, label); - body->param.opt_table = (const VALUE *)opt_table; + body->param.opt_table = (const VALUE *) opt_table; } if (keywords_list && keywords_list->size) { @@ -7608,25 +7972,21 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, // def foo(a, (b, *c, d), e = 1, *f, g, (h, *i, j), k:, l: 1, **m, &n) // ^^^^ case PM_OPTIONAL_KEYWORD_PARAMETER_NODE: { - pm_optional_keyword_parameter_node_t *cast = ((pm_optional_keyword_parameter_node_t *)keyword_parameter_node); + const pm_optional_keyword_parameter_node_t *cast = ((const pm_optional_keyword_parameter_node_t *) keyword_parameter_node); pm_node_t *value = cast->value; name = cast->name; - if (!(pm_static_literal_p(value)) || - PM_NODE_TYPE_P(value, PM_ARRAY_NODE) || - PM_NODE_TYPE_P(value, PM_HASH_NODE) || - PM_NODE_TYPE_P(value, PM_RANGE_NODE)) { - LABEL *end_label = NEW_LABEL(nd_line(&dummy_line_node)); + if (!PM_NODE_FLAG_P(value, PM_NODE_FLAG_STATIC_LITERAL) || PM_NODE_TYPE_P(value, PM_ARRAY_NODE) || PM_NODE_TYPE_P(value, PM_HASH_NODE) || PM_NODE_TYPE_P(value, PM_RANGE_NODE)) { + LABEL *end_label = NEW_LABEL(location.line); pm_local_index_t index = pm_lookup_local_index(iseq, scope_node, name, 0); int kw_bits_idx = table_size - body->param.keyword->bits_start; - ADD_INSN2(ret, &dummy_line_node, checkkeyword, INT2FIX(kw_bits_idx + VM_ENV_DATA_SIZE - 1), INT2FIX(optional_index)); - ADD_INSNL(ret, &dummy_line_node, branchif, end_label); + PUSH_INSN2(ret, location, checkkeyword, INT2FIX(kw_bits_idx + VM_ENV_DATA_SIZE - 1), INT2FIX(optional_index)); + PUSH_INSNL(ret, location, branchif, end_label); PM_COMPILE(value); - ADD_SETLOCAL(ret, &dummy_line_node, index.index, index.level); - - ADD_LABEL(ret, end_label); + PUSH_SETLOCAL(ret, location, index.index, index.level); + PUSH_LABEL(ret, end_label); } optional_index++; break; @@ -7651,7 +8011,7 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, const pm_node_t *required = requireds_list->nodes[i]; if (PM_NODE_TYPE_P(required, PM_MULTI_TARGET_NODE)) { - ADD_GETLOCAL(ret, &dummy_line_node, table_size - (int)i, 0); + PUSH_GETLOCAL(ret, location, table_size - (int)i, 0); pm_compile_destructured_param_writes(iseq, (const pm_multi_target_node_t *) required, ret, scope_node); } } @@ -7665,7 +8025,7 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, const pm_node_t *post = posts_list->nodes[i]; if (PM_NODE_TYPE_P(post, PM_MULTI_TARGET_NODE)) { - ADD_GETLOCAL(ret, &dummy_line_node, table_size - body->param.post_start - (int) i, 0); + PUSH_GETLOCAL(ret, location, table_size - body->param.post_start - (int) i, 0); pm_compile_destructured_param_writes(iseq, (const pm_multi_target_node_t *) post, ret, scope_node); } } @@ -7675,7 +8035,7 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, case ISEQ_TYPE_BLOCK: { LABEL *start = ISEQ_COMPILE_DATA(iseq)->start_label = NEW_LABEL(0); LABEL *end = ISEQ_COMPILE_DATA(iseq)->end_label = NEW_LABEL(0); - NODE dummy_line_node = generate_dummy_line_node(body->location.first_lineno, -1); + const pm_line_column_t block_location = { .line = body->location.first_lineno, .column = -1 }; start->rescued = LABEL_RESCUE_BEG; end->rescued = LABEL_RESCUE_END; @@ -7688,71 +8048,71 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, pm_compile_for_node_index(iseq, ((const pm_for_node_t *) scope_node->ast_node)->index, ret, scope_node); } - ADD_TRACE(ret, RUBY_EVENT_B_CALL); - PM_NOP; - ADD_LABEL(ret, start); + PUSH_TRACE(ret, RUBY_EVENT_B_CALL); + PUSH_INSN(ret, block_location, nop); + PUSH_LABEL(ret, start); if (scope_node->body != NULL) { switch (PM_NODE_TYPE(scope_node->ast_node)) { case PM_POST_EXECUTION_NODE: { - pm_post_execution_node_t *post_execution_node = (pm_post_execution_node_t *)scope_node->ast_node; - - ADD_INSN1(ret, &dummy_line_node, putspecialobject, INT2FIX(VM_SPECIAL_OBJECT_VMCORE)); + const pm_post_execution_node_t *cast = (const pm_post_execution_node_t *) scope_node->ast_node; + PUSH_INSN1(ret, block_location, putspecialobject, INT2FIX(VM_SPECIAL_OBJECT_VMCORE)); // We create another ScopeNode from the statements within the PostExecutionNode pm_scope_node_t next_scope_node; - pm_scope_node_init((pm_node_t *)post_execution_node->statements, &next_scope_node, scope_node); - const rb_iseq_t *block = NEW_CHILD_ISEQ(&next_scope_node, make_name_for_block(body->parent_iseq), ISEQ_TYPE_BLOCK, lineno); + pm_scope_node_init((const pm_node_t *) cast->statements, &next_scope_node, scope_node); + + const rb_iseq_t *block = NEW_CHILD_ISEQ(&next_scope_node, make_name_for_block(body->parent_iseq), ISEQ_TYPE_BLOCK, location.line); pm_scope_node_destroy(&next_scope_node); - ADD_CALL_WITH_BLOCK(ret, &dummy_line_node, id_core_set_postexe, INT2FIX(0), block); + PUSH_CALL_WITH_BLOCK(ret, block_location, id_core_set_postexe, INT2FIX(0), block); break; } case PM_INTERPOLATED_REGULAR_EXPRESSION_NODE: { - pm_interpolated_regular_expression_node_t *cast = (pm_interpolated_regular_expression_node_t *) scope_node->ast_node; - - int parts_size = pm_interpolated_node_compile(&cast->parts, iseq, dummy_line_node, ret, popped, scope_node); - - ADD_INSN2(ret, &dummy_line_node, toregexp, INT2FIX(pm_reg_flags((pm_node_t *)cast)), INT2FIX(parts_size)); + const pm_interpolated_regular_expression_node_t *cast = (const pm_interpolated_regular_expression_node_t *) scope_node->ast_node; + pm_compile_regexp_dynamic(iseq, (const pm_node_t *) cast, &cast->parts, &location, ret, popped, scope_node); break; } default: pm_compile_node(iseq, scope_node->body, ret, popped, scope_node); break; } - } else { - PM_PUTNIL; + } + else { + PUSH_INSN(ret, block_location, putnil); } - ADD_LABEL(ret, end); - ADD_TRACE(ret, RUBY_EVENT_B_RETURN); + PUSH_LABEL(ret, end); + PUSH_TRACE(ret, RUBY_EVENT_B_RETURN); ISEQ_COMPILE_DATA(iseq)->last_line = body->location.code_location.end_pos.lineno; /* wide range catch handler must put at last */ - ADD_CATCH_ENTRY(CATCH_TYPE_REDO, start, end, NULL, start); - ADD_CATCH_ENTRY(CATCH_TYPE_NEXT, start, end, NULL, end); + PUSH_CATCH_ENTRY(CATCH_TYPE_REDO, start, end, NULL, start); + PUSH_CATCH_ENTRY(CATCH_TYPE_NEXT, start, end, NULL, end); break; } case ISEQ_TYPE_ENSURE: { + const pm_line_column_t statements_location = (scope_node->body != NULL ? PM_NODE_START_LINE_COLUMN(scope_node->parser, scope_node->body) : location); iseq_set_exception_local_table(iseq); - if (scope_node->body) { - PM_COMPILE_POPPED((pm_node_t *)scope_node->body); + if (scope_node->body != NULL) { + PM_COMPILE_POPPED((const pm_node_t *) scope_node->body); } - ADD_GETLOCAL(ret, &dummy_line_node, 1, 0); - ADD_INSN1(ret, &dummy_line_node, throw, INT2FIX(0)); + PUSH_GETLOCAL(ret, statements_location, 1, 0); + PUSH_INSN1(ret, statements_location, throw, INT2FIX(0)); return; } case ISEQ_TYPE_METHOD: { - ADD_TRACE(ret, RUBY_EVENT_CALL); + PUSH_TRACE(ret, RUBY_EVENT_CALL); if (scope_node->body) { - PM_COMPILE((pm_node_t *)scope_node->body); - } else { - PM_PUTNIL; + PM_COMPILE((const pm_node_t *) scope_node->body); + } + else { + PUSH_INSN(ret, location, putnil); } - ADD_TRACE(ret, RUBY_EVENT_RETURN); + PUSH_TRACE(ret, RUBY_EVENT_RETURN); ISEQ_COMPILE_DATA(iseq)->last_line = body->location.code_location.end_pos.lineno; break; @@ -7762,46 +8122,61 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, if (PM_NODE_TYPE_P(scope_node->ast_node, PM_RESCUE_MODIFIER_NODE)) { LABEL *lab = NEW_LABEL(lineno); LABEL *rescue_end = NEW_LABEL(lineno); - ADD_GETLOCAL(ret, &dummy_line_node, LVAR_ERRINFO, 0); - ADD_INSN1(ret, &dummy_line_node, putobject, rb_eStandardError); - ADD_INSN1(ret, &dummy_line_node, checkmatch, INT2FIX(VM_CHECKMATCH_TYPE_RESCUE)); - ADD_INSNL(ret, &dummy_line_node, branchif, lab); - ADD_INSNL(ret, &dummy_line_node, jump, rescue_end); - ADD_LABEL(ret, lab); - PM_COMPILE((pm_node_t *)scope_node->body); - ADD_INSN(ret, &dummy_line_node, leave); - ADD_LABEL(ret, rescue_end); - ADD_GETLOCAL(ret, &dummy_line_node, LVAR_ERRINFO, 0); + PUSH_GETLOCAL(ret, location, LVAR_ERRINFO, 0); + PUSH_INSN1(ret, location, putobject, rb_eStandardError); + PUSH_INSN1(ret, location, checkmatch, INT2FIX(VM_CHECKMATCH_TYPE_RESCUE)); + PUSH_INSNL(ret, location, branchif, lab); + PUSH_INSNL(ret, location, jump, rescue_end); + PUSH_LABEL(ret, lab); + PM_COMPILE((const pm_node_t *) scope_node->body); + PUSH_INSN(ret, location, leave); + PUSH_LABEL(ret, rescue_end); + PUSH_GETLOCAL(ret, location, LVAR_ERRINFO, 0); } else { - PM_COMPILE((pm_node_t *)scope_node->ast_node); + PM_COMPILE((const pm_node_t *) scope_node->ast_node); } - ADD_INSN1(ret, &dummy_line_node, throw, INT2FIX(0)); + PUSH_INSN1(ret, location, throw, INT2FIX(0)); return; } default: if (scope_node->body) { - PM_COMPILE((pm_node_t *)scope_node->body); - } else { - PM_PUTNIL; + PM_COMPILE((const pm_node_t *) scope_node->body); + } + else { + PUSH_INSN(ret, location, putnil); } break; } + if (PM_NODE_TYPE_P(scope_node->ast_node, PM_CLASS_NODE)) { + const pm_line_column_t end_location = PM_NODE_END_LINE_COLUMN(scope_node->parser, scope_node->ast_node); + ADD_TRACE(ret, RUBY_EVENT_END); + ISEQ_COMPILE_DATA(iseq)->last_line = end_location.line; + } + if (!PM_NODE_TYPE_P(scope_node->ast_node, PM_ENSURE_NODE)) { - NODE dummy_line_node = generate_dummy_line_node(ISEQ_COMPILE_DATA(iseq)->last_line, -1); - ADD_INSN(ret, &dummy_line_node, leave); + const pm_line_column_t location = { .line = ISEQ_COMPILE_DATA(iseq)->last_line, .column = -1 }; + PUSH_INSN(ret, location, leave); } + return; } - case PM_SELF_NODE: + case PM_SELF_NODE: { // self // ^^^^ if (!popped) { PUSH_INSN(ret, location, putself); } return; + } + case PM_SHAREABLE_CONSTANT_NODE: { + // A value that is being written to a constant that is being marked as + // shared depending on the current lexical context. + PM_COMPILE(((const pm_shareable_constant_node_t *) node)->write); + return; + } case PM_SINGLETON_CLASS_NODE: { // class << self; end // ^^^^^^^^^^^^^^^^^^ @@ -7828,7 +8203,7 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, // __ENCODING__ // ^^^^^^^^^^^^ if (!popped) { - VALUE value = pm_static_literal_value(node, scope_node); + VALUE value = pm_static_literal_value(iseq, node, scope_node); PUSH_INSN1(ret, location, putobject, value); } return; @@ -7837,8 +8212,18 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, // __FILE__ // ^^^^^^^^ if (!popped) { - VALUE value = pm_static_literal_value(node, scope_node); - PUSH_INSN1(ret, location, putstring, value); + const pm_source_file_node_t *cast = (const pm_source_file_node_t *) node; + VALUE string = pm_source_file_value(cast, scope_node); + + if (PM_NODE_FLAG_P(cast, PM_STRING_FLAGS_FROZEN)) { + PUSH_INSN1(ret, location, putobject, string); + } + else if (PM_NODE_FLAG_P(cast, PM_STRING_FLAGS_MUTABLE)) { + PUSH_INSN1(ret, location, putstring, string); + } + else { + PUSH_INSN1(ret, location, putchilledstring, string); + } } return; } @@ -7846,7 +8231,7 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, // __LINE__ // ^^^^^^^^ if (!popped) { - VALUE value = pm_static_literal_value(node, scope_node); + VALUE value = pm_static_literal_value(iseq, node, scope_node); PUSH_INSN1(ret, location, putobject, value); } return; @@ -7885,14 +8270,17 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, // ^^^^^ if (!popped) { const pm_string_node_t *cast = (const pm_string_node_t *) node; - VALUE value = rb_fstring(parse_string_encoded(scope_node, node, &cast->unescaped)); + VALUE value = parse_static_literal_string(iseq, scope_node, node, &cast->unescaped); if (PM_NODE_FLAG_P(node, PM_STRING_FLAGS_FROZEN)) { PUSH_INSN1(ret, location, putobject, value); } - else { + else if (PM_NODE_FLAG_P(node, PM_STRING_FLAGS_MUTABLE)) { PUSH_INSN1(ret, location, putstring, value); } + else { + PUSH_INSN1(ret, location, putchilledstring, value); + } } return; } @@ -7903,20 +8291,27 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, DECL_ANCHOR(args); INIT_ANCHOR(args); - ISEQ_COMPILE_DATA(iseq)->current_block = NULL; + LABEL *retry_label = NEW_LABEL(location.line); + LABEL *retry_end_l = NEW_LABEL(location.line); + + const rb_iseq_t *previous_block = ISEQ_COMPILE_DATA(iseq)->current_block; + const rb_iseq_t *current_block; + ISEQ_COMPILE_DATA(iseq)->current_block = current_block = NULL; + + PUSH_LABEL(ret, retry_label); PUSH_INSN(ret, location, putself); int flags = 0; struct rb_callinfo_kwarg *keywords = NULL; - int argc = pm_setup_args(cast->arguments, cast->block, &flags, &keywords, iseq, ret, scope_node, dummy_line_node); + int argc = pm_setup_args(cast->arguments, cast->block, &flags, &keywords, iseq, ret, scope_node, &location); flags |= VM_CALL_SUPER | VM_CALL_FCALL; - const rb_iseq_t *parent_block = ISEQ_COMPILE_DATA(iseq)->current_block; if (cast->block && PM_NODE_TYPE_P(cast->block, PM_BLOCK_NODE)) { pm_scope_node_t next_scope_node; pm_scope_node_init(cast->block, &next_scope_node, scope_node); - parent_block = NEW_CHILD_ISEQ(&next_scope_node, make_name_for_block(iseq), ISEQ_TYPE_BLOCK, lineno); + + ISEQ_COMPILE_DATA(iseq)->current_block = current_block = NEW_CHILD_ISEQ(&next_scope_node, make_name_for_block(iseq), ISEQ_TYPE_BLOCK, lineno); pm_scope_node_destroy(&next_scope_node); } @@ -7924,28 +8319,33 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, PUSH_INSN(args, location, splatkw); } - ADD_SEQ(ret, args); - PUSH_INSN2(ret, location, invokesuper, new_callinfo(iseq, 0, argc, flags, keywords, parent_block != NULL), parent_block); - + PUSH_SEQ(ret, args); + PUSH_INSN2(ret, location, invokesuper, new_callinfo(iseq, 0, argc, flags, keywords, current_block != NULL), current_block); + PUSH_LABEL(ret, retry_end_l); if (popped) PUSH_INSN(ret, location, pop); + + ISEQ_COMPILE_DATA(iseq)->current_block = previous_block; + PUSH_CATCH_ENTRY(CATCH_TYPE_BREAK, retry_label, retry_end_l, current_block, retry_end_l); + return; } case PM_SYMBOL_NODE: { // :foo // ^^^^ if (!popped) { - VALUE value = pm_static_literal_value(node, scope_node); + VALUE value = pm_static_literal_value(iseq, node, scope_node); PUSH_INSN1(ret, location, putobject, value); } return; } - case PM_TRUE_NODE: + case PM_TRUE_NODE: { // true // ^^^^ if (!popped) { PUSH_INSN1(ret, location, putobject, Qtrue); } return; + } case PM_UNDEF_NODE: { // undef foo // ^^^^^^^^^ @@ -8006,7 +8406,7 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, // `foo` // ^^^^^ const pm_x_string_node_t *cast = (const pm_x_string_node_t *) node; - VALUE value = parse_string_encoded(scope_node, node, &cast->unescaped); + VALUE value = parse_static_literal_string(iseq, scope_node, node, &cast->unescaped); PUSH_INSN(ret, location, putself); PUSH_INSN1(ret, location, putobject, value); @@ -8037,7 +8437,7 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, struct rb_callinfo_kwarg *keywords = NULL; if (cast->arguments) { - argc = pm_setup_args(cast->arguments, NULL, &flags, &keywords, iseq, ret, scope_node, dummy_line_node); + argc = pm_setup_args(cast->arguments, NULL, &flags, &keywords, iseq, ret, scope_node, &location); } PUSH_INSN1(ret, location, invokeblock, new_callinfo(iseq, 0, argc, flags, keywords, FALSE)); @@ -8051,9 +8451,24 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, if (level > 0) access_outer_variables(iseq, level, rb_intern("yield"), true); return; } - default: + default: { rb_raise(rb_eNotImpError, "node type %s not implemented", pm_node_type_to_str(PM_NODE_TYPE(node))); return; + } + } +} + +/** True if the given iseq can have pre execution blocks. */ +static inline bool +pm_iseq_pre_execution_p(rb_iseq_t *iseq) +{ + switch (ISEQ_BODY(iseq)->type) { + case ISEQ_TYPE_TOP: + case ISEQ_TYPE_EVAL: + case ISEQ_TYPE_MAIN: + return true; + default: + return false; } } @@ -8070,7 +8485,32 @@ pm_iseq_compile_node(rb_iseq_t *iseq, pm_scope_node_t *node) DECL_ANCHOR(ret); INIT_ANCHOR(ret); - pm_compile_node(iseq, (const pm_node_t *) node, ret, false, node); + if (pm_iseq_pre_execution_p(iseq)) { + // Because these ISEQs can have BEGIN{}, we're going to create two + // anchors to compile them, a "pre" and a "body". We'll mark the "pre" + // on the scope node so that when BEGIN{} is found, its contents will be + // added to the "pre" anchor. + DECL_ANCHOR(pre); + INIT_ANCHOR(pre); + node->pre_execution_anchor = pre; + + // Now we'll compile the body as normal. We won't compile directly into + // the "ret" anchor yet because we want to add the "pre" anchor to the + // beginning of the "ret" anchor first. + DECL_ANCHOR(body); + INIT_ANCHOR(body); + pm_compile_node(iseq, (const pm_node_t *) node, body, false, node); + + // Now we'll join both anchors together so that the content is in the + // correct order. + PUSH_SEQ(ret, pre); + PUSH_SEQ(ret, body); + } + else { + // In other circumstances, we can just compile the node directly into + // the "ret" anchor. + pm_compile_node(iseq, (const pm_node_t *) node, ret, false, node); + } CHECK(iseq_setup_insn(iseq, ret)); return iseq_setup(iseq, ret); @@ -8100,7 +8540,7 @@ pm_parse_result_free(pm_parse_result_t *result) * as well. */ static bool -pm_parse_input_error_utf8_p(const pm_parser_t *parser, const pm_location_t *location) +pm_parse_process_error_utf8_p(const pm_parser_t *parser, const pm_location_t *location) { const size_t start_line = pm_newline_list_line_column(&parser->newline_list, location->start, 1).line; const size_t end_line = pm_newline_list_line_column(&parser->newline_list, location->end, 1).line; @@ -8122,45 +8562,93 @@ pm_parse_input_error_utf8_p(const pm_parser_t *parser, const pm_location_t *loca * information as possible about the errors that were encountered. */ static VALUE -pm_parse_input_error(const pm_parse_result_t *result) +pm_parse_process_error(const pm_parse_result_t *result) { - const pm_diagnostic_t *head = (const pm_diagnostic_t *) result->parser.error_list.head; + const pm_parser_t *parser = &result->parser; + const pm_diagnostic_t *head = (const pm_diagnostic_t *) parser->error_list.head; bool valid_utf8 = true; + pm_buffer_t buffer = { 0 }; + const pm_string_t *filepath = &parser->filepath; + for (const pm_diagnostic_t *error = head; error != NULL; error = (const pm_diagnostic_t *) error->node.next) { - // Any errors with the level PM_ERROR_LEVEL_ARGUMENT effectively take - // over as the only argument that gets raised. This is to allow priority - // messages that should be handled before anything else. - if (error->level == PM_ERROR_LEVEL_ARGUMENT) { - return rb_exc_new(rb_eArgError, error->message, strlen(error->message)); - } + switch (error->level) { + case PM_ERROR_LEVEL_SYNTAX: + // It is implicitly assumed that the error messages will be + // encodeable as UTF-8. Because of this, we can't include source + // examples that contain invalid byte sequences. So if any source + // examples include invalid UTF-8 byte sequences, we will skip + // showing source examples entirely. + if (valid_utf8 && !pm_parse_process_error_utf8_p(parser, &error->location)) { + valid_utf8 = false; + } + break; + case PM_ERROR_LEVEL_ARGUMENT: { + // Any errors with the level PM_ERROR_LEVEL_ARGUMENT take over as + // the only argument that gets raised. This is to allow priority + // messages that should be handled before anything else. + int32_t line_number = (int32_t) pm_location_line_number(parser, &error->location); + + pm_buffer_append_format( + &buffer, + "%.*s:%" PRIi32 ": %s", + (int) pm_string_length(filepath), + pm_string_source(filepath), + line_number, + error->message + ); + + if (pm_parse_process_error_utf8_p(parser, &error->location)) { + pm_buffer_append_byte(&buffer, '\n'); + + pm_list_node_t *list_node = (pm_list_node_t *) error; + pm_list_t error_list = { .size = 1, .head = list_node, .tail = list_node }; + + pm_parser_errors_format(parser, &error_list, &buffer, rb_stderr_tty_p(), false); + } + + VALUE value = rb_exc_new(rb_eArgError, pm_buffer_value(&buffer), pm_buffer_length(&buffer)); + pm_buffer_free(&buffer); - // It is implicitly assumed that the error messages will be encodeable - // as UTF-8. Because of this, we can't include source examples that - // contain invalid byte sequences. So if any source examples include - // invalid UTF-8 byte sequences, we will skip showing source examples - // entirely. - if (valid_utf8 && !pm_parse_input_error_utf8_p(&result->parser, &error->location)) { - valid_utf8 = false; + return value; + } + case PM_ERROR_LEVEL_LOAD: { + // Load errors are much simpler, because they don't include any of + // the source in them. We create the error directly from the + // message. + VALUE message = rb_enc_str_new_cstr(error->message, rb_locale_encoding()); + VALUE value = rb_exc_new3(rb_eLoadError, message); + rb_ivar_set(value, rb_intern_const("@path"), Qnil); + return value; + } } } - pm_buffer_t buffer = { 0 }; - pm_buffer_append_string(&buffer, "syntax errors found\n", 20); + pm_buffer_append_format( + &buffer, + "%.*s:%" PRIi32 ": syntax error%s found\n", + (int) pm_string_length(filepath), + pm_string_source(filepath), + (int32_t) pm_location_line_number(parser, &head->location), + (parser->error_list.size > 1) ? "s" : "" + ); if (valid_utf8) { - pm_parser_errors_format(&result->parser, &buffer, rb_stderr_tty_p()); + pm_parser_errors_format(parser, &parser->error_list, &buffer, rb_stderr_tty_p(), true); } else { - const pm_string_t *filepath = &result->parser.filepath; - - for (const pm_diagnostic_t *error = head; error != NULL; error = (pm_diagnostic_t *) error->node.next) { + for (const pm_diagnostic_t *error = head; error != NULL; error = (const pm_diagnostic_t *) error->node.next) { if (error != head) pm_buffer_append_byte(&buffer, '\n'); - pm_buffer_append_format(&buffer, "%.*s:%" PRIi32 ": %s", (int) pm_string_length(filepath), pm_string_source(filepath), (int32_t) pm_location_line_number(&result->parser, &error->location), error->message); + pm_buffer_append_format(&buffer, "%.*s:%" PRIi32 ": %s", (int) pm_string_length(filepath), pm_string_source(filepath), (int32_t) pm_location_line_number(parser, &error->location), error->message); } } VALUE error = rb_exc_new(rb_eSyntaxError, pm_buffer_value(&buffer), pm_buffer_length(&buffer)); + + rb_encoding *filepath_encoding = result->node.filepath_encoding != NULL ? result->node.filepath_encoding : rb_utf8_encoding(); + VALUE path = rb_enc_str_new((const char *) pm_string_source(filepath), pm_string_length(filepath), filepath_encoding); + + rb_ivar_set(error, rb_intern_const("@path"), path); pm_buffer_free(&buffer); return error; @@ -8172,19 +8660,21 @@ pm_parse_input_error(const pm_parse_result_t *result) * result object is zeroed out. */ static VALUE -pm_parse_input(pm_parse_result_t *result, VALUE filepath) +pm_parse_process(pm_parse_result_t *result, pm_node_t *node) { - // Set up the parser and parse the input. - pm_options_filepath_set(&result->options, RSTRING_PTR(filepath)); - RB_GC_GUARD(filepath); - pm_parser_t *parser = &result->parser; - pm_parser_init(parser, pm_string_source(&result->input), pm_string_length(&result->input), &result->options); - const pm_node_t *node = pm_parse(parser); + + // First, set up the scope node so that the AST node is attached and can be + // freed regardless of whether or we return an error. + pm_scope_node_t *scope_node = &result->node; + rb_encoding *filepath_encoding = scope_node->filepath_encoding; + + pm_scope_node_init(node, scope_node, NULL); + scope_node->filepath_encoding = filepath_encoding; // If there are errors, raise an appropriate error and free the result. - if (result->parser.error_list.size > 0) { - VALUE error = pm_parse_input_error(result); + if (parser->error_list.size > 0) { + VALUE error = pm_parse_process_error(result); // TODO: We need to set the backtrace. // rb_funcallv(error, rb_intern("set_backtrace"), 1, &path); @@ -8195,7 +8685,7 @@ pm_parse_input(pm_parse_result_t *result, VALUE filepath) const pm_diagnostic_t *warning; const char *warning_filepath = (const char *) pm_string_source(&parser->filepath); - for (warning = (pm_diagnostic_t *) parser->warning_list.head; warning != NULL; warning = (pm_diagnostic_t *) warning->node.next) { + for (warning = (const pm_diagnostic_t *) parser->warning_list.head; warning != NULL; warning = (const pm_diagnostic_t *) warning->node.next) { int line = pm_location_line_number(parser, &warning->location); if (warning->level == PM_WARNING_LEVEL_VERBOSE) { @@ -8208,9 +8698,6 @@ pm_parse_input(pm_parse_result_t *result, VALUE filepath) // Now set up the constant pool and intern all of the various constants into // their corresponding IDs. - pm_scope_node_t *scope_node = &result->node; - pm_scope_node_init(node, scope_node, NULL); - scope_node->encoding = rb_enc_find(parser->encoding->name); if (!scope_node->encoding) rb_bug("Encoding not found %s!", parser->encoding->name); @@ -8234,6 +8721,30 @@ pm_parse_input(pm_parse_result_t *result, VALUE filepath) return Qnil; } +/** + * Set the frozen_string_literal option based on the default value used by the + * CRuby compiler. + */ +static void +pm_options_frozen_string_literal_init(pm_options_t *options) +{ + int frozen_string_literal = rb_iseq_opt_frozen_string_literal(); + + switch (frozen_string_literal) { + case ISEQ_FROZEN_STRING_LITERAL_UNSET: + break; + case ISEQ_FROZEN_STRING_LITERAL_DISABLED: + pm_options_frozen_string_literal_set(options, false); + break; + case ISEQ_FROZEN_STRING_LITERAL_ENABLED: + pm_options_frozen_string_literal_set(options, true); + break; + default: + rb_bug("pm_options_frozen_string_literal_init: invalid frozen_string_literal=%d", frozen_string_literal); + break; + } +} + /** * Returns an array of ruby String objects that represent the lines of the * source file that the given parser parsed. @@ -8288,6 +8799,7 @@ pm_load_file(pm_parse_result_t *result, VALUE filepath) return err; } + pm_options_frozen_string_literal_init(&result->options); return Qnil; } @@ -8300,7 +8812,13 @@ pm_load_file(pm_parse_result_t *result, VALUE filepath) VALUE pm_parse_file(pm_parse_result_t *result, VALUE filepath) { - VALUE error = pm_parse_input(result, filepath); + pm_options_filepath_set(&result->options, RSTRING_PTR(filepath)); + RB_GC_GUARD(filepath); + + pm_parser_init(&result->parser, pm_string_source(&result->input), pm_string_length(&result->input), &result->options); + pm_node_t *node = pm_parse(&result->parser); + + VALUE error = pm_parse_process(result, node); // If we're parsing a filepath, then we need to potentially support the // SCRIPT_LINES__ constant, which can be a hash that has an array of lines @@ -8335,19 +8853,73 @@ pm_load_parse_file(pm_parse_result_t *result, VALUE filepath) /** * Parse the given source that corresponds to the given filepath and store the - * resulting scope node in the given parse result struct. This function could - * potentially raise a Ruby error. It is assumed that the parse result object is - * zeroed out. + * resulting scope node in the given parse result struct. It is assumed that the + * parse result object is zeroed out. If the string fails to parse, then a Ruby + * error is returned. */ VALUE pm_parse_string(pm_parse_result_t *result, VALUE source, VALUE filepath) { - pm_string_constant_init(&result->input, RSTRING_PTR(source), RSTRING_LEN(source)); - rb_encoding *encoding = rb_enc_get(source); + if (!rb_enc_asciicompat(encoding)) { + return rb_exc_new_cstr(rb_eArgError, "invalid source encoding"); + } + + pm_options_frozen_string_literal_init(&result->options); + pm_string_constant_init(&result->input, RSTRING_PTR(source), RSTRING_LEN(source)); pm_options_encoding_set(&result->options, rb_enc_name(encoding)); - return pm_parse_input(result, filepath); + result->node.filepath_encoding = rb_enc_get(filepath); + pm_options_filepath_set(&result->options, RSTRING_PTR(filepath)); + RB_GC_GUARD(filepath); + + pm_parser_init(&result->parser, pm_string_source(&result->input), pm_string_length(&result->input), &result->options); + pm_node_t *node = pm_parse(&result->parser); + + return pm_parse_process(result, node); +} + +/** + * An implementation of fgets that is suitable for use with Ruby IO objects. + */ +static char * +pm_parse_stdin_fgets(char *string, int size, void *stream) +{ + RUBY_ASSERT(size > 0); + + VALUE line = rb_funcall((VALUE) stream, rb_intern("gets"), 1, INT2FIX(size - 1)); + if (NIL_P(line)) { + return NULL; + } + + const char *cstr = StringValueCStr(line); + size_t length = strlen(cstr); + + memcpy(string, cstr, length); + string[length] = '\0'; + + return string; +} + +/** + * Parse the source off STDIN and store the resulting scope node in the given + * parse result struct. It is assumed that the parse result object is zeroed + * out. If the stream fails to parse, then a Ruby error is returned. + */ +VALUE +pm_parse_stdin(pm_parse_result_t *result) +{ + pm_options_frozen_string_literal_init(&result->options); + + pm_buffer_t buffer; + pm_node_t *node = pm_parse_stream(&result->parser, &buffer, (void *) rb_stdin, pm_parse_stdin_fgets, &result->options); + + // Copy the allocated buffer contents into the input string so that it gets + // freed. At this point we've handed over ownership, so we don't need to + // free the buffer itself. + pm_string_owned_init(&result->input, (uint8_t *) pm_buffer_value(&buffer), pm_buffer_length(&buffer)); + + return pm_parse_process(result, node); } #undef NEW_ISEQ diff --git a/prism_compile.h b/prism_compile.h index d170e1b7291981..e58bed271f2b66 100644 --- a/prism_compile.h +++ b/prism_compile.h @@ -13,6 +13,9 @@ typedef struct pm_local_index_struct { int index, level; } pm_local_index_t; +// A declaration for the struct that lives in compile.c. +struct iseq_link_anchor; + // ScopeNodes are helper nodes, and will never be part of the AST. We manually // declare them here to avoid generating them. typedef struct pm_scope_node { @@ -26,6 +29,13 @@ typedef struct pm_scope_node { const pm_parser_t *parser; rb_encoding *encoding; + /** + * This is the encoding of the actual filepath object that will be used when + * a __FILE__ node is compiled or when the path has to be set on a syntax + * error. + */ + rb_encoding *filepath_encoding; + // The size of the local table // on the iseq which includes // locals and hidden variables @@ -33,6 +43,12 @@ typedef struct pm_scope_node { ID *constants; st_table *index_lookup_table; + + /** + * This will only be set on the top-level scope node. It will contain all of + * the instructions pertaining to BEGIN{} nodes. + */ + struct iseq_link_anchor *pre_execution_anchor; } pm_scope_node_t; void pm_scope_node_init(const pm_node_t *node, pm_scope_node_t *scope, pm_scope_node_t *previous); @@ -40,10 +56,19 @@ void pm_scope_node_destroy(pm_scope_node_t *scope_node); bool *rb_ruby_prism_ptr(void); typedef struct { + /** The parser that will do the actual parsing. */ pm_parser_t parser; + + /** The options that will be passed to the parser. */ pm_options_t options; + + /** The input that represents the source to be parsed. */ pm_string_t input; + + /** The resulting scope node that will hold the generated AST. */ pm_scope_node_t node; + + /** Whether or not this parse result has performed its parsing yet. */ bool parsed; } pm_parse_result_t; @@ -51,6 +76,7 @@ VALUE pm_load_file(pm_parse_result_t *result, VALUE filepath); VALUE pm_parse_file(pm_parse_result_t *result, VALUE filepath); VALUE pm_load_parse_file(pm_parse_result_t *result, VALUE filepath); VALUE pm_parse_string(pm_parse_result_t *result, VALUE source, VALUE filepath); +VALUE pm_parse_stdin(pm_parse_result_t *result); void pm_parse_result_free(pm_parse_result_t *result); rb_iseq_t *pm_iseq_new(pm_scope_node_t *node, VALUE name, VALUE path, VALUE realpath, const rb_iseq_t *parent, enum rb_iseq_type); diff --git a/proc.c b/proc.c index a67fc64a32f8d6..bfde0d9abbb44c 100644 --- a/proc.c +++ b/proc.c @@ -2331,17 +2331,7 @@ rb_obj_define_method(int argc, VALUE *argv, VALUE obj) static VALUE top_define_method(int argc, VALUE *argv, VALUE obj) { - rb_thread_t *th = GET_THREAD(); - VALUE klass; - - klass = th->top_wrapper; - if (klass) { - rb_warning("main.define_method in the wrapped load is effective only in wrapper module"); - } - else { - klass = rb_cObject; - } - return rb_mod_define_method(argc, argv, klass); + return rb_mod_define_method(argc, argv, rb_top_main_class("define_method")); } /* diff --git a/process.c b/process.c index 11e6a2f8175671..aed4e1a08a0ea6 100644 --- a/process.c +++ b/process.c @@ -1452,7 +1452,7 @@ proc_wait(int argc, VALUE *argv) * or as the logical OR of both: * * - Process::WNOHANG: Does not block if no child process is available. - * - Process:WUNTRACED: May return a stopped child process, even if not yet reported. + * - Process::WUNTRACED: May return a stopped child process, even if not yet reported. * * Not all flags are available on all platforms. * @@ -1692,7 +1692,6 @@ after_fork_ruby(rb_pid_t pid) rb_mmtk_respawn_gc_threads(); } #endif - rb_threadptr_pending_interrupt_clear(GET_THREAD()); if (pid == 0) { // child clear_pid_cache(); @@ -3151,9 +3150,13 @@ f_exec(int c, const VALUE *a, VALUE _) UNREACHABLE_RETURN(Qnil); } -#define ERRMSG(str) do { if (errmsg && 0 < errmsg_buflen) strlcpy(errmsg, (str), errmsg_buflen); } while (0) -#define ERRMSG1(str, a) do { if (errmsg && 0 < errmsg_buflen) snprintf(errmsg, errmsg_buflen, (str), (a)); } while (0) -#define ERRMSG2(str, a, b) do { if (errmsg && 0 < errmsg_buflen) snprintf(errmsg, errmsg_buflen, (str), (a), (b)); } while (0) +#define ERRMSG(str) \ + ((errmsg && 0 < errmsg_buflen) ? \ + (void)strlcpy(errmsg, (str), errmsg_buflen) : (void)0) + +#define ERRMSG_FMT(...) \ + ((errmsg && 0 < errmsg_buflen) ? \ + (void)snprintf(errmsg, errmsg_buflen, __VA_ARGS__) : (void)0) static int fd_get_cloexec(int fd, char *errmsg, size_t errmsg_buflen); static int fd_set_cloexec(int fd, char *errmsg, size_t errmsg_buflen); @@ -5265,7 +5268,7 @@ static rb_pid_t ruby_setsid(void) { rb_pid_t pid; - int ret; + int ret, fd; pid = getpid(); #if defined(SETPGRP_VOID) diff --git a/ractor.c b/ractor.c index fbdca947af7efd..7b9c088ceb58a5 100644 --- a/ractor.c +++ b/ractor.c @@ -590,7 +590,7 @@ ractor_check_ints(rb_execution_context_t *ec, rb_ractor_t *cr, ractor_sleep_clea RACTOR_UNLOCK(cr); { if (cf_func) { - int state; + enum ruby_tag_type state; EC_PUSH_TAG(ec); if ((state = EC_EXEC_TAG()) == TAG_NONE) { rb_thread_check_ints(); @@ -1319,7 +1319,7 @@ ractor_try_yield(rb_execution_context_t *ec, rb_ractor_t *cr, struct rb_ractor_q type = basket_type_will; } else { - int state; + enum ruby_tag_type state; // begin EC_PUSH_TAG(ec); @@ -2985,7 +2985,10 @@ rb_obj_traverse(VALUE obj, static int frozen_shareable_p(VALUE obj, bool *made_shareable) { - if (!RB_TYPE_P(obj, T_DATA)) { + if (CHILLED_STRING_P(obj)) { + return false; + } + else if (!RB_TYPE_P(obj, T_DATA)) { return true; } else if (RTYPEDDATA_P(obj)) { @@ -3014,6 +3017,17 @@ make_shareable_check_shareable(VALUE obj) if (rb_ractor_shareable_p(obj)) { return traverse_skip; } + else if (CHILLED_STRING_P(obj)) { + rb_funcall(obj, idFreeze, 0); + + if (UNLIKELY(!RB_OBJ_FROZEN_RAW(obj))) { + rb_raise(rb_eRactorError, "#freeze does not freeze object correctly"); + } + + if (RB_OBJ_SHAREABLE_P(obj)) { + return traverse_skip; + } + } else if (!frozen_shareable_p(obj, &made_shareable)) { if (made_shareable) { return traverse_skip; diff --git a/range.c b/range.c index e9073e53c40eb3..a6bf0fca51eb35 100644 --- a/range.c +++ b/range.c @@ -827,7 +827,12 @@ sym_each_i(VALUE v, VALUE arg) * (1..4).size # => 4 * (1...4).size # => 3 * (1..).size # => Infinity - * ('a'..'z').size #=> nil + * ('a'..'z').size # => nil + * + * If +self+ is not iterable, raises an exception: + * + * (0.5..2.5).size # TypeError + * (..1).size # TypeError * * Related: Range#count. */ @@ -836,7 +841,8 @@ static VALUE range_size(VALUE range) { VALUE b = RANGE_BEG(range), e = RANGE_END(range); - if (rb_obj_is_kind_of(b, rb_cNumeric)) { + + if (RB_INTEGER_TYPE_P(b)) { if (rb_obj_is_kind_of(e, rb_cNumeric)) { return ruby_num_interval_step_size(b, e, INT2FIX(1), EXCL(range)); } @@ -844,10 +850,10 @@ range_size(VALUE range) return DBL2NUM(HUGE_VAL); } } - else if (NIL_P(b)) { - if (rb_obj_is_kind_of(e, rb_cNumeric)) { - return DBL2NUM(HUGE_VAL); - } + + if (!discrete_object_p(b)) { + rb_raise(rb_eTypeError, "can't iterate from %s", + rb_obj_classname(b)); } return Qnil; diff --git a/rjit.c b/rjit.c index 5f8ac4b2bd3e79..72660394b3e1ee 100644 --- a/rjit.c +++ b/rjit.c @@ -153,12 +153,12 @@ rb_rjit_setup_options(const char *s, struct rb_rjit_options *rjit_opt) #define M(shortopt, longopt, desc) RUBY_OPT_MESSAGE(shortopt, longopt, desc) const struct ruby_opt_message rb_rjit_option_messages[] = { - M("--rjit-exec-mem-size=num", "", "Size of executable memory block in MiB (default: " STRINGIZE(DEFAULT_EXEC_MEM_SIZE) ")"), - M("--rjit-call-threshold=num", "", "Number of calls to trigger JIT (default: " STRINGIZE(DEFAULT_CALL_THRESHOLD) ")"), - M("--rjit-stats", "", "Enable collecting RJIT statistics"), - M("--rjit-disable", "", "Disable RJIT for lazily enabling it with RubyVM::RJIT.enable"), - M("--rjit-trace", "", "Allow TracePoint during JIT compilation"), - M("--rjit-trace-exits", "", "Trace side exit locations"), + M("--rjit-exec-mem-size=num", "", "Size of executable memory block in MiB (default: " STRINGIZE(DEFAULT_EXEC_MEM_SIZE) ")."), + M("--rjit-call-threshold=num", "", "Number of calls to trigger JIT (default: " STRINGIZE(DEFAULT_CALL_THRESHOLD) ")."), + M("--rjit-stats", "", "Enable collecting RJIT statistics."), + M("--rjit-disable", "", "Disable RJIT for lazily enabling it with RubyVM::RJIT.enable."), + M("--rjit-trace", "", "Allow TracePoint during JIT compilation."), + M("--rjit-trace-exits", "", "Trace side exit locations."), #ifdef HAVE_LIBCAPSTONE M("--rjit-dump-disasm", "", "Dump all JIT code"), #endif diff --git a/ruby.c b/ruby.c index eb787a0e035c1c..174c7d55d6f6e1 100644 --- a/ruby.c +++ b/ruby.c @@ -157,22 +157,19 @@ enum feature_flag_bits { SEP \ X(parsetree) \ SEP \ - X(parsetree_with_comment) \ - SEP \ X(insns) \ - SEP \ - X(insns_without_opt) \ /* END OF DUMPS */ enum dump_flag_bits { dump_version_v, - dump_error_tolerant, + dump_opt_error_tolerant, + dump_opt_comment, + dump_opt_optimize, EACH_DUMPS(DEFINE_DUMP, COMMA), - dump_error_tolerant_bits = (DUMP_BIT(yydebug) | - DUMP_BIT(parsetree) | - DUMP_BIT(parsetree_with_comment)), dump_exit_bits = (DUMP_BIT(yydebug) | DUMP_BIT(syntax) | - DUMP_BIT(parsetree) | DUMP_BIT(parsetree_with_comment) | - DUMP_BIT(insns) | DUMP_BIT(insns_without_opt)) + DUMP_BIT(parsetree) | DUMP_BIT(insns)), + dump_optional_bits = (DUMP_BIT(opt_error_tolerant) | + DUMP_BIT(opt_comment) | + DUMP_BIT(opt_optimize)) }; static inline void @@ -232,6 +229,7 @@ cmdline_options_init(ruby_cmdline_options_t *opt) #ifdef MMTK_FORCE_ENABLE /* to use with: ./configure cppflags="-DMMTK_FORCE_ENABLE" */ opt->features.set |= FEATURE_BIT(mmtk); #endif + opt->dump |= DUMP_BIT(opt_optimize); opt->backtrace_length_limit = LONG_MIN; return opt; @@ -264,6 +262,7 @@ show_usage_part(const char *str, const unsigned int namelen, const char *sb = highlight ? esc_bold : esc_none; const char *se = highlight ? esc_reset : esc_none; unsigned int desclen = (unsigned int)strcspn(desc, "\n"); + if (!help && desclen > 0 && strchr(".;:", desc[desclen-1])) --desclen; if (help && (namelen + 1 > w) && /* a padding space */ (int)(namelen + secondlen + indent_width) >= columns) { printf(USAGE_INDENT "%s" "%.*s" "%s\n", sb, namelen, str, se); @@ -329,86 +328,89 @@ usage(const char *name, int help, int highlight, int columns) /* This message really ought to be max 23 lines. * Removed -h because the user already knows that option. Others? */ static const struct ruby_opt_message usage_msg[] = { - M("-0[octal]", "", "specify record separator (\\0, if no argument)\n" - "(-00 for paragraph mode, -0777 for slurp mode)"), - M("-a", "", "autosplit mode with -n or -p (splits $_ into $F)"), - M("-c", "", "check syntax only"), - M("-Cdirectory", "", "cd to directory before executing your script"), - M("-d", ", --debug", "set debugging flags (set $DEBUG to true)"), - M("-e 'command'", "", "one line of script. Several -e's allowed. Omit [programfile]"), - M("-Eex[:in]", ", --encoding=ex[:in]", "specify the default external and internal character encodings"), - M("-Fpattern", "", "split() pattern for autosplit (-a)"), - M("-i[extension]", "", "edit ARGV files in place (make backup if extension supplied)"), - M("-Idirectory", "", "specify $LOAD_PATH directory (may be used more than once)"), - M("-l", "", "enable line ending processing"), - M("-n", "", "assume 'while gets(); ... end' loop around your script"), - M("-p", "", "assume loop like -n but print line also like sed"), - M("-rlibrary", "", "require the library before executing your script"), - M("-s", "", "enable some switch parsing for switches after script name"), - M("-S", "", "look for the script using PATH environment variable"), - M("-v", "", "print the version number, then turn on verbose mode"), - M("-w", "", "turn warnings on for your script"), - M("-W[level=2|:category]", "", "set warning level; 0=silence, 1=medium, 2=verbose"), - M("-x[directory]", "", "strip off text before #!ruby line and perhaps cd to directory"), - M("--jit", "", "enable JIT for the platform, same as " PLATFORM_JIT_OPTION), + M("-0[octal]", "", "Set input record separator ($/):\n" + "-0 for \\0; -00 for paragraph mode; -0777 for slurp mode."), + M("-a", "", "Split each input line ($_) into fields ($F)."), + M("-c", "", "Check syntax (no execution)."), + M("-Cdirpath", "", "Execute program in specified directory."), + M("-d", ", --debug", "Set debugging flag ($DEBUG) to true."), + M("-e 'code'", "", "Execute given Ruby code; multiple -e allowed."), + M("-Eex[:in]", ", --encoding=ex[:in]", "Set default external and internal encodings."), + M("-Fpattern", "", "Set input field separator ($;); used with -a."), + M("-i[extension]", "", "Set ARGF in-place mode;\n" + "create backup files with given extension."), + M("-Idirpath", "", "Add specified directory to load paths ($LOAD_PATH);\n" + "multiple -I allowed."), + M("-l", "", "Set output record separator ($\\) to $/;\n" + "used for line-oriented output."), + M("-n", "", "Run program in gets loop."), + M("-p", "", "Like -n, with printing added."), + M("-rlibrary", "", "Require the given library."), + M("-s", "", "Define global variables using switches following program path."), + M("-S", "", "Search directories found in the PATH environment variable."), + M("-v", "", "Print version; set $VERBOSE to true."), + M("-w", "", "Synonym for -W1."), + M("-W[level=2|:category]", "", "Set warning flag ($-W):\n" + "0 for silent; 1 for moderate; 2 for verbose."), + M("-x[dirpath]", "", "Execute Ruby code starting from a #!ruby line."), + M("--jit", "", "Enable JIT for the platform; same as " PLATFORM_JIT_OPTION "."), #if USE_YJIT - M("--yjit", "", "enable in-process JIT compiler"), + M("--yjit", "", "Enable in-process JIT compiler."), #endif #if USE_RJIT - M("--rjit", "", "enable pure-Ruby JIT compiler (experimental)"), + M("--rjit", "", "Enable pure-Ruby JIT compiler (experimental)."), #endif #if USE_MMTK M("--mmtk", "", "use MMTk for garbage collection (experimental)"), #endif - M("-h", "", "show this message, --help for more info"), + M("-h", "", "Print this help message; use --help for longer message."), }; STATIC_ASSERT(usage_msg_size, numberof(usage_msg) < 26); static const struct ruby_opt_message help_msg[] = { - M("--copyright", "", "print the copyright"), - M("--dump={insns|parsetree|...}[,...]", "", - "dump debug information. see below for available dump list"), - M("--enable={jit|rubyopt|...}[,...]", ", --disable={jit|rubyopt|...}[,...]", - "enable or disable features. see below for available features"), - M("--external-encoding=encoding", ", --internal-encoding=encoding", - "specify the default external or internal character encoding"), - M("--parser={parse.y|prism}", ", --parser=prism", - "the parser used to parse Ruby code (experimental)"), - M("--backtrace-limit=num", "", "limit the maximum length of backtrace"), - M("--verbose", "", "turn on verbose mode and disable script from stdin"), - M("--version", "", "print the version number, then exit"), - M("--crash-report=TEMPLATE", "", "template of crash report files"), - M("-y", ", --yydebug", "print log of parser. Backward compatibility is not guaranteed"), - M("--help", "", "show this message, -h for short message"), + M("--backtrace-limit=num", "", "Set backtrace limit."), + M("--copyright", "", "Print Ruby copyright."), + M("--crash-report=template", "", "Set template for crash report file."), + M("--disable=features", "", "Disable features; see list below."), + M("--dump=items", "", "Dump items; see list below."), + M("--enable=features", "", "Enable features; see list below."), + M("--external-encoding=encoding", "", "Set default external encoding."), + M("--help", "", "Print long help message; use -h for short message."), + M("--internal-encoding=encoding", "", "Set default internal encoding."), + M("--parser=parser", "", "Set Ruby parser: parse.y or prism."), + M("--verbose", "", "Set $VERBOSE to true; ignore input from $stdin."), + M("--version", "", "Print Ruby version."), + M("-y", ", --yydebug", "Print parser log; backward compatibility not guaranteed."), }; static const struct ruby_opt_message dumps[] = { - M("insns", "", "instruction sequences"), - M("insns_without_opt", "", "instruction sequences compiled with no optimization"), - M("yydebug(+error-tolerant)", "", "yydebug of yacc parser generator"), - M("parsetree(+error-tolerant)","", "AST"), - M("parsetree_with_comment(+error-tolerant)", "", "AST with comments"), + M("insns", "", "Instruction sequences."), + M("yydebug", "", "yydebug of yacc parser generator."), + M("parsetree", "", "Abstract syntax tree (AST)."), + M("-optimize", "", "Disable optimization (affects insns)."), + M("+error-tolerant", "", "Error-tolerant parsing (affects yydebug, parsetree)."), + M("+comment", "", "Add comments to AST (affects parsetree)."), }; static const struct ruby_opt_message features[] = { - M("gems", "", "rubygems (only for debugging, default: "DEFAULT_RUBYGEMS_ENABLED")"), - M("error_highlight", "", "error_highlight (default: "DEFAULT_RUBYGEMS_ENABLED")"), - M("did_you_mean", "", "did_you_mean (default: "DEFAULT_RUBYGEMS_ENABLED")"), - M("syntax_suggest", "", "syntax_suggest (default: "DEFAULT_RUBYGEMS_ENABLED")"), - M("rubyopt", "", "RUBYOPT environment variable (default: enabled)"), - M("frozen-string-literal", "", "freeze all string literals (default: disabled)"), + M("gems", "", "Rubygems (only for debugging, default: "DEFAULT_RUBYGEMS_ENABLED")."), + M("error_highlight", "", "error_highlight (default: "DEFAULT_RUBYGEMS_ENABLED")."), + M("did_you_mean", "", "did_you_mean (default: "DEFAULT_RUBYGEMS_ENABLED")."), + M("syntax_suggest", "", "syntax_suggest (default: "DEFAULT_RUBYGEMS_ENABLED")."), + M("rubyopt", "", "RUBYOPT environment variable (default: enabled)."), + M("frozen-string-literal", "", "Freeze all string literals (default: disabled)."), #if USE_YJIT - M("yjit", "", "in-process JIT compiler (default: disabled)"), + M("yjit", "", "In-process JIT compiler (default: disabled)."), #endif #if USE_RJIT - M("rjit", "", "pure-Ruby JIT compiler (experimental, default: disabled)"), + M("rjit", "", "Pure-Ruby JIT compiler (experimental, default: disabled)."), #endif #if USE_MMTK M("mmtk", "", "MMTk garbage collection (default: disabled)"), #endif }; static const struct ruby_opt_message warn_categories[] = { - M("deprecated", "", "deprecated features"), - M("experimental", "", "experimental features"), - M("performance", "", "performance issues"), + M("deprecated", "", "Deprecated features."), + M("experimental", "", "Experimental features."), + M("performance", "", "Performance issues."), }; #if USE_RJIT extern const struct ruby_opt_message rb_rjit_option_messages[]; @@ -425,7 +427,7 @@ usage(const char *name, int help, int highlight, int columns) unsigned int w = (columns > 80 ? (columns - 79) / 2 : 0) + 16; #define SHOW(m) show_usage_line(&(m), help, highlight, w, columns) - printf("%sUsage:%s %s [switches] [--] [programfile] [arguments]\n", sb, se, name); + printf("%sUsage:%s %s [options] [--] [filepath] [arguments]\n", sb, se, name); for (i = 0; i < num; ++i) SHOW(usage_msg[i]); @@ -463,39 +465,31 @@ usage(const char *name, int help, int highlight, int columns) #define rubylib_path_new rb_str_new static void -push_include(const char *path, VALUE (*filter)(VALUE)) +ruby_push_include(const char *path, VALUE (*filter)(VALUE)) { const char sep = PATH_SEP_CHAR; const char *p, *s; VALUE load_path = GET_VM()->load_path; - - p = path; - while (*p) { - while (*p == sep) - p++; - if (!*p) break; - for (s = p; *s && *s != sep; s = CharNext(s)); - rb_ary_push(load_path, (*filter)(rubylib_path_new(p, s - p))); - p = s; - } -} - #ifdef __CYGWIN__ -static void -push_include_cygwin(const char *path, VALUE (*filter)(VALUE)) -{ - const char *p, *s; char rubylib[FILENAME_MAX]; VALUE buf = 0; +# define is_path_sep(c) ((c) == sep || (c) == ';') +#else +# define is_path_sep(c) ((c) == sep) +#endif + if (path == 0) return; p = path; while (*p) { - unsigned int len; - while (*p == ';') + long len; + while (is_path_sep(*p)) p++; if (!*p) break; - for (s = p; *s && *s != ';'; s = CharNext(s)); + for (s = p; *s && !is_path_sep(*s); s = CharNext(s)); len = s - p; +#undef is_path_sep + +#ifdef __CYGWIN__ if (*s) { if (!buf) { buf = rb_str_new(p, len); @@ -512,23 +506,14 @@ push_include_cygwin(const char *path, VALUE (*filter)(VALUE)) #else # error no cygwin_conv_path #endif - if (CONV_TO_POSIX_PATH(p, rubylib) == 0) + if (CONV_TO_POSIX_PATH(p, rubylib) == 0) { p = rubylib; - push_include(p, filter); - if (!*s) break; - p = s + 1; - } -} - -#define push_include push_include_cygwin + len = strlen(p); + } #endif - -void -ruby_push_include(const char *path, VALUE (*filter)(VALUE)) -{ - if (path == 0) - return; - push_include(path, filter); + rb_ary_push(load_path, (*filter)(rubylib_path_new(p, len))); + p = s; + } } static VALUE @@ -536,6 +521,7 @@ identical_path(VALUE path) { return path; } + static VALUE locale_path(VALUE path) { @@ -1001,7 +987,7 @@ name_match_p(const char *name, const char *str, size_t len) if (*str != '-' && *str != '_') return 0; while (ISALNUM(*name)) name++; if (*name != '-' && *name != '_') return 0; - ++name; + if (!*++name) return 1; ++str; if (--len == 0) return 1; } @@ -1121,21 +1107,45 @@ memtermspn(const char *str, char term, int len) static const char additional_opt_sep = '+'; static unsigned int -dump_additional_option(const char *str, int len, unsigned int bits, const char *name) +dump_additional_option_flag(const char *str, int len, unsigned int bits, bool set) +{ +#define SET_DUMP_OPT(bit) if (NAME_MATCH_P(#bit, str, len)) { \ + return set ? (bits | DUMP_BIT(opt_ ## bit)) : (bits & ~DUMP_BIT(opt_ ## bit)); \ + } + SET_DUMP_OPT(error_tolerant); + SET_DUMP_OPT(comment); + SET_DUMP_OPT(optimize); +#undef SET_DUMP_OPT + rb_warn("don't know how to dump with%s '%.*s'", set ? "" : "out", len, str); + return bits; +} + +static unsigned int +dump_additional_option(const char *str, int len, unsigned int bits) { int w; for (; len-- > 0 && *str++ == additional_opt_sep; len -= w, str += w) { w = memtermspn(str, additional_opt_sep, len); -#define SET_ADDITIONAL(bit) if (NAME_MATCH_P(#bit, str, w)) { \ - if (bits & DUMP_BIT(bit)) \ - rb_warn("duplicate option to dump %s: '%.*s'", name, w, str); \ - bits |= DUMP_BIT(bit); \ - continue; \ + bool set = true; + if (*str == '-' || *str == '+') { + set = *str++ == '+'; + --w; } - if (dump_error_tolerant_bits & bits) { - SET_ADDITIONAL(error_tolerant); + else { + int n = memtermspn(str, '-', w); + if (str[n] == '-') { + if (NAME_MATCH_P("with", str, n)) { + str += n; + w -= n; + } + else if (NAME_MATCH_P("without", str, n)) { + set = false; + str += n; + w -= n; + } + } } - rb_warn("don't know how to dump %s with '%.*s'", name, w, str); + bits = dump_additional_option_flag(str, w, bits, set); } return bits; } @@ -1144,12 +1154,17 @@ static void dump_option(const char *str, int len, void *arg) { static const char list[] = EACH_DUMPS(LITERAL_NAME_ELEMENT, ", "); + unsigned int *bits_ptr = (unsigned int *)arg; + if (*str == '+' || *str == '-') { + bool set = *str++ == '+'; + *bits_ptr = dump_additional_option_flag(str, --len, *bits_ptr, set); + return; + } int w = memtermspn(str, additional_opt_sep, len); #define SET_WHEN_DUMP(bit) \ - if (NAME_MATCH_P(#bit, (str), (w))) { \ - *(unsigned int *)arg |= \ - dump_additional_option(str + w, len - w, DUMP_BIT(bit), #bit); \ + if (NAME_MATCH_P(#bit "-", (str), (w))) { \ + *bits_ptr = dump_additional_option(str + w, len - w, *bits_ptr | DUMP_BIT(bit)); \ return; \ } EACH_DUMPS(SET_WHEN_DUMP, ;); @@ -2082,12 +2097,13 @@ process_script(ruby_cmdline_options_t *opt) { rb_ast_t *ast; VALUE parser = rb_parser_new(); + const unsigned int dump = opt->dump; - if (opt->dump & DUMP_BIT(yydebug)) { + if (dump & DUMP_BIT(yydebug)) { rb_parser_set_yydebug(parser, Qtrue); } - if (opt->dump & DUMP_BIT(error_tolerant)) { + if ((dump & dump_exit_bits) && (dump & DUMP_BIT(opt_error_tolerant))) { rb_parser_error_tolerant(parser); } @@ -2149,6 +2165,10 @@ prism_script(ruby_cmdline_options_t *opt, pm_parse_result_t *result) pm_options_t *options = &result->options; pm_options_line_set(options, 1); + if (opt->ext.enc.name != 0) { + pm_options_encoding_set(options, StringValueCStr(opt->ext.enc.name)); + } + uint8_t command_line = 0; if (opt->do_split) command_line |= PM_OPTIONS_COMMAND_LINE_A; if (opt->do_line) command_line |= PM_OPTIONS_COMMAND_LINE_L; @@ -2158,7 +2178,11 @@ prism_script(ruby_cmdline_options_t *opt, pm_parse_result_t *result) VALUE error; if (strcmp(opt->script, "-") == 0) { - rb_raise(rb_eRuntimeError, "Prism support for streaming code from stdin is not currently supported"); + pm_options_command_line_set(options, command_line); + pm_options_filepath_set(options, "-"); + + prism_opt_init(opt); + error = pm_parse_stdin(result); } else if (opt->e_script) { command_line |= PM_OPTIONS_COMMAND_LINE_E; @@ -2367,8 +2391,6 @@ process_options(int argc, char **argv, ruby_cmdline_options_t *opt) #ifdef _WIN32 translit_char_bin(RSTRING_PTR(opt->script_name), '\\', '/'); -#elif defined DOSISH - translit_char(RSTRING_PTR(opt->script_name), '\\', '/'); #endif ruby_gc_set_params(); @@ -2526,10 +2548,10 @@ process_options(int argc, char **argv, ruby_cmdline_options_t *opt) if (!dump) return Qtrue; } - if (dump & (DUMP_BIT(parsetree)|DUMP_BIT(parsetree_with_comment))) { + if (dump & DUMP_BIT(parsetree)) { VALUE tree; if (result.ast) { - int comment = dump & DUMP_BIT(parsetree_with_comment); + int comment = opt->dump & DUMP_BIT(opt_comment); tree = rb_parser_dump_tree(result.ast->body.root, comment); } else { @@ -2537,7 +2559,7 @@ process_options(int argc, char **argv, ruby_cmdline_options_t *opt) } rb_io_write(rb_stdout, tree); rb_io_flush(rb_stdout); - dump &= ~DUMP_BIT(parsetree)&~DUMP_BIT(parsetree_with_comment); + dump &= ~DUMP_BIT(parsetree); if (!dump) { dispose_result(); return Qtrue; @@ -2562,7 +2584,7 @@ process_options(int argc, char **argv, ruby_cmdline_options_t *opt) GetBindingPtr(rb_const_get(rb_cObject, rb_intern("TOPLEVEL_BINDING")), toplevel_binding); const struct rb_block *base_block = toplevel_context(toplevel_binding); const rb_iseq_t *parent = vm_block_iseq(base_block); - bool optimize = !(dump & DUMP_BIT(insns_without_opt)); + bool optimize = (opt->dump & DUMP_BIT(opt_optimize)) != 0; if (!result.ast) { pm_parse_result_t *pm = &result.prism; @@ -2576,7 +2598,7 @@ process_options(int argc, char **argv, ruby_cmdline_options_t *opt) } } - if (dump & (DUMP_BIT(insns) | DUMP_BIT(insns_without_opt))) { + if (dump & DUMP_BIT(insns)) { rb_io_write(rb_stdout, rb_iseq_disasm((const rb_iseq_t *)iseq)); rb_io_flush(rb_stdout); dump &= ~DUMP_BIT(insns); @@ -2610,7 +2632,7 @@ struct load_file_arg { VALUE f; }; -VALUE rb_script_lines_for(VALUE path, bool add); +VALUE rb_script_lines_for(VALUE path); static VALUE load_file_internal(VALUE argp_v) @@ -2715,7 +2737,7 @@ load_file_internal(VALUE argp_v) rb_parser_set_options(parser, opt->do_print, opt->do_loop, opt->do_line, opt->do_split); - VALUE lines = rb_script_lines_for(orig_fname, true); + VALUE lines = rb_script_lines_for(orig_fname); if (!NIL_P(lines)) { rb_parser_set_script_lines(parser, lines); } diff --git a/ruby_parser.c b/ruby_parser.c index 8e2371fd1d92dd..40c469f14bb44b 100644 --- a/ruby_parser.c +++ b/ruby_parser.c @@ -93,32 +93,6 @@ dvar_defined(ID id, const void *p) return rb_dvar_defined(id, (const rb_iseq_t *)p); } -static bool -hash_literal_key_p(VALUE k) -{ - switch (OBJ_BUILTIN_TYPE(k)) { - case T_NODE: - return false; - default: - return true; - } -} - -static int -literal_cmp(VALUE val, VALUE lit) -{ - if (val == lit) return 0; - if (!hash_literal_key_p(val) || !hash_literal_key_p(lit)) return -1; - return rb_iseq_cdhash_cmp(val, lit); -} - -static st_index_t -literal_hash(VALUE a) -{ - if (!hash_literal_key_p(a)) return (st_index_t)a; - return rb_iseq_cdhash_hash(a); -} - static int is_usascii_enc(void *enc) { @@ -263,18 +237,6 @@ enc_from_encoding(void *enc) return rb_enc_from_encoding((rb_encoding *)enc); } -static int -encoding_get(VALUE obj) -{ - return ENCODING_GET(obj); -} - -static void -encoding_set(VALUE obj, int encindex) -{ - ENCODING_SET(obj, encindex); -} - static int encoding_is_ascii8bit(VALUE obj) { @@ -335,12 +297,6 @@ rbool(VALUE v) return RBOOL(v); } -static int -undef_p(VALUE v) -{ - return RB_UNDEF_P(v); -} - static int rtest(VALUE obj) { @@ -365,12 +321,6 @@ obj_write(VALUE old, VALUE *slot, VALUE young) return RB_OBJ_WRITE(old, slot, young); } -static VALUE -obj_written(VALUE old, VALUE slot, VALUE young) -{ - return RB_OBJ_WRITTEN(old, slot, young); -} - static VALUE default_rs(void) { @@ -401,18 +351,6 @@ rb_errno_ptr2(void) return rb_errno_ptr(); } -static int -fixnum_p(VALUE obj) -{ - return (int)RB_FIXNUM_P(obj); -} - -static int -symbol_p(VALUE obj) -{ - return (int)RB_SYMBOL_P(obj); -} - static void * zalloc(size_t elemsiz) { @@ -425,24 +363,12 @@ gc_guard(VALUE obj) RB_GC_GUARD(obj); } -static rb_imemo_tmpbuf_t * -tmpbuf_parser_heap(void *buf, rb_imemo_tmpbuf_t *old_heap, size_t cnt) -{ - return rb_imemo_tmpbuf_parser_heap(buf, old_heap, cnt); -} - static VALUE arg_error(void) { return rb_eArgError; } -static VALUE -ruby_vm_frozen_core(void) -{ - return rb_mRubyVMFrozenCore; -} - static rb_ast_t * ast_new(VALUE nb) { @@ -461,10 +387,29 @@ str_coderange_scan_restartable(const char *s, const char *e, void *enc, int *cr) return rb_str_coderange_scan_restartable(s, e, (rb_encoding *)enc, cr); } +static int +enc_mbminlen(void *enc) +{ + return rb_enc_mbminlen((rb_encoding *)enc); +} + +static bool +enc_isascii(OnigCodePoint c, void *enc) +{ + return rb_enc_isascii(c, (rb_encoding *)enc); +} + +static OnigCodePoint +enc_mbc_to_codepoint(const char *p, const char *e, void *enc) +{ + const OnigUChar *up = RBIMPL_CAST((const OnigUChar *)p); + const OnigUChar *ue = RBIMPL_CAST((const OnigUChar *)e); + + return ONIGENC_MBC_TO_CODE((rb_encoding *)enc, up, ue); +} + VALUE rb_io_gets_internal(VALUE io); extern VALUE rb_eArgError; -extern VALUE rb_mRubyVMFrozenCore; -VALUE rb_node_case_when_optimizable_literal(const NODE *const node); static const rb_parser_config_t rb_global_parser_config = { .malloc = ruby_xmalloc, @@ -479,27 +424,17 @@ static const rb_parser_config_t rb_global_parser_config = { .nonempty_memcpy = nonempty_memcpy, .xmalloc_mul_add = rb_xmalloc_mul_add, - .tmpbuf_parser_heap = tmpbuf_parser_heap, .ast_new = ast_new, .compile_callback = rb_suppress_tracing, .reg_named_capture_assign = reg_named_capture_assign, - .obj_freeze = rb_obj_freeze, - .obj_hide = rb_obj_hide, - .obj_freeze_raw = OBJ_FREEZE_RAW, - - .fixnum_p = fixnum_p, - .symbol_p = symbol_p, - .attr_get = rb_attr_get, .ary_new = rb_ary_new, .ary_push = rb_ary_push, .ary_new_from_args = rb_ary_new_from_args, .ary_unshift = rb_ary_unshift, - .ary_new2 = rb_ary_new2, - .ary_clear = rb_ary_clear, .ary_modify = rb_ary_modify, .array_len = rb_array_len, .array_aref = RARRAY_AREF, @@ -547,14 +482,6 @@ static const rb_parser_config_t rb_global_parser_config = { .filesystem_str_new_cstr = rb_filesystem_str_new_cstr, .obj_as_string = rb_obj_as_string, - .hash_clear = rb_hash_clear, - .hash_new = rb_hash_new, - .hash_aset = rb_hash_aset, - .hash_delete = rb_hash_delete, - .hash_lookup = rb_hash_lookup, - .ident_hash_new = rb_ident_hash_new, - - .num2int = rb_num2int_inline, .int2num = rb_int2num_inline, .stderr_tty_p = rb_stderr_tty_p, @@ -582,8 +509,6 @@ static const rb_parser_config_t rb_global_parser_config = { .ascii8bit_encoding = ascii8bit_encoding, .enc_codelen = enc_codelen, .enc_mbcput = enc_mbcput, - .char_to_option_kcode = rb_char_to_option_kcode, - .ascii8bit_encindex = rb_ascii8bit_encindex, .enc_find_index = rb_enc_find_index, .enc_from_index = enc_from_index, .enc_associate_index = rb_enc_associate_index, @@ -592,19 +517,16 @@ static const rb_parser_config_t rb_global_parser_config = { .enc_coderange_unknown = ENC_CODERANGE_UNKNOWN, .enc_compatible = enc_compatible, .enc_from_encoding = enc_from_encoding, - .encoding_get = encoding_get, - .encoding_set = encoding_set, .encoding_is_ascii8bit = encoding_is_ascii8bit, .usascii_encoding = usascii_encoding, - - .ractor_make_shareable = rb_ractor_make_shareable, + .enc_coderange_broken = ENC_CODERANGE_BROKEN, + .enc_mbminlen = enc_mbminlen, + .enc_isascii = enc_isascii, + .enc_mbc_to_codepoint = enc_mbc_to_codepoint, .local_defined = local_defined, .dvar_defined = dvar_defined, - .literal_cmp = literal_cmp, - .literal_hash = literal_hash, - .syntax_error_append = syntax_error_append, .raise = rb_raise, .syntax_error_new = syntax_error_new, @@ -617,11 +539,9 @@ static const rb_parser_config_t rb_global_parser_config = { .sized_xfree = ruby_sized_xfree, .sized_realloc_n = ruby_sized_realloc_n, .obj_write = obj_write, - .obj_written = obj_written, .gc_guard = gc_guard, .gc_mark = rb_gc_mark, .gc_mark_and_move = rb_gc_mark_and_move, - .gc_location = rb_gc_location, .reg_compile = rb_reg_compile, .reg_check_preprocess = rb_reg_check_preprocess, @@ -642,24 +562,25 @@ static const rb_parser_config_t rb_global_parser_config = { .strtod = ruby_strtod, .rbool = rbool, - .undef_p = undef_p, .rtest = rtest, .nil_p = nil_p, .qnil = Qnil, .qtrue = Qtrue, .qfalse = Qfalse, - .qundef = Qundef, .eArgError = arg_error, - .mRubyVMFrozenCore = ruby_vm_frozen_core, .long2int = rb_long2int, - .node_case_when_optimizable_literal = rb_node_case_when_optimizable_literal, - /* For Ripper */ .static_id2sym = static_id2sym, .str_coderange_scan_restartable = str_coderange_scan_restartable, }; +const rb_parser_config_t * +rb_ruby_parser_config(void) +{ + return &rb_global_parser_config; +} + rb_parser_t * rb_parser_params_allocate(void) { @@ -820,6 +741,14 @@ rb_parser_set_yydebug(VALUE vparser, VALUE flag) VALUE rb_str_new_parser_string(rb_parser_string_t *str) +{ + VALUE string = rb_enc_interned_str(str->ptr, str->len, str->enc); + rb_enc_str_coderange(string); + return string; +} + +VALUE +rb_str_new_mutable_parser_string(rb_parser_string_t *str) { return rb_enc_str_new(str->ptr, str->len, str->enc); } @@ -1017,52 +946,14 @@ rb_node_encoding_val(const NODE *node) } VALUE -rb_node_const_decl_val(const NODE *node) -{ - VALUE path; - switch (nd_type(node)) { - case NODE_CDECL: - if (RNODE_CDECL(node)->nd_vid) { - path = rb_id2str(RNODE_CDECL(node)->nd_vid); - goto end; - } - else { - node = RNODE_CDECL(node)->nd_else; - } - break; - case NODE_COLON2: - break; - case NODE_COLON3: - // ::Const - path = rb_str_new_cstr("::"); - rb_str_append(path, rb_id2str(RNODE_COLON3(node)->nd_mid)); - goto end; - default: - rb_bug("unexpected node: %s", ruby_node_name(nd_type(node))); - UNREACHABLE_RETURN(0); - } - - path = rb_ary_new(); - if (node) { - for (; node && nd_type_p(node, NODE_COLON2); node = RNODE_COLON2(node)->nd_head) { - rb_ary_push(path, rb_id2str(RNODE_COLON2(node)->nd_mid)); - } - if (node && nd_type_p(node, NODE_CONST)) { - // Const::Name - rb_ary_push(path, rb_id2str(RNODE_CONST(node)->nd_vid)); - } - else if (node && nd_type_p(node, NODE_COLON3)) { - // ::Const::Name - rb_ary_push(path, rb_id2str(RNODE_COLON3(node)->nd_mid)); - rb_ary_push(path, rb_str_new(0, 0)); - } - else { - // expression::Name - rb_ary_push(path, rb_str_new_cstr("...")); - } - path = rb_ary_join(rb_ary_reverse(path), rb_str_new_cstr("::")); - } - end: - path = rb_fstring(path); - return path; +rb_script_lines_for(VALUE path) +{ + VALUE hash, lines; + ID script_lines; + CONST_ID(script_lines, "SCRIPT_LINES__"); + if (!rb_const_defined_at(rb_cObject, script_lines)) return Qnil; + hash = rb_const_get_at(rb_cObject, script_lines); + if (!RB_TYPE_P(hash, T_HASH)) return Qnil; + rb_hash_aset(hash, path, lines = rb_ary_new()); + return lines; } diff --git a/rubyparser.h b/rubyparser.h index 34ee117f650974..5ba2da8de11f98 100644 --- a/rubyparser.h +++ b/rubyparser.h @@ -34,6 +34,18 @@ #endif #endif +#if defined(__GNUC__) +# if defined(__MINGW_PRINTF_FORMAT) +# define RUBYPARSER_ATTRIBUTE_FORMAT(string_index, argument_index) __attribute__((format(__MINGW_PRINTF_FORMAT, string_index, argument_index))) +# else +# define RUBYPARSER_ATTRIBUTE_FORMAT(string_index, argument_index) __attribute__((format(printf, string_index, argument_index))) +# endif +#elif defined(__clang__) +# define RUBYPARSER_ATTRIBUTE_FORMAT(string_index, argument_index) __attribute__((__format__(__printf__, string_index, argument_index))) +#else +# define RUBYPARSER_ATTRIBUTE_FORMAT(string_index, argument_index) +#endif + /* * Parser String */ @@ -54,6 +66,13 @@ typedef struct rb_parser_string { char *ptr; } rb_parser_string_t; +enum rb_parser_shareability { + rb_parser_shareable_none, + rb_parser_shareable_literal, + rb_parser_shareable_copy, + rb_parser_shareable_everything, +}; + /* * AST Node */ @@ -117,7 +136,6 @@ enum node_type { NODE_MATCH, NODE_MATCH2, NODE_MATCH3, - NODE_LIT, NODE_INTEGER, NODE_FLOAT, NODE_RATIONAL, @@ -188,6 +206,24 @@ typedef struct rb_code_location_struct { rb_code_position_t beg_pos; rb_code_position_t end_pos; } rb_code_location_t; +#define YYLTYPE rb_code_location_t +#define YYLTYPE_IS_DECLARED 1 + +typedef struct rb_parser_ast_token { + int id; + const char *type_name; + rb_parser_string_t *str; + rb_code_location_t loc; +} rb_parser_ast_token_t; + +/* + * Array-like object for parser + */ +typedef struct rb_parser_ary { + rb_parser_ast_token_t **data; + long len; // current size + long capa; // capacity +} rb_parser_ary_t; /* Header part of AST Node */ typedef struct RNode { @@ -414,6 +450,7 @@ typedef struct RNode_CDECL { ID nd_vid; struct RNode *nd_value; struct RNode *nd_else; + enum rb_parser_shareability shareability; } rb_node_cdecl_t; typedef struct RNode_CVASGN { @@ -462,6 +499,7 @@ typedef struct RNode_OP_CDECL { struct RNode *nd_head; struct RNode *nd_value; ID nd_aid; + enum rb_parser_shareability shareability; } rb_node_op_cdecl_t; typedef struct RNode_CALL { @@ -636,16 +674,10 @@ typedef struct RNode_MATCH3 { struct RNode *nd_value; } rb_node_match3_t; -typedef struct RNode_LIT { - NODE node; - - VALUE nd_lit; -} rb_node_lit_t; - typedef struct RNode_INTEGER { NODE node; - char* val; + char *val; int minus; int base; } rb_node_integer_t; @@ -653,14 +685,14 @@ typedef struct RNode_INTEGER { typedef struct RNode_FLOAT { NODE node; - char* val; + char *val; int minus; } rb_node_float_t; typedef struct RNode_RATIONAL { NODE node; - char* val; + char *val; int minus; int base; int seen_point; @@ -675,7 +707,7 @@ enum rb_numeric_type { typedef struct RNode_IMAGINARY { NODE node; - char* val; + char *val; int minus; int base; int seen_point; @@ -773,7 +805,7 @@ typedef struct RNode_ARGS_AUX { NODE node; ID nd_pid; - long nd_plen; + int nd_plen; struct RNode *nd_next; } rb_node_args_aux_t; @@ -1092,7 +1124,6 @@ typedef struct RNode_ERROR { #define RNODE_MATCH(node) ((struct RNode_MATCH *)(node)) #define RNODE_MATCH2(node) ((struct RNode_MATCH2 *)(node)) #define RNODE_MATCH3(node) ((struct RNode_MATCH3 *)(node)) -#define RNODE_LIT(node) ((struct RNode_LIT *)(node)) #define RNODE_INTEGER(node) ((struct RNode_INTEGER *)(node)) #define RNODE_FLOAT(node) ((struct RNode_FLOAT *)(node)) #define RNODE_RATIONAL(node) ((struct RNode_RATIONAL *)(node)) @@ -1147,7 +1178,7 @@ typedef struct RNode_ERROR { #define RNODE_ENCODING(node) ((struct RNode_ENCODING *)(node)) /* FL : 0..4: T_TYPES, 5: KEEP_WB, 6: PROMOTED, 7: FINALIZE, 8: UNUSED, 9: UNUSED, 10: EXIVAR, 11: FREEZE */ -/* NODE_FL: 0..4: T_TYPES, 5: KEEP_WB, 6: PROMOTED, 7: NODE_FL_NEWLINE, +/* NODE_FL: 0..4: UNUSED, 5: UNUSED, 6: UNUSED, 7: NODE_FL_NEWLINE, * 8..14: nd_type, * 15..: nd_line */ @@ -1211,21 +1242,12 @@ typedef struct rb_parser_config_struct { void *(*xmalloc_mul_add)(size_t x, size_t y, size_t z); /* imemo */ - rb_imemo_tmpbuf_t *(*tmpbuf_parser_heap)(void *buf, rb_imemo_tmpbuf_t *old_heap, size_t cnt); rb_ast_t *(*ast_new)(VALUE nb); // VALUE rb_suppress_tracing(VALUE (*func)(VALUE), VALUE arg); VALUE (*compile_callback)(VALUE (*func)(VALUE), VALUE arg); NODE *(*reg_named_capture_assign)(struct parser_params* p, VALUE regexp, const rb_code_location_t *loc); - /* Object */ - VALUE (*obj_freeze)(VALUE obj); - VALUE (*obj_hide)(VALUE obj); - void (*obj_freeze_raw)(VALUE obj); - - int (*fixnum_p)(VALUE); - int (*symbol_p)(VALUE); - /* Variable */ VALUE (*attr_get)(VALUE obj, ID id); @@ -1234,8 +1256,6 @@ typedef struct rb_parser_config_struct { VALUE (*ary_push)(VALUE ary, VALUE elem); VALUE (*ary_new_from_args)(long n, ...); VALUE (*ary_unshift)(VALUE ary, VALUE item); - VALUE (*ary_new2)(long capa); // ary_new_capa - VALUE (*ary_clear)(VALUE ary); void (*ary_modify)(VALUE ary); long (*array_len)(VALUE a); VALUE (*array_aref)(VALUE, long); @@ -1288,16 +1308,7 @@ typedef struct rb_parser_config_struct { VALUE (*filesystem_str_new_cstr)(const char *ptr); VALUE (*obj_as_string)(VALUE); - /* Hash */ - VALUE (*hash_clear)(VALUE hash); - VALUE (*hash_new)(void); - VALUE (*hash_aset)(VALUE hash, VALUE key, VALUE val); - VALUE (*hash_delete)(VALUE hash, VALUE key); - VALUE (*hash_lookup)(VALUE hash, VALUE key); - VALUE (*ident_hash_new)(void); - /* Numeric */ - int (*num2int)(VALUE val); VALUE (*int2num)(int v); /* IO */ @@ -1328,21 +1339,18 @@ typedef struct rb_parser_config_struct { rb_encoding *(*ascii8bit_encoding)(void); int (*enc_codelen)(int c, rb_encoding *enc); int (*enc_mbcput)(unsigned int c, void *buf, rb_encoding *enc); - int (*char_to_option_kcode)(int c, int *option, int *kcode); - int (*ascii8bit_encindex)(void); int (*enc_find_index)(const char *name); rb_encoding *(*enc_from_index)(int idx); VALUE (*enc_associate_index)(VALUE obj, int encindex); int (*enc_isspace)(OnigCodePoint c, rb_encoding *enc); rb_encoding *(*enc_compatible)(VALUE str1, VALUE str2); VALUE (*enc_from_encoding)(rb_encoding *enc); - int (*encoding_get)(VALUE obj); - void (*encoding_set)(VALUE obj, int encindex); int (*encoding_is_ascii8bit)(VALUE obj); rb_encoding *(*usascii_encoding)(void); - - /* Ractor */ - VALUE (*ractor_make_shareable)(VALUE obj); + int enc_coderange_broken; + int (*enc_mbminlen)(rb_encoding *enc); + bool (*enc_isascii)(OnigCodePoint c, rb_encoding *enc); + OnigCodePoint (*enc_mbc_to_codepoint)(const char *p, const char *e, rb_encoding *enc); /* Compile */ // int rb_local_defined(ID id, const rb_iseq_t *iseq); @@ -1350,10 +1358,6 @@ typedef struct rb_parser_config_struct { // int rb_dvar_defined(ID id, const rb_iseq_t *iseq); int (*dvar_defined)(ID, const void*); - /* Compile (parse.y) */ - int (*literal_cmp)(VALUE val, VALUE lit); - parser_st_index_t (*literal_hash)(VALUE a); - /* Error (Exception) */ RBIMPL_ATTR_FORMAT(RBIMPL_PRINTF_FORMAT, 6, 0) VALUE (*syntax_error_append)(VALUE, VALUE, int, int, rb_encoding*, const char*, va_list); @@ -1371,11 +1375,9 @@ typedef struct rb_parser_config_struct { void (*sized_xfree)(void *x, size_t size); void *(*sized_realloc_n)(void *ptr, size_t new_count, size_t element_size, size_t old_count); VALUE (*obj_write)(VALUE, VALUE *, VALUE); - VALUE (*obj_written)(VALUE, VALUE, VALUE); void (*gc_guard)(VALUE); void (*gc_mark)(VALUE); void (*gc_mark_and_move)(VALUE *ptr); - VALUE (*gc_location)(VALUE value); /* Re */ VALUE (*reg_compile)(VALUE str, int options, const char *sourcefile, int sourceline); @@ -1383,10 +1385,10 @@ typedef struct rb_parser_config_struct { int (*memcicmp)(const void *x, const void *y, long len); /* Error */ - void (*compile_warn)(const char *file, int line, const char *fmt, ...); - void (*compile_warning)(const char *file, int line, const char *fmt, ...); - void (*bug)(const char *fmt, ...); - void (*fatal)(const char *fmt, ...); + void (*compile_warn)(const char *file, int line, const char *fmt, ...) RUBYPARSER_ATTRIBUTE_FORMAT(3, 4); + void (*compile_warning)(const char *file, int line, const char *fmt, ...) RUBYPARSER_ATTRIBUTE_FORMAT(3, 4); + void (*bug)(const char *fmt, ...) RUBYPARSER_ATTRIBUTE_FORMAT(1, 2); + void (*fatal)(const char *fmt, ...) RUBYPARSER_ATTRIBUTE_FORMAT(1, 2); VALUE (*verbose)(void); int *(*errno_ptr)(void); @@ -1401,19 +1403,14 @@ typedef struct rb_parser_config_struct { /* Misc */ VALUE (*rbool)(VALUE); - int (*undef_p)(VALUE); int (*rtest)(VALUE obj); int (*nil_p)(VALUE obj); VALUE qnil; VALUE qtrue; VALUE qfalse; - VALUE qundef; VALUE (*eArgError)(void); - VALUE (*mRubyVMFrozenCore)(void); int (*long2int)(long); - VALUE (*node_case_when_optimizable_literal)(const NODE *const node); - /* For Ripper */ int enc_coderange_7bit; int enc_coderange_unknown; diff --git a/shape.c b/shape.c index 06d0f2135d53cb..1158aad52c5f11 100644 --- a/shape.c +++ b/shape.c @@ -43,7 +43,6 @@ static ID id_frozen; static ID id_t_object; -static ID size_pool_edge_names[SIZE_POOL_COUNT]; #define LEAF 0 #define BLACK 0x0 @@ -474,11 +473,11 @@ rb_shape_alloc_new_child(ID id, rb_shape_t * shape, enum shape_type shape_type) } break; case SHAPE_FROZEN: - case SHAPE_T_OBJECT: new_shape->next_iv_index = shape->next_iv_index; break; case SHAPE_OBJ_TOO_COMPLEX: case SHAPE_ROOT: + case SHAPE_T_OBJECT: rb_bug("Unreachable"); break; } @@ -864,7 +863,13 @@ rb_shape_get_iv_index(rb_shape_t *shape, ID id, attr_index_t *value) RUBY_ASSERT(rb_shape_id(shape) != OBJ_TOO_COMPLEX_SHAPE_ID); if (!shape_cache_get_iv_index(shape, id, value)) { - return shape_get_iv_index(shape, id, value); + // If it wasn't in the ancestor cache, then don't do a linear search + if (shape->ancestor_index && shape->next_iv_index >= ANCESTOR_CACHE_THRESHOLD) { + return false; + } + else { + return shape_get_iv_index(shape, id, value); + } } return true; @@ -1243,11 +1248,6 @@ Init_default_shapes(void) } #endif - // Shapes by size pool - for (int i = 0; i < SIZE_POOL_COUNT; i++) { - size_pool_edge_names[i] = rb_make_internal_id(); - } - // Root shape rb_shape_t *root = rb_shape_alloc_with_parent_id(0, INVALID_SHAPE_ID); root->capacity = 0; @@ -1256,42 +1256,33 @@ Init_default_shapes(void) GET_SHAPE_TREE()->root_shape = root; RUBY_ASSERT(rb_shape_id(GET_SHAPE_TREE()->root_shape) == ROOT_SHAPE_ID); - // Shapes by size pool - for (int i = 1; i < SIZE_POOL_COUNT; i++) { - rb_shape_t *new_shape = rb_shape_alloc_with_parent_id(0, INVALID_SHAPE_ID); - new_shape->type = SHAPE_ROOT; - new_shape->size_pool_index = i; - new_shape->ancestor_index = LEAF; - RUBY_ASSERT(rb_shape_id(new_shape) == (shape_id_t)i); - } - - // Make shapes for T_OBJECT - for (int i = 0; i < SIZE_POOL_COUNT; i++) { - rb_shape_t * shape = rb_shape_get_shape_by_id(i); - bool dont_care; - rb_shape_t * t_object_shape = - get_next_shape_internal(shape, id_t_object, SHAPE_T_OBJECT, &dont_care, true); - t_object_shape->capacity = (uint32_t)((rb_size_pool_slot_size(i) - offsetof(struct RObject, as.ary)) / sizeof(VALUE)); - t_object_shape->edges = rb_id_table_create(0); - t_object_shape->ancestor_index = LEAF; - RUBY_ASSERT(rb_shape_id(t_object_shape) == (shape_id_t)(i + SIZE_POOL_COUNT)); - } - bool dont_care; // Special const shape #if RUBY_DEBUG - rb_shape_t * special_const_shape = + rb_shape_t *special_const_shape = #endif get_next_shape_internal(root, (ID)id_frozen, SHAPE_FROZEN, &dont_care, true); RUBY_ASSERT(rb_shape_id(special_const_shape) == SPECIAL_CONST_SHAPE_ID); RUBY_ASSERT(SPECIAL_CONST_SHAPE_ID == (GET_SHAPE_TREE()->next_shape_id - 1)); RUBY_ASSERT(rb_shape_frozen_shape_p(special_const_shape)); - rb_shape_t * hash_fallback_shape = rb_shape_alloc_with_parent_id(0, ROOT_SHAPE_ID); - hash_fallback_shape->type = SHAPE_OBJ_TOO_COMPLEX; - hash_fallback_shape->size_pool_index = 0; + rb_shape_t *too_complex_shape = rb_shape_alloc_with_parent_id(0, ROOT_SHAPE_ID); + too_complex_shape->type = SHAPE_OBJ_TOO_COMPLEX; + too_complex_shape->size_pool_index = 0; RUBY_ASSERT(OBJ_TOO_COMPLEX_SHAPE_ID == (GET_SHAPE_TREE()->next_shape_id - 1)); - RUBY_ASSERT(rb_shape_id(hash_fallback_shape) == OBJ_TOO_COMPLEX_SHAPE_ID); + RUBY_ASSERT(rb_shape_id(too_complex_shape) == OBJ_TOO_COMPLEX_SHAPE_ID); + + // Make shapes for T_OBJECT + size_t *sizes = rb_gc_size_pool_sizes(); + for (int i = 0; sizes[i] > 0; i++) { + rb_shape_t *t_object_shape = rb_shape_alloc_with_parent_id(0, INVALID_SHAPE_ID); + t_object_shape->type = SHAPE_T_OBJECT; + t_object_shape->size_pool_index = i; + t_object_shape->capacity = (uint32_t)((sizes[i] - offsetof(struct RObject, as.ary)) / sizeof(VALUE)); + t_object_shape->edges = rb_id_table_create(0); + t_object_shape->ancestor_index = LEAF; + RUBY_ASSERT(rb_shape_id(t_object_shape) == (shape_id_t)(i + FIRST_T_OBJECT_SHAPE_ID)); + } } void @@ -1320,6 +1311,7 @@ Init_shape(void) rb_define_const(rb_cShape, "SHAPE_FLAG_SHIFT", INT2NUM(SHAPE_FLAG_SHIFT)); rb_define_const(rb_cShape, "SPECIAL_CONST_SHAPE_ID", INT2NUM(SPECIAL_CONST_SHAPE_ID)); rb_define_const(rb_cShape, "OBJ_TOO_COMPLEX_SHAPE_ID", INT2NUM(OBJ_TOO_COMPLEX_SHAPE_ID)); + rb_define_const(rb_cShape, "FIRST_T_OBJECT_SHAPE_ID", INT2NUM(FIRST_T_OBJECT_SHAPE_ID)); rb_define_const(rb_cShape, "SHAPE_MAX_VARIATIONS", INT2NUM(SHAPE_MAX_VARIATIONS)); rb_define_const(rb_cShape, "SIZEOF_RB_SHAPE_T", INT2NUM(sizeof(rb_shape_t))); rb_define_const(rb_cShape, "SIZEOF_REDBLACK_NODE_T", INT2NUM(sizeof(redblack_node_t))); diff --git a/shape.h b/shape.h index 60e68dfe9b29c9..07eb2c979fc4ee 100644 --- a/shape.h +++ b/shape.h @@ -35,8 +35,9 @@ typedef uint32_t redblack_id_t; # define INVALID_SHAPE_ID SHAPE_MASK # define ROOT_SHAPE_ID 0x0 -# define SPECIAL_CONST_SHAPE_ID (SIZE_POOL_COUNT * 2) +# define SPECIAL_CONST_SHAPE_ID (ROOT_SHAPE_ID + 1) # define OBJ_TOO_COMPLEX_SHAPE_ID (SPECIAL_CONST_SHAPE_ID + 1) +# define FIRST_T_OBJECT_SHAPE_ID (OBJ_TOO_COMPLEX_SHAPE_ID + 1) typedef struct redblack_node redblack_node_t; diff --git a/signal.c b/signal.c index 3932e97d2743b3..1c8f8c112b73ba 100644 --- a/signal.c +++ b/signal.c @@ -867,16 +867,19 @@ check_stack_overflow(int sig, const void *addr) } } # endif + # ifdef _WIN32 # define CHECK_STACK_OVERFLOW() check_stack_overflow(sig, 0) # else # define FAULT_ADDRESS info->si_addr # ifdef USE_UCONTEXT_REG -# define CHECK_STACK_OVERFLOW() check_stack_overflow(sig, (uintptr_t)FAULT_ADDRESS, ctx) +# define CHECK_STACK_OVERFLOW_() check_stack_overflow(sig, (uintptr_t)FAULT_ADDRESS, ctx) # else -# define CHECK_STACK_OVERFLOW() check_stack_overflow(sig, FAULT_ADDRESS) +# define CHECK_STACK_OVERFLOW_() check_stack_overflow(sig, FAULT_ADDRESS) # endif # define MESSAGE_FAULT_ADDRESS " at %p", FAULT_ADDRESS +# define SIGNAL_FROM_USER_P() ((info)->si_code == SI_USER) +# define CHECK_STACK_OVERFLOW() (SIGNAL_FROM_USER_P() ? (void)0 : CHECK_STACK_OVERFLOW_()) # endif #else # define CHECK_STACK_OVERFLOW() (void)0 @@ -1545,21 +1548,3 @@ Init_signal(void) rb_enable_interrupt(); } - -#if defined(HAVE_GRANTPT) -extern int grantpt(int); -#else -static int -fake_grantfd(int masterfd) -{ - errno = ENOSYS; - return -1; -} -#define grantpt(fd) fake_grantfd(fd) -#endif - -int -rb_grantpt(int masterfd) -{ - return grantpt(masterfd); -} diff --git a/spec/bundler/bundler/digest_spec.rb b/spec/bundler/bundler/digest_spec.rb index fd7b0c968e007c..f876827964a3a0 100644 --- a/spec/bundler/bundler/digest_spec.rb +++ b/spec/bundler/bundler/digest_spec.rb @@ -11,7 +11,7 @@ it "is compatible with stdlib" do random_strings = ["foo", "skfjsdlkfjsdf", "3924m", "ldskfj"] - # https://datatracker.ietf.org/doc/html/rfc3174#section-7.3 + # https://www.rfc-editor.org/rfc/rfc3174#section-7.3 rfc3174_test_cases = ["abc", "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", "a", "01234567" * 8] (random_strings + rfc3174_test_cases).each do |payload| diff --git a/spec/bundler/bundler/gem_version_promoter_spec.rb b/spec/bundler/bundler/gem_version_promoter_spec.rb index fccbb58fea181e..917daba95de5e5 100644 --- a/spec/bundler/bundler/gem_version_promoter_spec.rb +++ b/spec/bundler/bundler/gem_version_promoter_spec.rb @@ -33,13 +33,13 @@ def sorted_versions(candidates:, current:, name: "foo", locked: []) it "numerically sorts versions" do versions = sorted_versions(candidates: %w[1.7.7 1.7.8 1.7.9 1.7.15 1.8.0], current: "1.7.8") - expect(versions).to eq %w[1.7.7 1.7.8 1.7.9 1.7.15 1.8.0] + expect(versions).to eq %w[1.8.0 1.7.15 1.7.9 1.7.8 1.7.7] end context "with no options" do it "defaults to level=:major, strict=false, pre=false" do versions = sorted_versions(candidates: %w[0.2.0 0.3.0 0.3.1 0.9.0 1.0.0 2.0.1 2.1.0], current: "0.3.0") - expect(versions).to eq %w[0.2.0 0.3.0 0.3.1 0.9.0 1.0.0 2.0.1 2.1.0] + expect(versions).to eq %w[2.1.0 2.0.1 1.0.0 0.9.0 0.3.1 0.3.0 0.2.0] end end @@ -51,25 +51,25 @@ def sorted_versions(candidates:, current:, name: "foo", locked: []) it "keeps downgrades" do versions = sorted_versions(candidates: %w[0.2.0 0.3.0 0.3.1 0.9.0 1.0.0 2.0.1 2.1.0], current: "0.3.0") - expect(versions).to eq %w[0.2.0 0.3.0 0.3.1 0.9.0 1.0.0 2.0.1 2.1.0] + expect(versions).to eq %w[2.1.0 2.0.1 1.0.0 0.9.0 0.3.1 0.3.0 0.2.0] end end context "when level is minor" do before { gvp.level = :minor } - it "removes downgrades and major upgrades" do + it "sorts highest minor within same major in first position" do versions = sorted_versions(candidates: %w[0.2.0 0.3.0 0.3.1 0.9.0 1.0.0 2.0.1 2.1.0], current: "0.3.0") - expect(versions).to eq %w[0.3.0 0.3.1 0.9.0] + expect(versions).to eq %w[0.9.0 0.3.1 0.3.0 1.0.0 2.1.0 2.0.1 0.2.0] end end context "when level is patch" do before { gvp.level = :patch } - it "removes downgrades and major and minor upgrades" do + it "sorts highest patch within same minor in first position" do versions = sorted_versions(candidates: %w[0.2.0 0.3.0 0.3.1 0.9.0 1.0.0 2.0.1 2.1.0], current: "0.3.0") - expect(versions).to eq %w[0.3.0 0.3.1] + expect(versions).to eq %w[0.3.1 0.3.0 0.9.0 1.0.0 2.0.1 2.1.0 0.2.0] end end end @@ -82,25 +82,25 @@ def sorted_versions(candidates:, current:, name: "foo", locked: []) it "orders by version" do versions = sorted_versions(candidates: %w[0.2.0 0.3.0 0.3.1 0.9.0 1.0.0 2.0.1 2.1.0], current: "0.3.0") - expect(versions).to eq %w[0.2.0 0.3.0 0.3.1 0.9.0 1.0.0 2.0.1 2.1.0] + expect(versions).to eq %w[2.1.0 2.0.1 1.0.0 0.9.0 0.3.1 0.3.0 0.2.0] end end context "when level is minor" do before { gvp.level = :minor } - it "favors downgrades, then upgrades by major descending, minor ascending, patch ascending" do + it "favors minor upgrades, then patch upgrades, then major upgrades, then downgrades" do versions = sorted_versions(candidates: %w[0.2.0 0.3.0 0.3.1 0.9.0 1.0.0 2.0.1 2.1.0], current: "0.3.0") - expect(versions).to eq %w[0.2.0 2.0.1 2.1.0 1.0.0 0.3.0 0.3.1 0.9.0] + expect(versions).to eq %w[0.9.0 0.3.1 0.3.0 1.0.0 2.1.0 2.0.1 0.2.0] end end context "when level is patch" do before { gvp.level = :patch } - it "favors downgrades, then upgrades by major descending, minor descending, patch ascending" do + it "favors patch upgrades, then minor upgrades, then major upgrades, then downgrades" do versions = sorted_versions(candidates: %w[0.2.0 0.3.0 0.3.1 0.9.0 1.0.0 2.0.1 2.1.0], current: "0.3.0") - expect(versions).to eq %w[0.2.0 2.1.0 2.0.1 1.0.0 0.9.0 0.3.0 0.3.1] + expect(versions).to eq %w[0.3.1 0.3.0 0.9.0 1.0.0 2.0.1 2.1.0 0.2.0] end end end @@ -110,7 +110,7 @@ def sorted_versions(candidates:, current:, name: "foo", locked: []) it "sorts regardless of prerelease status" do versions = sorted_versions(candidates: %w[1.7.7.pre 1.8.0 1.8.1.pre 1.8.1 2.0.0.pre 2.0.0], current: "1.8.0") - expect(versions).to eq %w[1.7.7.pre 1.8.0 1.8.1.pre 1.8.1 2.0.0.pre 2.0.0] + expect(versions).to eq %w[2.0.0 2.0.0.pre 1.8.1 1.8.1.pre 1.8.0 1.7.7.pre] end end @@ -119,16 +119,16 @@ def sorted_versions(candidates:, current:, name: "foo", locked: []) it "deprioritizes prerelease gems" do versions = sorted_versions(candidates: %w[1.7.7.pre 1.8.0 1.8.1.pre 1.8.1 2.0.0.pre 2.0.0], current: "1.8.0") - expect(versions).to eq %w[1.7.7.pre 1.8.1.pre 2.0.0.pre 1.8.0 1.8.1 2.0.0] + expect(versions).to eq %w[2.0.0 1.8.1 1.8.0 2.0.0.pre 1.8.1.pre 1.7.7.pre] end end context "when locking and not major" do before { gvp.level = :minor } - it "keeps the current version last" do + it "keeps the current version first" do versions = sorted_versions(candidates: %w[0.2.0 0.3.0 0.3.1 0.9.0 1.0.0 2.1.0 2.0.1], current: "0.3.0", locked: ["bar"]) - expect(versions.last).to eq("0.3.0") + expect(versions.first).to eq("0.3.0") end end end diff --git a/spec/bundler/bundler/plugin/installer_spec.rb b/spec/bundler/bundler/plugin/installer_spec.rb index 2cf69649db00b9..ed40029f5a4008 100644 --- a/spec/bundler/bundler/plugin/installer_spec.rb +++ b/spec/bundler/bundler/plugin/installer_spec.rb @@ -25,10 +25,10 @@ end it "returns the installed spec after installing local git plugins" do - allow(installer).to receive(:install_local_git). + allow(installer).to receive(:install_git). and_return("new-plugin" => spec) - expect(installer.install(["new-plugin"], local_git: "/phony/path/repo")). + expect(installer.install(["new-plugin"], git: "/phony/path/repo")). to eq("new-plugin" => spec) end @@ -80,7 +80,7 @@ end let(:result) do - installer.install(["ga-plugin"], local_git: lib_path("ga-plugin").to_s) + installer.install(["ga-plugin"], git: lib_path("ga-plugin").to_s) end it "returns the installed spec after installing" do diff --git a/spec/bundler/bundler/settings_spec.rb b/spec/bundler/bundler/settings_spec.rb index 469af2b8c6cc75..634e0faf913711 100644 --- a/spec/bundler/bundler/settings_spec.rb +++ b/spec/bundler/bundler/settings_spec.rb @@ -319,6 +319,15 @@ expect(settings["mirror.https://rubygems.org/"]).to eq("http://rubygems-mirror.org") end + it "ignores commented out keys" do + create_file bundled_app(".bundle/config"), <<~C + # BUNDLE_MY-PERSONAL-SERVER__ORG: my-personal-server.org + C + + expect(Bundler.ui).not_to receive(:warn) + expect(settings.all).to be_empty + end + it "converts older keys with dashes" do config("BUNDLE_MY-PERSONAL-SERVER__ORG" => "my-personal-server.org") expect(Bundler.ui).to receive(:warn).with( diff --git a/spec/bundler/commands/exec_spec.rb b/spec/bundler/commands/exec_spec.rb index 9f5f12739aeaa0..d59b690d2f4ef4 100644 --- a/spec/bundler/commands/exec_spec.rb +++ b/spec/bundler/commands/exec_spec.rb @@ -885,7 +885,7 @@ def bin_path(a,b,c) let(:exit_code) { Bundler::GemNotFound.new.status_code } let(:expected) { "" } let(:expected_err) { <<-EOS.strip } -Could not find gem 'rack (= 2)' in locally installed gems. +Could not find gem 'rack (= 2)' in cached gems or installed locally. The source contains the following gems matching 'rack': * rack-0.9.1 @@ -915,7 +915,7 @@ def bin_path(a,b,c) let(:exit_code) { Bundler::GemNotFound.new.status_code } let(:expected) { "" } let(:expected_err) { <<-EOS.strip } -Could not find gem 'rack (= 2)' in locally installed gems. +Could not find gem 'rack (= 2)' in cached gems or installed locally. The source contains the following gems matching 'rack': * rack-1.0.0 diff --git a/spec/bundler/commands/lock_spec.rb b/spec/bundler/commands/lock_spec.rb index 45582fc7cee422..f6793d393ba828 100644 --- a/spec/bundler/commands/lock_spec.rb +++ b/spec/bundler/commands/lock_spec.rb @@ -138,7 +138,7 @@ it "does not fetch remote specs when using the --local option" do bundle "lock --update --local", raise_on_error: false - expect(err).to match(/locally installed gems/) + expect(err).to match(/cached gems or installed locally/) end it "does not fetch remote checksums with --local" do @@ -392,6 +392,22 @@ expect(the_bundle.locked_gems.specs.map(&:full_name)).to eq(%w[foo-1.5.0 bar-2.1.1 qux-1.1.0].sort) end + it "shows proper error when Gemfile changes forbid patch upgrades, and --patch --strict is given" do + # force next minor via Gemfile + gemfile <<-G + source "#{file_uri_for(gem_repo4)}" + gem 'foo', '1.5.0' + gem 'qux' + G + + bundle "lock --update foo --patch --strict", raise_on_error: false + + expect(err).to include( + "foo is locked to 1.4.3, while Gemfile is requesting foo (= 1.5.0). " \ + "--strict --patch was specified, but there are no patch level upgrades from 1.4.3 satisfying foo (= 1.5.0), so version solving has failed" + ) + end + context "pre" do it "defaults to major" do bundle "lock --update --pre" @@ -1211,7 +1227,7 @@ Because rails >= 7.0.4 depends on railties = 7.0.4 and rails < 7.0.4 depends on railties = 7.0.3.1, railties = 7.0.3.1 OR = 7.0.4 is required. - So, because railties = 7.0.3.1 OR = 7.0.4 could not be found in rubygems repository #{file_uri_for(gem_repo4)}/ or installed locally, + So, because railties = 7.0.3.1 OR = 7.0.4 could not be found in rubygems repository #{file_uri_for(gem_repo4)}/, cached gems or installed locally, version solving has failed. ERR end @@ -1322,7 +1338,7 @@ Thus, rails >= 7.0.2.3, < 7.0.4 cannot be used. And because rails >= 7.0.4 depends on activemodel = 7.0.4, rails >= 7.0.2.3 requires activemodel = 7.0.4. - So, because activemodel = 7.0.4 could not be found in rubygems repository #{file_uri_for(gem_repo4)}/ or installed locally + So, because activemodel = 7.0.4 could not be found in rubygems repository #{file_uri_for(gem_repo4)}/, cached gems or installed locally and Gemfile depends on rails >= 7.0.2.3, version solving has failed. ERR diff --git a/spec/bundler/commands/post_bundle_message_spec.rb b/spec/bundler/commands/post_bundle_message_spec.rb index 3c7fd3486de8b9..07fd5a79e97d4d 100644 --- a/spec/bundler/commands/post_bundle_message_spec.rb +++ b/spec/bundler/commands/post_bundle_message_spec.rb @@ -120,7 +120,7 @@ gem "not-a-gem", :group => :development G expect(err).to include <<-EOS.strip -Could not find gem 'not-a-gem' in rubygems repository #{file_uri_for(gem_repo1)}/ or installed locally. +Could not find gem 'not-a-gem' in rubygems repository #{file_uri_for(gem_repo1)}/, cached gems or installed locally. EOS end diff --git a/spec/bundler/commands/update_spec.rb b/spec/bundler/commands/update_spec.rb index 2bde5a1586d7cd..cfb86ebb54832f 100644 --- a/spec/bundler/commands/update_spec.rb +++ b/spec/bundler/commands/update_spec.rb @@ -864,6 +864,90 @@ expect(exitstatus).to eq(22) end + context "with multiple sources and caching enabled" do + before do + build_repo2 do + build_gem "rack", "1.0.0" + + build_gem "request_store", "1.0.0" do |s| + s.add_dependency "rack", "1.0.0" + end + end + + build_repo4 do + # set up repo with no gems + end + + gemfile <<~G + source "#{file_uri_for(gem_repo2)}" + + gem "request_store" + + source "#{file_uri_for(gem_repo4)}" do + end + G + + lockfile <<~L + GEM + remote: #{file_uri_for(gem_repo2)}/ + specs: + rack (1.0.0) + request_store (1.0.0) + rack (= 1.0.0) + + GEM + remote: #{file_uri_for(gem_repo4)}/ + specs: + + PLATFORMS + #{local_platform} + + DEPENDENCIES + request_store + + BUNDLED WITH + #{Bundler::VERSION} + L + end + + it "works" do + bundle :install + bundle :cache + + update_repo2 do + build_gem "request_store", "1.1.0" do |s| + s.add_dependency "rack", "1.0.0" + end + end + + bundle "update request_store" + + expect(out).to include("Bundle updated!") + + expect(lockfile).to eq <<~L + GEM + remote: #{file_uri_for(gem_repo2)}/ + specs: + rack (1.0.0) + request_store (1.1.0) + rack (= 1.0.0) + + GEM + remote: #{file_uri_for(gem_repo4)}/ + specs: + + PLATFORMS + #{local_platform} + + DEPENDENCIES + request_store + + BUNDLED WITH + #{Bundler::VERSION} + L + end + end + context "with multiple, duplicated sources, with lockfile in old format", bundler: "< 3" do before do build_repo2 do @@ -1422,6 +1506,43 @@ end end + it "does not claim to update to Bundler version to a wrong version when cached gems are present" do + pristine_system_gems "bundler-2.99.0" + + build_repo4 do + build_gem "rack", "3.0.9.1" + + build_bundler "2.99.0" + end + + gemfile <<~G + source "#{file_uri_for(gem_repo4)}" + gem "rack" + G + + lockfile <<~L + GEM + remote: #{file_uri_for(gem_repo4)}/ + specs: + rack (3.0.9.1) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + rack + + BUNDLED WITH + 2.99.0 + L + + bundle :cache, verbose: true + + bundle :update, bundler: true, artifice: "compact_index", verbose: true, env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } + + expect(out).not_to include("Updating bundler to") + end + it "does not update the bundler version in the lockfile if the latest version is not compatible with current ruby", :ruby_repo do pristine_system_gems "bundler-2.3.9" diff --git a/spec/bundler/install/gemfile/git_spec.rb b/spec/bundler/install/gemfile/git_spec.rb index 24cf30eadbe2ec..45ee7b44d111e3 100644 --- a/spec/bundler/install/gemfile/git_spec.rb +++ b/spec/bundler/install/gemfile/git_spec.rb @@ -929,7 +929,7 @@ gem "has_submodule" end G - expect(err).to match(%r{submodule >= 0 could not be found in rubygems repository #{file_uri_for(gem_repo1)}/ or installed locally}) + expect(err).to match(%r{submodule >= 0 could not be found in rubygems repository #{file_uri_for(gem_repo1)}/, cached gems or installed locally}) expect(the_bundle).not_to include_gems "has_submodule 1.0" end diff --git a/spec/bundler/install/gemfile/sources_spec.rb b/spec/bundler/install/gemfile/sources_spec.rb index daee8a27443158..a5ba76f4d9e26e 100644 --- a/spec/bundler/install/gemfile/sources_spec.rb +++ b/spec/bundler/install/gemfile/sources_spec.rb @@ -520,7 +520,7 @@ it "fails" do bundle :install, artifice: "compact_index", raise_on_error: false - expect(err).to include("Could not find gem 'private_gem_1' in rubygems repository https://gem.repo2/ or installed locally.") + expect(err).to include("Could not find gem 'private_gem_1' in rubygems repository https://gem.repo2/, cached gems or installed locally.") end end @@ -611,7 +611,7 @@ Could not find compatible versions Because every version of depends_on_rack depends on rack >= 0 - and rack >= 0 could not be found in rubygems repository https://gem.repo2/ or installed locally, + and rack >= 0 could not be found in rubygems repository https://gem.repo2/, cached gems or installed locally, depends_on_rack cannot be used. So, because Gemfile depends on depends_on_rack >= 0, version solving has failed. diff --git a/spec/bundler/install/gemfile/specific_platform_spec.rb b/spec/bundler/install/gemfile/specific_platform_spec.rb index 3e13d00b546c8e..c81c7095b0e74a 100644 --- a/spec/bundler/install/gemfile/specific_platform_spec.rb +++ b/spec/bundler/install/gemfile/specific_platform_spec.rb @@ -395,7 +395,7 @@ G error_message = <<~ERROR.strip - Could not find gem 'sorbet-static (= 0.5.6433)' with platform 'arm64-darwin-21' in rubygems repository #{file_uri_for(gem_repo4)}/ or installed locally. + Could not find gem 'sorbet-static (= 0.5.6433)' with platform 'arm64-darwin-21' in rubygems repository #{file_uri_for(gem_repo4)}/, cached gems or installed locally. The source contains the following gems matching 'sorbet-static (= 0.5.6433)': * sorbet-static-0.5.6433-universal-darwin-20 @@ -434,7 +434,7 @@ Could not find compatible versions Because every version of sorbet depends on sorbet-static = 0.5.6433 - and sorbet-static = 0.5.6433 could not be found in rubygems repository #{file_uri_for(gem_repo4)}/ or installed locally for any resolution platforms (arm64-darwin-21), + and sorbet-static = 0.5.6433 could not be found in rubygems repository #{file_uri_for(gem_repo4)}/, cached gems or installed locally for any resolution platforms (arm64-darwin-21), sorbet cannot be used. So, because Gemfile depends on sorbet = 0.5.6433, version solving has failed. @@ -473,7 +473,7 @@ bundle "lock", raise_on_error: false, env: { "BUNDLE_FORCE_RUBY_PLATFORM" => "true" } expect(err).to include <<~ERROR.rstrip - Could not find gem 'sorbet-static (= 0.5.9889)' with platform 'ruby' in rubygems repository #{file_uri_for(gem_repo4)}/ or installed locally. + Could not find gem 'sorbet-static (= 0.5.9889)' with platform 'ruby' in rubygems repository #{file_uri_for(gem_repo4)}/, cached gems or installed locally. The source contains the following gems matching 'sorbet-static (= 0.5.9889)': * sorbet-static-0.5.9889-#{Gem::Platform.local} diff --git a/spec/bundler/install/gems/flex_spec.rb b/spec/bundler/install/gems/flex_spec.rb index 5e0c88fc9510c3..8ef3984975458b 100644 --- a/spec/bundler/install/gems/flex_spec.rb +++ b/spec/bundler/install/gems/flex_spec.rb @@ -197,7 +197,7 @@ Could not find compatible versions Because rack-obama >= 2.0 depends on rack = 1.2 - and rack = 1.2 could not be found in rubygems repository #{file_uri_for(gem_repo2)}/ or installed locally, + and rack = 1.2 could not be found in rubygems repository #{file_uri_for(gem_repo2)}/, cached gems or installed locally, rack-obama >= 2.0 cannot be used. So, because Gemfile depends on rack-obama = 2.0, version solving has failed. diff --git a/spec/bundler/install/gems/resolving_spec.rb b/spec/bundler/install/gems/resolving_spec.rb index b54674898da5b7..c5f9c4a3d3e456 100644 --- a/spec/bundler/install/gems/resolving_spec.rb +++ b/spec/bundler/install/gems/resolving_spec.rb @@ -434,7 +434,7 @@ end nice_error = <<~E.strip - Could not find gems matching 'sorbet-static (= 0.5.10554)' valid for all resolution platforms (arm64-darwin-21, aarch64-linux) in rubygems repository #{file_uri_for(gem_repo4)}/ or installed locally. + Could not find gems matching 'sorbet-static (= 0.5.10554)' valid for all resolution platforms (arm64-darwin-21, aarch64-linux) in rubygems repository #{file_uri_for(gem_repo4)}/, cached gems or installed locally. The source contains the following gems matching 'sorbet-static (= 0.5.10554)': * sorbet-static-0.5.10554-universal-darwin-21 @@ -490,7 +490,7 @@ it "raises a proper error" do nice_error = <<~E.strip - Could not find gems matching 'sorbet-static' valid for all resolution platforms (arm-linux, x86_64-linux) in rubygems repository #{file_uri_for(gem_repo4)}/ or installed locally. + Could not find gems matching 'sorbet-static' valid for all resolution platforms (arm-linux, x86_64-linux) in rubygems repository #{file_uri_for(gem_repo4)}/, cached gems or installed locally. The source contains the following gems matching 'sorbet-static': * sorbet-static-0.5.10696-x86_64-linux diff --git a/spec/bundler/install/yanked_spec.rb b/spec/bundler/install/yanked_spec.rb index 5aeabd2f236a71..7408c243272512 100644 --- a/spec/bundler/install/yanked_spec.rb +++ b/spec/bundler/install/yanked_spec.rb @@ -188,7 +188,7 @@ bundle :list, raise_on_error: false - expect(err).to include("Could not find rack-0.9.1 in locally installed gems") + expect(err).to include("Could not find rack-0.9.1 in cached gems or installed locally") expect(err).to_not include("Your bundle is locked to rack (0.9.1) from") expect(err).to_not include("If you haven't changed sources, that means the author of rack (0.9.1) has removed it.") expect(err).to_not include("You'll need to update your bundle to a different version of rack (0.9.1) that hasn't been removed in order to install.") @@ -197,7 +197,7 @@ lockfile lockfile.gsub(/PLATFORMS\n #{lockfile_platforms}/m, "PLATFORMS\n #{lockfile_platforms("ruby")}") bundle :list, raise_on_error: false - expect(err).to include("Could not find rack-0.9.1 in locally installed gems") + expect(err).to include("Could not find rack-0.9.1 in cached gems or installed locally") end it "does not suggest the author has yanked the gem when using more than one gem, but shows all gems that couldn't be found in the source" do @@ -224,7 +224,7 @@ bundle :list, raise_on_error: false - expect(err).to include("Could not find rack-0.9.1, rack_middleware-1.0 in locally installed gems") + expect(err).to include("Could not find rack-0.9.1, rack_middleware-1.0 in cached gems or installed locally") expect(err).to include("Install missing gems with `bundle install`.") expect(err).to_not include("Your bundle is locked to rack (0.9.1) from") expect(err).to_not include("If you haven't changed sources, that means the author of rack (0.9.1) has removed it.") diff --git a/spec/bundler/other/major_deprecation_spec.rb b/spec/bundler/other/major_deprecation_spec.rb index 7dad0e8c3dff34..939b68a0bbf6c9 100644 --- a/spec/bundler/other/major_deprecation_spec.rb +++ b/spec/bundler/other/major_deprecation_spec.rb @@ -601,6 +601,23 @@ pending "fails with a helpful message", bundler: "3" end + context "bundle plugin install --local_git" do + before do + build_git "foo" do |s| + s.write "plugins.rb" + end + end + + it "prints a deprecation warning", bundler: "< 3" do + bundle "plugin install foo --local_git #{lib_path("foo-1.0")}" + + expect(out).to include("Installed plugin foo") + expect(deprecations).to include "--local_git is deprecated, use --git" + end + + pending "fails with a helpful message", bundler: "3" + end + describe "deprecating rubocop", :readline do context "bundle gem --rubocop" do before do diff --git a/spec/bundler/plugins/install_spec.rb b/spec/bundler/plugins/install_spec.rb index ca8e2d2e51f036..20c2f1fd2641f3 100644 --- a/spec/bundler/plugins/install_spec.rb +++ b/spec/bundler/plugins/install_spec.rb @@ -92,16 +92,18 @@ expect(out).to include("Using foo 1.1") end - it "installs when --branch specified" do - bundle "plugin install foo --branch main --source #{file_uri_for(gem_repo2)}" + it "raises an error when when --branch specified" do + bundle "plugin install foo --branch main --source #{file_uri_for(gem_repo2)}", raise_on_error: false - expect(out).to include("Installed plugin foo") + expect(out).not_to include("Installed plugin foo") + + expect(err).to include("--branch can only be used with git sources") end - it "installs when --ref specified" do - bundle "plugin install foo --ref v1.2.3 --source #{file_uri_for(gem_repo2)}" + it "raises an error when --ref specified" do + bundle "plugin install foo --ref v1.2.3 --source #{file_uri_for(gem_repo2)}", raise_on_error: false - expect(out).to include("Installed plugin foo") + expect(err).to include("--ref can only be used with git sources") end it "raises error when both --branch and --ref options are specified" do @@ -196,20 +198,58 @@ def exec(command, args) s.write "plugins.rb" end - bundle "plugin install foo --local_git #{lib_path("foo-1.0")}" + bundle "plugin install foo --git #{lib_path("foo-1.0")}" expect(out).to include("Installed plugin foo") plugin_should_be_installed("foo") end - it "raises an error when both git and local git sources are specified" do - bundle "plugin install foo --local_git /phony/path/project --git git@gitphony.com:/repo/project", raise_on_error: false + it "raises an error when both git and local git sources are specified", bundler: "< 3" do + bundle "plugin install foo --git /phony/path/project --local_git git@gitphony.com:/repo/project", raise_on_error: false expect(exitstatus).not_to eq(0) expect(err).to eq("Remote and local plugin git sources can't be both specified") end end + context "path plugins" do + it "installs from a path source" do + build_lib "path_plugin" do |s| + s.write "plugins.rb" + end + bundle "plugin install path_plugin --path #{lib_path("path_plugin-1.0")}" + + expect(out).to include("Installed plugin path_plugin") + plugin_should_be_installed("path_plugin") + end + + it "installs from a relative path source" do + build_lib "path_plugin" do |s| + s.write "plugins.rb" + end + path = lib_path("path_plugin-1.0").relative_path_from(bundled_app) + bundle "plugin install path_plugin --path #{path}" + + expect(out).to include("Installed plugin path_plugin") + plugin_should_be_installed("path_plugin") + end + + it "installs from a relative path source when inside an app" do + allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile) + gemfile "" + + build_lib "ga-plugin" do |s| + s.write "plugins.rb" + end + + path = lib_path("ga-plugin-1.0").relative_path_from(bundled_app) + bundle "plugin install ga-plugin --path #{path}" + + plugin_should_be_installed("ga-plugin") + expect(local_plugin_gem("foo-1.0")).not_to be_directory + end + end + context "Gemfile eval" do before do allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile) @@ -279,6 +319,21 @@ def exec(command, args) plugin_should_be_installed("ga-plugin") end + it "accepts relative path sources" do + build_lib "ga-plugin" do |s| + s.write "plugins.rb" + end + + path = lib_path("ga-plugin-1.0").relative_path_from(bundled_app) + install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" + plugin 'ga-plugin', :path => "#{path}" + G + + expect(out).to include("Installed plugin ga-plugin") + plugin_should_be_installed("ga-plugin") + end + context "in deployment mode" do it "installs plugins" do install_gemfile <<-G diff --git a/spec/bundler/support/activate.rb b/spec/bundler/support/activate.rb index e19a6d0ed1e866..143b77833d9db7 100644 --- a/spec/bundler/support/activate.rb +++ b/spec/bundler/support/activate.rb @@ -5,5 +5,5 @@ require_relative "path" bundler_gemspec = Spec::Path.loaded_gemspec -bundler_gemspec.instance_variable_set(:@full_gem_path, Spec::Path.source_root) +bundler_gemspec.instance_variable_set(:@full_gem_path, Spec::Path.source_root.to_s) bundler_gemspec.activate if bundler_gemspec.respond_to?(:activate) diff --git a/spec/bundler/support/builders.rb b/spec/bundler/support/builders.rb index 46931e9ca530ba..ab2dafb0b99e63 100644 --- a/spec/bundler/support/builders.rb +++ b/spec/bundler/support/builders.rb @@ -18,7 +18,7 @@ def pl(platform) end def rake_version - "13.1.0" + "13.2.1" end def build_repo1 diff --git a/spec/mspec/lib/mspec/helpers/tmp.rb b/spec/mspec/lib/mspec/helpers/tmp.rb index b2a38ee983ea94..4c0eddab756ea9 100644 --- a/spec/mspec/lib/mspec/helpers/tmp.rb +++ b/spec/mspec/lib/mspec/helpers/tmp.rb @@ -12,7 +12,7 @@ end SPEC_TEMP_DIR = spec_temp_dir -SPEC_TEMP_UNIQUIFIER = "0" +SPEC_TEMP_UNIQUIFIER = +"0" at_exit do begin @@ -41,6 +41,7 @@ def tmp(name, uniquify = true) if uniquify and !name.empty? slash = name.rindex "/" index = slash ? slash + 1 : 0 + name = +name name.insert index, "#{SPEC_TEMP_UNIQUIFIER.succ!}-" end diff --git a/spec/mspec/lib/mspec/mocks/mock.rb b/spec/mspec/lib/mspec/mocks/mock.rb index 28a083cc1505dd..c61ba35ea7196c 100644 --- a/spec/mspec/lib/mspec/mocks/mock.rb +++ b/spec/mspec/lib/mspec/mocks/mock.rb @@ -18,20 +18,16 @@ def self.stubs @stubs ||= Hash.new { |h,k| h[k] = [] } end - def self.replaced_name(obj, sym) - :"__mspec_#{obj.__id__}_#{sym}__" + def self.replaced_name(key) + :"__mspec_#{key.last}__" end def self.replaced_key(obj, sym) - [replaced_name(obj, sym), sym] + [obj.__id__, sym] end - def self.has_key?(keys, sym) - !!keys.find { |k| k.first == sym } - end - - def self.replaced?(sym) - has_key?(mocks.keys, sym) or has_key?(stubs.keys, sym) + def self.replaced?(key) + mocks.include?(key) or stubs.include?(key) end def self.clear_replaced(key) @@ -40,8 +36,9 @@ def self.clear_replaced(key) end def self.mock_respond_to?(obj, sym, include_private = false) - name = replaced_name(obj, :respond_to?) - if replaced? name + key = replaced_key(obj, :respond_to?) + if replaced? key + name = replaced_name(key) obj.__send__ name, sym, include_private else obj.respond_to? sym, include_private @@ -59,8 +56,8 @@ def self.install_method(obj, sym, type = nil) return end - if (sym == :respond_to? or mock_respond_to?(obj, sym, true)) and !replaced?(key.first) - meta.__send__ :alias_method, key.first, sym + if (sym == :respond_to? or mock_respond_to?(obj, sym, true)) and !replaced?(key) + meta.__send__ :alias_method, replaced_name(key), sym end suppress_warning { @@ -191,7 +188,7 @@ def self.cleanup next end - replaced = key.first + replaced = replaced_name(key) sym = key.last meta = obj.singleton_class diff --git a/spec/mspec/spec/mocks/mock_spec.rb b/spec/mspec/spec/mocks/mock_spec.rb index 73f9bdfa141436..7426e0ff88cb7c 100644 --- a/spec/mspec/spec/mocks/mock_spec.rb +++ b/spec/mspec/spec/mocks/mock_spec.rb @@ -22,14 +22,14 @@ RSpec.describe Mock, ".replaced_name" do it "returns the name for a method that is being replaced by a mock method" do m = double('a fake id') - expect(Mock.replaced_name(m, :method_call)).to eq(:"__mspec_#{m.object_id}_method_call__") + expect(Mock.replaced_name(Mock.replaced_key(m, :method_call))).to eq(:"__mspec_method_call__") end end RSpec.describe Mock, ".replaced_key" do it "returns a key used internally by Mock" do m = double('a fake id') - expect(Mock.replaced_key(m, :method_call)).to eq([:"__mspec_#{m.object_id}_method_call__", :method_call]) + expect(Mock.replaced_key(m, :method_call)).to eq([m.object_id, :method_call]) end end @@ -42,16 +42,16 @@ it "returns true if a method has been stubbed on an object" do Mock.install_method @mock, :method_call - expect(Mock.replaced?(Mock.replaced_name(@mock, :method_call))).to be_truthy + expect(Mock.replaced?(Mock.replaced_key(@mock, :method_call))).to be_truthy end it "returns true if a method has been mocked on an object" do Mock.install_method @mock, :method_call, :stub - expect(Mock.replaced?(Mock.replaced_name(@mock, :method_call))).to be_truthy + expect(Mock.replaced?(Mock.replaced_key(@mock, :method_call))).to be_truthy end it "returns false if a method has not been stubbed or mocked" do - expect(Mock.replaced?(Mock.replaced_name(@mock, :method_call))).to be_falsey + expect(Mock.replaced?(Mock.replaced_key(@mock, :method_call))).to be_falsey end end @@ -197,11 +197,11 @@ Mock.install_method @mock, :method_call expect(@mock).to respond_to(:method_call) - expect(@mock).not_to respond_to(Mock.replaced_name(@mock, :method_call)) + expect(@mock).not_to respond_to(Mock.replaced_name(Mock.replaced_key(@mock, :method_call))) Mock.install_method @mock, :method_call, :stub expect(@mock).to respond_to(:method_call) - expect(@mock).not_to respond_to(Mock.replaced_name(@mock, :method_call)) + expect(@mock).not_to respond_to(Mock.replaced_name(Mock.replaced_key(@mock, :method_call))) end end @@ -493,7 +493,7 @@ class MockAndRaiseError < Exception; end it "removes the replaced method if the mock method overrides an existing method" do def @mock.already_here() :hey end expect(@mock).to respond_to(:already_here) - replaced_name = Mock.replaced_name(@mock, :already_here) + replaced_name = Mock.replaced_name(Mock.replaced_key(@mock, :already_here)) Mock.install_method @mock, :already_here expect(@mock).to respond_to(replaced_name) @@ -521,10 +521,9 @@ def @mock.already_here() :hey end replaced_key = Mock.replaced_key(@mock, :method_call) expect(Mock).to receive(:clear_replaced).with(replaced_key) - replaced_name = Mock.replaced_name(@mock, :method_call) - expect(Mock.replaced?(replaced_name)).to be_truthy + expect(Mock.replaced?(replaced_key)).to be_truthy Mock.cleanup - expect(Mock.replaced?(replaced_name)).to be_falsey + expect(Mock.replaced?(replaced_key)).to be_falsey end end diff --git a/spec/prism.mspec b/spec/prism.mspec new file mode 100644 index 00000000000000..efa0d931230f72 --- /dev/null +++ b/spec/prism.mspec @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +# This is turned off because when we run with --parser=prism we explicitly turn +# off experimental warnings to make sure the output is consistent. +MSpec.register(:exclude, "Warning.[] returns default values for categories :deprecated and :experimental") + +## Language +MSpec.register(:exclude, "The defined? keyword when called with a method name in a void context warns about the void context when parsing it") +MSpec.register(:exclude, "Hash literal expands an '**{}' or '**obj' element with the last key/value pair taking precedence") +MSpec.register(:exclude, "Hash literal expands an '**{}' and warns when finding an additional duplicate key afterwards") +MSpec.register(:exclude, "Hash literal merges multiple nested '**obj' in Hash literals") +MSpec.register(:exclude, "Hash literal raises a SyntaxError at parse time when Symbol key with invalid bytes") +MSpec.register(:exclude, "Hash literal raises a SyntaxError at parse time when Symbol key with invalid bytes and 'key: value' syntax used") +MSpec.register(:exclude, "Regexps with encoding modifiers supports /e (EUC encoding) with interpolation") +MSpec.register(:exclude, "Regexps with encoding modifiers supports /e (EUC encoding) with interpolation /o") +MSpec.register(:exclude, "Regexps with encoding modifiers preserves EUC-JP as /e encoding through interpolation") +MSpec.register(:exclude, "Regexps with encoding modifiers supports /s (Windows_31J encoding) with interpolation") +MSpec.register(:exclude, "Regexps with encoding modifiers supports /s (Windows_31J encoding) with interpolation and /o") +MSpec.register(:exclude, "Regexps with encoding modifiers preserves Windows-31J as /s encoding through interpolation") +MSpec.register(:exclude, "Regexps with encoding modifiers supports /u (UTF8 encoding) with interpolation") +MSpec.register(:exclude, "Regexps with encoding modifiers supports /u (UTF8 encoding) with interpolation and /o") +MSpec.register(:exclude, "Regexps with encoding modifiers preserves UTF-8 as /u encoding through interpolation") +MSpec.register(:exclude, "A Symbol literal raises an SyntaxError at parse time when Symbol with invalid bytes") + +## Core +MSpec.register(:exclude, "IO.popen with a leading Array argument accepts a trailing Hash of Process.exec options") +MSpec.register(:exclude, "IO.popen with a leading Array argument accepts an IO mode argument following the Array") +MSpec.register(:exclude, "TracePoint#eval_script is the evald source code") +MSpec.register(:exclude, "TracePoint#event returns the type of event") +MSpec.register(:exclude, "TracePoint#inspect returns a String showing the event, method, path and line for a :return event") +MSpec.register(:exclude, "TracePoint.new includes multiple events when multiple event names are passed as params") +MSpec.register(:exclude, "TracePoint#path equals \"(eval at __FILE__:__LINE__)\" inside an eval for :end event") + +## Library +MSpec.register(:exclude, "Coverage.peek_result returns the result so far") +MSpec.register(:exclude, "Coverage.peek_result second call after require returns accumulated result") +MSpec.register(:exclude, "Coverage.result gives the covered files as a hash with arrays of count or nil") +MSpec.register(:exclude, "Coverage.result returns results for each mode separately when enabled :all modes") +MSpec.register(:exclude, "Coverage.result returns results for each mode separately when enabled any mode explicitly") +MSpec.register(:exclude, "Coverage.result returns the correct results when eval coverage is enabled") +MSpec.register(:exclude, "Coverage.result returns the correct results when eval coverage is disabled") +MSpec.register(:exclude, "Coverage.result clears counters (sets 0 values) when stop is not specified but clear: true specified") +MSpec.register(:exclude, "Coverage.result does not clear counters when stop is not specified but clear: false specified") +MSpec.register(:exclude, "Coverage.result does not clear counters when stop: false and clear is not specified") +MSpec.register(:exclude, "Coverage.result clears counters (sets 0 values) when stop: false and clear: true specified") +MSpec.register(:exclude, "Coverage.result does not clear counters when stop: false and clear: false specified") +MSpec.register(:exclude, "Coverage.start measures coverage within eval") +MSpec.register(:exclude, "Socket.gethostbyaddr using an IPv6 address with an explicit address family raises SocketError when the address is not supported by the family") diff --git a/spec/ruby/command_line/dash_v_spec.rb b/spec/ruby/command_line/dash_v_spec.rb index 15569fa6423b9b..747db7f755888b 100644 --- a/spec/ruby/command_line/dash_v_spec.rb +++ b/spec/ruby/command_line/dash_v_spec.rb @@ -6,7 +6,7 @@ describe "when used alone" do it "prints version and ends" do - ruby_exe(nil, args: '-v').sub("+PRISM ", "").should include(RUBY_DESCRIPTION) + ruby_exe(nil, args: '-v').sub("+PRISM ", "").should include(RUBY_DESCRIPTION.sub("+PRISM ", "")) end unless (defined?(RubyVM::YJIT) && RubyVM::YJIT.enabled?) || (defined?(RubyVM::RJIT) && RubyVM::RJIT.enabled?) || (ENV['RUBY_MN_THREADS'] == '1') diff --git a/spec/ruby/command_line/fixtures/string_literal_frozen_comment.rb b/spec/ruby/command_line/fixtures/string_literal_frozen_comment.rb new file mode 100644 index 00000000000000..fb84b546c0a7b9 --- /dev/null +++ b/spec/ruby/command_line/fixtures/string_literal_frozen_comment.rb @@ -0,0 +1,4 @@ +# frozen_string_literal: true +frozen = "test".frozen? +interned = "test".equal?("test") +puts "frozen:#{frozen} interned:#{interned}" diff --git a/spec/ruby/command_line/fixtures/string_literal_mutable_comment.rb b/spec/ruby/command_line/fixtures/string_literal_mutable_comment.rb new file mode 100644 index 00000000000000..381a74200171dd --- /dev/null +++ b/spec/ruby/command_line/fixtures/string_literal_mutable_comment.rb @@ -0,0 +1,4 @@ +# frozen_string_literal: false +frozen = "test".frozen? +interned = "test".equal?("test") +puts "frozen:#{frozen} interned:#{interned}" diff --git a/spec/ruby/command_line/fixtures/string_literal_raw.rb b/spec/ruby/command_line/fixtures/string_literal_raw.rb new file mode 100644 index 00000000000000..56b1841296b572 --- /dev/null +++ b/spec/ruby/command_line/fixtures/string_literal_raw.rb @@ -0,0 +1,3 @@ +frozen = "test".frozen? +interned = "test".equal?("test") +puts "frozen:#{frozen} interned:#{interned}" diff --git a/spec/ruby/command_line/frozen_strings_spec.rb b/spec/ruby/command_line/frozen_strings_spec.rb index 647b69daed19e3..334b98273b7827 100644 --- a/spec/ruby/command_line/frozen_strings_spec.rb +++ b/spec/ruby/command_line/frozen_strings_spec.rb @@ -19,6 +19,50 @@ end end +describe "The --disable-frozen-string-literal flag causes string literals to" do + + it "produce a different object each time" do + ruby_exe(fixture(__FILE__, "freeze_flag_one_literal.rb"), options: "--disable-frozen-string-literal").chomp.should == "false" + end + +end + +describe "With neither --enable-frozen-string-literal nor --disable-frozen-string-literal flag set" do + before do + # disable --enable-frozen-string-literal and --disable-frozen-string-literal passed in $RUBYOPT + @rubyopt = ENV["RUBYOPT"] + ENV["RUBYOPT"] = "" + end + + after do + ENV["RUBYOPT"] = @rubyopt + end + + it "produce a different object each time" do + ruby_exe(fixture(__FILE__, "freeze_flag_one_literal.rb")).chomp.should == "false" + end + + ruby_version_is "3.4" do + it "if file has no frozen_string_literal comment produce different frozen strings each time" do + ruby_exe(fixture(__FILE__, "string_literal_raw.rb")).chomp.should == "frozen:true interned:false" + end + end + + ruby_version_is ""..."3.4" do + it "if file has no frozen_string_literal comment produce different mutable strings each time" do + ruby_exe(fixture(__FILE__, "string_literal_raw.rb")).chomp.should == "frozen:false interned:false" + end + end + + it "if file has frozen_string_literal:true comment produce same frozen strings each time" do + ruby_exe(fixture(__FILE__, "string_literal_frozen_comment.rb")).chomp.should == "frozen:true interned:true" + end + + it "if file has frozen_string_literal:false comment produce different mutable strings each time" do + ruby_exe(fixture(__FILE__, "string_literal_mutable_comment.rb")).chomp.should == "frozen:false interned:false" + end +end + describe "The --debug flag produces" do it "debugging info on attempted frozen string modification" do error_str = ruby_exe(fixture(__FILE__, 'debug_info.rb'), options: '--debug', args: "2>&1") diff --git a/spec/ruby/command_line/rubyopt_spec.rb b/spec/ruby/command_line/rubyopt_spec.rb index 734db8d519e29b..18a5959b18cd46 100644 --- a/spec/ruby/command_line/rubyopt_spec.rb +++ b/spec/ruby/command_line/rubyopt_spec.rb @@ -25,12 +25,12 @@ guard -> { not CROSS_COMPILING } do it "prints the version number for '-v'" do ENV["RUBYOPT"] = '-v' - ruby_exe("")[/\A.*/].should == RUBY_DESCRIPTION + ruby_exe("")[/\A.*/].should == RUBY_DESCRIPTION.sub("+PRISM ", "") end it "ignores whitespace around the option" do ENV["RUBYOPT"] = ' -v ' - ruby_exe("")[/\A.*/].should == RUBY_DESCRIPTION + ruby_exe("")[/\A.*/].should == RUBY_DESCRIPTION.sub("+PRISM ", "") end end diff --git a/spec/ruby/core/argf/readpartial_spec.rb b/spec/ruby/core/argf/readpartial_spec.rb index bbc8831131db36..ea4301f25c690d 100644 --- a/spec/ruby/core/argf/readpartial_spec.rb +++ b/spec/ruby/core/argf/readpartial_spec.rb @@ -29,7 +29,7 @@ it "clears output buffer even if EOFError is raised because @argf is at end" do begin - output = "to be cleared" + output = +"to be cleared" argf [@file1_name] do @argf.read diff --git a/spec/ruby/core/argf/shared/getc.rb b/spec/ruby/core/argf/shared/getc.rb index 8be39c60b6fd78..d63372d9d70a2a 100644 --- a/spec/ruby/core/argf/shared/getc.rb +++ b/spec/ruby/core/argf/shared/getc.rb @@ -9,7 +9,7 @@ it "reads each char of files" do argf [@file1, @file2] do - chars = "" + chars = +"" @chars.size.times { chars << @argf.send(@method) } chars.should == @chars end diff --git a/spec/ruby/core/argf/shared/read.rb b/spec/ruby/core/argf/shared/read.rb index fe903983c085a4..e76d0221390139 100644 --- a/spec/ruby/core/argf/shared/read.rb +++ b/spec/ruby/core/argf/shared/read.rb @@ -15,7 +15,7 @@ it "treats second argument as an output buffer" do argf [@file1_name] do - buffer = "" + buffer = +"" @argf.send(@method, @file1.size, buffer) buffer.should == @file1 end @@ -23,7 +23,7 @@ it "clears output buffer before appending to it" do argf [@file1_name] do - buffer = "to be cleared" + buffer = +"to be cleared" @argf.send(@method, @file1.size, buffer) buffer.should == @file1 end diff --git a/spec/ruby/core/array/fill_spec.rb b/spec/ruby/core/array/fill_spec.rb index 02360e550dbf42..2c3b5d9e84b6d3 100644 --- a/spec/ruby/core/array/fill_spec.rb +++ b/spec/ruby/core/array/fill_spec.rb @@ -21,7 +21,7 @@ it "does not replicate the filler" do ary = [1, 2, 3, 4] - str = "x" + str = +"x" ary.fill(str).should == [str, str, str, str] str << "y" ary.should == [str, str, str, str] diff --git a/spec/ruby/core/array/fixtures/encoded_strings.rb b/spec/ruby/core/array/fixtures/encoded_strings.rb index 5b85bd0e066191..b5888d86ae6a9b 100644 --- a/spec/ruby/core/array/fixtures/encoded_strings.rb +++ b/spec/ruby/core/array/fixtures/encoded_strings.rb @@ -2,14 +2,14 @@ module ArraySpecs def self.array_with_usascii_and_7bit_utf8_strings [ - 'foo'.force_encoding('US-ASCII'), + 'foo'.dup.force_encoding('US-ASCII'), 'bar' ] end def self.array_with_usascii_and_utf8_strings [ - 'foo'.force_encoding('US-ASCII'), + 'foo'.dup.force_encoding('US-ASCII'), 'báz' ] end @@ -17,7 +17,7 @@ def self.array_with_usascii_and_utf8_strings def self.array_with_7bit_utf8_and_usascii_strings [ 'bar', - 'foo'.force_encoding('US-ASCII') + 'foo'.dup.force_encoding('US-ASCII') ] end @@ -25,13 +25,13 @@ def self.array_with_utf8_and_usascii_strings [ 'báz', 'bar', - 'foo'.force_encoding('US-ASCII') + 'foo'.dup.force_encoding('US-ASCII') ] end def self.array_with_usascii_and_utf8_strings [ - 'foo'.force_encoding('US-ASCII'), + 'foo'.dup.force_encoding('US-ASCII'), 'bar', 'báz' ] @@ -41,7 +41,7 @@ def self.array_with_utf8_and_7bit_binary_strings [ 'bar', 'báz', - 'foo'.force_encoding('BINARY') + 'foo'.dup.force_encoding('BINARY') ] end @@ -55,14 +55,14 @@ def self.array_with_utf8_and_binary_strings def self.array_with_usascii_and_7bit_binary_strings [ - 'bar'.force_encoding('US-ASCII'), - 'foo'.force_encoding('BINARY') + 'bar'.dup.force_encoding('US-ASCII'), + 'foo'.dup.force_encoding('BINARY') ] end def self.array_with_usascii_and_binary_strings [ - 'bar'.force_encoding('US-ASCII'), + 'bar'.dup.force_encoding('US-ASCII'), [255].pack('C').force_encoding('BINARY') ] end diff --git a/spec/ruby/core/array/pack/buffer_spec.rb b/spec/ruby/core/array/pack/buffer_spec.rb index ecb40bfd06fd76..f1206efb3e84c3 100644 --- a/spec/ruby/core/array/pack/buffer_spec.rb +++ b/spec/ruby/core/array/pack/buffer_spec.rb @@ -13,13 +13,13 @@ it "adds result at the end of buffer content" do n = [ 65, 66, 67 ] # result without buffer is "ABC" - buffer = "" + buffer = +"" n.pack("ccc", buffer: buffer).should == "ABC" - buffer = "123" + buffer = +"123" n.pack("ccc", buffer: buffer).should == "123ABC" - buffer = "12345" + buffer = +"12345" n.pack("ccc", buffer: buffer).should == "12345ABC" end @@ -31,19 +31,19 @@ context "offset (@) is specified" do it 'keeps buffer content if it is longer than offset' do n = [ 65, 66, 67 ] - buffer = "123456" + buffer = +"123456" n.pack("@3ccc", buffer: buffer).should == "123ABC" end it "fills the gap with \\0 if buffer content is shorter than offset" do n = [ 65, 66, 67 ] - buffer = "123" + buffer = +"123" n.pack("@6ccc", buffer: buffer).should == "123\0\0\0ABC" end it 'does not keep buffer content if it is longer than offset + result' do n = [ 65, 66, 67 ] - buffer = "1234567890" + buffer = +"1234567890" n.pack("@3ccc", buffer: buffer).should == "123ABC" end end diff --git a/spec/ruby/core/array/pack/shared/string.rb b/spec/ruby/core/array/pack/shared/string.rb index 8c82e8c617b266..2f70dc3951cb13 100644 --- a/spec/ruby/core/array/pack/shared/string.rb +++ b/spec/ruby/core/array/pack/shared/string.rb @@ -40,7 +40,7 @@ f = pack_format("*") [ [["\u{3042 3044 3046 3048}", 0x2000B].pack(f+"U"), Encoding::BINARY], [["abcde\xd1", "\xFF\xFe\x81\x82"].pack(f+"u"), Encoding::BINARY], - [["a".force_encoding("ascii"), "\xFF\xFe\x81\x82"].pack(f+"u"), Encoding::BINARY], + [["a".dup.force_encoding("ascii"), "\xFF\xFe\x81\x82"].pack(f+"u"), Encoding::BINARY], # under discussion [ruby-dev:37294] [["\u{3042 3044 3046 3048}", 1].pack(f+"N"), Encoding::BINARY] ].should be_computed_by(:encoding) diff --git a/spec/ruby/core/array/shared/inspect.rb b/spec/ruby/core/array/shared/inspect.rb index a2b43d49599881..af5128c645ac29 100644 --- a/spec/ruby/core/array/shared/inspect.rb +++ b/spec/ruby/core/array/shared/inspect.rb @@ -19,7 +19,7 @@ end it "does not call #to_s on a String returned from #inspect" do - str = "abc" + str = +"abc" str.should_not_receive(:to_s) [str].send(@method).should == '["abc"]' @@ -98,8 +98,8 @@ end it "does not raise if inspected result is not default external encoding" do - utf_16be = mock("utf_16be") - utf_16be.should_receive(:inspect).and_return(%<"utf_16be \u3042">.encode!(Encoding::UTF_16BE)) + utf_16be = mock(+"utf_16be") + utf_16be.should_receive(:inspect).and_return(%<"utf_16be \u3042">.encode(Encoding::UTF_16BE)) [utf_16be].send(@method).should == '["utf_16be \u3042"]' end diff --git a/spec/ruby/core/class/subclasses_spec.rb b/spec/ruby/core/class/subclasses_spec.rb index a16b934d4fc5d6..50eb5358d97419 100644 --- a/spec/ruby/core/class/subclasses_spec.rb +++ b/spec/ruby/core/class/subclasses_spec.rb @@ -7,7 +7,7 @@ assert_subclasses(ModuleSpecs::Parent, [ModuleSpecs::Child, ModuleSpecs::Child2]) end - it "does not return included modules" do + it "does not return included modules from the parent" do parent = Class.new child = Class.new(parent) mod = Module.new @@ -16,6 +16,33 @@ assert_subclasses(parent, [child]) end + it "does not return included modules from the child" do + parent = Class.new + child = Class.new(parent) + mod = Module.new + parent.include(mod) + + assert_subclasses(parent, [child]) + end + + it "does not return prepended modules from the parent" do + parent = Class.new + child = Class.new(parent) + mod = Module.new + parent.prepend(mod) + + assert_subclasses(parent, [child]) + end + + it "does not return prepended modules from the child" do + parent = Class.new + child = Class.new(parent) + mod = Module.new + child.prepend(mod) + + assert_subclasses(parent, [child]) + end + it "does not return singleton classes" do a = Class.new diff --git a/spec/ruby/core/complex/inspect_spec.rb b/spec/ruby/core/complex/inspect_spec.rb index 7a89ec6854b95c..045be94b225ce2 100644 --- a/spec/ruby/core/complex/inspect_spec.rb +++ b/spec/ruby/core/complex/inspect_spec.rb @@ -17,7 +17,8 @@ it "calls #inspect on real and imaginary" do real = NumericSpecs::Subclass.new - real.should_receive(:inspect).and_return("1") + # + because of https://bugs.ruby-lang.org/issues/20337 + real.should_receive(:inspect).and_return(+"1") imaginary = NumericSpecs::Subclass.new imaginary.should_receive(:inspect).and_return("2") imaginary.should_receive(:<).any_number_of_times.and_return(false) @@ -26,7 +27,8 @@ it "adds an `*' before the `i' if the last character of the imaginary part is not numeric" do real = NumericSpecs::Subclass.new - real.should_receive(:inspect).and_return("(1)") + # + because of https://bugs.ruby-lang.org/issues/20337 + real.should_receive(:inspect).and_return(+"(1)") imaginary = NumericSpecs::Subclass.new imaginary.should_receive(:inspect).and_return("(2)") imaginary.should_receive(:<).any_number_of_times.and_return(false) diff --git a/spec/ruby/core/complex/to_s_spec.rb b/spec/ruby/core/complex/to_s_spec.rb index 7677dcd0b56eda..ceccffe4703779 100644 --- a/spec/ruby/core/complex/to_s_spec.rb +++ b/spec/ruby/core/complex/to_s_spec.rb @@ -45,7 +45,8 @@ it "treats real and imaginary parts as strings" do real = NumericSpecs::Subclass.new - real.should_receive(:to_s).and_return("1") + # + because of https://bugs.ruby-lang.org/issues/20337 + real.should_receive(:to_s).and_return(+"1") imaginary = NumericSpecs::Subclass.new imaginary.should_receive(:to_s).and_return("2") imaginary.should_receive(:<).any_number_of_times.and_return(false) diff --git a/spec/ruby/core/dir/children_spec.rb b/spec/ruby/core/dir/children_spec.rb index 03698cc246a81f..0ad3df4669e36d 100644 --- a/spec/ruby/core/dir/children_spec.rb +++ b/spec/ruby/core/dir/children_spec.rb @@ -47,7 +47,7 @@ encoding = Encoding.find("filesystem") encoding = Encoding::BINARY if encoding == Encoding::US_ASCII platform_is_not :windows do - children.should include("こんにちは.txt".force_encoding(encoding)) + children.should include("こんにちは.txt".dup.force_encoding(encoding)) end children.first.encoding.should equal(Encoding.find("filesystem")) end @@ -113,7 +113,7 @@ encoding = Encoding.find("filesystem") encoding = Encoding::BINARY if encoding == Encoding::US_ASCII platform_is_not :windows do - children.should include("こんにちは.txt".force_encoding(encoding)) + children.should include("こんにちは.txt".dup.force_encoding(encoding)) end children.first.encoding.should equal(Encoding.find("filesystem")) end @@ -131,4 +131,17 @@ children = @dir.children.sort children.first.encoding.should equal(Encoding::EUC_KR) end + + it "returns the same result when called repeatedly" do + @dir = Dir.open DirSpecs.mock_dir + + a = [] + @dir.each {|dir| a << dir} + + b = [] + @dir.each {|dir| b << dir} + + a.sort.should == b.sort + a.sort.should == DirSpecs.expected_paths + end end diff --git a/spec/ruby/core/dir/each_child_spec.rb b/spec/ruby/core/dir/each_child_spec.rb index 520186e79ef310..7194273b9529c4 100644 --- a/spec/ruby/core/dir/each_child_spec.rb +++ b/spec/ruby/core/dir/each_child_spec.rb @@ -86,6 +86,19 @@ @dir.each_child { |f| f }.should == @dir end + it "returns the same result when called repeatedly" do + @dir = Dir.open DirSpecs.mock_dir + + a = [] + @dir.each {|dir| a << dir} + + b = [] + @dir.each {|dir| b << dir} + + a.sort.should == b.sort + a.sort.should == DirSpecs.expected_paths + end + describe "when no block is given" do it "returns an Enumerator" do @dir = Dir.new(DirSpecs.mock_dir) diff --git a/spec/ruby/core/dir/each_spec.rb b/spec/ruby/core/dir/each_spec.rb index 8c69a7212be3c2..7674663d82b0e4 100644 --- a/spec/ruby/core/dir/each_spec.rb +++ b/spec/ruby/core/dir/each_spec.rb @@ -35,6 +35,17 @@ ls.should include(@dir.read) end + it "returns the same result when called repeatedly" do + a = [] + @dir.each {|dir| a << dir} + + b = [] + @dir.each {|dir| b << dir} + + a.sort.should == b.sort + a.sort.should == DirSpecs.expected_paths + end + describe "when no block is given" do it "returns an Enumerator" do @dir.each.should be_an_instance_of(Enumerator) diff --git a/spec/ruby/core/dir/entries_spec.rb b/spec/ruby/core/dir/entries_spec.rb index 91c30fccae0c14..7462542acf42e2 100644 --- a/spec/ruby/core/dir/entries_spec.rb +++ b/spec/ruby/core/dir/entries_spec.rb @@ -47,7 +47,7 @@ encoding = Encoding.find("filesystem") encoding = Encoding::BINARY if encoding == Encoding::US_ASCII platform_is_not :windows do - entries.should include("こんにちは.txt".force_encoding(encoding)) + entries.should include("こんにちは.txt".dup.force_encoding(encoding)) end entries.first.encoding.should equal(Encoding.find("filesystem")) end diff --git a/spec/ruby/core/dir/shared/glob.rb b/spec/ruby/core/dir/shared/glob.rb index 27ae0e300057b3..745f02d46b57fb 100644 --- a/spec/ruby/core/dir/shared/glob.rb +++ b/spec/ruby/core/dir/shared/glob.rb @@ -12,7 +12,7 @@ end it "raises an Encoding::CompatibilityError if the argument encoding is not compatible with US-ASCII" do - pattern = "file*".force_encoding Encoding::UTF_16BE + pattern = "file*".dup.force_encoding Encoding::UTF_16BE -> { Dir.send(@method, pattern) }.should raise_error(Encoding::CompatibilityError) end diff --git a/spec/ruby/core/encoding/compatible_spec.rb b/spec/ruby/core/encoding/compatible_spec.rb index 80ecab6155844a..f18d8680a96e1e 100644 --- a/spec/ruby/core/encoding/compatible_spec.rb +++ b/spec/ruby/core/encoding/compatible_spec.rb @@ -7,7 +7,7 @@ describe "Encoding.compatible? String, String" do describe "when the first's Encoding is valid US-ASCII" do before :each do - @str = "abc".force_encoding Encoding::US_ASCII + @str = "abc".dup.force_encoding Encoding::US_ASCII end it "returns US-ASCII when the second's is US-ASCII" do @@ -33,28 +33,28 @@ describe "when the first's Encoding is ASCII compatible and ASCII only" do it "returns the first's Encoding if the second is ASCII compatible and ASCII only" do - [ [Encoding, "abc".force_encoding("UTF-8"), "123".force_encoding("Shift_JIS"), Encoding::UTF_8], - [Encoding, "123".force_encoding("Shift_JIS"), "abc".force_encoding("UTF-8"), Encoding::Shift_JIS] + [ [Encoding, "abc".dup.force_encoding("UTF-8"), "123".dup.force_encoding("Shift_JIS"), Encoding::UTF_8], + [Encoding, "123".dup.force_encoding("Shift_JIS"), "abc".dup.force_encoding("UTF-8"), Encoding::Shift_JIS] ].should be_computed_by(:compatible?) end it "returns the first's Encoding if the second is ASCII compatible and ASCII only" do - [ [Encoding, "abc".force_encoding("BINARY"), "123".force_encoding("US-ASCII"), Encoding::BINARY], - [Encoding, "123".force_encoding("US-ASCII"), "abc".force_encoding("BINARY"), Encoding::US_ASCII] + [ [Encoding, "abc".dup.force_encoding("BINARY"), "123".dup.force_encoding("US-ASCII"), Encoding::BINARY], + [Encoding, "123".dup.force_encoding("US-ASCII"), "abc".dup.force_encoding("BINARY"), Encoding::US_ASCII] ].should be_computed_by(:compatible?) end it "returns the second's Encoding if the second is ASCII compatible but not ASCII only" do - [ [Encoding, "abc".force_encoding("UTF-8"), "\xff".force_encoding("Shift_JIS"), Encoding::Shift_JIS], - [Encoding, "123".force_encoding("Shift_JIS"), "\xff".force_encoding("UTF-8"), Encoding::UTF_8], - [Encoding, "abc".force_encoding("BINARY"), "\xff".force_encoding("US-ASCII"), Encoding::US_ASCII], - [Encoding, "123".force_encoding("US-ASCII"), "\xff".force_encoding("BINARY"), Encoding::BINARY], + [ [Encoding, "abc".dup.force_encoding("UTF-8"), "\xff".dup.force_encoding("Shift_JIS"), Encoding::Shift_JIS], + [Encoding, "123".dup.force_encoding("Shift_JIS"), "\xff".dup.force_encoding("UTF-8"), Encoding::UTF_8], + [Encoding, "abc".dup.force_encoding("BINARY"), "\xff".dup.force_encoding("US-ASCII"), Encoding::US_ASCII], + [Encoding, "123".dup.force_encoding("US-ASCII"), "\xff".dup.force_encoding("BINARY"), Encoding::BINARY], ].should be_computed_by(:compatible?) end it "returns nil if the second's Encoding is not ASCII compatible" do - a = "abc".force_encoding("UTF-8") - b = "1234".force_encoding("UTF-16LE") + a = "abc".dup.force_encoding("UTF-8") + b = "1234".dup.force_encoding("UTF-16LE") Encoding.compatible?(a, b).should be_nil end end @@ -75,7 +75,7 @@ describe "when the first's Encoding is not ASCII compatible" do before :each do - @str = "abc".force_encoding Encoding::UTF_7 + @str = "abc".dup.force_encoding Encoding::UTF_7 end it "returns nil when the second String is US-ASCII" do @@ -91,14 +91,14 @@ end it "returns the Encoding when the second's Encoding is not ASCII compatible but the same as the first's Encoding" do - encoding = Encoding.compatible?(@str, "def".force_encoding("utf-7")) + encoding = Encoding.compatible?(@str, "def".dup.force_encoding("utf-7")) encoding.should == Encoding::UTF_7 end end describe "when the first's Encoding is invalid" do before :each do - @str = "\xff".force_encoding Encoding::UTF_8 + @str = "\xff".dup.force_encoding Encoding::UTF_8 end it "returns the first's Encoding when the second's Encoding is US-ASCII" do @@ -114,11 +114,11 @@ end it "returns nil when the second's Encoding is invalid and ASCII only" do - Encoding.compatible?(@str, "\x7f".force_encoding("utf-16be")).should be_nil + Encoding.compatible?(@str, "\x7f".dup.force_encoding("utf-16be")).should be_nil end it "returns nil when the second's Encoding is invalid and not ASCII only" do - Encoding.compatible?(@str, "\xff".force_encoding("utf-16be")).should be_nil + Encoding.compatible?(@str, "\xff".dup.force_encoding("utf-16be")).should be_nil end it "returns the Encoding when the second's Encoding is invalid but the same as the first" do @@ -129,7 +129,7 @@ describe "when the first String is empty and the second is not" do describe "and the first's Encoding is ASCII compatible" do before :each do - @str = "".force_encoding("utf-8") + @str = "".dup.force_encoding("utf-8") end it "returns the first's encoding when the second String is ASCII only" do @@ -143,7 +143,7 @@ describe "when the first's Encoding is not ASCII compatible" do before :each do - @str = "".force_encoding Encoding::UTF_7 + @str = "".dup.force_encoding Encoding::UTF_7 end it "returns the second string's encoding" do @@ -154,7 +154,7 @@ describe "when the second String is empty" do before :each do - @str = "abc".force_encoding("utf-7") + @str = "abc".dup.force_encoding("utf-7") end it "returns the first Encoding" do @@ -165,7 +165,7 @@ describe "Encoding.compatible? String, Regexp" do it "returns US-ASCII if both are US-ASCII" do - str = "abc".force_encoding("us-ascii") + str = "abc".dup.force_encoding("us-ascii") Encoding.compatible?(str, /abc/).should == Encoding::US_ASCII end @@ -180,15 +180,15 @@ it "returns the String's Encoding if the String is not ASCII only" do [ [Encoding, "\xff", Encoding::BINARY], [Encoding, "\u3042".encode("utf-8"), Encoding::UTF_8], - [Encoding, "\xa4\xa2".force_encoding("euc-jp"), Encoding::EUC_JP], - [Encoding, "\x82\xa0".force_encoding("shift_jis"), Encoding::Shift_JIS], + [Encoding, "\xa4\xa2".dup.force_encoding("euc-jp"), Encoding::EUC_JP], + [Encoding, "\x82\xa0".dup.force_encoding("shift_jis"), Encoding::Shift_JIS], ].should be_computed_by(:compatible?, /abc/) end end describe "Encoding.compatible? String, Symbol" do it "returns US-ASCII if both are ASCII only" do - str = "abc".force_encoding("us-ascii") + str = "abc".dup.force_encoding("us-ascii") Encoding.compatible?(str, :abc).should == Encoding::US_ASCII end @@ -203,8 +203,8 @@ it "returns the String's Encoding if the String is not ASCII only" do [ [Encoding, "\xff", Encoding::BINARY], [Encoding, "\u3042".encode("utf-8"), Encoding::UTF_8], - [Encoding, "\xa4\xa2".force_encoding("euc-jp"), Encoding::EUC_JP], - [Encoding, "\x82\xa0".force_encoding("shift_jis"), Encoding::Shift_JIS], + [Encoding, "\xa4\xa2".dup.force_encoding("euc-jp"), Encoding::EUC_JP], + [Encoding, "\x82\xa0".dup.force_encoding("shift_jis"), Encoding::Shift_JIS], ].should be_computed_by(:compatible?, :abc) end end @@ -221,8 +221,8 @@ it "returns the String's encoding if the Encoding is US-ASCII" do [ [Encoding, "\xff", Encoding::BINARY], [Encoding, "\u3042".encode("utf-8"), Encoding::UTF_8], - [Encoding, "\xa4\xa2".force_encoding("euc-jp"), Encoding::EUC_JP], - [Encoding, "\x82\xa0".force_encoding("shift_jis"), Encoding::Shift_JIS], + [Encoding, "\xa4\xa2".dup.force_encoding("euc-jp"), Encoding::EUC_JP], + [Encoding, "\x82\xa0".dup.force_encoding("shift_jis"), Encoding::Shift_JIS], ].should be_computed_by(:compatible?, Encoding::US_ASCII) end @@ -242,7 +242,7 @@ describe "Encoding.compatible? Regexp, String" do it "returns US-ASCII if both are US-ASCII" do - str = "abc".force_encoding("us-ascii") + str = "abc".dup.force_encoding("us-ascii") Encoding.compatible?(/abc/, str).should == Encoding::US_ASCII end @@ -256,8 +256,8 @@ it "returns the first's Encoding if it is not US-ASCII and not ASCII only" do [ [Encoding, Regexp.new("\xff"), Encoding::BINARY], [Encoding, Regexp.new("\u3042".encode("utf-8")), Encoding::UTF_8], - [Encoding, Regexp.new("\xa4\xa2".force_encoding("euc-jp")), Encoding::EUC_JP], - [Encoding, Regexp.new("\x82\xa0".force_encoding("shift_jis")), Encoding::Shift_JIS], + [Encoding, Regexp.new("\xa4\xa2".dup.force_encoding("euc-jp")), Encoding::EUC_JP], + [Encoding, Regexp.new("\x82\xa0".dup.force_encoding("shift_jis")), Encoding::Shift_JIS], ].should be_computed_by(:compatible?, /abc/) end end @@ -270,15 +270,15 @@ it "returns the first's Encoding if it is not US-ASCII and not ASCII only" do [ [Encoding, Regexp.new("\xff"), Encoding::BINARY], [Encoding, Regexp.new("\u3042".encode("utf-8")), Encoding::UTF_8], - [Encoding, Regexp.new("\xa4\xa2".force_encoding("euc-jp")), Encoding::EUC_JP], - [Encoding, Regexp.new("\x82\xa0".force_encoding("shift_jis")), Encoding::Shift_JIS], + [Encoding, Regexp.new("\xa4\xa2".dup.force_encoding("euc-jp")), Encoding::EUC_JP], + [Encoding, Regexp.new("\x82\xa0".dup.force_encoding("shift_jis")), Encoding::Shift_JIS], ].should be_computed_by(:compatible?, /abc/) end end describe "Encoding.compatible? Symbol, String" do it "returns US-ASCII if both are ASCII only" do - str = "abc".force_encoding("us-ascii") + str = "abc".dup.force_encoding("us-ascii") Encoding.compatible?(str, :abc).should == Encoding::US_ASCII end end @@ -291,8 +291,8 @@ it "returns the Regexp's Encoding if it is not US-ASCII and not ASCII only" do a = Regexp.new("\xff") b = Regexp.new("\u3042".encode("utf-8")) - c = Regexp.new("\xa4\xa2".force_encoding("euc-jp")) - d = Regexp.new("\x82\xa0".force_encoding("shift_jis")) + c = Regexp.new("\xa4\xa2".dup.force_encoding("euc-jp")) + d = Regexp.new("\x82\xa0".dup.force_encoding("shift_jis")) [ [Encoding, :abc, a, Encoding::BINARY], [Encoding, :abc, b, Encoding::UTF_8], @@ -310,8 +310,8 @@ it "returns the first's Encoding if it is not ASCII only" do [ [Encoding, "\xff".to_sym, Encoding::BINARY], [Encoding, "\u3042".encode("utf-8").to_sym, Encoding::UTF_8], - [Encoding, "\xa4\xa2".force_encoding("euc-jp").to_sym, Encoding::EUC_JP], - [Encoding, "\x82\xa0".force_encoding("shift_jis").to_sym, Encoding::Shift_JIS], + [Encoding, "\xa4\xa2".dup.force_encoding("euc-jp").to_sym, Encoding::EUC_JP], + [Encoding, "\x82\xa0".dup.force_encoding("shift_jis").to_sym, Encoding::Shift_JIS], ].should be_computed_by(:compatible?, :abc) end end diff --git a/spec/ruby/core/encoding/converter/convert_spec.rb b/spec/ruby/core/encoding/converter/convert_spec.rb index 95a9e0b7589d2c..7f249d90a3f8d3 100644 --- a/spec/ruby/core/encoding/converter/convert_spec.rb +++ b/spec/ruby/core/encoding/converter/convert_spec.rb @@ -1,4 +1,5 @@ # -*- encoding: binary -*- +# frozen_string_literal: true require_relative '../../../spec_helper' describe "Encoding::Converter#convert" do @@ -9,31 +10,31 @@ it "sets the encoding of the result to the target encoding" do ec = Encoding::Converter.new('ascii', 'utf-8') - str = 'glark'.force_encoding('ascii') + str = 'glark'.dup.force_encoding('ascii') ec.convert(str).encoding.should == Encoding::UTF_8 end it "transcodes the given String to the target encoding" do ec = Encoding::Converter.new("utf-8", "euc-jp") - ec.convert("\u3042".force_encoding('UTF-8')).should == \ - "\xA4\xA2".force_encoding('EUC-JP') + ec.convert("\u3042".dup.force_encoding('UTF-8')).should == \ + "\xA4\xA2".dup.force_encoding('EUC-JP') end it "allows Strings of different encodings to the source encoding" do ec = Encoding::Converter.new('ascii', 'utf-8') - str = 'glark'.force_encoding('SJIS') + str = 'glark'.dup.force_encoding('SJIS') ec.convert(str).encoding.should == Encoding::UTF_8 end it "reuses the given encoding pair if called multiple times" do ec = Encoding::Converter.new('ascii', 'SJIS') - ec.convert('a'.force_encoding('ASCII')).should == 'a'.force_encoding('SJIS') - ec.convert('b'.force_encoding('ASCII')).should == 'b'.force_encoding('SJIS') + ec.convert('a'.dup.force_encoding('ASCII')).should == 'a'.dup.force_encoding('SJIS') + ec.convert('b'.dup.force_encoding('ASCII')).should == 'b'.dup.force_encoding('SJIS') end it "raises UndefinedConversionError if the String contains characters invalid for the target encoding" do ec = Encoding::Converter.new('UTF-8', Encoding.find('macCyrillic')) - -> { ec.convert("\u{6543}".force_encoding('UTF-8')) }.should \ + -> { ec.convert("\u{6543}".dup.force_encoding('UTF-8')) }.should \ raise_error(Encoding::UndefinedConversionError) end diff --git a/spec/ruby/core/encoding/converter/finish_spec.rb b/spec/ruby/core/encoding/converter/finish_spec.rb index 11ca7e8510f1aa..239243430b453c 100644 --- a/spec/ruby/core/encoding/converter/finish_spec.rb +++ b/spec/ruby/core/encoding/converter/finish_spec.rb @@ -16,8 +16,8 @@ end it "returns the last part of the converted String if it hasn't already" do - @ec.convert("\u{9999}").should == "\e$B9a".force_encoding('iso-2022-jp') - @ec.finish.should == "\e(B".force_encoding('iso-2022-jp') + @ec.convert("\u{9999}").should == "\e$B9a".dup.force_encoding('iso-2022-jp') + @ec.finish.should == "\e(B".dup.force_encoding('iso-2022-jp') end it "returns a String in the destination encoding" do diff --git a/spec/ruby/core/encoding/converter/last_error_spec.rb b/spec/ruby/core/encoding/converter/last_error_spec.rb index 68567737b7e7ee..78779be70b8f91 100644 --- a/spec/ruby/core/encoding/converter/last_error_spec.rb +++ b/spec/ruby/core/encoding/converter/last_error_spec.rb @@ -9,45 +9,45 @@ it "returns nil when the last conversion did not produce an error" do ec = Encoding::Converter.new('ascii','utf-8') - ec.convert('a'.force_encoding('ascii')) + ec.convert('a'.dup.force_encoding('ascii')) ec.last_error.should be_nil end it "returns nil when #primitive_convert last returned :destination_buffer_full" do ec = Encoding::Converter.new("utf-8", "iso-2022-jp") - ec.primitive_convert("\u{9999}", "", 0, 0, partial_input: false) \ + ec.primitive_convert(+"\u{9999}", +"", 0, 0, partial_input: false) \ .should == :destination_buffer_full ec.last_error.should be_nil end it "returns nil when #primitive_convert last returned :finished" do ec = Encoding::Converter.new("utf-8", "iso-8859-1") - ec.primitive_convert("glark".force_encoding('utf-8'),"").should == :finished + ec.primitive_convert("glark".dup.force_encoding('utf-8'), +"").should == :finished ec.last_error.should be_nil end it "returns nil if the last conversion succeeded but the penultimate failed" do ec = Encoding::Converter.new("utf-8", "iso-8859-1") - ec.primitive_convert("\xf1abcd","").should == :invalid_byte_sequence - ec.primitive_convert("glark".force_encoding('utf-8'),"").should == :finished + ec.primitive_convert(+"\xf1abcd", +"").should == :invalid_byte_sequence + ec.primitive_convert("glark".dup.force_encoding('utf-8'), +"").should == :finished ec.last_error.should be_nil end it "returns an Encoding::InvalidByteSequenceError when #primitive_convert last returned :invalid_byte_sequence" do ec = Encoding::Converter.new("utf-8", "iso-8859-1") - ec.primitive_convert("\xf1abcd","").should == :invalid_byte_sequence + ec.primitive_convert(+"\xf1abcd", +"").should == :invalid_byte_sequence ec.last_error.should be_an_instance_of(Encoding::InvalidByteSequenceError) end it "returns an Encoding::UndefinedConversionError when #primitive_convert last returned :undefined_conversion" do ec = Encoding::Converter.new("utf-8", "iso-8859-1") - ec.primitive_convert("\u{9876}","").should == :undefined_conversion + ec.primitive_convert(+"\u{9876}", +"").should == :undefined_conversion ec.last_error.should be_an_instance_of(Encoding::UndefinedConversionError) end it "returns an Encoding::InvalidByteSequenceError when #primitive_convert last returned :incomplete_input" do ec = Encoding::Converter.new("EUC-JP", "ISO-8859-1") - ec.primitive_convert("\xa4", "", nil, 10).should == :incomplete_input + ec.primitive_convert(+"\xa4", +"", nil, 10).should == :incomplete_input ec.last_error.should be_an_instance_of(Encoding::InvalidByteSequenceError) end diff --git a/spec/ruby/core/encoding/converter/new_spec.rb b/spec/ruby/core/encoding/converter/new_spec.rb index 1f7affc72b82cc..db9c3364d7fd84 100644 --- a/spec/ruby/core/encoding/converter/new_spec.rb +++ b/spec/ruby/core/encoding/converter/new_spec.rb @@ -107,7 +107,7 @@ it "sets the replacement String to '\\uFFFD'" do conv = Encoding::Converter.new("us-ascii", "utf-8", replace: nil) - conv.replacement.should == "\u{fffd}".force_encoding("utf-8") + conv.replacement.should == "\u{fffd}".dup.force_encoding("utf-8") end it "sets the replacement String encoding to UTF-8" do diff --git a/spec/ruby/core/encoding/converter/primitive_convert_spec.rb b/spec/ruby/core/encoding/converter/primitive_convert_spec.rb index ab34ebf33fe0e6..63f25eddef1be0 100644 --- a/spec/ruby/core/encoding/converter/primitive_convert_spec.rb +++ b/spec/ruby/core/encoding/converter/primitive_convert_spec.rb @@ -1,4 +1,5 @@ # -*- encoding: binary -*- +# frozen_string_literal: false require_relative '../../../spec_helper' describe "Encoding::Converter#primitive_convert" do diff --git a/spec/ruby/core/encoding/converter/primitive_errinfo_spec.rb b/spec/ruby/core/encoding/converter/primitive_errinfo_spec.rb index 1f836b259fdbd0..668eb9a924d576 100644 --- a/spec/ruby/core/encoding/converter/primitive_errinfo_spec.rb +++ b/spec/ruby/core/encoding/converter/primitive_errinfo_spec.rb @@ -1,4 +1,5 @@ # -*- encoding: binary -*- +# frozen_string_literal: false require_relative '../../../spec_helper' describe "Encoding::Converter#primitive_errinfo" do diff --git a/spec/ruby/core/encoding/converter/putback_spec.rb b/spec/ruby/core/encoding/converter/putback_spec.rb index c4e0a5da213c9f..e19fe6c314a742 100644 --- a/spec/ruby/core/encoding/converter/putback_spec.rb +++ b/spec/ruby/core/encoding/converter/putback_spec.rb @@ -4,7 +4,7 @@ describe "Encoding::Converter#putback" do before :each do @ec = Encoding::Converter.new("EUC-JP", "ISO-8859-1") - @ret = @ec.primitive_convert(@src="abc\xa1def", @dst="", nil, 10) + @ret = @ec.primitive_convert(@src=+"abc\xa1def", @dst=+"", nil, 10) end it "returns a String" do @@ -36,21 +36,21 @@ it "returns the problematic bytes for UTF-16LE" do ec = Encoding::Converter.new("utf-16le", "iso-8859-1") - src = "\x00\xd8\x61\x00" - dst = "" + src = +"\x00\xd8\x61\x00" + dst = +"" ec.primitive_convert(src, dst).should == :invalid_byte_sequence ec.primitive_errinfo.should == [:invalid_byte_sequence, "UTF-16LE", "UTF-8", "\x00\xD8", "a\x00"] - ec.putback.should == "a\x00".force_encoding("utf-16le") + ec.putback.should == "a\x00".dup.force_encoding("utf-16le") ec.putback.should == "" end it "accepts an integer argument corresponding to the number of bytes to be put back" do ec = Encoding::Converter.new("utf-16le", "iso-8859-1") - src = "\x00\xd8\x61\x00" - dst = "" + src = +"\x00\xd8\x61\x00" + dst = +"" ec.primitive_convert(src, dst).should == :invalid_byte_sequence ec.primitive_errinfo.should == [:invalid_byte_sequence, "UTF-16LE", "UTF-8", "\x00\xD8", "a\x00"] - ec.putback(2).should == "a\x00".force_encoding("utf-16le") + ec.putback(2).should == "a\x00".dup.force_encoding("utf-16le") ec.putback.should == "" end end diff --git a/spec/ruby/core/encoding/converter/replacement_spec.rb b/spec/ruby/core/encoding/converter/replacement_spec.rb index 5ca42e7e5a0f3b..ea514ca8ddf79d 100644 --- a/spec/ruby/core/encoding/converter/replacement_spec.rb +++ b/spec/ruby/core/encoding/converter/replacement_spec.rb @@ -13,7 +13,7 @@ it "returns \\uFFFD when the destination encoding is UTF-8" do ec = Encoding::Converter.new("us-ascii", "utf-8") - ec.replacement.should == "\u{fffd}".force_encoding('utf-8') + ec.replacement.should == "\u{fffd}".dup.force_encoding('utf-8') ec.replacement.encoding.should == Encoding::UTF_8 end end @@ -38,33 +38,33 @@ it "sets #replacement" do ec = Encoding::Converter.new("us-ascii", "utf-8") - ec.replacement.should == "\u{fffd}".force_encoding('utf-8') + ec.replacement.should == "\u{fffd}".dup.force_encoding('utf-8') ec.replacement = '?'.encode('utf-8') - ec.replacement.should == '?'.force_encoding('utf-8') + ec.replacement.should == '?'.dup.force_encoding('utf-8') end it "raises an UndefinedConversionError is the argument cannot be converted into the destination encoding" do ec = Encoding::Converter.new("sjis", "ascii") - utf8_q = "\u{986}".force_encoding('utf-8') - ec.primitive_convert(utf8_q.dup, "").should == :undefined_conversion + utf8_q = "\u{986}".dup.force_encoding('utf-8') + ec.primitive_convert(utf8_q.dup, +"").should == :undefined_conversion -> { ec.replacement = utf8_q }.should \ raise_error(Encoding::UndefinedConversionError) end it "does not change the replacement character if the argument cannot be converted into the destination encoding" do ec = Encoding::Converter.new("sjis", "ascii") - utf8_q = "\u{986}".force_encoding('utf-8') - ec.primitive_convert(utf8_q.dup, "").should == :undefined_conversion + utf8_q = "\u{986}".dup.force_encoding('utf-8') + ec.primitive_convert(utf8_q.dup, +"").should == :undefined_conversion -> { ec.replacement = utf8_q }.should \ raise_error(Encoding::UndefinedConversionError) - ec.replacement.should == "?".force_encoding('us-ascii') + ec.replacement.should == "?".dup.force_encoding('us-ascii') end it "uses the replacement character" do ec = Encoding::Converter.new("utf-8", "us-ascii", :invalid => :replace, :undef => :replace) ec.replacement = "!" - dest = "" - status = ec.primitive_convert "中文123", dest + dest = +"" + status = ec.primitive_convert(+"中文123", dest) status.should == :finished dest.should == "!!123" diff --git a/spec/ruby/core/encoding/invalid_byte_sequence_error/incomplete_input_spec.rb b/spec/ruby/core/encoding/invalid_byte_sequence_error/incomplete_input_spec.rb index 94201a9b153470..8a3f3de69a8522 100644 --- a/spec/ruby/core/encoding/invalid_byte_sequence_error/incomplete_input_spec.rb +++ b/spec/ruby/core/encoding/invalid_byte_sequence_error/incomplete_input_spec.rb @@ -8,7 +8,7 @@ it "returns true if #primitive_convert returned :incomplete_input for the same data" do ec = Encoding::Converter.new("EUC-JP", "ISO-8859-1") - ec.primitive_convert("\xA1",'').should == :incomplete_input + ec.primitive_convert(+"\xA1", +'').should == :incomplete_input begin ec.convert("\xA1") rescue Encoding::InvalidByteSequenceError => e @@ -18,7 +18,7 @@ it "returns false if #primitive_convert returned :invalid_byte_sequence for the same data" do ec = Encoding::Converter.new("ascii", "utf-8") - ec.primitive_convert("\xfffffffff",'').should == :invalid_byte_sequence + ec.primitive_convert(+"\xfffffffff", +'').should == :invalid_byte_sequence begin ec.convert("\xfffffffff") rescue Encoding::InvalidByteSequenceError => e diff --git a/spec/ruby/core/encoding/invalid_byte_sequence_error/readagain_bytes_spec.rb b/spec/ruby/core/encoding/invalid_byte_sequence_error/readagain_bytes_spec.rb index 9866310c250891..a5e282498479b2 100644 --- a/spec/ruby/core/encoding/invalid_byte_sequence_error/readagain_bytes_spec.rb +++ b/spec/ruby/core/encoding/invalid_byte_sequence_error/readagain_bytes_spec.rb @@ -15,11 +15,11 @@ it "returns the bytes to be read again" do @exception.readagain_bytes.size.should == 1 - @exception.readagain_bytes.should == "a".force_encoding('binary') + @exception.readagain_bytes.should == "a".dup.force_encoding('binary') @exception.readagain_bytes.should == @errinfo[-1] @exception2.readagain_bytes.size.should == 1 - @exception2.readagain_bytes.should == "\xFF".force_encoding('binary') + @exception2.readagain_bytes.should == "\xFF".dup.force_encoding('binary') @exception2.readagain_bytes.should == @errinfo2[-1] end diff --git a/spec/ruby/core/encoding/replicate_spec.rb b/spec/ruby/core/encoding/replicate_spec.rb index 68c285158d386c..e22673db7d3235 100644 --- a/spec/ruby/core/encoding/replicate_spec.rb +++ b/spec/ruby/core/encoding/replicate_spec.rb @@ -18,8 +18,8 @@ e.name.should == name Encoding.find(name).should == e - "a".force_encoding(e).valid_encoding?.should be_true - "\x80".force_encoding(e).valid_encoding?.should be_false + "a".dup.force_encoding(e).valid_encoding?.should be_true + "\x80".dup.force_encoding(e).valid_encoding?.should be_false end it "returns a replica of UTF-8" do @@ -28,9 +28,9 @@ e.name.should == name Encoding.find(name).should == e - "a".force_encoding(e).valid_encoding?.should be_true - "\u3042".force_encoding(e).valid_encoding?.should be_true - "\x80".force_encoding(e).valid_encoding?.should be_false + "a".dup.force_encoding(e).valid_encoding?.should be_true + "\u3042".dup.force_encoding(e).valid_encoding?.should be_true + "\x80".dup.force_encoding(e).valid_encoding?.should be_false end it "returns a replica of UTF-16BE" do @@ -39,9 +39,9 @@ e.name.should == name Encoding.find(name).should == e - "a".force_encoding(e).valid_encoding?.should be_false - "\x30\x42".force_encoding(e).valid_encoding?.should be_true - "\x80".force_encoding(e).valid_encoding?.should be_false + "a".dup.force_encoding(e).valid_encoding?.should be_false + "\x30\x42".dup.force_encoding(e).valid_encoding?.should be_true + "\x80".dup.force_encoding(e).valid_encoding?.should be_false end it "returns a replica of ISO-2022-JP" do @@ -61,7 +61,7 @@ e.name.should == name Encoding.find(name).should == e - s = "abc".force_encoding(e) + s = "abc".dup.force_encoding(e) s.encoding.should == e s.encoding.name.should == name end diff --git a/spec/ruby/core/enumerator/product/size_spec.rb b/spec/ruby/core/enumerator/product/size_spec.rb index fb0efdf748243f..46958b1a229544 100644 --- a/spec/ruby/core/enumerator/product/size_spec.rb +++ b/spec/ruby/core/enumerator/product/size_spec.rb @@ -23,14 +23,6 @@ def enum.size; Float::INFINITY; end product.size.should == Float::INFINITY end - it "returns -Float::INFINITY if any enumerable reports its size as -Float::INFINITY" do - enum = Object.new - def enum.size; -Float::INFINITY; end - - product = Enumerator::Product.new(1..2, enum) - product.size.should == -Float::INFINITY - end - it "returns nil if any enumerable reports its size as Float::NAN" do enum = Object.new def enum.size; Float::NAN; end diff --git a/spec/ruby/core/exception/fixtures/syntax_error.rb b/spec/ruby/core/exception/fixtures/syntax_error.rb new file mode 100644 index 00000000000000..ccec62f7a1a8f5 --- /dev/null +++ b/spec/ruby/core/exception/fixtures/syntax_error.rb @@ -0,0 +1,3 @@ +# rubocop:disable Lint/Syntax +1+1=2 +# rubocop:enable Lint/Syntax diff --git a/spec/ruby/core/exception/set_backtrace_spec.rb b/spec/ruby/core/exception/set_backtrace_spec.rb index ba2e1bf7aa4df4..12c1da919cecc8 100644 --- a/spec/ruby/core/exception/set_backtrace_spec.rb +++ b/spec/ruby/core/exception/set_backtrace_spec.rb @@ -11,9 +11,37 @@ it "allows the user to set the backtrace from a rescued exception" do bt = ExceptionSpecs::Backtrace.backtrace err = RuntimeError.new + err.backtrace.should == nil + err.backtrace_locations.should == nil err.set_backtrace bt + err.backtrace.should == bt + err.backtrace_locations.should == nil + end + + ruby_version_is "3.4" do + it "allows the user to set backtrace locations from a rescued exception" do + bt_locations = ExceptionSpecs::Backtrace.backtrace_locations + err = RuntimeError.new + err.backtrace.should == nil + err.backtrace_locations.should == nil + + err.set_backtrace bt_locations + + err.backtrace_locations.size.should == bt_locations.size + err.backtrace_locations.each_with_index do |loc, index| + other_loc = bt_locations[index] + + loc.path.should == other_loc.path + loc.label.should == other_loc.label + loc.base_label.should == other_loc.base_label + loc.lineno.should == other_loc.lineno + loc.absolute_path.should == other_loc.absolute_path + loc.to_s.should == other_loc.to_s + end + err.backtrace.size.should == err.backtrace_locations.size + end end it "accepts an empty Array" do diff --git a/spec/ruby/core/exception/syntax_error_spec.rb b/spec/ruby/core/exception/syntax_error_spec.rb new file mode 100644 index 00000000000000..6cc8522de388ed --- /dev/null +++ b/spec/ruby/core/exception/syntax_error_spec.rb @@ -0,0 +1,27 @@ +require_relative '../../spec_helper' + +ruby_version_is "3.2" do + describe "SyntaxError#path" do + it "returns the file path provided to eval" do + filename = "speccing.rb" + + -> { + eval("if true", TOPLEVEL_BINDING, filename) + }.should raise_error(SyntaxError) { |e| + e.path.should == filename + } + end + + it "returns the file path that raised an exception" do + expected_path = fixture(__FILE__, "syntax_error.rb") + + -> { + require_relative "fixtures/syntax_error" + }.should raise_error(SyntaxError) { |e| e.path.should == expected_path } + end + + it "returns nil when constructed directly" do + SyntaxError.new.path.should == nil + end + end +end diff --git a/spec/ruby/core/exception/top_level_spec.rb b/spec/ruby/core/exception/top_level_spec.rb index 6ef75395984454..cc961d06d565df 100644 --- a/spec/ruby/core/exception/top_level_spec.rb +++ b/spec/ruby/core/exception/top_level_spec.rb @@ -8,25 +8,32 @@ it "the Exception#cause is printed to STDERR with backtraces" do code = <<-RUBY def raise_cause - raise "the cause" + raise "the cause" # 2 end def raise_wrapped - raise "wrapped" + raise "wrapped" # 5 end begin - raise_cause + raise_cause # 8 rescue - raise_wrapped + raise_wrapped # 10 end RUBY lines = ruby_exe(code, args: "2>&1", exit_status: 1).lines - lines.map! { |l| l.chomp[/:(in.+)/, 1] } - lines.size.should == 5 - lines[0].should =~ /\Ain [`'](?:Object#)?raise_wrapped': wrapped \(RuntimeError\)\z/ - lines[1].should =~ /\Ain [`'](?:rescue in )?
'\z/ - lines[2].should =~ /\Ain [`']
'\z/ - lines[3].should =~ /\Ain [`'](?:Object#)?raise_cause': the cause \(RuntimeError\)\z/ - lines[4].should =~ /\Ain [`']
'\z/ + + lines.map! { |l| l.chomp[/:(\d+:in.+)/, 1] } + lines[0].should =~ /\A5:in [`'](?:Object#)?raise_wrapped': wrapped \(RuntimeError\)\z/ + if lines[1].include? 'rescue in' + # CRuby < 3.4 has an extra 'rescue in' backtrace entry + lines[1].should =~ /\A10:in [`']rescue in
'\z/ + lines.delete_at 1 + lines[1].should =~ /\A7:in [`']
'\z/ + else + lines[1].should =~ /\A10:in [`']
'\z/ + end + lines[2].should =~ /\A2:in [`'](?:Object#)?raise_cause': the cause \(RuntimeError\)\z/ + lines[3].should =~ /\A8:in [`']
'\z/ + lines.size.should == 4 end describe "with a custom backtrace" do diff --git a/spec/ruby/core/file/expand_path_spec.rb b/spec/ruby/core/file/expand_path_spec.rb index c31f885b92f2d2..1abcf93900c3a8 100644 --- a/spec/ruby/core/file/expand_path_spec.rb +++ b/spec/ruby/core/file/expand_path_spec.rb @@ -137,7 +137,7 @@ it "returns a String in the same encoding as the argument" do Encoding.default_external = Encoding::SHIFT_JIS - path = "./a".force_encoding Encoding::CP1251 + path = "./a".dup.force_encoding Encoding::CP1251 File.expand_path(path).encoding.should equal(Encoding::CP1251) weird_path = [222, 173, 190, 175].pack('C*') diff --git a/spec/ruby/core/file/shared/path.rb b/spec/ruby/core/file/shared/path.rb index ee8109ba0599ca..aa2a64cf250a95 100644 --- a/spec/ruby/core/file/shared/path.rb +++ b/spec/ruby/core/file/shared/path.rb @@ -1,7 +1,7 @@ describe :file_path, shared: true do before :each do - @name = "file_to_path" - @path = tmp(@name) + @path = tmp("file_to_path") + @name = File.basename(@path) touch @path end diff --git a/spec/ruby/core/hash/assoc_spec.rb b/spec/ruby/core/hash/assoc_spec.rb index 64442918d1e9bb..62b2a11b30024c 100644 --- a/spec/ruby/core/hash/assoc_spec.rb +++ b/spec/ruby/core/hash/assoc_spec.rb @@ -22,11 +22,11 @@ end it "only returns the first matching key-value pair for identity hashes" do - # Avoid literal String keys in Hash#[]= due to https://bugs.ruby-lang.org/issues/12855 + # Avoid literal String keys since string literals can be frozen and interned e.g. with --enable-frozen-string-literal h = {}.compare_by_identity - k1 = 'pear' + k1 = 'pear'.dup h[k1] = :red - k2 = 'pear' + k2 = 'pear'.dup h[k2] = :green h.size.should == 2 h.keys.grep(/pear/).size.should == 2 diff --git a/spec/ruby/core/hash/compare_by_identity_spec.rb b/spec/ruby/core/hash/compare_by_identity_spec.rb index 874cd46eb7cc7f..2975526a97a5f0 100644 --- a/spec/ruby/core/hash/compare_by_identity_spec.rb +++ b/spec/ruby/core/hash/compare_by_identity_spec.rb @@ -85,19 +85,21 @@ def o.hash; 123; end -> { @h.compare_by_identity }.should raise_error(FrozenError) end - # Behaviour confirmed in bug #1871 + # Behaviour confirmed in https://bugs.ruby-lang.org/issues/1871 it "persists over #dups" do - @idh['foo'] = :bar - @idh['foo'] = :glark + @idh['foo'.dup] = :bar + @idh['foo'.dup] = :glark @idh.dup.should == @idh @idh.dup.size.should == @idh.size + @idh.dup.should.compare_by_identity? end it "persists over #clones" do - @idh['foo'] = :bar - @idh['foo'] = :glark + @idh['foo'.dup] = :bar + @idh['foo'.dup] = :glark @idh.clone.should == @idh @idh.clone.size.should == @idh.size + @idh.dup.should.compare_by_identity? end it "does not copy string keys" do @@ -108,9 +110,16 @@ def o.hash; 123; end @idh.keys.first.should equal foo end + # Check `#[]=` call with a String literal. + # Don't use `#+` because with `#+` it's no longer a String literal. + # + # See https://bugs.ruby-lang.org/issues/12855 it "gives different identity for string literals" do + eval <<~RUBY + # frozen_string_literal: false @idh['foo'] = 1 @idh['foo'] = 2 + RUBY @idh.values.should == [1, 2] @idh.size.should == 2 end diff --git a/spec/ruby/core/hash/element_reference_spec.rb b/spec/ruby/core/hash/element_reference_spec.rb index e271f37ea6b485..94e82378395d43 100644 --- a/spec/ruby/core/hash/element_reference_spec.rb +++ b/spec/ruby/core/hash/element_reference_spec.rb @@ -30,7 +30,7 @@ end it "does not create copies of the immediate default value" do - str = "foo" + str = +"foo" h = Hash.new(str) a = h[:a] b = h[:b] diff --git a/spec/ruby/core/hash/shared/store.rb b/spec/ruby/core/hash/shared/store.rb index b823ea45ca60ea..dd1bb52bac1c7f 100644 --- a/spec/ruby/core/hash/shared/store.rb +++ b/spec/ruby/core/hash/shared/store.rb @@ -9,7 +9,7 @@ it "duplicates string keys using dup semantics" do # dup doesn't copy singleton methods - key = "foo" + key = +"foo" def key.reverse() "bar" end h = {} h.send(@method, key, 0) @@ -44,7 +44,7 @@ def key.reverse() "bar" end end it "duplicates and freezes string keys" do - key = "foo" + key = +"foo" h = {} h.send(@method, key, 0) key << "bar" @@ -75,8 +75,8 @@ def key.reverse() "bar" end it "keeps the existing String key in the hash if there is a matching one" do h = { "a" => 1, "b" => 2, "c" => 3, "d" => 4 } - key1 = "foo" - key2 = "foo" + key1 = "foo".dup + key2 = "foo".dup key1.should_not equal(key2) h[key1] = 41 frozen_key = h.keys.last diff --git a/spec/ruby/core/hash/shared/to_s.rb b/spec/ruby/core/hash/shared/to_s.rb index 2db3a96583bcc1..7864d7cd4c6985 100644 --- a/spec/ruby/core/hash/shared/to_s.rb +++ b/spec/ruby/core/hash/shared/to_s.rb @@ -24,7 +24,7 @@ end it "does not call #to_s on a String returned from #inspect" do - str = "abc" + str = +"abc" str.should_not_receive(:to_s) { a: str }.send(@method).should == '{:a=>"abc"}' @@ -78,7 +78,7 @@ it "does not raise if inspected result is not default external encoding" do utf_16be = mock("utf_16be") - utf_16be.should_receive(:inspect).and_return(%<"utf_16be \u3042">.encode!(Encoding::UTF_16BE)) + utf_16be.should_receive(:inspect).and_return(%<"utf_16be \u3042">.encode(Encoding::UTF_16BE)) {a: utf_16be}.send(@method).should == '{:a=>"utf_16be \u3042"}' end diff --git a/spec/ruby/core/io/ioctl_spec.rb b/spec/ruby/core/io/ioctl_spec.rb index 8dcd9eb2c61646..3f7b5ad5d7cbb6 100644 --- a/spec/ruby/core/io/ioctl_spec.rb +++ b/spec/ruby/core/io/ioctl_spec.rb @@ -12,7 +12,7 @@ guard -> { RUBY_PLATFORM.include?("86") } do # x86 / x86_64 it "resizes an empty String to match the output size" do File.open(__FILE__, 'r') do |f| - buffer = '' + buffer = +'' # FIONREAD in /usr/include/asm-generic/ioctls.h f.ioctl 0x541B, buffer buffer.unpack('I').first.should be_kind_of(Integer) diff --git a/spec/ruby/core/io/pread_spec.rb b/spec/ruby/core/io/pread_spec.rb index aa496ee803ab9d..28afc80e5c614f 100644 --- a/spec/ruby/core/io/pread_spec.rb +++ b/spec/ruby/core/io/pread_spec.rb @@ -21,19 +21,19 @@ end it "accepts a length, an offset, and an output buffer" do - buffer = "foo" + buffer = +"foo" @file.pread(3, 4, buffer) buffer.should == "567" end it "shrinks the buffer in case of less bytes read" do - buffer = "foo" + buffer = +"foo" @file.pread(1, 0, buffer) buffer.should == "1" end it "grows the buffer in case of more bytes read" do - buffer = "foo" + buffer = +"foo" @file.pread(5, 0, buffer) buffer.should == "12345" end @@ -57,7 +57,7 @@ end it "does not reset the buffer when reading with maxlen = 0" do - buffer = "foo" + buffer = +"foo" @file.pread(0, 4, buffer) buffer.should == "foo" @@ -79,7 +79,7 @@ it "converts a buffer to String using to_str" do buffer = mock('buffer') - buffer.should_receive(:to_str).at_least(1).and_return("foo") + buffer.should_receive(:to_str).at_least(1).and_return(+"foo") @file.pread(4, 0, buffer) buffer.should_not.is_a?(String) buffer.to_str.should == "1234" diff --git a/spec/ruby/core/io/puts_spec.rb b/spec/ruby/core/io/puts_spec.rb index 9a708fffef1a6e..9ed343c94c3c18 100644 --- a/spec/ruby/core/io/puts_spec.rb +++ b/spec/ruby/core/io/puts_spec.rb @@ -6,7 +6,7 @@ @before_separator = $/ @name = tmp("io_puts.txt") @io = new_io @name - ScratchPad.record "" + ScratchPad.record(+"") def @io.write(str) ScratchPad << str end diff --git a/spec/ruby/core/io/read_nonblock_spec.rb b/spec/ruby/core/io/read_nonblock_spec.rb index a62b75274c6774..51e7cd6bd2fdd4 100644 --- a/spec/ruby/core/io/read_nonblock_spec.rb +++ b/spec/ruby/core/io/read_nonblock_spec.rb @@ -96,21 +96,21 @@ end it "reads into the passed buffer" do - buffer = "" + buffer = +"" @write.write("1") @read.read_nonblock(1, buffer) buffer.should == "1" end it "returns the passed buffer" do - buffer = "" + buffer = +"" @write.write("1") output = @read.read_nonblock(1, buffer) output.should equal(buffer) end it "discards the existing buffer content upon successful read" do - buffer = "existing content" + buffer = +"existing content" @write.write("hello world") @write.close @read.read_nonblock(11, buffer) @@ -118,7 +118,7 @@ end it "discards the existing buffer content upon error" do - buffer = "existing content" + buffer = +"existing content" @write.close -> { @read.read_nonblock(1, buffer) }.should raise_error(EOFError) buffer.should be_empty diff --git a/spec/ruby/core/io/read_spec.rb b/spec/ruby/core/io/read_spec.rb index b37c6c71217e43..eb3652e692a581 100644 --- a/spec/ruby/core/io/read_spec.rb +++ b/spec/ruby/core/io/read_spec.rb @@ -294,19 +294,19 @@ it "clears the output buffer if there is nothing to read" do @io.pos = 10 - buf = 'non-empty string' + buf = +'non-empty string' @io.read(10, buf).should == nil buf.should == '' - buf = 'non-empty string' + buf = +'non-empty string' @io.read(nil, buf).should == "" buf.should == '' - buf = 'non-empty string' + buf = +'non-empty string' @io.read(0, buf).should == "" @@ -344,53 +344,53 @@ end it "places the specified number of bytes in the buffer" do - buf = "" + buf = +"" @io.read 5, buf buf.should == "12345" end it "expands the buffer when too small" do - buf = "ABCDE" + buf = +"ABCDE" @io.read nil, buf buf.should == @contents end it "overwrites the buffer" do - buf = "ABCDEFGHIJ" + buf = +"ABCDEFGHIJ" @io.read nil, buf buf.should == @contents end it "truncates the buffer when too big" do - buf = "ABCDEFGHIJKLMNO" + buf = +"ABCDEFGHIJKLMNO" @io.read nil, buf buf.should == @contents @io.rewind - buf = "ABCDEFGHIJKLMNO" + buf = +"ABCDEFGHIJKLMNO" @io.read 5, buf buf.should == @contents[0..4] end it "returns the given buffer" do - buf = "" + buf = +"" @io.read(nil, buf).should equal buf end it "returns the given buffer when there is nothing to read" do - buf = "" + buf = +"" @io.read @io.read(nil, buf).should equal buf end it "coerces the second argument to string and uses it as a buffer" do - buf = "ABCDE" + buf = +"ABCDE" obj = mock("buff") obj.should_receive(:to_str).any_number_of_times.and_return(buf) @@ -588,13 +588,13 @@ describe "when passed nil for limit" do it "sets the buffer to a transcoded String" do - result = @io.read(nil, buf = "") + result = @io.read(nil, buf = +"") buf.should equal(result) buf.should == "ありがとう\n" end it "sets the buffer's encoding to the internal encoding" do - buf = "".force_encoding Encoding::ISO_8859_1 + buf = "".dup.force_encoding Encoding::ISO_8859_1 @io.read(nil, buf) buf.encoding.should equal(Encoding::UTF_8) end @@ -612,14 +612,14 @@ end it "does not change the buffer's encoding when passed a limit" do - buf = "".force_encoding Encoding::ISO_8859_1 + buf = "".dup.force_encoding Encoding::ISO_8859_1 @io.read(4, buf) buf.should == [164, 162, 164, 234].pack('C*').force_encoding(Encoding::ISO_8859_1) buf.encoding.should equal(Encoding::ISO_8859_1) end it "truncates the buffer but does not change the buffer's encoding when no data remains" do - buf = "abc".force_encoding Encoding::ISO_8859_1 + buf = "abc".dup.force_encoding Encoding::ISO_8859_1 @io.read @io.read(1, buf).should be_nil diff --git a/spec/ruby/core/io/readpartial_spec.rb b/spec/ruby/core/io/readpartial_spec.rb index 2901b429c25181..0060beb54504a5 100644 --- a/spec/ruby/core/io/readpartial_spec.rb +++ b/spec/ruby/core/io/readpartial_spec.rb @@ -59,7 +59,7 @@ end it "discards the existing buffer content upon successful read" do - buffer = "existing content" + buffer = +"existing content" @wr.write("hello world") @wr.close @rd.readpartial(11, buffer) @@ -74,7 +74,7 @@ end it "discards the existing buffer content upon error" do - buffer = 'hello' + buffer = +'hello' @wr.close -> { @rd.readpartial(1, buffer) }.should raise_error(EOFError) buffer.should be_empty @@ -95,7 +95,7 @@ ruby_bug "#18421", ""..."3.0.4" do it "clears and returns the given buffer if the length argument is 0" do - buffer = "existing content" + buffer = +"existing content" @rd.readpartial(0, buffer).should == buffer buffer.should == "" end diff --git a/spec/ruby/core/io/shared/readlines.rb b/spec/ruby/core/io/shared/readlines.rb index d2b604bba357f7..6c1fa11a596800 100644 --- a/spec/ruby/core/io/shared/readlines.rb +++ b/spec/ruby/core/io/shared/readlines.rb @@ -99,7 +99,7 @@ end it "accepts non-ASCII data as separator" do - result = IO.send(@method, @name, "\303\250".force_encoding("utf-8"), &@object) + result = IO.send(@method, @name, "\303\250".dup.force_encoding("utf-8"), &@object) (result ? result : ScratchPad.recorded).should == IOSpecs.lines_arbitrary_separator end end diff --git a/spec/ruby/core/io/sysread_spec.rb b/spec/ruby/core/io/sysread_spec.rb index e7f63cefec70e5..003bb9eb94911a 100644 --- a/spec/ruby/core/io/sysread_spec.rb +++ b/spec/ruby/core/io/sysread_spec.rb @@ -21,25 +21,25 @@ end it "reads the specified number of bytes from the file to the buffer" do - buf = "" # empty buffer + buf = +"" # empty buffer @file.sysread(15, buf).should == buf buf.should == "012345678901234" @file.rewind - buf = "ABCDE" # small buffer + buf = +"ABCDE" # small buffer @file.sysread(15, buf).should == buf buf.should == "012345678901234" @file.rewind - buf = "ABCDE" * 5 # large buffer + buf = +"ABCDE" * 5 # large buffer @file.sysread(15, buf).should == buf buf.should == "012345678901234" end it "coerces the second argument to string and uses it as a buffer" do - buf = "ABCDE" + buf = +"ABCDE" (obj = mock("buff")).should_receive(:to_str).any_number_of_times.and_return(buf) @file.sysread(15, obj).should == buf buf.should == "012345678901234" @@ -90,19 +90,19 @@ end it "immediately returns the given buffer if the length argument is 0" do - buffer = "existing content" + buffer = +"existing content" @file.sysread(0, buffer).should == buffer buffer.should == "existing content" end it "discards the existing buffer content upon successful read" do - buffer = "existing content" + buffer = +"existing content" @file.sysread(11, buffer) buffer.should == "01234567890" end it "discards the existing buffer content upon error" do - buffer = "existing content" + buffer = +"existing content" @file.seek(0, :END) -> { @file.sysread(1, buffer) }.should raise_error(EOFError) buffer.should be_empty diff --git a/spec/ruby/core/kernel/Float_spec.rb b/spec/ruby/core/kernel/Float_spec.rb index 015bcb33d6e195..0f83cb5824e9d3 100644 --- a/spec/ruby/core/kernel/Float_spec.rb +++ b/spec/ruby/core/kernel/Float_spec.rb @@ -41,7 +41,7 @@ end it "converts Strings to floats without calling #to_f" do - string = "10" + string = +"10" string.should_not_receive(:to_f) @object.send(:Float, string).should == 10.0 end diff --git a/spec/ruby/core/kernel/String_spec.rb b/spec/ruby/core/kernel/String_spec.rb index 47ee797be5b99b..7caec6eda5c384 100644 --- a/spec/ruby/core/kernel/String_spec.rb +++ b/spec/ruby/core/kernel/String_spec.rb @@ -78,7 +78,7 @@ def method_missing(meth, *args) end it "returns the same object if it is already a String" do - string = "Hello" + string = +"Hello" string.should_not_receive(:to_s) string2 = @object.send(@method, string) string.should equal(string2) diff --git a/spec/ruby/core/kernel/catch_spec.rb b/spec/ruby/core/kernel/catch_spec.rb index 4060172429a4a1..9f59d3b384e032 100644 --- a/spec/ruby/core/kernel/catch_spec.rb +++ b/spec/ruby/core/kernel/catch_spec.rb @@ -35,7 +35,7 @@ end it "raises an ArgumentError if a String with different identity is thrown" do - -> { catch("exit") { throw "exit" } }.should raise_error(ArgumentError) + -> { catch("exit".dup) { throw "exit".dup } }.should raise_error(ArgumentError) end it "catches a Symbol when thrown a matching Symbol" do diff --git a/spec/ruby/core/kernel/class_spec.rb b/spec/ruby/core/kernel/class_spec.rb index 2725bde19b47f4..b1d9df16718bb4 100644 --- a/spec/ruby/core/kernel/class_spec.rb +++ b/spec/ruby/core/kernel/class_spec.rb @@ -19,7 +19,7 @@ end it "returns the first non-singleton class" do - a = "hello" + a = +"hello" def a.my_singleton_method; end a.class.should equal(String) end diff --git a/spec/ruby/core/kernel/eval_spec.rb b/spec/ruby/core/kernel/eval_spec.rb index cf3cd47a432119..454bc4a58e5c34 100644 --- a/spec/ruby/core/kernel/eval_spec.rb +++ b/spec/ruby/core/kernel/eval_spec.rb @@ -135,7 +135,7 @@ it "includes file and line information in syntax error" do expected = 'speccing.rb' -> { - eval('if true',TOPLEVEL_BINDING, expected) + eval('if true', TOPLEVEL_BINDING, expected) }.should raise_error(SyntaxError) { |e| e.message.should =~ /#{expected}:1:.+/ } @@ -144,7 +144,7 @@ it "evaluates string with given filename and negative linenumber" do expected_file = 'speccing.rb' -> { - eval('if true',TOPLEVEL_BINDING, expected_file, -100) + eval('if true', TOPLEVEL_BINDING, expected_file, -100) }.should raise_error(SyntaxError) { |e| e.message.should =~ /#{expected_file}:-100:.+/ } @@ -350,12 +350,11 @@ class EvalSpecs end it "allows a magic encoding comment and a subsequent frozen_string_literal magic comment" do - # Make sure frozen_string_literal is not default true - eval("'foo'".b).frozen?.should be_false + frozen_string_default = "test".frozen? code = < { eval(code) }.should complain(/warning: [`']frozen_string_literal' is ignored after any tokens/, verbose: true) - EvalSpecs::Vπstring_not_frozen.frozen?.should be_false + EvalSpecs::Vπstring_not_frozen.frozen?.should == frozen_string_default EvalSpecs.send :remove_const, :Vπstring_not_frozen -> { eval(code) }.should_not complain(verbose: false) - EvalSpecs::Vπstring_not_frozen.frozen?.should be_false + EvalSpecs::Vπstring_not_frozen.frozen?.should == frozen_string_default EvalSpecs.send :remove_const, :Vπstring_not_frozen end end diff --git a/spec/ruby/core/kernel/shared/sprintf_encoding.rb b/spec/ruby/core/kernel/shared/sprintf_encoding.rb index 9cedb8b662466b..7ec0fe4c48cfd4 100644 --- a/spec/ruby/core/kernel/shared/sprintf_encoding.rb +++ b/spec/ruby/core/kernel/shared/sprintf_encoding.rb @@ -14,14 +14,14 @@ end it "returns a String in the same encoding as the format String if compatible" do - string = "%s".force_encoding(Encoding::KOI8_U) + string = "%s".dup.force_encoding(Encoding::KOI8_U) result = @method.call(string, "dogs") result.encoding.should equal(Encoding::KOI8_U) end it "returns a String in the argument's encoding if format encoding is more restrictive" do - string = "foo %s".force_encoding(Encoding::US_ASCII) - argument = "b\303\274r".force_encoding(Encoding::UTF_8) + string = "foo %s".dup.force_encoding(Encoding::US_ASCII) + argument = "b\303\274r".dup.force_encoding(Encoding::UTF_8) result = @method.call(string, argument) result.encoding.should equal(Encoding::UTF_8) @@ -56,7 +56,7 @@ end it "uses the encoding of the format string to interpret codepoints" do - format = "%c".force_encoding("euc-jp") + format = "%c".dup.force_encoding("euc-jp") result = @method.call(format, 9415601) result.encoding.should == Encoding::EUC_JP diff --git a/spec/ruby/core/marshal/dump_spec.rb b/spec/ruby/core/marshal/dump_spec.rb index 34db6fef83156e..0f77279a4fa7e0 100644 --- a/spec/ruby/core/marshal/dump_spec.rb +++ b/spec/ruby/core/marshal/dump_spec.rb @@ -76,7 +76,7 @@ end it "dumps a binary encoded Symbol" do - s = "\u2192".force_encoding("binary").to_sym + s = "\u2192".dup.force_encoding("binary").to_sym Marshal.dump(s).should == "\x04\b:\b\xE2\x86\x92" end @@ -85,8 +85,8 @@ symbol1 = "I:\t\xE2\x82\xACa\x06:\x06ET" symbol2 = "I:\t\xE2\x82\xACb\x06;\x06T" value = [ - "€a".force_encoding(Encoding::UTF_8).to_sym, - "€b".force_encoding(Encoding::UTF_8).to_sym + "€a".dup.force_encoding(Encoding::UTF_8).to_sym, + "€b".dup.force_encoding(Encoding::UTF_8).to_sym ] Marshal.dump(value).should == "\x04\b[\a#{symbol1}#{symbol2}" @@ -150,7 +150,7 @@ it "indexes instance variables of a String returned by #_dump at first and then indexes the object itself" do class MarshalSpec::M1::A def _dump(level) - s = "" + s = +"" s.instance_variable_set(:@foo, "bar") s end @@ -194,7 +194,7 @@ def _dump(level) end it "dumps a class with multibyte characters in name" do - source_object = eval("MarshalSpec::MultibyteぁあぃいClass".force_encoding(Encoding::UTF_8)) + source_object = eval("MarshalSpec::MultibyteぁあぃいClass".dup.force_encoding(Encoding::UTF_8)) Marshal.dump(source_object).should == "\x04\bc,MarshalSpec::Multibyte\xE3\x81\x81\xE3\x81\x82\xE3\x81\x83\xE3\x81\x84Class" end @@ -217,7 +217,7 @@ def _dump(level) end it "dumps a module with multibyte characters in name" do - source_object = eval("MarshalSpec::MultibyteけげこごModule".force_encoding(Encoding::UTF_8)) + source_object = eval("MarshalSpec::MultibyteけげこごModule".dup.force_encoding(Encoding::UTF_8)) Marshal.dump(source_object).should == "\x04\bm-MarshalSpec::Multibyte\xE3\x81\x91\xE3\x81\x92\xE3\x81\x93\xE3\x81\x94Module" end @@ -285,11 +285,11 @@ def _dump(level) describe "with a String" do it "dumps a blank String" do - Marshal.dump("".force_encoding("binary")).should == "\004\b\"\000" + Marshal.dump("".dup.force_encoding("binary")).should == "\004\b\"\000" end it "dumps a short String" do - Marshal.dump("short".force_encoding("binary")).should == "\004\b\"\012short" + Marshal.dump("short".dup.force_encoding("binary")).should == "\004\b\"\012short" end it "dumps a long String" do @@ -297,7 +297,7 @@ def _dump(level) end it "dumps a String extended with a Module" do - Marshal.dump("".extend(Meths).force_encoding("binary")).should == "\004\be:\nMeths\"\000" + Marshal.dump("".dup.extend(Meths).force_encoding("binary")).should == "\004\be:\nMeths\"\000" end it "dumps a String subclass" do @@ -314,23 +314,23 @@ def _dump(level) end it "dumps a String with instance variables" do - str = "" + str = +"" str.instance_variable_set("@foo", "bar") Marshal.dump(str.force_encoding("binary")).should == "\x04\bI\"\x00\x06:\t@foo\"\bbar" end it "dumps a US-ASCII String" do - str = "abc".force_encoding("us-ascii") + str = "abc".dup.force_encoding("us-ascii") Marshal.dump(str).should == "\x04\bI\"\babc\x06:\x06EF" end it "dumps a UTF-8 String" do - str = "\x6d\xc3\xb6\x68\x72\x65".force_encoding("utf-8") + str = "\x6d\xc3\xb6\x68\x72\x65".dup.force_encoding("utf-8") Marshal.dump(str).should == "\x04\bI\"\vm\xC3\xB6hre\x06:\x06ET" end it "dumps a String in another encoding" do - str = "\x6d\x00\xf6\x00\x68\x00\x72\x00\x65\x00".force_encoding("utf-16le") + str = "\x6d\x00\xf6\x00\x68\x00\x72\x00\x65\x00".dup.force_encoding("utf-16le") result = "\x04\bI\"\x0Fm\x00\xF6\x00h\x00r\x00e\x00\x06:\rencoding\"\rUTF-16LE" Marshal.dump(str).should == result end @@ -364,7 +364,7 @@ def _dump(level) end it "dumps a binary Regexp" do - o = Regexp.new("".force_encoding("binary"), Regexp::FIXEDENCODING) + o = Regexp.new("".dup.force_encoding("binary"), Regexp::FIXEDENCODING) Marshal.dump(o).should == "\x04\b/\x00\x10" end @@ -383,18 +383,18 @@ def _dump(level) end it "dumps a UTF-8 Regexp" do - o = Regexp.new("".force_encoding("utf-8"), Regexp::FIXEDENCODING) + o = Regexp.new("".dup.force_encoding("utf-8"), Regexp::FIXEDENCODING) Marshal.dump(o).should == "\x04\bI/\x00\x10\x06:\x06ET" - o = Regexp.new("a".force_encoding("utf-8"), Regexp::FIXEDENCODING) + o = Regexp.new("a".dup.force_encoding("utf-8"), Regexp::FIXEDENCODING) Marshal.dump(o).should == "\x04\bI/\x06a\x10\x06:\x06ET" - o = Regexp.new("\u3042".force_encoding("utf-8"), Regexp::FIXEDENCODING) + o = Regexp.new("\u3042".dup.force_encoding("utf-8"), Regexp::FIXEDENCODING) Marshal.dump(o).should == "\x04\bI/\b\xE3\x81\x82\x10\x06:\x06ET" end it "dumps a Regexp in another encoding" do - o = Regexp.new("".force_encoding("utf-16le"), Regexp::FIXEDENCODING) + o = Regexp.new("".dup.force_encoding("utf-16le"), Regexp::FIXEDENCODING) Marshal.dump(o).should == "\x04\bI/\x00\x10\x06:\rencoding\"\rUTF-16LE" o = Regexp.new("a".encode("utf-16le"), Regexp::FIXEDENCODING) @@ -553,7 +553,7 @@ def _dump(level) it "dumps an Object with a non-US-ASCII instance variable" do obj = Object.new - ivar = "@é".force_encoding(Encoding::UTF_8).to_sym + ivar = "@é".dup.force_encoding(Encoding::UTF_8).to_sym obj.instance_variable_set(ivar, 1) Marshal.dump(obj).should == "\x04\bo:\vObject\x06I:\b@\xC3\xA9\x06:\x06ETi\x06" end @@ -685,7 +685,7 @@ def finalizer.noop(_) end it "dumps a Time subclass with multibyte characters in name" do - source_object = eval("MarshalSpec::MultibyteぁあぃいTime".force_encoding(Encoding::UTF_8)) + source_object = eval("MarshalSpec::MultibyteぁあぃいTime".dup.force_encoding(Encoding::UTF_8)) Marshal.dump(source_object).should == "\x04\bc+MarshalSpec::Multibyte\xE3\x81\x81\xE3\x81\x82\xE3\x81\x83\xE3\x81\x84Time" end diff --git a/spec/ruby/core/marshal/fixtures/marshal_data.rb b/spec/ruby/core/marshal/fixtures/marshal_data.rb index 680cb08ac7f0a3..a508b6bea1dc48 100644 --- a/spec/ruby/core/marshal/fixtures/marshal_data.rb +++ b/spec/ruby/core/marshal/fixtures/marshal_data.rb @@ -38,7 +38,7 @@ class UserDefinedWithIvar attr_reader :a, :b, :c def initialize - @a = 'stuff' + @a = +'stuff' @a.instance_variable_set :@foo, :UserDefinedWithIvar @b = 'more' @c = @b @@ -267,7 +267,7 @@ def self.name end end - module_eval(<<~ruby.force_encoding(Encoding::UTF_8)) + module_eval(<<~ruby.dup.force_encoding(Encoding::UTF_8)) class MultibyteぁあぃいClass end @@ -313,7 +313,7 @@ class ObjectWithoutFreeze < Object "\004\b\"\012small"], "String big" => ['big' * 100, "\004\b\"\002,\001#{'big' * 100}"], - "String extended" => [''.extend(Meths), # TODO: check for module on load + "String extended" => [''.dup.extend(Meths), # TODO: check for module on load "\004\be:\nMeths\"\000"], "String subclass" => [UserString.new, "\004\bC:\017UserString\"\000"], @@ -420,7 +420,7 @@ class ObjectWithoutFreeze < Object "\x04\bI\"\nsmall\x06:\x06EF"], "String big" => ['big' * 100, "\x04\bI\"\x02,\x01bigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbig\x06:\x06EF"], - "String extended" => [''.extend(Meths), # TODO: check for module on load + "String extended" => [''.dup.extend(Meths), # TODO: check for module on load "\x04\bIe:\nMeths\"\x00\x06:\x06EF"], "String subclass" => [UserString.new, "\004\bC:\017UserString\"\000"], diff --git a/spec/ruby/core/marshal/shared/load.rb b/spec/ruby/core/marshal/shared/load.rb index b70fb7a974c02c..f599042529341a 100644 --- a/spec/ruby/core/marshal/shared/load.rb +++ b/spec/ruby/core/marshal/shared/load.rb @@ -183,7 +183,7 @@ describe "when called with a proc" do it "call the proc with frozen objects" do arr = [] - s = 'hi' + s = +'hi' s.instance_variable_set(:@foo, 5) st = Struct.new("Brittle", :a).new st.instance_variable_set(:@clue, 'none') @@ -268,7 +268,7 @@ it "loads an Array with proc" do arr = [] - s = 'hi' + s = +'hi' s.instance_variable_set(:@foo, 5) st = Struct.new("Brittle", :a).new st.instance_variable_set(:@clue, 'none') @@ -413,13 +413,13 @@ end it "raises a TypeError with bad Marshal version" do - marshal_data = '\xff\xff' + marshal_data = +'\xff\xff' marshal_data[0] = (Marshal::MAJOR_VERSION).chr marshal_data[1] = (Marshal::MINOR_VERSION + 1).chr -> { Marshal.send(@method, marshal_data) }.should raise_error(TypeError) - marshal_data = '\xff\xff' + marshal_data = +'\xff\xff' marshal_data[0] = (Marshal::MAJOR_VERSION - 1).chr marshal_data[1] = (Marshal::MINOR_VERSION).chr @@ -470,7 +470,7 @@ end it "loads an array having ivar" do - s = 'well' + s = +'well' s.instance_variable_set(:@foo, 10) obj = ['5', s, 'hi'].extend(Meths, MethsMore) obj.instance_variable_set(:@mix, s) @@ -516,7 +516,7 @@ end it "preserves hash ivars when hash contains a string having ivar" do - s = 'string' + s = +'string' s.instance_variable_set :@string_ivar, 'string ivar' h = { key: s } h.instance_variable_set :@hash_ivar, 'hash ivar' @@ -600,7 +600,7 @@ end it "loads a binary encoded Symbol" do - s = "\u2192".force_encoding("binary").to_sym + s = "\u2192".dup.force_encoding("binary").to_sym sym = Marshal.send(@method, "\x04\b:\b\xE2\x86\x92") sym.should == s sym.encoding.should == Encoding::BINARY @@ -614,8 +614,8 @@ value = Marshal.send(@method, dump) value.map(&:encoding).should == [Encoding::UTF_8, Encoding::UTF_8] expected = [ - "€a".force_encoding(Encoding::UTF_8).to_sym, - "€b".force_encoding(Encoding::UTF_8).to_sym + "€a".dup.force_encoding(Encoding::UTF_8).to_sym, + "€b".dup.force_encoding(Encoding::UTF_8).to_sym ] value.should == expected @@ -635,7 +635,7 @@ describe "for a String" do it "loads a string having ivar with ref to self" do - obj = 'hi' + obj = +'hi' obj.instance_variable_set(:@self, obj) Marshal.send(@method, "\004\bI\"\ahi\006:\n@self@\000").should == obj end @@ -663,7 +663,7 @@ def io.binmode; raise "binmode"; end end it "loads a US-ASCII String" do - str = "abc".force_encoding("us-ascii") + str = "abc".dup.force_encoding("us-ascii") data = "\x04\bI\"\babc\x06:\x06EF" result = Marshal.send(@method, data) result.should == str @@ -671,7 +671,7 @@ def io.binmode; raise "binmode"; end end it "loads a UTF-8 String" do - str = "\x6d\xc3\xb6\x68\x72\x65".force_encoding("utf-8") + str = "\x6d\xc3\xb6\x68\x72\x65".dup.force_encoding("utf-8") data = "\x04\bI\"\vm\xC3\xB6hre\x06:\x06ET" result = Marshal.send(@method, data) result.should == str @@ -679,7 +679,7 @@ def io.binmode; raise "binmode"; end end it "loads a String in another encoding" do - str = "\x6d\x00\xf6\x00\x68\x00\x72\x00\x65\x00".force_encoding("utf-16le") + str = "\x6d\x00\xf6\x00\x68\x00\x72\x00\x65\x00".dup.force_encoding("utf-16le") data = "\x04\bI\"\x0Fm\x00\xF6\x00h\x00r\x00e\x00\x06:\rencoding\"\rUTF-16LE" result = Marshal.send(@method, data) result.should == str @@ -687,8 +687,8 @@ def io.binmode; raise "binmode"; end end it "loads a String as BINARY if no encoding is specified at the end" do - str = "\xC3\xB8".force_encoding("BINARY") - data = "\x04\b\"\a\xC3\xB8".force_encoding("UTF-8") + str = "\xC3\xB8".dup.force_encoding("BINARY") + data = "\x04\b\"\a\xC3\xB8".dup.force_encoding("UTF-8") result = Marshal.send(@method, data) result.encoding.should == Encoding::BINARY result.should == str @@ -823,7 +823,7 @@ def io.binmode; raise "binmode"; end end it "loads an Object with a non-US-ASCII instance variable" do - ivar = "@é".force_encoding(Encoding::UTF_8).to_sym + ivar = "@é".dup.force_encoding(Encoding::UTF_8).to_sym obj = Marshal.send(@method, "\x04\bo:\vObject\x06I:\b@\xC3\xA9\x06:\x06ETi\x06") obj.instance_variables.should == [ivar] obj.instance_variables[0].encoding.should == Encoding::UTF_8 diff --git a/spec/ruby/core/matchdata/element_reference_spec.rb b/spec/ruby/core/matchdata/element_reference_spec.rb index 1be399cfe1ed14..806db2d7b503b5 100644 --- a/spec/ruby/core/matchdata/element_reference_spec.rb +++ b/spec/ruby/core/matchdata/element_reference_spec.rb @@ -113,7 +113,7 @@ it "returns matches in the String's encoding" do rex = /(?t(?ack))/u - md = 'haystack'.force_encoding('euc-jp').match(rex) + md = 'haystack'.dup.force_encoding('euc-jp').match(rex) md[:t].encoding.should == Encoding::EUC_JP end end diff --git a/spec/ruby/core/matchdata/post_match_spec.rb b/spec/ruby/core/matchdata/post_match_spec.rb index b8d1e032eb29ea..7bfe6df119b299 100644 --- a/spec/ruby/core/matchdata/post_match_spec.rb +++ b/spec/ruby/core/matchdata/post_match_spec.rb @@ -8,12 +8,12 @@ end it "sets the encoding to the encoding of the source String" do - str = "abc".force_encoding Encoding::EUC_JP + str = "abc".dup.force_encoding Encoding::EUC_JP str.match(/b/).post_match.encoding.should equal(Encoding::EUC_JP) end it "sets an empty result to the encoding of the source String" do - str = "abc".force_encoding Encoding::ISO_8859_1 + str = "abc".dup.force_encoding Encoding::ISO_8859_1 str.match(/c/).post_match.encoding.should equal(Encoding::ISO_8859_1) end diff --git a/spec/ruby/core/matchdata/pre_match_spec.rb b/spec/ruby/core/matchdata/pre_match_spec.rb index 741cb6e9236f56..2f1ba9b8f64702 100644 --- a/spec/ruby/core/matchdata/pre_match_spec.rb +++ b/spec/ruby/core/matchdata/pre_match_spec.rb @@ -8,12 +8,12 @@ end it "sets the encoding to the encoding of the source String" do - str = "abc".force_encoding Encoding::EUC_JP + str = "abc".dup.force_encoding Encoding::EUC_JP str.match(/b/).pre_match.encoding.should equal(Encoding::EUC_JP) end it "sets an empty result to the encoding of the source String" do - str = "abc".force_encoding Encoding::ISO_8859_1 + str = "abc".dup.force_encoding Encoding::ISO_8859_1 str.match(/a/).pre_match.encoding.should equal(Encoding::ISO_8859_1) end diff --git a/spec/ruby/core/matchdata/string_spec.rb b/spec/ruby/core/matchdata/string_spec.rb index 420233e1f39b2a..952e95331849c1 100644 --- a/spec/ruby/core/matchdata/string_spec.rb +++ b/spec/ruby/core/matchdata/string_spec.rb @@ -17,8 +17,9 @@ md.string.should equal(md.string) end - it "returns a frozen copy of the matched string for gsub(String)" do - 'he[[o'.gsub!('[', ']') + it "returns a frozen copy of the matched string for gsub!(String)" do + s = +'he[[o' + s.gsub!('[', ']') $~.string.should == 'he[[o' $~.string.should.frozen? end diff --git a/spec/ruby/core/method/to_proc_spec.rb b/spec/ruby/core/method/to_proc_spec.rb index 29b7bec2b30790..4993cce2392c81 100644 --- a/spec/ruby/core/method/to_proc_spec.rb +++ b/spec/ruby/core/method/to_proc_spec.rb @@ -35,7 +35,7 @@ end it "returns a proc that can be used by define_method" do - x = 'test' + x = +'test' to_s = class << x define_method :foo, method(:to_s).to_proc to_s diff --git a/spec/ruby/core/module/autoload_spec.rb b/spec/ruby/core/module/autoload_spec.rb index 271c55ebf0ea02..45d18b86080044 100644 --- a/spec/ruby/core/module/autoload_spec.rb +++ b/spec/ruby/core/module/autoload_spec.rb @@ -406,6 +406,8 @@ def check_before_during_thread_after(const, &check) before :each do @path = fixture(__FILE__, "autoload_during_autoload_after_define.rb") ModuleSpecs::Autoload.autoload :DuringAutoloadAfterDefine, @path + @autoload_location = [__FILE__, __LINE__ - 1] + @const_location = [@path, 2] @remove << :DuringAutoloadAfterDefine raise unless ModuleSpecs::Autoload.autoload?(:DuringAutoloadAfterDefine) == @path end @@ -437,6 +439,15 @@ def check_before_during_thread_after(const, &check) } results.should == [@path, nil, @path, nil] end + + ruby_bug("#20188", ""..."3.4") do + it "returns the real constant location in autoload thread and returns the autoload location in other threads for Module#const_source_location" do + results = check_before_during_thread_after(:DuringAutoloadAfterDefine) { + ModuleSpecs::Autoload.const_source_location(:DuringAutoloadAfterDefine) + } + results.should == [@autoload_location, @const_location, @autoload_location, @const_location] + end + end end it "does not remove the constant from Module#constants if load fails and keeps it as an autoload" do diff --git a/spec/ruby/core/module/const_source_location_spec.rb b/spec/ruby/core/module/const_source_location_spec.rb index ded2aa51d7daed..c194c9113f7a54 100644 --- a/spec/ruby/core/module/const_source_location_spec.rb +++ b/spec/ruby/core/module/const_source_location_spec.rb @@ -233,5 +233,17 @@ line = ConstantSpecs::CONST_LOCATION ConstantSpecs.const_source_location('CONST_LOCATION').should == [file, line] end + + ruby_bug("#20188", ""..."3.4") do + it 'returns the real constant location as soon as it is defined' do + file = fixture(__FILE__, 'autoload_const_source_location.rb') + ConstantSpecs.autoload :ConstSource, file + autoload_location = [__FILE__, __LINE__ - 1] + + ConstantSpecs.const_source_location(:ConstSource).should == autoload_location + ConstantSpecs::ConstSource::LOCATION.should == ConstantSpecs.const_source_location(:ConstSource) + ConstantSpecs::BEFORE_DEFINE_LOCATION.should == autoload_location + end + end end end diff --git a/spec/ruby/core/module/fixtures/autoload_const_source_location.rb b/spec/ruby/core/module/fixtures/autoload_const_source_location.rb new file mode 100644 index 00000000000000..ee0e5a689ffeef --- /dev/null +++ b/spec/ruby/core/module/fixtures/autoload_const_source_location.rb @@ -0,0 +1,6 @@ +module ConstantSpecs + BEFORE_DEFINE_LOCATION = const_source_location(:ConstSource) + module ConstSource + LOCATION = Object.const_source_location(name) + end +end diff --git a/spec/ruby/core/module/using_spec.rb b/spec/ruby/core/module/using_spec.rb index 4781b99bb7b483..a908363c960114 100644 --- a/spec/ruby/core/module/using_spec.rb +++ b/spec/ruby/core/module/using_spec.rb @@ -316,7 +316,7 @@ def foo; "foo from refinement"; end using refinement def initialize - @a = "1703" + @a = +"1703" @a.instance_eval do def abc diff --git a/spec/ruby/core/objectspace/define_finalizer_spec.rb b/spec/ruby/core/objectspace/define_finalizer_spec.rb index 6be83e518e4494..effecc41d0a6de 100644 --- a/spec/ruby/core/objectspace/define_finalizer_spec.rb +++ b/spec/ruby/core/objectspace/define_finalizer_spec.rb @@ -52,7 +52,7 @@ def scoped Proc.new { puts "finalizer run" } end handler = scoped - obj = "Test" + obj = +"Test" ObjectSpace.define_finalizer(obj, handler) exit 0 RUBY @@ -111,7 +111,7 @@ def initialize it "calls a finalizer at exit even if it is self-referencing" do code = <<-RUBY - obj = "Test" + obj = +"Test" handler = Proc.new { puts "finalizer run" } ObjectSpace.define_finalizer(obj, handler) exit 0 @@ -141,9 +141,9 @@ def finalizer(zelf) it "calls a finalizer defined in a finalizer running at exit" do code = <<-RUBY - obj = "Test" + obj = +"Test" handler = Proc.new do - obj2 = "Test" + obj2 = +"Test" handler2 = Proc.new { puts "finalizer 2 run" } ObjectSpace.define_finalizer(obj2, handler2) exit 0 diff --git a/spec/ruby/core/proc/fixtures/proc_aref.rb b/spec/ruby/core/proc/fixtures/proc_aref.rb index a305667797ed80..8ee355b14ca6df 100644 --- a/spec/ruby/core/proc/fixtures/proc_aref.rb +++ b/spec/ruby/core/proc/fixtures/proc_aref.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: false module ProcArefSpecs def self.aref proc {|a| a }["sometext"] diff --git a/spec/ruby/core/range/size_spec.rb b/spec/ruby/core/range/size_spec.rb index 81ea5a3846ab37..a1fe3ce17de3d7 100644 --- a/spec/ruby/core/range/size_spec.rb +++ b/spec/ruby/core/range/size_spec.rb @@ -4,34 +4,22 @@ it "returns the number of elements in the range" do (1..16).size.should == 16 (1...16).size.should == 15 - - (1.0..16.0).size.should == 16 - (1.0...16.0).size.should == 15 - (1.0..15.9).size.should == 15 - (1.1..16.0).size.should == 15 - (1.1..15.9).size.should == 15 end it "returns 0 if last is less than first" do (16..0).size.should == 0 - (16.0..0.0).size.should == 0 - (Float::INFINITY..0).size.should == 0 end it 'returns Float::INFINITY for increasing, infinite ranges' do (0..Float::INFINITY).size.should == Float::INFINITY - (-Float::INFINITY..0).size.should == Float::INFINITY - (-Float::INFINITY..Float::INFINITY).size.should == Float::INFINITY end it 'returns Float::INFINITY for endless ranges if the start is numeric' do eval("(1..)").size.should == Float::INFINITY - eval("(0.5...)").size.should == Float::INFINITY end it 'returns nil for endless ranges if the start is not numeric' do eval("('z'..)").size.should == nil - eval("([]...)").size.should == nil end ruby_version_is ""..."3.2" do @@ -43,7 +31,7 @@ end end - ruby_version_is "3.2" do + ruby_version_is "3.2"..."3.4" do it 'returns Float::INFINITY for all beginless ranges if the end is numeric' do (..1).size.should == Float::INFINITY (...0.5).size.should == Float::INFINITY @@ -58,6 +46,54 @@ end end + ruby_version_is ""..."3.4" do + it "returns the number of elements in the range" do + (1.0..16.0).size.should == 16 + (1.0...16.0).size.should == 15 + (1.0..15.9).size.should == 15 + (1.1..16.0).size.should == 15 + (1.1..15.9).size.should == 15 + end + + it "returns 0 if last is less than first" do + (16.0..0.0).size.should == 0 + (Float::INFINITY..0).size.should == 0 + end + + it 'returns Float::INFINITY for increasing, infinite ranges' do + (-Float::INFINITY..0).size.should == Float::INFINITY + (-Float::INFINITY..Float::INFINITY).size.should == Float::INFINITY + end + + it 'returns Float::INFINITY for endless ranges if the start is numeric' do + eval("(0.5...)").size.should == Float::INFINITY + end + + it 'returns nil for endless ranges if the start is not numeric' do + eval("([]...)").size.should == nil + end + end + + ruby_version_is "3.4" do + it 'raises TypeError if a range is not iterable' do + -> { (1.0..16.0).size }.should raise_error(TypeError, /can't iterate from/) + -> { (1.0...16.0).size }.should raise_error(TypeError, /can't iterate from/) + -> { (1.0..15.9).size }.should raise_error(TypeError, /can't iterate from/) + -> { (1.1..16.0).size }.should raise_error(TypeError, /can't iterate from/) + -> { (1.1..15.9).size }.should raise_error(TypeError, /can't iterate from/) + -> { (16.0..0.0).size }.should raise_error(TypeError, /can't iterate from/) + -> { (Float::INFINITY..0).size }.should raise_error(TypeError, /can't iterate from/) + -> { (-Float::INFINITY..0).size }.should raise_error(TypeError, /can't iterate from/) + -> { (-Float::INFINITY..Float::INFINITY).size }.should raise_error(TypeError, /can't iterate from/) + -> { (..1).size }.should raise_error(TypeError, /can't iterate from/) + -> { (...0.5).size }.should raise_error(TypeError, /can't iterate from/) + -> { (..nil).size }.should raise_error(TypeError, /can't iterate from/) + -> { (...'o').size }.should raise_error(TypeError, /can't iterate from/) + -> { eval("(0.5...)").size }.should raise_error(TypeError, /can't iterate from/) + -> { eval("([]...)").size }.should raise_error(TypeError, /can't iterate from/) + end + end + it "returns nil if first and last are not Numeric" do (:a..:z).size.should be_nil ('a'..'z').size.should be_nil diff --git a/spec/ruby/core/regexp/shared/new.rb b/spec/ruby/core/regexp/shared/new.rb index 06c32e36cd9149..7c3fabf61298c7 100644 --- a/spec/ruby/core/regexp/shared/new.rb +++ b/spec/ruby/core/regexp/shared/new.rb @@ -489,12 +489,12 @@ def obj.to_int() ScratchPad.record(:called) end end it "returns a Regexp with the input String's encoding" do - str = "\x82\xa0".force_encoding(Encoding::Shift_JIS) + str = "\x82\xa0".dup.force_encoding(Encoding::Shift_JIS) Regexp.send(@method, str).encoding.should == Encoding::Shift_JIS end it "returns a Regexp with source String having the input String's encoding" do - str = "\x82\xa0".force_encoding(Encoding::Shift_JIS) + str = "\x82\xa0".dup.force_encoding(Encoding::Shift_JIS) Regexp.send(@method, str).source.encoding.should == Encoding::Shift_JIS end end diff --git a/spec/ruby/core/regexp/shared/quote.rb b/spec/ruby/core/regexp/shared/quote.rb index 953310276692f9..b5ecc35f042390 100644 --- a/spec/ruby/core/regexp/shared/quote.rb +++ b/spec/ruby/core/regexp/shared/quote.rb @@ -18,23 +18,23 @@ end it "works for broken strings" do - Regexp.send(@method, "a.\x85b.".force_encoding("US-ASCII")).should =="a\\.\x85b\\.".force_encoding("US-ASCII") - Regexp.send(@method, "a.\x80".force_encoding("UTF-8")).should == "a\\.\x80".force_encoding("UTF-8") + Regexp.send(@method, "a.\x85b.".dup.force_encoding("US-ASCII")).should =="a\\.\x85b\\.".dup.force_encoding("US-ASCII") + Regexp.send(@method, "a.\x80".dup.force_encoding("UTF-8")).should == "a\\.\x80".dup.force_encoding("UTF-8") end it "sets the encoding of the result to US-ASCII if there are only US-ASCII characters present in the input String" do - str = "abc".force_encoding("euc-jp") + str = "abc".dup.force_encoding("euc-jp") Regexp.send(@method, str).encoding.should == Encoding::US_ASCII end it "sets the encoding of the result to the encoding of the String if any non-US-ASCII characters are present in an input String with valid encoding" do - str = "ありがとう".force_encoding("utf-8") + str = "ありがとう".dup.force_encoding("utf-8") str.valid_encoding?.should be_true Regexp.send(@method, str).encoding.should == Encoding::UTF_8 end it "sets the encoding of the result to BINARY if any non-US-ASCII characters are present in an input String with invalid encoding" do - str = "\xff".force_encoding "us-ascii" + str = "\xff".dup.force_encoding "us-ascii" str.valid_encoding?.should be_false Regexp.send(@method, "\xff").encoding.should == Encoding::BINARY end diff --git a/spec/ruby/core/string/ascii_only_spec.rb b/spec/ruby/core/string/ascii_only_spec.rb index c7e02fd8743b8b..88a0559cfda1c2 100644 --- a/spec/ruby/core/string/ascii_only_spec.rb +++ b/spec/ruby/core/string/ascii_only_spec.rb @@ -7,12 +7,12 @@ it "returns true if the encoding is UTF-8" do [ ["hello", true], ["hello".encode('UTF-8'), true], - ["hello".force_encoding('UTF-8'), true], + ["hello".dup.force_encoding('UTF-8'), true], ].should be_computed_by(:ascii_only?) end it "returns true if the encoding is US-ASCII" do - "hello".force_encoding(Encoding::US_ASCII).ascii_only?.should be_true + "hello".dup.force_encoding(Encoding::US_ASCII).ascii_only?.should be_true "hello".encode(Encoding::US_ASCII).ascii_only?.should be_true end @@ -34,13 +34,13 @@ [ ["\u{6666}", false], ["hello, \u{6666}", false], ["\u{6666}".encode('UTF-8'), false], - ["\u{6666}".force_encoding('UTF-8'), false], + ["\u{6666}".dup.force_encoding('UTF-8'), false], ].should be_computed_by(:ascii_only?) end it "returns false if the encoding is US-ASCII" do - [ ["\u{6666}".force_encoding(Encoding::US_ASCII), false], - ["hello, \u{6666}".force_encoding(Encoding::US_ASCII), false], + [ ["\u{6666}".dup.force_encoding(Encoding::US_ASCII), false], + ["hello, \u{6666}".dup.force_encoding(Encoding::US_ASCII), false], ].should be_computed_by(:ascii_only?) end end @@ -51,17 +51,16 @@ end it "returns false for the empty String with a non-ASCII-compatible encoding" do - "".force_encoding('UTF-16LE').ascii_only?.should be_false + "".dup.force_encoding('UTF-16LE').ascii_only?.should be_false "".encode('UTF-16BE').ascii_only?.should be_false end it "returns false for a non-empty String with non-ASCII-compatible encoding" do - "\x78\x00".force_encoding("UTF-16LE").ascii_only?.should be_false + "\x78\x00".dup.force_encoding("UTF-16LE").ascii_only?.should be_false end it "returns false when interpolating non ascii strings" do - base = "EU currency is" - base.force_encoding(Encoding::US_ASCII) + base = "EU currency is".dup.force_encoding(Encoding::US_ASCII) euro = "\u20AC" interp = "#{base} #{euro}" euro.ascii_only?.should be_false @@ -70,14 +69,14 @@ end it "returns false after appending non ASCII characters to an empty String" do - ("" << "λ").ascii_only?.should be_false + ("".dup << "λ").ascii_only?.should be_false end it "returns false when concatenating an ASCII and non-ASCII String" do - "".concat("λ").ascii_only?.should be_false + "".dup.concat("λ").ascii_only?.should be_false end it "returns false when replacing an ASCII String with a non-ASCII String" do - "".replace("λ").ascii_only?.should be_false + "".dup.replace("λ").ascii_only?.should be_false end end diff --git a/spec/ruby/core/string/b_spec.rb b/spec/ruby/core/string/b_spec.rb index 37c7994700a07e..4b1fafff117806 100644 --- a/spec/ruby/core/string/b_spec.rb +++ b/spec/ruby/core/string/b_spec.rb @@ -1,4 +1,5 @@ # -*- encoding: utf-8 -*- +# frozen_string_literal: false require_relative '../../spec_helper' describe "String#b" do diff --git a/spec/ruby/core/string/byteindex_spec.rb b/spec/ruby/core/string/byteindex_spec.rb index 7be0c7ec1ef9c1..47c7be10298b5e 100644 --- a/spec/ruby/core/string/byteindex_spec.rb +++ b/spec/ruby/core/string/byteindex_spec.rb @@ -156,11 +156,11 @@ end it "handles a substring in a superset encoding" do - 'abc'.force_encoding(Encoding::US_ASCII).byteindex('é').should == nil + 'abc'.dup.force_encoding(Encoding::US_ASCII).byteindex('é').should == nil end it "handles a substring in a subset encoding" do - 'été'.byteindex('t'.force_encoding(Encoding::US_ASCII)).should == 2 + 'été'.byteindex('t'.dup.force_encoding(Encoding::US_ASCII)).should == 2 end end end diff --git a/spec/ruby/core/string/byterindex_spec.rb b/spec/ruby/core/string/byterindex_spec.rb index 717708c97d364b..150f709b900684 100644 --- a/spec/ruby/core/string/byterindex_spec.rb +++ b/spec/ruby/core/string/byterindex_spec.rb @@ -191,11 +191,11 @@ def obj.method_missing(*args) 5 end end it "handles a substring in a superset encoding" do - 'abc'.force_encoding(Encoding::US_ASCII).byterindex('é').should == nil + 'abc'.dup.force_encoding(Encoding::US_ASCII).byterindex('é').should == nil end it "handles a substring in a subset encoding" do - 'été'.byterindex('t'.force_encoding(Encoding::US_ASCII)).should == 2 + 'été'.byterindex('t'.dup.force_encoding(Encoding::US_ASCII)).should == 2 end end end diff --git a/spec/ruby/core/string/bytes_spec.rb b/spec/ruby/core/string/bytes_spec.rb index 859b3465506d81..02151eebbcc678 100644 --- a/spec/ruby/core/string/bytes_spec.rb +++ b/spec/ruby/core/string/bytes_spec.rb @@ -50,6 +50,6 @@ end it "is unaffected by #force_encoding" do - @utf8.force_encoding('ASCII').bytes.to_a.should == @utf8.bytes.to_a + @utf8.dup.force_encoding('ASCII').bytes.to_a.should == @utf8.bytes.to_a end end diff --git a/spec/ruby/core/string/bytesize_spec.rb b/spec/ruby/core/string/bytesize_spec.rb index a31f3ae67165aa..2bbefc08207b2a 100644 --- a/spec/ruby/core/string/bytesize_spec.rb +++ b/spec/ruby/core/string/bytesize_spec.rb @@ -13,21 +13,21 @@ end it "works with pseudo-ASCII strings containing single UTF-8 characters" do - "\u{6666}".force_encoding('ASCII').bytesize.should == 3 + "\u{6666}".dup.force_encoding('ASCII').bytesize.should == 3 end it "works with strings containing UTF-8 characters" do - "c \u{6666}".force_encoding('UTF-8').bytesize.should == 5 + "c \u{6666}".dup.force_encoding('UTF-8').bytesize.should == 5 "c \u{6666}".bytesize.should == 5 end it "works with pseudo-ASCII strings containing UTF-8 characters" do - "c \u{6666}".force_encoding('ASCII').bytesize.should == 5 + "c \u{6666}".dup.force_encoding('ASCII').bytesize.should == 5 end it "returns 0 for the empty string" do "".bytesize.should == 0 - "".force_encoding('ASCII').bytesize.should == 0 - "".force_encoding('UTF-8').bytesize.should == 0 + "".dup.force_encoding('ASCII').bytesize.should == 0 + "".dup.force_encoding('UTF-8').bytesize.should == 0 end end diff --git a/spec/ruby/core/string/byteslice_spec.rb b/spec/ruby/core/string/byteslice_spec.rb index 312229523de6f8..5b1027f4a579b9 100644 --- a/spec/ruby/core/string/byteslice_spec.rb +++ b/spec/ruby/core/string/byteslice_spec.rb @@ -19,10 +19,10 @@ describe "String#byteslice on on non ASCII strings" do it "returns byteslice of unicode strings" do - "\u3042".byteslice(1).should == "\x81".force_encoding("UTF-8") - "\u3042".byteslice(1, 2).should == "\x81\x82".force_encoding("UTF-8") - "\u3042".byteslice(1..2).should == "\x81\x82".force_encoding("UTF-8") - "\u3042".byteslice(-1).should == "\x82".force_encoding("UTF-8") + "\u3042".byteslice(1).should == "\x81".dup.force_encoding("UTF-8") + "\u3042".byteslice(1, 2).should == "\x81\x82".dup.force_encoding("UTF-8") + "\u3042".byteslice(1..2).should == "\x81\x82".dup.force_encoding("UTF-8") + "\u3042".byteslice(-1).should == "\x82".dup.force_encoding("UTF-8") end it "returns a String in the same encoding as self" do diff --git a/spec/ruby/core/string/bytesplice_spec.rb b/spec/ruby/core/string/bytesplice_spec.rb index f13024a79b45b9..967edcba2981ed 100644 --- a/spec/ruby/core/string/bytesplice_spec.rb +++ b/spec/ruby/core/string/bytesplice_spec.rb @@ -1,4 +1,5 @@ # -*- encoding: utf-8 -*- +# frozen_string_literal: false require_relative '../../spec_helper' describe "String#bytesplice" do diff --git a/spec/ruby/core/string/capitalize_spec.rb b/spec/ruby/core/string/capitalize_spec.rb index b79e9cfdbd7781..5e59b656c52d56 100644 --- a/spec/ruby/core/string/capitalize_spec.rb +++ b/spec/ruby/core/string/capitalize_spec.rb @@ -90,7 +90,7 @@ describe "String#capitalize!" do it "capitalizes self in place" do - a = "hello" + a = +"hello" a.capitalize!.should equal(a) a.should == "Hello" end @@ -103,13 +103,13 @@ describe "full Unicode case mapping" do it "modifies self in place for all of Unicode with no option" do - a = "äöÜ" + a = +"äöÜ" a.capitalize! a.should == "Äöü" end it "only capitalizes the first resulting character when upcasing a character produces a multi-character sequence" do - a = "ß" + a = +"ß" a.capitalize! a.should == "Ss" end @@ -121,7 +121,7 @@ end it "updates string metadata" do - capitalized = "ßeT" + capitalized = +"ßeT" capitalized.capitalize! capitalized.should == "Sset" @@ -133,7 +133,7 @@ describe "modifies self in place for ASCII-only case mapping" do it "does not capitalize non-ASCII characters" do - a = "ßet" + a = +"ßet" a.capitalize!(:ascii) a.should == "ßet" end @@ -147,13 +147,13 @@ describe "modifies self in place for full Unicode case mapping adapted for Turkic languages" do it "capitalizes ASCII characters according to Turkic semantics" do - a = "iSa" + a = +"iSa" a.capitalize!(:turkic) a.should == "İsa" end it "allows Lithuanian as an extra option" do - a = "iSa" + a = +"iSa" a.capitalize!(:turkic, :lithuanian) a.should == "İsa" end @@ -165,13 +165,13 @@ describe "modifies self in place for full Unicode case mapping adapted for Lithuanian" do it "currently works the same as full Unicode case mapping" do - a = "iß" + a = +"iß" a.capitalize!(:lithuanian) a.should == "Iß" end it "allows Turkic as an extra option (and applies Turkic semantics)" do - a = "iß" + a = +"iß" a.capitalize!(:lithuanian, :turkic) a.should == "İß" end @@ -190,12 +190,12 @@ end it "returns nil when no changes are made" do - a = "Hello" + a = +"Hello" a.capitalize!.should == nil a.should == "Hello" - "".capitalize!.should == nil - "H".capitalize!.should == nil + (+"").capitalize!.should == nil + (+"H").capitalize!.should == nil end it "raises a FrozenError when self is frozen" do diff --git a/spec/ruby/core/string/center_spec.rb b/spec/ruby/core/string/center_spec.rb index a59dd2a91bc088..1667b59327d104 100644 --- a/spec/ruby/core/string/center_spec.rb +++ b/spec/ruby/core/string/center_spec.rb @@ -92,7 +92,7 @@ describe "with width" do it "returns a String in the same encoding as the original" do - str = "abc".force_encoding Encoding::IBM437 + str = "abc".dup.force_encoding Encoding::IBM437 result = str.center 6 result.should == " abc " result.encoding.should equal(Encoding::IBM437) @@ -101,7 +101,7 @@ describe "with width, pattern" do it "returns a String in the compatible encoding" do - str = "abc".force_encoding Encoding::IBM437 + str = "abc".dup.force_encoding Encoding::IBM437 result = str.center 6, "あ" result.should == "あabcああ" result.encoding.should equal(Encoding::UTF_8) diff --git a/spec/ruby/core/string/chilled_string_spec.rb b/spec/ruby/core/string/chilled_string_spec.rb new file mode 100644 index 00000000000000..8de4fc421b4765 --- /dev/null +++ b/spec/ruby/core/string/chilled_string_spec.rb @@ -0,0 +1,69 @@ +require_relative '../../spec_helper' + +describe "chilled String" do + guard -> { ruby_version_is "3.4" and !"test".equal?("test") } do + describe "#frozen?" do + it "returns true" do + "chilled".frozen?.should == true + end + end + + describe "#-@" do + it "returns a different instance" do + input = "chilled" + interned = (-input) + interned.frozen?.should == true + interned.object_id.should_not == input.object_id + end + end + + describe "#+@" do + it "returns a different instance" do + input = "chilled" + duped = (+input) + duped.frozen?.should == false + duped.object_id.should_not == input.object_id + end + end + + describe "#clone" do + it "preserves chilled status" do + input = "chilled".clone + -> { + input << "-mutated" + }.should complain(/literal string will be frozen in the future/) + input.should == "chilled-mutated" + end + end + + describe "mutation" do + it "emits a warning" do + input = "chilled" + -> { + input << "-mutated" + }.should complain(/literal string will be frozen in the future/) + input.should == "chilled-mutated" + end + + it "emits a warning on singleton_class creaation" do + -> { + "chilled".singleton_class + }.should complain(/literal string will be frozen in the future/) + end + + it "emits a warning on instance variable assignment" do + -> { + "chilled".instance_variable_set(:@ivar, 42) + }.should complain(/literal string will be frozen in the future/) + end + + it "raises FrozenError after the string was explictly frozen" do + input = "chilled" + input.freeze + -> { + input << "mutated" + }.should raise_error(FrozenError) + end + end + end +end diff --git a/spec/ruby/core/string/chomp_spec.rb b/spec/ruby/core/string/chomp_spec.rb index ec0490220bd903..d27c84c6f6a33f 100644 --- a/spec/ruby/core/string/chomp_spec.rb +++ b/spec/ruby/core/string/chomp_spec.rb @@ -1,4 +1,5 @@ # -*- encoding: utf-8 -*- +# frozen_string_literal: false require_relative '../../spec_helper' require_relative 'fixtures/classes' diff --git a/spec/ruby/core/string/chop_spec.rb b/spec/ruby/core/string/chop_spec.rb index 75f25b39cd507e..99c2c821909ec0 100644 --- a/spec/ruby/core/string/chop_spec.rb +++ b/spec/ruby/core/string/chop_spec.rb @@ -1,4 +1,5 @@ # -*- encoding: utf-8 -*- +# frozen_string_literal: false require_relative '../../spec_helper' require_relative 'fixtures/classes' diff --git a/spec/ruby/core/string/clear_spec.rb b/spec/ruby/core/string/clear_spec.rb index e1d68e03bd2268..152986fd0fee9c 100644 --- a/spec/ruby/core/string/clear_spec.rb +++ b/spec/ruby/core/string/clear_spec.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: false require_relative '../../spec_helper' describe "String#clear" do diff --git a/spec/ruby/core/string/codepoints_spec.rb b/spec/ruby/core/string/codepoints_spec.rb index 0b6cde82f7385d..b276d0baa891c1 100644 --- a/spec/ruby/core/string/codepoints_spec.rb +++ b/spec/ruby/core/string/codepoints_spec.rb @@ -11,7 +11,7 @@ end it "raises an ArgumentError when no block is given if self has an invalid encoding" do - s = "\xDF".force_encoding(Encoding::UTF_8) + s = "\xDF".dup.force_encoding(Encoding::UTF_8) s.valid_encoding?.should be_false -> { s.codepoints }.should raise_error(ArgumentError) end diff --git a/spec/ruby/core/string/comparison_spec.rb b/spec/ruby/core/string/comparison_spec.rb index 91cfdca25a10c8..9db0cff5ee2277 100644 --- a/spec/ruby/core/string/comparison_spec.rb +++ b/spec/ruby/core/string/comparison_spec.rb @@ -61,12 +61,12 @@ end it "ignores encoding difference" do - ("ÄÖÛ".force_encoding("utf-8") <=> "ÄÖÜ".force_encoding("iso-8859-1")).should == -1 - ("ÄÖÜ".force_encoding("utf-8") <=> "ÄÖÛ".force_encoding("iso-8859-1")).should == 1 + ("ÄÖÛ".dup.force_encoding("utf-8") <=> "ÄÖÜ".dup.force_encoding("iso-8859-1")).should == -1 + ("ÄÖÜ".dup.force_encoding("utf-8") <=> "ÄÖÛ".dup.force_encoding("iso-8859-1")).should == 1 end it "returns 0 with identical ASCII-compatible bytes of different encodings" do - ("abc".force_encoding("utf-8") <=> "abc".force_encoding("iso-8859-1")).should == 0 + ("abc".dup.force_encoding("utf-8") <=> "abc".dup.force_encoding("iso-8859-1")).should == 0 end it "compares the indices of the encodings when the strings have identical non-ASCII-compatible bytes" do @@ -77,7 +77,7 @@ end it "returns 0 when comparing 2 empty strings but one is not ASCII-compatible" do - ("" <=> "".force_encoding('iso-2022-jp')).should == 0 + ("" <=> "".dup.force_encoding('iso-2022-jp')).should == 0 end end diff --git a/spec/ruby/core/string/concat_spec.rb b/spec/ruby/core/string/concat_spec.rb index 6f487eaa3ad0ba..cbd7df54e2bd0a 100644 --- a/spec/ruby/core/string/concat_spec.rb +++ b/spec/ruby/core/string/concat_spec.rb @@ -8,19 +8,19 @@ it_behaves_like :string_concat_type_coercion, :concat it "takes multiple arguments" do - str = "hello " + str = +"hello " str.concat "wo", "", "rld" str.should == "hello world" end it "concatenates the initial value when given arguments contain 2 self" do - str = "hello" + str = +"hello" str.concat str, str str.should == "hellohellohello" end it "returns self when given no arguments" do - str = "hello" + str = +"hello" str.concat.should equal(str) str.should == "hello" end diff --git a/spec/ruby/core/string/delete_prefix_spec.rb b/spec/ruby/core/string/delete_prefix_spec.rb index 4214fdecce2937..ee7f04490579cc 100644 --- a/spec/ruby/core/string/delete_prefix_spec.rb +++ b/spec/ruby/core/string/delete_prefix_spec.rb @@ -1,4 +1,5 @@ # -*- encoding: utf-8 -*- +# frozen_string_literal: false require_relative '../../spec_helper' require_relative 'fixtures/classes' diff --git a/spec/ruby/core/string/delete_spec.rb b/spec/ruby/core/string/delete_spec.rb index 3b9aa4fb75bd48..6d359776e45f71 100644 --- a/spec/ruby/core/string/delete_spec.rb +++ b/spec/ruby/core/string/delete_spec.rb @@ -1,4 +1,5 @@ # -*- encoding: utf-8 -*- +# frozen_string_literal: false require_relative '../../spec_helper' require_relative 'fixtures/classes' diff --git a/spec/ruby/core/string/delete_suffix_spec.rb b/spec/ruby/core/string/delete_suffix_spec.rb index 9381f4cee7ce28..1842d75aa5e1d9 100644 --- a/spec/ruby/core/string/delete_suffix_spec.rb +++ b/spec/ruby/core/string/delete_suffix_spec.rb @@ -1,4 +1,5 @@ # -*- encoding: utf-8 -*- +# frozen_string_literal: false require_relative '../../spec_helper' require_relative 'fixtures/classes' diff --git a/spec/ruby/core/string/downcase_spec.rb b/spec/ruby/core/string/downcase_spec.rb index 7ee9d6df1d9078..2d260f23f181e4 100644 --- a/spec/ruby/core/string/downcase_spec.rb +++ b/spec/ruby/core/string/downcase_spec.rb @@ -1,4 +1,5 @@ # -*- encoding: utf-8 -*- +# frozen_string_literal: false require_relative '../../spec_helper' require_relative 'fixtures/classes' diff --git a/spec/ruby/core/string/dup_spec.rb b/spec/ruby/core/string/dup_spec.rb index 73f71b8ffcc4e5..073802d84b48ba 100644 --- a/spec/ruby/core/string/dup_spec.rb +++ b/spec/ruby/core/string/dup_spec.rb @@ -51,7 +51,7 @@ class << @obj end it "does not modify the original setbyte-mutated string when changing dupped string" do - orig = "a" + orig = +"a" orig.setbyte 0, "b".ord copy = orig.dup orig.setbyte 0, "c".ord diff --git a/spec/ruby/core/string/each_byte_spec.rb b/spec/ruby/core/string/each_byte_spec.rb index e04dca807faa95..7b3db265ac2f97 100644 --- a/spec/ruby/core/string/each_byte_spec.rb +++ b/spec/ruby/core/string/each_byte_spec.rb @@ -9,26 +9,26 @@ end it "keeps iterating from the old position (to new string end) when self changes" do - r = "" - s = "hello world" + r = +"" + s = +"hello world" s.each_byte do |c| r << c s.insert(0, "<>") if r.size < 3 end r.should == "h><>hello world" - r = "" - s = "hello world" + r = +"" + s = +"hello world" s.each_byte { |c| s.slice!(-1); r << c } r.should == "hello " - r = "" - s = "hello world" + r = +"" + s = +"hello world" s.each_byte { |c| s.slice!(0); r << c } r.should == "hlowrd" - r = "" - s = "hello world" + r = +"" + s = +"hello world" s.each_byte { |c| s.slice!(0..-1); r << c } r.should == "h" end diff --git a/spec/ruby/core/string/element_set_spec.rb b/spec/ruby/core/string/element_set_spec.rb index fa041fa31da2f9..e7599f832c0558 100644 --- a/spec/ruby/core/string/element_set_spec.rb +++ b/spec/ruby/core/string/element_set_spec.rb @@ -1,4 +1,5 @@ # -*- encoding: utf-8 -*- +# frozen_string_literal: false require_relative '../../spec_helper' require_relative 'fixtures/classes' diff --git a/spec/ruby/core/string/encode_spec.rb b/spec/ruby/core/string/encode_spec.rb index 35ed27bb40d3bc..97dd753b6290e4 100644 --- a/spec/ruby/core/string/encode_spec.rb +++ b/spec/ruby/core/string/encode_spec.rb @@ -34,8 +34,8 @@ it "encodes an ascii substring of a binary string to UTF-8" do x82 = [0x82].pack('C') - str = "#{x82}foo".force_encoding("binary")[1..-1].encode("utf-8") - str.should == "foo".force_encoding("utf-8") + str = "#{x82}foo".dup.force_encoding("binary")[1..-1].encode("utf-8") + str.should == "foo".dup.force_encoding("utf-8") str.encoding.should equal(Encoding::UTF_8) end end @@ -49,7 +49,7 @@ end it "round trips a String" do - str = "abc def".force_encoding Encoding::US_ASCII + str = "abc def".dup.force_encoding Encoding::US_ASCII str.encode("utf-32be").encode("ascii").should == "abc def" end end @@ -122,8 +122,7 @@ describe "when passed to, from" do it "returns a copy in the destination encoding when both encodings are the same" do - str = "あ" - str.force_encoding("binary") + str = "あ".dup.force_encoding("binary") encoded = str.encode("utf-8", "utf-8") encoded.should_not equal(str) @@ -155,8 +154,7 @@ end it "returns a copy in the destination encoding when both encodings are the same" do - str = "あ" - str.force_encoding("binary") + str = "あ".dup.force_encoding("binary") encoded = str.encode("utf-8", "utf-8", invalid: :replace) encoded.should_not equal(str) @@ -191,13 +189,13 @@ describe "when passed no options" do it "returns self when Encoding.default_internal is nil" do Encoding.default_internal = nil - str = "あ" + str = +"あ" str.encode!.should equal(str) end it "returns self for a ASCII-only String when Encoding.default_internal is nil" do Encoding.default_internal = nil - str = "abc" + str = +"abc" str.encode!.should equal(str) end end @@ -205,14 +203,14 @@ describe "when passed options" do it "returns self for ASCII-only String when Encoding.default_internal is nil" do Encoding.default_internal = nil - str = "abc" + str = +"abc" str.encode!(invalid: :replace).should equal(str) end end describe "when passed to encoding" do it "returns self" do - str = "abc" + str = +"abc" result = str.encode!(Encoding::BINARY) result.encoding.should equal(Encoding::BINARY) result.should equal(str) @@ -221,7 +219,7 @@ describe "when passed to, from" do it "returns self" do - str = "ああ" + str = +"ああ" result = str.encode!("euc-jp", "utf-8") result.encoding.should equal(Encoding::EUC_JP) result.should equal(str) diff --git a/spec/ruby/core/string/encoding_spec.rb b/spec/ruby/core/string/encoding_spec.rb index 574a1e2f9287df..f6e8fd34702116 100644 --- a/spec/ruby/core/string/encoding_spec.rb +++ b/spec/ruby/core/string/encoding_spec.rb @@ -14,11 +14,11 @@ end it "returns the given encoding if #force_encoding has been called" do - "a".force_encoding(Encoding::SHIFT_JIS).encoding.should == Encoding::SHIFT_JIS + "a".dup.force_encoding(Encoding::SHIFT_JIS).encoding.should == Encoding::SHIFT_JIS end it "returns the given encoding if #encode!has been called" do - "a".encode!(Encoding::SHIFT_JIS).encoding.should == Encoding::SHIFT_JIS + "a".dup.encode!(Encoding::SHIFT_JIS).encoding.should == Encoding::SHIFT_JIS end end @@ -108,13 +108,13 @@ end it "returns the given encoding if #force_encoding has been called" do - "\u{20}".force_encoding(Encoding::SHIFT_JIS).encoding.should == Encoding::SHIFT_JIS - "\u{2020}".force_encoding(Encoding::SHIFT_JIS).encoding.should == Encoding::SHIFT_JIS + "\u{20}".dup.force_encoding(Encoding::SHIFT_JIS).encoding.should == Encoding::SHIFT_JIS + "\u{2020}".dup.force_encoding(Encoding::SHIFT_JIS).encoding.should == Encoding::SHIFT_JIS end it "returns the given encoding if #encode!has been called" do - "\u{20}".encode!(Encoding::SHIFT_JIS).encoding.should == Encoding::SHIFT_JIS - "\u{2020}".encode!(Encoding::SHIFT_JIS).encoding.should == Encoding::SHIFT_JIS + "\u{20}".dup.encode!(Encoding::SHIFT_JIS).encoding.should == Encoding::SHIFT_JIS + "\u{2020}".dup.encode!(Encoding::SHIFT_JIS).encoding.should == Encoding::SHIFT_JIS end end @@ -173,16 +173,12 @@ end it "returns the given encoding if #force_encoding has been called" do - x50 = "\x50" - x50.force_encoding(Encoding::SHIFT_JIS).encoding.should == Encoding::SHIFT_JIS - xD4 = [212].pack('C') - xD4.force_encoding(Encoding::ISO_8859_9).encoding.should == Encoding::ISO_8859_9 + "\x50".dup.force_encoding(Encoding::SHIFT_JIS).encoding.should == Encoding::SHIFT_JIS + [212].pack('C').force_encoding(Encoding::ISO_8859_9).encoding.should == Encoding::ISO_8859_9 end it "returns the given encoding if #encode!has been called" do - x50 = "\x50" - x50.encode!(Encoding::SHIFT_JIS).encoding.should == Encoding::SHIFT_JIS - x00 = "x\00" - x00.encode!(Encoding::UTF_8).encoding.should == Encoding::UTF_8 + "\x50".dup.encode!(Encoding::SHIFT_JIS).encoding.should == Encoding::SHIFT_JIS + "x\00".dup.encode!(Encoding::UTF_8).encoding.should == Encoding::UTF_8 end end diff --git a/spec/ruby/core/string/force_encoding_spec.rb b/spec/ruby/core/string/force_encoding_spec.rb index f37aaf9eb4319b..2259dcf3cf8650 100644 --- a/spec/ruby/core/string/force_encoding_spec.rb +++ b/spec/ruby/core/string/force_encoding_spec.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: false require_relative '../../spec_helper' describe "String#force_encoding" do diff --git a/spec/ruby/core/string/freeze_spec.rb b/spec/ruby/core/string/freeze_spec.rb index 04d1e9513c39ab..2e8e70386dcaad 100644 --- a/spec/ruby/core/string/freeze_spec.rb +++ b/spec/ruby/core/string/freeze_spec.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: false require_relative '../../spec_helper' describe "String#freeze" do diff --git a/spec/ruby/core/string/gsub_spec.rb b/spec/ruby/core/string/gsub_spec.rb index 9e3b50322c6729..0d9f32eca2e11d 100644 --- a/spec/ruby/core/string/gsub_spec.rb +++ b/spec/ruby/core/string/gsub_spec.rb @@ -1,4 +1,5 @@ # -*- encoding: utf-8 -*- +# frozen_string_literal: false require_relative '../../spec_helper' require_relative 'fixtures/classes' diff --git a/spec/ruby/core/string/include_spec.rb b/spec/ruby/core/string/include_spec.rb index 23e1e134ec73f3..9781140a55ea1e 100644 --- a/spec/ruby/core/string/include_spec.rb +++ b/spec/ruby/core/string/include_spec.rb @@ -15,16 +15,16 @@ it "returns true if both strings are empty" do "".should.include?("") - "".force_encoding("EUC-JP").should.include?("") - "".should.include?("".force_encoding("EUC-JP")) - "".force_encoding("EUC-JP").should.include?("".force_encoding("EUC-JP")) + "".dup.force_encoding("EUC-JP").should.include?("") + "".should.include?("".dup.force_encoding("EUC-JP")) + "".dup.force_encoding("EUC-JP").should.include?("".dup.force_encoding("EUC-JP")) end it "returns true if the RHS is empty" do "a".should.include?("") - "a".force_encoding("EUC-JP").should.include?("") - "a".should.include?("".force_encoding("EUC-JP")) - "a".force_encoding("EUC-JP").should.include?("".force_encoding("EUC-JP")) + "a".dup.force_encoding("EUC-JP").should.include?("") + "a".should.include?("".dup.force_encoding("EUC-JP")) + "a".dup.force_encoding("EUC-JP").should.include?("".dup.force_encoding("EUC-JP")) end it "tries to convert other to string using to_str" do diff --git a/spec/ruby/core/string/index_spec.rb b/spec/ruby/core/string/index_spec.rb index b500cf6ca77d77..be797080451a41 100644 --- a/spec/ruby/core/string/index_spec.rb +++ b/spec/ruby/core/string/index_spec.rb @@ -161,16 +161,16 @@ end it "handles a substring in a superset encoding" do - 'abc'.force_encoding(Encoding::US_ASCII).index('é').should == nil + 'abc'.dup.force_encoding(Encoding::US_ASCII).index('é').should == nil end it "handles a substring in a subset encoding" do - 'été'.index('t'.force_encoding(Encoding::US_ASCII)).should == 1 + 'été'.index('t'.dup.force_encoding(Encoding::US_ASCII)).should == 1 end it "raises an Encoding::CompatibilityError if the encodings are incompatible" do - str = 'abc'.force_encoding("ISO-2022-JP") - pattern = 'b'.force_encoding("EUC-JP") + str = 'abc'.dup.force_encoding("ISO-2022-JP") + pattern = 'b'.dup.force_encoding("EUC-JP") -> { str.index(pattern) }.should raise_error(Encoding::CompatibilityError, "incompatible character encodings: ISO-2022-JP and EUC-JP") end diff --git a/spec/ruby/core/string/insert_spec.rb b/spec/ruby/core/string/insert_spec.rb index 0c87df3a9551b6..483f3c9367b16e 100644 --- a/spec/ruby/core/string/insert_spec.rb +++ b/spec/ruby/core/string/insert_spec.rb @@ -1,5 +1,5 @@ # -*- encoding: utf-8 -*- - +# frozen_string_literal: false require_relative '../../spec_helper' require_relative 'fixtures/classes' diff --git a/spec/ruby/core/string/inspect_spec.rb b/spec/ruby/core/string/inspect_spec.rb index 8bf3d3161fba7c..15db06c7f5f7e0 100644 --- a/spec/ruby/core/string/inspect_spec.rb +++ b/spec/ruby/core/string/inspect_spec.rb @@ -327,7 +327,7 @@ end it "works for broken US-ASCII strings" do - s = "©".force_encoding("US-ASCII") + s = "©".dup.force_encoding("US-ASCII") s.inspect.should == '"\xC2\xA9"' end diff --git a/spec/ruby/core/string/ljust_spec.rb b/spec/ruby/core/string/ljust_spec.rb index 9208ec58977c09..47324c59d2f4de 100644 --- a/spec/ruby/core/string/ljust_spec.rb +++ b/spec/ruby/core/string/ljust_spec.rb @@ -75,7 +75,7 @@ describe "with width" do it "returns a String in the same encoding as the original" do - str = "abc".force_encoding Encoding::IBM437 + str = "abc".dup.force_encoding Encoding::IBM437 result = str.ljust 5 result.should == "abc " result.encoding.should equal(Encoding::IBM437) @@ -84,7 +84,7 @@ describe "with width, pattern" do it "returns a String in the compatible encoding" do - str = "abc".force_encoding Encoding::IBM437 + str = "abc".dup.force_encoding Encoding::IBM437 result = str.ljust 5, "あ" result.should == "abcああ" result.encoding.should equal(Encoding::UTF_8) diff --git a/spec/ruby/core/string/lstrip_spec.rb b/spec/ruby/core/string/lstrip_spec.rb index 85685deb0ac0a2..99bab6f349836f 100644 --- a/spec/ruby/core/string/lstrip_spec.rb +++ b/spec/ruby/core/string/lstrip_spec.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: false require_relative '../../spec_helper' require_relative 'fixtures/classes' require_relative 'shared/strip' diff --git a/spec/ruby/core/string/ord_spec.rb b/spec/ruby/core/string/ord_spec.rb index 4cf26990fedec8..35af3b5458eb28 100644 --- a/spec/ruby/core/string/ord_spec.rb +++ b/spec/ruby/core/string/ord_spec.rb @@ -27,7 +27,7 @@ end it "raises ArgumentError if the character is broken" do - s = "©".force_encoding("US-ASCII") + s = "©".dup.force_encoding("US-ASCII") -> { s.ord }.should raise_error(ArgumentError, "invalid byte sequence in US-ASCII") end end diff --git a/spec/ruby/core/string/partition_spec.rb b/spec/ruby/core/string/partition_spec.rb index 9cb3672881ff02..d5370dcc7352d3 100644 --- a/spec/ruby/core/string/partition_spec.rb +++ b/spec/ruby/core/string/partition_spec.rb @@ -40,7 +40,7 @@ end it "handles a pattern in a superset encoding" do - string = "hello".force_encoding(Encoding::US_ASCII) + string = "hello".dup.force_encoding(Encoding::US_ASCII) result = string.partition("é") @@ -51,7 +51,7 @@ end it "handles a pattern in a subset encoding" do - pattern = "o".force_encoding(Encoding::US_ASCII) + pattern = "o".dup.force_encoding(Encoding::US_ASCII) result = "héllo world".partition(pattern) diff --git a/spec/ruby/core/string/prepend_spec.rb b/spec/ruby/core/string/prepend_spec.rb index a0393d47607678..5248ea8056b53f 100644 --- a/spec/ruby/core/string/prepend_spec.rb +++ b/spec/ruby/core/string/prepend_spec.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: false require_relative '../../spec_helper' require_relative 'fixtures/classes' diff --git a/spec/ruby/core/string/reverse_spec.rb b/spec/ruby/core/string/reverse_spec.rb index e67122c05c2e55..aa6abe6036e466 100644 --- a/spec/ruby/core/string/reverse_spec.rb +++ b/spec/ruby/core/string/reverse_spec.rb @@ -1,4 +1,5 @@ # encoding: utf-8 +# frozen_string_literal: false require_relative '../../spec_helper' require_relative 'fixtures/classes' diff --git a/spec/ruby/core/string/rindex_spec.rb b/spec/ruby/core/string/rindex_spec.rb index f6271b270d1167..88ce7335833ed7 100644 --- a/spec/ruby/core/string/rindex_spec.rb +++ b/spec/ruby/core/string/rindex_spec.rb @@ -197,16 +197,16 @@ def obj.method_missing(*args) 5 end end it "handles a substring in a superset encoding" do - 'abc'.force_encoding(Encoding::US_ASCII).rindex('é').should == nil + 'abc'.dup.force_encoding(Encoding::US_ASCII).rindex('é').should == nil end it "handles a substring in a subset encoding" do - 'été'.rindex('t'.force_encoding(Encoding::US_ASCII)).should == 1 + 'été'.rindex('t'.dup.force_encoding(Encoding::US_ASCII)).should == 1 end it "raises an Encoding::CompatibilityError if the encodings are incompatible" do - str = 'abc'.force_encoding("ISO-2022-JP") - pattern = 'b'.force_encoding("EUC-JP") + str = 'abc'.dup.force_encoding("ISO-2022-JP") + pattern = 'b'.dup.force_encoding("EUC-JP") -> { str.rindex(pattern) }.should raise_error(Encoding::CompatibilityError, "incompatible character encodings: ISO-2022-JP and EUC-JP") end diff --git a/spec/ruby/core/string/rjust_spec.rb b/spec/ruby/core/string/rjust_spec.rb index fcbaf3b938d7fe..4ad3e54aea2cf5 100644 --- a/spec/ruby/core/string/rjust_spec.rb +++ b/spec/ruby/core/string/rjust_spec.rb @@ -75,7 +75,7 @@ describe "with width" do it "returns a String in the same encoding as the original" do - str = "abc".force_encoding Encoding::IBM437 + str = "abc".dup.force_encoding Encoding::IBM437 result = str.rjust 5 result.should == " abc" result.encoding.should equal(Encoding::IBM437) @@ -84,7 +84,7 @@ describe "with width, pattern" do it "returns a String in the compatible encoding" do - str = "abc".force_encoding Encoding::IBM437 + str = "abc".dup.force_encoding Encoding::IBM437 result = str.rjust 5, "あ" result.should == "ああabc" result.encoding.should equal(Encoding::UTF_8) diff --git a/spec/ruby/core/string/rpartition_spec.rb b/spec/ruby/core/string/rpartition_spec.rb index 21e87f530a8013..cef0384c7396c2 100644 --- a/spec/ruby/core/string/rpartition_spec.rb +++ b/spec/ruby/core/string/rpartition_spec.rb @@ -48,7 +48,7 @@ end it "handles a pattern in a superset encoding" do - string = "hello".force_encoding(Encoding::US_ASCII) + string = "hello".dup.force_encoding(Encoding::US_ASCII) result = string.rpartition("é") @@ -59,7 +59,7 @@ end it "handles a pattern in a subset encoding" do - pattern = "o".force_encoding(Encoding::US_ASCII) + pattern = "o".dup.force_encoding(Encoding::US_ASCII) result = "héllo world".rpartition(pattern) diff --git a/spec/ruby/core/string/rstrip_spec.rb b/spec/ruby/core/string/rstrip_spec.rb index e4cf93315ebf6c..6d46eb590ef298 100644 --- a/spec/ruby/core/string/rstrip_spec.rb +++ b/spec/ruby/core/string/rstrip_spec.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: false require_relative '../../spec_helper' require_relative 'fixtures/classes' require_relative 'shared/strip' diff --git a/spec/ruby/core/string/scrub_spec.rb b/spec/ruby/core/string/scrub_spec.rb index bcee4db4630727..b9ef0f1a16e8c0 100644 --- a/spec/ruby/core/string/scrub_spec.rb +++ b/spec/ruby/core/string/scrub_spec.rb @@ -1,4 +1,5 @@ # -*- encoding: utf-8 -*- +# frozen_string_literal: false require_relative '../../spec_helper' require_relative 'fixtures/classes' diff --git a/spec/ruby/core/string/setbyte_spec.rb b/spec/ruby/core/string/setbyte_spec.rb index 77bff6403850f6..85403ca62c6cc7 100644 --- a/spec/ruby/core/string/setbyte_spec.rb +++ b/spec/ruby/core/string/setbyte_spec.rb @@ -1,4 +1,5 @@ # -*- encoding: utf-8 -*- +# frozen_string_literal: false require_relative '../../spec_helper' describe "String#setbyte" do diff --git a/spec/ruby/core/string/shared/chars.rb b/spec/ruby/core/string/shared/chars.rb index e9fdf89fd6208c..c730643cf49874 100644 --- a/spec/ruby/core/string/shared/chars.rb +++ b/spec/ruby/core/string/shared/chars.rb @@ -21,12 +21,12 @@ end it "returns characters in the same encoding as self" do - "&%".force_encoding('Shift_JIS').send(@method).to_a.all? {|c| c.encoding.name.should == 'Shift_JIS'} + "&%".dup.force_encoding('Shift_JIS').send(@method).to_a.all? {|c| c.encoding.name.should == 'Shift_JIS'} "&%".encode('BINARY').send(@method).to_a.all? {|c| c.encoding.should == Encoding::BINARY } end it "works with multibyte characters" do - s = "\u{8987}".force_encoding("UTF-8") + s = "\u{8987}".dup.force_encoding("UTF-8") s.bytesize.should == 3 s.send(@method).to_a.should == [s] end @@ -39,14 +39,14 @@ end it "returns a different character if the String is transcoded" do - s = "\u{20AC}".force_encoding('UTF-8') - s.encode('UTF-8').send(@method).to_a.should == ["\u{20AC}".force_encoding('UTF-8')] + s = "\u{20AC}".dup.force_encoding('UTF-8') + s.encode('UTF-8').send(@method).to_a.should == ["\u{20AC}".dup.force_encoding('UTF-8')] s.encode('iso-8859-15').send(@method).to_a.should == [[0xA4].pack('C').force_encoding('iso-8859-15')] - s.encode('iso-8859-15').encode('UTF-8').send(@method).to_a.should == ["\u{20AC}".force_encoding('UTF-8')] + s.encode('iso-8859-15').encode('UTF-8').send(@method).to_a.should == ["\u{20AC}".dup.force_encoding('UTF-8')] end it "uses the String's encoding to determine what characters it contains" do - s = "\u{24B62}" + s = +"\u{24B62}" s.force_encoding('UTF-8').send(@method).to_a.should == [ s.force_encoding('UTF-8') diff --git a/spec/ruby/core/string/shared/codepoints.rb b/spec/ruby/core/string/shared/codepoints.rb index 0b2e078e0a47a7..f71263054a5398 100644 --- a/spec/ruby/core/string/shared/codepoints.rb +++ b/spec/ruby/core/string/shared/codepoints.rb @@ -7,7 +7,7 @@ end it "raises an ArgumentError when self has an invalid encoding and a method is called on the returned Enumerator" do - s = "\xDF".force_encoding(Encoding::UTF_8) + s = "\xDF".dup.force_encoding(Encoding::UTF_8) s.valid_encoding?.should be_false -> { s.send(@method).to_a }.should raise_error(ArgumentError) end @@ -21,7 +21,7 @@ end it "raises an ArgumentError if self's encoding is invalid and a block is given" do - s = "\xDF".force_encoding(Encoding::UTF_8) + s = "\xDF".dup.force_encoding(Encoding::UTF_8) s.valid_encoding?.should be_false -> { s.send(@method) { } }.should raise_error(ArgumentError) end @@ -49,7 +49,7 @@ it "round-trips to the original String using Integer#chr" do s = "\u{13}\u{7711}\u{1010}" - s2 = "" + s2 = +"" s.send(@method) {|n| s2 << n.chr(Encoding::UTF_8)} s.should == s2 end diff --git a/spec/ruby/core/string/shared/concat.rb b/spec/ruby/core/string/shared/concat.rb index ee5ef2a98fdf2e..dded9a69e73e35 100644 --- a/spec/ruby/core/string/shared/concat.rb +++ b/spec/ruby/core/string/shared/concat.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: false describe :string_concat, shared: true do it "concatenates the given argument to self and returns self" do str = 'hello ' diff --git a/spec/ruby/core/string/shared/dedup.rb b/spec/ruby/core/string/shared/dedup.rb index 893fd1e360ea36..97b5df6ed104be 100644 --- a/spec/ruby/core/string/shared/dedup.rb +++ b/spec/ruby/core/string/shared/dedup.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: false describe :string_dedup, shared: true do it 'returns self if the String is frozen' do input = 'foo'.freeze diff --git a/spec/ruby/core/string/shared/each_codepoint_without_block.rb b/spec/ruby/core/string/shared/each_codepoint_without_block.rb index 92b7f76032e7d8..31b4c02c9c3194 100644 --- a/spec/ruby/core/string/shared/each_codepoint_without_block.rb +++ b/spec/ruby/core/string/shared/each_codepoint_without_block.rb @@ -6,7 +6,7 @@ end it "returns an Enumerator even when self has an invalid encoding" do - s = "\xDF".force_encoding(Encoding::UTF_8) + s = "\xDF".dup.force_encoding(Encoding::UTF_8) s.valid_encoding?.should be_false s.send(@method).should be_an_instance_of(Enumerator) end @@ -23,7 +23,7 @@ end it "should return the size of the string even when the string has an invalid encoding" do - s = "\xDF".force_encoding(Encoding::UTF_8) + s = "\xDF".dup.force_encoding(Encoding::UTF_8) s.valid_encoding?.should be_false s.send(@method).size.should == 1 end diff --git a/spec/ruby/core/string/shared/each_line.rb b/spec/ruby/core/string/shared/each_line.rb index a14b4d7779eba9..231a6d9d4ff3a2 100644 --- a/spec/ruby/core/string/shared/each_line.rb +++ b/spec/ruby/core/string/shared/each_line.rb @@ -106,7 +106,7 @@ end it "does not care if the string is modified while substituting" do - str = "hello\nworld." + str = +"hello\nworld." out = [] str.send(@method){|x| out << x; str[-1] = '!' }.should == "hello\nworld!" out.should == ["hello\n", "world."] diff --git a/spec/ruby/core/string/shared/encode.rb b/spec/ruby/core/string/shared/encode.rb index a73de5b9434be4..3776e0d709b7da 100644 --- a/spec/ruby/core/string/shared/encode.rb +++ b/spec/ruby/core/string/shared/encode.rb @@ -1,4 +1,5 @@ # -*- encoding: utf-8 -*- +# frozen_string_literal: false describe :string_encode, shared: true do describe "when passed no options" do it "transcodes to Encoding.default_internal when set" do diff --git a/spec/ruby/core/string/shared/eql.rb b/spec/ruby/core/string/shared/eql.rb index 6f268c929cd378..845b0a3e15628d 100644 --- a/spec/ruby/core/string/shared/eql.rb +++ b/spec/ruby/core/string/shared/eql.rb @@ -13,15 +13,15 @@ end it "ignores encoding difference of compatible string" do - "hello".force_encoding("utf-8").send(@method, "hello".force_encoding("iso-8859-1")).should be_true + "hello".dup.force_encoding("utf-8").send(@method, "hello".dup.force_encoding("iso-8859-1")).should be_true end it "considers encoding difference of incompatible string" do - "\xff".force_encoding("utf-8").send(@method, "\xff".force_encoding("iso-8859-1")).should be_false + "\xff".dup.force_encoding("utf-8").send(@method, "\xff".dup.force_encoding("iso-8859-1")).should be_false end it "considers encoding compatibility" do - "abcd".force_encoding("utf-8").send(@method, "abcd".force_encoding("utf-32le")).should be_false + "abcd".dup.force_encoding("utf-8").send(@method, "abcd".dup.force_encoding("utf-32le")).should be_false end it "ignores subclass differences" do @@ -33,6 +33,6 @@ end it "returns true when comparing 2 empty strings but one is not ASCII-compatible" do - "".send(@method, "".force_encoding('iso-2022-jp')).should == true + "".send(@method, "".dup.force_encoding('iso-2022-jp')).should == true end end diff --git a/spec/ruby/core/string/shared/length.rb b/spec/ruby/core/string/shared/length.rb index 94e5ec135b07da..ae572ba75562ee 100644 --- a/spec/ruby/core/string/shared/length.rb +++ b/spec/ruby/core/string/shared/length.rb @@ -18,7 +18,7 @@ end it "returns the length of the new self after encoding is changed" do - str = 'こにちわ' + str = +'こにちわ' str.send(@method) str.force_encoding('BINARY').send(@method).should == 12 @@ -44,12 +44,12 @@ end it "adds 1 (and not 2) for a incomplete surrogate in UTF-16" do - "\x00\xd8".force_encoding("UTF-16LE").send(@method).should == 1 - "\xd8\x00".force_encoding("UTF-16BE").send(@method).should == 1 + "\x00\xd8".dup.force_encoding("UTF-16LE").send(@method).should == 1 + "\xd8\x00".dup.force_encoding("UTF-16BE").send(@method).should == 1 end it "adds 1 for a broken sequence in UTF-32" do - "\x04\x03\x02\x01".force_encoding("UTF-32LE").send(@method).should == 1 - "\x01\x02\x03\x04".force_encoding("UTF-32BE").send(@method).should == 1 + "\x04\x03\x02\x01".dup.force_encoding("UTF-32LE").send(@method).should == 1 + "\x01\x02\x03\x04".dup.force_encoding("UTF-32BE").send(@method).should == 1 end end diff --git a/spec/ruby/core/string/shared/replace.rb b/spec/ruby/core/string/shared/replace.rb index a5108d9e7cc167..24dac0eb270873 100644 --- a/spec/ruby/core/string/shared/replace.rb +++ b/spec/ruby/core/string/shared/replace.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: false describe :string_replace, shared: true do it "returns self" do a = "a" diff --git a/spec/ruby/core/string/shared/slice.rb b/spec/ruby/core/string/shared/slice.rb index 3ef4bc50d7e0a2..2f69b9ddce35b9 100644 --- a/spec/ruby/core/string/shared/slice.rb +++ b/spec/ruby/core/string/shared/slice.rb @@ -84,8 +84,8 @@ s = "hello there" s.send(@method, 1, 9).encoding.should == s.encoding - a = "hello".force_encoding("binary") - b = " there".force_encoding("ISO-8859-1") + a = "hello".dup.force_encoding("binary") + b = " there".dup.force_encoding("ISO-8859-1") c = (a + b).force_encoding(Encoding::US_ASCII) c.send(@method, 0, 5).encoding.should == Encoding::US_ASCII diff --git a/spec/ruby/core/string/shared/succ.rb b/spec/ruby/core/string/shared/succ.rb index 24a729ce26e578..b69a3948750ea4 100644 --- a/spec/ruby/core/string/shared/succ.rb +++ b/spec/ruby/core/string/shared/succ.rb @@ -73,6 +73,7 @@ describe :string_succ_bang, shared: true do it "is equivalent to succ, but modifies self in place (still returns self)" do ["", "abcd", "THX1138"].each do |s| + s = +s r = s.dup.send(@method) s.send(@method).should equal(s) s.should == r diff --git a/spec/ruby/core/string/shared/to_sym.rb b/spec/ruby/core/string/shared/to_sym.rb index 52d8314211ad10..833eae100e4e6a 100644 --- a/spec/ruby/core/string/shared/to_sym.rb +++ b/spec/ruby/core/string/shared/to_sym.rb @@ -56,9 +56,9 @@ it "ignores existing symbols with different encoding" do source = "fée" - iso_symbol = source.force_encoding(Encoding::ISO_8859_1).send(@method) + iso_symbol = source.dup.force_encoding(Encoding::ISO_8859_1).send(@method) iso_symbol.encoding.should == Encoding::ISO_8859_1 - binary_symbol = source.force_encoding(Encoding::BINARY).send(@method) + binary_symbol = source.dup.force_encoding(Encoding::BINARY).send(@method) binary_symbol.encoding.should == Encoding::BINARY end diff --git a/spec/ruby/core/string/slice_spec.rb b/spec/ruby/core/string/slice_spec.rb index 87c5a7ac37ced2..5aba2d3be069e1 100644 --- a/spec/ruby/core/string/slice_spec.rb +++ b/spec/ruby/core/string/slice_spec.rb @@ -1,5 +1,5 @@ # -*- encoding: utf-8 -*- - +# frozen_string_literal: false require_relative '../../spec_helper' require_relative 'fixtures/classes' require_relative 'shared/slice' diff --git a/spec/ruby/core/string/split_spec.rb b/spec/ruby/core/string/split_spec.rb index c5cca651c2ad03..3c6d1864d1797e 100644 --- a/spec/ruby/core/string/split_spec.rb +++ b/spec/ruby/core/string/split_spec.rb @@ -4,7 +4,7 @@ describe "String#split with String" do it "throws an ArgumentError if the string is not a valid" do - s = "\xDF".force_encoding(Encoding::UTF_8) + s = "\xDF".dup.force_encoding(Encoding::UTF_8) -> { s.split }.should raise_error(ArgumentError) -> { s.split(':') }.should raise_error(ArgumentError) @@ -12,7 +12,7 @@ it "throws an ArgumentError if the pattern is not a valid string" do str = 'проверка' - broken_str = "\xDF".force_encoding(Encoding::UTF_8) + broken_str = "\xDF".dup.force_encoding(Encoding::UTF_8) -> { str.split(broken_str) }.should raise_error(ArgumentError) end @@ -229,7 +229,7 @@ describe "String#split with Regexp" do it "throws an ArgumentError if the string is not a valid" do - s = "\xDF".force_encoding(Encoding::UTF_8) + s = "\xDF".dup.force_encoding(Encoding::UTF_8) -> { s.split(/./) }.should raise_error(ArgumentError) end @@ -409,7 +409,7 @@ end it "returns an ArgumentError if an invalid UTF-8 string is supplied" do - broken_str = 'проверка' # in russian, means "test" + broken_str = +'проверка' # in russian, means "test" broken_str.force_encoding('binary') broken_str.chop! broken_str.force_encoding('utf-8') diff --git a/spec/ruby/core/string/squeeze_spec.rb b/spec/ruby/core/string/squeeze_spec.rb index 4796a170f28898..4ea238e6b531b7 100644 --- a/spec/ruby/core/string/squeeze_spec.rb +++ b/spec/ruby/core/string/squeeze_spec.rb @@ -1,4 +1,5 @@ # -*- encoding: binary -*- +# frozen_string_literal: false require_relative '../../spec_helper' require_relative 'fixtures/classes' diff --git a/spec/ruby/core/string/strip_spec.rb b/spec/ruby/core/string/strip_spec.rb index 5e90fe35d02033..edb6ea3b444edc 100644 --- a/spec/ruby/core/string/strip_spec.rb +++ b/spec/ruby/core/string/strip_spec.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: false require_relative '../../spec_helper' require_relative 'fixtures/classes' require_relative 'shared/strip' diff --git a/spec/ruby/core/string/sub_spec.rb b/spec/ruby/core/string/sub_spec.rb index 51920486f53e15..4f9f87a433463e 100644 --- a/spec/ruby/core/string/sub_spec.rb +++ b/spec/ruby/core/string/sub_spec.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: false require_relative '../../spec_helper' require_relative 'fixtures/classes' diff --git a/spec/ruby/core/string/swapcase_spec.rb b/spec/ruby/core/string/swapcase_spec.rb index d740fb86c6d427..7f4c68366dc11d 100644 --- a/spec/ruby/core/string/swapcase_spec.rb +++ b/spec/ruby/core/string/swapcase_spec.rb @@ -1,4 +1,5 @@ # -*- encoding: utf-8 -*- +# frozen_string_literal: false require_relative '../../spec_helper' require_relative 'fixtures/classes' diff --git a/spec/ruby/core/string/tr_s_spec.rb b/spec/ruby/core/string/tr_s_spec.rb index 3c3147304478e3..dd72da440c93d5 100644 --- a/spec/ruby/core/string/tr_s_spec.rb +++ b/spec/ruby/core/string/tr_s_spec.rb @@ -1,4 +1,5 @@ # -*- encoding: utf-8 -*- +# frozen_string_literal: false require_relative '../../spec_helper' require_relative 'fixtures/classes' diff --git a/spec/ruby/core/string/tr_spec.rb b/spec/ruby/core/string/tr_spec.rb index d60480dc7e4bdb..75841a974fcc53 100644 --- a/spec/ruby/core/string/tr_spec.rb +++ b/spec/ruby/core/string/tr_spec.rb @@ -1,4 +1,5 @@ # -*- encoding: utf-8 -*- +# frozen_string_literal: false require_relative '../../spec_helper' require_relative 'fixtures/classes' diff --git a/spec/ruby/core/string/unicode_normalize_spec.rb b/spec/ruby/core/string/unicode_normalize_spec.rb index 6de7533fc7aebe..2e7d22394a28a2 100644 --- a/spec/ruby/core/string/unicode_normalize_spec.rb +++ b/spec/ruby/core/string/unicode_normalize_spec.rb @@ -1,4 +1,5 @@ # -*- encoding: utf-8 -*- +# frozen_string_literal: false require_relative '../../spec_helper' # Examples taken from http://www.unicode.org/reports/tr15/#Norm_Forms diff --git a/spec/ruby/core/string/unicode_normalized_spec.rb b/spec/ruby/core/string/unicode_normalized_spec.rb index 87f3740459bb0b..91cf2086b25972 100644 --- a/spec/ruby/core/string/unicode_normalized_spec.rb +++ b/spec/ruby/core/string/unicode_normalized_spec.rb @@ -1,4 +1,5 @@ # -*- encoding: utf-8 -*- +# frozen_string_literal: false require_relative '../../spec_helper' describe "String#unicode_normalized?" do diff --git a/spec/ruby/core/string/unpack/a_spec.rb b/spec/ruby/core/string/unpack/a_spec.rb index 2d83b4c8240a7a..4002ece697c145 100644 --- a/spec/ruby/core/string/unpack/a_spec.rb +++ b/spec/ruby/core/string/unpack/a_spec.rb @@ -31,7 +31,7 @@ end it "decodes into raw (ascii) string values" do - str = "str".force_encoding('UTF-8').unpack("A*")[0] + str = "str".dup.force_encoding('UTF-8').unpack("A*")[0] str.encoding.should == Encoding::BINARY end diff --git a/spec/ruby/core/string/unpack/b_spec.rb b/spec/ruby/core/string/unpack/b_spec.rb index 5c53eff7213b99..23d93a8aeae9af 100644 --- a/spec/ruby/core/string/unpack/b_spec.rb +++ b/spec/ruby/core/string/unpack/b_spec.rb @@ -107,7 +107,7 @@ end it "decodes into US-ASCII string values" do - str = "s".force_encoding('UTF-8').unpack("B*")[0] + str = "s".dup.force_encoding('UTF-8').unpack("B*")[0] str.encoding.name.should == 'US-ASCII' end end @@ -215,7 +215,7 @@ end it "decodes into US-ASCII string values" do - str = "s".force_encoding('UTF-8').unpack("b*")[0] + str = "s".dup.force_encoding('UTF-8').unpack("b*")[0] str.encoding.name.should == 'US-ASCII' end end diff --git a/spec/ruby/core/string/unpack/u_spec.rb b/spec/ruby/core/string/unpack/u_spec.rb index 7845e6d5f238ec..456abee7847b25 100644 --- a/spec/ruby/core/string/unpack/u_spec.rb +++ b/spec/ruby/core/string/unpack/u_spec.rb @@ -33,7 +33,7 @@ str = "".unpack("u")[0] str.encoding.should == Encoding::BINARY - str = "1".force_encoding('UTF-8').unpack("u")[0] + str = "1".dup.force_encoding('UTF-8').unpack("u")[0] str.encoding.should == Encoding::BINARY end diff --git a/spec/ruby/core/string/upcase_spec.rb b/spec/ruby/core/string/upcase_spec.rb index a2e34f5f406827..652de5c2ef0c26 100644 --- a/spec/ruby/core/string/upcase_spec.rb +++ b/spec/ruby/core/string/upcase_spec.rb @@ -1,4 +1,5 @@ # -*- encoding: utf-8 -*- +# frozen_string_literal: false require_relative '../../spec_helper' require_relative 'fixtures/classes' diff --git a/spec/ruby/core/string/uplus_spec.rb b/spec/ruby/core/string/uplus_spec.rb index 65b66260dd1074..c0b0c49edeef22 100644 --- a/spec/ruby/core/string/uplus_spec.rb +++ b/spec/ruby/core/string/uplus_spec.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: false require_relative '../../spec_helper' describe 'String#+@' do diff --git a/spec/ruby/core/string/upto_spec.rb b/spec/ruby/core/string/upto_spec.rb index 3799e338e08f31..8bc847d5ac1358 100644 --- a/spec/ruby/core/string/upto_spec.rb +++ b/spec/ruby/core/string/upto_spec.rb @@ -81,8 +81,8 @@ def other.to_str() "abd" end end it "raises Encoding::CompatibilityError when incompatible characters are given" do - char1 = 'a'.force_encoding("EUC-JP") - char2 = 'b'.force_encoding("ISO-2022-JP") + char1 = 'a'.dup.force_encoding("EUC-JP") + char2 = 'b'.dup.force_encoding("ISO-2022-JP") -> { char1.upto(char2) {} }.should raise_error(Encoding::CompatibilityError, "incompatible character encodings: EUC-JP and ISO-2022-JP") end diff --git a/spec/ruby/core/string/valid_encoding_spec.rb b/spec/ruby/core/string/valid_encoding_spec.rb index bb26062c0f6633..375035cd9496a9 100644 --- a/spec/ruby/core/string/valid_encoding_spec.rb +++ b/spec/ruby/core/string/valid_encoding_spec.rb @@ -7,13 +7,13 @@ end it "returns true if self is valid in the current encoding and other encodings" do - str = "\x77" + str = +"\x77" str.force_encoding('utf-8').valid_encoding?.should be_true str.force_encoding('binary').valid_encoding?.should be_true end it "returns true for all encodings self is valid in" do - str = "\xE6\x9D\x94" + str = +"\xE6\x9D\x94" str.force_encoding('BINARY').valid_encoding?.should be_true str.force_encoding('UTF-8').valid_encoding?.should be_true str.force_encoding('US-ASCII').valid_encoding?.should be_false @@ -43,10 +43,10 @@ str.force_encoding('KOI8-R').valid_encoding?.should be_true str.force_encoding('KOI8-U').valid_encoding?.should be_true str.force_encoding('Shift_JIS').valid_encoding?.should be_false - "\xD8\x00".force_encoding('UTF-16BE').valid_encoding?.should be_false - "\x00\xD8".force_encoding('UTF-16LE').valid_encoding?.should be_false - "\x04\x03\x02\x01".force_encoding('UTF-32BE').valid_encoding?.should be_false - "\x01\x02\x03\x04".force_encoding('UTF-32LE').valid_encoding?.should be_false + "\xD8\x00".dup.force_encoding('UTF-16BE').valid_encoding?.should be_false + "\x00\xD8".dup.force_encoding('UTF-16LE').valid_encoding?.should be_false + "\x04\x03\x02\x01".dup.force_encoding('UTF-32BE').valid_encoding?.should be_false + "\x01\x02\x03\x04".dup.force_encoding('UTF-32LE').valid_encoding?.should be_false str.force_encoding('Windows-1251').valid_encoding?.should be_true str.force_encoding('IBM437').valid_encoding?.should be_true str.force_encoding('IBM737').valid_encoding?.should be_true @@ -101,24 +101,24 @@ end it "returns true for IBM720 encoding self is valid in" do - str = "\xE6\x9D\x94" + str = +"\xE6\x9D\x94" str.force_encoding('IBM720').valid_encoding?.should be_true str.force_encoding('CP720').valid_encoding?.should be_true end it "returns false if self is valid in one encoding, but invalid in the one it's tagged with" do - str = "\u{8765}" + str = +"\u{8765}" str.valid_encoding?.should be_true - str = str.force_encoding('ascii') + str.force_encoding('ascii') str.valid_encoding?.should be_false end it "returns false if self contains a character invalid in the associated encoding" do - "abc#{[0x80].pack('C')}".force_encoding('ascii').valid_encoding?.should be_false + "abc#{[0x80].pack('C')}".dup.force_encoding('ascii').valid_encoding?.should be_false end it "returns false if a valid String had an invalid character appended to it" do - str = "a" + str = +"a" str.valid_encoding?.should be_true str << [0xDD].pack('C').force_encoding('utf-8') str.valid_encoding?.should be_false diff --git a/spec/ruby/core/struct/new_spec.rb b/spec/ruby/core/struct/new_spec.rb index 8758051a819cfe..a94eb852e1fa8a 100644 --- a/spec/ruby/core/struct/new_spec.rb +++ b/spec/ruby/core/struct/new_spec.rb @@ -48,7 +48,7 @@ def obj.to_str() "Foo" end end it "allows non-ASCII member name" do - name = "r\xe9sum\xe9".force_encoding(Encoding::ISO_8859_1).to_sym + name = "r\xe9sum\xe9".dup.force_encoding(Encoding::ISO_8859_1).to_sym struct = Struct.new(name) struct.new("foo").send(name).should == "foo" end diff --git a/spec/ruby/core/time/_load_spec.rb b/spec/ruby/core/time/_load_spec.rb index 152934370fbb69..bb0d705bbc18f1 100644 --- a/spec/ruby/core/time/_load_spec.rb +++ b/spec/ruby/core/time/_load_spec.rb @@ -44,8 +44,7 @@ end it "treats the data as binary data" do - data = "\x04\bu:\tTime\r\fM\x1C\xC0\x00\x00\xD0\xBE" - data.force_encoding Encoding::UTF_8 + data = "\x04\bu:\tTime\r\fM\x1C\xC0\x00\x00\xD0\xBE".dup.force_encoding Encoding::UTF_8 t = Marshal.load(data) t.to_s.should == "2013-04-08 12:47:45 UTC" end diff --git a/spec/ruby/core/time/at_spec.rb b/spec/ruby/core/time/at_spec.rb index 7fec8ad548b9bd..48fb3c6f523313 100644 --- a/spec/ruby/core/time/at_spec.rb +++ b/spec/ruby/core/time/at_spec.rb @@ -196,7 +196,7 @@ end it "does not try to convert format to Symbol with #to_sym" do - format = "usec" + format = +"usec" format.should_not_receive(:to_sym) -> { Time.at(0, 123456, format) }.should raise_error(ArgumentError) end diff --git a/spec/ruby/language/assignments_spec.rb b/spec/ruby/language/assignments_spec.rb index 005c1f0dc3f928..2773508d8d44ed 100644 --- a/spec/ruby/language/assignments_spec.rb +++ b/spec/ruby/language/assignments_spec.rb @@ -1,7 +1,61 @@ require_relative '../spec_helper' # Should be synchronized with spec/ruby/language/optional_assignments_spec.rb +# Some specs for assignments are located in language/variables_spec.rb describe 'Assignments' do + describe 'using =' do + describe 'evaluation order' do + it 'evaluates expressions left to right when assignment with an accessor' do + object = Object.new + def object.a=(value) end + ScratchPad.record [] + + (ScratchPad << :receiver; object).a = (ScratchPad << :rhs; :value) + ScratchPad.recorded.should == [:receiver, :rhs] + end + + it 'evaluates expressions left to right when assignment with a #[]=' do + object = Object.new + def object.[]=(_, _) end + ScratchPad.record [] + + (ScratchPad << :receiver; object)[(ScratchPad << :argument; :a)] = (ScratchPad << :rhs; :value) + ScratchPad.recorded.should == [:receiver, :argument, :rhs] + end + + # similar tests for evaluation order are located in language/constants_spec.rb + ruby_version_is ''...'3.2' do + it 'evaluates expressions right to left when assignment with compounded constant' do + m = Module.new + ScratchPad.record [] + + (ScratchPad << :module; m)::A = (ScratchPad << :rhs; :value) + ScratchPad.recorded.should == [:rhs, :module] + end + end + + ruby_version_is '3.2' do + it 'evaluates expressions left to right when assignment with compounded constant' do + m = Module.new + ScratchPad.record [] + + (ScratchPad << :module; m)::A = (ScratchPad << :rhs; :value) + ScratchPad.recorded.should == [:module, :rhs] + end + end + + it 'raises TypeError after evaluation of right-hand-side when compounded constant module is not a module' do + ScratchPad.record [] + + -> { + (:not_a_module)::A = (ScratchPad << :rhs; :value) + }.should raise_error(TypeError) + + ScratchPad.recorded.should == [:rhs] + end + end + end + describe 'using +=' do describe 'using an accessor' do before do @@ -148,3 +202,328 @@ module ConstantSpecs end end end + +# generic cases +describe 'Multiple assignments' do + it 'assigns multiple targets when assignment with an accessor' do + object = Object.new + class << object + attr_accessor :a, :b + end + + object.a, object.b = :a, :b + + object.a.should == :a + object.b.should == :b + end + + it 'assigns multiple targets when assignment with a nested accessor' do + object = Object.new + class << object + attr_accessor :a, :b + end + + (object.a, object.b), c = [:a, :b], nil + + object.a.should == :a + object.b.should == :b + end + + it 'assigns multiple targets when assignment with a #[]=' do + object = Object.new + class << object + def []=(k, v) (@h ||= {})[k] = v; end + def [](k) (@h ||= {})[k]; end + end + + object[:a], object[:b] = :a, :b + + object[:a].should == :a + object[:b].should == :b + end + + it 'assigns multiple targets when assignment with a nested #[]=' do + object = Object.new + class << object + def []=(k, v) (@h ||= {})[k] = v; end + def [](k) (@h ||= {})[k]; end + end + + (object[:a], object[:b]), c = [:v1, :v2], nil + + object[:a].should == :v1 + object[:b].should == :v2 + end + + it 'assigns multiple targets when assignment with compounded constant' do + m = Module.new + + m::A, m::B = :a, :b + + m::A.should == :a + m::B.should == :b + end + + it 'assigns multiple targets when assignment with a nested compounded constant' do + m = Module.new + + (m::A, m::B), c = [:a, :b], nil + + m::A.should == :a + m::B.should == :b + end +end + +describe 'Multiple assignments' do + describe 'evaluation order' do + ruby_version_is ''...'3.1' do + it 'evaluates expressions right to left when assignment with an accessor' do + object = Object.new + def object.a=(value) end + ScratchPad.record [] + + (ScratchPad << :a; object).a, (ScratchPad << :b; object).a = (ScratchPad << :c; :c), (ScratchPad << :d; :d) + ScratchPad.recorded.should == [:c, :d, :a, :b] + end + + it 'evaluates expressions right to left when assignment with a nested accessor' do + object = Object.new + def object.a=(value) end + ScratchPad.record [] + + ((ScratchPad << :a; object).a, foo), bar = [(ScratchPad << :b; :b)] + ScratchPad.recorded.should == [:b, :a] + end + end + + ruby_version_is '3.1' do + it 'evaluates expressions left to right when assignment with an accessor' do + object = Object.new + def object.a=(value) end + ScratchPad.record [] + + (ScratchPad << :a; object).a, (ScratchPad << :b; object).a = (ScratchPad << :c; :c), (ScratchPad << :d; :d) + ScratchPad.recorded.should == [:a, :b, :c, :d] + end + + it 'evaluates expressions left to right when assignment with a nested accessor' do + object = Object.new + def object.a=(value) end + ScratchPad.record [] + + ((ScratchPad << :a; object).a, foo), bar = [(ScratchPad << :b; :b)] + ScratchPad.recorded.should == [:a, :b] + end + + it 'evaluates expressions left to right when assignment with a deeply nested accessor' do + o = Object.new + def o.a=(value) end + def o.b=(value) end + def o.c=(value) end + def o.d=(value) end + def o.e=(value) end + def o.f=(value) end + ScratchPad.record [] + + (ScratchPad << :a; o).a, + ((ScratchPad << :b; o).b, + ((ScratchPad << :c; o).c, (ScratchPad << :d; o).d), + (ScratchPad << :e; o).e), + (ScratchPad << :f; o).f = (ScratchPad << :value; :value) + + ScratchPad.recorded.should == [:a, :b, :c, :d, :e, :f, :value] + end + end + + ruby_version_is ''...'3.1' do + it 'evaluates expressions right to left when assignment with a #[]=' do + object = Object.new + def object.[]=(_, _) end + ScratchPad.record [] + + (ScratchPad << :a; object)[(ScratchPad << :b; :b)], (ScratchPad << :c; object)[(ScratchPad << :d; :d)] = (ScratchPad << :e; :e), (ScratchPad << :f; :f) + ScratchPad.recorded.should == [:e, :f, :a, :b, :c, :d] + end + + it 'evaluates expressions right to left when assignment with a nested #[]=' do + object = Object.new + def object.[]=(_, _) end + ScratchPad.record [] + + ((ScratchPad << :a; object)[(ScratchPad << :b; :b)], foo), bar = [(ScratchPad << :c; :c)] + ScratchPad.recorded.should == [:c, :a, :b] + end + end + + ruby_version_is '3.1' do + it 'evaluates expressions left to right when assignment with a #[]=' do + object = Object.new + def object.[]=(_, _) end + ScratchPad.record [] + + (ScratchPad << :a; object)[(ScratchPad << :b; :b)], (ScratchPad << :c; object)[(ScratchPad << :d; :d)] = (ScratchPad << :e; :e), (ScratchPad << :f; :f) + ScratchPad.recorded.should == [:a, :b, :c, :d, :e, :f] + end + + it 'evaluates expressions left to right when assignment with a nested #[]=' do + object = Object.new + def object.[]=(_, _) end + ScratchPad.record [] + + ((ScratchPad << :a; object)[(ScratchPad << :b; :b)], foo), bar = [(ScratchPad << :c; :c)] + ScratchPad.recorded.should == [:a, :b, :c] + end + + it 'evaluates expressions left to right when assignment with a deeply nested #[]=' do + o = Object.new + def o.[]=(_, _) end + ScratchPad.record [] + + (ScratchPad << :ra; o)[(ScratchPad << :aa; :aa)], + ((ScratchPad << :rb; o)[(ScratchPad << :ab; :ab)], + ((ScratchPad << :rc; o)[(ScratchPad << :ac; :ac)], (ScratchPad << :rd; o)[(ScratchPad << :ad; :ad)]), + (ScratchPad << :re; o)[(ScratchPad << :ae; :ae)]), + (ScratchPad << :rf; o)[(ScratchPad << :af; :af)] = (ScratchPad << :value; :value) + + ScratchPad.recorded.should == [:ra, :aa, :rb, :ab, :rc, :ac, :rd, :ad, :re, :ae, :rf, :af, :value] + end + end + + ruby_version_is ''...'3.2' do + it 'evaluates expressions right to left when assignment with compounded constant' do + m = Module.new + ScratchPad.record [] + + (ScratchPad << :a; m)::A, (ScratchPad << :b; m)::B = (ScratchPad << :c; :c), (ScratchPad << :d; :d) + ScratchPad.recorded.should == [:c, :d, :a, :b] + end + end + + ruby_version_is '3.2' do + it 'evaluates expressions left to right when assignment with compounded constant' do + m = Module.new + ScratchPad.record [] + + (ScratchPad << :a; m)::A, (ScratchPad << :b; m)::B = (ScratchPad << :c; :c), (ScratchPad << :d; :d) + ScratchPad.recorded.should == [:a, :b, :c, :d] + end + + it 'evaluates expressions left to right when assignment with a nested compounded constant' do + m = Module.new + ScratchPad.record [] + + ((ScratchPad << :a; m)::A, foo), bar = [(ScratchPad << :b; :b)] + ScratchPad.recorded.should == [:a, :b] + end + + it 'evaluates expressions left to right when assignment with deeply nested compounded constants' do + m = Module.new + ScratchPad.record [] + + (ScratchPad << :a; m)::A, + ((ScratchPad << :b; m)::B, + ((ScratchPad << :c; m)::C, (ScratchPad << :d; m)::D), + (ScratchPad << :e; m)::E), + (ScratchPad << :f; m)::F = (ScratchPad << :value; :value) + + ScratchPad.recorded.should == [:a, :b, :c, :d, :e, :f, :value] + end + end + end + + context 'when assignment with method call and receiver is self' do + it 'assigns values correctly when assignment with accessor' do + object = Object.new + class << object + attr_accessor :a, :b + + def assign(v1, v2) + self.a, self.b = v1, v2 + end + end + + object.assign :v1, :v2 + object.a.should == :v1 + object.b.should == :v2 + end + + it 'evaluates expressions right to left when assignment with a nested accessor' do + object = Object.new + class << object + attr_accessor :a, :b + + def assign(v1, v2) + (self.a, self.b), c = [v1, v2], nil + end + end + + object.assign :v1, :v2 + object.a.should == :v1 + object.b.should == :v2 + end + + it 'assigns values correctly when assignment with a #[]=' do + object = Object.new + class << object + def []=(key, v) + @h ||= {} + @h[key] = v + end + + def [](key) + (@h || {})[key] + end + + def assign(k1, v1, k2, v2) + self[k1], self[k2] = v1, v2 + end + end + + object.assign :k1, :v1, :k2, :v2 + object[:k1].should == :v1 + object[:k2].should == :v2 + end + + it 'assigns values correctly when assignment with a nested #[]=' do + object = Object.new + class << object + def []=(key, v) + @h ||= {} + @h[key] = v + end + + def [](key) + (@h || {})[key] + end + + def assign(k1, v1, k2, v2) + (self[k1], self[k2]), c = [v1, v2], nil + end + end + + object.assign :k1, :v1, :k2, :v2 + object[:k1].should == :v1 + object[:k2].should == :v2 + end + + it 'assigns values correctly when assignment with compounded constant' do + m = Module.new + m.module_exec do + self::A, self::B = :v1, :v2 + end + + m::A.should == :v1 + m::B.should == :v2 + end + + it 'assigns values correctly when assignment with a nested compounded constant' do + m = Module.new + m.module_exec do + (self::A, self::B), c = [:v1, :v2], nil + end + + m::A.should == :v1 + m::B.should == :v2 + end + end +end diff --git a/spec/ruby/language/case_spec.rb b/spec/ruby/language/case_spec.rb index cfa612b93abb78..3262f09dd590f5 100644 --- a/spec/ruby/language/case_spec.rb +++ b/spec/ruby/language/case_spec.rb @@ -415,6 +415,19 @@ def test(v) self.test("bar").should == false self.test(true).should == true end + + it "warns if there are identical when clauses" do + -> { + eval <<~RUBY + case 1 + when 2 + :foo + when 2 + :bar + end + RUBY + }.should complain(/warning: duplicated .when' clause with line \d+ is ignored/, verbose: true) + end end describe "The 'case'-construct with no target expression" do diff --git a/spec/ruby/language/def_spec.rb b/spec/ruby/language/def_spec.rb index c8531343c06dcc..42e721c68c0285 100644 --- a/spec/ruby/language/def_spec.rb +++ b/spec/ruby/language/def_spec.rb @@ -238,7 +238,7 @@ def @a.foo end it "can be declared for a global variable" do - $__a__ = "hi" + $__a__ = +"hi" def $__a__.foo 7 end diff --git a/spec/ruby/language/encoding_spec.rb b/spec/ruby/language/encoding_spec.rb index 5430c9cb98cffd..e761a53cb69371 100644 --- a/spec/ruby/language/encoding_spec.rb +++ b/spec/ruby/language/encoding_spec.rb @@ -13,15 +13,15 @@ end it "is the evaluated strings's one inside an eval" do - eval("__ENCODING__".force_encoding("US-ASCII")).should == Encoding::US_ASCII - eval("__ENCODING__".force_encoding("BINARY")).should == Encoding::BINARY + eval("__ENCODING__".dup.force_encoding("US-ASCII")).should == Encoding::US_ASCII + eval("__ENCODING__".dup.force_encoding("BINARY")).should == Encoding::BINARY end it "is the encoding specified by a magic comment inside an eval" do - code = "# encoding: BINARY\n__ENCODING__".force_encoding("US-ASCII") + code = "# encoding: BINARY\n__ENCODING__".dup.force_encoding("US-ASCII") eval(code).should == Encoding::BINARY - code = "# encoding: us-ascii\n__ENCODING__".force_encoding("BINARY") + code = "# encoding: us-ascii\n__ENCODING__".dup.force_encoding("BINARY") eval(code).should == Encoding::US_ASCII end diff --git a/spec/ruby/language/ensure_spec.rb b/spec/ruby/language/ensure_spec.rb index e893904bcb16d5..16e626b4d0be5e 100644 --- a/spec/ruby/language/ensure_spec.rb +++ b/spec/ruby/language/ensure_spec.rb @@ -328,4 +328,21 @@ class EnsureInClassExample result.should == :begin end + + ruby_version_is "3.4" do + it "does not introduce extra backtrace entries" do + def foo + begin + raise "oops" + ensure + return caller(0, 2) # rubocop:disable Lint/EnsureReturn + end + end + line = __LINE__ + foo.should == [ + "#{__FILE__}:#{line-3}:in 'foo'", + "#{__FILE__}:#{line+1}:in 'block (3 levels) in '" + ] + end + end end diff --git a/spec/ruby/language/execution_spec.rb b/spec/ruby/language/execution_spec.rb index 4e0310946dc3c5..ef1de38899809d 100644 --- a/spec/ruby/language/execution_spec.rb +++ b/spec/ruby/language/execution_spec.rb @@ -5,6 +5,45 @@ ip = 'world' `echo disc #{ip}`.should == "disc world\n" end + + it "can be redefined and receive a frozen string as argument" do + called = false + runner = Object.new + + runner.singleton_class.define_method(:`) do |str| + called = true + + str.should == "test command" + str.frozen?.should == true + end + + runner.instance_exec do + `test command` + end + + called.should == true + end + + it "the argument isn't frozen if it contains interpolation" do + called = false + runner = Object.new + + runner.singleton_class.define_method(:`) do |str| + called = true + + str.should == "test command" + str.frozen?.should == false + str << "mutated" + end + + 2.times do + runner.instance_exec do + `test #{:command}` + end + end + + called.should == true + end end describe "%x" do @@ -12,4 +51,43 @@ ip = 'world' %x(echo disc #{ip}).should == "disc world\n" end + + it "can be redefined and receive a frozen string as argument" do + called = false + runner = Object.new + + runner.singleton_class.define_method(:`) do |str| + called = true + + str.should == "test command" + str.frozen?.should == true + end + + runner.instance_exec do + %x{test command} + end + + called.should == true + end + + it "the argument isn't frozen if it contains interpolation" do + called = false + runner = Object.new + + runner.singleton_class.define_method(:`) do |str| + called = true + + str.should == "test command" + str.frozen?.should == false + str << "mutated" + end + + 2.times do + runner.instance_exec do + %x{test #{:command}} + end + end + + called.should == true + end end diff --git a/spec/ruby/language/hash_spec.rb b/spec/ruby/language/hash_spec.rb index 60e357fe616b84..a7631fb0d6b6fb 100644 --- a/spec/ruby/language/hash_spec.rb +++ b/spec/ruby/language/hash_spec.rb @@ -33,7 +33,7 @@ end it "freezes string keys on initialization" do - key = "foo" + key = +"foo" h = {key => "bar"} key.reverse! h["foo"].should == "bar" diff --git a/spec/ruby/language/if_spec.rb b/spec/ruby/language/if_spec.rb index a5da69600049c4..2d1a89f081f60a 100644 --- a/spec/ruby/language/if_spec.rb +++ b/spec/ruby/language/if_spec.rb @@ -305,6 +305,16 @@ 6.times(&b) ScratchPad.recorded.should == [4, 5, 4, 5] end + + it "warns when Integer literals are used instead of predicates" do + -> { + eval <<~RUBY + $. = 0 + 10.times { |i| ScratchPad << i if 4..5 } + RUBY + }.should complain(/warning: integer literal in flip-flop/, verbose: true) + ScratchPad.recorded.should == [] + end end describe "when a branch syntactically does not return a value" do diff --git a/spec/ruby/language/method_spec.rb b/spec/ruby/language/method_spec.rb index e34ff7e1a665bd..9abe4cde204ccd 100644 --- a/spec/ruby/language/method_spec.rb +++ b/spec/ruby/language/method_spec.rb @@ -1459,7 +1459,7 @@ def foo(val) describe "Inside 'endless' method definitions" do it "allows method calls without parenthesis" do eval <<-ruby - def greet(person) = "Hi, ".concat person + def greet(person) = "Hi, ".dup.concat person ruby greet("Homer").should == "Hi, Homer" diff --git a/spec/ruby/language/predefined_spec.rb b/spec/ruby/language/predefined_spec.rb index ed823f185afdb0..ac28f1e8a0fe16 100644 --- a/spec/ruby/language/predefined_spec.rb +++ b/spec/ruby/language/predefined_spec.rb @@ -133,7 +133,7 @@ def obj.foo2(&proc); proc.call; end end it "sets the encoding to the encoding of the source String" do - "abc".force_encoding(Encoding::EUC_JP) =~ /b/ + "abc".dup.force_encoding(Encoding::EUC_JP) =~ /b/ $&.encoding.should equal(Encoding::EUC_JP) end end @@ -146,12 +146,12 @@ def obj.foo2(&proc); proc.call; end end it "sets the encoding to the encoding of the source String" do - "abc".force_encoding(Encoding::EUC_JP) =~ /b/ + "abc".dup.force_encoding(Encoding::EUC_JP) =~ /b/ $`.encoding.should equal(Encoding::EUC_JP) end it "sets an empty result to the encoding of the source String" do - "abc".force_encoding(Encoding::ISO_8859_1) =~ /a/ + "abc".dup.force_encoding(Encoding::ISO_8859_1) =~ /a/ $`.encoding.should equal(Encoding::ISO_8859_1) end end @@ -164,12 +164,12 @@ def obj.foo2(&proc); proc.call; end end it "sets the encoding to the encoding of the source String" do - "abc".force_encoding(Encoding::EUC_JP) =~ /b/ + "abc".dup.force_encoding(Encoding::EUC_JP) =~ /b/ $'.encoding.should equal(Encoding::EUC_JP) end it "sets an empty result to the encoding of the source String" do - "abc".force_encoding(Encoding::ISO_8859_1) =~ /c/ + "abc".dup.force_encoding(Encoding::ISO_8859_1) =~ /c/ $'.encoding.should equal(Encoding::ISO_8859_1) end end @@ -187,7 +187,7 @@ def obj.foo2(&proc); proc.call; end end it "sets the encoding to the encoding of the source String" do - "abc".force_encoding(Encoding::EUC_JP) =~ /(b)/ + "abc".dup.force_encoding(Encoding::EUC_JP) =~ /(b)/ $+.encoding.should equal(Encoding::EUC_JP) end end @@ -214,7 +214,7 @@ def test(arg) end it "sets the encoding to the encoding of the source String" do - "abc".force_encoding(Encoding::EUC_JP) =~ /(b)/ + "abc".dup.force_encoding(Encoding::EUC_JP) =~ /(b)/ $1.encoding.should equal(Encoding::EUC_JP) end end diff --git a/spec/ruby/language/regexp/encoding_spec.rb b/spec/ruby/language/regexp/encoding_spec.rb index febc3fdb3726f9..0571b2d3cf42c3 100644 --- a/spec/ruby/language/regexp/encoding_spec.rb +++ b/spec/ruby/language/regexp/encoding_spec.rb @@ -4,18 +4,18 @@ describe "Regexps with encoding modifiers" do it "supports /e (EUC encoding)" do - match = /./e.match("\303\251".force_encoding(Encoding::EUC_JP)) - match.to_a.should == ["\303\251".force_encoding(Encoding::EUC_JP)] + match = /./e.match("\303\251".dup.force_encoding(Encoding::EUC_JP)) + match.to_a.should == ["\303\251".dup.force_encoding(Encoding::EUC_JP)] end it "supports /e (EUC encoding) with interpolation" do - match = /#{/./}/e.match("\303\251".force_encoding(Encoding::EUC_JP)) - match.to_a.should == ["\303\251".force_encoding(Encoding::EUC_JP)] + match = /#{/./}/e.match("\303\251".dup.force_encoding(Encoding::EUC_JP)) + match.to_a.should == ["\303\251".dup.force_encoding(Encoding::EUC_JP)] end it "supports /e (EUC encoding) with interpolation /o" do - match = /#{/./}/e.match("\303\251".force_encoding(Encoding::EUC_JP)) - match.to_a.should == ["\303\251".force_encoding(Encoding::EUC_JP)] + match = /#{/./}/e.match("\303\251".dup.force_encoding(Encoding::EUC_JP)) + match.to_a.should == ["\303\251".dup.force_encoding(Encoding::EUC_JP)] end it 'uses EUC-JP as /e encoding' do @@ -39,7 +39,7 @@ end it "warns when using /n with a match string with non-ASCII characters and an encoding other than ASCII-8BIT" do - -> { /./n.match("\303\251".force_encoding('utf-8')) }.should complain(%r{historical binary regexp match /.../n against UTF-8 string}) + -> { /./n.match("\303\251".dup.force_encoding('utf-8')) }.should complain(%r{historical binary regexp match /.../n against UTF-8 string}) end it 'uses US-ASCII as /n encoding if all chars are 7-bit' do @@ -63,18 +63,18 @@ end it "supports /s (Windows_31J encoding)" do - match = /./s.match("\303\251".force_encoding(Encoding::Windows_31J)) - match.to_a.should == ["\303".force_encoding(Encoding::Windows_31J)] + match = /./s.match("\303\251".dup.force_encoding(Encoding::Windows_31J)) + match.to_a.should == ["\303".dup.force_encoding(Encoding::Windows_31J)] end it "supports /s (Windows_31J encoding) with interpolation" do - match = /#{/./}/s.match("\303\251".force_encoding(Encoding::Windows_31J)) - match.to_a.should == ["\303".force_encoding(Encoding::Windows_31J)] + match = /#{/./}/s.match("\303\251".dup.force_encoding(Encoding::Windows_31J)) + match.to_a.should == ["\303".dup.force_encoding(Encoding::Windows_31J)] end it "supports /s (Windows_31J encoding) with interpolation and /o" do - match = /#{/./}/s.match("\303\251".force_encoding(Encoding::Windows_31J)) - match.to_a.should == ["\303".force_encoding(Encoding::Windows_31J)] + match = /#{/./}/s.match("\303\251".dup.force_encoding(Encoding::Windows_31J)) + match.to_a.should == ["\303".dup.force_encoding(Encoding::Windows_31J)] end it 'uses Windows-31J as /s encoding' do @@ -86,15 +86,15 @@ end it "supports /u (UTF8 encoding)" do - /./u.match("\303\251".force_encoding('utf-8')).to_a.should == ["\u{e9}"] + /./u.match("\303\251".dup.force_encoding('utf-8')).to_a.should == ["\u{e9}"] end it "supports /u (UTF8 encoding) with interpolation" do - /#{/./}/u.match("\303\251".force_encoding('utf-8')).to_a.should == ["\u{e9}"] + /#{/./}/u.match("\303\251".dup.force_encoding('utf-8')).to_a.should == ["\u{e9}"] end it "supports /u (UTF8 encoding) with interpolation and /o" do - /#{/./}/u.match("\303\251".force_encoding('utf-8')).to_a.should == ["\u{e9}"] + /#{/./}/u.match("\303\251".dup.force_encoding('utf-8')).to_a.should == ["\u{e9}"] end it 'uses UTF-8 as /u encoding' do @@ -122,26 +122,26 @@ end it "raises Encoding::CompatibilityError when the regexp has a fixed, non-ASCII-compatible encoding" do - -> { Regexp.new("".force_encoding("UTF-16LE"), Regexp::FIXEDENCODING) =~ " ".encode("UTF-8") }.should raise_error(Encoding::CompatibilityError) + -> { Regexp.new("".dup.force_encoding("UTF-16LE"), Regexp::FIXEDENCODING) =~ " ".encode("UTF-8") }.should raise_error(Encoding::CompatibilityError) end it "raises Encoding::CompatibilityError when the regexp has a fixed encoding and the match string has non-ASCII characters" do - -> { Regexp.new("".force_encoding("US-ASCII"), Regexp::FIXEDENCODING) =~ "\303\251".force_encoding('UTF-8') }.should raise_error(Encoding::CompatibilityError) + -> { Regexp.new("".dup.force_encoding("US-ASCII"), Regexp::FIXEDENCODING) =~ "\303\251".dup.force_encoding('UTF-8') }.should raise_error(Encoding::CompatibilityError) end it "raises ArgumentError when trying to match a broken String" do - s = "\x80".force_encoding('UTF-8') + s = "\x80".dup.force_encoding('UTF-8') -> { s =~ /./ }.should raise_error(ArgumentError, "invalid byte sequence in UTF-8") end it "computes the Regexp Encoding for each interpolated Regexp instance" do make_regexp = -> str { /#{str}/ } - r = make_regexp.call("été".force_encoding(Encoding::UTF_8)) + r = make_regexp.call("été".dup.force_encoding(Encoding::UTF_8)) r.should.fixed_encoding? r.encoding.should == Encoding::UTF_8 - r = make_regexp.call("abc".force_encoding(Encoding::UTF_8)) + r = make_regexp.call("abc".dup.force_encoding(Encoding::UTF_8)) r.should_not.fixed_encoding? r.encoding.should == Encoding::US_ASCII end diff --git a/spec/ruby/language/rescue_spec.rb b/spec/ruby/language/rescue_spec.rb index d6e7b6c802464c..a3ee4807acd0ae 100644 --- a/spec/ruby/language/rescue_spec.rb +++ b/spec/ruby/language/rescue_spec.rb @@ -553,6 +553,23 @@ class RescueInClassExample eval('1.+((1 rescue 1))').should == 2 end + ruby_version_is "3.4" do + it "does not introduce extra backtrace entries" do + def foo + begin + raise "oops" + rescue + return caller(0, 2) + end + end + line = __LINE__ + foo.should == [ + "#{__FILE__}:#{line-3}:in 'foo'", + "#{__FILE__}:#{line+1}:in 'block (3 levels) in '" + ] + end + end + describe "inline form" do it "can be inlined" do a = 1/0 rescue 1 diff --git a/spec/ruby/language/singleton_class_spec.rb b/spec/ruby/language/singleton_class_spec.rb index 9d037717b24cb9..45e1f7f3ad5936 100644 --- a/spec/ruby/language/singleton_class_spec.rb +++ b/spec/ruby/language/singleton_class_spec.rb @@ -70,7 +70,7 @@ end it "has class String as the superclass of a String instance" do - "blah".singleton_class.superclass.should == String + "blah".dup.singleton_class.superclass.should == String end it "doesn't have singleton class" do diff --git a/spec/ruby/language/string_spec.rb b/spec/ruby/language/string_spec.rb index 418dc2ca7d9622..083a7f5db50de2 100644 --- a/spec/ruby/language/string_spec.rb +++ b/spec/ruby/language/string_spec.rb @@ -231,8 +231,16 @@ def long_string_literals ruby_exe(fixture(__FILE__, "freeze_magic_comment_across_files.rb")).chomp.should == "true" end - it "produce different objects for literals with the same content in different files if the other file doesn't have the comment" do - ruby_exe(fixture(__FILE__, "freeze_magic_comment_across_files_no_comment.rb")).chomp.should == "true" + guard -> { !(eval("'test'").frozen? && "test".equal?("test")) } do + it "produces different objects for literals with the same content in different files if the other file doesn't have the comment and String literals aren't frozen by default" do + ruby_exe(fixture(__FILE__, "freeze_magic_comment_across_files_no_comment.rb")).chomp.should == "true" + end + end + + guard -> { eval("'test'").frozen? && "test".equal?("test") } do + it "produces the same objects for literals with the same content in different files if the other file doesn't have the comment and String literals are frozen by default" do + ruby_exe(fixture(__FILE__, "freeze_magic_comment_across_files_no_comment.rb")).chomp.should == "false" + end end it "produce different objects for literals with the same content in different files if they have different encodings" do @@ -251,12 +259,12 @@ def long_string_literals it "returns a string with the source encoding by default" do "a#{"b"}c".encoding.should == Encoding::BINARY - eval('"a#{"b"}c"'.force_encoding("us-ascii")).encoding.should == Encoding::US_ASCII + eval('"a#{"b"}c"'.dup.force_encoding("us-ascii")).encoding.should == Encoding::US_ASCII eval("# coding: US-ASCII \n 'a#{"b"}c'").encoding.should == Encoding::US_ASCII end it "returns a string with the source encoding, even if the components have another encoding" do - a = "abc".force_encoding("euc-jp") + a = "abc".dup.force_encoding("euc-jp") "#{a}".encoding.should == Encoding::BINARY b = "abc".encode("utf-8") @@ -265,7 +273,7 @@ def long_string_literals it "raises an Encoding::CompatibilityError if the Encodings are not compatible" do a = "\u3042" - b = "\xff".force_encoding "binary" + b = "\xff".dup.force_encoding "binary" -> { "#{a} #{b}" }.should raise_error(Encoding::CompatibilityError) end diff --git a/spec/ruby/library/cgi/escapeURIComponent_spec.rb b/spec/ruby/library/cgi/escapeURIComponent_spec.rb index 2cf283c7786265..f05795a2f5695a 100644 --- a/spec/ruby/library/cgi/escapeURIComponent_spec.rb +++ b/spec/ruby/library/cgi/escapeURIComponent_spec.rb @@ -14,7 +14,7 @@ end it "supports String with invalid encoding" do - string = "\xC0\<\<".force_encoding("UTF-8") + string = "\xC0\<\<".dup.force_encoding("UTF-8") CGI.escapeURIComponent(string).should == "%C0%3C%3C" end diff --git a/spec/ruby/library/csv/generate_spec.rb b/spec/ruby/library/csv/generate_spec.rb index 0a1e3d9604fa7b..b45e2eb95ba262 100644 --- a/spec/ruby/library/csv/generate_spec.rb +++ b/spec/ruby/library/csv/generate_spec.rb @@ -21,7 +21,7 @@ end it "appends and returns the argument itself" do - str = "" + str = +"" csv_str = CSV.generate(str) do |csv| csv.add_row [1, 2, 3] csv << [4, 5, 6] diff --git a/spec/ruby/library/erb/run_spec.rb b/spec/ruby/library/erb/run_spec.rb index 8c07442d8f41e6..602e53ab385855 100644 --- a/spec/ruby/library/erb/run_spec.rb +++ b/spec/ruby/library/erb/run_spec.rb @@ -6,7 +6,7 @@ # lambda { ... }.should output def _steal_stdout orig = $stdout - s = '' + s = +'' def s.write(arg); self << arg.to_s; end $stdout = s begin diff --git a/spec/ruby/library/io-wait/wait_spec.rb b/spec/ruby/library/io-wait/wait_spec.rb index 3861281277bc12..fc07c6a8d9831f 100644 --- a/spec/ruby/library/io-wait/wait_spec.rb +++ b/spec/ruby/library/io-wait/wait_spec.rb @@ -48,25 +48,18 @@ end it "waits for the READABLE event to be ready" do - queue = Queue.new - thread = Thread.new { queue.pop; sleep 1; @w.write('data to read') }; + @r.wait(IO::READABLE, 0).should == nil - queue.push('signal'); - @r.wait(IO::READABLE, 2).should_not == nil - - thread.join + @w.write('data to read') + @r.wait(IO::READABLE, 0).should_not == nil end it "waits for the WRITABLE event to be ready" do written_bytes = IOWaitSpec.exhaust_write_buffer(@w) + @w.wait(IO::WRITABLE, 0).should == nil - queue = Queue.new - thread = Thread.new { queue.pop; sleep 1; @r.read(written_bytes) }; - - queue.push('signal'); - @w.wait(IO::WRITABLE, 2).should_not == nil - - thread.join + @r.read(written_bytes) + @w.wait(IO::WRITABLE, 0).should_not == nil end it "returns nil when the READABLE event is not ready during the timeout" do @@ -89,6 +82,24 @@ -> { @w.wait(-1, 0) }.should raise_error(ArgumentError, "Events must be positive integer!") end end + + it "changes thread status to 'sleep' when waits for READABLE event" do + t = Thread.new { @r.wait(IO::READABLE, 10) } + sleep 1 + t.status.should == 'sleep' + t.kill + t.join # Thread#kill doesn't wait for the thread to end + end + + it "changes thread status to 'sleep' when waits for WRITABLE event" do + written_bytes = IOWaitSpec.exhaust_write_buffer(@w) + + t = Thread.new { @w.wait(IO::WRITABLE, 10) } + sleep 1 + t.status.should == 'sleep' + t.kill + t.join # Thread#kill doesn't wait for the thread to end + end end context "[timeout, mode] passed" do diff --git a/spec/ruby/library/net-ftp/shared/puttextfile.rb b/spec/ruby/library/net-ftp/shared/puttextfile.rb index 3836e954b8072f..4722439674afac 100644 --- a/spec/ruby/library/net-ftp/shared/puttextfile.rb +++ b/spec/ruby/library/net-ftp/shared/puttextfile.rb @@ -27,8 +27,8 @@ it "sends the contents of the passed local_file, using \\r\\n as the newline separator" do @ftp.send(@method, @local_fixture_file, "text") - remote_lines = open(@remote_tmp_file, "rb") {|f| f.read } - local_lines = open(@local_fixture_file, "rb") {|f| f.read } + remote_lines = File.binread(@remote_tmp_file) + local_lines = File.binread(@local_fixture_file) remote_lines.should_not == local_lines remote_lines.should == local_lines.gsub("\n", "\r\n") diff --git a/spec/ruby/library/net-http/http/post_spec.rb b/spec/ruby/library/net-http/http/post_spec.rb index 9e7574015c6079..d7d94fec4a70b4 100644 --- a/spec/ruby/library/net-http/http/post_spec.rb +++ b/spec/ruby/library/net-http/http/post_spec.rb @@ -60,7 +60,7 @@ describe "when passed a block" do it "yields fragments of the response body to the passed block" do - str = "" + str = +"" @http.post("/request", "test=test") do |res| str << res end diff --git a/spec/ruby/library/net-http/httpgenericrequest/exec_spec.rb b/spec/ruby/library/net-http/httpgenericrequest/exec_spec.rb index cf13e9dfd67e96..7de03d7da0490a 100644 --- a/spec/ruby/library/net-http/httpgenericrequest/exec_spec.rb +++ b/spec/ruby/library/net-http/httpgenericrequest/exec_spec.rb @@ -4,7 +4,7 @@ describe "Net::HTTPGenericRequest#exec when passed socket, version, path" do before :each do - @socket = StringIO.new("") + @socket = StringIO.new(+"") @buffered_socket = Net::BufferedIO.new(@socket) end diff --git a/spec/ruby/library/net-http/httpresponse/read_body_spec.rb b/spec/ruby/library/net-http/httpresponse/read_body_spec.rb index 380d17d3b9719e..4530a26bfc264b 100644 --- a/spec/ruby/library/net-http/httpresponse/read_body_spec.rb +++ b/spec/ruby/library/net-http/httpresponse/read_body_spec.rb @@ -25,7 +25,7 @@ describe "when passed a buffer" do it "reads the body to the passed buffer" do @res.reading_body(@socket, true) do - buffer = "" + buffer = +"" @res.read_body(buffer) buffer.should == "test body" end @@ -33,15 +33,15 @@ it "returns the passed buffer" do @res.reading_body(@socket, true) do - buffer = "" + buffer = +"" @res.read_body(buffer).should equal(buffer) end end it "raises an IOError if called a second time" do @res.reading_body(@socket, true) do - @res.read_body("") - -> { @res.read_body("") }.should raise_error(IOError) + @res.read_body(+"") + -> { @res.read_body(+"") }.should raise_error(IOError) end end end @@ -51,7 +51,7 @@ @res.reading_body(@socket, true) do yielded = false - buffer = "" + buffer = +"" @res.read_body do |body| yielded = true buffer << body @@ -79,7 +79,7 @@ describe "when passed buffer and block" do it "raises an ArgumentError" do @res.reading_body(@socket, true) do - -> { @res.read_body("") {} }.should raise_error(ArgumentError) + -> { @res.read_body(+"") {} }.should raise_error(ArgumentError) end end end diff --git a/spec/ruby/library/objectspace/fixtures/trace.rb b/spec/ruby/library/objectspace/fixtures/trace.rb index fd4524b0ba5124..e53a7a0cac8d5b 100644 --- a/spec/ruby/library/objectspace/fixtures/trace.rb +++ b/spec/ruby/library/objectspace/fixtures/trace.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: false require "objspace/trace" a = "foo" b = "b" + "a" + "r" diff --git a/spec/ruby/library/objectspace/trace_spec.rb b/spec/ruby/library/objectspace/trace_spec.rb index 59952a006c32b3..532c282ce47216 100644 --- a/spec/ruby/library/objectspace/trace_spec.rb +++ b/spec/ruby/library/objectspace/trace_spec.rb @@ -6,8 +6,8 @@ file = fixture(__FILE__ , "trace.rb") ruby_exe(file, args: "2>&1").lines(chomp: true).should == [ "objspace/trace is enabled", - "\"foo\" @ #{file}:2", - "\"bar\" @ #{file}:3", + "\"foo\" @ #{file}:3", + "\"bar\" @ #{file}:4", "42" ] end diff --git a/spec/ruby/library/set/compare_by_identity_spec.rb b/spec/ruby/library/set/compare_by_identity_spec.rb index 9ed16021897f2d..602d1e758e9fea 100644 --- a/spec/ruby/library/set/compare_by_identity_spec.rb +++ b/spec/ruby/library/set/compare_by_identity_spec.rb @@ -5,7 +5,7 @@ it "compares its members by identity" do a = "a" b1 = "b" - b2 = "b" + b2 = b1.dup set = Set.new set.compare_by_identity diff --git a/spec/ruby/library/socket/basicsocket/read_nonblock_spec.rb b/spec/ruby/library/socket/basicsocket/read_nonblock_spec.rb index df44a50afa9b77..ea5e65da5c93f7 100644 --- a/spec/ruby/library/socket/basicsocket/read_nonblock_spec.rb +++ b/spec/ruby/library/socket/basicsocket/read_nonblock_spec.rb @@ -18,7 +18,37 @@ it "receives data after it's ready" do IO.select([@r], nil, nil, 2) - @r.recv_nonblock(5).should == "aaa" + @r.read_nonblock(5).should == "aaa" + end + + platform_is_not :windows do + it 'returned data is binary encoded regardless of the external encoding' do + IO.select([@r], nil, nil, 2) + @r.read_nonblock(1).encoding.should == Encoding::BINARY + + @w.send("bbb", 0, @r.getsockname) + @r.set_encoding(Encoding::ISO_8859_1) + IO.select([@r], nil, nil, 2) + buffer = @r.read_nonblock(3) + buffer.should == "bbb" + buffer.encoding.should == Encoding::BINARY + end + end + + it 'replaces the content of the provided buffer without changing its encoding' do + buffer = "initial data".dup.force_encoding(Encoding::UTF_8) + + IO.select([@r], nil, nil, 2) + @r.read_nonblock(3, buffer) + buffer.should == "aaa" + buffer.encoding.should == Encoding::UTF_8 + + @w.send("bbb", 0, @r.getsockname) + @r.set_encoding(Encoding::ISO_8859_1) + IO.select([@r], nil, nil, 2) + @r.read_nonblock(3, buffer) + buffer.should == "bbb" + buffer.encoding.should == Encoding::UTF_8 end platform_is :linux do diff --git a/spec/ruby/library/socket/basicsocket/read_spec.rb b/spec/ruby/library/socket/basicsocket/read_spec.rb new file mode 100644 index 00000000000000..ba9de7d5cf81ba --- /dev/null +++ b/spec/ruby/library/socket/basicsocket/read_spec.rb @@ -0,0 +1,47 @@ +require_relative '../spec_helper' +require_relative '../fixtures/classes' + +describe "BasicSocket#read" do + SocketSpecs.each_ip_protocol do |family, ip_address| + before :each do + @r = Socket.new(family, :DGRAM) + @w = Socket.new(family, :DGRAM) + + @r.bind(Socket.pack_sockaddr_in(0, ip_address)) + @w.send("aaa", 0, @r.getsockname) + end + + after :each do + @r.close unless @r.closed? + @w.close unless @w.closed? + end + + it "receives data after it's ready" do + @r.read(3).should == "aaa" + end + + it 'returned data is binary encoded regardless of the external encoding' do + @r.read(3).encoding.should == Encoding::BINARY + + @w.send("bbb", 0, @r.getsockname) + @r.set_encoding(Encoding::UTF_8) + buffer = @r.read(3) + buffer.should == "bbb" + buffer.encoding.should == Encoding::BINARY + end + + it 'replaces the content of the provided buffer without changing its encoding' do + buffer = "initial data".dup.force_encoding(Encoding::UTF_8) + + @r.read(3, buffer) + buffer.should == "aaa" + buffer.encoding.should == Encoding::UTF_8 + + @w.send("bbb", 0, @r.getsockname) + @r.set_encoding(Encoding::ISO_8859_1) + @r.read(3, buffer) + buffer.should == "bbb" + buffer.encoding.should == Encoding::UTF_8 + end + end +end diff --git a/spec/ruby/library/socket/basicsocket/recv_nonblock_spec.rb b/spec/ruby/library/socket/basicsocket/recv_nonblock_spec.rb index b6ab8a9cea6c2c..17c846054df17a 100644 --- a/spec/ruby/library/socket/basicsocket/recv_nonblock_spec.rb +++ b/spec/ruby/library/socket/basicsocket/recv_nonblock_spec.rb @@ -52,7 +52,7 @@ @s2.send("data", 0, @s1.getsockname) IO.select([@s1], nil, nil, 2) - buf = "foo" + buf = +"foo" @s1.recv_nonblock(5, 0, buf) buf.should == "data" end diff --git a/spec/ruby/library/socket/basicsocket/recv_spec.rb b/spec/ruby/library/socket/basicsocket/recv_spec.rb index a56114f4ab57d6..9fe8c52f9a5233 100644 --- a/spec/ruby/library/socket/basicsocket/recv_spec.rb +++ b/spec/ruby/library/socket/basicsocket/recv_spec.rb @@ -100,7 +100,7 @@ socket.write("data") client = @server.accept - buf = "foo" + buf = +"foo" begin client.recv(4, 0, buf) ensure diff --git a/spec/ruby/library/socket/basicsocket/send_spec.rb b/spec/ruby/library/socket/basicsocket/send_spec.rb index 041ce03d72c7ee..86b5567026d8bc 100644 --- a/spec/ruby/library/socket/basicsocket/send_spec.rb +++ b/spec/ruby/library/socket/basicsocket/send_spec.rb @@ -17,7 +17,7 @@ end it "sends a message to another socket and returns the number of bytes sent" do - data = "" + data = +"" t = Thread.new do client = @server.accept loop do @@ -62,7 +62,7 @@ end it "accepts a sockaddr as recipient address" do - data = "" + data = +"" t = Thread.new do client = @server.accept loop do diff --git a/spec/ruby/library/stringio/append_spec.rb b/spec/ruby/library/stringio/append_spec.rb index 5383e3e795035b..cb50d73d1bf1b7 100644 --- a/spec/ruby/library/stringio/append_spec.rb +++ b/spec/ruby/library/stringio/append_spec.rb @@ -3,7 +3,7 @@ describe "StringIO#<< when passed [Object]" do before :each do - @io = StringIO.new("example") + @io = StringIO.new(+"example") end it "returns self" do @@ -44,10 +44,10 @@ describe "StringIO#<< when self is not writable" do it "raises an IOError" do - io = StringIO.new("test", "r") + io = StringIO.new(+"test", "r") -> { io << "test" }.should raise_error(IOError) - io = StringIO.new("test") + io = StringIO.new(+"test") io.close_write -> { io << "test" }.should raise_error(IOError) end @@ -55,7 +55,7 @@ describe "StringIO#<< when in append mode" do before :each do - @io = StringIO.new("example", "a") + @io = StringIO.new(+"example", "a") end it "appends the passed argument to the end of self, ignoring current position" do diff --git a/spec/ruby/library/stringio/binmode_spec.rb b/spec/ruby/library/stringio/binmode_spec.rb index 853d9c9bd66542..9e92c63814e84a 100644 --- a/spec/ruby/library/stringio/binmode_spec.rb +++ b/spec/ruby/library/stringio/binmode_spec.rb @@ -3,7 +3,7 @@ describe "StringIO#binmode" do it "returns self" do - io = StringIO.new("example") + io = StringIO.new(+"example") io.binmode.should equal(io) end diff --git a/spec/ruby/library/stringio/close_read_spec.rb b/spec/ruby/library/stringio/close_read_spec.rb index 80bd547e853a6f..0f08e1ff2e5958 100644 --- a/spec/ruby/library/stringio/close_read_spec.rb +++ b/spec/ruby/library/stringio/close_read_spec.rb @@ -3,7 +3,7 @@ describe "StringIO#close_read" do before :each do - @io = StringIO.new("example") + @io = StringIO.new(+"example") end it "returns nil" do @@ -21,7 +21,7 @@ end it "raises an IOError when in write-only mode" do - io = StringIO.new("example", "w") + io = StringIO.new(+"example", "w") -> { io.close_read }.should raise_error(IOError) io = StringIO.new("example") diff --git a/spec/ruby/library/stringio/close_write_spec.rb b/spec/ruby/library/stringio/close_write_spec.rb index 1a4cfa113e0d85..c86c3f982622ed 100644 --- a/spec/ruby/library/stringio/close_write_spec.rb +++ b/spec/ruby/library/stringio/close_write_spec.rb @@ -3,7 +3,7 @@ describe "StringIO#close_write" do before :each do - @io = StringIO.new("example") + @io = StringIO.new(+"example") end it "returns nil" do @@ -21,10 +21,10 @@ end it "raises an IOError when in read-only mode" do - io = StringIO.new("example", "r") + io = StringIO.new(+"example", "r") -> { io.close_write }.should raise_error(IOError) - io = StringIO.new("example") + io = StringIO.new(+"example") io.close_write io.close_write.should == nil end diff --git a/spec/ruby/library/stringio/closed_read_spec.rb b/spec/ruby/library/stringio/closed_read_spec.rb index cb4267ac9873c9..b4dcadc3a43e10 100644 --- a/spec/ruby/library/stringio/closed_read_spec.rb +++ b/spec/ruby/library/stringio/closed_read_spec.rb @@ -3,7 +3,7 @@ describe "StringIO#closed_read?" do it "returns true if self is not readable" do - io = StringIO.new("example", "r+") + io = StringIO.new(+"example", "r+") io.close_write io.closed_read?.should be_false io.close_read diff --git a/spec/ruby/library/stringio/closed_spec.rb b/spec/ruby/library/stringio/closed_spec.rb index ca8a2232a89307..bf7ba63184e7cb 100644 --- a/spec/ruby/library/stringio/closed_spec.rb +++ b/spec/ruby/library/stringio/closed_spec.rb @@ -3,13 +3,13 @@ describe "StringIO#closed?" do it "returns true if self is completely closed" do - io = StringIO.new("example", "r+") + io = StringIO.new(+"example", "r+") io.close_read io.closed?.should be_false io.close_write io.closed?.should be_true - io = StringIO.new("example", "r+") + io = StringIO.new(+"example", "r+") io.close io.closed?.should be_true end diff --git a/spec/ruby/library/stringio/closed_write_spec.rb b/spec/ruby/library/stringio/closed_write_spec.rb index 5c111affd85828..2bd3e6fa8b6a11 100644 --- a/spec/ruby/library/stringio/closed_write_spec.rb +++ b/spec/ruby/library/stringio/closed_write_spec.rb @@ -3,7 +3,7 @@ describe "StringIO#closed_write?" do it "returns true if self is not writable" do - io = StringIO.new("example", "r+") + io = StringIO.new(+"example", "r+") io.close_read io.closed_write?.should be_false io.close_write diff --git a/spec/ruby/library/stringio/flush_spec.rb b/spec/ruby/library/stringio/flush_spec.rb index 17a16dfdd540aa..4dc58b1d486128 100644 --- a/spec/ruby/library/stringio/flush_spec.rb +++ b/spec/ruby/library/stringio/flush_spec.rb @@ -3,7 +3,7 @@ describe "StringIO#flush" do it "returns self" do - io = StringIO.new("flush") + io = StringIO.new(+"flush") io.flush.should equal(io) end end diff --git a/spec/ruby/library/stringio/fsync_spec.rb b/spec/ruby/library/stringio/fsync_spec.rb index 8fb2b59a246831..85053cb2e5335a 100644 --- a/spec/ruby/library/stringio/fsync_spec.rb +++ b/spec/ruby/library/stringio/fsync_spec.rb @@ -3,7 +3,7 @@ describe "StringIO#fsync" do it "returns zero" do - io = StringIO.new("fsync") + io = StringIO.new(+"fsync") io.fsync.should eql(0) end end diff --git a/spec/ruby/library/stringio/gets_spec.rb b/spec/ruby/library/stringio/gets_spec.rb index d597ec0e45ed83..4af7704a41749c 100644 --- a/spec/ruby/library/stringio/gets_spec.rb +++ b/spec/ruby/library/stringio/gets_spec.rb @@ -233,7 +233,7 @@ describe "StringIO#gets when in write-only mode" do it "raises an IOError" do - io = StringIO.new("xyz", "w") + io = StringIO.new(+"xyz", "w") -> { io.gets }.should raise_error(IOError) io = StringIO.new("xyz") diff --git a/spec/ruby/library/stringio/initialize_spec.rb b/spec/ruby/library/stringio/initialize_spec.rb index 158c08488b215f..ad067a0be17ca8 100644 --- a/spec/ruby/library/stringio/initialize_spec.rb +++ b/spec/ruby/library/stringio/initialize_spec.rb @@ -13,99 +13,99 @@ it "sets the mode based on the passed mode" do io = StringIO.allocate - io.send(:initialize, "example", "r") + io.send(:initialize, +"example", "r") io.closed_read?.should be_false io.closed_write?.should be_true io = StringIO.allocate - io.send(:initialize, "example", "rb") + io.send(:initialize, +"example", "rb") io.closed_read?.should be_false io.closed_write?.should be_true io = StringIO.allocate - io.send(:initialize, "example", "r+") + io.send(:initialize, +"example", "r+") io.closed_read?.should be_false io.closed_write?.should be_false io = StringIO.allocate - io.send(:initialize, "example", "rb+") + io.send(:initialize, +"example", "rb+") io.closed_read?.should be_false io.closed_write?.should be_false io = StringIO.allocate - io.send(:initialize, "example", "w") + io.send(:initialize, +"example", "w") io.closed_read?.should be_true io.closed_write?.should be_false io = StringIO.allocate - io.send(:initialize, "example", "wb") + io.send(:initialize, +"example", "wb") io.closed_read?.should be_true io.closed_write?.should be_false io = StringIO.allocate - io.send(:initialize, "example", "w+") + io.send(:initialize, +"example", "w+") io.closed_read?.should be_false io.closed_write?.should be_false io = StringIO.allocate - io.send(:initialize, "example", "wb+") + io.send(:initialize, +"example", "wb+") io.closed_read?.should be_false io.closed_write?.should be_false io = StringIO.allocate - io.send(:initialize, "example", "a") + io.send(:initialize, +"example", "a") io.closed_read?.should be_true io.closed_write?.should be_false io = StringIO.allocate - io.send(:initialize, "example", "ab") + io.send(:initialize, +"example", "ab") io.closed_read?.should be_true io.closed_write?.should be_false io = StringIO.allocate - io.send(:initialize, "example", "a+") + io.send(:initialize, +"example", "a+") io.closed_read?.should be_false io.closed_write?.should be_false io = StringIO.allocate - io.send(:initialize, "example", "ab+") + io.send(:initialize, +"example", "ab+") io.closed_read?.should be_false io.closed_write?.should be_false end it "allows passing the mode as an Integer" do io = StringIO.allocate - io.send(:initialize, "example", IO::RDONLY) + io.send(:initialize, +"example", IO::RDONLY) io.closed_read?.should be_false io.closed_write?.should be_true io = StringIO.allocate - io.send(:initialize, "example", IO::RDWR) + io.send(:initialize, +"example", IO::RDWR) io.closed_read?.should be_false io.closed_write?.should be_false io = StringIO.allocate - io.send(:initialize, "example", IO::WRONLY) + io.send(:initialize, +"example", IO::WRONLY) io.closed_read?.should be_true io.closed_write?.should be_false io = StringIO.allocate - io.send(:initialize, "example", IO::WRONLY | IO::TRUNC) + io.send(:initialize, +"example", IO::WRONLY | IO::TRUNC) io.closed_read?.should be_true io.closed_write?.should be_false io = StringIO.allocate - io.send(:initialize, "example", IO::RDWR | IO::TRUNC) + io.send(:initialize, +"example", IO::RDWR | IO::TRUNC) io.closed_read?.should be_false io.closed_write?.should be_false io = StringIO.allocate - io.send(:initialize, "example", IO::WRONLY | IO::APPEND) + io.send(:initialize, +"example", IO::WRONLY | IO::APPEND) io.closed_read?.should be_true io.closed_write?.should be_false io = StringIO.allocate - io.send(:initialize, "example", IO::RDWR | IO::APPEND) + io.send(:initialize, +"example", IO::RDWR | IO::APPEND) io.closed_read?.should be_false io.closed_write?.should be_false end @@ -118,7 +118,7 @@ it "tries to convert the passed mode to a String using #to_str" do obj = mock('to_str') obj.should_receive(:to_str).and_return("r") - @io.send(:initialize, "example", obj) + @io.send(:initialize, +"example", obj) @io.closed_read?.should be_false @io.closed_write?.should be_true @@ -142,12 +142,18 @@ @io.string.should equal(str) end - it "sets the mode to read-write" do - @io.send(:initialize, "example") + it "sets the mode to read-write if the string is mutable" do + @io.send(:initialize, +"example") @io.closed_read?.should be_false @io.closed_write?.should be_false end + it "sets the mode to read if the string is frozen" do + @io.send(:initialize, -"example") + @io.closed_read?.should be_false + @io.closed_write?.should be_true + end + it "tries to convert the passed Object to a String using #to_str" do obj = mock('to_str') obj.should_receive(:to_str).and_return("example") @@ -172,22 +178,22 @@ end it "accepts a mode argument set to nil with a valid :mode option" do - @io = StringIO.new('', nil, mode: "w") + @io = StringIO.new(+'', nil, mode: "w") @io.write("foo").should == 3 end it "accepts a mode argument with a :mode option set to nil" do - @io = StringIO.new('', "w", mode: nil) + @io = StringIO.new(+'', "w", mode: nil) @io.write("foo").should == 3 end it "sets binmode from :binmode option" do - @io = StringIO.new('', 'w', binmode: true) + @io = StringIO.new(+'', 'w', binmode: true) @io.external_encoding.to_s.should == "ASCII-8BIT" # #binmode? isn't implemented in StringIO end it "does not set binmode from false :binmode" do - @io = StringIO.new('', 'w', binmode: false) + @io = StringIO.new(+'', 'w', binmode: false) @io.external_encoding.to_s.should == "UTF-8" # #binmode? isn't implemented in StringIO end end @@ -196,54 +202,54 @@ describe "StringIO#initialize when passed keyword arguments and error happens" do it "raises an error if passed encodings two ways" do -> { - @io = StringIO.new('', 'w:ISO-8859-1', encoding: 'ISO-8859-1') + @io = StringIO.new(+'', 'w:ISO-8859-1', encoding: 'ISO-8859-1') }.should raise_error(ArgumentError) -> { - @io = StringIO.new('', 'w:ISO-8859-1', external_encoding: 'ISO-8859-1') + @io = StringIO.new(+'', 'w:ISO-8859-1', external_encoding: 'ISO-8859-1') }.should raise_error(ArgumentError) -> { - @io = StringIO.new('', 'w:ISO-8859-1:UTF-8', internal_encoding: 'ISO-8859-1') + @io = StringIO.new(+'', 'w:ISO-8859-1:UTF-8', internal_encoding: 'ISO-8859-1') }.should raise_error(ArgumentError) end it "raises an error if passed matching binary/text mode two ways" do -> { - @io = StringIO.new('', "wb", binmode: true) + @io = StringIO.new(+'', "wb", binmode: true) }.should raise_error(ArgumentError) -> { - @io = StringIO.new('', "wt", textmode: true) + @io = StringIO.new(+'', "wt", textmode: true) }.should raise_error(ArgumentError) -> { - @io = StringIO.new('', "wb", textmode: false) + @io = StringIO.new(+'', "wb", textmode: false) }.should raise_error(ArgumentError) -> { - @io = StringIO.new('', "wt", binmode: false) + @io = StringIO.new(+'', "wt", binmode: false) }.should raise_error(ArgumentError) end it "raises an error if passed conflicting binary/text mode two ways" do -> { - @io = StringIO.new('', "wb", binmode: false) + @io = StringIO.new(+'', "wb", binmode: false) }.should raise_error(ArgumentError) -> { - @io = StringIO.new('', "wt", textmode: false) + @io = StringIO.new(+'', "wt", textmode: false) }.should raise_error(ArgumentError) -> { - @io = StringIO.new('', "wb", textmode: true) + @io = StringIO.new(+'', "wb", textmode: true) }.should raise_error(ArgumentError) -> { - @io = StringIO.new('', "wt", binmode: true) + @io = StringIO.new(+'', "wt", binmode: true) }.should raise_error(ArgumentError) end it "raises an error when trying to set both binmode and textmode" do -> { - @io = StringIO.new('', "w", textmode: true, binmode: true) + @io = StringIO.new(+'', "w", textmode: true, binmode: true) }.should raise_error(ArgumentError) -> { - @io = StringIO.new('', File::Constants::WRONLY, textmode: true, binmode: true) + @io = StringIO.new(+'', File::Constants::WRONLY, textmode: true, binmode: true) }.should raise_error(ArgumentError) end end @@ -258,7 +264,7 @@ end it "sets the mode to read-write" do - @io.send(:initialize, "example") + @io.send(:initialize) @io.closed_read?.should be_false @io.closed_write?.should be_false end @@ -289,13 +295,13 @@ end it "the encoding to the encoding of the String when passed a String" do - s = ''.force_encoding(Encoding::EUC_JP) + s = ''.dup.force_encoding(Encoding::EUC_JP) io = StringIO.new(s) io.string.encoding.should == Encoding::EUC_JP end it "the #external_encoding to the encoding of the String when passed a String" do - s = ''.force_encoding(Encoding::EUC_JP) + s = ''.dup.force_encoding(Encoding::EUC_JP) io = StringIO.new(s) io.external_encoding.should == Encoding::EUC_JP end diff --git a/spec/ruby/library/stringio/open_spec.rb b/spec/ruby/library/stringio/open_spec.rb index 3068e19435903a..b7c90661f992a3 100644 --- a/spec/ruby/library/stringio/open_spec.rb +++ b/spec/ruby/library/stringio/open_spec.rb @@ -8,26 +8,26 @@ end it "returns the blocks return value when yielding" do - ret = StringIO.open("example", "r") { :test } + ret = StringIO.open(+"example", "r") { :test } ret.should equal(:test) end it "yields self to the passed block" do io = nil - StringIO.open("example", "r") { |strio| io = strio } + StringIO.open(+"example", "r") { |strio| io = strio } io.should be_kind_of(StringIO) end it "closes self after yielding" do io = nil - StringIO.open("example", "r") { |strio| io = strio } + StringIO.open(+"example", "r") { |strio| io = strio } io.closed?.should be_true end it "even closes self when an exception is raised while yielding" do io = nil begin - StringIO.open("example", "r") do |strio| + StringIO.open(+"example", "r") do |strio| io = strio raise "Error" end @@ -38,14 +38,14 @@ it "sets self's string to nil after yielding" do io = nil - StringIO.open("example", "r") { |strio| io = strio } + StringIO.open(+"example", "r") { |strio| io = strio } io.string.should be_nil end it "even sets self's string to nil when an exception is raised while yielding" do io = nil begin - StringIO.open("example", "r") do |strio| + StringIO.open(+"example", "r") do |strio| io = strio raise "Error" end @@ -55,81 +55,81 @@ end it "sets the mode based on the passed mode" do - io = StringIO.open("example", "r") + io = StringIO.open(+"example", "r") io.closed_read?.should be_false io.closed_write?.should be_true - io = StringIO.open("example", "rb") + io = StringIO.open(+"example", "rb") io.closed_read?.should be_false io.closed_write?.should be_true - io = StringIO.open("example", "r+") + io = StringIO.open(+"example", "r+") io.closed_read?.should be_false io.closed_write?.should be_false - io = StringIO.open("example", "rb+") + io = StringIO.open(+"example", "rb+") io.closed_read?.should be_false io.closed_write?.should be_false - io = StringIO.open("example", "w") + io = StringIO.open(+"example", "w") io.closed_read?.should be_true io.closed_write?.should be_false - io = StringIO.open("example", "wb") + io = StringIO.open(+"example", "wb") io.closed_read?.should be_true io.closed_write?.should be_false - io = StringIO.open("example", "w+") + io = StringIO.open(+"example", "w+") io.closed_read?.should be_false io.closed_write?.should be_false - io = StringIO.open("example", "wb+") + io = StringIO.open(+"example", "wb+") io.closed_read?.should be_false io.closed_write?.should be_false - io = StringIO.open("example", "a") + io = StringIO.open(+"example", "a") io.closed_read?.should be_true io.closed_write?.should be_false - io = StringIO.open("example", "ab") + io = StringIO.open(+"example", "ab") io.closed_read?.should be_true io.closed_write?.should be_false - io = StringIO.open("example", "a+") + io = StringIO.open(+"example", "a+") io.closed_read?.should be_false io.closed_write?.should be_false - io = StringIO.open("example", "ab+") + io = StringIO.open(+"example", "ab+") io.closed_read?.should be_false io.closed_write?.should be_false end it "allows passing the mode as an Integer" do - io = StringIO.open("example", IO::RDONLY) + io = StringIO.open(+"example", IO::RDONLY) io.closed_read?.should be_false io.closed_write?.should be_true - io = StringIO.open("example", IO::RDWR) + io = StringIO.open(+"example", IO::RDWR) io.closed_read?.should be_false io.closed_write?.should be_false - io = StringIO.open("example", IO::WRONLY) + io = StringIO.open(+"example", IO::WRONLY) io.closed_read?.should be_true io.closed_write?.should be_false - io = StringIO.open("example", IO::WRONLY | IO::TRUNC) + io = StringIO.open(+"example", IO::WRONLY | IO::TRUNC) io.closed_read?.should be_true io.closed_write?.should be_false - io = StringIO.open("example", IO::RDWR | IO::TRUNC) + io = StringIO.open(+"example", IO::RDWR | IO::TRUNC) io.closed_read?.should be_false io.closed_write?.should be_false - io = StringIO.open("example", IO::WRONLY | IO::APPEND) + io = StringIO.open(+"example", IO::WRONLY | IO::APPEND) io.closed_read?.should be_true io.closed_write?.should be_false - io = StringIO.open("example", IO::RDWR | IO::APPEND) + io = StringIO.open(+"example", IO::RDWR | IO::APPEND) io.closed_read?.should be_false io.closed_write?.should be_false end @@ -141,7 +141,7 @@ it "tries to convert the passed mode to a String using #to_str" do obj = mock('to_str') obj.should_receive(:to_str).and_return("r") - io = StringIO.open("example", obj) + io = StringIO.open(+"example", obj) io.closed_read?.should be_false io.closed_write?.should be_true @@ -163,16 +163,16 @@ it "yields self to the passed block" do io = nil - ret = StringIO.open("example") { |strio| io = strio } + ret = StringIO.open(+"example") { |strio| io = strio } io.should equal(ret) end it "sets the mode to read-write (r+)" do - io = StringIO.open("example") + io = StringIO.open(+"example") io.closed_read?.should be_false io.closed_write?.should be_false - io = StringIO.new("example") + io = StringIO.new(+"example") io.printf("%d", 123) io.string.should == "123mple" end @@ -204,7 +204,7 @@ io.closed_read?.should be_false io.closed_write?.should be_false - io = StringIO.new("example") + io = StringIO.new(+"example") io.printf("%d", 123) io.string.should == "123mple" end diff --git a/spec/ruby/library/stringio/print_spec.rb b/spec/ruby/library/stringio/print_spec.rb index 6ac64309002902..00c33367dc695e 100644 --- a/spec/ruby/library/stringio/print_spec.rb +++ b/spec/ruby/library/stringio/print_spec.rb @@ -3,7 +3,7 @@ describe "StringIO#print" do before :each do - @io = StringIO.new('example') + @io = StringIO.new(+'example') end it "prints $_ when passed no arguments" do @@ -73,7 +73,7 @@ describe "StringIO#print when in append mode" do before :each do - @io = StringIO.new("example", "a") + @io = StringIO.new(+"example", "a") end it "appends the passed argument to the end of self" do @@ -92,10 +92,10 @@ describe "StringIO#print when self is not writable" do it "raises an IOError" do - io = StringIO.new("test", "r") + io = StringIO.new(+"test", "r") -> { io.print("test") }.should raise_error(IOError) - io = StringIO.new("test") + io = StringIO.new(+"test") io.close_write -> { io.print("test") }.should raise_error(IOError) end diff --git a/spec/ruby/library/stringio/printf_spec.rb b/spec/ruby/library/stringio/printf_spec.rb index f3f669a1855266..ca82e847575e1d 100644 --- a/spec/ruby/library/stringio/printf_spec.rb +++ b/spec/ruby/library/stringio/printf_spec.rb @@ -41,7 +41,7 @@ describe "StringIO#printf when in read-write mode" do before :each do - @io = StringIO.new("example", "r+") + @io = StringIO.new(+"example", "r+") end it "starts from the beginning" do @@ -62,7 +62,7 @@ describe "StringIO#printf when in append mode" do before :each do - @io = StringIO.new("example", "a") + @io = StringIO.new(+"example", "a") end it "appends the passed argument to the end of self" do @@ -81,10 +81,10 @@ describe "StringIO#printf when self is not writable" do it "raises an IOError" do - io = StringIO.new("test", "r") + io = StringIO.new(+"test", "r") -> { io.printf("test") }.should raise_error(IOError) - io = StringIO.new("test") + io = StringIO.new(+"test") io.close_write -> { io.printf("test") }.should raise_error(IOError) end diff --git a/spec/ruby/library/stringio/putc_spec.rb b/spec/ruby/library/stringio/putc_spec.rb index 1ce53b7ef20ed4..9f1ac8ffb2b50e 100644 --- a/spec/ruby/library/stringio/putc_spec.rb +++ b/spec/ruby/library/stringio/putc_spec.rb @@ -3,7 +3,7 @@ describe "StringIO#putc when passed [String]" do before :each do - @io = StringIO.new('example') + @io = StringIO.new(+'example') end it "overwrites the character at the current position" do @@ -54,7 +54,7 @@ describe "StringIO#putc when passed [Object]" do before :each do - @io = StringIO.new('example') + @io = StringIO.new(+'example') end it "it writes the passed Integer % 256 to self" do @@ -85,7 +85,7 @@ describe "StringIO#putc when in append mode" do it "appends to the end of self" do - io = StringIO.new("test", "a") + io = StringIO.new(+"test", "a") io.putc(?t) io.string.should == "testt" end @@ -93,10 +93,10 @@ describe "StringIO#putc when self is not writable" do it "raises an IOError" do - io = StringIO.new("test", "r") + io = StringIO.new(+"test", "r") -> { io.putc(?a) }.should raise_error(IOError) - io = StringIO.new("test") + io = StringIO.new(+"test") io.close_write -> { io.putc("t") }.should raise_error(IOError) end diff --git a/spec/ruby/library/stringio/puts_spec.rb b/spec/ruby/library/stringio/puts_spec.rb index 9c890262dd996f..054ec8227f5563 100644 --- a/spec/ruby/library/stringio/puts_spec.rb +++ b/spec/ruby/library/stringio/puts_spec.rb @@ -145,7 +145,7 @@ describe "StringIO#puts when in append mode" do before :each do - @io = StringIO.new("example", "a") + @io = StringIO.new(+"example", "a") end it "appends the passed argument to the end of self" do @@ -164,10 +164,10 @@ describe "StringIO#puts when self is not writable" do it "raises an IOError" do - io = StringIO.new("test", "r") + io = StringIO.new(+"test", "r") -> { io.puts }.should raise_error(IOError) - io = StringIO.new("test") + io = StringIO.new(+"test") io.close_write -> { io.puts }.should raise_error(IOError) end @@ -175,7 +175,7 @@ describe "StringIO#puts when passed an encoded string" do it "stores the bytes unmodified" do - io = StringIO.new("") + io = StringIO.new(+"") io.puts "\x00\x01\x02" io.puts "æåø" diff --git a/spec/ruby/library/stringio/read_nonblock_spec.rb b/spec/ruby/library/stringio/read_nonblock_spec.rb index d4ec56d9aadafa..74736f7792dc86 100644 --- a/spec/ruby/library/stringio/read_nonblock_spec.rb +++ b/spec/ruby/library/stringio/read_nonblock_spec.rb @@ -8,7 +8,7 @@ it "accepts :exception option" do io = StringIO.new("example") - io.read_nonblock(3, buffer = "", exception: true) + io.read_nonblock(3, buffer = +"", exception: true) buffer.should == "exa" end end @@ -40,7 +40,7 @@ context "when exception option is set to false" do context "when the end is reached" do it "returns nil" do - stringio = StringIO.new('') + stringio = StringIO.new(+'') stringio << "hello" stringio.rewind diff --git a/spec/ruby/library/stringio/read_spec.rb b/spec/ruby/library/stringio/read_spec.rb index 52ab3dcf470f5e..e49f26212719f2 100644 --- a/spec/ruby/library/stringio/read_spec.rb +++ b/spec/ruby/library/stringio/read_spec.rb @@ -53,7 +53,7 @@ end it "reads [length] characters into the buffer" do - buf = "foo" + buf = +"foo" result = @io.read(10, buf) buf.should == "abcdefghij" diff --git a/spec/ruby/library/stringio/readline_spec.rb b/spec/ruby/library/stringio/readline_spec.rb index b794e5fade5f57..b16a16e23fd894 100644 --- a/spec/ruby/library/stringio/readline_spec.rb +++ b/spec/ruby/library/stringio/readline_spec.rb @@ -113,7 +113,7 @@ describe "StringIO#readline when in write-only mode" do it "raises an IOError" do - io = StringIO.new("xyz", "w") + io = StringIO.new(+"xyz", "w") -> { io.readline }.should raise_error(IOError) io = StringIO.new("xyz") diff --git a/spec/ruby/library/stringio/readlines_spec.rb b/spec/ruby/library/stringio/readlines_spec.rb index c471d0fd737407..ed7cc22b3d7ca4 100644 --- a/spec/ruby/library/stringio/readlines_spec.rb +++ b/spec/ruby/library/stringio/readlines_spec.rb @@ -83,7 +83,7 @@ describe "StringIO#readlines when in write-only mode" do it "raises an IOError" do - io = StringIO.new("xyz", "w") + io = StringIO.new(+"xyz", "w") -> { io.readlines }.should raise_error(IOError) io = StringIO.new("xyz") diff --git a/spec/ruby/library/stringio/readpartial_spec.rb b/spec/ruby/library/stringio/readpartial_spec.rb index 2601fe8c427092..f25cef40148137 100644 --- a/spec/ruby/library/stringio/readpartial_spec.rb +++ b/spec/ruby/library/stringio/readpartial_spec.rb @@ -3,7 +3,7 @@ describe "StringIO#readpartial" do before :each do - @string = StringIO.new('Stop, look, listen') + @string = StringIO.new(+'Stop, look, listen') end after :each do @@ -48,7 +48,7 @@ end it "discards the existing buffer content upon successful read" do - buffer = "existing" + buffer = +"existing" @string.readpartial(11, buffer) buffer.should == "Stop, look," end @@ -59,7 +59,7 @@ end it "discards the existing buffer content upon error" do - buffer = 'hello' + buffer = +'hello' @string.readpartial(100) -> { @string.readpartial(1, buffer) }.should raise_error(EOFError) buffer.should be_empty diff --git a/spec/ruby/library/stringio/reopen_spec.rb b/spec/ruby/library/stringio/reopen_spec.rb index 9851c5b7069718..7021ff17e5cd9d 100644 --- a/spec/ruby/library/stringio/reopen_spec.rb +++ b/spec/ruby/library/stringio/reopen_spec.rb @@ -12,12 +12,12 @@ @io.closed_write?.should be_true @io.string.should == "reopened" - @io.reopen("reopened, twice", IO::WRONLY) + @io.reopen(+"reopened, twice", IO::WRONLY) @io.closed_read?.should be_true @io.closed_write?.should be_false @io.string.should == "reopened, twice" - @io.reopen("reopened, another time", IO::RDWR) + @io.reopen(+"reopened, another time", IO::RDWR) @io.closed_read?.should be_false @io.closed_write?.should be_false @io.string.should == "reopened, another time" @@ -25,7 +25,7 @@ it "tries to convert the passed Object to a String using #to_str" do obj = mock("to_str") - obj.should_receive(:to_str).and_return("to_str") + obj.should_receive(:to_str).and_return(+"to_str") @io.reopen(obj, IO::RDWR) @io.string.should == "to_str" end @@ -60,24 +60,24 @@ @io.closed_write?.should be_true @io.string.should == "reopened" - @io.reopen("reopened, twice", "r+") + @io.reopen(+"reopened, twice", "r+") @io.closed_read?.should be_false @io.closed_write?.should be_false @io.string.should == "reopened, twice" - @io.reopen("reopened, another", "w+") + @io.reopen(+"reopened, another", "w+") @io.closed_read?.should be_false @io.closed_write?.should be_false @io.string.should == "" - @io.reopen("reopened, another time", "r+") + @io.reopen(+"reopened, another time", "r+") @io.closed_read?.should be_false @io.closed_write?.should be_false @io.string.should == "reopened, another time" end it "truncates the passed String when opened in truncate mode" do - @io.reopen(str = "reopened", "w") + @io.reopen(str = +"reopened", "w") str.should == "" end @@ -94,13 +94,13 @@ it "resets self's position to 0" do @io.read(5) - @io.reopen("reopened") + @io.reopen(+"reopened") @io.pos.should eql(0) end it "resets self's line number to 0" do @io.gets - @io.reopen("reopened") + @io.reopen(+"reopened") @io.lineno.should eql(0) end @@ -134,7 +134,7 @@ it "reopens self with the passed String in read-write mode" do @io.close - @io.reopen("reopened") + @io.reopen(+"reopened") @io.closed_write?.should be_false @io.closed_read?.should be_false @@ -144,13 +144,13 @@ it "resets self's position to 0" do @io.read(5) - @io.reopen("reopened") + @io.reopen(+"reopened") @io.pos.should eql(0) end it "resets self's line number to 0" do @io.gets - @io.reopen("reopened") + @io.reopen(+"reopened") @io.lineno.should eql(0) end end @@ -172,7 +172,7 @@ it "tries to convert the passed Object to a StringIO using #to_strio" do obj = mock("to_strio") - obj.should_receive(:to_strio).and_return(StringIO.new("to_strio")) + obj.should_receive(:to_strio).and_return(StringIO.new(+"to_strio")) @io.reopen(obj) @io.string.should == "to_strio" end @@ -208,40 +208,40 @@ # for details. describe "StringIO#reopen" do before :each do - @io = StringIO.new('hello','a') + @io = StringIO.new(+'hello', 'a') end # TODO: find out if this is really a bug it "reopens a stream when given a String argument" do - @io.reopen('goodbye').should == @io + @io.reopen(+'goodbye').should == @io @io.string.should == 'goodbye' @io << 'x' @io.string.should == 'xoodbye' end it "reopens a stream in append mode when flagged as such" do - @io.reopen('goodbye', 'a').should == @io + @io.reopen(+'goodbye', 'a').should == @io @io.string.should == 'goodbye' @io << 'x' @io.string.should == 'goodbyex' end it "reopens and truncate when reopened in write mode" do - @io.reopen('goodbye', 'wb').should == @io + @io.reopen(+'goodbye', 'wb').should == @io @io.string.should == '' @io << 'x' @io.string.should == 'x' end it "truncates the given string, not a copy" do - str = 'goodbye' + str = +'goodbye' @io.reopen(str, 'w') @io.string.should == '' str.should == '' end it "does not truncate the content even when the StringIO argument is in the truncate mode" do - orig_io = StringIO.new("Original StringIO", IO::RDWR|IO::TRUNC) + orig_io = StringIO.new(+"Original StringIO", IO::RDWR|IO::TRUNC) orig_io.write("BLAH") # make sure the content is not empty @io.reopen(orig_io) diff --git a/spec/ruby/library/stringio/shared/codepoints.rb b/spec/ruby/library/stringio/shared/codepoints.rb index 9d84aa491987f4..25333bb0fd2f52 100644 --- a/spec/ruby/library/stringio/shared/codepoints.rb +++ b/spec/ruby/library/stringio/shared/codepoints.rb @@ -27,7 +27,7 @@ @io.close_read -> { @enum.to_a }.should raise_error(IOError) - io = StringIO.new("xyz", "w") + io = StringIO.new(+"xyz", "w") -> { io.send(@method).to_a }.should raise_error(IOError) end diff --git a/spec/ruby/library/stringio/shared/each.rb b/spec/ruby/library/stringio/shared/each.rb index acd8d22c141a69..e0dd3f9b8f4a06 100644 --- a/spec/ruby/library/stringio/shared/each.rb +++ b/spec/ruby/library/stringio/shared/each.rb @@ -107,7 +107,7 @@ describe :stringio_each_not_readable, shared: true do it "raises an IOError" do - io = StringIO.new("a b c d e", "w") + io = StringIO.new(+"a b c d e", "w") -> { io.send(@method) { |b| b } }.should raise_error(IOError) io = StringIO.new("a b c d e") diff --git a/spec/ruby/library/stringio/shared/each_byte.rb b/spec/ruby/library/stringio/shared/each_byte.rb index 56734ff99d899d..b51fa38f2f5295 100644 --- a/spec/ruby/library/stringio/shared/each_byte.rb +++ b/spec/ruby/library/stringio/shared/each_byte.rb @@ -38,7 +38,7 @@ describe :stringio_each_byte_not_readable, shared: true do it "raises an IOError" do - io = StringIO.new("xyz", "w") + io = StringIO.new(+"xyz", "w") -> { io.send(@method) { |b| b } }.should raise_error(IOError) io = StringIO.new("xyz") diff --git a/spec/ruby/library/stringio/shared/each_char.rb b/spec/ruby/library/stringio/shared/each_char.rb index bcdac53282cd35..197237c1c85094 100644 --- a/spec/ruby/library/stringio/shared/each_char.rb +++ b/spec/ruby/library/stringio/shared/each_char.rb @@ -26,7 +26,7 @@ describe :stringio_each_char_not_readable, shared: true do it "raises an IOError" do - io = StringIO.new("xyz", "w") + io = StringIO.new(+"xyz", "w") -> { io.send(@method) { |b| b } }.should raise_error(IOError) io = StringIO.new("xyz") diff --git a/spec/ruby/library/stringio/shared/getc.rb b/spec/ruby/library/stringio/shared/getc.rb index 6318bcc30f3e3e..ba65040bce0798 100644 --- a/spec/ruby/library/stringio/shared/getc.rb +++ b/spec/ruby/library/stringio/shared/getc.rb @@ -33,7 +33,7 @@ describe :stringio_getc_not_readable, shared: true do it "raises an IOError" do - io = StringIO.new("xyz", "w") + io = StringIO.new(+"xyz", "w") -> { io.send(@method) }.should raise_error(IOError) io = StringIO.new("xyz") diff --git a/spec/ruby/library/stringio/shared/isatty.rb b/spec/ruby/library/stringio/shared/isatty.rb index 3da59999537fc0..c9e7ee73214cc7 100644 --- a/spec/ruby/library/stringio/shared/isatty.rb +++ b/spec/ruby/library/stringio/shared/isatty.rb @@ -1,5 +1,5 @@ describe :stringio_isatty, shared: true do it "returns false" do - StringIO.new('tty').send(@method).should be_false + StringIO.new("tty").send(@method).should be_false end end diff --git a/spec/ruby/library/stringio/shared/read.rb b/spec/ruby/library/stringio/shared/read.rb index 252a85d89d9042..e3840786d902fa 100644 --- a/spec/ruby/library/stringio/shared/read.rb +++ b/spec/ruby/library/stringio/shared/read.rb @@ -5,19 +5,19 @@ it "returns the passed buffer String" do # Note: Rubinius bug: - # @io.send(@method, 7, buffer = "").should equal(buffer) - ret = @io.send(@method, 7, buffer = "") + # @io.send(@method, 7, buffer = +"").should equal(buffer) + ret = @io.send(@method, 7, buffer = +"") ret.should equal(buffer) end it "reads length bytes and writes them to the buffer String" do - @io.send(@method, 7, buffer = "") + @io.send(@method, 7, buffer = +"") buffer.should == "example" end it "tries to convert the passed buffer Object to a String using #to_str" do obj = mock("to_str") - obj.should_receive(:to_str).and_return(buffer = "") + obj.should_receive(:to_str).and_return(buffer = +"") @io.send(@method, 7, obj) buffer.should == "example" @@ -75,7 +75,7 @@ describe :stringio_read_no_arguments, shared: true do before :each do - @io = StringIO.new("example") + @io = StringIO.new(+"example") end it "reads the whole content starting from the current position" do @@ -117,7 +117,7 @@ describe :stringio_read_not_readable, shared: true do it "raises an IOError" do - io = StringIO.new("test", "w") + io = StringIO.new(+"test", "w") -> { io.send(@method) }.should raise_error(IOError) io = StringIO.new("test") diff --git a/spec/ruby/library/stringio/shared/readchar.rb b/spec/ruby/library/stringio/shared/readchar.rb index 4248e754200fab..72d7446c362d15 100644 --- a/spec/ruby/library/stringio/shared/readchar.rb +++ b/spec/ruby/library/stringio/shared/readchar.rb @@ -19,7 +19,7 @@ describe :stringio_readchar_not_readable, shared: true do it "raises an IOError" do - io = StringIO.new("a b c d e", "w") + io = StringIO.new(+"a b c d e", "w") -> { io.send(@method) }.should raise_error(IOError) io = StringIO.new("a b c d e") diff --git a/spec/ruby/library/stringio/shared/write.rb b/spec/ruby/library/stringio/shared/write.rb index aa67bb73c721f4..404e08b93de745 100644 --- a/spec/ruby/library/stringio/shared/write.rb +++ b/spec/ruby/library/stringio/shared/write.rb @@ -1,6 +1,6 @@ describe :stringio_write, shared: true do before :each do - @io = StringIO.new('12345') + @io = StringIO.new(+'12345') end it "tries to convert the passed Object to a String using #to_s" do @@ -13,7 +13,7 @@ describe :stringio_write_string, shared: true do before :each do - @io = StringIO.new('12345') + @io = StringIO.new(+'12345') end # TODO: RDoc says that #write appends at the current position. @@ -106,10 +106,10 @@ describe :stringio_write_not_writable, shared: true do it "raises an IOError" do - io = StringIO.new("test", "r") + io = StringIO.new(+"test", "r") -> { io.send(@method, "test") }.should raise_error(IOError) - io = StringIO.new("test") + io = StringIO.new(+"test") io.close_write -> { io.send(@method, "test") }.should raise_error(IOError) end @@ -117,7 +117,7 @@ describe :stringio_write_append, shared: true do before :each do - @io = StringIO.new("example", "a") + @io = StringIO.new(+"example", "a") end it "appends the passed argument to the end of self" do diff --git a/spec/ruby/library/stringio/truncate_spec.rb b/spec/ruby/library/stringio/truncate_spec.rb index e8d7f1a15de984..592ca5a6e1854d 100644 --- a/spec/ruby/library/stringio/truncate_spec.rb +++ b/spec/ruby/library/stringio/truncate_spec.rb @@ -3,7 +3,7 @@ describe "StringIO#truncate when passed [length]" do before :each do - @io = StringIO.new('123456789') + @io = StringIO.new(+'123456789') end it "returns an Integer" do @@ -16,7 +16,7 @@ end it "does not create a copy of the underlying string" do - io = StringIO.new(str = "123456789") + io = StringIO.new(str = +"123456789") io.truncate(4) io.string.should equal(str) end @@ -52,10 +52,10 @@ describe "StringIO#truncate when self is not writable" do it "raises an IOError" do - io = StringIO.new("test", "r") + io = StringIO.new(+"test", "r") -> { io.truncate(2) }.should raise_error(IOError) - io = StringIO.new("test") + io = StringIO.new(+"test") io.close_write -> { io.truncate(2) }.should raise_error(IOError) end diff --git a/spec/ruby/library/stringio/ungetc_spec.rb b/spec/ruby/library/stringio/ungetc_spec.rb index 91ef2100a12db0..bceafa79ffa007 100644 --- a/spec/ruby/library/stringio/ungetc_spec.rb +++ b/spec/ruby/library/stringio/ungetc_spec.rb @@ -3,7 +3,7 @@ describe "StringIO#ungetc when passed [char]" do before :each do - @io = StringIO.new('1234') + @io = StringIO.new(+'1234') end it "writes the passed char before the current position" do @@ -45,11 +45,11 @@ describe "StringIO#ungetc when self is not readable" do it "raises an IOError" do - io = StringIO.new("test", "w") + io = StringIO.new(+"test", "w") io.pos = 1 -> { io.ungetc(?A) }.should raise_error(IOError) - io = StringIO.new("test") + io = StringIO.new(+"test") io.pos = 1 io.close_read -> { io.ungetc(?A) }.should raise_error(IOError) @@ -60,11 +60,11 @@ # # describe "StringIO#ungetc when self is not writable" do # it "raises an IOError" do -# io = StringIO.new("test", "r") +# io = StringIO.new(+"test", "r") # io.pos = 1 # lambda { io.ungetc(?A) }.should raise_error(IOError) # -# io = StringIO.new("test") +# io = StringIO.new(+"test") # io.pos = 1 # io.close_write # lambda { io.ungetc(?A) }.should raise_error(IOError) diff --git a/spec/ruby/library/stringio/write_nonblock_spec.rb b/spec/ruby/library/stringio/write_nonblock_spec.rb index a457b976679ee6..b48ef6698aa82e 100644 --- a/spec/ruby/library/stringio/write_nonblock_spec.rb +++ b/spec/ruby/library/stringio/write_nonblock_spec.rb @@ -10,7 +10,7 @@ it_behaves_like :stringio_write_string, :write_nonblock it "accepts :exception option" do - io = StringIO.new("12345", "a") + io = StringIO.new(+"12345", "a") io.write_nonblock("67890", exception: true) io.string.should == "1234567890" end diff --git a/spec/ruby/library/stringscanner/getch_spec.rb b/spec/ruby/library/stringscanner/getch_spec.rb index a6be0d4221c833..449c20ad3b0f5c 100644 --- a/spec/ruby/library/stringscanner/getch_spec.rb +++ b/spec/ruby/library/stringscanner/getch_spec.rb @@ -13,7 +13,7 @@ it "is multi-byte character sensitive" do # Japanese hiragana "A" in EUC-JP - src = "\244\242".force_encoding("euc-jp") + src = "\244\242".dup.force_encoding("euc-jp") s = StringScanner.new(src) s.getch.should == src diff --git a/spec/ruby/library/stringscanner/shared/concat.rb b/spec/ruby/library/stringscanner/shared/concat.rb index cb884a5c01a6ca..1dbae11f7cc799 100644 --- a/spec/ruby/library/stringscanner/shared/concat.rb +++ b/spec/ruby/library/stringscanner/shared/concat.rb @@ -1,6 +1,6 @@ describe :strscan_concat, shared: true do it "concatenates the given argument to self and returns self" do - s = StringScanner.new("hello ") + s = StringScanner.new(+"hello ") s.send(@method, 'world').should == s s.string.should == "hello world" s.eos?.should be_false diff --git a/spec/ruby/library/stringscanner/string_spec.rb b/spec/ruby/library/stringscanner/string_spec.rb index 28e2f0ed373a6e..cba6bd51dd6cc8 100644 --- a/spec/ruby/library/stringscanner/string_spec.rb +++ b/spec/ruby/library/stringscanner/string_spec.rb @@ -3,7 +3,7 @@ describe "StringScanner#string" do before :each do - @string = "This is a test" + @string = +"This is a test" @s = StringScanner.new(@string) end diff --git a/spec/ruby/library/zlib/deflate/deflate_spec.rb b/spec/ruby/library/zlib/deflate/deflate_spec.rb index 50a563ef6f4bb8..e16e6ad0ef186b 100644 --- a/spec/ruby/library/zlib/deflate/deflate_spec.rb +++ b/spec/ruby/library/zlib/deflate/deflate_spec.rb @@ -23,7 +23,7 @@ it "deflates chunked data" do random_generator = Random.new(0) - deflated = '' + deflated = +'' Zlib::Deflate.deflate(random_generator.bytes(20000)) do |chunk| deflated << chunk @@ -70,7 +70,7 @@ before :each do @deflator = Zlib::Deflate.new @random_generator = Random.new(0) - @original = '' + @original = +'' @chunks = [] end diff --git a/spec/ruby/library/zlib/deflate/params_spec.rb b/spec/ruby/library/zlib/deflate/params_spec.rb index 0b1cca8c8a1f71..0242653528b746 100644 --- a/spec/ruby/library/zlib/deflate/params_spec.rb +++ b/spec/ruby/library/zlib/deflate/params_spec.rb @@ -3,7 +3,7 @@ describe "Zlib::Deflate#params" do it "changes the deflate parameters" do - data = 'abcdefghijklm' + data = +'abcdefghijklm' d = Zlib::Deflate.new Zlib::NO_COMPRESSION, Zlib::MAX_WBITS, Zlib::DEF_MEM_LEVEL, Zlib::DEFAULT_STRATEGY diff --git a/spec/ruby/library/zlib/inflate/inflate_spec.rb b/spec/ruby/library/zlib/inflate/inflate_spec.rb index 79b72bf91c821f..b308a4ba67b8ce 100644 --- a/spec/ruby/library/zlib/inflate/inflate_spec.rb +++ b/spec/ruby/library/zlib/inflate/inflate_spec.rb @@ -72,7 +72,7 @@ data = [120, 156, 75, 203, 207, 7, 0, 2, 130, 1, 69].pack('C*') z = Zlib::Inflate.new # add bytes, one by one - result = "" + result = +"" data.each_byte { |d| result << z.inflate(d.chr)} result << z.finish result.should == "foo" @@ -82,7 +82,7 @@ data = [120, 156, 75, 203, 207, 7, 0, 2, 130, 1, 69].pack('C*')[0,5] z = Zlib::Inflate.new # add bytes, one by one, but not all - result = "" + result = +"" data.each_byte { |d| result << z.inflate(d.chr)} -> { result << z.finish }.should raise_error(Zlib::BufError) end @@ -90,7 +90,7 @@ it "properly handles excessive data, byte-by-byte" do main_data = [120, 156, 75, 203, 207, 7, 0, 2, 130, 1, 69].pack('C*') data = main_data * 2 - result = "" + result = +"" z = Zlib::Inflate.new # add bytes, one by one @@ -105,7 +105,7 @@ it "properly handles excessive data, in one go" do main_data = [120, 156, 75, 203, 207, 7, 0, 2, 130, 1, 69].pack('C*') data = main_data * 2 - result = "" + result = +"" z = Zlib::Inflate.new result << z.inflate(data) diff --git a/spec/ruby/optional/capi/encoding_spec.rb b/spec/ruby/optional/capi/encoding_spec.rb index b0c38d75a93538..1529e012b0a466 100644 --- a/spec/ruby/optional/capi/encoding_spec.rb +++ b/spec/ruby/optional/capi/encoding_spec.rb @@ -1,4 +1,5 @@ # -*- encoding: utf-8 -*- +# frozen_string_literal: false require_relative 'spec_helper' require_relative 'fixtures/encoding' diff --git a/spec/ruby/optional/capi/ext/gc_spec.c b/spec/ruby/optional/capi/ext/gc_spec.c index 7dd0b876550ec4..1392bc6ee668da 100644 --- a/spec/ruby/optional/capi/ext/gc_spec.c +++ b/spec/ruby/optional/capi/ext/gc_spec.c @@ -8,7 +8,12 @@ extern "C" { VALUE registered_tagged_value; VALUE registered_reference_value; VALUE registered_before_rb_gc_register_address; -VALUE registered_before_rb_global_variable; +VALUE registered_before_rb_global_variable_string; +VALUE registered_before_rb_global_variable_bignum; +VALUE registered_before_rb_global_variable_float; +VALUE registered_after_rb_global_variable_string; +VALUE registered_after_rb_global_variable_bignum; +VALUE registered_after_rb_global_variable_float; VALUE rb_gc_register_address_outside_init; static VALUE registered_tagged_address(VALUE self) { @@ -23,8 +28,28 @@ static VALUE get_registered_before_rb_gc_register_address(VALUE self) { return registered_before_rb_gc_register_address; } -static VALUE get_registered_before_rb_global_variable(VALUE self) { - return registered_before_rb_global_variable; +static VALUE get_registered_before_rb_global_variable_string(VALUE self) { + return registered_before_rb_global_variable_string; +} + +static VALUE get_registered_before_rb_global_variable_bignum(VALUE self) { + return registered_before_rb_global_variable_bignum; +} + +static VALUE get_registered_before_rb_global_variable_float(VALUE self) { + return registered_before_rb_global_variable_float; +} + +static VALUE get_registered_after_rb_global_variable_string(VALUE self) { + return registered_after_rb_global_variable_string; +} + +static VALUE get_registered_after_rb_global_variable_bignum(VALUE self) { + return registered_after_rb_global_variable_bignum; +} + +static VALUE get_registered_after_rb_global_variable_float(VALUE self) { + return registered_after_rb_global_variable_float; } static VALUE gc_spec_rb_gc_register_address(VALUE self) { @@ -71,17 +96,34 @@ void Init_gc_spec(void) { rb_gc_register_address(®istered_tagged_value); rb_gc_register_address(®istered_reference_value); rb_gc_register_address(®istered_before_rb_gc_register_address); - rb_global_variable(®istered_before_rb_global_variable); + rb_global_variable(®istered_before_rb_global_variable_string); + rb_global_variable(®istered_before_rb_global_variable_bignum); + rb_global_variable(®istered_before_rb_global_variable_float); registered_tagged_value = INT2NUM(10); registered_reference_value = rb_str_new2("Globally registered data"); registered_before_rb_gc_register_address = rb_str_new_cstr("registered before rb_gc_register_address()"); - registered_before_rb_global_variable = rb_str_new_cstr("registered before rb_global_variable()"); + + registered_before_rb_global_variable_string = rb_str_new_cstr("registered before rb_global_variable()"); + registered_before_rb_global_variable_bignum = LL2NUM(INT64_MAX); + registered_before_rb_global_variable_float = DBL2NUM(3.14); + + registered_after_rb_global_variable_string = rb_str_new_cstr("registered after rb_global_variable()"); + rb_global_variable(®istered_after_rb_global_variable_string); + registered_after_rb_global_variable_bignum = LL2NUM(INT64_MAX); + rb_global_variable(®istered_after_rb_global_variable_bignum); + registered_after_rb_global_variable_float = DBL2NUM(6.28); + rb_global_variable(®istered_after_rb_global_variable_float); rb_define_method(cls, "registered_tagged_address", registered_tagged_address, 0); rb_define_method(cls, "registered_reference_address", registered_reference_address, 0); rb_define_method(cls, "registered_before_rb_gc_register_address", get_registered_before_rb_gc_register_address, 0); - rb_define_method(cls, "registered_before_rb_global_variable", get_registered_before_rb_global_variable, 0); + rb_define_method(cls, "registered_before_rb_global_variable_string", get_registered_before_rb_global_variable_string, 0); + rb_define_method(cls, "registered_before_rb_global_variable_bignum", get_registered_before_rb_global_variable_bignum, 0); + rb_define_method(cls, "registered_before_rb_global_variable_float", get_registered_before_rb_global_variable_float, 0); + rb_define_method(cls, "registered_after_rb_global_variable_string", get_registered_after_rb_global_variable_string, 0); + rb_define_method(cls, "registered_after_rb_global_variable_bignum", get_registered_after_rb_global_variable_bignum, 0); + rb_define_method(cls, "registered_after_rb_global_variable_float", get_registered_after_rb_global_variable_float, 0); rb_define_method(cls, "rb_gc_register_address", gc_spec_rb_gc_register_address, 0); rb_define_method(cls, "rb_gc_unregister_address", gc_spec_rb_gc_unregister_address, 0); rb_define_method(cls, "rb_gc_enable", gc_spec_rb_gc_enable, 0); diff --git a/spec/ruby/optional/capi/ext/object_spec.c b/spec/ruby/optional/capi/ext/object_spec.c index 7023c29bdb3184..8aa98cc5ce24c1 100644 --- a/spec/ruby/optional/capi/ext/object_spec.c +++ b/spec/ruby/optional/capi/ext/object_spec.c @@ -179,11 +179,7 @@ static VALUE object_spec_rb_method_boundp(VALUE self, VALUE obj, VALUE method, V } static VALUE object_spec_rb_special_const_p(VALUE self, VALUE value) { - if (rb_special_const_p(value)) { - return Qtrue; - } else { - return Qfalse; - } + return rb_special_const_p(value); } static VALUE so_to_id(VALUE self, VALUE obj) { @@ -388,16 +384,8 @@ static VALUE object_spec_rb_ivar_foreach(VALUE self, VALUE obj) { } static VALUE speced_allocator(VALUE klass) { - VALUE flags = 0; - VALUE instance; - if (RTEST(rb_class_inherited_p(klass, rb_cString))) { - flags = T_STRING; - } else if (RTEST(rb_class_inherited_p(klass, rb_cArray))) { - flags = T_ARRAY; - } else { - flags = T_OBJECT; - } - instance = rb_newobj_of(klass, flags); + VALUE super = rb_class_get_superclass(klass); + VALUE instance = rb_get_alloc_func(super)(klass); rb_iv_set(instance, "@from_custom_allocator", Qtrue); return instance; } diff --git a/spec/ruby/optional/capi/file_spec.rb b/spec/ruby/optional/capi/file_spec.rb index 96d731e4fa24f0..12449b4e34d7d9 100644 --- a/spec/ruby/optional/capi/file_spec.rb +++ b/spec/ruby/optional/capi/file_spec.rb @@ -69,7 +69,7 @@ end it "does not call #to_str on a String" do - obj = "path" + obj = +"path" obj.should_not_receive(:to_str) @s.FilePathValue(obj).should eql(obj) end diff --git a/spec/ruby/optional/capi/gc_spec.rb b/spec/ruby/optional/capi/gc_spec.rb index d76ea7394f304e..aaced56483a0cc 100644 --- a/spec/ruby/optional/capi/gc_spec.rb +++ b/spec/ruby/optional/capi/gc_spec.rb @@ -27,9 +27,36 @@ end describe "rb_global_variable" do - it "keeps the value alive even if the value is assigned after rb_global_variable() is called" do + before :all do GC.start - @f.registered_before_rb_global_variable.should == "registered before rb_global_variable()" + end + + describe "keeps the value alive even if the value is assigned after rb_global_variable() is called" do + it "for a string" do + @f.registered_before_rb_global_variable_string.should == "registered before rb_global_variable()" + end + + it "for a bignum" do + @f.registered_before_rb_global_variable_bignum.should == 2**63 - 1 + end + + it "for a Float" do + @f.registered_before_rb_global_variable_float.should == 3.14 + end + end + + describe "keeps the value alive when the value is assigned before rb_global_variable() is called" do + it "for a string" do + @f.registered_after_rb_global_variable_string.should == "registered after rb_global_variable()" + end + + it "for a bignum" do + @f.registered_after_rb_global_variable_bignum.should == 2**63 - 1 + end + + it "for a Float" do + @f.registered_after_rb_global_variable_float.should == 6.28 + end end end diff --git a/spec/ruby/optional/capi/object_spec.rb b/spec/ruby/optional/capi/object_spec.rb index 6ee6d65680f52e..7bc7bd992aaa91 100644 --- a/spec/ruby/optional/capi/object_spec.rb +++ b/spec/ruby/optional/capi/object_spec.rb @@ -686,7 +686,7 @@ def reach end it "returns false if object passed to it is not frozen" do - obj = "" + obj = +"" @o.rb_obj_frozen_p(obj).should == false end end @@ -700,7 +700,7 @@ def reach end it "does nothing when object isn't frozen" do - obj = "" + obj = +"" -> { @o.rb_check_frozen(obj) }.should_not raise_error(TypeError) end end @@ -894,9 +894,9 @@ def reach describe "rb_copy_generic_ivar for objects which do not store ivars directly" do it "copies the instance variables from one object to another" do - original = "abc" + original = +"abc" original.instance_variable_set(:@foo, :bar) - clone = "def" + clone = +"def" @o.rb_copy_generic_ivar(clone, original) clone.instance_variable_get(:@foo).should == :bar end @@ -904,7 +904,7 @@ def reach describe "rb_free_generic_ivar for objects which do not store ivars directly" do it "removes the instance variables from an object" do - o = "abc" + o = +"abc" o.instance_variable_set(:@baz, :flibble) @o.rb_free_generic_ivar(o) o.instance_variables.should == [] diff --git a/spec/ruby/optional/capi/string_spec.rb b/spec/ruby/optional/capi/string_spec.rb index 378bf7323f1d25..a8edf998b54b25 100644 --- a/spec/ruby/optional/capi/string_spec.rb +++ b/spec/ruby/optional/capi/string_spec.rb @@ -1,4 +1,5 @@ # encoding: utf-8 +# frozen_string_literal: false require_relative 'spec_helper' require_relative '../../shared/string/times' @@ -47,7 +48,7 @@ def inspect [Encoding::BINARY, Encoding::UTF_8].each do |enc| describe "rb_str_set_len on a #{enc.name} String" do before :each do - @str = "abcdefghij".force_encoding(enc) + @str = "abcdefghij".dup.force_encoding(enc) # Make sure to unshare the string @s.rb_str_modify(@str) end @@ -99,7 +100,7 @@ def inspect describe "rb_str_set_len on a UTF-16 String" do before :each do - @str = "abcdefghij".force_encoding(Encoding::UTF_16BE) + @str = "abcdefghij".dup.force_encoding(Encoding::UTF_16BE) # Make sure to unshare the string @s.rb_str_modify(@str) end @@ -112,7 +113,7 @@ def inspect describe "rb_str_set_len on a UTF-32 String" do before :each do - @str = "abcdefghijkl".force_encoding(Encoding::UTF_32BE) + @str = "abcdefghijkl".dup.force_encoding(Encoding::UTF_32BE) # Make sure to unshare the string @s.rb_str_modify(@str) end @@ -231,7 +232,7 @@ def inspect describe "rb_usascii_str_new" do it "creates a new String with US-ASCII Encoding from a char buffer of len characters" do - str = "abc".force_encoding("us-ascii") + str = "abc".dup.force_encoding("us-ascii") result = @s.rb_usascii_str_new("abcdef", 3) result.should == str result.encoding.should == Encoding::US_ASCII @@ -247,14 +248,14 @@ def inspect it "returns US-ASCII string for non-US-ASCII string literal" do str = @s.rb_usascii_str_new_lit_non_ascii - str.should == "r\xC3\xA9sum\xC3\xA9".force_encoding(Encoding::US_ASCII) + str.should == "r\xC3\xA9sum\xC3\xA9".dup.force_encoding(Encoding::US_ASCII) str.encoding.should == Encoding::US_ASCII end end describe "rb_usascii_str_new_cstr" do it "creates a new String with US-ASCII Encoding" do - str = "abc".force_encoding("us-ascii") + str = "abc".dup.force_encoding("us-ascii") result = @s.rb_usascii_str_new_cstr("abc") result.should == str result.encoding.should == Encoding::US_ASCII @@ -418,7 +419,7 @@ def inspect describe "rb_enc_str_buf_cat" do it "concatenates a C string literal to a ruby string with the given encoding" do - input = "hello ".force_encoding(Encoding::US_ASCII) + input = "hello ".dup.force_encoding(Encoding::US_ASCII) result = @s.rb_enc_str_buf_cat(input, "résumé", Encoding::UTF_8) result.should == "hello résumé" result.encoding.should == Encoding::UTF_8 @@ -500,8 +501,8 @@ def inspect describe "rb_str_subseq" do it "returns a byte-indexed substring" do - str = "\x00\x01\x02\x03\x04".force_encoding("binary") - @s.rb_str_subseq(str, 1, 2).should == "\x01\x02".force_encoding("binary") + str = "\x00\x01\x02\x03\x04".dup.force_encoding("binary") + @s.rb_str_subseq(str, 1, 2).should == "\x01\x02".dup.force_encoding("binary") end end @@ -712,7 +713,7 @@ def inspect end it "increases the size of the string" do - expected = "test".force_encoding("US-ASCII") + expected = "test".dup.force_encoding("US-ASCII") str = @s.rb_str_resize(expected.dup, 12) str.size.should == 12 str.bytesize.should == 12 @@ -843,11 +844,11 @@ def inspect # it "transcodes a String to Encoding.default_internal if it is set" do # Encoding.default_internal = Encoding::EUC_JP # -# - a = "\xE3\x81\x82\xe3\x82\x8c".force_encoding("utf-8") +# - a = "\xE3\x81\x82\xe3\x82\x8c".dup.force_encoding("utf-8") # + a = [0xE3, 0x81, 0x82, 0xe3, 0x82, 0x8c].pack('C6').force_encoding("utf-8") # s = @s.rb_external_str_new_with_enc(a, a.bytesize, Encoding::UTF_8) # - -# - s.should == "\xA4\xA2\xA4\xEC".force_encoding("euc-jp") +# - s.should == "\xA4\xA2\xA4\xEC".dup.force_encoding("euc-jp") # + x = [0xA4, 0xA2, 0xA4, 0xEC].pack('C4')#.force_encoding('binary') # + s.should == x # s.encoding.should equal(Encoding::EUC_JP) @@ -867,7 +868,7 @@ def inspect describe "rb_locale_str_new" do it "returns a String with 'locale' encoding" do s = @s.rb_locale_str_new("abc", 3) - s.should == "abc".force_encoding(Encoding.find("locale")) + s.should == "abc".dup.force_encoding(Encoding.find("locale")) s.encoding.should equal(Encoding.find("locale")) end end @@ -875,14 +876,14 @@ def inspect describe "rb_locale_str_new_cstr" do it "returns a String with 'locale' encoding" do s = @s.rb_locale_str_new_cstr("abc") - s.should == "abc".force_encoding(Encoding.find("locale")) + s.should == "abc".dup.force_encoding(Encoding.find("locale")) s.encoding.should equal(Encoding.find("locale")) end end describe "rb_str_conv_enc" do it "returns the original String when to encoding is not specified" do - a = "abc".force_encoding("us-ascii") + a = "abc".dup.force_encoding("us-ascii") @s.rb_str_conv_enc(a, Encoding::US_ASCII, nil).should equal(a) end @@ -892,7 +893,7 @@ def inspect end it "returns a transcoded String" do - a = "\xE3\x81\x82\xE3\x82\x8C".force_encoding("utf-8") + a = "\xE3\x81\x82\xE3\x82\x8C".dup.force_encoding("utf-8") result = @s.rb_str_conv_enc(a, Encoding::UTF_8, Encoding::EUC_JP) x = [0xA4, 0xA2, 0xA4, 0xEC].pack('C4').force_encoding('utf-8') result.should == x.force_encoding("euc-jp") @@ -901,7 +902,7 @@ def inspect describe "when the String encoding is equal to the destination encoding" do it "returns the original String" do - a = "abc".force_encoding("us-ascii") + a = "abc".dup.force_encoding("us-ascii") @s.rb_str_conv_enc(a, Encoding::US_ASCII, Encoding::US_ASCII).should equal(a) end @@ -911,7 +912,7 @@ def inspect end it "returns the origin String if the destination encoding is BINARY" do - a = "abc".force_encoding("binary") + a = "abc".dup.force_encoding("binary") @s.rb_str_conv_enc(a, Encoding::US_ASCII, Encoding::BINARY).should equal(a) end end @@ -919,7 +920,7 @@ def inspect describe "rb_str_conv_enc_opts" do it "returns the original String when to encoding is not specified" do - a = "abc".force_encoding("us-ascii") + a = "abc".dup.force_encoding("us-ascii") @s.rb_str_conv_enc_opts(a, Encoding::US_ASCII, nil, 0, nil).should equal(a) end @@ -930,7 +931,7 @@ def inspect end it "returns a transcoded String" do - a = "\xE3\x81\x82\xE3\x82\x8C".force_encoding("utf-8") + a = "\xE3\x81\x82\xE3\x82\x8C".dup.force_encoding("utf-8") result = @s.rb_str_conv_enc_opts(a, Encoding::UTF_8, Encoding::EUC_JP, 0, nil) x = [0xA4, 0xA2, 0xA4, 0xEC].pack('C4').force_encoding('utf-8') result.should == x.force_encoding("euc-jp") @@ -939,7 +940,7 @@ def inspect describe "when the String encoding is equal to the destination encoding" do it "returns the original String" do - a = "abc".force_encoding("us-ascii") + a = "abc".dup.force_encoding("us-ascii") @s.rb_str_conv_enc_opts(a, Encoding::US_ASCII, Encoding::US_ASCII, 0, nil).should equal(a) end @@ -951,7 +952,7 @@ def inspect end it "returns the origin String if the destination encoding is BINARY" do - a = "abc".force_encoding("binary") + a = "abc".dup.force_encoding("binary") @s.rb_str_conv_enc_opts(a, Encoding::US_ASCII, Encoding::BINARY, 0, nil).should equal(a) end @@ -969,7 +970,7 @@ def inspect describe "rb_str_export_locale" do it "returns the original String with the locale encoding" do s = @s.rb_str_export_locale("abc") - s.should == "abc".force_encoding(Encoding.find("locale")) + s.should == "abc".dup.force_encoding(Encoding.find("locale")) s.encoding.should equal(Encoding.find("locale")) end end @@ -1262,8 +1263,8 @@ def inspect end it "returns different frozen strings for different encodings" do - result1 = @s.rb_str_to_interned_str("hello".force_encoding(Encoding::US_ASCII)) - result2 = @s.rb_str_to_interned_str("hello".force_encoding(Encoding::UTF_8)) + result1 = @s.rb_str_to_interned_str("hello".dup.force_encoding(Encoding::US_ASCII)) + result2 = @s.rb_str_to_interned_str("hello".dup.force_encoding(Encoding::UTF_8)) result1.should_not.equal?(result2) end diff --git a/spec/ruby/optional/capi/util_spec.rb b/spec/ruby/optional/capi/util_spec.rb index 2c16999cdc3589..9ff8b4760a83f7 100644 --- a/spec/ruby/optional/capi/util_spec.rb +++ b/spec/ruby/optional/capi/util_spec.rb @@ -48,7 +48,7 @@ ScratchPad.recorded.should == [1, 2, [3, 4]] end - it "assigns the required and optional arguments and and empty Array when there are no arguments to splat" do + it "assigns the required and optional arguments and empty Array when there are no arguments to splat" do @o.rb_scan_args([1, 2], "11*", 3, @acc).should == 2 ScratchPad.recorded.should == [1, 2, []] end diff --git a/spec/ruby/security/cve_2010_1330_spec.rb b/spec/ruby/security/cve_2010_1330_spec.rb index 33e88d652ee6e8..25944395500adb 100644 --- a/spec/ruby/security/cve_2010_1330_spec.rb +++ b/spec/ruby/security/cve_2010_1330_spec.rb @@ -8,7 +8,7 @@ # #gsub on a string in the UTF-8 encoding but with invalid an UTF-8 byte # sequence. - str = "\xF6